diff options
author | Jannis Pohlmann <jannis.pohlmann@codethink.co.uk> | 2012-12-22 11:51:38 +0000 |
---|---|---|
committer | Jannis Pohlmann <jannis.pohlmann@codethink.co.uk> | 2012-12-22 11:51:38 +0000 |
commit | 8821237240c5374d83298b2da5ad88fa1e3c1ef7 (patch) | |
tree | e1bc03fa83c61165f48ffe3f528ac5496332872a /com32/chain/mangle.c | |
parent | 38dcca25c8855c95649e3f0b5b09fae862ed5c7a (diff) | |
parent | 7307d60063ee4303da4de45f9d984fdc8df92146 (diff) | |
download | syslinux-8821237240c5374d83298b2da5ad88fa1e3c1ef7.tar.gz |
Merge remote-tracking branch 'remotes/upstream/master' into baserock/morph
Diffstat (limited to 'com32/chain/mangle.c')
-rw-r--r-- | com32/chain/mangle.c | 618 |
1 files changed, 618 insertions, 0 deletions
diff --git a/com32/chain/mangle.c b/com32/chain/mangle.c new file mode 100644 index 00000000..8358106e --- /dev/null +++ b/com32/chain/mangle.c @@ -0,0 +1,618 @@ +#include <com32.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <stdint.h> +#include <dprintf.h> +#include <syslinux/config.h> +#include "common.h" +#include "chain.h" +#include "options.h" +#include "utility.h" +#include "partiter.h" +#include "mangle.h" + +static const char cmldr_signature[8] = "cmdcons"; + +/* 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) + */ +int manglef_isolinux(struct data_area *data) +{ + const union syslinux_derivative_info *sdi; + unsigned char *isolinux_bin; + uint32_t *checksum, *chkhead, *chktail; + uint32_t file_lba = 0; + + if (!(opt.file && opt.isolinux)) + return 0; + + sdi = syslinux_derivative_info(); + + if (sdi->c.filesystem != SYSLINUX_FS_ISOLINUX) { + error ("The isolinux= option is only valid when run from ISOLINUX.\n"); + goto bail; + } + + /* 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->data; + + /* Get LBA address of bootfile */ + file_lba = get_file_lba(opt.file); + + 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->size; + + /* Calculate checksum */ + checksum = (uint32_t *) & isolinux_bin[20]; + chkhead = (uint32_t *) & isolinux_bin[64]; + chktail = (uint32_t *) & isolinux_bin[data->size & ~3u]; + *checksum = 0; + while (chkhead < chktail) + *checksum += *chkhead++; + + /* + * Deal with possible fractional dword at the end; + * this *should* never happen... + */ + if (data->size & 3) { + uint32_t xword = 0; + memcpy(&xword, chkhead, data->size & 3); + *checksum += xword; + } + return 0; +bail: + return -1; +} + +/* + * Legacy grub's stage2 chainloading + */ +int manglef_grub(const struct part_iter *iter, struct data_area *data) +{ + /* 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 (!(opt.file && opt.grub)) + return 0; + + if (data->size < sizeof(struct grub_stage2_patch_area)) { + error("The file specified by grub=<loader> is too small to be stage2 of GRUB Legacy.\n"); + goto bail; + } + stage2 = data->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; + } + + /* + * 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 = (uint8_t)(iter->index - 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); + } + + return 0; +bail: + return -1; +} +#if 0 +/* + * Dell's DRMK chainloading. + */ +int manglef_drmk(struct data_area *data) +{ + /* + * DRMK entry is different than MS-DOS/PC-DOS + * A new size, aligned to 16 bytes to ease use of ds:[bp+28]. + * We only really need 4 new, usable bytes at the end. + */ + + if (!(opt.file && opt.drmk)) + return 0; + + uint32_t tsize = (data->size + 19) & 0xfffffff0; + const union syslinux_derivative_info *sdi; + uint64_t fs_lba; + + sdi = syslinux_derivative_info(); + /* We should lookup the Syslinux partition offset and use it */ + fs_lba = *sdi->disk.partoffset; + + /* + * fs_lba should be verified against the disk as some DRMK + * variants will check and fail if it does not match + */ + dprintf(" fs_lba offset is %d\n", fs_lba); + /* DRMK only uses a DWORD */ + if (fs_lba > 0xffffffff) { + error("LBA very large; Only using lower 32 bits; DRMK will probably fail\n"); + } + opt.regs.ss = opt.regs.fs = opt.regs.gs = 0; /* Used before initialized */ + if (!realloc(data->data, tsize)) { + error("Failed to realloc for DRMK.\n"); + goto bail; + } + data->size = tsize; + /* ds:bp is assumed by DRMK to be the boot sector */ + /* offset 28 is the FAT HiddenSectors value */ + opt.regs.ds = (uint16_t)((tsize >> 4) + (opt.fseg - 2)); + /* "Patch" into tail of the new space */ + *(uint32_t *)((char*)data->data + tsize - 4) = (uint32_t)fs_lba; + + return 0; +bail: + return -1; +} +#endif +/* Adjust BPB common function */ +static int mangle_bpb(const struct part_iter *iter, struct data_area *data, const char *tag) +{ + unsigned int off; + int type = bpb_detect(data->data, tag); + + /* BPB: hidden sectors 32bit*/ + if (type >= bpbV34) { + if (iter->start_lba < ~0u) + *(uint32_t *) ((char *)data->data + 0x1c) = (uint32_t)iter->start_lba; + else + /* won't really help much, but ... */ + *(uint32_t *) ((char *)data->data + 0x1c) = ~0u; + } + /* BPB: hidden sectors 16bit*/ + if (bpbV30 <= type && type <= bpbV32) { + if (iter->start_lba < 0xFFFF) + *(uint16_t *) ((char *)data->data + 0x1c) = (uint16_t)iter->start_lba; + else + /* won't really help much, but ... */ + *(uint16_t *) ((char *)data->data + 0x1c) = (uint16_t)~0u; + } + /* BPB: legacy geometry */ + if (type >= bpbV30) { + if (iter->di.cbios) + *(uint32_t *)((char *)data->data + 0x18) = (uint32_t)((iter->di.head << 16) | iter->di.spt); + else { + if (iter->di.disk & 0x80) + *(uint32_t *)((char *)data->data + 0x18) = 0x00FF003F; + else + *(uint32_t *)((char *)data->data + 0x18) = 0x00020012; + } + } + /* BPB: drive */ + if (drvoff_detect(type, &off)) { + *(uint8_t *)((char *)data->data + off) = (uint8_t) + (opt.swap ? iter->di.disk & 0x80 : iter->di.disk); + } + + return 0; +} + +/* + * Adjust BPB of a BPB-compatible file + */ +int manglef_bpb(const struct part_iter *iter, struct data_area *data) +{ + if (!(opt.file && opt.filebpb)) + return 0; + + return mangle_bpb(iter, data, "file"); +} + +/* + * Adjust BPB of a sector + */ +int mangles_bpb(const struct part_iter *iter, struct data_area *data) +{ + if (!(opt.sect && opt.setbpb)) + return 0; + + return mangle_bpb(iter, data, "sect"); +} + +/* + * This function performs full BPB patching, analogously to syslinux's + * native BSS. + */ +int manglesf_bss(struct data_area *sec, struct data_area *fil) +{ + int type1, type2; + unsigned int cnt = 0; + + if (!(opt.sect && opt.file && opt.bss)) + return 0; + + type1 = bpb_detect(fil->data, "bss/file"); + type2 = bpb_detect(sec->data, "bss/sect"); + + if (!type1 || !type2) { + error("Couldn't determine the BPB type for option 'bss'.\n"); + goto bail; + } + if (type1 != type2) { + error("Option 'bss' can't be used,\n" + "when a sector and a file have incompatible BPBs.\n"); + goto bail; + } + + /* Copy common 2.0 data */ + memcpy((char *)fil->data + 0x0B, (char *)sec->data + 0x0B, 0x0D); + + /* Copy 3.0+ data */ + if (type1 <= bpbV30) { + cnt = 0x06; + } else if (type1 <= bpbV32) { + cnt = 0x08; + } else if (type1 <= bpbV34) { + cnt = 0x0C; + } else if (type1 <= bpbV40) { + cnt = 0x2E; + } else if (type1 <= bpbVNT) { + cnt = 0x3C; + } else if (type1 <= bpbV70) { + cnt = 0x42; + } + memcpy((char *)fil->data + 0x18, (char *)sec->data + 0x18, cnt); + + return 0; +bail: + return -1; +} + +/* + * Save sector. + */ +int mangles_save(const struct part_iter *iter, const struct data_area *data, void *org) +{ + if (!(opt.sect && opt.save)) + return 0; + + if (memcmp(org, data->data, data->size)) { + if (disk_write_sectors(&iter->di, iter->start_lba, data->data, 1)) { + error("Cannot write the updated sector.\n"); + goto bail; + } + /* function can be called again */ + memcpy(org, data->data, data->size); + } + + return 0; +bail: + return -1; +} + +/* + * 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. + */ +int mangles_cmldr(struct data_area *data) +{ + if (!(opt.sect && opt.cmldr)) + return 0; + + memcpy((char *)data->data + 3, cmldr_signature, sizeof(cmldr_signature)); + return 0; +} + +/* Set common registers */ +int mangler_init(const struct part_iter *iter) +{ + /* Set initial registry values */ + if (opt.file) { + opt.regs.cs = opt.regs.ds = opt.regs.ss = (uint16_t)opt.fseg; + opt.regs.ip = (uint16_t)opt.fip; + } else { + opt.regs.cs = opt.regs.ds = opt.regs.ss = (uint16_t)opt.sseg; + opt.regs.ip = (uint16_t)opt.sip; + } + + if (opt.regs.ip == 0x7C00 && !opt.regs.cs) + opt.regs.esp.l = 0x7C00; + + /* DOS kernels want the drive number in BL instead of DL. Indulge them. */ + opt.regs.ebx.b[0] = opt.regs.edx.b[0] = (uint8_t)iter->di.disk; + + return 0; +} + +/* ds:si & ds:bp */ +int mangler_handover(const struct part_iter *iter, const struct data_area *data) +{ + if (opt.file && opt.maps && !opt.hptr) { + opt.regs.esi.l = opt.regs.ebp.l = opt.soff; + opt.regs.ds = (uint16_t)opt.sseg; + opt.regs.eax.l = 0; + } else if (opt.hand) { + /* base is really 0x7be */ + opt.regs.esi.l = opt.regs.ebp.l = data->base; + opt.regs.ds = 0; + if (iter->index && iter->type == typegpt) /* must be iterated and GPT */ + opt.regs.eax.l = 0x54504721; /* '!GPT' */ + else + opt.regs.eax.l = 0; + } + + return 0; +} + +/* + * GRLDR of GRUB4DOS wants the partition number in DH: + * -1: whole drive (default) + * 0-3: primary partitions + * 4-*: logical partitions + */ +int mangler_grldr(const struct part_iter *iter) +{ + if (opt.grldr) + opt.regs.edx.b[1] = (uint8_t)(iter->index - 1); + + return 0; +} + +/* + * try to copy values from temporary iterator, if positions match + */ +static void push_embr(struct part_iter *diter, struct part_iter *siter) +{ + if (diter->sub.dos.cebr_lba == siter->sub.dos.cebr_lba && + diter->di.disk == siter->di.disk) { + memcpy(diter->data, siter->data, sizeof(struct disk_dos_mbr)); + } +} + +static int mpe_sethide(struct part_iter *iter, struct part_iter *miter) +{ + struct disk_dos_part_entry *dp; + static const uint16_t mask = + (1 << 0x01) | (1 << 0x04) | (1 << 0x06) | + (1 << 0x07) | (1 << 0x0b) | (1 << 0x0c) | (1 << 0x0e); + uint8_t t; + + dp = (struct disk_dos_part_entry *)iter->record; + t = dp->ostype; + + if ((t <= 0x1f) && ((mask >> (t & ~0x10u)) & 1)) { + /* It's a hideable partition type */ + if (miter->index == iter->index || opt.hide & 4) + t &= (uint8_t)(~0x10u); /* unhide */ + else + t |= 0x10u; /* hide */ + } + if (dp->ostype != t) { + dp->ostype = t; + return -1; + } + return 0; +} + +/* + * miter - iterator we match against + * hide bits meaning: + * ..| - enable (1) / disable (0) + * .|. - all (1) / pri (0) + * |.. - unhide (1) / hide (0) + */ +int manglepe_hide(struct part_iter *miter) +{ + int wb = 0, werr = 0; + struct part_iter *iter = NULL; + struct disk_dos_part_entry *dp; + int ridx; + + if (!opt.hide) + return 0; + + if (miter->type != typedos) { + error("Options '*hide*' is meaningful only for legacy partition scheme.\n"); + return -1; + } + + if (miter->index < 1) + error("WARNING: It's impossible to unhide a disk.\n"); + + if (miter->index > 4 && !(opt.hide & 2)) + error("WARNING: your partition is beyond mbr, so it can't be unhidden without '*hideall'.\n"); + + if (!(iter = pi_begin(&miter->di, 1))) /* turn stepall on */ + return -1; + + while (!pi_next(&iter) && !werr) { + ridx = iter->rawindex; + if (!(opt.hide & 2) && ridx > 4) + break; /* skip when we're constrained to pri only */ + + dp = (struct disk_dos_part_entry *)iter->record; + if (dp->ostype) + wb |= mpe_sethide(iter, miter); + + if (ridx >= 4 && wb && !werr) { + push_embr(miter, iter); + werr |= disk_write_sectors(&iter->di, iter->sub.dos.cebr_lba, iter->data, 1); + wb = 0; + } + } + + if (iter->status > PI_DONE) + goto bail; + + /* last write */ + if (wb && !werr) { + push_embr(miter, iter); + werr |= disk_write_sectors(&iter->di, iter->sub.dos.cebr_lba, iter->data, 1); + } + if (werr) + error("WARNING: failed to write E/MBR during '*hide*'\n"); + +bail: + pi_del(&iter); + return 0; +} + +static int mpe_setchs(const struct disk_info *di, + struct disk_dos_part_entry *dp, + uint32_t lba1) +{ + uint32_t ochs1, ochs2; + + ochs1 = *(uint32_t *)dp->start; + ochs2 = *(uint32_t *)dp->end; + + lba2chs(&dp->start, di, lba1, l2c_cadd); + lba2chs(&dp->end, di, lba1 + dp->length - 1, l2c_cadd); + + return + *(uint32_t *)dp->start != ochs1 || + *(uint32_t *)dp->end != ochs2; +} + +/* + * miter - iterator we match against + */ +int manglepe_fixchs(struct part_iter *miter) +{ + int wb = 0, werr = 0; + struct part_iter *iter = NULL; + struct disk_dos_part_entry *dp; + int ridx; + + if (!opt.fixchs) + return 0; + + if (miter->type != typedos) { + error("Options 'fixchs' is meaningful only for legacy partition scheme.\n"); + return -1; + } + + if (!(iter = pi_begin(&miter->di, 1))) /* turn stepall on */ + return -1; + + while (!pi_next(&iter) && !werr) { + ridx = iter->rawindex; + dp = (struct disk_dos_part_entry *)iter->record; + + wb |= mpe_setchs(&iter->di, dp, (uint32_t)iter->start_lba); + if (ridx > 4) + wb |= mpe_setchs(&iter->di, dp + 1, iter->sub.dos.nebr_lba); + + if (ridx >= 4 && wb && !werr) { + push_embr(miter, iter); + werr |= disk_write_sectors(&iter->di, iter->sub.dos.cebr_lba, iter->data, 1); + wb = 0; + } + } + + if (iter->status > PI_DONE) + goto bail; + + /* last write */ + if (wb && !werr) { + push_embr(miter, iter); + werr |= disk_write_sectors(&iter->di, iter->sub.dos.cebr_lba, iter->data, 1); + } + if (werr) + error("WARNING: failed to write E/MBR during 'fixchs'\n"); + +bail: + pi_del(&iter); + return 0; +} + +/* vim: set ts=8 sts=4 sw=4 noet: */ |