[syslinux] [PATCH 4/4] chain.c: New functionality

Michal Soltys soltys at ziu.info
Mon Jul 26 15:50:35 PDT 2010


1) Updated seg and segbs options to control load and jump addresses precisely.
2) Carefully check overlapping regions.
3) bsnomap option
4) Update all win/dos related options.
5) setgeometry option
6) setdrive[@<offset>] option
7) setbpb shortcut option
8) writebs, filebpb options

Signed-off-by: Michal Soltys <soltys at ziu.info>
---
 com32/modules/chain.c |  498 ++++++++++++++++++++++++++++++++++++++-----------
 1 files changed, 389 insertions(+), 109 deletions(-)

diff --git a/com32/modules/chain.c b/com32/modules/chain.c
index 6c3bb64..82654cf 100644
--- a/com32/modules/chain.c
+++ b/com32/modules/chain.c
@@ -45,12 +45,28 @@
  *
  * Options:
  *
+ * segbs=<seg[:off[:ip]]>
+ *	main control of where the sector (boot sector or mbr) should be loaded
+ *	at, and where to actually jump. Default value is 0:7C00:7C00. Numbers
+ *	are parsed as *HEX* values. Of course, if you set custom segbs without
+ *	specifying some of the values (or leaving empty field near colon),
+ *	unspecified ones are assumed 0. Overall, sector is loaded at seg:off,
+ *	and the jump is made to seg:ip.
+ *
+ * seg=<seg[:off[:ip]]>
+ *	similary to segbs option, this controls where to load file (if
+ *	applicable - see file= option and derivatives). File takes precedence
+ *	over boot sector, so in case their areas overlap, only file will be
+ *	loaded.
+ *
+ * bsnomap
+ *	load sector, but don't map it into real memory before chainloading.
+ *	Useful for fixing BPB values in DOS cases.
+ *
  * file=<loader>
  *	loads the file <loader> **from the Syslinux filesystem**
- *	instead of loading the boot sector.
- *
- * seg=<segment>
- *	loads at and jumps to <seg>:0000 instead of 0000:7C00.
+ *	Boot sector will be loaded as well, if possible. Then handover area
+ *	will be prepared - again - if possible.
  *
  * isolinux=<loader>
  *	chainload another version/build of the ISOLINUX bootloader and patch
@@ -59,26 +75,58 @@
  *	when you want more than one ISOLINUX per CD/DVD.
  *
  * ntldr=<loader>
- *	equivalent to seg=0x2000 file=<loader> sethidden,
- *	used with WinNT's loaders
+ *	equivalent of file=<loader> seg=2000 setbpb setdrive writebs,
+ *	used with WinNT's loaders. This is a bit interesting, actually
+ *	required BPB adjustments are:
+ *
+ *	- valid drive unit (which also has to be 0x80)
+ *	- sethidden value in memory matching the one written to the disk
+ *
+ *	Geometry settings stored in boot sector seems to be ignored, "hidden
+ *	sectors" /can/ be junk, as long as on-disk and in-memory values match.
+ *	To boot from other disks, you will have to use swap option. If by some
+ *	miracle you still have some NTish system on FAT32, add setdrive at 40 to
+ *	override default 0x24.
+ *
+ *	*NOTE* though - if you plan to boot windows directly through its
+ *	bootsector (without loading file), you will need ALL BPB fields valid
+ *	and written to the disk (for partitions starting under 8GB, bootsector
+ *	and possiby $Boot insist on using CHS functions ...), e.g.
+ *	chain.c32 hd2 2 setbpb setdrive swap writebs.
  *
  * cmldr=<loader>
  *	used with Recovery Console of Windows NT/2K/XP.
- *	same as ntldr=<loader> & "cmdcons\0" written to
- *	the system name field in the bootsector
+ *	same as ntldr=<loader> with "cmdcons\0" written to
+ *	the system name field in the boot sector
  *
  * freedos=<loader>
