Merge branch 'chaindev'
diff --git a/Makefile b/Makefile
index 48b3420..1b419ae 100644
--- a/Makefile
+++ b/Makefile
@@ -34,7 +34,7 @@
 MODULES = memdisk/memdisk memdump/memdump.com modules/*.com \
 	com32/menu/*.c32 com32/modules/*.c32 com32/mboot/*.c32 \
 	com32/hdt/*.c32 com32/rosh/*.c32 com32/gfxboot/*.c32 \
-	com32/sysdump/*.c32 com32/lua/src/*.c32
+	com32/sysdump/*.c32 com32/lua/src/*.c32 com32/chain/*.c32
 
 # syslinux.exe is BTARGET so as to not require everyone to have the
 # mingw suite installed
diff --git a/com32/Makefile b/com32/Makefile
index da632a1..b59fd3f 100644
--- a/com32/Makefile
+++ b/com32/Makefile
@@ -1,5 +1,5 @@
 SUBDIRS = libupload tools lib gpllib libutil modules mboot menu samples rosh cmenu \
-	  hdt gfxboot sysdump lua/src
+	  hdt gfxboot sysdump lua/src chain
 
 all tidy dist clean spotless install:
 	set -e; for d in $(SUBDIRS); do $(MAKE) -C $$d $@; done
diff --git a/com32/chain/Makefile b/com32/chain/Makefile
new file mode 100644
index 0000000..9d398a8
--- /dev/null
+++ b/com32/chain/Makefile
@@ -0,0 +1,42 @@
+## -----------------------------------------------------------------------
+##
+##   Copyright 2001-2010 H. Peter Anvin - All Rights Reserved
+##   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.
+##
+## -----------------------------------------------------------------------
+
+
+topdir = ../..
+MAKEDIR = $(topdir)/mk
+include $(MAKEDIR)/com32.mk
+
+OBJS = chain.o partiter.o utility.o options.o mangle.o
+
+all: chain.c32
+
+chain.elf: $(OBJS) $(LIBS) $(C_LIBS)
+	$(LD) $(LDFLAGS) -o $@ $^
+
+%.o: %.c
+	$(CC) $(MAKEDEPS) $(CFLAGS) $(CHAINEXTOPT) -c -o $@ $<
+
+tidy dist:
+	rm -f *.o *.lo *.a *.lst *.elf .*.d *.tmp
+
+clean: tidy
+	rm -f *.lnx
+
+spotless: clean
+	rm -f *.lss *.c32 *.com
+	rm -f *~ \#*
+
+install:
+
+
+-include .*.d
diff --git a/com32/chain/chain.c b/com32/chain/chain.c
new file mode 100644
index 0000000..30153c4
--- /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: */
diff --git a/com32/chain/chain.h b/com32/chain/chain.h
new file mode 100644
index 0000000..fc481bc
--- /dev/null
+++ b/com32/chain/chain.h
@@ -0,0 +1,14 @@
+#ifndef _COM32_CHAIN_CHAIN_H
+#define _COM32_CHAIN_CHAIN_H
+
+#include <syslinux/movebits.h>
+
+struct data_area {
+    void *data;
+    addr_t base;
+    addr_t size;
+};
+
+#endif
+
+/* vim: set ts=8 sts=4 sw=4 noet: */
diff --git a/com32/chain/common.h b/com32/chain/common.h
new file mode 100644
index 0000000..b170a73
--- /dev/null
+++ b/com32/chain/common.h
@@ -0,0 +1,9 @@
+#ifndef _COM32_CHAIN_COMMON_H
+#define _COM32_CHAIN_COMMON_H
+
+#define ADDRMAX 0x9EFFFu
+#define ADDRMIN 0x500u
+
+#endif
+
+/* vim: set ts=8 sts=4 sw=4 noet: */
diff --git a/com32/chain/mangle.c b/com32/chain/mangle.c
new file mode 100644
index 0000000..8358106
--- /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: */
diff --git a/com32/chain/mangle.h b/com32/chain/mangle.h
new file mode 100644
index 0000000..bcefea3
--- /dev/null
+++ b/com32/chain/mangle.h
@@ -0,0 +1,32 @@
+#ifndef _COM32_CHAIN_MANGLE_H
+#define _COM32_CHAIN_MANGLE_H
+
+#include "chain.h"
+#include "partiter.h"
+
+/* file's manglers */
+int manglef_isolinux(struct data_area *data);
+int manglef_grub(const struct part_iter *iter, struct data_area *data);
+int manglef_bpb(const struct part_iter *iter, struct data_area *data);
+/* int manglef_drmk(struct data_area *data);*/
+
+/* sector's manglers */
+int mangles_bpb(const struct part_iter *iter, struct data_area *data);
+int mangles_save(const struct part_iter *iter, const struct data_area *data, void *org);
+int mangles_cmldr(struct data_area *data);
+
+/* sector + file's manglers */
+int manglesf_bss(struct data_area *sec, struct data_area *fil);
+
+/* registers' manglers */
+int mangler_init(const struct part_iter *iter);
+int mangler_handover(const struct part_iter *iter, const struct data_area *data);
+int mangler_grldr(const struct part_iter *iter);
+
+/* partition layout's manglers */
+int manglepe_fixchs(struct part_iter *miter);
+int manglepe_hide(struct part_iter *miter);
+
+#endif
+
+/* vim: set ts=8 sts=4 sw=4 noet: */
diff --git a/com32/chain/options.c b/com32/chain/options.c
new file mode 100644
index 0000000..658a45c
--- /dev/null
+++ b/com32/chain/options.c
@@ -0,0 +1,376 @@
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include "common.h"
+#include "chain.h"
+#include "utility.h"
+#include "options.h"
+
+struct options opt;
+
+static int soi_s2n(char *ptr, unsigned int *seg,
+		       unsigned int *off,
+		       unsigned int *ip,
+		       unsigned int def)
+{
+    unsigned int segval = 0, offval, ipval, val;
+    char *p;
+
+    offval = def;
+    ipval = def;
+
+    segval = strtoul(ptr, &p, 0);
+    if (p[0] == ':' && p[1] && p[1] != ':')
+	offval = strtoul(p+1, &p, 0);
+    if (p[0] == ':' && p[1] && p[1] != ':')
+	ipval = strtoul(p+1, NULL, 0);
+
+    val = (segval << 4) + offval;
+
+    if (val < ADDRMIN || val > ADDRMAX) {
+	error("Invalid seg:off:* address specified..\n");
+	goto bail;
+    }
+
+    val = (segval << 4) + ipval;
+
+    if (ipval > 0xFFFE || val < ADDRMIN || val > ADDRMAX) {
+	error("Invalid seg:*:ip address specified.\n");
+	goto bail;
+    }
+
+    if (seg)
+	*seg = segval;
+    if (off)
+	*off = offval;
+    if (ip)
+	*ip  = ipval;
+
+    return 0;
+bail:
+    return -1;
+}
+
+static void usage(void)
+{
+    unsigned int i;
+    static const char key[] = "Press any key...\n";
+    static const char *const usage[] = {
+"\
+Usage:\n\
+    chain.c32 [options]\n\
+    chain.c32 {fd|hd}<disk#>{,| }[<part#>] [options]\n\
+    chain.c32 mbr{:|=}<id>{,| }[<part#>] [options]\n\
+    chain.c32 guid{:|=}<guid>{,| }[<part#>] [options]\n\
+    chain.c32 label{:|=}<label> [<part#>] [options]\n\
+    chain.c32 boot{,| }[<part#>] [options]\n\
+    chain.c32 fs [options]\n\
+", "\
+\nOptions ('no' prefix specifies default value):\n\
+    sect[=<s[:o[:i]]>]   Load sector at <s:o>, jump to <s:i>\n\
+                         - defaults to 0:0x7C00:0x7C00\n\
+                         - ommited o/i values default to 0\n\
+    maps                 Map loaded sector into real memory\n\
+    nosetbpb             Fix BPB fields in loaded sector\n\
+    nofilebpb            Apply 'setbpb' to loaded file\n\
+    nosave               Write adjusted sector back to disk\n\
+    hand                 Prepare handover area\n\
+    nohptr               Force ds:si and ds:bp to point to handover area\n\
+    noswap               Swap drive numbers, if bootdisk is not fd0/hd0\n\
+    nohide               Disable all hide variations (also the default)\n\
+    hide                 Hide primary partitions, unhide selected partition\n\
+    hideall              Hide *all* partitions, unhide selected partition\n\
+    unhide               Unhide primary partitions\n\
+    unhideall            Unhide *all* partitions\n\
+    nofixchs             Walk *all* partitions and fix E/MBRs' chs values\n\
+    nokeeppxe            Keep the PXE and UNDI stacks in memory (PXELINUX)\n\
+    nowarn               Wait for a keypress to continue chainloading\n\
+                         - useful to see emited warnings\n\
+    nobreak              Actually perform the chainloading\n\
+", "\
+\nOptions continued ...\n\
+    file=<file>          Load and execute <file>\n\
+    seg=<s[:o[:i]]>      Load file at <s:o>, jump to <s:i>\n\
+                         - defaults to 0:0x7C00:0x7C00\n\
+                         - ommited o/i values default to 0\n\
+    isolinux=<loader>    Load another version of ISOLINUX\n\
+    ntldr=<loader>       Load Windows NTLDR, SETUPLDR.BIN or BOOTMGR\n\
+    reactos=<loader>     Load ReactOS's loader\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 2.xx - 6.xx IO.SYS\n\
+    msdos7=<loader>      Load MS-DOS 7+ IO.SYS\n\
+    pcdos=<loader>       Load PC-DOS IBMBIO.COM\n\
+    drmk=<loader>        Load DRMK DELLBIO.BIN\n\
+    grub=<loader>        Load GRUB Legacy stage2\n\
+    grubcfg=<filename>   Set alternative config filename for GRUB Legacy\n\
+    grldr=<loader>       Load GRUB4DOS grldr\n\
+    bss=<filename>       Emulate syslinux's BSS\n\
+    bs=<filename>        Emulate syslinux's BS\n\
+\nPlease see doc/chain.txt for the detailed documentation.\n\
+"
+    };
+    for (i = 0; i < sizeof(usage)/sizeof(usage[0]); i++) {
+	if (i) {
+	    error(key);
+	    wait_key();
+	}
+	error(usage[i]);
+    }
+}
+
+void opt_set_defs(void)
+{
+    memset(&opt, 0, sizeof(opt));
+    opt.sect = true;	    /* by def. load sector */
+    opt.maps = true;	    /* by def. map sector */
+    opt.hand = true;	    /* by def. prepare handover */
+    opt.brkchain = false;   /* by def. do chainload */
+    opt.foff = opt.soff = opt.fip = opt.sip = 0x7C00;
+    opt.drivename = "boot";
+#ifdef DEBUG
+    opt.warn = true;
+#endif
+}
+
+int opt_parse_args(int argc, char *argv[])
+{
+    int i;
+    unsigned int v;
+    char *p;
+
+    for (i = 1; i < argc; i++) {
+	if (!strncmp(argv[i], "file=", 5)) {
+	    opt.file = argv[i] + 5;
+	} else if (!strcmp(argv[i], "nofile")) {
+	    opt.file = NULL;
+	} else if (!strncmp(argv[i], "seg=", 4)) {
+	    if (soi_s2n(argv[i] + 4, &opt.fseg, &opt.foff, &opt.fip, 0))
+		goto bail;
+	} else if (!strncmp(argv[i], "bss=", 4)) {
+	    opt.file = argv[i] + 4;
+	    opt.bss = true;
+	    opt.maps = false;
+	    opt.setbpb = true;
+	    /* opt.save = true; */
+	} else if (!strncmp(argv[i], "bs=", 3)) {
+	    opt.file = argv[i] + 3;
+	    opt.sect = false;
+	    opt.filebpb = true;
+	} else if (!strncmp(argv[i], "isolinux=", 9)) {
+	    opt.file = argv[i] + 9;
+	    opt.isolinux = true;
+	    opt.hand = false;
+	    opt.sect = false;
+	} else if (!strncmp(argv[i], "ntldr=", 6)) {
+	    opt.fseg = 0x2000;  /* NTLDR wants this address */
+	    opt.foff = 0;
+	    opt.fip = 0;
+	    opt.file = argv[i] + 6;
+	    opt.setbpb = true;
+	    /* opt.save = true; */
+	    opt.hand = false;
+	} else if (!strncmp(argv[i], "reactos=", 8)) {
+	    /*
+	     * settings based on commit
+	     *   ad4cf1470977f648ee1dd45e97939589ccb0393c
+	     * note, conflicts with:
+	     *   http://reactos.freedoors.org/Reactos%200.3.13/ReactOS-0.3.13-REL-src/boot/freeldr/notes.txt
+	     */
+	    opt.fseg = 0;
+	    opt.foff = 0x8000;
+	    opt.fip = 0x8100;
+	    opt.file = argv[i] + 8;
+	    opt.setbpb = true;
+	    /* opt.save = true; */
+	    opt.hand = false;
+	} else if (!strncmp(argv[i], "cmldr=", 6)) {
+	    opt.fseg = 0x2000;  /* CMLDR wants this address */
+	    opt.foff = 0;
+	    opt.fip = 0;
+	    opt.file = argv[i] + 6;
+	    opt.cmldr = true;
+	    opt.setbpb = true;
+	    /* opt.save = true; */
+	    opt.hand = false;
+	} else if (!strncmp(argv[i], "freedos=", 8)) {
+	    opt.fseg = 0x60;    /* FREEDOS wants this address */
+	    opt.foff = 0;
+	    opt.fip = 0;
+	    opt.sseg = 0x1FE0;
+	    opt.file = argv[i] + 8;
+	    opt.setbpb = true;
+	    /* opt.save = true; */
+	    opt.hand = false;
+	} else if ( (v = 6, !strncmp(argv[i], "msdos=", v) ||
+		     !strncmp(argv[i], "pcdos=", v)) ||
+		    (v = 7, !strncmp(argv[i], "msdos7=", v)) ) {
+	    opt.fseg = 0x70;    /* MS-DOS 2.00 .. 6.xx wants this address */
+	    opt.foff = 0;
+	    opt.fip = v == 7 ? 0x200 : 0;  /* MS-DOS 7.0+ wants this ip */
+	    opt.sseg = 0x8000;
+	    opt.file = argv[i] + v;
+	    opt.setbpb = true;
+	    /* opt.save = true; */
+	    opt.hand = false;
+	} else if (!strncmp(argv[i], "drmk=", 5)) {
+	    opt.fseg = 0x70;    /* DRMK wants this address */
+	    opt.foff = 0;
+	    opt.fip = 0;
+	    opt.sseg = 0x2000;
+	    opt.soff = 0;
+	    opt.sip = 0;
+	    opt.file = argv[i] + 5;
+	    /* opt.drmk = true; */
+	    opt.setbpb = true;
+	    /* opt.save = true; */
+	    opt.hand = false;
+	} else if (!strncmp(argv[i], "grub=", 5)) {
+	    opt.fseg = 0x800;	/* stage2 wants this address */
+	    opt.foff = 0;
+	    opt.fip = 0x200;
+	    opt.file = argv[i] + 5;
+	    opt.grub = true;
+	    opt.hand = false;
+	    opt.sect = false;
+	} else if (!strncmp(argv[i], "grubcfg=", 8)) {
+	    opt.grubcfg = argv[i] + 8;
+	} else if (!strncmp(argv[i], "grldr=", 6)) {
+	    opt.file = argv[i] + 6;
+	    opt.grldr = true;
+	    opt.hand = false;
+	    opt.sect = false;
+	} else if (!strcmp(argv[i], "keeppxe")) {
+	    opt.keeppxe = 3;
+	} else if (!strcmp(argv[i], "nokeeppxe")) {
+	    opt.keeppxe = 0;
+	} else if (!strcmp(argv[i], "maps")) {
+	    opt.maps = true;
+	} else if (!strcmp(argv[i], "nomaps")) {
+	    opt.maps = false;
+	} else if (!strcmp(argv[i], "hand")) {
+	    opt.hand = true;
+	} else if (!strcmp(argv[i], "nohand")) {
+	    opt.hand = false;
+	} else if (!strcmp(argv[i], "hptr")) {
+	    opt.hptr = true;
+	} else if (!strcmp(argv[i], "nohptr")) {
+	    opt.hptr = false;
+	} else if (!strcmp(argv[i], "swap")) {
+	    opt.swap = true;
+	} else if (!strcmp(argv[i], "noswap")) {
+	    opt.swap = false;
+	} else if (!strcmp(argv[i], "nohide")) {
+	    opt.hide = 0;
+	} else if (!strcmp(argv[i], "hide")) {
+	    opt.hide = 1; /* 001b */
+	} else if (!strcmp(argv[i], "hideall")) {
+	    opt.hide = 2; /* 010b */
+	} else if (!strcmp(argv[i], "unhide")) {
+	    opt.hide = 5; /* 101b */
+	} else if (!strcmp(argv[i], "unhideall")) {
+	    opt.hide = 6; /* 110b */
+	} else if (!strcmp(argv[i], "setbpb")) {
+	    opt.setbpb = true;
+	} else if (!strcmp(argv[i], "nosetbpb")) {
+	    opt.setbpb = false;
+	} else if (!strcmp(argv[i], "filebpb")) {
+	    opt.filebpb = true;
+	} else if (!strcmp(argv[i], "nofilebpb")) {
+	    opt.filebpb = false;
+	} else if (!strncmp(argv[i], "sect=", 5) ||
+		   !strcmp(argv[i], "sect")) {
+	    if (argv[i][4]) {
+		if (soi_s2n(argv[i] + 5, &opt.sseg, &opt.soff, &opt.sip, 0))
+		    goto bail;
+	    }
+	    opt.sect = true;
+	} else if (!strcmp(argv[i], "nosect")) {
+	    opt.sect = false;
+	    opt.maps = false;
+	} else if (!strcmp(argv[i], "save")) {
+	    opt.save = true;
+	} else if (!strcmp(argv[i], "nosave")) {
+	    opt.save = false;
+	} else if (!strcmp(argv[i], "fixchs")) {
+	    opt.fixchs = true;
+	} else if (!strcmp(argv[i], "nofixchs")) {
+	    opt.fixchs = false;
+	} else if (!strcmp(argv[i], "warn")) {
+	    opt.warn = true;
+	} else if (!strcmp(argv[i], "nowarn")) {
+	    opt.warn = false;
+	} else if (!strcmp(argv[i], "nobreak")) {
+	    opt.brkchain = false;
+	} else if (!strcmp(argv[i], "break")) {
+	    opt.brkchain = true;
+	    opt.file = NULL;
+	    opt.maps = false;
+	    opt.hand = 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")) {
+	    opt.drivename = argv[i];
+	    if (strncmp(argv[i], "label", 5))
+		p = strchr(opt.drivename, ',');
+	    else
+		p = NULL;
+	    if (p) {
+		*p = '\0';
+		opt.partition = p + 1;
+	    } else if (argv[i + 1] && argv[i + 1][0] >= '0'
+		    && argv[i + 1][0] <= '9') {
+		opt.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 0
+    if ((!opt.maps || !opt.sect) && !opt.file) {
+	error("You have to load something.\n");
+	goto bail;
+    }
+#endif
+
+    if (opt.filebpb && !opt.file) {
+	error("Option 'filebpb' requires a file.\n");
+	goto bail;
+    }
+
+    if (opt.save && !opt.sect) {
+	error("Option 'save' requires a sector.\n");
+	goto bail;
+    }
+
+    if (opt.setbpb && !opt.sect) {
+	error("Option 'setbpb' requires a sector.\n");
+	goto bail;
+    }
+
+    if (opt.maps && !opt.sect) {
+	error("Option 'maps' requires a sector.\n");
+	goto bail;
+    }
+
+    return 0;
+bail:
+    return -1;
+}
+
+/* vim: set ts=8 sts=4 sw=4 noet: */
diff --git a/com32/chain/options.h b/com32/chain/options.h
new file mode 100644
index 0000000..4493ef1
--- /dev/null
+++ b/com32/chain/options.h
@@ -0,0 +1,47 @@
+#ifndef _COM32_CHAIN_OPTIONS_H
+#define _COM32_CHAIN_OPTIONS_H
+
+#include <stdint.h>
+#include <syslinux/bootrm.h>
+
+struct options {
+    unsigned int fseg;
+    unsigned int foff;
+    unsigned int fip;
+    unsigned int sseg;
+    unsigned int soff;
+    unsigned int sip;
+    const char *drivename;
+    const char *partition;
+    const char *file;
+    const char *grubcfg;
+    bool isolinux;
+    bool cmldr;
+    bool drmk;
+    bool grub;
+    bool grldr;
+    bool maps;
+    bool hand;
+    bool hptr;
+    bool swap;
+    int hide;
+    bool sect;
+    bool save;
+    bool bss;
+    bool setbpb;
+    bool filebpb;
+    bool fixchs;
+    bool warn;
+    bool brkchain;
+    uint16_t keeppxe;
+    struct syslinux_rm_regs regs;
+};
+
+extern struct options opt;
+
+void opt_set_defs(void);
+int opt_parse_args(int argc, char *argv[]);
+
+#endif
+
+/* vim: set ts=8 sts=4 sw=4 noet: */
diff --git a/com32/chain/partiter.c b/com32/chain/partiter.c
new file mode 100644
index 0000000..1acd195
--- /dev/null
+++ b/com32/chain/partiter.c
@@ -0,0 +1,805 @@
+/* ----------------------------------------------------------------------- *
+ *
+ *   Copyright 2003-2010 H. Peter Anvin - All Rights Reserved
+ *   Copyright 2010 Shao Miller
+ *   Copyright 2010 Michal Soltys
+ *
+ *   Permission is hereby granted, free of charge, to any person
+ *   obtaining a copy of this software and associated documentation
+ *   files (the "Software"), to deal in the Software without
+ *   restriction, including without limitation the rights to use,
+ *   copy, modify, merge, publish, distribute, sublicense, and/or
+ *   sell copies of the Software, and to permit persons to whom
+ *   the Software is furnished to do so, subject to the following
+ *   conditions:
+ *
+ *   The above copyright notice and this permission notice shall
+ *   be included in all copies or substantial portions of the Software.
+ *
+ *   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ *   EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ *   OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ *   NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ *   HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ *   WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ *   FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ *   OTHER DEALINGS IN THE SOFTWARE.
+ *
+ * ----------------------------------------------------------------------- */
+
+/*
+ * partiter.c
+ *
+ * Provides disk / partition iteration.
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdarg.h>
+#include <zlib.h>
+#include <syslinux/disk.h>
+#include "common.h"
+#include "partiter.h"
+#include "utility.h"
+
+#define ost_is_ext(type) ((type) == 0x05 || (type) == 0x0F || (type) == 0x85)
+#define ost_is_nondata(type) (ost_is_ext(type) || (type) == 0x00)
+#define sane(s,l) ((s)+(l) > (s))
+
+/* forwards */
+
+static int iter_ctor(struct part_iter *, va_list *);
+static int iter_dos_ctor(struct part_iter *, va_list *);
+static int iter_gpt_ctor(struct part_iter *, va_list *);
+static void iter_dtor(struct part_iter *);
+static struct part_iter *pi_dos_next(struct part_iter *);
+static struct part_iter *pi_gpt_next(struct part_iter *);
+static struct part_iter *pi_raw_next(struct part_iter *);
+
+static struct itertype types[] = {
+   [0] = {
+	.ctor = &iter_dos_ctor,
+	.dtor = &iter_dtor,
+	.next = &pi_dos_next,
+}, [1] = {
+	.ctor = &iter_gpt_ctor,
+	.dtor = &iter_dtor,
+	.next = &pi_gpt_next,
+}, [2] = {
+	.ctor = &iter_ctor,
+	.dtor = &iter_dtor,
+	.next = &pi_raw_next,
+}};
+
+const struct itertype * const typedos = types;
+const struct itertype * const typegpt = types+1;
+const struct itertype * const typeraw = types+2;
+
+#ifdef DEBUG
+static int inv_type(const void *type)
+{
+    int i, cnt = sizeof(types)/sizeof(types[0]);
+    for (i = 0; i < cnt; i++) {
+	if (type == types + i)
+	    return 0;
+    }
+    return -1;
+}
+#endif
+
+/**
+ * iter_ctor() - common iterator initialization
+ * @iter:	iterator pointer
+ * @args(0):	disk_info structure used for disk functions
+ * @args(1):	stepall modifier
+ *
+ * Second and further arguments are passed as a pointer to va_list
+ **/
+static int iter_ctor(struct part_iter *iter, va_list *args)
+{
+    const struct disk_info *di = va_arg(*args, const struct disk_info *);
+    int stepall = va_arg(*args, int);
+
+#ifdef DEBUG
+    if (!di)
+	return -1;
+#endif
+
+    memcpy(&iter->di, di, sizeof(struct disk_info));
+    iter->stepall = stepall;
+    iter->index0 = -1;
+    iter->length = di->lbacnt;
+
+    return 0;
+}
+
+/**
+ * iter_dtor() - common iterator cleanup
+ * @iter:	iterator pointer
+ *
+ **/
+static void iter_dtor(struct part_iter *iter)
+{
+    free(iter->data);
+}
+
+/**
+ * iter_dos_ctor() - MBR/EBR iterator specific initialization
+ * @iter:	iterator pointer
+ * @args(0):	disk_info structure used for disk functions
+ * @args(1):	pointer to buffer with loaded valid MBR
+ *
+ * Second and further arguments are passed as a pointer to va_list.
+ * This function only makes rudimentary checks. If user uses
+ * pi_new(), he/she is responsible for doing proper sanity checks.
+ **/
+static int iter_dos_ctor(struct part_iter *iter, va_list *args)
+{
+    const struct disk_dos_mbr *mbr;
+
+    /* uses args(0) */
+    if (iter_ctor(iter, args))
+	return -1;
+
+    mbr = va_arg(*args, const struct disk_dos_mbr *);
+
+#ifdef DEBUG
+    if (!mbr)
+	goto bail;
+#endif
+
+    if (!(iter->data = malloc(sizeof(struct disk_dos_mbr))))
+	goto bail;
+
+    memcpy(iter->data, mbr, sizeof(struct disk_dos_mbr));
+
+    iter->sub.dos.bebr_index0 = -1;
+    iter->sub.dos.disk_sig = mbr->disk_sig;
+
+    return 0;
+bail:
+    iter->type->dtor(iter);
+    return -1;
+}
+
+/**
+ * iter_gpt_ctor() - GPT iterator specific initialization
+ * @iter:	iterator pointer
+ * @args(0):	ptr to disk_info structure
+ * @args(1):	ptr to buffer with GPT header
+ * @args(2):	ptr to buffer with GPT partition list
+ *
+ * Second and further arguments are passed as a pointer to va_list.
+ * This function only makes rudimentary checks. If user uses
+ * pi_new(), he/she is responsible for doing proper sanity checks.
+ **/
+static int iter_gpt_ctor(struct part_iter *iter, va_list *args)
+{
+    uint64_t siz;
+    const struct disk_gpt_header *gpth;
+    const struct disk_gpt_part_entry *gptl;
+
+    /* uses args(0) */
+    if (iter_ctor(iter, args))
+	return -1;
+
+    gpth = va_arg(*args, const struct disk_gpt_header *);
+    gptl = va_arg(*args, const struct disk_gpt_part_entry *);
+
+#ifdef DEBUG
+    if (!gpth || !gptl)
+	goto bail;
+#endif
+
+    siz = (uint64_t)gpth->part_count * gpth->part_size;
+
+#ifdef DEBUG
+    if (!siz || (siz + iter->di.bps - 1) / iter->di.bps > 255u ||
+	    gpth->part_size < sizeof(struct disk_gpt_part_entry)) {
+	goto bail;
+    }
+#endif
+
+    if (!(iter->data = malloc((size_t)siz)))
+	goto bail;
+
+    memcpy(iter->data, gptl, (size_t)siz);
+
+    iter->sub.gpt.pe_count = (int)gpth->part_count;
+    iter->sub.gpt.pe_size = (int)gpth->part_size;
+    iter->sub.gpt.ufirst = gpth->lba_first_usable;
+    iter->sub.gpt.ulast = gpth->lba_last_usable;
+
+    memcpy(&iter->sub.gpt.disk_guid, &gpth->disk_guid, sizeof(struct guid));
+
+    return 0;
+bail:
+    iter->type->dtor(iter);
+    return -1;
+}
+
+/* Logical partition must be sane, meaning:
+ * - must be data or empty
+ * - must have non-0 start and length
+ * - values must not wrap around 32bit
+ * - must be inside current EBR frame
+ */
+
+static int notsane_logical(const struct part_iter *iter)
+{
+    const struct disk_dos_part_entry *dp;
+    uint32_t end_log;
+
+    dp = ((struct disk_dos_mbr *)iter->data)->table;
+
+    if (!dp[0].ostype)
+	return 0;
+
+    if (ost_is_ext(dp[0].ostype)) {
+	error("1st EBR entry must be data or empty.\n");
+	return -1;
+    }
+
+    end_log = dp[0].start_lba + dp[0].length;
+
+    if (!dp[0].start_lba ||
+	!dp[0].length ||
+	!sane(dp[0].start_lba, dp[0].length) ||
+	end_log > iter->sub.dos.ebr_size) {
+
+	error("Insane logical partition.\n");
+	return -1;
+    }
+
+    return 0;
+}
+
+/* Extended partition must be sane, meaning:
+ * - must be extended or empty
+ * - must have non-0 start and length
+ * - values must not wrap around 32bit
+ * - must be inside base EBR frame
+ */
+
+static int notsane_extended(const struct part_iter *iter)
+{
+    const struct disk_dos_part_entry *dp;
+    uint32_t end_ebr;
+
+    dp = ((struct disk_dos_mbr *)iter->data)->table;
+
+    if (!dp[1].ostype)
+	return 0;
+
+    if (!ost_is_nondata(dp[1].ostype)) {
+	error("2nd EBR entry must be extended or empty.\n");
+	return -1;
+    }
+
+    end_ebr = dp[1].start_lba + dp[1].length;
+
+    if (!dp[1].start_lba ||
+	!dp[1].length ||
+	!sane(dp[1].start_lba, dp[1].length) ||
+	end_ebr > iter->sub.dos.bebr_size) {
+
+	error("Insane extended partition.\n");
+	return -1;
+    }
+
+    return 0;
+}
+
+/* Primary partition must be sane, meaning:
+ * - must have non-0 start and length
+ * - values must not wrap around 32bit
+ */
+
+static int notsane_primary(const struct part_iter *iter)
+{
+    const struct disk_dos_part_entry *dp;
+    dp = ((struct disk_dos_mbr *)iter->data)->table + iter->index0;
+
+    if (!dp->ostype)
+	return 0;
+
+    if (!dp->start_lba ||
+	!dp->length ||
+	!sane(dp->start_lba, dp->length) ||
+	dp->start_lba + dp->length > iter->di.lbacnt) {
+	error("Insane primary (MBR) partition.\n");
+	return -1;
+    }
+
+    return 0;
+}
+
+static int notsane_gpt(const struct part_iter *iter)
+{
+    const struct disk_gpt_part_entry *gp;
+    gp = (const struct disk_gpt_part_entry *)
+	(iter->data + iter->index0 * iter->sub.gpt.pe_size);
+
+    if (guid_is0(&gp->type))
+	return 0;
+
+    if (gp->lba_first < iter->sub.gpt.ufirst ||
+	gp->lba_last > iter->sub.gpt.ulast) {
+	error("Insane GPT partition.\n");
+	return -1;
+    }
+
+    return 0;
+}
+
+static int pi_dos_next_mbr(struct part_iter *iter, uint32_t *lba,
+			    struct disk_dos_part_entry **_dp)
+{
+    struct disk_dos_part_entry *dp;
+
+    while (++iter->index0 < 4) {
+	dp = ((struct disk_dos_mbr *)iter->data)->table + iter->index0;
+
+	if (notsane_primary(iter)) {
+	    iter->status = PI_INSANE;
+	    goto bail;
+	}
+
+	if (ost_is_ext(dp->ostype)) {
+	    if (iter->sub.dos.bebr_index0 >= 0) {
+		error("You have more than 1 extended partition.\n");
+		iter->status = PI_INSANE;
+		goto bail;
+	    }
+	    /* record base EBR index */
+	    iter->sub.dos.bebr_index0 = iter->index0;
+	}
+	if (!ost_is_nondata(dp->ostype) || iter->stepall) {
+	    *lba = dp->start_lba;
+	    *_dp = dp;
+	    break;
+	}
+    }
+
+    return 0;
+bail:
+    return -1;
+}
+
+static int prep_base_ebr(struct part_iter *iter)
+{
+    struct disk_dos_part_entry *dp;
+
+    if (iter->sub.dos.bebr_index0 < 0)	/* if we don't have base extended partition at all */
+	return -1;
+    else if (!iter->sub.dos.bebr_start) { /* if not initialized yet */
+	dp = ((struct disk_dos_mbr *)iter->data)->table + iter->sub.dos.bebr_index0;
+
+	iter->sub.dos.bebr_start = dp->start_lba;
+	iter->sub.dos.bebr_size = dp->length;
+
+	iter->sub.dos.ebr_start = 0;
+	iter->sub.dos.ebr_size = iter->sub.dos.bebr_size;
+
+	iter->sub.dos.cebr_lba = 0;
+	iter->sub.dos.nebr_lba = iter->sub.dos.bebr_start;
+
+	iter->index0--;
+    }
+    return 0;
+}
+
+static int pi_dos_next_ebr(struct part_iter *iter, uint32_t *lba,
+			    struct disk_dos_part_entry **_dp)
+{
+    struct disk_dos_part_entry *dp;
+
+    if (prep_base_ebr(iter)) {
+	iter->status = PI_DONE;
+	return -1;
+    }
+
+    while (++iter->index0 < 1024 && iter->sub.dos.nebr_lba) {
+	free(iter->data);
+	if (!(iter->data =
+		    disk_read_sectors(&iter->di, iter->sub.dos.nebr_lba, 1))) {
+	    error("Couldn't load EBR.\n");
+	    iter->status = PI_ERRLOAD;
+	    return -1;
+	}
+
+	if (notsane_logical(iter) || notsane_extended(iter)) {
+	    iter->status = PI_INSANE;
+	    return -1;
+	}
+
+	dp = ((struct disk_dos_mbr *)iter->data)->table;
+
+	iter->sub.dos.cebr_lba = iter->sub.dos.nebr_lba;
+
+	/* setup next frame values */
+	if (dp[1].ostype) {
+	    iter->sub.dos.ebr_start = dp[1].start_lba;
+	    iter->sub.dos.ebr_size = dp[1].length;
+	    iter->sub.dos.nebr_lba = iter->sub.dos.bebr_start + dp[1].start_lba;
+	} else {
+	    iter->sub.dos.ebr_start = 0;
+	    iter->sub.dos.ebr_size = 0;
+	    iter->sub.dos.nebr_lba = 0;
+	}
+
+	if (!dp[0].ostype)
+	    iter->sub.dos.skipcnt++;
+
+	if (dp[0].ostype || iter->stepall) {
+	    *lba = iter->sub.dos.cebr_lba + dp[0].start_lba;
+	    *_dp = dp;
+	    return 0;
+	}
+	/*
+	 * This way it's possible to continue, if some crazy soft left a "hole"
+	 * - EBR with a valid extended partition without a logical one. In
+	 * such case, linux will not reserve a number for such hole - so we
+	 * don't increase index0. If stepall flag is set, we will never reach
+	 * this place.
+	 */
+    }
+    iter->status = PI_DONE;
+    return -1;
+}
+
+static struct part_iter *pi_dos_next(struct part_iter *iter)
+{
+    uint32_t start_lba = 0;
+    struct disk_dos_part_entry *dos_part = NULL;
+
+    if (iter->status)
+	goto bail;
+
+    /* look for primary partitions */
+    if (iter->index0 < 4 &&
+	    pi_dos_next_mbr(iter, &start_lba, &dos_part))
+	goto bail;
+
+    /* look for logical partitions */
+    if (iter->index0 >= 4 &&
+	    pi_dos_next_ebr(iter, &start_lba, &dos_part))
+	goto bail;
+
+    /*
+     * note special index handling, if we have stepall set -
+     * this is made to keep index consistent with non-stepall
+     * iterators
+     */
+
+    if (iter->index0 >= 4 && !dos_part->ostype)
+	iter->index = -1;
+    else
+	iter->index = iter->index0 - iter->sub.dos.skipcnt + 1;
+    iter->rawindex = iter->index0 + 1;
+    iter->start_lba = start_lba;
+    iter->length = dos_part->length;
+    iter->record = (char *)dos_part;
+
+#ifdef DEBUG
+    disk_dos_part_dump(dos_part);
+#endif
+
+    return iter;
+bail:
+    return NULL;
+}
+
+static void gpt_conv_label(struct part_iter *iter)
+{
+    const struct disk_gpt_part_entry *gp;
+    const int16_t *orig_lab;
+
+    gp = (const struct disk_gpt_part_entry *)
+	(iter->data + iter->index0 * iter->sub.gpt.pe_size);
+    orig_lab = (const int16_t *)gp->name;
+
+    /* caveat: this is very crude conversion */
+    for (int i = 0; i < PI_GPTLABSIZE/2; i++) {
+	iter->sub.gpt.part_label[i] = (char)orig_lab[i];
+    }
+    iter->sub.gpt.part_label[PI_GPTLABSIZE/2] = 0;
+}
+
+static struct part_iter *pi_gpt_next(struct part_iter *iter)
+{
+    const struct disk_gpt_part_entry *gpt_part = NULL;
+
+    if (iter->status)
+	goto bail;
+
+    while (++iter->index0 < iter->sub.gpt.pe_count) {
+	gpt_part = (const struct disk_gpt_part_entry *)
+	    (iter->data + iter->index0 * iter->sub.gpt.pe_size);
+
+	if (notsane_gpt(iter)) {
+	    iter->status = PI_INSANE;
+	    goto bail;
+	}
+
+	if (!guid_is0(&gpt_part->type) || iter->stepall)
+	    break;
+    }
+    /* no more partitions ? */
+    if (iter->index0 == iter->sub.gpt.pe_count) {
+	iter->status = PI_DONE;
+	goto bail;
+    }
+    /* gpt_part is guaranteed to be valid here */
+    iter->index = iter->index0 + 1;
+    iter->rawindex = iter->index0 + 1;
+    iter->start_lba = gpt_part->lba_first;
+    iter->length = gpt_part->lba_last - gpt_part->lba_first + 1;
+    iter->record = (char *)gpt_part;
+    memcpy(&iter->sub.gpt.part_guid, &gpt_part->uid, sizeof(struct guid));
+    gpt_conv_label(iter);
+
+#ifdef DEBUG
+    disk_gpt_part_dump(gpt_part);
+#endif
+
+    return iter;
+bail:
+    return NULL;
+}
+
+static struct part_iter *pi_raw_next(struct part_iter *iter)
+{
+    iter->status = PI_DONE;
+    return NULL;
+}
+
+static int check_crc(uint32_t crc_match, const uint8_t *buf, unsigned int siz)
+{
+    uint32_t crc;
+
+    crc = crc32(0, NULL, 0);
+    crc = crc32(crc, buf, siz);
+
+    return crc_match != crc;
+}
+
+static int gpt_check_hdr_crc(const struct disk_info * const diskinfo, struct disk_gpt_header **_gh)
+{
+    struct disk_gpt_header *gh = *_gh;
+    uint64_t lba_alt;
+    uint32_t hold_crc32;
+
+    hold_crc32 = gh->chksum;
+    gh->chksum = 0;
+    if (check_crc(hold_crc32, (const uint8_t *)gh, gh->hdr_size)) {
+	error("WARNING: Primary GPT header checksum invalid.\n");
+	/* retry with backup */
+	lba_alt = gh->lba_alt;
+	free(gh);
+	if (!(gh = *_gh = disk_read_sectors(diskinfo, lba_alt, 1))) {
+	    error("Couldn't read backup GPT header.\n");
+	    return -1;
+	}
+	hold_crc32 = gh->chksum;
+	gh->chksum = 0;
+	if (check_crc(hold_crc32, (const uint8_t *)gh, gh->hdr_size)) {
+	    error("Secondary GPT header checksum invalid.\n");
+	    return -1;
+	}
+    }
+    /* restore old checksum */
+    gh->chksum = hold_crc32;
+
+    return 0;
+}
+
+/*
+ * ----------------------------------------------------------------------------
+ * Following functions are for users to call.
+ * ----------------------------------------------------------------------------
+ */
+
+
+int pi_next(struct part_iter **_iter)
+{
+    struct part_iter *iter;
+
+    if(!_iter || !*_iter)
+	return 0;
+    iter = *_iter;
+#ifdef DEBUG
+    if (inv_type(iter->type)) {
+	error("This is not a valid iterator.\n");
+	return 0;
+    }
+#endif
+    if ((iter = iter->type->next(iter))) {
+	*_iter = iter;
+    }
+    return (*_iter)->status;
+}
+
+/**
+ * pi_new() - get new iterator
+ * @itertype:	iterator type
+ * @...:	variable arguments passed to ctors
+ *
+ * Variable arguments depend on the type. Please see functions:
+ * iter_gpt_ctor() and iter_dos_ctor() for details.
+ **/
+struct part_iter *pi_new(const struct itertype *type, ...)
+{
+    int badctor = 0;
+    struct part_iter *iter = NULL;
+    va_list ap;
+
+    va_start(ap, type);
+
+#ifdef DEBUG
+    if (inv_type(type)) {
+	error("Unknown iterator requested.\n");
+	goto bail;
+    }
+#endif
+
+    if (!(iter = malloc(sizeof(struct part_iter)))) {
+	error("Couldn't allocate memory for the iterator.\n");
+	goto bail;
+    }
+
+    memset(iter, 0, sizeof(struct part_iter));
+    iter->type = type;
+
+    if (type->ctor(iter, &ap)) {
+	badctor = -1;
+	error("Cannot initialize the iterator.\n");
+	goto bail;
+    }
+
+bail:
+    va_end(ap);
+    if (badctor) {
+	free(iter);
+	iter = NULL;
+    }
+    return iter;
+}
+
+/**
+ * pi_del() - delete iterator
+ * @iter:       iterator double pointer
+ *
+ **/
+
+void pi_del(struct part_iter **_iter)
+{
+    struct part_iter *iter;
+
+    if(!_iter || !*_iter)
+	return;
+    iter = *_iter;
+
+#ifdef DEBUG
+    if (inv_type(iter->type)) {
+	error("This is not a valid iterator.\n");
+	return;
+    }
+#endif
+
+    iter->type->dtor(iter);
+    free(iter);
+    *_iter = NULL;
+}
+
+/**
+ * pi_begin() - check disk, validate, and get proper iterator
+ * @di:	    diskinfo struct pointer
+ *
+ * This function checks the disk for GPT or legacy partition table and allocates
+ * an appropriate iterator.
+ **/
+struct part_iter *pi_begin(const struct disk_info *di, int stepall)
+{
+    int setraw = 0;
+    struct part_iter *iter = NULL;
+    struct disk_dos_mbr *mbr = NULL;
+    struct disk_gpt_header *gpth = NULL;
+    struct disk_gpt_part_entry *gptl = NULL;
+
+    /* Read MBR */
+    if (!(mbr = disk_read_sectors(di, 0, 1))) {
+	error("Couldn't read first disk sector.\n");
+	goto bail;
+    }
+
+    setraw = -1;
+
+    /* Check for MBR magic*/
+    if (mbr->sig != disk_mbr_sig_magic) {
+	error("No MBR magic.\n");
+	goto bail;
+    }
+
+    /* Check for GPT protective MBR */
+    if (mbr->table[0].ostype == 0xEE) {
+	if (!(gpth = disk_read_sectors(di, 1, 1))) {
+	    error("Couldn't read potential GPT header.\n");
+	    goto bail;
+	}
+    }
+
+    if (gpth && gpth->rev.uint32 == 0x00010000 &&
+	    !memcmp(gpth->sig, disk_gpt_sig_magic, sizeof(disk_gpt_sig_magic))) {
+	/* looks like GPT v1.0 */
+	uint64_t gpt_loff;	    /* offset to GPT partition list in sectors */
+	uint64_t gpt_lsiz;	    /* size of GPT partition list in bytes */
+	uint64_t gpt_lcnt;	    /* size of GPT partition in sectors */
+#ifdef DEBUG
+	puts("Looks like a GPT v1.0 disk.");
+	disk_gpt_header_dump(gpth);
+#endif
+	/* Verify checksum, fallback to backup, then bail if invalid */
+	if (gpt_check_hdr_crc(di, &gpth))
+	    goto bail;
+
+	gpt_loff = gpth->lba_table;
+	gpt_lsiz = (uint64_t)gpth->part_size * gpth->part_count;
+	gpt_lcnt = (gpt_lsiz + di->bps - 1) / di->bps;
+
+	/*
+	 * disk_read_sectors allows reading of max 255 sectors, so we use
+	 * it as a sanity check base. EFI doesn't specify max (AFAIK).
+	 * Apart from that, some extensive sanity checks.
+	 */
+	if (!gpt_loff || !gpt_lsiz || gpt_lcnt > 255u ||
+		gpth->lba_first_usable > gpth->lba_last_usable ||
+		!sane(gpt_loff, gpt_lcnt) ||
+		gpt_loff + gpt_lcnt > gpth->lba_first_usable ||
+		!sane(gpth->lba_last_usable, gpt_lcnt) ||
+		gpth->lba_last_usable + gpt_lcnt >= gpth->lba_alt ||
+		gpth->lba_alt >= di->lbacnt ||
+		gpth->part_size < sizeof(struct disk_gpt_part_entry)) {
+	    error("Invalid GPT header's values.\n");
+	    goto bail;
+	}
+	if (!(gptl = disk_read_sectors(di, gpt_loff, (uint8_t)gpt_lcnt))) {
+	    error("Couldn't read GPT partition list.\n");
+	    goto bail;
+	}
+	/* Check array checksum(s). */
+	if (check_crc(gpth->table_chksum, (const uint8_t *)gptl, (unsigned int)gpt_lsiz)) {
+	    error("WARNING: GPT partition list checksum invalid, trying backup.\n");
+	    free(gptl);
+	    /* secondary array directly precedes secondary header */
+	    if (!(gptl = disk_read_sectors(di, gpth->lba_alt - gpt_lcnt, (uint8_t)gpt_lcnt))) {
+		error("Couldn't read backup GPT partition list.\n");
+		goto bail;
+	    }
+	    if (check_crc(gpth->table_chksum, (const uint8_t *)gptl, (unsigned int)gpt_lsiz)) {
+		error("Backup GPT partition list checksum invalid.\n");
+		goto bail;
+	    }
+	}
+	/* allocate iterator and exit */
+	iter = pi_new(typegpt, di, stepall, gpth, gptl);
+    } else {
+	/* looks like MBR */
+	iter = pi_new(typedos, di, stepall, mbr);
+    }
+
+    setraw = 0;
+bail:
+    if (setraw) {
+	error("WARNING: treating disk as raw.\n");
+	iter = pi_new(typeraw, di, stepall);
+    }
+    free(mbr);
+    free(gpth);
+    free(gptl);
+
+    return iter;
+}
+
+/* vim: set ts=8 sts=4 sw=4 noet: */
diff --git a/com32/chain/partiter.h b/com32/chain/partiter.h
new file mode 100644
index 0000000..7deeb53
--- /dev/null
+++ b/com32/chain/partiter.h
@@ -0,0 +1,107 @@
+/* ----------------------------------------------------------------------- *
+ *
+ *   Copyright 2003-2010 H. Peter Anvin - All Rights Reserved
+ *   Copyright 2010 Michal Soltys
+ *   Copyright 2010 Shao Miller
+ *
+ *   Permission is hereby granted, free of charge, to any person
+ *   obtaining a copy of this software and associated documentation
+ *   files (the "Software"), to deal in the Software without
+ *   restriction, including without limitation the rights to use,
+ *   copy, modify, merge, publish, distribute, sublicense, and/or
+ *   sell copies of the Software, and to permit persons to whom
+ *   the Software is furnished to do so, subject to the following
+ *   conditions:
+ *
+ *   The above copyright notice and this permission notice shall
+ *   be included in all copies or substantial portions of the Software.
+ *
+ *   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ *   EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ *   OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ *   NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ *   HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ *   WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ *   FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ *   OTHER DEALINGS IN THE SOFTWARE.
+ *
+ * ----------------------------------------------------------------------- */
+
+/*
+ * partiter.h
+ *
+ * Provides disk / partition iteration.
+ */
+
+#ifndef _COM32_CHAIN_PARTITER_H
+#define _COM32_CHAIN_PARTITER_H
+
+#include <stdint.h>
+#include <syslinux/disk.h>
+
+#define PI_ERRLOAD 3
+#define PI_INSANE 2
+#define PI_DONE 1
+#define PI_OK 0
+
+struct itertype;
+struct part_iter;
+
+struct itertype {
+	int (*ctor)(struct part_iter *, va_list *);
+	void (*dtor)(struct part_iter *);
+	struct part_iter *(*next) (struct part_iter *);
+};
+
+#define PI_GPTLABSIZE ((int)sizeof(((struct disk_gpt_part_entry *)0)->name))
+
+struct part_iter {
+    const struct itertype *type;
+    char *data;
+    char *record;
+    uint64_t start_lba;
+    uint64_t length;
+    int index;
+    int rawindex;
+    struct disk_info di;
+    int stepall;
+    int status;
+    /* internal */
+    int index0;
+    union _sub {
+	struct _dos {
+	    uint32_t disk_sig;
+	    uint32_t nebr_lba;
+	    uint32_t cebr_lba;
+	    /* internal */
+	    uint32_t ebr_start;
+	    uint32_t ebr_size;
+	    uint32_t bebr_start;
+	    uint32_t bebr_size;
+	    int bebr_index0;
+	    int skipcnt;
+	} dos;
+	struct _gpt {
+	    struct guid disk_guid;
+	    struct guid part_guid;
+	    char part_label[PI_GPTLABSIZE/2+1];
+	    int pe_count;
+	    int pe_size;
+	    uint64_t ufirst;
+	    uint64_t ulast;
+	} gpt;
+    } sub;
+};
+
+extern const struct itertype * const typedos;
+extern const struct itertype * const typegpt;
+extern const struct itertype * const typeraw;
+
+struct part_iter *pi_begin(const struct disk_info *, int stepall);
+struct part_iter *pi_new(const struct itertype *, ...);
+void pi_del(struct part_iter **);
+int pi_next(struct part_iter **);
+
+#endif
+
+/* vim: set ts=8 sts=4 sw=4 noet: */
diff --git a/com32/chain/utility.c b/com32/chain/utility.c
new file mode 100644
index 0000000..fb59551
--- /dev/null
+++ b/com32/chain/utility.c
@@ -0,0 +1,214 @@
+#include <com32.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <errno.h>
+#include <unistd.h>
+#include <string.h>
+#include <syslinux/disk.h>
+#include "utility.h"
+
+static const char *bpbtypes[] = {
+    [0] =  "unknown",
+    [1] =  "2.0",
+    [2] =  "3.0",
+    [3] =  "3.2",
+    [4] =  "3.4",
+    [5] =  "4.0",
+    [6] =  "8.0 (NT+)",
+    [7] =  "7.0",
+};
+
+void error(const char *msg)
+{
+    fputs(msg, stderr);
+}
+
+int guid_is0(const struct guid *guid)
+{
+    return !*(const uint64_t *)guid && !*((const uint64_t *)guid + 1);
+}
+
+void wait_key(void)
+{
+    int cnt;
+    char junk;
+
+    /* drain */
+    do {
+	errno = 0;
+	cnt = read(0, &junk, 1);
+    } while (cnt > 0 || (cnt < 0 && errno == EAGAIN));
+
+    /* wait */
+    do {
+	errno = 0;
+	cnt = read(0, &junk, 1);
+    } while (!cnt || (cnt < 0 && errno == EAGAIN));
+}
+
+void lba2chs(disk_chs *dst, const struct disk_info *di, uint64_t lba, uint32_t mode)
+{
+    uint32_t c, h, s, t;
+    uint32_t cs, hs, ss;
+
+    /*
+     * Not much reason here, but if we have no valid CHS geometry, we assume
+     * "typical" ones to have something to return.
+     */
+    if (di->cbios) {
+	cs = di->cyl;
+	hs = di->head;
+	ss = di->spt;
+	if (mode == l2c_cadd && cs < 1024 && di->lbacnt > cs*hs*ss)
+	    cs++;
+	else if (mode == l2c_cmax)
+	    cs = 1024;
+    } else {
+	if (di->disk & 0x80) {
+	    cs = 1024;
+	    hs = 255;
+	    ss = 63;
+	} else {
+	    cs = 80;
+	    hs = 2;
+	    ss = 18;
+	}
+    }
+
+    if (lba >= cs*hs*ss) {
+	s = ss;
+	h = hs - 1;
+	c = cs - 1;
+    } else {
+	s = ((uint32_t)lba % ss) + 1;
+	t = (uint32_t)lba / ss;
+	h = t % hs;
+	c = t / hs;
+    }
+
+    (*dst)[0] = h;
+    (*dst)[1] = s | ((c & 0x300) >> 2);
+    (*dst)[2] = c;
+}
+
+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;
+}
+
+/* drive offset detection */
+int drvoff_detect(int type, unsigned int *off)
+{
+    if (bpbV40 <= type && type <= bpbVNT) {
+	*off = 0x24;
+    } else if (type == bpbV70) {
+	*off = 0x40;
+    } else
+	return 0;
+
+    return -1;
+}
+
+/*
+ * heuristics could certainly be improved
+ */
+int bpb_detect(const uint8_t *sec, const char *tag)
+{
+    int a, b, c, jmp = -1, rev = 0;
+
+    /* media descriptor check */
+    if ((sec[0x15] & 0xF0) != 0xF0)
+	goto out;
+
+    if (sec[0] == 0xEB)	/* jump short */
+	jmp = 2 + *(int8_t *)(sec + 1);
+    else if (sec[0] == 0xE9) /* jump near */
+	jmp = 3 + *(int16_t *)(sec + 1);
+
+    if (jmp < 0)    /* no boot code at all ? */
+	goto nocode;
+
+    /* sanity */
+    if (jmp < 0x18 || jmp > 0x1F0)
+	goto out;
+
+    /* detect by jump */
+    if (jmp >= 0x18 && jmp < 0x1E)
+	rev = bpbV20;
+    else if (jmp >= 0x1E && jmp < 0x20)
+	rev = bpbV30;
+    else if (jmp >= 0x20 && jmp < 0x24)
+	rev = bpbV32;
+    else if (jmp >= 0x24 && jmp < 0x46)
+	rev = bpbV34;
+
+    /* TODO: some better V2 - V3.4 checks ? */
+
+    if (rev)
+	goto out;
+    /*
+     * BPB info:
+     * 2.0 ==       0x0B - 0x17
+     * 3.0 == 2.0 + 0x18 - 0x1D
+     * 3.2 == 3.0 + 0x1E - 0x1F
+     * 3.4 ==!2.0 + 0x18 - 0x23
+     * 4.0 == 3.4 + 0x24 - 0x45
+     *  NT ==~3.4 + 0x24 - 0x53
+     * 7.0 == 3.4 + 0x24 - 0x59
+     */
+
+nocode:
+    a = memcmp(sec + 0x03, "NTFS", 4);
+    b = memcmp(sec + 0x36, "FAT", 3);
+    c = memcmp(sec + 0x52, "FAT", 3);	/* ext. DOS 7+ bs */
+
+    if ((sec[0x26] & 0xFE) == 0x28 && !b) {
+	rev = bpbV40;
+    } else if (sec[0x26] == 0x80 && !a) {
+	rev = bpbVNT;
+    } else if ((sec[0x42] & 0xFE) == 0x28 && !c) {
+	rev = bpbV70;
+    }
+
+out:
+    printf("BPB detection (%s): %s\n", tag, bpbtypes[rev]);
+    return rev;
+}
+
+/* vim: set ts=8 sts=4 sw=4 noet: */
diff --git a/com32/chain/utility.h b/com32/chain/utility.h
new file mode 100644
index 0000000..8a08be7
--- /dev/null
+++ b/com32/chain/utility.h
@@ -0,0 +1,30 @@
+#ifndef _COM32_CHAIN_UTILITY_H
+#define _COM32_CHAIN_UTILITY_H
+
+#include <stdint.h>
+#include <syslinux/disk.h>
+
+#define bpbUNK	0
+#define bpbV20	1
+#define bpbV30	2
+#define bpbV32	3
+#define bpbV34	4
+#define bpbV40	5
+#define bpbVNT	6
+#define bpbV70	7
+
+#define l2c_cnul 0
+#define l2c_cadd 1
+#define l2c_cmax 2
+
+void error(const char *msg);
+int guid_is0(const struct guid *guid);
+void wait_key(void);
+void lba2chs(disk_chs *dst, const struct disk_info *di, uint64_t lba, uint32_t mode);
+uint32_t get_file_lba(const char *filename);
+int drvoff_detect(int type, unsigned int *off);
+int bpb_detect(const uint8_t *bpb, const char *tag);
+
+#endif
+
+/* vim: set ts=8 sts=4 sw=4 noet: */
diff --git a/com32/include/syslinux/disk.h b/com32/include/syslinux/disk.h
new file mode 100644
index 0000000..f96ca68
--- /dev/null
+++ b/com32/include/syslinux/disk.h
@@ -0,0 +1,180 @@
+/* ----------------------------------------------------------------------- *
+ *
+ *   Copyright 2003-2009 H. Peter Anvin - All Rights Reserved
+ *   Copyright 2009-2010 Intel Corporation; author: H. Peter Anvin
+ *   Copyright (C) 2010 Shao Miller
+ *
+ *   Permission is hereby granted, free of charge, to any person
+ *   obtaining a copy of this software and associated documentation
+ *   files (the "Software"), to deal in the Software without
+ *   restriction, including without limitation the rights to use,
+ *   copy, modify, merge, publish, distribute, sublicense, and/or
+ *   sell copies of the Software, and to permit persons to whom
+ *   the Software is furnished to do so, subject to the following
+ *   conditions:
+ *
+ *   The above copyright notice and this permission notice shall
+ *   be included in all copies or substantial portions of the Software.
+ *
+ *   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ *   EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ *   OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ *   NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ *   HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ *   WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ *   FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ *   OTHER DEALINGS IN THE SOFTWARE.
+ *
+ * ----------------------------------------------------------------------- */
+
+/**
+ * @file syslinux/disk.h
+ *
+ * Deal with disks and partitions
+ */
+
+#ifndef _SYSLINUX_DISK_H
+#define _SYSLINUX_DISK_H
+
+#include <com32.h>
+#include <stdint.h>
+
+#define SECTOR 512u		/* bytes/sector */
+
+struct disk_info {
+    int disk;
+    int ebios;			/* EBIOS supported on this disk */
+    int cbios;			/* CHS geometry is valid */
+    uint32_t bps;		/* bytes per sector */
+    uint64_t lbacnt;		/* total amount of sectors */
+    uint32_t cyl;
+    uint32_t head;
+    uint32_t spt;
+};
+
+struct disk_ebios_dapa {
+    uint16_t len;
+    uint16_t count;
+    uint16_t off;
+    uint16_t seg;
+    uint64_t lba;
+} __attribute__ ((packed));
+
+struct disk_ebios_eparam {
+    uint16_t len;
+    uint16_t info;
+    uint32_t cyl;
+    uint32_t head;
+    uint32_t spt;
+    uint64_t lbacnt;
+    uint16_t bps;	/* bytes per sector */
+    uint32_t edd;
+    uint16_t dpi_sig;
+    uint8_t  dpi_len;
+    char     reserved1[3];
+    char     hostbus[4];
+    char     if_type[8];
+    char     if_path[8];
+    char     dev_path[8];
+    char     reserved2;
+    uint8_t  checksum;
+} __attribute__ ((packed));
+
+/**
+ * CHS (cylinder, head, sector) value extraction macros.
+ * Taken from WinVBlock.  None 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 disk_chs[3];
+
+/* A DOS partition table entry */
+struct disk_dos_part_entry {
+    uint8_t active_flag;	/* 0x80 if "active" */
+    disk_chs start;
+    uint8_t ostype;
+    disk_chs end;
+    uint32_t start_lba;
+    uint32_t length;
+} __attribute__ ((packed));
+
+/* A DOS MBR */
+struct disk_dos_mbr {
+    char code[440];
+    uint32_t disk_sig;
+    char pad[2];
+    struct disk_dos_part_entry table[4];
+    uint16_t sig;
+} __attribute__ ((packed));
+#define disk_mbr_sig_magic 0xAA55
+
+/**
+ * A GPT disk/partition GUID
+ *
+ * Be careful with endianness, you must adjust it yourself
+ * iff you are directly using the fourth data chunk.
+ * There might be a better header for this...
+ */
+struct guid {
+    uint32_t data1;
+    uint16_t data2;
+    uint16_t data3;
+    uint64_t data4;
+} __attribute__ ((packed));
+
+/* A GPT partition */
+struct disk_gpt_part_entry {
+    struct guid type;
+    struct guid uid;
+    uint64_t lba_first;
+    uint64_t lba_last;
+    uint64_t attribs;
+    char name[72];
+} __attribute__ ((packed));
+
+/* A GPT header */
+struct disk_gpt_header {
+    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 disk_gpt_sig_magic[] = "EFI PART";
+
+extern int disk_int13_retry(const com32sys_t * inreg, com32sys_t * outreg);
+extern int disk_get_params(int disk, struct disk_info *const diskinfo);
+extern void *disk_read_sectors(const struct disk_info *const diskinfo,
+			       uint64_t lba, uint8_t count);
+extern int disk_write_sectors(const struct disk_info *const diskinfo,
+			      uint64_t lba, const void *data, uint8_t count);
+extern int disk_write_verify_sectors(const struct disk_info *const diskinfo,
+				     uint64_t lba, const void *buf, uint8_t count);
+extern void disk_dos_part_dump(const struct disk_dos_part_entry *const part);
+extern void guid_to_str(char *buf, const struct guid *const id);
+extern int str_to_guid(const char *buf, struct guid *const id);
+extern void disk_gpt_part_dump(const struct disk_gpt_part_entry *const
+			       gpt_part);
+extern void disk_gpt_header_dump(const struct disk_gpt_header *const gpt);
+
+#endif /* _SYSLINUX_DISK_H */
diff --git a/com32/lib/Makefile b/com32/lib/Makefile
index 3f67e42..62a322a 100644
--- a/com32/lib/Makefile
+++ b/com32/lib/Makefile
@@ -127,7 +127,9 @@
 	syslinux/setadv.o						\
 	\
 	syslinux/video/fontquery.o syslinux/video/forcetext.o		\
-	syslinux/video/reportmode.o
+	syslinux/video/reportmode.o					\
+	\
+	syslinux/disk.o
 
 # These are the objects which are also imported into the core
 LIBCOREOBJS = 	\
diff --git a/com32/lib/syslinux/disk.c b/com32/lib/syslinux/disk.c
new file mode 100644
index 0000000..d6409af
--- /dev/null
+++ b/com32/lib/syslinux/disk.c
@@ -0,0 +1,534 @@
+/* ----------------------------------------------------------------------- *
+ *
+ *   Copyright 2003-2009 H. Peter Anvin - All Rights Reserved
+ *   Copyright 2009-2010 Intel Corporation; author: H. Peter Anvin
+ *   Copyright (C) 2010 Shao Miller
+ *
+ *   Permission is hereby granted, free of charge, to any person
+ *   obtaining a copy of this software and associated documentation
+ *   files (the "Software"), to deal in the Software without
+ *   restriction, including without limitation the rights to use,
+ *   copy, modify, merge, publish, distribute, sublicense, and/or
+ *   sell copies of the Software, and to permit persons to whom
+ *   the Software is furnished to do so, subject to the following
+ *   conditions:
+ *
+ *   The above copyright notice and this permission notice shall
+ *   be included in all copies or substantial portions of the Software.
+ *
+ *   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ *   EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ *   OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ *   NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ *   HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ *   WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ *   FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ *   OTHER DEALINGS IN THE SOFTWARE.
+ *
+ * ----------------------------------------------------------------------- */
+
+/**
+ * @file disk.c
+ *
+ * Deal with disks and partitions
+ */
+
+#include <dprintf.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslinux/disk.h>
+
+/**
+ * Call int 13h, but with retry on failure.  Especially floppies need this.
+ *
+ * @v inreg			CPU register settings upon INT call
+ * @v outreg			CPU register settings returned by INT call
+ * @ret (int)			0 upon success, -1 upon failure
+ */
+int disk_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.
+ *
+ * @v disk			The INT 0x13 disk drive number to process
+ * @v diskinfo			The structure to save the queried params to
+ * @ret (int)			0 upon success, -1 upon failure
+ */
+int disk_get_params(int disk, struct disk_info *const diskinfo)
+{
+    static com32sys_t inreg, outreg;
+    struct disk_ebios_eparam *eparam = __com32.cs_bounce;
+
+    memset(diskinfo, 0, sizeof *diskinfo);
+    diskinfo->disk = disk;
+    diskinfo->bps = SECTOR;
+
+    /* Get EBIOS support */
+    memset(&inreg, 0, sizeof inreg);
+    inreg.eax.b[1] = 0x41;
+    inreg.ebx.w[0] = 0x55aa;
+    inreg.edx.b[0] = disk;
+    inreg.eflags.b[0] = 0x3;	/* CF set */
+
+    __intcall(0x13, &inreg, &outreg);
+
+    if (!(outreg.eflags.l & EFLAGS_CF) &&
+	outreg.ebx.w[0] == 0xaa55 && (outreg.ecx.b[0] & 1)) {
+	diskinfo->ebios = 1;
+    }
+
+    /* Get extended disk parameters if ebios == 1 */
+    if (diskinfo->ebios) {
+	memset(&inreg, 0, sizeof inreg);
+	inreg.eax.b[1] = 0x48;
+	inreg.edx.b[0] = disk;
+	inreg.esi.w[0] = OFFS(eparam);
+	inreg.ds = SEG(eparam);
+
+	memset(eparam, 0, sizeof *eparam);
+	eparam->len = sizeof *eparam;
+
+	__intcall(0x13, &inreg, &outreg);
+
+	if (!(outreg.eflags.l & EFLAGS_CF)) {
+	    diskinfo->lbacnt = eparam->lbacnt;
+	    if (eparam->bps)
+		diskinfo->bps = eparam->bps;
+	    /*
+	     * don't think about using geometry data returned by
+	     * 48h, as it can differ from 08h a lot ...
+	     */
+	}
+    }
+    /*
+     * Get disk parameters the old way - really only useful for hard
+     * disks, but if we have a partitioned floppy it's actually our best
+     * chance...
+     */
+    memset(&inreg, 0, sizeof inreg);
+    inreg.eax.b[1] = 0x08;
+    inreg.edx.b[0] = disk;
+
+    __intcall(0x13, &inreg, &outreg);
+
+    if (outreg.eflags.l & EFLAGS_CF)
+	return diskinfo->ebios ? 0 : -1;
+
+    diskinfo->spt = 0x3f & outreg.ecx.b[0];
+    diskinfo->head = 1 + outreg.edx.b[1];
+    diskinfo->cyl = 1 + (outreg.ecx.b[1] | ((outreg.ecx.b[0] & 0xc0u) << 2));
+
+    if (diskinfo->spt)
+	diskinfo->cbios = 1;	/* Valid geometry */
+    else {
+	diskinfo->head = 1;
+	diskinfo->spt = 1;
+	diskinfo->cyl = 1;
+    }
+
+    if (!diskinfo->lbacnt)
+	diskinfo->lbacnt = diskinfo->cyl * diskinfo->head * diskinfo->spt;
+
+    return 0;
+}
+
+/**
+ * Get disk block(s) and return a malloc'd buffer.
+ *
+ * @v diskinfo			The disk drive to read from
+ * @v lba			The logical block address to begin reading at
+ * @v count			The number of sectors to read
+ * @ret data			An allocated buffer with the read data
+ *
+ * Uses the disk number and information from diskinfo.  Read count sectors
+ * from drive, starting at lba.  Return a new buffer, or NULL upon failure.
+ */
+void *disk_read_sectors(const struct disk_info *const diskinfo, uint64_t lba,
+			uint8_t count)
+{
+    com32sys_t inreg;
+    struct disk_ebios_dapa *dapa = __com32.cs_bounce;
+    void *buf = (char *)__com32.cs_bounce + diskinfo->bps;
+    void *data;
+    uint32_t maxcnt;
+
+    maxcnt = (__com32.cs_bounce_size - diskinfo->bps) / diskinfo->bps;
+    if (!count || count > maxcnt || lba + count > diskinfo->lbacnt)
+	return NULL;
+
+    memset(&inreg, 0, sizeof inreg);
+
+    if (diskinfo->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] = diskinfo->disk;
+	inreg.eax.b[1] = 0x42;	/* Extended read */
+    } else {
+	unsigned int c, h, s, t;
+	/*
+	 * if we passed lba + count check and we get here, that means that
+	 * lbacnt was calculated from chs geometry (or faked from 1/1/1), thus
+	 * 32bits are perfectly enough and lbacnt corresponds to cylinder
+	 * boundary
+	 */
+	s = lba % diskinfo->spt;
+	t = lba / diskinfo->spt;
+	h = t % diskinfo->head;
+	c = t / diskinfo->head;
+
+	inreg.eax.b[0] = count;
+	inreg.eax.b[1] = 0x02;	/* Read */
+	inreg.ecx.b[1] = c;
+	inreg.ecx.b[0] = ((c & 0x300) >> 2) | (s+1);
+	inreg.edx.b[1] = h;
+	inreg.edx.b[0] = diskinfo->disk;
+	inreg.ebx.w[0] = OFFS(buf);
+	inreg.es = SEG(buf);
+    }
+
+    if (disk_int13_retry(&inreg, NULL))
+	return NULL;
+
+    data = malloc(count * diskinfo->bps);
+    if (data)
+	memcpy(data, buf, count * diskinfo->bps);
+    return data;
+}
+
+/**
+ * Write disk block(s).
+ *
+ * @v diskinfo			The disk drive to write to
+ * @v lba			The logical block address to begin writing at
+ * @v data			The data to write
+ * @v count			The number of sectors to write
+ * @ret (int)			0 upon success, -1 upon failure
+ *
+ * Uses the disk number and information from diskinfo.
+ * Write sector(s) to a disk drive, starting at lba.
+ */
+int disk_write_sectors(const struct disk_info *const diskinfo, uint64_t lba,
+		       const void *data, uint8_t count)
+{
+    com32sys_t inreg;
+    struct disk_ebios_dapa *dapa = __com32.cs_bounce;
+    void *buf = (char *)__com32.cs_bounce + diskinfo->bps;
+    uint32_t maxcnt;
+
+    maxcnt = (__com32.cs_bounce_size - diskinfo->bps) / diskinfo->bps;
+    if (!count || count > maxcnt || lba + count > diskinfo->lbacnt)
+	return -1;
+
+    memcpy(buf, data, count * diskinfo->bps);
+    memset(&inreg, 0, sizeof inreg);
+
+    if (diskinfo->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] = diskinfo->disk;
+	inreg.eax.b[1] = 0x43;	/* Extended write */
+    } else {
+	unsigned int c, h, s, t;
+	/*
+	 * if we passed lba + count check and we get here, that means that
+	 * lbacnt was calculated from chs geometry (or faked from 1/1/1), thus
+	 * 32bits are perfectly enough and lbacnt corresponds to cylinder
+	 * boundary
+	 */
+	s = lba % diskinfo->spt;
+	t = lba / diskinfo->spt;
+	h = t % diskinfo->head;
+	c = t / diskinfo->head;
+
+	inreg.eax.b[0] = count;
+	inreg.eax.b[1] = 0x03;	/* Write */
+	inreg.ecx.b[1] = c;
+	inreg.ecx.b[0] = ((c & 0x300) >> 2) | (s+1);
+	inreg.edx.b[1] = h;
+	inreg.edx.b[0] = diskinfo->disk;
+	inreg.ebx.w[0] = OFFS(buf);
+	inreg.es = SEG(buf);
+    }
+
+    if (disk_int13_retry(&inreg, NULL))
+	return -1;
+
+    return 0;			/* ok */
+}
+
+/**
+ * Write disk blocks and verify they were written.
+ *
+ * @v diskinfo			The disk drive to write to
+ * @v lba			The logical block address to begin writing at
+ * @v buf			The data to write
+ * @v count			The number of sectors to write
+ * @ret rv			0 upon success, -1 upon failure
+ *
+ * Uses the disk number and information from diskinfo.
+ * Writes sectors to a disk drive starting at lba, then reads them back
+ * to verify they were written correctly.
+ */
+int disk_write_verify_sectors(const struct disk_info *const diskinfo,
+			      uint64_t lba, const void *buf, uint8_t count)
+{
+    char *rb;
+    int rv;
+
+    rv = disk_write_sectors(diskinfo, lba, buf, count);
+    if (rv)
+	return rv;		/* Write failure */
+    rb = disk_read_sectors(diskinfo, lba, count);
+    if (!rb)
+	return -1;		/* Readback failure */
+    rv = memcmp(buf, rb, count * diskinfo->bps);
+    free(rb);
+    return rv ? -1 : 0;
+}
+
+/**
+ * Dump info about a DOS partition entry
+ *
+ * @v part			The 16-byte partition entry to examine
+ */
+void disk_dos_part_dump(const struct disk_dos_part_entry *const 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);
+}
+
+/* Trivial error message output */
+static inline void error(const char *msg)
+{
+    fputs(msg, stderr);
+}
+
+/**
+ * This walk-map effectively reverses the little-endian
+ * portions of a GPT disk/partition GUID for a string representation.
+ * There might be a better header for this...
+ */
+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
+};
+
+/**
+ * Fill a buffer with a textual GUID representation.
+ *
+ * @v buf			Points to a minimum array of 37 chars
+ * @v id			The GUID to represent as text
+ *
+ * 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
+ */
+void guid_to_str(char *buf, const struct guid *const 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;
+}
+
+/**
+ * Create a GUID structure from a textual GUID representation.
+ *
+ * @v buf			Points to a GUID string to parse
+ * @v id			Points to a GUID to be populated
+ * @ret (int)			Returns 0 upon success, -1 upon failure
+ *
+ * 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
+ */
+int str_to_guid(const char *buf, struct guid *const 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;
+}
+
+/**
+ * Display GPT partition details.
+ *
+ * @v gpt_part			The GPT partition entry to display
+ */
+void disk_gpt_part_dump(const struct disk_gpt_part_entry *const gpt_part)
+{
+    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);
+}
+
+/**
+ * Display GPT header details.
+ *
+ * @v gpt			The GPT header to display
+ */
+void disk_gpt_header_dump(const struct disk_gpt_header *const 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);
+}
diff --git a/com32/modules/Makefile b/com32/modules/Makefile
index e9ce1d1..1b2854f 100644
--- a/com32/modules/Makefile
+++ b/com32/modules/Makefile
@@ -19,7 +19,7 @@
 MAKEDIR = $(topdir)/mk
 include $(MAKEDIR)/com32.mk
 
