1 /* ----------------------------------------------------------------------- *
3 * Copyright 2003-2009 H. Peter Anvin - All Rights Reserved
4 * Copyright 2009-2010 Intel Corporation; author: H. Peter Anvin
5 * Significant portions copyright (C) 2010 Shao Miller
6 * [partition iteration, GPT, "fs"]
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation, Inc., 53 Temple Place Ste 330,
11 * Boston MA 02111-1307, USA; either version 2 of the License, or
12 * (at your option) any later version; incorporated herein by reference.
14 * ----------------------------------------------------------------------- */
19 * Chainload a hard disk (currently rather braindead.)
21 * Usage: chain [options]
22 * chain hd<disk#> [<partition>] [options]
23 * chain fd<disk#> [options]
24 * chain mbr:<id> [<partition>] [options]
25 * chain guid:<guid> [<partition>] [options]
26 * chain label:<label> [<partition>] [options]
27 * chain boot [<partition>] [options]
29 * For example, "chain msdos=io.sys" will load DOS from the current Syslinux
30 * filesystem. "chain hd0 1" will boot the first partition on the first hard
33 * When none of the "hdX", "fdX", "mbr:", "guid:", "label:", "boot" or "fs"
34 * options are specified, the default behaviour is equivalent to "boot".
35 * "boot" means to use the current Syslinux drive, and you can also specify
38 * The mbr: syntax means search all the hard disks until one with a
39 * specific MBR serial number (bytes 440-443) is found.
41 * Partitions 1-4 are primary, 5+ logical, 0 = boot MBR (default.)
43 * "fs" will use the current Syslinux filesystem as the boot drive/partition.
44 * When booting from PXELINUX, you will most likely wish to specify a disk.
49 * loads the file <loader> **from the Syslinux filesystem**
50 * instead of loading the boot sector.
53 * loads at and jumps to <seg>:0000 instead of 0000:7C00.
56 * chainload another version/build of the ISOLINUX bootloader and patch
57 * the loader with appropriate parameters in memory.
58 * This avoids the need for the -eltorito-alt-boot parameter of mkisofs,
59 * when you want more than one ISOLINUX per CD/DVD.
62 * equivalent to seg=0x2000 file=<loader> sethidden,
63 * used with WinNT's loaders
66 * used with Recovery Console of Windows NT/2K/XP.
67 * same as ntldr=<loader> & "cmdcons\0" written to
68 * the system name field in the bootsector
71 * equivalent to seg=0x60 file=<loader> sethidden,
72 * used with FreeDOS' kernel.sys.
76 * equivalent to seg=0x70 file=<loader> sethidden,
77 * used with DOS' io.sys.
80 * same as seg=0x800 file=<loader> & jumping to seg 0x820,
81 * used with GRUB Legacy stage2 files.
84 * set an alternative config filename in stage2 of Grub Legacy,
85 * only applicable in combination with "grub=<loader>".
88 * pass the partition number to GRUB4DOS,
89 * used with GRUB4DOS' grldr.
92 * if the disk is not fd0/hd0, install a BIOS stub which swaps
96 * change type of primary partitions with IDs 01, 04, 06, 07,
97 * 0b, 0c, or 0e to 1x, except for the selected partition, which
98 * is converted the other way.
101 * update the "hidden sectors" (partition offset) field in a
102 * FAT/NTFS boot sector.
105 * keep the PXE and UNDI stacks in memory (PXELINUX only).
117 #include <syslinux/loadfile.h>
118 #include <syslinux/bootrm.h>
119 #include <syslinux/config.h>
120 #include <syslinux/disk.h>
121 #include <syslinux/video.h>
123 static struct options {
124 const char *loadfile;
143 static inline void error(const char *msg)
148 static struct disk_info diskinfo;
150 /* Search for a specific drive, based on the MBR signature; bytes 440-443 */
151 static int find_disk(uint32_t mbr_sig)
155 struct disk_dos_mbr *mbr;
157 for (drive = 0x80; drive <= 0xff; drive++) {
158 if (disk_get_params(drive, &diskinfo))
159 continue; /* Drive doesn't exist */
160 if (!(mbr = disk_read_sectors(&diskinfo, 0, 1)))
161 continue; /* Cannot read sector */
162 is_me = (mbr->disk_sig == mbr_sig);
170 /* Forward declaration */
171 struct disk_part_iter;
173 /* Partition-/scheme-specific routine returning the next partition */
174 typedef struct disk_part_iter *(*disk_part_iter_func) (struct disk_part_iter *
177 /* Contains details for a partition under examination */
178 struct disk_part_iter {
179 /* The block holding the table we are part of */
181 /* The LBA for the beginning of data */
183 /* The partition number, as determined by our heuristic */
185 /* The DOS partition record to pass, if applicable */
186 const struct disk_dos_part_entry *record;
187 /* Function returning the next available partition */
188 disk_part_iter_func next;
189 /* Partition-/scheme-specific details */
195 /* The first extended partition's start LBA */
196 uint64_t lba_extended;
197 /* Any applicable parent, or NULL */
198 struct disk_part_iter *parent;
199 /* The parent extended partition index */
204 /* Real (not effective) index in the partition table */
206 /* Current partition GUID */
207 const struct guid *part_guid;
208 /* Current partition label */
209 const char *part_label;
210 /* Count of entries in GPT */
212 /* Partition record size */
218 static struct disk_part_iter *next_ebr_part(struct disk_part_iter *part)
220 const struct disk_dos_part_entry *ebr_table;
221 const struct disk_dos_part_entry *parent_table =
222 ((const struct disk_dos_mbr *)part->private.ebr.parent->block)->table;
223 static const struct disk_dos_part_entry phony = {.start_lba = 0 };
226 /* Don't look for a "next EBR" the first time around */
227 if (part->private.ebr.parent_index >= 0)
228 /* Look at the linked list */
229 ebr_table = ((const struct disk_dos_mbr *)part->block)->table + 1;
230 /* Do we need to look for an extended partition? */
231 if (part->private.ebr.parent_index < 0 || !ebr_table->start_lba) {
232 /* Start looking for an extended partition in the MBR */
233 while (++part->private.ebr.parent_index < 4) {
234 uint8_t type = parent_table[part->private.ebr.parent_index].ostype;
236 if ((type == 0x05) || (type == 0x0F) || (type == 0x85))
239 if (part->private.ebr.parent_index == 4)
240 /* No extended partitions found */
242 part->private.ebr.lba_extended =
243 parent_table[part->private.ebr.parent_index].start_lba;
247 ebr_lba = ebr_table->start_lba + part->private.ebr.lba_extended;
249 part->block = disk_read_sectors(&diskinfo, ebr_lba, 1);
251 error("Could not load EBR!\n");
254 ebr_table = ((const struct disk_dos_mbr *)part->block)->table;
255 dprintf("next_ebr_part:\n");
256 disk_dos_part_dump(ebr_table);
259 * Sanity check entry: must not extend outside the
260 * extended partition. This is necessary since some OSes
261 * put crap in some entries.
264 const struct disk_dos_mbr *mbr =
265 (const struct disk_dos_mbr *)part->private.ebr.parent->block;
266 const struct disk_dos_part_entry *extended =
267 mbr->table + part->private.ebr.parent_index;
269 if (ebr_table[0].start_lba >= extended->start_lba + extended->length) {
270 dprintf("Insane logical partition!\n");
275 part->lba_data = ebr_table[0].start_lba + ebr_lba;
276 dprintf("Partition %d logical lba %u\n", part->index, part->lba_data);
278 part->record = ebr_table;
288 free(part->private.ebr.parent->block);
289 free(part->private.ebr.parent);
295 static struct disk_part_iter *next_mbr_part(struct disk_part_iter *part)
297 struct disk_part_iter *ebr_part;
298 /* Look at the partition table */
299 struct disk_dos_part_entry *table =
300 ((struct disk_dos_mbr *)part->block)->table;
302 /* Look for data partitions */
303 while (++part->private.mbr_index < 4) {
304 uint8_t type = table[part->private.mbr_index].ostype;
306 if (type == 0x00 || type == 0x05 || type == 0x0F || type == 0x85)
307 /* Skip empty or extended partitions */
309 if (!table[part->private.mbr_index].length)
314 /* If we're currently the last partition, it's time for EBR processing */
315 if (part->private.mbr_index == 4) {
316 /* Allocate another iterator for extended partitions */
317 ebr_part = malloc(sizeof(*ebr_part));
319 error("Could not allocate extended partition iterator!\n");
322 /* Setup EBR iterator parameters */
323 ebr_part->block = NULL;
325 ebr_part->record = NULL;
326 ebr_part->next = next_ebr_part;
327 ebr_part->private.ebr.parent = part;
328 /* Trigger an initial EBR load */
329 ebr_part->private.ebr.parent_index = -1;
330 /* The EBR iterator is responsible for freeing us */
331 return next_ebr_part(ebr_part);
333 dprintf("next_mbr_part:\n");
334 disk_dos_part_dump(table + part->private.mbr_index);
336 /* Update parameters to reflect this new partition. Re-use iterator */
337 part->lba_data = table[part->private.mbr_index].start_lba;
338 dprintf("Partition %d primary lba %u\n", part->index, part->lba_data);
340 part->record = table + part->private.mbr_index;
353 * Fill a buffer with a textual GUID representation.
354 * The buffer must be >= char[37] and will be populated
355 * with an ASCII NUL C string terminator.
356 * Example: 11111111-2222-3333-4444-444444444444
357 * Endian: LLLLLLLL-LLLL-LLLL-BBBB-BBBBBBBBBBBB
359 static void guid_to_str(char *buf, const struct guid *id)
362 const char *walker = (const char *)id;
364 while (i < sizeof(guid_le_walk_map)) {
365 walker += guid_le_walk_map[i];
366 if (!guid_le_walk_map[i])
369 *buf = ((*walker & 0xF0) >> 4) + '0';
371 *buf += 'A' - '9' - 1;
373 *buf = (*walker & 0x0F) + '0';
375 *buf += 'A' - '9' - 1;
385 * Create a GUID structure from a textual GUID representation.
386 * The input buffer must be >= 32 hexadecimal chars and be
387 * terminated with an ASCII NUL. Returns non-zero on failure.
388 * Example: 11111111-2222-3333-4444-444444444444
389 * Endian: LLLLLLLL-LLLL-LLLL-BBBB-BBBBBBBBBBBB
391 static int str_to_guid(const char *buf, struct guid *id)
393 char guid_seq[sizeof(struct guid) * 2];
395 char *walker = (char *)id;
397 while (*buf && i < sizeof(guid_seq)) {
399 /* Skip these three characters */
405 /* Copy something useful to the temp. sequence */
406 if ((*buf >= '0') && (*buf <= '9'))
407 guid_seq[i] = *buf - '0';
408 else if ((*buf >= 'A') && (*buf <= 'F'))
409 guid_seq[i] = *buf - 'A' + 10;
410 else if ((*buf >= 'a') && (*buf <= 'f'))
411 guid_seq[i] = *buf - 'a' + 10;
414 error("Illegal character in GUID!\n");
421 /* Check for insufficient valid characters */
422 if (i < sizeof(guid_seq)) {
423 error("Too few GUID characters!\n");
428 while (i < sizeof(guid_le_walk_map)) {
429 if (!guid_le_walk_map[i])
431 walker += guid_le_walk_map[i];
441 /* A GPT partition */
449 } __attribute__ ((packed));
451 static void gpt_part_dump(const struct gpt_part *gpt_part)
457 dprintf("----------------------------------\n"
458 "GPT part. LBA first __ : 0x%.16llx\n"
459 "GPT part. LBA last ___ : 0x%.16llx\n"
460 "GPT part. attribs ____ : 0x%.16llx\n"
461 "GPT part. name _______ : '",
462 gpt_part->lba_first, gpt_part->lba_last, gpt_part->attribs);
463 for (i = 0; i < sizeof(gpt_part->name); i++) {
464 if (gpt_part->name[i])
465 dprintf("%c", gpt_part->name[i]);
468 guid_to_str(guid_text, &gpt_part->type);
469 dprintf("GPT part. type GUID __ : {%s}\n", guid_text);
470 guid_to_str(guid_text, &gpt_part->uid);
471 dprintf("GPT part. unique ID __ : {%s}\n", guid_text);
483 } fields __attribute__ ((packed));
486 } rev __attribute__ ((packed));
492 uint64_t lba_first_usable;
493 uint64_t lba_last_usable;
494 struct guid disk_guid;
498 uint32_t table_chksum;
500 } __attribute__ ((packed));
501 static const char gpt_sig_magic[] = "EFI PART";
504 static void gpt_dump(const struct gpt *gpt)
508 printf("GPT sig ______________ : '%8.8s'\n"
509 "GPT major revision ___ : 0x%.4x\n"
510 "GPT minor revision ___ : 0x%.4x\n"
511 "GPT header size ______ : 0x%.8x\n"
512 "GPT header checksum __ : 0x%.8x\n"
513 "GPT reserved _________ : '%4.4s'\n"
514 "GPT LBA current ______ : 0x%.16llx\n"
515 "GPT LBA alternative __ : 0x%.16llx\n"
516 "GPT LBA first usable _ : 0x%.16llx\n"
517 "GPT LBA last usable __ : 0x%.16llx\n"
518 "GPT LBA part. table __ : 0x%.16llx\n"
519 "GPT partition count __ : 0x%.8x\n"
520 "GPT partition size ___ : 0x%.8x\n"
521 "GPT part. table chksum : 0x%.8x\n",
523 gpt->rev.fields.major,
524 gpt->rev.fields.minor,
530 gpt->lba_first_usable,
531 gpt->lba_last_usable,
532 gpt->lba_table, gpt->part_count, gpt->part_size, gpt->table_chksum);
533 guid_to_str(guid_text, &gpt->disk_guid);
534 printf("GPT disk GUID ________ : {%s}\n", guid_text);
538 static struct disk_part_iter *next_gpt_part(struct disk_part_iter *part)
540 const struct gpt_part *gpt_part = NULL;
542 while (++part->private.gpt.index < part->private.gpt.parts) {
544 (const struct gpt_part *)(part->block +
545 (part->private.gpt.index *
546 part->private.gpt.size));
547 if (!gpt_part->lba_first)
551 /* Were we the last partition? */
552 if (part->private.gpt.index == part->private.gpt.parts) {
555 part->lba_data = gpt_part->lba_first;
556 part->private.gpt.part_guid = &gpt_part->uid;
557 part->private.gpt.part_label = gpt_part->name;
558 /* Update our index */
560 gpt_part_dump(gpt_part);
562 /* In a GPT scheme, we re-use the iterator */
572 static struct disk_part_iter *get_first_partition(struct disk_part_iter *part)
574 const struct gpt *gpt_candidate;
577 * Ignore any passed partition iterator. The caller should
578 * have passed NULL. Allocate a new partition iterator
580 part = malloc(sizeof(*part));
582 error("Count not allocate partition iterator!\n");
586 part->block = disk_read_sectors(&diskinfo, 0, 2);
588 error("Could not read two sectors!\n");
591 /* Check for an MBR */
592 if (((struct disk_dos_mbr *)part->block)->sig != disk_mbr_sig_magic) {
593 error("No MBR magic!\n");
596 /* Establish a pseudo-partition for the MBR (index 0) */
599 part->private.mbr_index = -1;
600 part->next = next_mbr_part;
601 /* Check for a GPT disk */
602 gpt_candidate = (const struct gpt *)(part->block + SECTOR);
603 if (!memcmp(gpt_candidate->sig, gpt_sig_magic, sizeof(gpt_sig_magic))) {
604 /* LBA for partition table */
607 /* It looks like one */
608 /* TODO: Check checksum. Possibly try alternative GPT */
610 puts("Looks like a GPT disk.");
611 gpt_dump(gpt_candidate);
613 /* TODO: Check table checksum (maybe) */
614 /* Note relevant GPT details */
615 part->next = next_gpt_part;
616 part->private.gpt.index = -1;
617 part->private.gpt.parts = gpt_candidate->part_count;
618 part->private.gpt.size = gpt_candidate->part_size;
619 lba_table = gpt_candidate->lba_table;
620 gpt_candidate = NULL;
621 /* Load the partition table */
624 disk_read_sectors(&diskinfo, lba_table,
625 ((part->private.gpt.size *
626 part->private.gpt.parts) + SECTOR -
629 error("Could not read GPT partition list!\n");
633 /* Return the pseudo-partition's next partition, which is real */
634 return part->next(part);
651 * Search for a specific drive/partition, based on the GPT GUID.
652 * We return the disk drive number if found, as well as populating the
653 * boot_part pointer with the matching partition, if applicable.
654 * If no matching partition is found or the GUID is a disk GUID,
655 * boot_part will be populated with NULL. If not matching disk is
656 * found, we return -1.
658 static int find_by_guid(const struct guid *gpt_guid,
659 struct disk_part_iter **boot_part)
665 for (drive = 0x80; drive <= 0xff; drive++) {
666 if (disk_get_params(drive, &diskinfo))
667 continue; /* Drive doesn't exist */
668 if (!(header = disk_read_sectors(&diskinfo, 1, 1)))
669 continue; /* Cannot read sector */
670 if (memcmp(&header->sig, gpt_sig_magic, sizeof(gpt_sig_magic))) {
678 is_me = !memcmp(&header->disk_guid, &gpt_guid, sizeof(*gpt_guid));
681 /* Check for a matching partition */
682 boot_part[0] = get_first_partition(NULL);
683 while (boot_part[0]) {
685 !memcmp(boot_part[0]->private.gpt.part_guid, gpt_guid,
689 boot_part[0] = boot_part[0]->next(boot_part[0]);
700 * Search for a specific partition, based on the GPT label.
701 * We return the disk drive number if found, as well as populating the
702 * boot_part pointer with the matching partition, if applicable.
703 * If no matching partition is found, boot_part will be populated with
704 * NULL and we return -1.
706 static int find_by_label(const char *label, struct disk_part_iter **boot_part)
711 for (drive = 0x80; drive <= 0xff; drive++) {
712 if (disk_get_params(drive, &diskinfo))
713 continue; /* Drive doesn't exist */
714 /* Check for a GPT disk */
715 boot_part[0] = get_first_partition(NULL);
716 if (!(boot_part[0]->next == next_gpt_part)) {
718 while (boot_part[0]) {
719 /* Run through until the end */
720 boot_part[0] = boot_part[0]->next(boot_part[0]);
724 /* Check for a matching partition */
725 while (boot_part[0]) {
726 char gpt_label[sizeof(((struct gpt_part *) NULL)->name)];
727 const char *gpt_label_scanner =
728 boot_part[0]->private.gpt.part_label;
731 /* Re-write the GPT partition label as ASCII */
732 while (gpt_label_scanner <
733 boot_part[0]->private.gpt.part_label + sizeof(gpt_label)) {
734 if ((gpt_label[j] = *gpt_label_scanner))
738 if ((is_me = !strcmp(label, gpt_label)))
740 boot_part[0] = boot_part[0]->next(boot_part[0]);
749 static void do_boot(struct data_area *data, int ndata,
750 struct syslinux_rm_regs *regs)
752 uint16_t *const bios_fbm = (uint16_t *) 0x413;
753 addr_t dosmem = *bios_fbm << 10; /* Technically a low bound */
754 struct syslinux_memmap *mmap;
755 struct syslinux_movelist *mlist = NULL;
757 uint8_t driveno = regs->edx.b[0];
758 uint8_t swapdrive = driveno & 0x80;
761 mmap = syslinux_memory_map();
764 error("Cannot read system memory map\n");
769 for (i = 0; i < ndata; i++) {
770 if (data[i].base + data[i].size > endimage)
771 endimage = data[i].base + data[i].size;
773 if (endimage > dosmem)
776 for (i = 0; i < ndata; i++) {
777 if (syslinux_add_movelist(&mlist, data[i].base,
778 (addr_t) data[i].data, data[i].size))
782 if (opt.swap && driveno != swapdrive) {
783 static const uint8_t swapstub_master[] = {
784 /* The actual swap code */
785 0x53, /* 00: push bx */
786 0x0f, 0xb6, 0xda, /* 01: movzx bx,dl */
787 0x2e, 0x8a, 0x57, 0x60, /* 04: mov dl,[cs:bx+0x60] */
788 0x5b, /* 08: pop bx */
789 0xea, 0, 0, 0, 0, /* 09: jmp far 0:0 */
790 0x90, 0x90, /* 0E: nop; nop */
791 /* Code to install this in the right location */
792 /* Entry with DS = CS; ES = SI = 0; CX = 256 */
793 0x26, 0x66, 0x8b, 0x7c, 0x4c, /* 10: mov edi,[es:si+4*0x13] */
794 0x66, 0x89, 0x3e, 0x0a, 0x00, /* 15: mov [0x0A],edi */
795 0x26, 0x8b, 0x3e, 0x13, 0x04, /* 1A: mov di,[es:0x413] */
796 0x4f, /* 1F: dec di */
797 0x26, 0x89, 0x3e, 0x13, 0x04, /* 20: mov [es:0x413],di */
798 0x66, 0xc1, 0xe7, 0x16, /* 25: shl edi,16+6 */
799 0x26, 0x66, 0x89, 0x7c, 0x4c, /* 29: mov [es:si+4*0x13],edi */
800 0x66, 0xc1, 0xef, 0x10, /* 2E: shr edi,16 */
801 0x8e, 0xc7, /* 32: mov es,di */
802 0x31, 0xff, /* 34: xor di,di */
803 0xf3, 0x66, 0xa5, /* 36: rep movsd */
804 0xbe, 0, 0, /* 39: mov si,0 */
805 0xbf, 0, 0, /* 3C: mov di,0 */
806 0x8e, 0xde, /* 3F: mov ds,si */
807 0x8e, 0xc7, /* 41: mov es,di */
808 0x66, 0xb9, 0, 0, 0, 0, /* 43: mov ecx,0 */
809 0x66, 0xbe, 0, 0, 0, 0, /* 49: mov esi,0 */
810 0x66, 0xbf, 0, 0, 0, 0, /* 4F: mov edi,0 */
811 0xea, 0, 0, 0, 0, /* 55: jmp 0:0 */
812 /* pad out to segment boundary */
813 0x90, 0x90, /* 5A: ... */
814 0x90, 0x90, 0x90, 0x90, /* 5C: ... */
816 static uint8_t swapstub[1024];
819 /* Note: we can't rely on either INT 13h nor the dosmem
820 vector to be correct at this stage, so we have to use an
821 installer stub to put things in the right place.
822 Round the installer location to a 1K boundary so the only
823 possible overlap is the identity mapping. */
824 endimage = (endimage + 1023) & ~1023;
826 /* Create swap stub */
827 memcpy(swapstub, swapstub_master, sizeof swapstub_master);
828 *(uint16_t *) & swapstub[0x3a] = regs->ds;
829 *(uint16_t *) & swapstub[0x3d] = regs->es;
830 *(uint32_t *) & swapstub[0x45] = regs->ecx.l;
831 *(uint32_t *) & swapstub[0x4b] = regs->esi.l;
832 *(uint32_t *) & swapstub[0x51] = regs->edi.l;
833 *(uint16_t *) & swapstub[0x56] = regs->ip;
834 *(uint16_t *) & swapstub[0x58] = regs->cs;
835 p = &swapstub[sizeof swapstub_master];
837 /* Mapping table; start out with identity mapping everything */
838 for (i = 0; i < 256; i++)
841 /* And the actual swap */
842 p[driveno] = swapdrive;
843 p[swapdrive] = driveno;
845 /* Adjust registers */
846 regs->ds = regs->cs = endimage >> 4;
847 regs->es = regs->esi.l = 0;
848 regs->ecx.l = sizeof swapstub >> 2;
849 regs->ip = 0x10; /* Installer offset */
850 regs->ebx.b[0] = regs->edx.b[0] = swapdrive;
852 if (syslinux_add_movelist(&mlist, endimage, (addr_t) swapstub,
856 endimage += sizeof swapstub;
859 /* Tell the shuffler not to muck with this area... */
860 syslinux_add_memmap(&mmap, endimage, 0xa0000 - endimage, SMT_RESERVED);
862 /* Force text mode */
863 syslinux_force_text_mode();
865 fputs("Booting...\n", stdout);
866 syslinux_shuffle_boot_rm(mlist, mmap, opt.keeppxe, regs);
867 error("Chainboot failed!\n");
871 error("Loader file too large\n");
875 error("Out of memory\n");
879 static int hide_unhide(struct disk_dos_mbr *mbr, int part)
882 struct disk_dos_part_entry *pt;
883 const uint16_t mask =
884 (1 << 0x01) | (1 << 0x04) | (1 << 0x06) | (1 << 0x07) | (1 << 0x0b) | (1
889 bool write_back = false;
891 for (i = 1; i <= 4; i++) {
892 pt = mbr->table + i - 1;
894 if ((t <= 0x1f) && ((mask >> (t & ~0x10)) & 1)) {
895 /* It's a hideable partition type */
897 t &= ~0x10; /* unhide */
899 t |= 0x10; /* hide */
901 if (t != pt->ostype) {
908 return disk_write_verify_sector(&diskinfo, 0, mbr);
913 static uint32_t get_file_lba(const char *filename)
918 /* Start with clean registers */
919 memset(&inregs, 0, sizeof(com32sys_t));
921 /* Put the filename in the bounce buffer */
922 strlcpy(__com32.cs_bounce, filename, __com32.cs_bounce_size);
924 /* Call comapi_open() which returns a structure pointer in SI
925 * to a structure whose first member happens to be the LBA.
927 inregs.eax.w[0] = 0x0006;
928 inregs.esi.w[0] = OFFS(__com32.cs_bounce);
929 inregs.es = SEG(__com32.cs_bounce);
930 __com32.cs_intcall(0x22, &inregs, &inregs);
932 if ((inregs.eflags.l & EFLAGS_CF) || inregs.esi.w[0] == 0) {
933 return 0; /* Filename not found */
936 /* Since the first member is the LBA, we simply cast */
937 lba = *((uint32_t *) MK_PTR(inregs.ds, inregs.esi.w[0]));
939 /* Clean the registers for the next call */
940 memset(&inregs, 0, sizeof(com32sys_t));
942 /* Put the filename in the bounce buffer */
943 strlcpy(__com32.cs_bounce, filename, __com32.cs_bounce_size);
945 /* Call comapi_close() to free the structure */
946 inregs.eax.w[0] = 0x0008;
947 inregs.esi.w[0] = OFFS(__com32.cs_bounce);
948 inregs.es = SEG(__com32.cs_bounce);
949 __com32.cs_intcall(0x22, &inregs, &inregs);
954 static void usage(void)
956 static const char usage[] = "\
957 Usage: chain.c32 [options]\n\
958 chain.c32 hd<disk#> [<partition>] [options]\n\
959 chain.c32 fd<disk#> [options]\n\
960 chain.c32 mbr:<id> [<partition>] [options]\n\
961 chain.c32 guid:<guid> [<partition>] [options]\n\
962 chain.c32 label:<label> [<partition>] [options]\n\
963 chain.c32 boot [<partition>] [options]\n\
964 chain.c32 fs [options]\n\
965 Options: file=<loader> Load and execute file, instead of boot sector\n\
966 isolinux=<loader> Load another version of ISOLINUX\n\
967 ntldr=<loader> Load Windows NTLDR, SETUPLDR.BIN or BOOTMGR\n\
968 cmldr=<loader> Load Recovery Console of Windows NT/2K/XP/2003\n\
969 freedos=<loader> Load FreeDOS KERNEL.SYS\n\
970 msdos=<loader> Load MS-DOS IO.SYS\n\
971 pcdos=<loader> Load PC-DOS IBMBIO.COM\n\
972 grub=<loader> Load GRUB Legacy stage2\n\
973 grubcfg=<filename> Set alternative config filename for GRUB Legacy\n\
974 grldr=<loader> Load GRUB4DOS grldr\n\
975 seg=<segment> Jump to <seg>:0000, instead of 0000:7C00\n\
976 swap Swap drive numbers, if bootdisk is not fd0/hd0\n\
977 hide Hide primary partitions, except selected partition\n\
978 sethidden Set the FAT/NTFS hidden sectors field\n\
979 keeppxe Keep the PXE and UNDI stacks in memory (PXELINUX)\n\
980 See syslinux/com32/modules/chain.c for more information\n";
984 int main(int argc, char *argv[])
986 struct disk_dos_mbr *mbr = NULL;
988 struct disk_part_iter *cur_part = NULL;
989 struct syslinux_rm_regs regs;
990 char *drivename, *partition;
991 int hd, drive, whichpart = 0; /* MBR by default */
993 uint64_t fs_lba = 0; /* Syslinux partition */
994 uint32_t file_lba = 0;
995 struct guid gpt_guid;
996 unsigned char *isolinux_bin;
997 uint32_t *checksum, *chkhead, *chktail;
998 struct data_area data[3];
1001 static const char cmldr_signature[8] = "cmdcons";
1003 openconsole(&dev_null_r, &dev_stdcon_w);
1008 /* Prepare the register set */
1009 memset(®s, 0, sizeof regs);
1011 for (i = 1; i < argc; i++) {
1012 if (!strncmp(argv[i], "file=", 5)) {
1013 opt.loadfile = argv[i] + 5;
1014 } else if (!strncmp(argv[i], "seg=", 4)) {
1015 uint32_t segval = strtoul(argv[i] + 4, NULL, 0);
1016 if (segval < 0x50 || segval > 0x9f000) {
1017 error("Invalid segment\n");
1021 } else if (!strncmp(argv[i], "isolinux=", 9)) {
1022 opt.loadfile = argv[i] + 9;
1023 opt.isolinux = true;
1024 } else if (!strncmp(argv[i], "ntldr=", 6)) {
1025 opt.seg = 0x2000; /* NTLDR wants this address */
1026 opt.loadfile = argv[i] + 6;
1027 opt.sethidden = true;
1028 } else if (!strncmp(argv[i], "cmldr=", 6)) {
1029 opt.seg = 0x2000; /* CMLDR wants this address */
1030 opt.loadfile = argv[i] + 6;
1032 opt.sethidden = true;
1033 } else if (!strncmp(argv[i], "freedos=", 8)) {
1034 opt.seg = 0x60; /* FREEDOS wants this address */
1035 opt.loadfile = argv[i] + 8;
1036 opt.sethidden = true;
1037 } else if (!strncmp(argv[i], "msdos=", 6) ||
1038 !strncmp(argv[i], "pcdos=", 6)) {
1039 opt.seg = 0x70; /* MS-DOS 2.0+ wants this address */
1040 opt.loadfile = argv[i] + 6;
1041 opt.sethidden = true;
1042 } else if (!strncmp(argv[i], "grub=", 5)) {
1043 opt.seg = 0x800; /* stage2 wants this address */
1044 opt.loadfile = argv[i] + 5;
1046 } else if (!strncmp(argv[i], "grubcfg=", 8)) {
1047 opt.grubcfg = argv[i] + 8;
1048 } else if (!strncmp(argv[i], "grldr=", 6)) {
1049 opt.loadfile = argv[i] + 6;
1051 } else if (!strcmp(argv[i], "swap")) {
1053 } else if (!strcmp(argv[i], "noswap")) {
1055 } else if (!strcmp(argv[i], "hide")) {
1057 } else if (!strcmp(argv[i], "nohide")) {
1059 } else if (!strcmp(argv[i], "keeppxe")) {
1061 } else if (!strcmp(argv[i], "sethidden")) {
1062 opt.sethidden = true;
1063 } else if (!strcmp(argv[i], "nosethidden")) {
1064 opt.sethidden = false;
1065 } else if (((argv[i][0] == 'h' || argv[i][0] == 'f')
1066 && argv[i][1] == 'd')
1067 || !strncmp(argv[i], "mbr:", 4)
1068 || !strncmp(argv[i], "mbr=", 4)
1069 || !strncmp(argv[i], "guid:", 5)
1070 || !strncmp(argv[i], "guid=", 5)
1071 || !strncmp(argv[i], "label:", 6)
1072 || !strncmp(argv[i], "label=", 6)
1073 || !strcmp(argv[i], "boot")
1074 || !strncmp(argv[i], "boot,", 5)
1075 || !strcmp(argv[i], "fs")) {
1076 drivename = argv[i];
1077 p = strchr(drivename, ',');
1081 } else if (argv[i + 1] && argv[i + 1][0] >= '0'
1082 && argv[i + 1][0] <= '9') {
1083 partition = argv[++i];
1091 if (opt.grubcfg && !opt.grub) {
1092 error("grubcfg=<filename> must be used together with grub=<loader>.\n");
1097 regs.es = regs.cs = regs.ss = regs.ds = regs.fs = regs.gs = opt.seg;
1099 regs.ip = regs.esp.l = 0x7c00;
1103 if (!strncmp(drivename, "mbr", 3)) {
1104 drive = find_disk(strtoul(drivename + 4, NULL, 0));
1106 error("Unable to find requested MBR signature\n");
1109 } else if (!strncmp(drivename, "guid", 4)) {
1110 if (str_to_guid(drivename + 5, &gpt_guid))
1112 drive = find_by_guid(&gpt_guid, &cur_part);
1114 error("Unable to find requested GPT disk/partition\n");
1117 } else if (!strncmp(drivename, "label", 5)) {
1118 if (!drivename[6]) {
1119 error("No label specified.\n");
1122 drive = find_by_label(drivename + 6, &cur_part);
1124 error("Unable to find requested partition by label\n");
1127 } else if ((drivename[0] == 'h' || drivename[0] == 'f') &&
1128 drivename[1] == 'd') {
1129 hd = drivename[0] == 'h';
1131 drive = (hd ? 0x80 : 0) | strtoul(drivename, NULL, 0);
1132 } else if (!strcmp(drivename, "boot") || !strcmp(drivename, "fs")) {
1133 const union syslinux_derivative_info *sdi;
1135 sdi = syslinux_derivative_info();
1136 if (sdi->c.filesystem == SYSLINUX_FS_PXELINUX)
1137 drive = 0x80; /* Boot drive not available */
1139 drive = sdi->disk.drive_number;
1140 if (!strcmp(drivename, "fs")
1141 && (sdi->c.filesystem == SYSLINUX_FS_SYSLINUX
1142 || sdi->c.filesystem == SYSLINUX_FS_EXTLINUX
1143 || sdi->c.filesystem == SYSLINUX_FS_ISOLINUX))
1144 /* We should lookup the Syslinux partition number and use it */
1145 fs_lba = *sdi->disk.partoffset;
1147 error("Unparsable drive specification\n");
1151 /* DOS kernels want the drive number in BL instead of DL. Indulge them. */
1152 regs.ebx.b[0] = regs.edx.b[0] = drive;
1154 /* Get the disk geometry and disk access setup */
1155 if (disk_get_params(drive, &diskinfo)) {
1156 error("Cannot get disk parameters\n");
1161 if (!(mbr = disk_read_sectors(&diskinfo, 0, 1))) {
1162 error("Cannot read Master Boot Record or sector 0\n");
1167 whichpart = strtoul(partition, NULL, 0);
1168 /* "guid:" or "label:" might have specified a partition */
1170 whichpart = cur_part->index;
1172 /* Boot the MBR by default */
1173 if (!cur_part && (whichpart || fs_lba)) {
1174 /* Boot a partition, possibly the Syslinux partition itself */
1175 cur_part = get_first_partition(NULL);
1177 if ((cur_part->index == whichpart)
1178 || (cur_part->lba_data == fs_lba))
1179 /* Found the partition to boot */
1181 cur_part = cur_part->next(cur_part);
1184 error("Requested partition not found!\n");
1187 whichpart = cur_part->index;
1190 if (!(drive & 0x80) && whichpart) {
1191 error("Warning: Partitions of floppy devices may not work\n");
1195 * GRLDR of GRUB4DOS wants the partition number in DH:
1196 * -1: whole drive (default)
1197 * 0-3: primary partitions
1198 * 4-*: logical partitions
1201 regs.edx.b[1] = whichpart - 1;
1204 if (whichpart < 1 || whichpart > 4)
1205 error("WARNING: hide specified without a non-primary partition\n");
1206 if (hide_unhide(mbr, whichpart))
1207 error("WARNING: failed to write MBR for 'hide'\n");
1210 /* Do the actual chainloading */
1211 load_base = opt.seg ? (opt.seg << 4) : 0x7c00;
1214 fputs("Loading the boot file...\n", stdout);
1215 if (loadfile(opt.loadfile, &data[ndata].data, &data[ndata].size)) {
1216 error("Failed to load the boot file\n");
1219 data[ndata].base = load_base;
1220 load_base = 0x7c00; /* If we also load a boot sector */
1222 /* Create boot info table: needed when you want to chainload
1223 another version of ISOLINUX (or another bootlaoder that needs
1224 the -boot-info-table switch of mkisofs)
1225 (will only work when run from ISOLINUX) */
1227 const union syslinux_derivative_info *sdi;
1228 sdi = syslinux_derivative_info();
1230 if (sdi->c.filesystem == SYSLINUX_FS_ISOLINUX) {
1231 /* Boot info table info (integers in little endian format)
1233 Offset Name Size Meaning
1234 8 bi_pvd 4 bytes LBA of primary volume descriptor
1235 12 bi_file 4 bytes LBA of boot file
1236 16 bi_length 4 bytes Boot file length in bytes
1237 20 bi_csum 4 bytes 32-bit checksum
1238 24 bi_reserved 40 bytes Reserved
1240 The 32-bit checksum is the sum of all the 32-bit words in the
1241 boot file starting at byte offset 64. All linear block
1242 addresses (LBAs) are given in CD sectors (normally 2048 bytes).
1244 LBA of primary volume descriptor should already be set to 16.
1247 isolinux_bin = (unsigned char *)data[ndata].data;
1249 /* Get LBA address of bootfile */
1250 file_lba = get_file_lba(opt.loadfile);
1252 if (file_lba == 0) {
1253 error("Failed to find LBA offset of the boot file\n");
1257 *((uint32_t *) & isolinux_bin[12]) = file_lba;
1259 /* Set boot file length */
1260 *((uint32_t *) & isolinux_bin[16]) = data[ndata].size;
1262 /* Calculate checksum */
1263 checksum = (uint32_t *) & isolinux_bin[20];
1264 chkhead = (uint32_t *) & isolinux_bin[64];
1265 chktail = (uint32_t *) & isolinux_bin[data[ndata].size & ~3];
1267 while (chkhead < chktail)
1268 *checksum += *chkhead++;
1271 * Deal with possible fractional dword at the end;
1272 * this *should* never happen...
1274 if (data[ndata].size & 3) {
1276 memcpy(&xword, chkhead, data[ndata].size & 3);
1281 ("The isolinux= option is only valid when run from ISOLINUX\n");
1287 /* Layout of stage2 file (from byte 0x0 to 0x270) */
1288 struct grub_stage2_patch_area {
1290 char unknown[0x206];
1291 /* 0x206: compatibility version number major */
1292 uint8_t compat_version_major;
1293 /* 0x207: compatibility version number minor */
1294 uint8_t compat_version_minor;
1296 /* 0x208: install_partition variable */
1298 /* 0x208: sub-partition in sub-partition part2 */
1300 /* 0x209: sub-partition in top-level partition */
1302 /* 0x20a: top-level partiton number */
1304 /* 0x20b: BIOS drive number (must be 0) */
1306 } __attribute__ ((packed)) install_partition;
1308 /* 0x20c: deprecated (historical reason only) */
1309 uint32_t saved_entryno;
1310 /* 0x210: stage2_ID: will always be STAGE2_ID_STAGE2 = 0 in stage2 */
1312 /* 0x211: force LBA */
1314 /* 0x212: version string (will probably be 0.97) */
1315 char version_string[5];
1316 /* 0x217: config filename */
1317 char config_file[89];
1318 /* 0x270: start of code (after jump from 0x200) */
1320 } __attribute__ ((packed)) *stage2;
1322 if (data[ndata].size < sizeof(struct grub_stage2_patch_area)) {
1324 ("The file specified by grub=<loader> is to small to be stage2 of GRUB Legacy.\n");
1328 stage2 = data[ndata].data;
1331 * Check the compatibility version number to see if we loaded a real
1332 * stage2 file or a stage2 file that we support.
1334 if (stage2->compat_version_major != 3
1335 || stage2->compat_version_minor != 2) {
1337 ("The file specified by grub=<loader> is not a supported stage2 GRUB Legacy binary\n");
1341 /* jump 0x200 bytes into the loadfile */
1345 * GRUB Legacy wants the partition number in the install_partition
1346 * variable, located at offset 0x208 of stage2.
1347 * When GRUB Legacy is loaded, it is located at memory address 0x8208.
1349 * It looks very similar to the "boot information format" of the
1350 * Multiboot specification:
1351 * http://www.gnu.org/software/grub/manual/multiboot/multiboot.html#Boot-information-format
1353 * 0x208 = part3: sub-partition in sub-partition part2
1354 * 0x209 = part2: sub-partition in top-level partition
1355 * 0x20a = part1: top-level partition number
1356 * 0x20b = drive: BIOS drive number (must be 0)
1358 * GRUB Legacy doesn't store the BIOS drive number at 0x20b, but at
1361 * Partition numbers always start from zero.
1362 * Unused partition bytes must be set to 0xFF.
1364 * We only care about top-level partition, so we only need to change
1365 * "part1" to the appropriate value:
1366 * -1: whole drive (default) (-1 = 0xFF)
1367 * 0-3: primary partitions
1368 * 4-*: logical partitions
1370 stage2->install_partition.part1 = whichpart - 1;
1373 * Grub Legacy reserves 89 bytes (from 0x8217 to 0x826f) for the
1374 * config filename. The filename passed via grubcfg= will overwrite
1375 * the default config filename "/boot/grub/menu.lst".
1378 if (strlen(opt.grubcfg) > sizeof(stage2->config_file) - 1) {
1380 ("The config filename length can't exceed 88 characters.\n");
1384 strcpy((char *)stage2->config_file, opt.grubcfg);
1391 if (!opt.loadfile || data[0].base >= 0x7c00 + SECTOR) {
1392 /* Actually read the boot sector */
1394 data[ndata].data = mbr;
1398 disk_read_sectors(&diskinfo, cur_part->lba_data, 1))) {
1399 error("Cannot read boot sector\n");
1402 data[ndata].size = SECTOR;
1403 data[ndata].base = load_base;
1405 if (!opt.loadfile) {
1406 const struct disk_dos_mbr *br =
1407 (const struct disk_dos_mbr *)((char *)data[ndata].data +
1409 sizeof(struct disk_dos_mbr));
1410 if (br->sig != disk_mbr_sig_magic) {
1412 ("Boot sector signature not found (unbootable disk/partition?)\n");
1417 * To boot the Recovery Console of Windows NT/2K/XP we need to write
1418 * the string "cmdcons\0" to memory location 0000:7C03.
1419 * Memory location 0000:7C00 contains the bootsector of the partition.
1421 if (cur_part && opt.cmldr) {
1422 memcpy((char *)data[ndata].data + 3, cmldr_signature,
1423 sizeof cmldr_signature);
1427 * Modify the hidden sectors (partition offset) copy in memory;
1428 * this modifies the field used by FAT and NTFS filesystems, and
1429 * possibly other boot loaders which use the same format.
1431 if (cur_part && opt.sethidden) {
1432 *(uint32_t *) ((char *)data[ndata].data + 28) = cur_part->lba_data;
1439 if (cur_part->next == next_gpt_part) {
1440 /* Do GPT hand-over, if applicable (as per syslinux/doc/gpt.txt) */
1441 struct disk_dos_part_entry *record;
1442 /* Look at the GPT partition */
1443 const struct gpt_part *gp = (const struct gpt_part *)
1445 (cur_part->private.gpt.size * cur_part->private.gpt.index));
1446 /* Note the partition length */
1447 uint64_t lba_count = gp->lba_last - gp->lba_first + 1;
1448 /* The length of the hand-over */
1450 sizeof(struct disk_dos_part_entry) + sizeof(uint32_t) +
1451 cur_part->private.gpt.size;
1452 /* Will point to the partition record length in the hand-over */
1455 /* Allocate the hand-over record */
1456 record = malloc(synth_size);
1458 error("Could not build GPT hand-over record!\n");
1461 /* Synthesize the record */
1462 memset(record, 0, synth_size);
1463 record->active_flag = 0x80;
1464 record->ostype = 0xED;
1465 /* All bits set by default */
1466 record->start_lba = ~(uint32_t) 0;
1467 record->length = ~(uint32_t) 0;
1468 /* If these fit the precision, pass them on */
1469 if (cur_part->lba_data < record->start_lba)
1470 record->start_lba = cur_part->lba_data;
1471 if (lba_count < record->length)
1472 record->length = lba_count;
1473 /* Next comes the GPT partition record length */
1474 plen = (uint32_t *) (record + 1);
1475 plen[0] = cur_part->private.gpt.size;
1476 /* Next comes the GPT partition record copy */
1477 memcpy(plen + 1, gp, plen[0]);
1478 cur_part->record = record;
1480 regs.eax.l = 0x54504721; /* '!GPT' */
1481 data[ndata].base = 0x7be;
1482 data[ndata].size = synth_size;
1483 data[ndata].data = (void *)record;
1485 regs.esi.w[0] = 0x7be;
1487 dprintf("GPT handover:\n");
1488 disk_dos_part_dump(record);
1489 gpt_part_dump((struct gpt_part *)(plen + 1));
1490 } else if (cur_part->record) {
1491 /* MBR handover protocol */
1492 static struct disk_dos_part_entry handover_record;
1494 handover_record = *cur_part->record;
1495 handover_record.start_lba = cur_part->lba_data;
1497 data[ndata].base = 0x7be;
1498 data[ndata].size = sizeof handover_record;
1499 data[ndata].data = &handover_record;
1501 regs.esi.w[0] = 0x7be;
1503 dprintf("MBR handover:\n");
1504 disk_dos_part_dump(&handover_record);
1508 do_boot(data, ndata, ®s);
1512 free(cur_part->block);
1513 free((void *)cur_part->record);