diff options
Diffstat (limited to 'com32/chain/chain.c')
-rw-r--r-- | com32/chain/chain.c | 658 |
1 files changed, 658 insertions, 0 deletions
diff --git a/com32/chain/chain.c b/com32/chain/chain.c new file mode 100644 index 00000000..30153c4d --- /dev/null +++ b/com32/chain/chain.c @@ -0,0 +1,658 @@ +/* ----------------------------------------------------------------------- * + * + * Copyright 2003-2009 H. Peter Anvin - All Rights Reserved + * Copyright 2009-2010 Intel Corporation; author: H. Peter Anvin + * Copyright 2010 Shao Miller + * Copyright 2010 Michal Soltys + * + * 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. + * + * ----------------------------------------------------------------------- */ + +/* + * Please see doc/chain.txt for the detailed documentation. + */ + +#include <com32.h> +#include <stdlib.h> +#include <stdio.h> +#include <ctype.h> +#include <string.h> +#include <console.h> +#include <consoles.h> +#include <minmax.h> +#include <stdbool.h> +#include <dprintf.h> +#include <errno.h> +#include <unistd.h> +#include <syslinux/loadfile.h> +#include <syslinux/bootrm.h> +#include <syslinux/config.h> +#include <syslinux/disk.h> +#include <syslinux/video.h> +#include "common.h" +#include "chain.h" +#include "utility.h" +#include "options.h" +#include "partiter.h" +#include "mangle.h" + +static int fixed_cnt = 128; /* see comments in main() */ + +static int overlap(const struct data_area *a, const struct data_area *b) +{ + return + a->base + a->size > b->base && + b->base + b->size > a->base; +} + +static int is_phys(uint8_t sdifs) +{ + return + sdifs == SYSLINUX_FS_SYSLINUX || + sdifs == SYSLINUX_FS_EXTLINUX || + sdifs == SYSLINUX_FS_ISOLINUX; +} + +/* + * Search for a specific drive, based on the MBR signature. + * Return drive and iterator at 0th position. + */ +static int find_by_sig(uint32_t mbr_sig, + struct part_iter **_boot_part) +{ + struct part_iter *boot_part = NULL; + struct disk_info diskinfo; + int drive; + + for (drive = 0x80; drive < 0x80 + fixed_cnt; drive++) { + if (disk_get_params(drive, &diskinfo)) + continue; /* Drive doesn't exist */ + if (!(boot_part = pi_begin(&diskinfo, 0))) + continue; + /* Check for a MBR disk */ + if (boot_part->type != typedos) { + pi_del(&boot_part); + continue; + } + if (boot_part->sub.dos.disk_sig == mbr_sig) { + goto ok; + } + } + drive = -1; +ok: + *_boot_part = boot_part; + return drive; +} + +/* + * Search for a specific drive/partition, based on the GPT GUID. + * Return drive and iterator at proper position. + */ +static int find_by_guid(const struct guid *gpt_guid, + struct part_iter **_boot_part) +{ + struct part_iter *boot_part = NULL; + struct disk_info diskinfo; + int drive; + + for (drive = 0x80; drive < 0x80 + fixed_cnt; drive++) { + if (disk_get_params(drive, &diskinfo)) + continue; /* Drive doesn't exist */ + if (!(boot_part = pi_begin(&diskinfo, 0))) + continue; + /* Check for a GPT disk */ + if (boot_part->type != typegpt) { + pi_del(&boot_part); + continue; + } + /* Check for a matching GPT disk guid */ + if (!memcmp(&boot_part->sub.gpt.disk_guid, gpt_guid, sizeof(*gpt_guid))) { + goto ok; + } + /* disk guid doesn't match, maybe partition guid will */ + while (!pi_next(&boot_part)) { + if (!memcmp(&boot_part->sub.gpt.part_guid, gpt_guid, sizeof(*gpt_guid))) + goto ok; + } + } + drive = -1; +ok: + *_boot_part = boot_part; + return drive; +} + +/* + * Search for a specific drive/partition, based on the GPT label. + * Return drive and iterator at proper position. + */ +static int find_by_label(const char *label, struct part_iter **_boot_part) +{ + struct part_iter *boot_part = NULL; + struct disk_info diskinfo; + int drive; + + for (drive = 0x80; drive < 0x80 + fixed_cnt; drive++) { + if (disk_get_params(drive, &diskinfo)) + continue; /* Drive doesn't exist */ + if (!(boot_part = pi_begin(&diskinfo, 0))) + continue; + /* Check for a GPT disk */ + if (!(boot_part->type == typegpt)) { + pi_del(&boot_part); + continue; + } + /* Check for a matching partition */ + while (!pi_next(&boot_part)) { + if (!strcmp(label, boot_part->sub.gpt.part_label)) + goto ok; + } + } + drive = -1; +ok: + *_boot_part = boot_part; + return drive; +} + +static void do_boot(struct data_area *data, int ndata) +{ + uint16_t *const bios_fbm = (uint16_t *) 0x413; + addr_t dosmem = (addr_t)(*bios_fbm << 10); /* Technically a low bound */ + struct syslinux_memmap *mmap; + struct syslinux_movelist *mlist = NULL; + addr_t endimage; + uint8_t driveno = opt.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 + 1023u) & ~1023u; + + /* Create swap stub */ + memcpy(swapstub, swapstub_master, sizeof swapstub_master); + *(uint16_t *) & swapstub[0x3a] = opt.regs.ds; + *(uint16_t *) & swapstub[0x3d] = opt.regs.es; + *(uint32_t *) & swapstub[0x45] = opt.regs.ecx.l; + *(uint32_t *) & swapstub[0x4b] = opt.regs.esi.l; + *(uint32_t *) & swapstub[0x51] = opt.regs.edi.l; + *(uint16_t *) & swapstub[0x56] = opt.regs.ip; + *(uint16_t *) & swapstub[0x58] = opt.regs.cs; + p = &swapstub[sizeof swapstub_master]; + + /* Mapping table; start out with identity mapping everything */ + for (i = 0; i < 256; i++) + p[i] = (uint8_t)i; + + /* And the actual swap */ + p[driveno] = swapdrive; + p[swapdrive] = driveno; + + /* Adjust registers */ + opt.regs.ds = opt.regs.cs = (uint16_t)(endimage >> 4); + opt.regs.esi.l = opt.regs.es = 0; + opt.regs.ecx.l = sizeof swapstub >> 2; + opt.regs.ip = 0x10; /* Installer offset */ + opt.regs.ebx.b[0] = opt.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, &opt.regs); + error("Chainboot failed!\n"); + return; + +too_big: + error("Loader file too large\n"); + return; + +enomem: + error("Out of memory\n"); + return; +} + +int find_dp(struct part_iter **_iter) +{ + struct part_iter *iter = NULL; + struct disk_info diskinfo; + struct guid gpt_guid; + uint64_t fs_lba; + int drive, hd, partition; + const union syslinux_derivative_info *sdi; + + sdi = syslinux_derivative_info(); + + if (!strncmp(opt.drivename, "mbr", 3)) { + if (find_by_sig(strtoul(opt.drivename + 4, NULL, 0), &iter) < 0) { + error("Unable to find requested MBR signature.\n"); + goto bail; + } + } else if (!strncmp(opt.drivename, "guid", 4)) { + if (str_to_guid(opt.drivename + 5, &gpt_guid)) + goto bail; + if (find_by_guid(&gpt_guid, &iter) < 0) { + error("Unable to find requested GPT disk or partition by guid.\n"); + goto bail; + } + } else if (!strncmp(opt.drivename, "label", 5)) { + if (!opt.drivename[6]) { + error("No label specified.\n"); + goto bail; + } + if (find_by_label(opt.drivename + 6, &iter) < 0) { + error("Unable to find requested GPT partition by label.\n"); + goto bail; + } + } else if ((opt.drivename[0] == 'h' || opt.drivename[0] == 'f') && + opt.drivename[1] == 'd') { + hd = opt.drivename[0] == 'h' ? 0x80 : 0; + opt.drivename += 2; + drive = hd | strtol(opt.drivename, NULL, 0); + + if (disk_get_params(drive, &diskinfo)) + goto bail; + /* this will start iteration over FDD, possibly raw */ + if (!(iter = pi_begin(&diskinfo, 0))) + goto bail; + + } else if (!strcmp(opt.drivename, "boot") || !strcmp(opt.drivename, "fs")) { + if (!is_phys(sdi->c.filesystem)) { + error("When syslinux is not booted from physical disk (or its emulation),\n" + "'boot' and 'fs' are meaningless.\n"); + goto bail; + } + /* offsets match, but in case it changes in the future */ + if (sdi->c.filesystem == SYSLINUX_FS_ISOLINUX) { + drive = sdi->iso.drive_number; + fs_lba = *sdi->iso.partoffset; + } else { + drive = sdi->disk.drive_number; + fs_lba = *sdi->disk.partoffset; + } + if (disk_get_params(drive, &diskinfo)) + goto bail; + /* this will start iteration over disk emulation, possibly raw */ + if (!(iter = pi_begin(&diskinfo, 0))) + goto bail; + + /* 'fs' => we should lookup the syslinux partition number and use it */ + if (!strcmp(opt.drivename, "fs")) { + while (!pi_next(&iter)) { + if (iter->start_lba == fs_lba) + break; + } + /* broken part structure or other problems */ + if (iter->status) { + error("Can't find myself on the drive I booted from.\n"); + goto bail; + } + } + } else { + error("Unparsable drive specification.\n"); + goto bail; + } + /* main options done - only thing left is explicit partition specification, + * if we're still at the disk stage with the iterator AND user supplied + * partition number (including disk pseudo-partition). + */ + if (!iter->index && opt.partition) { + partition = strtol(opt.partition, NULL, 0); + /* search for matching part#, including disk */ + do { + if (iter->index == partition) + break; + } while (!pi_next(&iter)); + if (iter->status) { + error("Requested disk / partition combination not found.\n"); + goto bail; + } + } + + if (!(iter->di.disk & 0x80) && iter->index) { + error("WARNING: Partitions on floppy devices may not work.\n"); + } + + *_iter = iter; + + return 0; + +bail: + pi_del(&iter); + return -1; +} + +static int setup_handover(const struct part_iter *iter, + struct data_area *data) +{ + struct disk_dos_part_entry *ha; + uint32_t synth_size; + uint32_t *plen; + + if (!iter->index) { /* implies typeraw or non-iterated */ + uint32_t len; + /* RAW handover protocol */ + synth_size = sizeof(struct disk_dos_part_entry); + ha = malloc(synth_size); + if (!ha) { + error("Could not build RAW hand-over record!\n"); + goto bail; + } + len = ~0u; + if (iter->length < len) + len = (uint32_t)iter->length; + lba2chs(&ha->start, &iter->di, 0, l2c_cadd); + lba2chs(&ha->end, &iter->di, len - 1, l2c_cadd); + ha->active_flag = 0x80; + ha->ostype = 0xDA; /* "Non-FS Data", anything is good here though ... */ + ha->start_lba = 0; + ha->length = len; + } else if (iter->type == typegpt) { + /* GPT handover protocol */ + synth_size = sizeof(struct disk_dos_part_entry) + + sizeof(uint32_t) + (uint32_t)iter->sub.gpt.pe_size; + ha = malloc(synth_size); + if (!ha) { + error("Could not build GPT hand-over record!\n"); + goto bail; + } + lba2chs(&ha->start, &iter->di, iter->start_lba, l2c_cadd); + lba2chs(&ha->end, &iter->di, iter->start_lba + iter->length - 1, l2c_cadd); + ha->active_flag = 0x80; + ha->ostype = 0xED; + /* All bits set by default */ + ha->start_lba = ~0u; + ha->length = ~0u; + /* If these fit the precision, pass them on */ + if (iter->start_lba < ha->start_lba) + ha->start_lba = (uint32_t)iter->start_lba; + if (iter->length < ha->length) + ha->length = (uint32_t)iter->length; + /* Next comes the GPT partition record length */ + plen = (uint32_t *) (ha + 1); + plen[0] = (uint32_t)iter->sub.gpt.pe_size; + /* Next comes the GPT partition record copy */ + memcpy(plen + 1, iter->record, plen[0]); +#ifdef DEBUG + dprintf("GPT handover:\n"); + disk_dos_part_dump(ha); + disk_gpt_part_dump((struct disk_gpt_part_entry *)(plen + 1)); +#endif + } else if (iter->type == typedos) { + /* MBR handover protocol */ + synth_size = sizeof(struct disk_dos_part_entry); + ha = malloc(synth_size); + if (!ha) { + error("Could not build MBR hand-over record!\n"); + goto bail; + } + memcpy(ha, iter->record, synth_size); + /* make sure these match bios imaginations and are ebr agnostic */ + lba2chs(&ha->start, &iter->di, iter->start_lba, l2c_cadd); + lba2chs(&ha->end, &iter->di, iter->start_lba + iter->length - 1, l2c_cadd); + ha->start_lba = (uint32_t)iter->start_lba; + ha->length = (uint32_t)iter->length; + +#ifdef DEBUG + dprintf("MBR handover:\n"); + disk_dos_part_dump(ha); +#endif + } else { + /* shouldn't ever happen */ + goto bail; + } + + data->base = 0x7be; + data->size = synth_size; + data->data = (void *)ha; + + return 0; +bail: + return -1; +} + +int main(int argc, char *argv[]) +{ + struct part_iter *iter = NULL; + void *sbck = NULL; + struct data_area fdat, hdat, sdat, data[3]; + int ndata = 0; + + console_ansi_raw(); + + memset(&fdat, 0, sizeof(fdat)); + memset(&hdat, 0, sizeof(hdat)); + memset(&sdat, 0, sizeof(sdat)); + + opt_set_defs(); + if (opt_parse_args(argc, argv)) + goto bail; + +#if 0 + /* Get max fixed disk number */ + fixed_cnt = *(uint8_t *)(0x475); + + /* + * hmm, looks like we can't do that - + * some bioses/vms just set it to 1 + * and go on living happily + * any better options than hardcoded 0x80 - 0xFF ? + */ +#endif + + /* Get disk/part iterator matching user supplied options */ + if (find_dp(&iter)) + goto bail; + + /* Perform initial partition entry mangling */ + if (manglepe_fixchs(iter)) + goto bail; + if (manglepe_hide(iter)) + goto bail; + + /* Load the boot file */ + if (opt.file) { + fdat.base = (opt.fseg << 4) + opt.foff; + + if (loadfile(opt.file, &fdat.data, &fdat.size)) { + error("Couldn't read the boot file.\n"); + goto bail; + } + if (fdat.base + fdat.size - 1 > ADDRMAX) { + error("The boot file is too big to load at this address.\n"); + goto bail; + } + } + + /* Load the sector */ + if (opt.sect) { + sdat.base = (opt.sseg << 4) + opt.soff; + sdat.size = iter->di.bps; + + if (sdat.base + sdat.size - 1 > ADDRMAX) { + error("The sector cannot be loaded at such high address.\n"); + goto bail; + } + if (!(sdat.data = disk_read_sectors(&iter->di, iter->start_lba, 1))) { + error("Couldn't read the sector.\n"); + goto bail; + } + if (opt.save) { + if (!(sbck = malloc(sdat.size))) { + error("Couldn't allocate cmp-buf for option 'save'.\n"); + goto bail; + } + memcpy(sbck, sdat.data, sdat.size); + } + if (opt.file && opt.maps && overlap(&fdat, &sdat)) { + error("WARNING: The sector won't be mmapped, as it would conflict with the boot file.\n"); + opt.maps = false; + } + } + + /* Prep the handover */ + if (opt.hand) { + if (setup_handover(iter, &hdat)) + goto bail; + /* Verify possible conflicts */ + if ( ( opt.file && overlap(&fdat, &hdat)) || + ( opt.maps && overlap(&sdat, &hdat)) ) { + error("WARNING: Handover area won't be prepared,\n" + "as it would conflict with the boot file and/or the sector.\n"); + opt.hand = false; + } + } + + /* Adjust registers */ + + mangler_init(iter); + mangler_handover(iter, &hdat); + mangler_grldr(iter); + + /* Patching functions */ + + if (manglef_isolinux(&fdat)) + goto bail; + + if (manglef_grub(iter, &fdat)) + goto bail; +#if 0 + if (manglef_drmk(&fdat)) + goto bail; +#endif + if (manglef_bpb(iter, &fdat)) + goto bail; + + if (mangles_bpb(iter, &sdat)) + goto bail; + + if (mangles_save(iter, &sdat, sbck)) + goto bail; + + if (manglesf_bss(&sdat, &fdat)) + goto bail; + + /* This *must* be after BPB saving or copying */ + if (mangles_cmldr(&sdat)) + goto bail; + + /* + * Prepare boot-time mmap data. We should to it here, as manglers could + * potentially alter some of the data. + */ + + if (opt.file) + memcpy(data + ndata++, &fdat, sizeof(fdat)); + if (opt.maps) + memcpy(data + ndata++, &sdat, sizeof(sdat)); + if (opt.hand) + memcpy(data + ndata++, &hdat, sizeof(hdat)); + +#ifdef DEBUG + printf("iter->di dsk, bps: %X, %u\niter->di lbacnt, C*H*S: %"PRIu64", %u\n" + "iter->di C, H, S: %u, %u, %u\n", + iter->di.disk, iter->di.bps, + iter->di.lbacnt, iter->di.cyl * iter->di.head * iter->di.spt, + iter->di.cyl, iter->di.head, iter->di.spt); + printf("iter idx: %d\n", iter->index); + printf("iter lba: %"PRIu64"\n", iter->start_lba); + if (opt.hand) + printf("hand lba: %u\n", + ((struct disk_dos_part_entry *)hdat.data)->start_lba); +#endif + + if (opt.warn) { + puts("Press any key to continue booting..."); + wait_key(); + } + + if (ndata && !opt.brkchain) /* boot only if we actually chainload */ + do_boot(data, ndata); + else + error("Service-only run completed, exiting.\n"); +bail: + pi_del(&iter); + /* Free allocated areas */ + free(fdat.data); + free(sdat.data); + free(hdat.data); + free(sbck); + return 255; +} + +/* vim: set ts=8 sts=4 sw=4 noet: */ |