summaryrefslogtreecommitdiff
path: root/com32/chain/mangle.c
diff options
context:
space:
mode:
authorJannis Pohlmann <jannis.pohlmann@codethink.co.uk>2012-12-22 11:51:38 +0000
committerJannis Pohlmann <jannis.pohlmann@codethink.co.uk>2012-12-22 11:51:38 +0000
commit8821237240c5374d83298b2da5ad88fa1e3c1ef7 (patch)
treee1bc03fa83c61165f48ffe3f528ac5496332872a /com32/chain/mangle.c
parent38dcca25c8855c95649e3f0b5b09fae862ed5c7a (diff)
parent7307d60063ee4303da4de45f9d984fdc8df92146 (diff)
downloadsyslinux-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.c618
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: */