- *	equivalent to seg=0x60 file=<loader> sethidden,
- *	used with FreeDOS' kernel.sys.
+ *	equivalent to file=<loader> seg=60 setbpb writebs bsnomap,
+ *	used with FreeDOS' kernel.sys. FreeDOS requires proper geometry written
+ *	to the disk. BPB's fields "hidden sectors" and "drive unit" seem to be
+ *	ignored - FreeDOS rescans all fixed disks and assigns drive letters
+ *	accordingly.
+ *
+ *	If you want C: to match the disk you're booting from, you will likely
+ *	have to use swap option - possibly with hide option, if you have other
+ *	primary partitions before the one you're booting from.  You can add
+ *	appropriate setdrive@ option to keep things nice and tidy, depending on
+ *	your filesystem.
  *
  * msdos=<loader>
  * pcdos=<loader>
- *	equivalent to seg=0x70 file=<loader> sethidden,
- *	used with DOS' io.sys.
+ *	equivalent to file=<loader> seg=70 setbpb writebs bsnomap,
+ *	used with DOS' io.sys. Comments the same as in freedos= case.
+ *	TODO: test with live M$ dos system(s), not clones, to check exact
+ *	behaviour.
+ *
+ * msdos7=<loader>
+ *	equivalent to file=<loader> seg=70::200 setbpb writebs bsnomap,
+ *	DOS 7+ bootsectors jump to 0x200 after loading io.sys, instead of 0 as
+ *	it was in pre-win95 age.
  *
  * grub=<loader>
- *	same as seg=0x800 file=<loader> & jumping to seg 0x820,
- *	used with GRUB Legacy stage2 files.
+ *	same as file=<loader> seg=800::200, used with GRUB Legacy stage2
+ *	files.
  *
  * grubcfg=<filename>
  *	set an alternative config filename in stage2 of Grub Legacy,
@@ -95,11 +143,38 @@
  * hide
  *	change type of primary partitions with IDs 01, 04, 06, 07,
  *	0b, 0c, or 0e to 1x, except for the selected partition, which
- *	is converted the other way.
+ *	is converted the other way. Currently this works only within
+ *	boundaries of primary mbr (partitions 1 - 4).
  *
  * sethidden
- *	update the "hidden sectors" (partition offset) field in a
- *	FAT/NTFS boot sector.
+ *	update the "hidden sectors" (partition offset) field in a FAT/NTFS boot
+ *	sector.
+ *
+ * setgeometry
+ *	update the "sectors per track" and "heads" fields in a FAT/NTFS boot
+ *	sector.
+ *
+ * setdrive[@<offset>]
+ *	update the "drive unit" field in a FAT/NTFS boot sector. Offset should
+ *	be either 0x24 (FAT/NTFS excluding FAT32) or 0x40 (FAT32 only) -
+ *	chainloader won't accept other values. Offset is parsed as a *HEX*
+ *	number. Offsetless value defaults to 0x24.
+ *
+ * setbpb
+ *	A shortcut that enables set{hidden,geometry} options;
+ *	setdrive is not covered by this shortcut, due to the ambiguity.
+ *
+ * writebs
+ *	Write updated boot sector to the disk. This is performed only
+ *	if some of the BPB's fields actually changed.
+ *
+ * filebpb
+ *	An option that let loaded file be treated as BPB compatible. If any of
+ *	the previous set* options is specified and file is being loaded, BPB at
+ *	appropriate offsets will be adjusted accordingly. Obviously, writebs
+ *	options is ignored for file. It can be used for crude sort-of emulation
+ *	of syslinux's native .BSS capability, where BPB patching is limited only
+ *	to options specified by set* (but it reflects actual BIOS imaginations).
  *
  * keeppxe
  *	keep the PXE and UNDI stacks in memory (PXELINUX only).
@@ -120,11 +195,19 @@
 #include <syslinux/video.h>
 
 #define SECTOR 512		/* bytes/sector */
