aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPierre-Alexandre Meyer <pierre@ning.com>2009-10-27 13:04:31 -0700
committerPierre-Alexandre Meyer <pierre@ning.com>2009-10-27 13:04:31 -0700
commit57c8555003f53800da1bb93a765d80678ae230ae (patch)
treeab40516182979cff6e3a7d2c5846bc6065aca4b1
parent47ac912cd5d8df0a96b8f5b98d7476f7cfb1cb19 (diff)
parent3fc3126d0c7df5701680e043c34f200166f24564 (diff)
downloadsyslinux.git-57c8555003f53800da1bb93a765d80678ae230ae.tar.gz
syslinux.git-57c8555003f53800da1bb93a765d80678ae230ae.tar.xz
syslinux.git-57c8555003f53800da1bb93a765d80678ae230ae.zip
Merge commit 'erwan/master' into hdt-0.3.5
-rw-r--r--MCONFIG3
-rw-r--r--NEWS7
-rw-r--r--com32/gplinclude/cpuid.h2
-rw-r--r--com32/gpllib/cpuid.c2
-rw-r--r--com32/hdt/Makefile39
-rw-r--r--com32/hdt/README19
-rw-r--r--com32/hdt/floppy/hdt.cfg6
-rw-r--r--com32/hdt/floppy/mtools.conf2
-rw-r--r--com32/hdt/hdt-cli-cpu.c4
-rw-r--r--com32/hdt/hdt-cli-hdt.c4
-rw-r--r--com32/hdt/hdt-menu-processor.c4
-rw-r--r--com32/hdt/hdt.c4
-rw-r--r--com32/include/cpufeature.h2
-rw-r--r--com32/mboot/map.c2
-rw-r--r--com32/modules/Makefile8
-rw-r--r--com32/modules/chain.c21
-rw-r--r--com32/modules/cpuidtest.c4
-rw-r--r--com32/modules/gpxecmd.c137
-rw-r--r--core/localboot.inc1
-rw-r--r--core/pxelinux.asm184
-rw-r--r--core/serirq.inc28
-rw-r--r--modules/Makefile2
-rw-r--r--modules/int18.asm16
-rw-r--r--utils/Makefile3
-rwxr-xr-xutils/pxelinux-options499
25 files changed, 974 insertions, 29 deletions
diff --git a/MCONFIG b/MCONFIG
index daeb9d4c..998646f6 100644
--- a/MCONFIG
+++ b/MCONFIG
@@ -52,6 +52,9 @@ NM = nm
RANLIB = ranlib
GZIPPROG = gzip
PNGTOPNM = pngtopnm
+MCOPY = mcopy
+MFORMAT = mformat
+MKISOFS = mkisofs
com32 = $(topdir)/com32
diff --git a/NEWS b/NEWS
index 594cf755..c934a3e3 100644
--- a/NEWS
+++ b/NEWS
@@ -18,6 +18,13 @@ Changes in 3.83:
* Simple menu: fix Ctrl-W (word erase) in command-line edit.
* Simple menu: fix crash on some platforms.
* Gfxboot: fixes to the configuration file parsing.
+ * PXELINUX: add a tool to override specific DHCP options via
+ values hardcoded in the pxelinux.0 file. These hardcoded
+ values can be either "before DHCP" (defaults if DHCP do not
+ provide values), or "after DHCP" (overrides DHCP). The tool
+ pxelinux-options can be used to set these options. This
+ feature does not apply to gpxelinux.0; when used with gPXE
+ this is better handled by modifying the embedded script.
Changes in 3.82:
* isohybrid: fix the -partok logic for loading from a partition.
diff --git a/com32/gplinclude/cpuid.h b/com32/gplinclude/cpuid.h
index f984751f..f85e6ab3 100644
--- a/com32/gplinclude/cpuid.h
+++ b/com32/gplinclude/cpuid.h
@@ -65,6 +65,8 @@ typedef struct {
bool nowext; /* AMD 3DNow! extensions */
bool now; /* 3DNow! */
bool smp; /* A smp configuration has been found */
+ bool vmx; /* Hardware virtualization */
+ bool svm; /* Secure virtual machine */
} s_cpu_flags;
typedef struct {
diff --git a/com32/gpllib/cpuid.c b/com32/gpllib/cpuid.c
index 6d464c7e..fb69cef6 100644
--- a/com32/gpllib/cpuid.c
+++ b/com32/gpllib/cpuid.c
@@ -295,6 +295,8 @@ void set_cpu_flags(struct cpuinfo_x86 *c, s_cpu * cpu)
cpu->flags.nowext = cpu_has(c, X86_FEATURE_3DNOWEXT);
cpu->flags.now = cpu_has(c, X86_FEATURE_3DNOW);
cpu->flags.smp = find_smp_config();
+ cpu->flags.vmx = cpu_has(c, X86_FEATURE_VMX);
+ cpu->flags.svm = cpu_has(c, X86_FEATURE_SVM);
}
void set_generic_info(struct cpuinfo_x86 *c, s_cpu * cpu)
diff --git a/com32/hdt/Makefile b/com32/hdt/Makefile
index 8f8cae21..a939d7b2 100644
--- a/com32/hdt/Makefile
+++ b/com32/hdt/Makefile
@@ -27,11 +27,47 @@ TESTFILES =
OBJS = $(patsubst %.c,%.o,$(wildcard *.c))
+KERNEL_VERSION ?= $(shell uname -r)
+MODULES_ALIAS_FILE ?= /lib/modules/$(KERNEL_VERSION)/modules.alias
+MODULES_PCIMAP_FILE ?= /lib/modules/$(KERNEL_VERSION)/modules.pcimap
+ISO_DIR ?= iso
+ISOLINUX_DIR ?= isolinux
+FLOPPY_DIR ?= floppy
+PCI_IDS_FILE ?= $(PWD)/$(FLOPPY_DIR)/pci.ids
+
all: $(MODULES) $(TESTFILES)
hdt.elf : $(OBJS) $(LIBS) $(C_LIBS)
$(LD) $(LDFLAGS) -o $@ $^
+hdt.img: hdt.c32 $(FLOPPY_DIR)/hdt.cfg $(FLOPPY_DIR)/mtools.conf $(topdir)/mtools/syslinux
+ rm -f hdt.img
+ MTOOLSRC=$(PWD)/$(FLOPPY_DIR)/mtools.conf $(MFORMAT) -v HDT -f 2880 -C a:
+ $(topdir)/mtools/syslinux hdt.img
+ -[ ! -f $(PCI_IDS_FILE) ] && cp /usr/share/hwdata/pci.ids $(PCI_IDS_FILE)
+ -[ ! -f $(PCI_IDS_FILE) ] && cp /usr/share/pci.ids $(PCI_IDS_FILE)
+ -[ -f $(MODULES_ALIAS_FILE) ] && MTOOLSRC=$(PWD)/$(FLOPPY_DIR)/mtools.conf $(MCOPY) $(MODULES_ALIAS_FILE) a:
+ -[ -f $(MODULES_PCIMAP_FILE) ] && MTOOLSRC=$(PWD)/$(FLOPPY_DIR)/mtools.conf $(MCOPY) $(MODULES_PCIMAP_FILE) a:
+ MTOOLSRC=$(PWD)/$(FLOPPY_DIR)/mtools.conf $(MCOPY) hdt.c32 a:
+ @ [ -f $(PCI_IDS_FILE) ] && MTOOLSRC=$(PWD)/$(FLOPPY_DIR)/mtools.conf $(MCOPY) $(PCI_IDS_FILE) a: || printf "\nThe $(FLOPPY_DIR)/pci.ids file is missing and can be downloaded from http://pciids.sourceforge.net and put in\nthe ./com32/hdt/$(FLOPPY_DIR) directory of the extracted Syslinux source.\n\n"
+ MTOOLSRC=$(PWD)/$(FLOPPY_DIR)/mtools.conf $(MCOPY) $(FLOPPY_DIR)/hdt.cfg a:syslinux.cfg
+
+hdt.iso: hdt.c32 $(topdir)/core/isolinux.bin $(FLOPPY_DIR)/hdt.cfg
+ rm -rf $(ISO_DIR)
+ rm -f hdt.iso
+ mkdir -p $(ISO_DIR)/$(ISOLINUX_DIR)
+ cp $(topdir)/core/isolinux.bin $(ISO_DIR)/$(ISOLINUX_DIR)
+ cp $(FLOPPY_DIR)/hdt.cfg $(ISO_DIR)/$(ISOLINUX_DIR)/isolinux.cfg
+ cp hdt.c32 $(ISO_DIR)/$(ISOLINUX_DIR)
+ -[ ! -f $(PCI_IDS_FILE) ] && cp /usr/share/hwdata/pci.ids $(ISO_DIR)/$(ISOLINUX_DIR)
+ -[ ! -f $(PCI_IDS_FILE) ] && cp /usr/share/pci.ids $(ISO_DIR)/$(ISOLINUX_DIR)
+ -[ -f $(MODULES_ALIAS_FILE) ] && cp $(MODULES_ALIAS_FILE) $(ISO_DIR)/$(ISOLINUX_DIR)
+ -[ -f $(MODULES_PCIMAP_FILE) ] && cp $(MODULES_PCIMAP_FILE) $(ISO_DIR)/$(ISOLINUX_DIR)
+ -[ ! -f $(ISO_DIR)/$(ISOLINUX_DIR)/pci.ids ] && printf "\nThe $(FLOPPY_DIR)/pci.ids file is missing and can be downloaded from http://pciids.sourceforge.net and put in\nthe ./com32/hdt/$(FLOPPY_DIR) directory of the extracted Syslinux source.\n\n"
+ $(MKISOFS) -o hdt.iso -b $(ISOLINUX_DIR)/isolinux.bin -c $(ISOLINUX_DIR)/boot.cat \
+ -no-emul-boot -boot-load-size 4 -boot-info-table \
+ $(ISO_DIR)
+
tidy dist:
rm -f *.o *.lo *.a *.lst *.elf .*.d *.tmp
@@ -39,7 +75,8 @@ clean: tidy
rm -f *.lnx
spotless: clean
- rm -f *.lss *.c32 *.com
+ rm -f *.lss *.c32 *.com hdt.img hdt.iso
+ rm -rf $(ISO_DIR)
rm -f *~ \#*
install:
diff --git a/com32/hdt/README b/com32/hdt/README
new file mode 100644
index 00000000..8e171616
--- /dev/null
+++ b/com32/hdt/README
@@ -0,0 +1,19 @@
+--------------
+Compiling HDT
+--------------
+To build HDT, you just have to do a "make" call in this directory.
+
+---------------------------
+Creating a bootable floppy
+--------------------------
+To build a bootable HDT floppy image, you can do a "make hdt.img" call.
+This will requires the mtools (http://mtools.linux.lu) to be installed.
+The script will try to pick several files from your system :
+- /lib/modules/`uname -r`/modules.alias
+- /lib/modules/`uname -r`/modules.pcimap
+- /usr/share/pci.ids or /usr/share/hwdata/pci.ids
+
+This paths can be overrided with the following command line:
+make MODULES_ALIAS_FILE=$(PWD)/floppy/modules.alias MODULES_PCIMAP_FILE=$(PWD)/floppy/modules.pcimap PCI_IDS_FILE=$(PWD)/floppy/pci.ids hdt.img
+
+If your system doesn't have pci.ids, please download it from http://pciids.sourceforge.net/ and put it into the floppy/ directory.
diff --git a/com32/hdt/floppy/hdt.cfg b/com32/hdt/floppy/hdt.cfg
new file mode 100644
index 00000000..04e30476
--- /dev/null
+++ b/com32/hdt/floppy/hdt.cfg
@@ -0,0 +1,6 @@
+DEFAULT hdt
+PROMPT 0
+
+LABEL hdt
+COM32 hdt.c32
+APPEND modules_pcimap=modules.pcimap modules_alias=modules.alias pciids=pci.ids
diff --git a/com32/hdt/floppy/mtools.conf b/com32/hdt/floppy/mtools.conf
new file mode 100644
index 00000000..adbe2c83
--- /dev/null
+++ b/com32/hdt/floppy/mtools.conf
@@ -0,0 +1,2 @@
+# Floppy image for HDT
+drive a: file="hdt.img"
diff --git a/com32/hdt/hdt-cli-cpu.c b/com32/hdt/hdt-cli-cpu.c
index f0f0a1bb..d2b5979f 100644
--- a/com32/hdt/hdt-cli-cpu.c
+++ b/com32/hdt/hdt-cli-cpu.c
@@ -174,6 +174,10 @@ static void show_cpu(int argc __unused, char **argv __unused,
strcat(buffer1, "3dnowext ");
if (hardware->cpu.flags.now)
strcat(buffer1, "3dnow! ");
+ if (hardware->cpu.flags.svm)
+ strcat(buffer1, "svm ");
+ if (hardware->cpu.flags.vmx)
+ strcat(buffer1, "vmx ");
if (buffer1[0]) {
snprintf(buffer, sizeof buffer, "Flags : %s\n", buffer1);
more_printf(buffer);
diff --git a/com32/hdt/hdt-cli-hdt.c b/com32/hdt/hdt-cli-hdt.c
index 5bebf584..b5cd5359 100644
--- a/com32/hdt/hdt-cli-hdt.c
+++ b/com32/hdt/hdt-cli-hdt.c
@@ -191,8 +191,8 @@ static void goto_menu(int argc __unused, char** argv __unused,
struct s_hardware *hardware)
{
char version_string[256];
- snprintf(version_string, sizeof version_string, "%s %s by %s",
- PRODUCT_NAME, VERSION, AUTHOR);
+ snprintf(version_string, sizeof version_string, "%s %s",
+ PRODUCT_NAME, VERSION);
start_menu_mode(hardware, version_string);
return;
}
diff --git a/com32/hdt/hdt-menu-processor.c b/com32/hdt/hdt-menu-processor.c
index a30cf0f6..4e102cb5 100644
--- a/com32/hdt/hdt-menu-processor.c
+++ b/com32/hdt/hdt-menu-processor.c
@@ -231,6 +231,10 @@ void compute_processor(struct s_my_menu *menu, struct s_hardware *hardware)
strcat(buffer1, "3dnowext ");
if (hardware->cpu.flags.now)
strcat(buffer1, "3dnow! ");
+ if (hardware->cpu.flags.vmx)
+ strcat(buffer1, "vmx ");
+ if (hardware->cpu.flags.svm)
+ strcat(buffer1, "svm ");
snprintf(buffer, sizeof buffer, "Flags : %s", buffer1);
snprintf(statbuffer, sizeof statbuffer, "Flags: %s", buffer1);
add_item(buffer, statbuffer, OPT_INACTIVE, NULL, 0);
diff --git a/com32/hdt/hdt.c b/com32/hdt/hdt.c
index 93c4aae2..e10e56f5 100644
--- a/com32/hdt/hdt.c
+++ b/com32/hdt/hdt.c
@@ -48,8 +48,8 @@ int main(const int argc, const char *argv[])
const char *arg;
struct s_hardware hardware;
- snprintf(version_string, sizeof version_string, "%s %s by %s",
- PRODUCT_NAME,VERSION,AUTHOR);
+ snprintf(version_string, sizeof version_string, "%s %s",
+ PRODUCT_NAME,VERSION);
/* Opening the Syslinux console */
console_ansi_raw();
diff --git a/com32/include/cpufeature.h b/com32/include/cpufeature.h
index 2fd47579..036631a7 100644
--- a/com32/include/cpufeature.h
+++ b/com32/include/cpufeature.h
@@ -72,6 +72,7 @@
#define X86_FEATURE_XMM3 (4*32+ 0) /* Streaming SIMD Extensions-3 */
#define X86_FEATURE_MWAIT (4*32+ 3) /* Monitor/Mwait support */
#define X86_FEATURE_DSCPL (4*32+ 4) /* CPL Qualified Debug Store */
+#define X86_FEATURE_VMX (4*32+ 5) /* Hardware virtualization */
#define X86_FEATURE_EST (4*32+ 7) /* Enhanced SpeedStep */
#define X86_FEATURE_TM2 (4*32+ 8) /* Thermal Monitor 2 */
#define X86_FEATURE_CID (4*32+10) /* Context ID */
@@ -87,6 +88,7 @@
/* More extended AMD flags: CPUID level 0x80000001, ecx, word 6 */
#define X86_FEATURE_LAHF_LM (6*32+ 0) /* LAHF/SAHF in long mode */
#define X86_FEATURE_CMP_LEGACY (6*32+ 1) /* If yes HyperThreading not valid */
+#define X86_FEATURE_SVM (6*32+ 2) /* Secure virtual machine */
#endif /* __ASM_I386_CPUFEATURE_H */
diff --git a/com32/mboot/map.c b/com32/mboot/map.c
index 1a788ef7..887776fe 100644
--- a/com32/mboot/map.c
+++ b/com32/mboot/map.c
@@ -127,7 +127,7 @@ int map_image(void *ptr, size_t len)
else
mbh_len = 12;
- if (i + mbh_len < len)
+ if (i + mbh_len > len)
mbh_len = 0; /* Invalid... */
else
break; /* Found something... */
diff --git a/com32/modules/Makefile b/com32/modules/Makefile
index c93d3afc..52327b61 100644
--- a/com32/modules/Makefile
+++ b/com32/modules/Makefile
@@ -18,10 +18,10 @@
topdir = ../..
include ../MCONFIG
-MODULES = chain.c32 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
+MODULES = chain.c32 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 gpxecmd.c32
TESTFILES =
diff --git a/com32/modules/chain.c b/com32/modules/chain.c
index bdeb82d5..53746c85 100644
--- a/com32/modules/chain.c
+++ b/com32/modules/chain.c
@@ -446,7 +446,7 @@ static void do_boot(void *boot_sector, size_t boot_size,
mmap = syslinux_memory_map();
if (!mmap) {
- error("Cannot read system memory map");
+ error("Cannot read system memory map\n");
return;
}
@@ -547,11 +547,11 @@ static void do_boot(void *boot_sector, size_t boot_size,
return;
too_big:
- error("Loader file too large");
+ error("Loader file too large\n");
return;
enomem:
- error("Out of memory");
+ error("Out of memory\n");
return;
}
@@ -614,7 +614,7 @@ int main(int argc, char *argv[])
} else if (!strncmp(argv[i], "seg=", 4)) {
uint32_t segval = strtoul(argv[i] + 4, NULL, 0);
if (segval < 0x50 || segval > 0x9f000) {
- error("Invalid segment");
+ error("Invalid segment\n");
goto bail;
}
opt.seg = segval;
@@ -650,7 +650,18 @@ int main(int argc, char *argv[])
}
} else {
error
- ("Usage: chain.c32 (hd#|fd#|mbr:#|boot)[,partition] [options]\n");
+ ("Usage: chain.c32 hd<disk#> [<partition>] [options]\n"
+ " chain.c32 fd<disk#> [options]\n"
+ " chain.c32 mbr:<id> [<partition>] [options]\n"
+ " chain.c32 boot [<partition>] [options]\n"
+ "Options: file=<loader> load file, instead of boot sector\n"
+ " ntldr=<loader> load Windows bootloaders: NTLDR, SETUPLDR, BOOTMGR\n"
+ " freedos=<loader> load FreeDOS kernel.sys\n"
+ " msdos=<loader> load MS-DOS io.sys\n"
+ " pcdos=<loader> load PC-DOS ibmbio.com\n"
+ " seg=<segment> jump to <seg>:0000 instead of 0000:7C00\n"
+ " swap swap drive numbers, if bootdisk is not fd0/hd0\n"
+ " hide hide primary partitions, except selected partition\n");
goto bail;
}
}
diff --git a/com32/modules/cpuidtest.c b/com32/modules/cpuidtest.c
index bfc1c190..b7688852 100644
--- a/com32/modules/cpuidtest.c
+++ b/com32/modules/cpuidtest.c
@@ -121,6 +121,10 @@ int main(void)
printf("3dnowext ");
if (cpu.flags.now)
printf("3dnow! ");
+ if (cpu.flags.vmx)
+ printf("vmx ");
+ if (cpu.flags.svm)
+ printf("svm ");
printf("\n");
printf("SMP = ");
if (cpu.flags.smp)
diff --git a/com32/modules/gpxecmd.c b/com32/modules/gpxecmd.c
new file mode 100644
index 00000000..de6ffb21
--- /dev/null
+++ b/com32/modules/gpxecmd.c
@@ -0,0 +1,137 @@
+/* ----------------------------------------------------------------------- *
+ *
+ * Copyright 2008 H. Peter Anvin - All Rights Reserved
+ *
+ * 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., 51 Franklin St, Fifth Floor,
+ * Boston MA 02110-1301, USA; either version 2 of the License, or
+ * (at your option) any later version; incorporated herein by reference.
+ *
+ * ----------------------------------------------------------------------- */
+
+/*
+ * gpxecmd.c
+ *
+ * Invoke an arbitrary gPXE command, if available.
+ */
+
+#include <alloca.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <console.h>
+#include <com32.h>
+#include <stdbool.h>
+#include <string.h>
+#include <syslinux/config.h>
+
+struct segoff16 {
+ uint16_t offs, seg;
+};
+
+struct s_PXENV_FILE_CHECK_API {
+ uint16_t Status;
+ uint16_t Size;
+ uint32_t Magic;
+ uint32_t Provider;
+ uint32_t APIMask;
+ uint32_t Flags;
+};
+
+static bool is_gpxe(void)
+{
+ const struct syslinux_version *sv;
+ com32sys_t reg;
+ struct s_PXENV_FILE_CHECK_API *fca;
+
+ sv = syslinux_version();
+ if (sv->filesystem != SYSLINUX_FS_PXELINUX)
+ return false; /* Not PXELINUX */
+
+ fca = __com32.cs_bounce;
+ memset(fca, 0, sizeof *fca);
+ fca->Size = sizeof *fca;
+ fca->Magic = 0x91d447b2;
+
+ memset(&reg, 0, sizeof reg);
+ reg.eax.w[0] = 0x0009;
+ reg.ebx.w[0] = 0x00e6; /* PXENV_FILE_API_CHECK */
+ reg.edi.w[0] = OFFS(fca);
+ reg.es = SEG(fca);
+
+ __intcall(0x22, &reg, &reg);
+
+ if (reg.eflags.l & EFLAGS_CF)
+ return false; /* Cannot invoke PXE stack */
+
+ if (reg.eax.w[0] || fca->Status)
+ return false; /* PXE failure */
+
+ if (fca->Magic != 0xe9c17b20)
+ return false; /* Incorrect magic */
+
+ if (fca->Size < sizeof *fca)
+ return false; /* Short return */
+
+ if (!(fca->APIMask & (1 << 5)))
+ return false; /* No FILE EXEC */
+
+ return true;
+}
+
+struct s_PXENV_FILE_EXEC {
+ uint16_t Status;
+ struct segoff16 Command;
+};
+
+static void gpxecmd(const char **args)
+{
+ char *q;
+ struct s_PXENV_FILE_EXEC *fx;
+ com32sys_t reg;
+
+ memset(&reg, 0, sizeof reg);
+
+ fx = __com32.cs_bounce;
+ q = (char *)(fx + 1);
+
+ fx->Status = 1;
+ fx->Command.offs = OFFS(q);
+ fx->Command.seg = SEG(q);
+
+ while (*args) {
+ q = stpcpy(q, *args);
+ *q++ = ' ';
+ args++;
+ }
+ *--q = '\0';
+
+ memset(&reg, 0, sizeof reg);
+ reg.eax.w[0] = 0x0009;
+ reg.ebx.w[0] = 0x00e5; /* PXENV_FILE_EXEC */
+ reg.edi.w[0] = OFFS(fx);
+ reg.es = SEG(fx);
+
+ __intcall(0x22, &reg, &reg);
+
+ /* This should not return... */
+}
+
+int main(int argc, const char *argv[])
+{
+ openconsole(&dev_null_r, &dev_stdcon_w);
+
+ if (argc < 2) {
+ printf("Usage: gpxecmd command...\n");
+ return 1;
+ }
+
+ if (!is_gpxe()) {
+ printf("gpxecmd: gPXE API not detected\n");
+ return 1;
+ }
+
+ gpxecmd(argv + 1);
+
+ return 0;
+}
diff --git a/core/localboot.inc b/core/localboot.inc
index ae54737a..03d5cfd9 100644
--- a/core/localboot.inc
+++ b/core/localboot.inc
@@ -31,6 +31,7 @@ local_boot:
mov gs,dx
mov si,localboot_msg
call writestr
+ call cleanup_hardware
cmp ax,-1
je .int18
diff --git a/core/pxelinux.asm b/core/pxelinux.asm
index 190f4c66..651dd4c3 100644
--- a/core/pxelinux.asm
+++ b/core/pxelinux.asm
@@ -224,6 +224,25 @@ StackBuf equ $ ; Base of stack if we use our own
;
bootsec equ $
_start:
+ jmp 0:_start1 ; Canonicalize the address and skip
+ ; the patch header
+
+;
+; Patch area for adding hardwired DHCP options
+;
+ align 4
+
+hcdhcp_magic dd 0x2983c8ac ; Magic number
+hcdhcp_len dd 7*4 ; Size of this structure
+hcdhcp_flags dd 0 ; Reserved for the future
+ ; Parameters to be parsed before the ones from PXE
+bdhcp_offset dd 0 ; Offset (entered by patcher)
+bdhcp_len dd 0 ; Length (entered by patcher)
+ ; Parameters to be parsed *after* the ones from PXE
+adhcp_offset dd 0 ; Offset (entered by patcher)
+adhcp_len dd 0 ; Length (entered by patcher)
+
+_start1:
pushfd ; Paranoia... in case of return to PXE
pushad ; ... save as much state as possible
push ds
@@ -236,8 +255,6 @@ _start:
mov ds,ax
mov es,ax
- jmp 0:_start1 ; Canonicalize address
-_start1:
; That is all pushed onto the PXE stack. Save the pointer
; to it and switch to an internal stack.
mov [InitStack],sp
@@ -252,6 +269,54 @@ _start1:
lss esp,[BaseStack]
sti ; Stack set up and ready
+;
+; Move the hardwired DHCP options (if present) to a safe place...
+;
+bdhcp_copy:
+ mov cx,[bdhcp_len]
+ mov ax,trackbufsize/2
+ jcxz .none
+ cmp cx,ax
+ jbe .oksize
+ mov cx,ax
+ mov [bdhcp_len],ax
+.oksize:
+ mov eax,[bdhcp_offset]
+ add eax,_start
+ mov si,ax
+ and si,000Fh
+ shr eax,4
+ push ds
+ mov ds,ax
+ mov di,trackbuf
+ add cx,3
+ shr cx,2
+ rep movsd
+ pop ds
+.none:
+
+adhcp_copy:
+ mov cx,[adhcp_len]
+ mov ax,trackbufsize/2
+ jcxz .none
+ cmp cx,ax
+ jbe .oksize
+ mov cx,ax
+ mov [adhcp_len],ax
+.oksize:
+ mov eax,[adhcp_offset]
+ add eax,_start
+ mov si,ax
+ and si,000Fh
+ shr eax,4
+ push ds
+ mov ds,ax
+ mov di,trackbuf+trackbufsize/2
+ add cx,3
+ shr cx,2
+ rep movsd
+ pop ds
+.none:
;
; Initialize screen (if we're using one)
@@ -268,6 +333,81 @@ _start1:
call writestr_early
;
+; Look to see if we are on an EFI CSM system. Some EFI
+; CSM systems put the BEV stack in low memory, which means
+; a return to the PXE stack will crash the system. However,
+; INT 18h works reliably, so in that case hack the stack and
+; point the "return address" to an INT 18h instruction.
+;
+; Hack the stack instead of the much simpler "just invoke INT 18h
+; if we want to reset", so that chainloading other NBPs will work.
+;
+efi_csm_workaround:
+ les bp,[InitStack] ; GS:SP -> original stack
+ les bx,[es:bp+44] ; Return address
+ cmp word [es:bx],18CDh ; Already pointing to INT 18h?
+ je .skip
+
+ ; Search memory from E0000 to FFFFF for a $EFI structure
+ mov bx,0E000h
+.scan_mem:
+ mov es,bx
+ cmp dword [es:0],'IFE$' ; $EFI is byte-reversed...
+ jne .not_here
+ ;
+ ; Verify the table. We don't check the checksum because
+ ; it seems some CSMs leave it at zero.
+ ;
+ movzx cx,byte [es:5] ; Table length
+ cmp cx,83 ; 83 bytes is the current length...
+ jae .found_it
+
+.not_here:
+ inc bx
+ jnz .scan_mem
+ jmp .skip ; No $EFI structure found
+
+ ;
+ ; Found a $EFI structure. Move down the original stack
+ ; and put an INT 18h instruction there instead.
+ ;
+.found_it:
+%if USE_PXE_PROVIDED_STACK
+ mov cx,efi_csm_hack_size
+ mov si,sp
+ sub sp,cx
+ mov di,sp
+ mov ax,ss
+ mov es,ax
+ sub [InitStack],cx
+ sub [BaseStack],cx
+%else
+ les si,[InitStack]
+ lea di,[si-efi_csm_hack_size]
+ mov [InitStack],di
+%endif
+ lea cx,[bp+52] ; End of the stack we care about
+ sub cx,si
+ es rep movsb
+ mov [es:di-8],di ; Clobber the return address
+ mov [es:di-6],es
+ mov si,efi_csm_hack
+ mov cx,efi_csm_hack_size
+ rep movsb
+
+.skip:
+
+ section .data
+ alignz 4
+efi_csm_hack:
+ int 18h
+ jmp 0F000h:0FFF0h
+ hlt
+efi_csm_hack_size equ $-efi_csm_hack
+
+ section .text
+
+;
; Assume API version 2.1, in case we find the !PXE structure without
; finding the PXENV+ structure. This should really look at the Base
; Code ROM ID structure in have_pxe, but this is adequate for now --
@@ -450,6 +590,24 @@ have_entrypoint:
xor ax,ax
mov [LocalDomain],al ; No LocalDomain received
+
+; This is a good time to initialize DHCPMagic...
+; Initialize it to 1 meaning we will accept options found;
+; in earlier versions of PXELINUX bit 0 was used to indicate
+; we have found option 208 with the appropriate magic number;
+; we no longer require that, but MAY want to re-introduce
+; it in the future for vendor encapsulated options.
+ mov byte [DHCPMagic],1
+
+;
+; Process any hardwired options the user may have specified. This is
+; different than the actual packets in that there is no header, just
+; an option field.
+;
+ mov cx,[bdhcp_len]
+ mov si,trackbuf
+ call parse_dhcp_options
+
;
; The DHCP client identifiers are best gotten from the DHCPREQUEST
; packet (query info 1).
@@ -462,15 +620,6 @@ query_bootp_1:
call pxe_get_cached_info
call parse_dhcp
- ; We don't use flags from the request packet, so
- ; this is a good time to initialize DHCPMagic...
- ; Initialize it to 1 meaning we will accept options found;
- ; in earlier versions of PXELINUX bit 0 was used to indicate
- ; we have found option 208 with the appropriate magic number;
- ; we no longer require that, but MAY want to re-introduce
- ; it in the future for vendor encapsulated options.
- mov byte [DHCPMagic],1
-
;
; Now attempt to get the BOOTP/DHCP packet that brought us life (and an IP
; address). This lives in the DHCPACK packet (query info 2).
@@ -514,6 +663,15 @@ query_bootp_3:
call crlf
;
+; Process any hardwired options the user may have specified. This is
+; different than the actual packets in that there is no header, just
+; an option field. This handles the "after" options
+;
+ mov cx,[adhcp_len]
+ mov si,trackbuf+trackbufsize/2
+ call parse_dhcp_options
+
+;
; Generate the bootif string, and the hardware-based config string.
;
make_bootif_string:
@@ -789,6 +947,7 @@ local_boot:
mov si,localboot_msg
call writestr_early
; Restore the environment we were called with
+ call cleanup_hardware
lss sp,[InitStack]
pop gs
pop fs
@@ -2176,6 +2335,7 @@ xchexbytes:
; pxe_get_cached_info
;
; Get a DHCP packet from the PXE stack into the trackbuf.
+; Leaves the upper half of the trackbuf untouched.
;
; Input:
; DL = packet type
@@ -2196,7 +2356,7 @@ pxe_get_cached_info:
stosw ; Status
movzx ax,dl
stosw ; Packet type
- mov ax,trackbufsize
+ mov ax,trackbufsize/2
stosw ; Buffer size
mov ax,trackbuf
stosw ; Buffer offset
diff --git a/core/serirq.inc b/core/serirq.inc
index 579c42b4..b7d79e0f 100644
--- a/core/serirq.inc
+++ b/core/serirq.inc
@@ -91,6 +91,9 @@ SerialIRQPort dw 0 ; Serial port w IRQ service
SerialHead dw 0 ; Head of serial port rx buffer
SerialTail dw 0 ; Tail of serial port rx buffer
+ section .bss
+IRQMask resw 1 ; PIC IRQ mask status
+
section .text
sirq_install:
@@ -133,6 +136,22 @@ sirq_install:
mov al,1 ; Enable receive interrupt
slow_out dx,al
+ ;
+ ; Enable all ther interupt lines at the PIC. Some BIOSes
+ ; only enable the timer interrupts and other interrupts
+ ; actively in use by the BIOS.
+ ;
+ in al,0xA1 ; Secondary PIC mask register
+ mov ah,al
+ in al,0x21 ; Primary PIC mask register
+ mov [IRQMask],ax
+
+ io_delay
+
+ xor ax,ax ; Remove all interrupt masks
+ out 0x21,al
+ out 0xA1,al
+
popad
ret
@@ -156,6 +175,12 @@ sirq_cleanup_nowipe:
xor ax,ax
slow_out dx,al ; Clear IER
+ ; Restore PIC masks
+ mov ax,[IRQMask]
+ out 0x21,al
+ mov al,ah
+ out 0xA1,al
+
; Restore the original interrupt vectors
mov si,oldirq0
mov di,4*08h
@@ -165,6 +190,9 @@ sirq_cleanup_nowipe:
mov cx,8
rep movsd
+ xor ax,ax
+ mov [SerialIRQPort],ax ; No active interrupt system
+
.done:
pop es
pop ds
diff --git a/modules/Makefile b/modules/Makefile
index 80eb995d..77020ea0 100644
--- a/modules/Makefile
+++ b/modules/Makefile
@@ -19,7 +19,7 @@ include $(topdir)/MCONFIG.embedded
INCLUDES = -I$(com32)/include
-BINS = pxechain.com gfxboot.com poweroff.com
+BINS = pxechain.com gfxboot.com poweroff.com int18.com
all: $(BINS)
diff --git a/modules/int18.asm b/modules/int18.asm
new file mode 100644
index 00000000..a13ada75
--- /dev/null
+++ b/modules/int18.asm
@@ -0,0 +1,16 @@
+ bits 16
+ org 100h
+_start:
+ mov ax,5
+ int 22h
+ mov ah,09h
+ mov dx,msg
+ int 21h
+ mov ax,000Ch
+ xor dx,dx
+ int 22h
+ int 18h
+ jmp 0F000h:0FFF0h ; INT 18h should not return...
+
+ section .data
+msg: db 'Local boot via INT 18...', 13, 10, '$'
diff --git a/utils/Makefile b/utils/Makefile
index 9df9595a..39adf7d3 100644
--- a/utils/Makefile
+++ b/utils/Makefile
@@ -21,7 +21,8 @@ CFLAGS = -W -Wall -Os -fomit-frame-pointer -D_FILE_OFFSET_BITS=64
LDFLAGS = -O2 -s
TARGETS = mkdiskimage isohybrid gethostip
-ASIS = keytab-lilo lss16toppm md5pass ppmtolss16 sha1pass syslinux2ansi
+ASIS = keytab-lilo lss16toppm md5pass ppmtolss16 sha1pass syslinux2ansi \
+ pxelinux-options
ISOHDPFX = ../mbr/isohdpfx.bin ../mbr/isohdpfx_f.bin ../mbr/isohdpfx_c.bin \
../mbr/isohdppx.bin ../mbr/isohdppx_f.bin ../mbr/isohdppx_c.bin
diff --git a/utils/pxelinux-options b/utils/pxelinux-options
new file mode 100755
index 00000000..ab7075b5
--- /dev/null
+++ b/utils/pxelinux-options
@@ -0,0 +1,499 @@
+#!/usr/bin/perl
+#
+# Set PXELINUX hard-coded options
+#
+
+use Socket; # For gethostbyname
+use Fcntl;
+use bytes;
+
+%option_names = (
+ 6 => 'domain-name-servers',
+ 15 => 'domain-name',
+ 54 => 'next-server',
+ 209 => 'config-file',
+ 210 => 'path-prefix',
+ 211 => 'reboottime'
+ );
+
+@fmt_oneip = ("ip-address", \&parse_oneip, \&show_ip);
+@fmt_multiip = ("ip-address-list", \&parse_multiip, \&show_ip);
+@fmt_string = ("string", \&parse_string, \&show_string);
+@fmt_uint32 = ("uint32", \&parse_uint32, \&show_uint32);
+
+%option_format = (
+ 6 => \@fmt_multiip,
+ 15 => \@fmt_string,
+ 54 => \@fmt_oneip,
+ 67 => \@fmt_string,
+ 209 => \@fmt_string,
+ 210 => \@fmt_string,
+ 211 => \@fmt_uint32
+ );
+
+sub parse_oneip($)
+{
+ my($s) = @_;
+ my($name,$aliases,$addrtype,$length,@addrs) = gethostbyname($s);
+
+ return ($addrtype == AF_INET) ? $addrs[0] : undef;
+}
+
+sub parse_multiip($)
+{
+ my($l) = @_;
+ my $s;
+ my @a = ();
+ my $addr;
+ my $d = '';
+
+ foreach $s (split(/,/, $l)) {
+ my($name,$aliases,$addrtype,$length,@addrs)
+ = gethostbyname($s);
+ if ($addrtype == AF_INET) {
+ foreach $addr (@addrs) {
+ $d .= $addr;
+ }
+ }
+ }
+
+ return $d ne '' ? $d : undef;
+}
+
+sub show_ip($)
+{
+ my($l) = @_;
+
+ if (length($l) & 3) {
+ return undef;
+ } else {
+ my @h = ();
+ my $i;
+
+ for ($i = 0; $i < length($l); $i += 4) {
+ push(@h, inet_ntoa(substr($l, $i, 4)));
+ }
+
+ return join(',', @h);
+ }
+}
+
+sub parse_string($)
+{
+ return $_[0];
+}
+
+sub show_string($)
+{
+ my($s) = @_;
+ my $o, $i, $c;
+
+ $o = "\'";
+ for ($i = 0; $i < length($s); $i++) {
+ $c = substr($s, $i, 1);
+ if ($c eq "\'" || $c eq '!') {
+ $o .= "\'\\$c\'";
+ } else {
+ $o .= $c;
+ }
+ }
+ $o .= "\'";
+
+ return $o;
+}
+
+sub parse_uint32($)
+{
+ my($s) = @_;
+
+ if ($s =~ /^[0-9]+$/) {
+ return pack("N", $s);
+ } else {
+ return undef;
+ }
+}
+
+sub show_uint32($)
+{
+ my($l) = @_;
+
+ if (length($l) == 4) {
+ return unpack("N", $l);
+ } else {
+ return undef;
+ }
+}
+
+sub parse_generic($)
+{
+ my($s) = @_;
+
+ if ($s =~ /^[0-9a-f]{1,2}(:[0-9a-f]{1,2})*$/) {
+ my $h;
+ my @b = ();
+
+ foreach $h (split(/\:/, $s)) {
+ push(@b, hex $h);
+ }
+
+ return pack("C", @b);
+ } else {
+ return undef;
+ }
+}
+
+sub show_generic($)
+{
+ my($l) = @_;
+ my $i;
+ my @h;
+
+ for ($i = 0; $i < length($l); $i++) {
+ push(@h, sprintf("%02x", unpack("C", substr($l, $i, $1))));
+ }
+
+ return join(':', @h);
+}
+
+sub parse_option($$)
+{
+ my($opt, $arg) = @_;
+ my $v;
+
+ if (defined($option_format{$opt})) {
+ $v = $option_format{$opt}[1]($arg);
+ return $v if (defined($v));
+ }
+
+ return parse_generic($arg);
+}
+
+sub show_option($$)
+{
+ my($opt, $arg) = @_;
+ my $v;
+
+ if (defined($option_format{$opt})) {
+ $v = $option_format{$opt}[2]($arg);
+ return $v if (defined($v));
+ }
+
+ return show_generic($arg);
+}
+
+sub option_number($)
+{
+ my($n) = @_;
+
+ if (defined($option_rnames{$n})) {
+ return $option_rnames{$n};
+ } elsif ($n =~ /^[0-9]+$/ && $n >= 1 && $n <= 254) {
+ return $n+0;
+ } else {
+ return undef;
+ }
+}
+
+sub read_optsets($)
+{
+ my($file) = @_;
+ my $data, $bdata, $adata;
+ my $patch_start = (stat($file))[7];
+
+ return undef unless (seek($file, 8, SEEK_SET));
+ return undef unless (read($file, $data, 7*4) == 7*4);
+
+ my($magic, $len, $flags, $boff, $blen, $aoff, $alen)
+ = unpack("VVVVVVV", $data);
+ return undef if ($magic != 0x2983c8ac);
+ return undef if ($len < 7*4);
+
+ if ($blen == 0) {
+ $bdata = '';
+ } else {
+ return undef unless (seek($file, $boff, SEEK_SET));
+ return undef unless (read($file, $bdata, $blen) == $blen);
+ $patch_start = $boff if ($boff < $patch_start);
+ }
+
+ if ($alen == 0) {
+ $adata = '';
+ } else {
+ return undef unless (seek($file, $aoff, SEEK_SET));
+ return undef unless (read($file, $adata, $alen) == $alen);
+ $patch_start = $aoff if ($aoff < $patch_start);
+ }
+
+ return ($patch_start, $bdata, $adata);
+}
+
+sub write_optsets($$@)
+{
+ my($file, $patch_start, $bdata, $adata) = @_;
+ my $boff = 0;
+ my $aoff = 0;
+
+ if (length($bdata) > 0) {
+ $bdata .= "\xff";
+ $boff = $patch_start;
+ return undef unless (seek($file, $boff, SEEK_SET));
+ return undef unless (print $file $bdata);
+ $patch_start += length($bdata);
+ }
+
+ if (length($adata) > 0) {
+ $adata .= "\xff";
+ $aoff = $patch_start;
+ return undef unless (seek($file, $aoff, SEEK_SET));
+ return undef unless (print $file $adata);
+ $patch_start += length($adata);
+ }
+
+ my $hdr = pack("VVVV", $boff, length($bdata), $aoff, length($adata));
+
+ return undef unless (seek($file, 8+3*4, SEEK_SET));
+ return undef unless (print $file $hdr);
+
+ truncate($file, $patch_start);
+ return 1;
+}
+
+sub delete_option($$)
+{
+ my ($num, $block) = @_;
+ my $o, $l, $c, $x;
+
+ $x = 0;
+ while ($x < length($block)) {
+ ($o, $l) = unpack("CC", substr($block, $x, 2));
+ if ($o == $num) {
+ # Delete this option
+ substr($block, $x, $l+2) = '';
+ } elsif ($o == 0) {
+ # Delete a null option
+ substr($block, $x, 1) = '';
+ } elsif ($o == 255) {
+ # End marker - truncate block
+ $block = substr($block, 0, $x);
+ last;
+ } else {
+ # Skip to the next option
+ $x += $l+2;
+ }
+ }
+
+ return $block;
+}
+
+sub add_option($$$)
+{
+ my ($num, $data, $block) = @_;
+
+ $block = delete_option($num, $block);
+
+ if (length($data) == 0) {
+ return $block;
+ } elsif (length($data) > 255) {
+ die "$0: option $num has too much data (max 255 bytes)\n";
+ } else {
+ return $block . pack("CC", $num, length($data)) . $data;
+ }
+}
+
+sub list_options($$)
+{
+ my($pfx, $data) = @_;
+ my $x, $o, $l;
+
+ while ($x < length($data)) {
+ ($o, $l) = unpack("CC", substr($data, $x, 2));
+
+ if ($o == 0) {
+ $x++;
+ } elsif ($o == 255) {
+ last;
+ } else {
+ my $odata = substr($data, $x+2, $l);
+ last if (length($odata) != $l); # Incomplete option
+
+ printf "%s%-20s %s\n", $pfx,
+ $option_names{$o} || sprintf("%d", $o),
+ show_option($o, $odata);
+
+ $x += $l+2;
+ }
+ }
+}
+
+sub usage()
+{
+ my $i;
+
+ print STDERR "Usage: $0 options pxelinux.0\n";
+ print STDERR "Options:\n";
+ print STDERR "--before option value -b Add an option before DHCP data\n";
+ print STDERR "--after option value -a Add an option after DHCP data\n";
+ print STDERR "--delete option -d Delete an option\n";
+ print STDERR "--list -l List set options\n";
+ print STDERR "--dry-run -n Don't modify the target file\n";
+ print STDERR "--help -h Display this help text\n";
+ print STDERR "\n";
+ print STDERR "The following DHCP options are currently recognized:\n";
+ printf STDERR "%-23s %-3s %s\n", 'Name', 'Num', 'Value Format';
+
+ foreach $i (sort { $a <=> $b } keys(%option_names)) {
+ printf STDERR "%-23s %3d %s\n",
+ $option_names{$i}, $i, $option_format{$i}[0];
+ }
+}
+
+%option_rnames = ();
+foreach $opt (keys(%option_names)) {
+ $option_rnames{$option_names{$opt}} = $opt;
+}
+
+%before = ();
+%after = ();
+@clear = ();
+$usage = 0;
+$err = 0;
+$list = 0;
+$no_write = 0;
+undef $file;
+
+while (defined($opt = shift(@ARGV))) {
+ if ($opt !~ /^-/) {
+ if (defined($file)) {
+ $err = $usage = 1;
+ last;
+ }
+ $file = $opt;
+ } elsif ($opt eq '-b' || $opt eq '--before') {
+ $oname = shift(@ARGV);
+ $odata = shift(@ARGV);
+
+ if (!defined($odata)) {
+ $err = $usage = 1;
+ last;
+ }
+
+ $onum = option_number($oname);
+ if (!defined($onum)) {
+ print STDERR "$0: unknown option name: $oname\n";
+ $err = 1;
+ next;
+ }
+
+ $odata = parse_option($onum, $odata);
+ if (!defined($odata)) {
+ print STDERR "$0: unable to parse data for option $oname\n";
+ $err = 1;
+ next;
+ }
+
+ delete $after{$onum};
+ $before{$onum} = $odata;
+ push(@clear, $onum);
+ } elsif ($opt eq '-a' || $opt eq '--after') {
+ $oname = shift(@ARGV);
+ $odata = shift(@ARGV);
+
+ if (!defined($odata)) {
+ $err = $usage = 1;
+ last;
+ }
+
+ $onum = option_number($oname);
+ if (!defined($onum)) {
+ print STDERR "$0: unknown option name: $oname\n";
+ $err = 1;
+ next;
+ }
+
+ $odata = parse_option($onum, $odata);
+ if (!defined($odata)) {
+ print STDERR "$0: unable to parse data for option $oname\n";
+ $err = 1;
+ next;
+ }
+
+ delete $before{$onum};
+ $after{$onum} = $odata;
+ push(@clear, $onum);
+ } elsif ($opt eq '-d' || $opt eq '--delete') {
+ $oname = shift(@ARGV);
+
+ if (!defined($oname)) {
+ $err = $usage = 1;
+ last;
+ }
+
+ $onum = option_number($oname);
+ if (!defined($onum)) {
+ print STDERR "$0: unknown option name: $oname\n";
+ $err = 1;
+ next;
+ }
+
+ push(@clear, $onum);
+ delete $before{$onum};
+ delete $after{$onum};
+ } elsif ($opt eq '-n' || $opt eq '--no-write' || $opt eq '--dry-run') {
+ $no_write = 1;
+ } elsif ($opt eq '-l' || $opt eq '--list') {
+ $list = 1;
+ } elsif ($opt eq '-h' || $opt eq '--help') {
+ $usage = 1;
+ } else {
+ print STDERR "Invalid option: $opt\n";
+ $err = $usage = 1;
+ }
+}
+
+if (!defined($file) && !$usage) {
+ $err = $usage = 1;
+}
+if ($usage) {
+ usage();
+}
+if ($err || $usage) {
+ exit($err);
+}
+
+if (!scalar(@clear)) {
+ $no_write = 1; # No modifications requested
+}
+
+$mode = $no_write ? '<' : '+<';
+
+open(FILE, $mode, $file)
+ or die "$0: cannot open: $file: $!\n";
+($patch_start, @data) = read_optsets(\*FILE);
+if (!defined($patch_start)) {
+ die "$0: $file: patch block not found or file corrupt\n";
+}
+
+foreach $o (@clear) {
+ $data[0] = delete_option($o, $data[0]);
+ $data[1] = delete_option($o, $data[1]);
+}
+foreach $o (keys(%before)) {
+ $data[0] = add_option($o, $before{$o}, $data[0]);
+}
+foreach $o (keys(%after)) {
+ $data[1] = add_option($o, $after{$o}, $data[1]);
+}
+
+if ($list) {
+ list_options('-b ', $data[0]);
+ list_options('-a ', $data[1]);
+}
+
+if (!$no_write) {
+ if (!write_optsets(\*FILE, $patch_start, @data)) {
+ die "$0: $file: failed to write options: $!\n";
+ }
+}
+
+close(FILE);
+exit 0;