diff options
Diffstat (limited to 'contrib/syslinux-4.02/com32/modules/chain.c')
-rw-r--r-- | contrib/syslinux-4.02/com32/modules/chain.c | 1826 |
1 files changed, 1826 insertions, 0 deletions
diff --git a/contrib/syslinux-4.02/com32/modules/chain.c b/contrib/syslinux-4.02/com32/modules/chain.c new file mode 100644 index 0000000..48a83d2 --- /dev/null +++ b/contrib/syslinux-4.02/com32/modules/chain.c @@ -0,0 +1,1826 @@ +/* ----------------------------------------------------------------------- * + * + * Copyright 2003-2009 H. Peter Anvin - All Rights Reserved + * Copyright 2009-2010 Intel Corporation; author: H. Peter Anvin + * Significant portions copyright (C) 2010 Shao Miller + * [partition iteration, GPT, "fs"] + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, Inc., 53 Temple Place Ste 330, + * Boston MA 02111-1307, USA; either version 2 of the License, or + * (at your option) any later version; incorporated herein by reference. + * + * ----------------------------------------------------------------------- */ + +/* + * chain.c + * + * Chainload a hard disk (currently rather braindead.) + * + * Usage: chain [options] + * chain hd<disk#> [<partition>] [options] + * chain fd<disk#> [options] + * chain mbr:<id> [<partition>] [options] + * chain guid:<guid> [<partition>] [options] + * chain label:<label> [<partition>] [options] + * chain boot [<partition>] [options] + * + * For example, "chain msdos=io.sys" will load DOS from the current Syslinux + * filesystem. "chain hd0 1" will boot the first partition on the first hard + * disk. + * + * When none of the "hdX", "fdX", "mbr:", "guid:", "label:", "boot" or "fs" + * options are specified, the default behaviour is equivalent to "boot". + * "boot" means to use the current Syslinux drive, and you can also specify + * a partition. + * + * The mbr: syntax means search all the hard disks until one with a + * specific MBR serial number (bytes 440-443) is found. + * + * Partitions 1-4 are primary, 5+ logical, 0 = boot MBR (default.) + * + * "fs" will use the current Syslinux filesystem as the boot drive/partition. + * When booting from PXELINUX, you will most likely wish to specify a disk. + * + * Options: + * + * 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. + * + * isolinux=<loader> + * chainload another version/build of the ISOLINUX bootloader and patch + * the loader with appropriate parameters in memory. + * This avoids the need for the -eltorito-alt-boot parameter of mkisofs, + * when you want more than one ISOLINUX per CD/DVD. + * + * ntldr=<loader> + * equivalent to seg=0x2000 file=<loader> sethidden, + * used with WinNT's loaders + * + * 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 + * + * freedos=<loader> + * equivalent to seg=0x60 file=<loader> sethidden, + * used with FreeDOS' kernel.sys. + * + * msdos=<loader> + * pcdos=<loader> + * equivalent to seg=0x70 file=<loader> sethidden, + * used with DOS' io.sys. + * + * grub=<loader> + * same as seg=0x800 file=<loader> & jumping to seg 0x820, + * used with GRUB Legacy stage2 files. + * + * grubcfg=<filename> + * set an alternative config filename in stage2 of Grub Legacy, + * only applicable in combination with "grub=<loader>". + * + * grldr=<loader> + * pass the partition number to GRUB4DOS, + * used with GRUB4DOS' grldr. + * + * swap + * if the disk is not fd0/hd0, install a BIOS stub which swaps + * the drive numbers. + * + * 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. + * + * sethidden + * update the "hidden sectors" (partition offset) field in a + * FAT/NTFS boot sector. + * + * keeppxe + * keep the PXE and UNDI stacks in memory (PXELINUX only). + */ + +#include <com32.h> +#include <stdlib.h> +#include <stdio.h> +#include <ctype.h> +#include <string.h> +#include <console.h> +#include <minmax.h> +#include <stdbool.h> +#include <dprintf.h> +#include <syslinux/loadfile.h> +#include <syslinux/bootrm.h> +#include <syslinux/config.h> +#include <syslinux/video.h> + +#define SECTOR 512 /* bytes/sector */ + +static struct options { + const char *loadfile; + uint16_t keeppxe; + uint16_t seg; + bool isolinux; + bool cmldr; + bool grub; + bool grldr; + const char *grubcfg; + bool swap; + bool hide; + bool sethidden; +} opt; + +struct data_area { + void *data; + addr_t base; + addr_t size; +}; + +static inline void error(const char *msg) +{ + fputs(msg, stderr); +} + +/* + * Call int 13h, but with retry on failure. Especially floppies need this. + */ +static int int13_retry(const com32sys_t * inreg, com32sys_t * outreg) +{ + int retry = 6; /* Number of retries */ + com32sys_t tmpregs; + + if (!outreg) + outreg = &tmpregs; + + while (retry--) { + __intcall(0x13, inreg, outreg); + if (!(outreg->eflags.l & EFLAGS_CF)) + return 0; /* CF=0, OK */ + } + + return -1; /* Error */ +} + +/* + * Query disk parameters and EBIOS availability for a particular disk. + */ +struct diskinfo { + int disk; + int ebios; /* EBIOS supported on this disk */ + int cbios; /* CHS geometry is valid */ + int head; + int sect; +} disk_info; + +static int get_disk_params(int disk) +{ + static com32sys_t getparm, parm, getebios, ebios; + + disk_info.disk = disk; + disk_info.ebios = disk_info.cbios = 0; + + /* Get EBIOS support */ + getebios.eax.w[0] = 0x4100; + getebios.ebx.w[0] = 0x55aa; + getebios.edx.b[0] = disk; + getebios.eflags.b[0] = 0x3; /* CF set */ + + __intcall(0x13, &getebios, &ebios); + + if (!(ebios.eflags.l & EFLAGS_CF) && + ebios.ebx.w[0] == 0xaa55 && (ebios.ecx.b[0] & 1)) { + disk_info.ebios = 1; + } + + /* Get disk parameters -- really only useful for + hard disks, but if we have a partitioned floppy + it's actually our best chance... */ + getparm.eax.b[1] = 0x08; + getparm.edx.b[0] = disk; + + __intcall(0x13, &getparm, &parm); + + if (parm.eflags.l & EFLAGS_CF) + return disk_info.ebios ? 0 : -1; + + disk_info.head = parm.edx.b[1] + 1; + disk_info.sect = parm.ecx.b[0] & 0x3f; + if (disk_info.sect == 0) { + disk_info.sect = 1; + } else { + disk_info.cbios = 1; /* Valid geometry */ + } + + return 0; +} + +/* + * Get a disk block and return a malloc'd buffer. + * Uses the disk number and information from disk_info. + */ +struct ebios_dapa { + uint16_t len; + uint16_t count; + uint16_t off; + uint16_t seg; + uint64_t lba; +}; + +/* Read count sectors from drive, starting at lba. Return a new buffer */ +static void *read_sectors(uint64_t lba, uint8_t count) +{ + com32sys_t inreg; + struct ebios_dapa *dapa = __com32.cs_bounce; + void *buf = (char *)__com32.cs_bounce + SECTOR; + void *data; + + if (!count) + /* Silly */ + return NULL; + + memset(&inreg, 0, sizeof inreg); + + if (disk_info.ebios) { + dapa->len = sizeof(*dapa); + dapa->count = count; + dapa->off = OFFS(buf); + dapa->seg = SEG(buf); + dapa->lba = lba; + + inreg.esi.w[0] = OFFS(dapa); + inreg.ds = SEG(dapa); + inreg.edx.b[0] = disk_info.disk; + inreg.eax.b[1] = 0x42; /* Extended read */ + } else { + unsigned int c, h, s, t; + + if (!disk_info.cbios) { + /* We failed to get the geometry */ + + if (lba) + return NULL; /* Can only read MBR */ + + s = 1; + h = 0; + c = 0; + } else { + s = (lba % disk_info.sect) + 1; + t = lba / disk_info.sect; /* Track = head*cyl */ + h = t % disk_info.head; + c = t / disk_info.head; + } + + if (s > 63 || h > 256 || c > 1023) + return NULL; + + inreg.eax.b[0] = count; + inreg.eax.b[1] = 0x02; /* Read */ + inreg.ecx.b[1] = c & 0xff; + inreg.ecx.b[0] = s + (c >> 6); + inreg.edx.b[1] = h; + inreg.edx.b[0] = disk_info.disk; + inreg.ebx.w[0] = OFFS(buf); + inreg.es = SEG(buf); + } + + if (int13_retry(&inreg, NULL)) + return NULL; + + data = malloc(count * SECTOR); + if (data) + memcpy(data, buf, count * SECTOR); + return data; +} + +static int write_sector(unsigned int lba, const void *data) +{ + com32sys_t inreg; + struct ebios_dapa *dapa = __com32.cs_bounce; + void *buf = (char *)__com32.cs_bounce + SECTOR; + + memcpy(buf, data, SECTOR); + memset(&inreg, 0, sizeof inreg); + + if (disk_info.ebios) { + dapa->len = sizeof(*dapa); + dapa->count = 1; /* 1 sector */ + dapa->off = OFFS(buf); + dapa->seg = SEG(buf); + dapa->lba = lba; + + inreg.esi.w[0] = OFFS(dapa); + inreg.ds = SEG(dapa); + inreg.edx.b[0] = disk_info.disk; + inreg.eax.w[0] = 0x4300; /* Extended write */ + } else { + unsigned int c, h, s, t; + + if (!disk_info.cbios) { + /* We failed to get the geometry */ + + if (lba) + return -1; /* Can only write MBR */ + + s = 1; + h = 0; + c = 0; + } else { + s = (lba % disk_info.sect) + 1; + t = lba / disk_info.sect; /* Track = head*cyl */ + h = t % disk_info.head; + c = t / disk_info.head; + } + + if (s > 63 || h > 256 || c > 1023) + return -1; + + inreg.eax.w[0] = 0x0301; /* Write one sector */ + inreg.ecx.b[1] = c & 0xff; + inreg.ecx.b[0] = s + (c >> 6); + inreg.edx.b[1] = h; + inreg.edx.b[0] = disk_info.disk; + inreg.ebx.w[0] = OFFS(buf); + inreg.es = SEG(buf); + } + + if (int13_retry(&inreg, NULL)) + return -1; + + return 0; /* ok */ +} + +static int write_verify_sector(unsigned int lba, const void *buf) +{ + char *rb; + int rv; + + rv = write_sector(lba, buf); + if (rv) + return rv; /* Write failure */ + rb = read_sectors(lba, 1); + if (!rb) + return -1; /* Readback failure */ + rv = memcmp(buf, rb, SECTOR); + free(rb); + return rv ? -1 : 0; +} + +/* + * CHS (cylinder, head, sector) value extraction macros. + * Taken from WinVBlock. Does not expand to an lvalue +*/ +#define chs_head(chs) chs[0] +#define chs_sector(chs) (chs[1] & 0x3F) +#define chs_cyl_high(chs) (((uint16_t)(chs[1] & 0xC0)) << 2) +#define chs_cyl_low(chs) ((uint16_t)chs[2]) +#define chs_cylinder(chs) (chs_cyl_high(chs) | chs_cyl_low(chs)) +typedef uint8_t chs[3]; + +/* A DOS partition table entry */ +struct part_entry { + uint8_t active_flag; /* 0x80 if "active" */ + chs start; + uint8_t ostype; + chs end; + uint32_t start_lba; + uint32_t length; +} __attribute__ ((packed)); + +static void mbr_part_dump(const struct part_entry *part) +{ + (void)part; + dprintf("Partition status _____ : 0x%.2x\n" + "Partition CHS start\n" + " Cylinder ___________ : 0x%.4x (%u)\n" + " Head _______________ : 0x%.2x (%u)\n" + " Sector _____________ : 0x%.2x (%u)\n" + "Partition type _______ : 0x%.2x\n" + "Partition CHS end\n" + " Cylinder ___________ : 0x%.4x (%u)\n" + " Head _______________ : 0x%.2x (%u)\n" + " Sector _____________ : 0x%.2x (%u)\n" + "Partition LBA start __ : 0x%.8x (%u)\n" + "Partition LBA count __ : 0x%.8x (%u)\n" + "-------------------------------\n", + part->active_flag, + chs_cylinder(part->start), + chs_cylinder(part->start), + chs_head(part->start), + chs_head(part->start), + chs_sector(part->start), + chs_sector(part->start), + part->ostype, + chs_cylinder(part->end), + chs_cylinder(part->end), + chs_head(part->end), + chs_head(part->end), + chs_sector(part->end), + chs_sector(part->end), + part->start_lba, + part->start_lba, + part->length, + part->length); +} + +/* A DOS MBR */ +struct mbr { + char code[440]; + uint32_t disk_sig; + char pad[2]; + struct part_entry table[4]; + uint16_t sig; +} __attribute__ ((packed)); +static const uint16_t mbr_sig_magic = 0xAA55; + +/* Search for a specific drive, based on the MBR signature; bytes 440-443 */ +static int find_disk(uint32_t mbr_sig) +{ + int drive; + bool is_me; + struct mbr *mbr; + + for (drive = 0x80; drive <= 0xff; drive++) { + if (get_disk_params(drive)) + continue; /* Drive doesn't exist */ + if (!(mbr = read_sectors(0, 1))) + continue; /* Cannot read sector */ + is_me = (mbr->disk_sig == mbr_sig); + free(mbr); + if (is_me) + return drive; + } + return -1; +} + +/* Forward declaration */ +struct disk_part_iter; + +/* Partition-/scheme-specific routine returning the next partition */ +typedef struct disk_part_iter *(*disk_part_iter_func) (struct disk_part_iter * + part); + +/* Contains details for a partition under examination */ +struct disk_part_iter { + /* The block holding the table we are part of */ + char *block; + /* The LBA for the beginning of data */ + uint64_t lba_data; + /* The partition number, as determined by our heuristic */ + int index; + /* The DOS partition record to pass, if applicable */ + const struct part_entry *record; + /* Function returning the next available partition */ + disk_part_iter_func next; + /* Partition-/scheme-specific details */ + union { + /* MBR specifics */ + int mbr_index; + /* EBR specifics */ + struct { + /* The first extended partition's start LBA */ + uint64_t lba_extended; + /* Any applicable parent, or NULL */ + struct disk_part_iter *parent; + /* The parent extended partition index */ + int parent_index; + } ebr; + /* GPT specifics */ + struct { + /* Real (not effective) index in the partition table */ + int index; + /* Current partition GUID */ + const struct guid *part_guid; + /* Current partition label */ + const char *part_label; + /* Count of entries in GPT */ + int parts; + /* Partition record size */ + uint32_t size; + } gpt; + } private; +}; + +static struct disk_part_iter *next_ebr_part(struct disk_part_iter *part) +{ + const struct part_entry *ebr_table; + const struct part_entry *parent_table = + ((const struct mbr *)part->private.ebr.parent->block)->table; + static const struct part_entry phony = {.start_lba = 0 }; + uint64_t ebr_lba; + + /* Don't look for a "next EBR" the first time around */ + if (part->private.ebr.parent_index >= 0) + /* Look at the linked list */ + ebr_table = ((const struct mbr *)part->block)->table + 1; + /* Do we need to look for an extended partition? */ + if (part->private.ebr.parent_index < 0 || !ebr_table->start_lba) { + /* Start looking for an extended partition in the MBR */ + while (++part->private.ebr.parent_index < 4) { + uint8_t type = parent_table[part->private.ebr.parent_index].ostype; + + if ((type == 0x05) || (type == 0x0F) || (type == 0x85)) + break; + } + if (part->private.ebr.parent_index == 4) + /* No extended partitions found */ + goto out_finished; + part->private.ebr.lba_extended = + parent_table[part->private.ebr.parent_index].start_lba; + ebr_table = &phony; + } + /* Load next EBR */ + ebr_lba = ebr_table->start_lba + part->private.ebr.lba_extended; + free(part->block); + part->block = read_sectors(ebr_lba, 1); + if (!part->block) { + error("Could not load EBR!\n"); + goto err_ebr; + } + ebr_table = ((const struct mbr *)part->block)->table; + dprintf("next_ebr_part:\n"); + mbr_part_dump(ebr_table); + + /* + * Sanity check entry: must not extend outside the + * extended partition. This is necessary since some OSes + * put crap in some entries. + */ + { + const struct mbr *mbr = + (const struct mbr *)part->private.ebr.parent->block; + const struct part_entry *extended = + mbr->table + part->private.ebr.parent_index; + + if (ebr_table[0].start_lba >= extended->start_lba + extended->length) { + dprintf("Insane logical partition!\n"); + goto err_insane; + } + } + /* Success */ + part->lba_data = ebr_table[0].start_lba + ebr_lba; + dprintf("Partition %d logical lba %u\n", part->index, part->lba_data); + part->index++; + part->record = ebr_table; + return part; + +err_insane: + + free(part->block); + part->block = NULL; +err_ebr: + +out_finished: + free(part->private.ebr.parent->block); + free(part->private.ebr.parent); + free(part->block); + free(part); + return NULL; +} + +static struct disk_part_iter *next_mbr_part(struct disk_part_iter *part) +{ + struct disk_part_iter *ebr_part; + /* Look at the partition table */ + struct part_entry *table = ((struct mbr *)part->block)->table; + + /* Look for data partitions */ + while (++part->private.mbr_index < 4) { + uint8_t type = table[part->private.mbr_index].ostype; + + if (type == 0x00 || type == 0x05 || type == 0x0F || type == 0x85) + /* Skip empty or extended partitions */ + continue; + if (!table[part->private.mbr_index].length) + /* Empty */ + continue; + break; + } + /* If we're currently the last partition, it's time for EBR processing */ + if (part->private.mbr_index == 4) { + /* Allocate another iterator for extended partitions */ + ebr_part = malloc(sizeof(*ebr_part)); + if (!ebr_part) { + error("Could not allocate extended partition iterator!\n"); + goto err_alloc; + } + /* Setup EBR iterator parameters */ + ebr_part->block = NULL; + ebr_part->index = 4; + ebr_part->record = NULL; + ebr_part->next = next_ebr_part; + ebr_part->private.ebr.parent = part; + /* Trigger an initial EBR load */ + ebr_part->private.ebr.parent_index = -1; + /* The EBR iterator is responsible for freeing us */ + return next_ebr_part(ebr_part); + } + dprintf("next_mbr_part:\n"); + mbr_part_dump(table + part->private.mbr_index); + + /* Update parameters to reflect this new partition. Re-use iterator */ + part->lba_data = table[part->private.mbr_index].start_lba; + dprintf("Partition %d primary lba %u\n", part->index, part->lba_data); + part->index++; + part->record = table + part->private.mbr_index; + return part; + + free(ebr_part); +err_alloc: + + free(part->block); + free(part); + return NULL; +} + +/* + * GUID + * Be careful with endianness, you must adjust it yourself + * iff you are directly using the fourth data chunk + */ +struct guid { + uint32_t data1; + uint16_t data2; + uint16_t data3; + uint64_t data4; +} __attribute__ ((packed)); + + /* + * This walk-map effectively reverses the little-endian + * portions of the GUID in the output text + */ +static const char guid_le_walk_map[] = { + 3, -1, -1, -1, 0, + 5, -1, 0, + 3, -1, 0, + 2, 1, 0, + 1, 1, 1, 1, 1, 1 +}; + +#if DEBUG +/* + * Fill a buffer with a textual GUID representation. + * The buffer must be >= char[37] and will be populated + * with an ASCII NUL C string terminator. + * Example: 11111111-2222-3333-4444-444444444444 + * Endian: LLLLLLLL-LLLL-LLLL-BBBB-BBBBBBBBBBBB + */ +static void guid_to_str(char *buf, const struct guid *id) +{ + unsigned int i = 0; + const char *walker = (const char *)id; + + while (i < sizeof(guid_le_walk_map)) { + walker += guid_le_walk_map[i]; + if (!guid_le_walk_map[i]) + *buf = '-'; + else { + *buf = ((*walker & 0xF0) >> 4) + '0'; + if (*buf > '9') + *buf += 'A' - '9' - 1; + buf++; + *buf = (*walker & 0x0F) + '0'; + if (*buf > '9') + *buf += 'A' - '9' - 1; + } + buf++; + i++; + } + *buf = 0; +} +#endif + +/* + * Create a GUID structure from a textual GUID representation. + * The input buffer must be >= 32 hexadecimal chars and be + * terminated with an ASCII NUL. Returns non-zero on failure. + * Example: 11111111-2222-3333-4444-444444444444 + * Endian: LLLLLLLL-LLLL-LLLL-BBBB-BBBBBBBBBBBB + */ +static int str_to_guid(const char *buf, struct guid *id) +{ + char guid_seq[sizeof(struct guid) * 2]; + unsigned int i = 0; + char *walker = (char *)id; + + while (*buf && i < sizeof(guid_seq)) { + switch (*buf) { + /* Skip these three characters */ + case '{': + case '}': + case '-': + break; + default: + /* Copy something useful to the temp. sequence */ + if ((*buf >= '0') && (*buf <= '9')) + guid_seq[i] = *buf - '0'; + else if ((*buf >= 'A') && (*buf <= 'F')) + guid_seq[i] = *buf - 'A' + 10; + else if ((*buf >= 'a') && (*buf <= 'f')) + guid_seq[i] = *buf - 'a' + 10; + else { + /* Or not */ + error("Illegal character in GUID!\n"); + return -1; + } + i++; + } + buf++; + } + /* Check for insufficient valid characters */ + if (i < sizeof(guid_seq)) { + error("Too few GUID characters!\n"); + return -1; + } + buf = guid_seq; + i = 0; + while (i < sizeof(guid_le_walk_map)) { + if (!guid_le_walk_map[i]) + i++; + walker += guid_le_walk_map[i]; + *walker = *buf << 4; + buf++; + *walker |= *buf; + buf++; + i++; + } + return 0; +} + +/* A GPT partition */ +struct gpt_part { + struct guid type; + struct guid uid; + uint64_t lba_first; + uint64_t lba_last; + uint64_t attribs; + char name[72]; +} __attribute__ ((packed)); + +static void gpt_part_dump(const struct gpt_part *gpt_part) +{ +#ifdef DEBUG + unsigned int i; + char guid_text[37]; + + dprintf("----------------------------------\n" + "GPT part. LBA first __ : 0x%.16llx\n" + "GPT part. LBA last ___ : 0x%.16llx\n" + "GPT part. attribs ____ : 0x%.16llx\n" + "GPT part. name _______ : '", + gpt_part->lba_first, gpt_part->lba_last, gpt_part->attribs); + for (i = 0; i < sizeof(gpt_part->name); i++) { + if (gpt_part->name[i]) + dprintf("%c", gpt_part->name[i]); + } + dprintf("'"); + guid_to_str(guid_text, &gpt_part->type); + dprintf("GPT part. type GUID __ : {%s}\n", guid_text); + guid_to_str(guid_text, &gpt_part->uid); + dprintf("GPT part. unique ID __ : {%s}\n", guid_text); +#endif + (void)gpt_part; +} + +/* A GPT header */ +struct gpt { + char sig[8]; + union { + struct { + uint16_t minor; + uint16_t major; + } fields __attribute__ ((packed)); + uint32_t uint32; + char raw[4]; + } rev __attribute__ ((packed)); + uint32_t hdr_size; + uint32_t chksum; + char reserved1[4]; + uint64_t lba_cur; + uint64_t lba_alt; + uint64_t lba_first_usable; + uint64_t lba_last_usable; + struct guid disk_guid; + uint64_t lba_table; + uint32_t part_count; + uint32_t part_size; + uint32_t table_chksum; + char reserved2[1]; +} __attribute__ ((packed)); +static const char gpt_sig_magic[] = "EFI PART"; + +#if DEBUG +static void gpt_dump(const struct gpt *gpt) +{ + char guid_text[37]; + + printf("GPT sig ______________ : '%8.8s'\n" + "GPT major revision ___ : 0x%.4x\n" + "GPT minor revision ___ : 0x%.4x\n" + "GPT header size ______ : 0x%.8x\n" + "GPT header checksum __ : 0x%.8x\n" + "GPT reserved _________ : '%4.4s'\n" + "GPT LBA current ______ : 0x%.16llx\n" + "GPT LBA alternative __ : 0x%.16llx\n" + "GPT LBA first usable _ : 0x%.16llx\n" + "GPT LBA last usable __ : 0x%.16llx\n" + "GPT LBA part. table __ : 0x%.16llx\n" + "GPT partition count __ : 0x%.8x\n" + "GPT partition size ___ : 0x%.8x\n" + "GPT part. table chksum : 0x%.8x\n", + gpt->sig, + gpt->rev.fields.major, + gpt->rev.fields.minor, + gpt->hdr_size, + gpt->chksum, + gpt->reserved1, + gpt->lba_cur, + gpt->lba_alt, + gpt->lba_first_usable, + gpt->lba_last_usable, + gpt->lba_table, gpt->part_count, gpt->part_size, gpt->table_chksum); + guid_to_str(guid_text, &gpt->disk_guid); + printf("GPT disk GUID ________ : {%s}\n", guid_text); +} +#endif + +static struct disk_part_iter *next_gpt_part(struct disk_part_iter *part) +{ + const struct gpt_part *gpt_part = NULL; + + while (++part->private.gpt.index < part->private.gpt.parts) { + gpt_part = + (const struct gpt_part *)(part->block + + (part->private.gpt.index * + part->private.gpt.size)); + if (!gpt_part->lba_first) + continue; + break; + } + /* Were we the last partition? */ + if (part->private.gpt.index == part->private.gpt.parts) { + goto err_last; + } + part->lba_data = gpt_part->lba_first; + part->private.gpt.part_guid = &gpt_part->uid; + part->private.gpt.part_label = gpt_part->name; + /* Update our index */ + part->index++; + gpt_part_dump(gpt_part); + + /* In a GPT scheme, we re-use the iterator */ + return part; + +err_last: + free(part->block); + free(part); + + return NULL; +} + +static struct disk_part_iter *get_first_partition(struct disk_part_iter *part) +{ + const struct gpt *gpt_candidate; + + /* + * Ignore any passed partition iterator. The caller should + * have passed NULL. Allocate a new partition iterator + */ + part = malloc(sizeof(*part)); + if (!part) { + error("Count not allocate partition iterator!\n"); + goto err_alloc_iter; + } + /* Read MBR */ + part->block = read_sectors(0, 2); + if (!part->block) { + error("Could not read two sectors!\n"); + goto err_read_mbr; + } + /* Check for an MBR */ + if (((struct mbr *)part->block)->sig != mbr_sig_magic) { + error("No MBR magic!\n"); + goto err_mbr; + } + /* Establish a pseudo-partition for the MBR (index 0) */ + part->index = 0; + part->record = NULL; + part->private.mbr_index = -1; + part->next = next_mbr_part; + /* Check for a GPT disk */ + gpt_candidate = (const struct gpt *)(part->block + SECTOR); + if (!memcmp(gpt_candidate->sig, gpt_sig_magic, sizeof(gpt_sig_magic))) { + /* LBA for partition table */ + uint64_t lba_table; + + /* It looks like one */ + /* TODO: Check checksum. Possibly try alternative GPT */ +#if DEBUG + puts("Looks like a GPT disk."); + gpt_dump(gpt_candidate); +#endif + /* TODO: Check table checksum (maybe) */ + /* Note relevant GPT details */ + part->next = next_gpt_part; + part->private.gpt.index = -1; + part->private.gpt.parts = gpt_candidate->part_count; + part->private.gpt.size = gpt_candidate->part_size; + lba_table = gpt_candidate->lba_table; + gpt_candidate = NULL; + /* Load the partition table */ + free(part->block); + part->block = + read_sectors(lba_table, + ((part->private.gpt.size * part->private.gpt.parts) + + SECTOR - 1) / SECTOR); + if (!part->block) { + error("Could not read GPT partition list!\n"); + goto err_gpt_table; + } + } + /* Return the pseudo-partition's next partition, which is real */ + return part->next(part); + +err_gpt_table: + +err_mbr: + + free(part->block); + part->block = NULL; +err_read_mbr: + + free(part); +err_alloc_iter: + + return NULL; +} + +/* + * Search for a specific drive/partition, based on the GPT GUID. + * We return the disk drive number if found, as well as populating the + * boot_part pointer with the matching partition, if applicable. + * If no matching partition is found or the GUID is a disk GUID, + * boot_part will be populated with NULL. If not matching disk is + * found, we return -1. + */ +static int find_by_guid(const struct guid *gpt_guid, + struct disk_part_iter **boot_part) +{ + int drive; + bool is_me; + struct gpt *header; + + for (drive = 0x80; drive <= 0xff; drive++) { + if (get_disk_params(drive)) + continue; /* Drive doesn't exist */ + if (!(header = read_sectors(1, 1))) + continue; /* Cannot read sector */ + if (memcmp(&header->sig, gpt_sig_magic, sizeof(gpt_sig_magic))) { + /* Not a GPT disk */ + free(header); + continue; + } +#if DEBUG + gpt_dump(header); +#endif + is_me = !memcmp(&header->disk_guid, &gpt_guid, sizeof(*gpt_guid)); + free(header); + if (!is_me) { + /* Check for a matching partition */ + boot_part[0] = get_first_partition(NULL); + while (boot_part[0]) { + is_me = + !memcmp(boot_part[0]->private.gpt.part_guid, gpt_guid, + sizeof(*gpt_guid)); + if (is_me) + break; + boot_part[0] = boot_part[0]->next(boot_part[0]); + } + } else + boot_part[0] = NULL; + if (is_me) + return drive; + } + return -1; +} + +/* + * Search for a specific partition, based on the GPT label. + * We return the disk drive number if found, as well as populating the + * boot_part pointer with the matching partition, if applicable. + * If no matching partition is found, boot_part will be populated with + * NULL and we return -1. + */ +static int find_by_label(const char *label, struct disk_part_iter **boot_part) +{ + int drive; + bool is_me; + + for (drive = 0x80; drive <= 0xff; drive++) { + if (get_disk_params(drive)) + continue; /* Drive doesn't exist */ + /* Check for a GPT disk */ + boot_part[0] = get_first_partition(NULL); + if (!(boot_part[0]->next == next_gpt_part)) { + /* Not a GPT disk */ + while (boot_part[0]) { + /* Run through until the end */ + boot_part[0] = boot_part[0]->next(boot_part[0]); + } + continue; + } + /* Check for a matching partition */ + while (boot_part[0]) { + char gpt_label[sizeof(((struct gpt_part *) NULL)->name)]; + const char *gpt_label_scanner = + boot_part[0]->private.gpt.part_label; + int j = 0; + + /* Re-write the GPT partition label as ASCII */ + while (gpt_label_scanner < + boot_part[0]->private.gpt.part_label + sizeof(gpt_label)) { + if ((gpt_label[j] = *gpt_label_scanner)) + j++; + gpt_label_scanner++; + } + if ((is_me = !strcmp(label, gpt_label))) + break; + boot_part[0] = boot_part[0]->next(boot_part[0]); + } + if (is_me) + return drive; + } + + return -1; +} + +static void do_boot(struct data_area *data, int ndata, + struct syslinux_rm_regs *regs) +{ + uint16_t *const bios_fbm = (uint16_t *) 0x413; + addr_t dosmem = *bios_fbm << 10; /* Technically a low bound */ + struct syslinux_memmap *mmap; + struct syslinux_movelist *mlist = NULL; + addr_t endimage; + uint8_t driveno = regs->edx.b[0]; + uint8_t swapdrive = driveno & 0x80; + int i; + + mmap = syslinux_memory_map(); + + if (!mmap) { + error("Cannot read system memory map\n"); + return; + } + + endimage = 0; + for (i = 0; i < ndata; i++) { + if (data[i].base + data[i].size > endimage) + endimage = data[i].base + data[i].size; + } + if (endimage > dosmem) + goto too_big; + + for (i = 0; i < ndata; i++) { + if (syslinux_add_movelist(&mlist, data[i].base, + (addr_t) data[i].data, data[i].size)) + goto enomem; + } + + if (opt.swap && driveno != swapdrive) { + static const uint8_t swapstub_master[] = { + /* The actual swap code */ + 0x53, /* 00: push bx */ + 0x0f, 0xb6, 0xda, /* 01: movzx bx,dl */ + 0x2e, 0x8a, 0x57, 0x60, /* 04: mov dl,[cs:bx+0x60] */ + 0x5b, /* 08: pop bx */ + 0xea, 0, 0, 0, 0, /* 09: jmp far 0:0 */ + 0x90, 0x90, /* 0E: nop; nop */ + /* Code to install this in the right location */ + /* Entry with DS = CS; ES = SI = 0; CX = 256 */ + 0x26, 0x66, 0x8b, 0x7c, 0x4c, /* 10: mov edi,[es:si+4*0x13] */ + 0x66, 0x89, 0x3e, 0x0a, 0x00, /* 15: mov [0x0A],edi */ + 0x26, 0x8b, 0x3e, 0x13, 0x04, /* 1A: mov di,[es:0x413] */ + 0x4f, /* 1F: dec di */ + 0x26, 0x89, 0x3e, 0x13, 0x04, /* 20: mov [es:0x413],di */ + 0x66, 0xc1, 0xe7, 0x16, /* 25: shl edi,16+6 */ + 0x26, 0x66, 0x89, 0x7c, 0x4c, /* 29: mov [es:si+4*0x13],edi */ + 0x66, 0xc1, 0xef, 0x10, /* 2E: shr edi,16 */ + 0x8e, 0xc7, /* 32: mov es,di */ + 0x31, 0xff, /* 34: xor di,di */ + 0xf3, 0x66, 0xa5, /* 36: rep movsd */ + 0xbe, 0, 0, /* 39: mov si,0 */ + 0xbf, 0, 0, /* 3C: mov di,0 */ + 0x8e, 0xde, /* 3F: mov ds,si */ + 0x8e, 0xc7, /* 41: mov es,di */ + 0x66, 0xb9, 0, 0, 0, 0, /* 43: mov ecx,0 */ + 0x66, 0xbe, 0, 0, 0, 0, /* 49: mov esi,0 */ + 0x66, 0xbf, 0, 0, 0, 0, /* 4F: mov edi,0 */ + 0xea, 0, 0, 0, 0, /* 55: jmp 0:0 */ + /* pad out to segment boundary */ + 0x90, 0x90, /* 5A: ... */ + 0x90, 0x90, 0x90, 0x90, /* 5C: ... */ + }; + static uint8_t swapstub[1024]; + uint8_t *p; + + /* Note: we can't rely on either INT 13h nor the dosmem + vector to be correct at this stage, so we have to use an + installer stub to put things in the right place. + Round the installer location to a 1K boundary so the only + possible overlap is the identity mapping. */ + endimage = (endimage + 1023) & ~1023; + + /* Create swap stub */ + memcpy(swapstub, swapstub_master, sizeof swapstub_master); + *(uint16_t *) & swapstub[0x3a] = regs->ds; + *(uint16_t *) & swapstub[0x3d] = regs->es; + *(uint32_t *) & swapstub[0x45] = regs->ecx.l; + *(uint32_t *) & swapstub[0x4b] = regs->esi.l; + *(uint32_t *) & swapstub[0x51] = regs->edi.l; + *(uint16_t *) & swapstub[0x56] = regs->ip; + *(uint16_t *) & swapstub[0x58] = regs->cs; + p = &swapstub[sizeof swapstub_master]; + + /* Mapping table; start out with identity mapping everything */ + for (i = 0; i < 256; i++) + p[i] = i; + + /* And the actual swap */ + p[driveno] = swapdrive; + p[swapdrive] = driveno; + + /* Adjust registers */ + regs->ds = regs->cs = endimage >> 4; + regs->es = regs->esi.l = 0; + regs->ecx.l = sizeof swapstub >> 2; + regs->ip = 0x10; /* Installer offset */ + regs->ebx.b[0] = regs->edx.b[0] = swapdrive; + + if (syslinux_add_movelist(&mlist, endimage, (addr_t) swapstub, + sizeof swapstub)) + goto enomem; + + endimage += sizeof swapstub; + } + + /* Tell the shuffler not to muck with this area... */ + syslinux_add_memmap(&mmap, endimage, 0xa0000 - endimage, SMT_RESERVED); + + /* Force text mode */ + syslinux_force_text_mode(); + + fputs("Booting...\n", stdout); + syslinux_shuffle_boot_rm(mlist, mmap, opt.keeppxe, regs); + error("Chainboot failed!\n"); + return; + +too_big: + error("Loader file too large\n"); + return; + +enomem: + error("Out of memory\n"); + return; +} + +static int hide_unhide(struct mbr *mbr, int part) +{ + int i; + struct part_entry *pt; + const uint16_t mask = + (1 << 0x01) | (1 << 0x04) | (1 << 0x06) | (1 << 0x07) | (1 << 0x0b) | (1 + << + 0x0c) + | (1 << 0x0e); + uint8_t t; + bool write_back = false; + + for (i = 1; i <= 4; i++) { + pt = mbr->table + i - 1; + t = pt->ostype; + if ((t <= 0x1f) && ((mask >> (t & ~0x10)) & 1)) { + /* It's a hideable partition type */ + if (i == part) + t &= ~0x10; /* unhide */ + else + t |= 0x10; /* hide */ + } + if (t != pt->ostype) { + write_back = true; + pt->ostype = t; + } + } + + if (write_back) + return write_verify_sector(0, mbr); + + return 0; /* ok */ +} + +static uint32_t get_file_lba(const char *filename) +{ + com32sys_t inregs; + uint32_t lba; + + /* Start with clean registers */ + memset(&inregs, 0, sizeof(com32sys_t)); + + /* Put the filename in the bounce buffer */ + strlcpy(__com32.cs_bounce, filename, __com32.cs_bounce_size); + + /* Call comapi_open() which returns a structure pointer in SI + * to a structure whose first member happens to be the LBA. + */ + inregs.eax.w[0] = 0x0006; + inregs.esi.w[0] = OFFS(__com32.cs_bounce); + inregs.es = SEG(__com32.cs_bounce); + __com32.cs_intcall(0x22, &inregs, &inregs); + + if ((inregs.eflags.l & EFLAGS_CF) || inregs.esi.w[0] == 0) { + return 0; /* Filename not found */ + } + + /* Since the first member is the LBA, we simply cast */ + lba = *((uint32_t *) MK_PTR(inregs.ds, inregs.esi.w[0])); + + /* Clean the registers for the next call */ + memset(&inregs, 0, sizeof(com32sys_t)); + + /* Put the filename in the bounce buffer */ + strlcpy(__com32.cs_bounce, filename, __com32.cs_bounce_size); + + /* Call comapi_close() to free the structure */ + inregs.eax.w[0] = 0x0008; + inregs.esi.w[0] = OFFS(__com32.cs_bounce); + inregs.es = SEG(__com32.cs_bounce); + __com32.cs_intcall(0x22, &inregs, &inregs); + + return lba; +} + +static void usage(void) +{ + static const char usage[] = "\ +Usage: chain.c32 [options]\n\ + chain.c32 hd<disk#> [<partition>] [options]\n\ + chain.c32 fd<disk#> [options]\n\ + chain.c32 mbr:<id> [<partition>] [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\ +See syslinux/com32/modules/chain.c for more information\n"; + error(usage); +} + +int main(int argc, char *argv[]) +{ + struct mbr *mbr = NULL; + char *p; + struct disk_part_iter *cur_part = 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"; + + openconsole(&dev_null_r, &dev_stdcon_w); + + drivename = "boot"; + partition = NULL; + + /* Prepare the register set */ + memset(®s, 0, sizeof regs); + + 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"); + 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.loadfile = argv[i] + 6; + opt.sethidden = true; + } else if (!strncmp(argv[i], "cmldr=", 6)) { + opt.seg = 0x2000; /* CMLDR wants this address */ + opt.loadfile = argv[i] + 6; + opt.cmldr = true; + opt.sethidden = true; + } else if (!strncmp(argv[i], "freedos=", 8)) { + opt.seg = 0x60; /* FREEDOS wants this address */ + 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.sethidden = true; + } else if (!strncmp(argv[i], "grub=", 5)) { + opt.seg = 0x800; /* stage2 wants this address */ + opt.loadfile = argv[i] + 5; + opt.grub = true; + } else if (!strncmp(argv[i], "grubcfg=", 8)) { + opt.grubcfg = argv[i] + 8; + } else if (!strncmp(argv[i], "grldr=", 6)) { + opt.loadfile = argv[i] + 6; + opt.grldr = true; + } else if (!strcmp(argv[i], "swap")) { + opt.swap = true; + } else if (!strcmp(argv[i], "noswap")) { + opt.swap = false; + } else if (!strcmp(argv[i], "hide")) { + opt.hide = true; + } else if (!strcmp(argv[i], "nohide")) { + opt.hide = false; + } else if (!strcmp(argv[i], "keeppxe")) { + opt.keeppxe = 3; + } else if (!strcmp(argv[i], "sethidden")) { + opt.sethidden = true; + } else if (!strcmp(argv[i], "nosethidden")) { + opt.sethidden = false; + } else if (((argv[i][0] == 'h' || argv[i][0] == 'f') + && argv[i][1] == 'd') + || !strncmp(argv[i], "mbr:", 4) + || !strncmp(argv[i], "mbr=", 4) + || !strncmp(argv[i], "guid:", 5) + || !strncmp(argv[i], "guid=", 5) + || !strncmp(argv[i], "label:", 6) + || !strncmp(argv[i], "label=", 6) + || !strcmp(argv[i], "boot") + || !strncmp(argv[i], "boot,", 5) + || !strcmp(argv[i], "fs")) { + drivename = argv[i]; + p = strchr(drivename, ','); + if (p) { + *p = '\0'; + partition = p + 1; + } else if (argv[i + 1] && argv[i + 1][0] >= '0' + && argv[i + 1][0] <= '9') { + partition = argv[++i]; + } + } else { + usage(); + goto bail; + } + } + + if (opt.grubcfg && !opt.grub) { + error("grubcfg=<filename> must be used together with grub=<loader>.\n"); + goto bail; + } + + if (opt.seg) { + regs.es = regs.cs = regs.ss = regs.ds = regs.fs = regs.gs = opt.seg; + } else { + regs.ip = regs.esp.l = 0x7c00; + } + + hd = 0; + if (!strncmp(drivename, "mbr", 3)) { + drive = find_disk(strtoul(drivename + 4, NULL, 0)); + if (drive == -1) { + error("Unable to find requested MBR signature\n"); + goto bail; + } + } else if (!strncmp(drivename, "guid", 4)) { + if (str_to_guid(drivename + 5, &gpt_guid)) + goto bail; + drive = find_by_guid(&gpt_guid, &cur_part); + if (drive == -1) { + error("Unable to find requested GPT disk/partition\n"); + goto bail; + } + } else if (!strncmp(drivename, "label", 5)) { + if (!drivename[6]) { + error("No label specified.\n"); + goto bail; + } + drive = find_by_label(drivename + 6, &cur_part); + if (drive == -1) { + error("Unable to find requested partition by label\n"); + goto bail; + } + } else if ((drivename[0] == 'h' || drivename[0] == 'f') && + drivename[1] == 'd') { + hd = drivename[0] == 'h'; + drivename += 2; + drive = (hd ? 0x80 : 0) | strtoul(drivename, NULL, 0); + } else if (!strcmp(drivename, "boot") || !strcmp(drivename, "fs")) { + const union syslinux_derivative_info *sdi; + + sdi = syslinux_derivative_info(); + if (sdi->c.filesystem == SYSLINUX_FS_PXELINUX) + drive = 0x80; /* Boot drive not available */ + else + drive = sdi->disk.drive_number; + if (!strcmp(drivename, "fs") + && (sdi->c.filesystem == SYSLINUX_FS_SYSLINUX + || sdi->c.filesystem == SYSLINUX_FS_EXTLINUX + || sdi->c.filesystem == SYSLINUX_FS_ISOLINUX)) + /* We should lookup the Syslinux partition number and use it */ + fs_lba = *sdi->disk.partoffset; + } else { + error("Unparsable drive specification\n"); + goto bail; + } + + /* DOS kernels want the drive number in BL instead of DL. Indulge them. */ + regs.ebx.b[0] = regs.edx.b[0] = drive; + + /* Get the disk geometry and disk access setup */ + if (get_disk_params(drive)) { + error("Cannot get disk parameters\n"); + goto bail; + } + + /* Get MBR */ + if (!(mbr = read_sectors(0, 1))) { + error("Cannot read Master Boot Record or sector 0\n"); + goto bail; + } + + if (partition) + whichpart = strtoul(partition, NULL, 0); + /* "guid:" or "label:" might have specified a partition */ + if (cur_part) + whichpart = cur_part->index; + + /* Boot the MBR by default */ + if (!cur_part && (whichpart || fs_lba)) { + /* Boot a partition, possibly the Syslinux partition itself */ + cur_part = get_first_partition(NULL); + while (cur_part) { + if ((cur_part->index == whichpart) + || (cur_part->lba_data == fs_lba)) + /* Found the partition to boot */ + break; + cur_part = cur_part->next(cur_part); + } + if (!cur_part) { + error("Requested partition not found!\n"); + goto bail; + } + whichpart = cur_part->index; + } + + if (!(drive & 0x80) && whichpart) { + error("Warning: Partitions of floppy devices may not work\n"); + } + + /* + * GRLDR of GRUB4DOS wants the partition number in DH: + * -1: whole drive (default) + * 0-3: primary partitions + * 4-*: logical partitions + */ + if (opt.grldr) + regs.edx.b[1] = whichpart - 1; + + if (opt.hide) { + if (whichpart < 1 || whichpart > 4) + error("WARNING: hide specified without a non-primary partition\n"); + if (hide_unhide(mbr, whichpart)) + error("WARNING: failed to write MBR for 'hide'\n"); + } + + /* Do the actual chainloading */ + load_base = opt.seg ? (opt.seg << 4) : 0x7c00; + + 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; + load_base = 0x7c00; /* If we also load a boot sector */ + + /* Create boot info table: needed when you want to chainload + another version of ISOLINUX (or another bootlaoder that needs + the -boot-info-table switch of mkisofs) + (will only work when run from ISOLINUX) */ + if (opt.isolinux) { + const union syslinux_derivative_info *sdi; + sdi = syslinux_derivative_info(); + + if (sdi->c.filesystem == SYSLINUX_FS_ISOLINUX) { + /* Boot info table info (integers in little endian format) + + Offset Name Size Meaning + 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 + 24 bi_reserved 40 bytes Reserved + + The 32-bit checksum is the sum of all the 32-bit words in the + boot file starting at byte offset 64. All linear block + addresses (LBAs) are given in CD sectors (normally 2048 bytes). + + LBA of primary volume descriptor should already be set to 16. + */ + + isolinux_bin = (unsigned char *)data[ndata].data; + + /* Get LBA address of bootfile */ + file_lba = get_file_lba(opt.loadfile); + + if (file_lba == 0) { + error("Failed to find LBA offset of the boot file\n"); + goto bail; + } + /* Set it */ + *((uint32_t *) & isolinux_bin[12]) = file_lba; + + /* Set boot file length */ + *((uint32_t *) & isolinux_bin[16]) = data[ndata].size; + + /* Calculate checksum */ + checksum = (uint32_t *) & isolinux_bin[20]; + chkhead = (uint32_t *) & isolinux_bin[64]; + chktail = (uint32_t *) & isolinux_bin[data[ndata].size & ~3]; + *checksum = 0; + while (chkhead < chktail) + *checksum += *chkhead++; + + /* + * Deal with possible fractional dword at the end; + * this *should* never happen... + */ + if (data[ndata].size & 3) { + uint32_t xword = 0; + memcpy(&xword, chkhead, data[ndata].size & 3); + *checksum += xword; + } + } else { + error + ("The isolinux= option is only valid when run from ISOLINUX\n"); + goto bail; + } + } + + if (opt.grub) { + /* Layout of stage2 file (from byte 0x0 to 0x270) */ + struct grub_stage2_patch_area { + /* 0x0 to 0x205 */ + char unknown[0x206]; + /* 0x206: compatibility version number major */ + uint8_t compat_version_major; + /* 0x207: compatibility version number minor */ + uint8_t compat_version_minor; + + /* 0x208: install_partition variable */ + struct { + /* 0x208: sub-partition in sub-partition part2 */ + uint8_t part3; + /* 0x209: sub-partition in top-level partition */ + uint8_t part2; + /* 0x20a: top-level partiton number */ + uint8_t part1; + /* 0x20b: BIOS drive number (must be 0) */ + uint8_t drive; + } __attribute__ ((packed)) install_partition; + + /* 0x20c: deprecated (historical reason only) */ + uint32_t saved_entryno; + /* 0x210: stage2_ID: will always be STAGE2_ID_STAGE2 = 0 in stage2 */ + uint8_t stage2_id; + /* 0x211: force LBA */ + uint8_t force_lba; + /* 0x212: version string (will probably be 0.97) */ + char version_string[5]; + /* 0x217: config filename */ + char config_file[89]; + /* 0x270: start of code (after jump from 0x200) */ + char codestart[1]; + } __attribute__ ((packed)) *stage2; + + 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"); + goto bail; + } + + stage2 = data[ndata].data; + + /* + * Check the compatibility version number to see if we loaded a real + * stage2 file or a stage2 file that we support. + */ + if (stage2->compat_version_major != 3 + || stage2->compat_version_minor != 2) { + error + ("The file specified by grub=<loader> is not a supported stage2 GRUB Legacy binary\n"); + 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. + * When GRUB Legacy is loaded, it is located at memory address 0x8208. + * + * It looks very similar to the "boot information format" of the + * Multiboot specification: + * http://www.gnu.org/software/grub/manual/multiboot/multiboot.html#Boot-information-format + * + * 0x208 = part3: sub-partition in sub-partition part2 + * 0x209 = part2: sub-partition in top-level partition + * 0x20a = part1: top-level partition number + * 0x20b = drive: BIOS drive number (must be 0) + * + * GRUB Legacy doesn't store the BIOS drive number at 0x20b, but at + * another location. + * + * Partition numbers always start from zero. + * Unused partition bytes must be set to 0xFF. + * + * We only care about top-level partition, so we only need to change + * "part1" to the appropriate value: + * -1: whole drive (default) (-1 = 0xFF) + * 0-3: primary partitions + * 4-*: logical partitions + */ + stage2->install_partition.part1 = whichpart - 1; + + /* + * Grub Legacy reserves 89 bytes (from 0x8217 to 0x826f) for the + * config filename. The filename passed via grubcfg= will overwrite + * the default config filename "/boot/grub/menu.lst". + */ + if (opt.grubcfg) { + if (strlen(opt.grubcfg) > sizeof(stage2->config_file) - 1) { + error + ("The config filename length can't exceed 88 characters.\n"); + goto bail; + } + + strcpy((char *)stage2->config_file, opt.grubcfg); + } + } + + ndata++; + } + + if (!opt.loadfile || data[0].base >= 0x7c00 + SECTOR) { + /* Actually read the boot sector */ + if (!cur_part) { + data[ndata].data = mbr; + } else if (!(data[ndata].data = read_sectors(cur_part->lba_data, 1))) { + error("Cannot read boot sector\n"); + goto bail; + } + data[ndata].size = SECTOR; + data[ndata].base = load_base; + + if (!opt.loadfile) { + const struct mbr *br = + (const struct mbr *)((char *)data[ndata].data + + data[ndata].size - sizeof(struct mbr)); + if (br->sig != mbr_sig_magic) { + error + ("Boot sector signature not found (unbootable disk/partition?)\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 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. + */ + if (cur_part && opt.sethidden) { + *(uint32_t *) ((char *)data[ndata].data + 28) = cur_part->lba_data; + } + + ndata++; + } + + if (cur_part) { + if (cur_part->next == next_gpt_part) { + /* Do GPT hand-over, if applicable (as per syslinux/doc/gpt.txt) */ + struct part_entry *record; + /* Look at the GPT partition */ + const struct gpt_part *gp = (const struct gpt_part *) + (cur_part->block + + (cur_part->private.gpt.size * cur_part->private.gpt.index)); + /* Note the partition length */ + uint64_t lba_count = gp->lba_last - gp->lba_first + 1; + /* The length of the hand-over */ + int synth_size = + sizeof(struct part_entry) + sizeof(uint32_t) + + cur_part->private.gpt.size; + /* Will point to the partition record length in the hand-over */ + uint32_t *plen; + + /* Allocate the hand-over record */ + record = malloc(synth_size); + if (!record) { + error("Could not build GPT hand-over record!\n"); + goto bail; + } + /* Synthesize the record */ + memset(record, 0, synth_size); + record->active_flag = 0x80; + record->ostype = 0xED; + /* All bits set by default */ + record->start_lba = ~(uint32_t) 0; + record->length = ~(uint32_t) 0; + /* If these fit the precision, pass them on */ + if (cur_part->lba_data < record->start_lba) + record->start_lba = cur_part->lba_data; + if (lba_count < record->length) + record->length = lba_count; + /* Next comes the GPT partition record length */ + plen = (uint32_t *) (record + 1); + plen[0] = cur_part->private.gpt.size; + /* Next comes the GPT partition record copy */ + memcpy(plen + 1, gp, plen[0]); + cur_part->record = record; + + regs.eax.l = 0x54504721; /* '!GPT' */ + data[ndata].base = 0x7be; + data[ndata].size = synth_size; + data[ndata].data = (void *)record; + ndata++; + regs.esi.w[0] = 0x7be; + + dprintf("GPT handover:\n"); + mbr_part_dump(record); + gpt_part_dump((struct gpt_part *)(plen + 1)); + } else if (cur_part->record) { + /* MBR handover protocol */ + static struct part_entry handover_record; + + handover_record = *cur_part->record; + handover_record.start_lba = cur_part->lba_data; + + data[ndata].base = 0x7be; + data[ndata].size = sizeof handover_record; + data[ndata].data = &handover_record; + ndata++; + regs.esi.w[0] = 0x7be; + + dprintf("MBR handover:\n"); + mbr_part_dump(&handover_record); + } + } + + do_boot(data, ndata, ®s); + +bail: + if (cur_part) { + free(cur_part->block); + free((void *)cur_part->record); + } + free(cur_part); + free(mbr); + return 255; +} |