-MODULES	  = chain.c32 config.c32 ethersel.c32 dmitest.c32 cpuidtest.c32 \
+MODULES	  = config.c32 ethersel.c32 dmitest.c32 cpuidtest.c32 \
 	    disk.c32 pcitest.c32 elf.c32 linux.c32 reboot.c32 pmload.c32 \
 	    meminfo.c32 sdi.c32 sanboot.c32 ifcpu64.c32 vesainfo.c32 \
 	    kbdmap.c32 cmd.c32 vpdtest.c32 host.c32 ls.c32 gpxecmd.c32 \
diff --git a/com32/modules/chain.c b/com32/modules/chain.c
deleted file mode 100644
index 2ed0f14..0000000
--- a/com32/modules/chain.c
+++ /dev/null
@@ -1,1932 +0,0 @@
-/* ----------------------------------------------------------------------- *
- *
- *   Copyright 2003-2009 H. Peter Anvin - All Rights Reserved
- *   Copyright 2009-2011 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>[:<offset>][{+@}entry]
- *	loads at <segment>:<offset> and jumps to <seg>:<entry> instead
- *	of the default 0000:7C00.  <offset> and <entry> default to 0 and +0
- *	repectively.  If <entry> start with + (rather than @) then the
- *	entry point address is added to the offset.
- *
- * 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.
- *
- * drmk=<loader>
- *	Similar to msdos=<loader> but prepares the special options
- *	for the Dell Real Mode Kernel.
- *
- * 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).
- *
- * freeldr=<loader>
- *  loads ReactOS' FreeLdr.sys to 0:8000 and jumps to the PE entry-point
- */
-
-#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;
-    uint16_t offs;
-    uint16_t entry;
-    bool isolinux;
-    bool cmldr;
-    bool grub;
-    bool grldr;
-    const char *grubcfg;
-    bool swap;
-    bool hide;
-    bool sethidden;
-    bool drmk;
-} 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 = h = c = 0;
-	} else {
-	    s = lba % disk_info.sect;
-	    t = lba / disk_info.sect;	/* Track = head*cyl */
-	    h = t % disk_info.head;
-	    c = t / disk_info.head;
-	}
-
-	if (s >= 63 || h >= 256 || c >= 1024)
-	    return NULL;
-
-	inreg.eax.b[0] = count;
-	inreg.eax.b[1] = 0x02;	/* Read */
-	inreg.ecx.b[1] = c;
-	inreg.ecx.b[0] = ((c & 0x300) >> 2) | (s + 1);
-	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 = h = c = 0;
-	} else {
-	    s = lba % disk_info.sect;
-	    t = lba / disk_info.sect;	/* Track = head*cyl */
-	    h = t % disk_info.head;
-	    c = t / disk_info.head;
-	}
-
-	if (s >= 63 || h >= 256 || c >= 1024)
-	    return -1;
-
-	inreg.eax.w[0] = 0x0301;	/* Write one sector */
-	inreg.ecx.b[1] = c;
-	inreg.ecx.b[0] = ((c & 0x300) >> 2) | (s + 1);
-	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 %"PRIu64"\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 %"PRIu64"\n",
-	    part->private.mbr_index, part->lba_data);
-    part->index = part->private.mbr_index + 1;
-    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 = part->private.gpt.index + 1;
-    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\
-         freeldr=<loader>   Load ReactOS' FREELDR.SYS\n\
-         msdos=<loader>     Load MS-DOS IO.SYS\n\
-         pcdos=<loader>     Load PC-DOS IBMBIO.COM\n\
-         drmk=<loader>      Load DRMK DELLBIO.BIN\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=<seg>          Jump to <seg>:0000, instead of 0000:7C00\n\
-         seg=<seg>[:<offs>][{+@}<entry>] also specified offset and entrypoint\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(&regs, 0, sizeof regs);
-
-    opt.seg   = 0;
-    opt.offs  = 0x7c00;
-    opt.entry = 0x7c00;
-
-    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 v;
-	    bool add_entry = true;
-	    char *ep, *p = argv[i] + 4;
-	    
-	    v = strtoul(p, &ep, 0);
-	    if (ep == p || v < 0x50 || v > 0x9f000) {
-		error("seg: Invalid segment\n");
-		goto bail;
-	    }
-	    opt.seg = v;
-	    opt.offs = opt.entry = 0;
-	    if (*ep == ':') {
-		p = ep+1;
-		v = strtoul(p, &ep, 0);
-		if (ep == p) {
-		    error("seg: Invalid offset\n");
-		    goto bail;
-		}
-		opt.offs = v;
-	    }
-	    if (*ep == '@' || *ep == '+') {
-		add_entry = (*ep == '+');
-		p = ep+1;
-		v = strtoul(p, &ep, 0);
-		if (ep == p) {
-		    error("seg: Invalid entry point\n");
-		    goto bail;
-		}
-		opt.entry = v;
-	    }
-	    if (add_entry)
-		opt.entry += opt.offs;
-	} 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.offs = opt.entry = 0;
-	    opt.loadfile = argv[i] + 6;
-	    opt.sethidden = true;
-	} else if (!strncmp(argv[i], "cmldr=", 6)) {
-	    opt.seg = 0x2000;	/* CMLDR wants this address */
-	    opt.offs = opt.entry = 0;
-	    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.offs = opt.entry = 0;
-	    opt.loadfile = argv[i] + 8;
-	    opt.sethidden = true;
-	} else if (!strncmp(argv[i], "freeldr=", 8)) {
-	    opt.loadfile = argv[i] + 8;
-	    opt.sethidden = true;
-	    /* The FreeLdr PE wants to be at 0:8000 */
-	    opt.seg = 0;
-	    opt.offs = 0x8000;
-	    /* TODO: Properly parse the PE.  Right now, this is hard-coded */
-	    opt.entry = 0x8100;
-	} else if (!strncmp(argv[i], "msdos=", 6) ||
-		   !strncmp(argv[i], "pcdos=", 6)) {
-	    opt.seg = 0x70;	/* MS-DOS 2.0+ wants this address */
-	    opt.offs = opt.entry = 0;
-	    opt.loadfile = argv[i] + 6;
-	    opt.sethidden = true;
-	} else if (!strncmp(argv[i], "drmk=", 5)) {
-	    opt.seg = 0x70;	/* DRMK wants this address */
-	    opt.offs = opt.entry = 0;
-	    opt.loadfile = argv[i] + 5;
-	    opt.sethidden = true;
-	    opt.drmk = true;
-	} else if (!strncmp(argv[i], "grub=", 5)) {
-	    opt.seg = 0x800;	/* stage2 wants this address */
-	    opt.offs = opt.entry = 0;
-	    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], "uuid:", 5)
-		   || !strncmp(argv[i], "uuid=", 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;
-    }
-
-    /*
-     * Set up initial register values
-     */
-    regs.es = regs.cs = regs.ss = regs.ds = regs.fs = regs.gs = opt.seg;
-    regs.ip = opt.entry;
-
-    /* 
-     * For the special case of the standard 0:7C00 entry point, put
-     * the stack below; otherwise leave the stack pointer at the end
-     * of the segment (sp = 0).
-     */
-    if (opt.seg == 0 && opt.offs == 0x7c00)
-	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) || !strncmp(drivename, "uuid", 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 << 4) + opt.offs;
-
-    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);
-	    }
-	}
-
-	if (opt.drmk) {
-	    /* 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.
-	     */
-	    int tsize = (data[ndata].size + 19) & 0xfffffff0;
-	    const union syslinux_derivative_info *sdi;
-
-	    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 %"PRIu64"\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");
-	    }
-	    regs.ss = regs.fs = regs.gs = 0;	/* Used before initialized */
-	    if (!realloc(data[ndata].data, tsize)) {
-		error("Failed to realloc for DRMK\n");
-		goto bail;	/* We'll never make it */
-	    }
-	    data[ndata].size = tsize;
-	    /* ds:bp is assumed by DRMK to be the boot sector */
-	    /* offset 28 is the FAT HiddenSectors value */
-	    regs.ds = (tsize >> 4) + (opt.seg - 2);
-	    /* "Patch" into tail of the new space */
-	    *(int *)(data[ndata].data + tsize - 4) = (int)(fs_lba & 0xffffffff);
-	}
-
-	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, &regs);
-
-bail:
-    if (cur_part) {
-	free(cur_part->block);
-	free((void *)cur_part->record);
-    }
-    free(cur_part);
-    free(mbr);
-    return 255;
-}
diff --git a/doc/chain.txt b/doc/chain.txt
new file mode 100644
index 0000000..6dd0632
--- /dev/null
+++ b/doc/chain.txt
@@ -0,0 +1,327 @@
+			    chain.c32 documentation
+
+Although syslinux is capable of (very simple) native chainloading (through .bss
+and .bs options - see doc/syslinux.txt), it also features a very roboust and
+rich com32 module designed for such purpose.
+
+Chain module can perform few basic tasks:
+
+- load and jump to a sector
+- load and jump to a file (also loading a sector for other purposes)
+- prepare handover data to use by a file / boot sector
+- fix different options in a file / sector / partition entries
+- perform a "service-only" run
+
+It can chainload data from both GPT and DOS partitions, as well as boot the
+first sector from a raw disk.
+
+In more details, the rough overview of code is as follows:
+
+1.  Parse arguments.
+2.  Find drive and/or partition to boot from.
+3.  Perform partition-level patching - for example hiding, unhiding, fixing chs values, etc.
+4.  Load a file to boot from.
+5.  Load a sector to boot from, if it doesn't conflict with #5.
+6.  Prepare handover area, if it doesn't conflict with #5 & #6.
+7.  Prepare registers.
+8.  Patch loaded file if necessary.
+9.  Patch loaded sector if necessary.
+10. Chainload.
+
+In most basic form, syslinux loads specified boot sector (or mbr, if not
+specified) at 0:0x7c00, prepares handover area as a standard mbr would do, and
+jumps to 0:0x7c00.
+
+A "service-only" run is possible when either:
+
+- 'break' is in effect
+
+or
+
+- 'nofile' and 'nomaps' (or 'nosect') are in effect
+
+This is useful for invocations such as:
+
+chain.c32 hdN M setbpb save break
+chain.c32 hdN fixchs break
+chain.c32 hdN unhideall break
+
+Please see respective options for more details.
+
+
+Module invocation:
+
+chain [drive/partition] [options]
+
+			DRIVE / PARTITION SPECIFICATION
+
+Drive can be specified as 'hd#', 'fd#', 'boot', 'mbr', or 'guid'.
+
+- 'mbr' will select a drive by a signature.
+- 'guid' will select a drive by a guid
+- 'boot' is the drive syslinux was booted from. This is the default value, if
+  nothing else is specified.
+- 'hd#' and 'fd#' are standard ways to specify drive number as seen by bios,
+  starting from 0.
+
+Option 'guid' is shared with partition selection (see below). If you happened
+to have non-unique guids, they are searched in disk0, partitions of disk0,
+disk1 ...  order.
+
+The priority of those options are the same as in the above list.
+
+If you specify the same value more than once, the last value will be used.
+
+'mbr' and 'guid' take extra parameter - you should use ':' or '=' as a
+delimiter.
+
+
+Partition can be specified as '#', 'guid', 'label' or 'fs'.
+
+- 'guid' option will select a partition by a guid (not a type guid !)
+- 'label' will select a partition by a label (searching is done in
+  disk order)
+- 'fs' will select a partition from which syslinux was executed
+- '#' is the standard method. Partitions 1-4 are primary, 5+ logical, 0 = boot
+  MBR (default).
+
+The priority of those options are the same as in the above list.
+
+If you use a number to select a partition it should be specified after a drive
+using space or comma as delimiters (after 'hd#', 'fd#', 'mbr', 'guid' or 'boot').
+
+				    OPTIONS
+	file=<file>
+       *nofile
+
+It's often convenient to load a file directly and transfer control to it,
+instead of the sector from the disk. Note, that the <file> must reside on
+syslinux partition.
+
+If you choose this option without specifying any addresses explicitly (see
+options 'sect=' and 'seg='), the file will cause sector to not be loaded at all
+(as their memory placement would overlap).
+
+	seg=<segment>:<offset>:<ip>
+	*seg=0:0x7c00:0x7c00
+
+This triplet lets you alter the addresses a file will use. It's loaded at
+<segment:offset>, the entry point is at <segment:ip>. When you chainload some
+other bootloader or kernel, it's almost always mandatory.
+
+The defaults, if option is not specified, are 0:0x7c00:0x7c00
+If any of the fields are ommited (e.g. 0x2000::), they default to 0.
+
+	sect=<segment>:<offset>:<ip>
+	nosect
+	*sect=0:0x7c00:0x7c00
+	nosect sets: nomaps
+
+This triplet lets you alter the addresses a sector will use. It's loaded at
+<segment:offset>, the entry point is at <segment:ip>. This option is mostly
+used in tandem with 'file=' and 'seg=' options, as some loaders/kernels will
+expect relocated sector at some particular address (e.g. DRKM).
+
+'nosect' will cause sector to not be loaded at all. In plenty cases, when a file
+is being chainloaded, sector is not necessary.
+
+The defaults if option is not specified, are 0:0x7c00:0x7c00.
+If some of the fields are ommited (e.g. 0x2000::), they default to 0.
+
+	*maps
+	nomaps
+
+In some cases, it's useful to fix BPB values in NTFS/FATxx bootsectors and
+evntually write them back, but otherwise boot sector itself is not necessary to
+continue booting. 'nomaps' allows that - a sector will be loaded, but won't be
+mmapped into real memory. Any overlap tests (vs. handover or file areas) are
+not performed, being meaningless in such case.
+
+	setbpb
+	*nosetbpb
+
+Microsoft side of the world is paritculary sensitive to certain BPB values.
+Depending on the system and chainloading method (sector or file), some or all
+of those fields must match reality - and after e.g. drive clonning or
+when using usb stick in different computers - that is often not the case.
+
+The "reality" means:
+
+"hidden sectors" - valid offset of the partition from the beginning of the disk
+"geometry" - valid disk geometry as reported by BIOS
+"drive" - valid drive number
+
+This option will automatically determine the type of BPB and fix what is possible
+to fix, relatively to detected BPB. If it's impossible to detect BPB, function
+will do nothing.
+
+	filebpb
+	*nofilebpb
+
+Chainloaded file can simply be an image of a sector. In such case, it could be
+useful to also fix its BPB values.
+
+	save
+	*nosave
+
+Fixing BPB values only in memory might not be enough. This option allows
+writing of the corrected sector. You will probably want to use this option
+together with 'setbpb'.
+
+- this option never applies to a loaded file
+- chain module will not save anything to disk by default (besides options such
+  as hide or fixchs - so options related directly to partition entries)
+- writing is only performed, if the values actually changed
+
+	*hand
+	nohand
+
+By default, a handover area is always prepared if possible - meaning it doesn't
+overlap with other areas. It's often not necessary though - usually, a
+chainloaded file or kernel don't care about it anymore, so a user can disable
+it explicitly with this option.
+
+	hptr
+	*nohptr
+
+In case when both file and sector are loaded, ds:si and ds:bp will point to
+sector address before the chainloading. This option lets user force those
+registers to point to handover area. This is useful when both the file and the
+sector are actually a sector's image and the sector is mmapped.
+
+	swap
+	*noswap
+
+This option will install a tiny stub code used to swap drive numbers, if the
+drive we use during chainloading is not fd0 or hd0.
+
+	hide[all]
+	unhide[all]
+	*nohide
+
+In certain situations it's useful to hide partitions - for example to make sure
+DOS gets C:. 'hide' will hide hidable primary partitions, except the one we're
+booting from. Similary, 'hideall' will hide all hidable partitions, except the
+one we're booting from. Hiding is performed only on the selected drive. Options
+starting with 'un' will simply unhide every partition (primary ones or all).
+Writing is only performed, if the os type values actually changed.
+
+	fixchs
+	*nofixchs
+
+If you want to make a drive you're booting from totally compatible with current
+BIOS, you can use this to fix all partitions' CHS numbers. Good to silence e.g.
+FreeDOS complainig about 'logical CHS differs from physical' of sfdisk about
+'found (...) expected (...).  Functionally seems to be mostly cosmetic, as
+Microsoft world - in cases it cares about geometry - generally sticks to values
+written in bootsectors. And the rest of the world generally doesn't care about
+them at all. Writing is only performed, if the values actually got changed.
+
+	keepexe
+	*nokeepexe
+
+If you're booting over a network using pxelinux - this lets you keep UNDI
+stacks in memory (pxelinux only).
+
+	warn
+	*nowarn
+
+This option will wait for a keypress right before continuing the chainloading.
+Useful to see warnings emited by the chain module.
+
+	*nobreak
+	break
+	break sets: nofile nomaps nohand
+
+It is possible to trigger a "service-only" run - The chain module will do
+everything requested as usual, but it will not perform the actual chainloading.
+'break' option disables handover, file loading and sector mapping, as these
+are pointless in such scenario (although file might be reenabled in some future
+version, if writing to actual files becomes possible). Mainly useful for
+options 'fixchs', '[un]hide[all]' and setbpb.
+
+	isolinux=<file>
+	sets: file=<file> nohand nosect isolinux
+
+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=<file>
+	sets: file=<file> seg=0x2000 setbpb nohand
+
+Prepares to load ntldr directly. You might want to add 'save' option to store
+corrected BPB values.
+
+	cmldr=<file>
+	sets: file=<file> seg=0x2000 setbpb nohand cmldr
+
+Prepares to load recovery console directly. In-memory copy of bootsector is
+patched with "cmdcons\0". Remarks the same as in 'ntldr='.
+
+	reactos=<file>
+	sets: file=<file> seg=0:0x8000:0x8100 setbpb nohand
+
+Prepares to load ReactOS's freeldr directly. You might want to add 'save'
+option to store corrected BPB values.
+
+	freedos=<file>
+	sets: file=<file> seg=0x60 sect=0x1FE0 setbpb nohand
+
+Prepares to load freedos kernel directly. You will likely want to add 'save'
+option, as those kernels seem to require proper geometry written back to disk.
+Sector address is chosen based on where freedos' bootsectors relocate themselves,
+although it seems the kernel doesn't rely on it.
+
+You might also want to employ 'hide' option, if you have problems with properly
+assigned C: drive.
+
+	pcdos=<file>
+	msdos=<file>
+	sets: file=<file> seg=0x70 sect=0x8000 setbpb nohand
+
+Similary to 'freedos=', This prepares to load MSDOS 2.00 - 6.xx or derivatives.
+Sector address is chosen arbitrarily. Otherwise comments as above.
+
+	msdos7=<file>
+	sets: file=<file> seg=0x70::0x200 sect=0x8000 setbpb nohand
+
+Only for MSDOS 7+ versions (98se ~ 7.xx, Me ~ 8.xx). Comments as above.
+TODO/TEST
+
+	drmk=<file>
+	sets: file=<file> seg=0x70 sect=0x2000:0:0 setbpb nohand
+
+This is used for loading of *only* Dell's DOS derivatives. It does require boot
+sector at 0x2000 and overall valid BPB values. As in other DOS-ish cases,
+likely candidates for use are 'save' and 'hide'.
+
+	grub=<file> [grubcfg=<config>]
+	sets: file=<file> seg=0x800::0x200 nohand nosect grub
+
+Chainloads grub legacy's stage2, performing additional corrections on the file
+in memory. Additionally, alternate config file can be specified through
+'grubcfg=' option
+
+	grldr=<file>
+	sets: file=<file> nohand nosect grldr
+
+Chainloads GRUB4DOS grldr, performing additional corrections on the file
+in memory.
+
+	bss=<file>
+	sets: file=<file> nomaps setbpb bss
+
+This emulates syslinux's native BSS option. This loads both the file and the
+sector, adjusts BPB values in the loaded sector, then copies all possible BPB
+fields to the loaded file. Everything is made with reference to the selected
+disk/partition.
+
+	bs=<file>
+	sets: file=<file> nosect filebpb
+
+This emulates syslinux's native BS option. This loads the file and if possible
+- adjusts its BPB values. Everything is made with reference to the selected
+disk/partition.
+
diff --git a/doc/gpt.txt b/doc/gpt.txt
index 0909932..2ef387a 100644
--- a/doc/gpt.txt
+++ b/doc/gpt.txt
@@ -51,7 +51,7 @@
 	  4	  1	0xED (partition type: synthetic)
 	  5	  3	CHS of partition end
 	  8	  4	Partition start LBA
-	 12	  4	Partition end LBA
+	 12	  4	Partition length in sectors
 	 16	  4	Length of the GPT entry
 	 20	varies	GPT partition entry