+#define ADDRMAX 0x9F000
+#define ADDRMIN 0x500
 
 static struct options {
     const char *loadfile;
+    uint32_t file_lin;
+    uint16_t file_seg;
+    uint16_t file_ip;
+    uint32_t sect_lin;
+    uint16_t sect_seg;
+    uint16_t sect_ip;
+    uint32_t drvoff;
     uint16_t keeppxe;
-    uint16_t seg;
     bool isolinux;
     bool cmldr;
     bool grub;
@@ -133,6 +216,10 @@ static struct options {
     bool swap;
     bool hide;
     bool sethidden;
+    bool setgeometry;
+    bool writebs;
+    bool filebpb;
+    bool bsnomap;
 } opt;
 
 struct data_area {
@@ -173,8 +260,8 @@ struct diskinfo {
     int disk;
     int ebios;			/* EBIOS supported on this disk */
     int cbios;			/* CHS geometry is valid */
-    int head;
-    int sect;
+    unsigned int head;
+    unsigned int sect;
 } disk_info;
 
 static int get_disk_params(int disk)
@@ -1272,89 +1359,158 @@ Usage:   chain.c32 [options]\n\
          chain.c32 guid:<guid> [<partition>] [options]\n\
          chain.c32 label:<label> [<partition>] [options]\n\
          chain.c32 boot [<partition>] [options]\n\
-         chain.c32 fs [options]\n\
-Options: file=<loader>      Load and execute file, instead of boot sector\n\
-         isolinux=<loader>  Load another version of ISOLINUX\n\
-         ntldr=<loader>     Load Windows NTLDR, SETUPLDR.BIN or BOOTMGR\n\
-         cmldr=<loader>     Load Recovery Console of Windows NT/2K/XP/2003\n\
-         freedos=<loader>   Load FreeDOS KERNEL.SYS\n\
-         msdos=<loader>     Load MS-DOS IO.SYS\n\
-         pcdos=<loader>     Load PC-DOS IBMBIO.COM\n\
-         grub=<loader>      Load GRUB Legacy stage2\n\
-         grubcfg=<filename> Set alternative config filename for GRUB Legacy\n\
-         grldr=<loader>     Load GRUB4DOS grldr\n\
-         seg=<segment>      Jump to <seg>:0000, instead of 0000:7C00\n\
-         swap               Swap drive numbers, if bootdisk is not fd0/hd0\n\
-         hide               Hide primary partitions, except selected partition\n\
-         sethidden          Set the FAT/NTFS hidden sectors field\n\
-         keeppxe            Keep the PXE and UNDI stacks in memory (PXELINUX)\n\
+         chain.c32 fs [options]\n\n\
+Options:\n\
+    file=<loader>      Load and execute file; boot sector is also\n\
+                           loaded if possible\n\
+    isolinux=<loader>  Load another version of ISOLINUX\n\
+    ntldr=<loader>     Load Windows NTLDR, SETUPLDR.BIN or BOOTMGR\n\
+    cmldr=<loader>     Load Recovery Console of Windows NT/2K/XP/2003\n\
+    freedos=<loader>   Load FreeDOS KERNEL.SYS\n\
+    msdos=<loader>     Load MS-DOS IO.SYS\n\
+    pcdos=<loader>     Load PC-DOS IBMBIO.COM\n\
+    msdos7=<loader>    Load MS-DOS 7+ IO.SYS\n\
+    grub=<loader>      Load GRUB Legacy stage2\n\
+    grubcfg=<filename> Set alternative config filename for GRUB Legacy\n\
+    grldr=<loader>     Load GRUB4DOS grldr\n\
+    seg=<s[:o[:i]]>    Load file at <s:o>, jump to <s:i>\n\
+    segbs=<s[:o[:i]]>  Load boot sector at <s:o>, jump to <s:i>;\n\
+                           file takes precedence over boot sector, if loaded\n\
+    swap               Swap drive numbers, if bootdisk is not fd0/hd0\n\
+    hide               Hide primary partitions, except selected partition\n\
+    sethidden          Set the FAT/NTFS hidden sectors field\n\
+    setgeometry        Set the FAT/NTFS sectors per track and heads fields\n\
+    setdrive@<offset>  Set the FAT/NTFS drive unit field at <offset>;\n\
+                           setdrive option alone defaults to 0x24\n\
+    setbpb             Enable set{hidden,geometry} options\n\
+    writebs            Write updated boot sector to the disk\n\
+    filebpb            Also mangle file with bpb options\n\
+    bsnomap            Don't map boot sector into real memory.\n\
+    keeppxe            Keep the PXE and UNDI stacks in memory (PXELINUX)\n\n\
 See syslinux/com32/modules/chain.c for more information\n";
     error(usage);
 }
 
-int main(int argc, char *argv[])
+/* Adjust BPB options according to chosen options */
+
+static void mangle_bpb(void *bootr, uint32_t sec, uint8_t drv)
 {
-    struct mbr *mbr = NULL;
+    if(opt.setgeometry && disk_info.cbios)
+	*(uint32_t *)((char *)bootr + 0x18) = (disk_info.head << 16 ) | disk_info.sect;
+    if(opt.sethidden)
+        *(uint32_t *)((char *)bootr + 0x1C) = sec;
+    if(opt.drvoff < 512)
+	*(uint8_t *)((char *)bootr + opt.drvoff) = (opt.swap ? drv & 0x80 : drv);
+}
+
+/* Convert seg:off:ip values into numerical seg:linear_address:ip */
+
+static int segseq2vals(char *ptr, uint16_t *seg, uint32_t *lin, uint16_t *ip)
+{
+    uint32_t segval = 0, offval = 0, ipval = 0, val;
     char *p;
-    struct disk_part_iter *cur_part = NULL;
-    void *sect_area = NULL, *file_area = NULL;
-    struct part_entry *hand_area = NULL;
 
-    struct syslinux_rm_regs regs;
-    char *drivename, *partition;
-    int hd, drive, whichpart = 0;	/* MBR by default */
-    int i;
-    uint64_t fs_lba = 0;	/* Syslinux partition */
-    uint32_t file_lba = 0;
-    struct guid gpt_guid;
-    unsigned char *isolinux_bin;
-    uint32_t *checksum, *chkhead, *chktail;
-    struct data_area data[3];
-    int ndata = 0;
-    addr_t load_base;
-    static const char cmldr_signature[8] = "cmdcons";
+    segval = strtoul(ptr, &p, 16);
+    if(*p == ':')
+	offval = strtoul(p+1, &p, 16);
+    if(*p == ':')
+	ipval = strtoul(p+1, NULL, 16);
 
-    openconsole(&dev_null_r, &dev_stdcon_w);
+    offval = (segval << 4) + offval;
 
-    drivename = "boot";
-    partition = NULL;
+    if (offval < ADDRMIN || offval > ADDRMAX) {
+	error("Invalid seg or segbs address.\n");
+	goto bail;
+    }
 
-    /* Prepare the register set */
-    memset(&regs, 0, sizeof regs);
+    val = (segval << 4) + ipval;
+
+    if (ipval > 0xFFFE || val < ADDRMIN || val > ADDRMAX) {
+	error("Invalid seg or segbs ip value.\n");
+	goto bail;
+    }
+
+    if(seg)
+	*seg = (uint16_t)segval;
+    if(lin)
+	*lin = offval;
+    if(ip)
+	*ip  = (uint16_t)ipval;
+
+    return 0;
+
+bail:
+    return -1;
+}
+
+static int parse_args(int argc, char *argv[], char **drivename, char **partition)
+{
+    uint32_t val;
+    int i;
+    char *p;
+
+    *drivename = "boot";
+    *partition = NULL;
 
     for (i = 1; i < argc; i++) {
 	if (!strncmp(argv[i], "file=", 5)) {
 	    opt.loadfile = argv[i] + 5;
 	} else if (!strncmp(argv[i], "seg=", 4)) {
-	    uint32_t segval = strtoul(argv[i] + 4, NULL, 0);
-	    if (segval < 0x50 || segval > 0x9f000) {
-		error("Invalid segment\n");
+	    if(segseq2vals(argv[i] + 4, &opt.file_seg, &opt.file_lin, &opt.file_ip))
+		goto bail;
+	} else if (!strncmp(argv[i], "segbs=", 5)) {
+	    if(segseq2vals(argv[i] + 6, &opt.sect_seg, &opt.sect_lin, &opt.sect_ip))
+		goto bail;
+	    if(opt.sect_lin + SECTOR > ADDRMAX) {
+		error("Sector's address too big.\n");
 		goto bail;
 	    }
-	    opt.seg = segval;
 	} else if (!strncmp(argv[i], "isolinux=", 9)) {
 	    opt.loadfile = argv[i] + 9;
 	    opt.isolinux = true;
 	} else if (!strncmp(argv[i], "ntldr=", 6)) {
-	    opt.seg = 0x2000;	/* NTLDR wants this address */
+	    opt.file_seg = 0x2000;	/* NTLDR wants this address */
+	    opt.file_lin = 0x20000;
+	    opt.file_ip = 0;
 	    opt.loadfile = argv[i] + 6;
 	    opt.sethidden = true;
+	    opt.setgeometry = true;
+	    opt.drvoff = 0x24;
+	    opt.writebs = true;
 	} else if (!strncmp(argv[i], "cmldr=", 6)) {
-	    opt.seg = 0x2000;	/* CMLDR wants this address */
+	    opt.file_seg = 0x2000;	/* CMLDR wants this address */
+	    opt.file_lin = 0x20000;
+	    opt.file_ip = 0;
 	    opt.loadfile = argv[i] + 6;
 	    opt.cmldr = true;
 	    opt.sethidden = true;
+	    opt.setgeometry = true;
+	    opt.drvoff = 0x24;
+	    opt.writebs = true;
 	} else if (!strncmp(argv[i], "freedos=", 8)) {
-	    opt.seg = 0x60;	/* FREEDOS wants this address */
+	    opt.file_seg = 0x60;	/* FREEDOS wants this address */
+	    opt.file_lin = 0x600;
+	    opt.file_ip = 0;
 	    opt.loadfile = argv[i] + 8;
 	    opt.sethidden = true;
-	} else if (!strncmp(argv[i], "msdos=", 6) ||
-		   !strncmp(argv[i], "pcdos=", 6)) {
-	    opt.seg = 0x70;	/* MS-DOS 2.0+ wants this address */
-	    opt.loadfile = argv[i] + 6;
+	    opt.setgeometry = true;
+	    opt.writebs = true;
+	    opt.bsnomap = true;
+	} else if ( (val = 6, !strncmp(argv[i], "msdos=", 6) ||
+			      !strncmp(argv[i], "pcdos=", 6)) ||
+		    (val = 7, !strncmp(argv[i], "msdos7=", 7)) ) {
+	    opt.file_seg = 0x70;	/* MS-DOS 2.0+ wants this address */
+	    opt.file_lin = 0x700;
+	    opt.file_ip = val == 7 ? 0x200 : 0;  /* MS-DOS 7.0+ wants this ip */
+	    opt.loadfile = argv[i] + val;
 	    opt.sethidden = true;
+	    opt.setgeometry = true;
+	    opt.writebs = true;
+	    opt.bsnomap = true;
 	} else if (!strncmp(argv[i], "grub=", 5)) {
-	    opt.seg = 0x800;	/* stage2 wants this address */
+	    opt.file_seg = 0x800;	/* stage2 wants this address */
+	    opt.file_lin = 0x8000;
+	    opt.file_ip = 0x200;
 	    opt.loadfile = argv[i] + 5;
 	    opt.grub = true;
 	} else if (!strncmp(argv[i], "grubcfg=", 8)) {
@@ -1376,6 +1532,40 @@ int main(int argc, char *argv[])
 	    opt.sethidden = true;
 	} else if (!strcmp(argv[i], "nosethidden")) {
 	    opt.sethidden = false;
+	} else if (!strcmp(argv[i], "setgeometry")) {
+	    opt.setgeometry = true;
+	} else if (!strcmp(argv[i], "nosetgeometry")) {
+	    opt.setgeometry = false;
+	} else if (!strcmp(argv[i], "bsnomap")) {
+	    opt.bsnomap = true;
+	} else if (!strcmp(argv[i], "nobsnomap")) {
+	    opt.bsnomap = false;
+	} else if (!strncmp(argv[i], "setdrive",8)) {
+	    if(!argv[i][8])
+		val = 0x24;
+	    else
+		val = strtoul(argv[i]+9, NULL, 16);
+	    if (val >= SECTOR || !(val == 0x24 || val == 0x40)) {
+		error("Invalid setdrive offset.\n");
+		goto bail;
+	    }
+	    opt.drvoff = val;
+	} else if (!strcmp(argv[i], "nosetdrive")) {
+	    opt.drvoff = SECTOR;
+	} else if (!strcmp(argv[i], "setbpb")) {
+	    opt.sethidden = true;
+	    opt.setgeometry = true;
+	} else if (!strcmp(argv[i], "nosetbpb")) {
+	    opt.sethidden = false;
+	    opt.setgeometry = false;
+	} else if (!strcmp(argv[i], "writebs")) {
+	    opt.writebs = true;
+	} else if (!strcmp(argv[i], "nowritebs")) {
+	    opt.writebs = false;
+	} else if (!strcmp(argv[i], "filebpb")) {
+	    opt.filebpb = true;
+	} else if (!strcmp(argv[i], "nofilebpb")) {
+	    opt.filebpb = false;
 	} else if (((argv[i][0] == 'h' || argv[i][0] == 'f')
 		    && argv[i][1] == 'd')
 		   || !strncmp(argv[i], "mbr:", 4)
@@ -1387,14 +1577,14 @@ int main(int argc, char *argv[])
 		   || !strcmp(argv[i], "boot")
 		   || !strncmp(argv[i], "boot,", 5)
 		   || !strcmp(argv[i], "fs")) {
-	    drivename = argv[i];
-	    p = strchr(drivename, ',');
+	    *drivename = argv[i];
+	    p = strchr(*drivename, ',');
 	    if (p) {
 		*p = '\0';
-		partition = p + 1;
+		*partition = p + 1;
 	    } else if (argv[i + 1] && argv[i + 1][0] >= '0'
 		       && argv[i + 1][0] <= '9') {
-		partition = argv[++i];
+		*partition = argv[++i];
 	    }
 	} else {
 	    usage();
@@ -1407,12 +1597,59 @@ int main(int argc, char *argv[])
 	goto bail;
     }
 
-    if (opt.seg) {
-	regs.es = regs.cs = regs.ss = regs.ds = regs.fs = regs.gs = opt.seg;
+    return 0;
+bail:
+    return -1;
+}
+
+int main(int argc, char *argv[])
+{
+    struct mbr *mbr = NULL;
+    struct disk_part_iter *cur_part = NULL;
+    void *sect_area = NULL, *file_area = NULL;
+    struct part_entry *hand_area = NULL;
+    void *cmp_buf = NULL;   /* compare buffer for bpb mangling & writing */
+
+    struct syslinux_rm_regs regs;
+    char *drivename, *partition;
+    int hd, drive, whichpart = 0;	/* MBR by default */
+    uint64_t fs_lba = 0;	/* Syslinux partition */
+    uint32_t file_lba = 0;
+    struct guid gpt_guid;
+    unsigned char *isolinux_bin;
+    uint32_t *checksum, *chkhead, *chktail;
+    struct data_area data[3];
+    int ndata = 0, file_didx = -1, sect_didx = -1;
+    static const char cmldr_signature[8] = "cmdcons";
+
+    openconsole(&dev_null_r, &dev_stdcon_w);
+
+    /* Prepare the register set */
+    memset(&regs, 0, sizeof regs);
+
+    /* Prepare and set non-0 default values */
+    memset(&opt, 0, sizeof(opt));
+    opt.file_lin = opt.sect_lin = opt.file_ip = opt.sect_ip = 0x7C00;
+    opt.drvoff = SECTOR;	/* offset outside sector means turned off */
+
+    if(parse_args(argc, argv, &drivename, &partition))
+	goto bail;
+
+    /* set initial registry values, file takes precedence */
+
+    if(opt.loadfile) {
+	regs.es = regs.cs = regs.ss = regs.ds = regs.fs = regs.gs = opt.file_seg;
+	regs.ip = opt.file_ip;
     } else {
-	regs.ip = regs.esp.l = 0x7c00;
+	regs.es = regs.cs = regs.ss = regs.ds = regs.fs = regs.gs = opt.sect_seg;
+	regs.ip = opt.sect_ip;
     }
 
+    /* if handover address is default, set sp as well */
+
+    if(regs.ip == 0x7C00 && !regs.cs)
+	regs.esp.l = 0x7C00;
+
     hd = 0;
     if (!strncmp(drivename, "mbr", 3)) {
 	drive = find_disk(strtoul(drivename + 4, NULL, 0));
@@ -1521,18 +1758,20 @@ int main(int argc, char *argv[])
 	    error("WARNING: failed to write MBR for 'hide'\n");
     }
 
-    /* Do the actual chainloading */
-    load_base = opt.seg ? (opt.seg << 4) : 0x7c00;
-
+    /* Chainload the file if required */
     if (opt.loadfile) {
 	fputs("Loading the boot file...\n", stdout);
 	if (loadfile(opt.loadfile, &data[ndata].data, &data[ndata].size)) {
 	    error("Failed to load the boot file\n");
 	    goto bail;
 	}
-	data[ndata].base = load_base;
+	if (opt.file_lin + data[ndata].size > ADDRMAX) {
+	    error("File too large to load at this address\n");
+	    goto bail;
+	}
+	data[ndata].base = opt.file_lin;
 	file_area = (void *)data[ndata].data;
-	load_base = 0x7c00;	/* If we also load a boot sector */
+	file_didx = ndata;
 
 	/* Create boot info table: needed when you want to chainload
 	   another version of ISOLINUX (or another bootlaoder that needs
@@ -1546,7 +1785,7 @@ int main(int argc, char *argv[])
 		/* Boot info table info (integers in little endian format)
 
 		   Offset Name         Size      Meaning
-		   8     bi_pvd       4 bytes   LBA of primary volume descriptor
+		   8      bi_pvd       4 bytes   LBA of primary volume descriptor
 		   12     bi_file      4 bytes   LBA of boot file
 		   16     bi_length    4 bytes   Boot file length in bytes
 		   20     bi_csum      4 bytes   32-bit checksum
@@ -1636,7 +1875,7 @@ int main(int argc, char *argv[])
 
 	    if (data[ndata].size < sizeof(struct grub_stage2_patch_area)) {
 		error
-		    ("The file specified by grub=<loader> is to small to be stage2 of GRUB Legacy.\n");
+		    ("The file specified by grub=<loader> is too small to be stage2 of GRUB Legacy.\n");
 		goto bail;
 	    }
 
@@ -1653,9 +1892,6 @@ int main(int argc, char *argv[])
 		goto bail;
 	    }
 
-	    /* jump 0x200 bytes into the loadfile */
-	    regs.ip = 0x200;
-
 	    /*
 	     * GRUB Legacy wants the partition number in the install_partition
 	     * variable, located at offset 0x208 of stage2.
@@ -1700,11 +1936,18 @@ int main(int argc, char *argv[])
 	    }
 	}
 
+	if(opt.filebpb && cur_part)
+	    mangle_bpb(data[ndata].data, (uint32_t)cur_part->lba_data, regs.edx.b[0]);
 	ndata++;
     }
 
-    if (!opt.loadfile || data[0].base >= 0x7c00 + SECTOR) {
-	/* Actually read the boot sector */
+    /* Check if loaded file doesn't overlap with boot sector */
+    if (file_didx < 0 || opt.bsnomap ||
+	    data[file_didx].base + data[file_didx].size <= opt.sect_lin ||
+	    opt.sect_lin + SECTOR <= data[file_didx].base
+	) {
+
+	/* Actually read the boot sector (or point to the mbr) */
 	if (!cur_part) {
 	    data[ndata].data = mbr;
 	} else if (!(data[ndata].data = read_sectors(cur_part->lba_data, 1))) {
@@ -1714,7 +1957,9 @@ int main(int argc, char *argv[])
 	    sect_area = (void *)data[ndata].data;
 
 	data[ndata].size = SECTOR;
-	data[ndata].base = load_base;
+	data[ndata].base = opt.sect_lin;
+	if(!opt.bsnomap)
+		sect_didx = ndata;
 
 	if (!opt.loadfile) {
 	    const struct mbr *br =
@@ -1726,29 +1971,47 @@ int main(int argc, char *argv[])
 		goto bail;
 	    }
 	}
-	/*
-	 * To boot the Recovery Console of Windows NT/2K/XP we need to write
-	 * the string "cmdcons\0" to memory location 0000:7C03.
-	 * Memory location 0000:7C00 contains the bootsector of the partition.
-	 */
-	if (cur_part && opt.cmldr) {
-	    memcpy((char *)data[ndata].data + 3, cmldr_signature,
-		   sizeof cmldr_signature);
-	}
 
 	/*
-	 * Modify the hidden sectors (partition offset) copy in memory;
-	 * this modifies the field used by FAT and NTFS filesystems, and
-	 * possibly other boot loaders which use the same format.
+	 * Adjust some of the boot sector fields of FAT/NTFS filesystems - if
+	 * applicable and requested by set{hidden,geometry,drive} or cmldr=
 	 */
-	if (cur_part && opt.sethidden) {
-	    *(uint32_t *) ((char *)data[ndata].data + 28) = cur_part->lba_data;
-	}
 
-	ndata++;
+	if (cur_part) {
+	    if (!(cmp_buf = malloc(SECTOR))) {
+		error("Could not allocate sector-compare buffer.\n");
+		goto bail;
+	    }
+	    memcpy(cmp_buf, data[ndata].data, SECTOR);
+
+	    mangle_bpb((char*)data[ndata].data, (uint32_t)cur_part->lba_data, regs.edx.b[0]);
+
+	    /*
+	     * Write adjusted boot sector to the disk, but only if at least one
+	     * of the fields changed. It must be done before cmldr=.
+	     */
+	    if (opt.writebs && memcmp(cmp_buf, data[ndata].data, SECTOR)) {
+		if (write_verify_sector(cur_part->lba_data, data[ndata].data)) {
+		    error("Cannot write updated boot sector.\n");
+		    goto bail;
+		}
+	    }
+
+	    /*
+	     * To boot the Recovery Console of Windows NT/2K/XP we need to write
+	     * the string "cmdcons\0" to memory location 0000:7C03.
+	     * Memory location 0000:7C00 contains the boot sector of the partition.
+	     */
+	    if (opt.cmldr)
+		memcpy((char *)data[ndata].data + 3, cmldr_signature, sizeof cmldr_signature);
+
+	}
+	if(!opt.bsnomap)
+		ndata++;
     }
 
     if (cur_part) {
+	data[ndata].data = NULL;
 	if (cur_part->next == next_gpt_part) {
 	    /* Do GPT hand-over, if applicable (as per syslinux/doc/gpt.txt) */
 	    /* Look at the GPT partition */
@@ -1792,7 +2055,6 @@ int main(int argc, char *argv[])
 	    data[ndata].base = 0x7be;
 	    data[ndata].size = synth_size;
 	    data[ndata].data = (void *)hand_area;
-	    ndata++;
 	    regs.esi.w[0] = 0x7be;
 
 	    dprintf("GPT handover:\n");
@@ -1814,12 +2076,30 @@ int main(int argc, char *argv[])
 	    data[ndata].base = 0x7be;
 	    data[ndata].size = sizeof(struct part_entry);
 	    data[ndata].data = (void *)hand_area;
-	    ndata++;
 	    regs.esi.w[0] = 0x7be;
 
 	    dprintf("MBR handover:\n");
 	    mbr_part_dump(hand_area);
 	}
+
+	if(data[ndata].data) {
+	    /* We have to make sure, that handover data doesn't overlap with the file and/or the boot sector.
+	     * For example, part of FreeDOS kernel loaded at 0x600 would conflict with the handover data.
+	     * Still, in case of file=, the handover data is generally not needed (not always though - e.g.
+	     * recovery console), so we do the following check and increase ndata if there're no conflicts.
+	     */
+	    if (( file_didx < 0 || /* check against file */
+		data[file_didx].base + data[file_didx].size <= data[ndata].base ||
+		data[ndata].base + data[ndata].size <= data[file_didx].base
+	    ) && ( sect_didx < 0 || /* check against sector */
+		data[sect_didx].base + data[sect_didx].size <= data[ndata].base ||
+		data[ndata].base + data[ndata].size <= data[sect_didx].base
+	    )) {
+		ndata++;
+	    } else {
+		dprintf("Handover ignored due to overlapping regions.\n");
+	    }
+	}
     }
 
     do_boot(data, ndata, &regs);
-- 
1.6.3.1




More information about the Syslinux mailing list