summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorH. Peter Anvin <hpa@zytor.com>2008-09-05 14:49:42 -0700
committerH. Peter Anvin <hpa@zytor.com>2008-09-05 14:49:42 -0700
commit058dd028ac7d8ae217d1b3b00213751d63034195 (patch)
tree53bd1e95555370d939258873062db00c7d74bf7d
parent1eb0d6f7cfa7547470580d821ca815e20df4e93e (diff)
downloadsyslinux-058dd028ac7d8ae217d1b3b00213751d63034195.tar.gz
ISOLINUX: support for hybrid mode (CD-ROM/USB key)
Still a work in progress. Signed-off-by: H. Peter Anvin <hpa@zytor.com>
-rw-r--r--.gitignore1
-rw-r--r--core/isolinux.asm255
-rw-r--r--mbr/Makefile12
-rw-r--r--mbr/isohdpfx.S212
-rw-r--r--utils/Makefile6
-rw-r--r--utils/isohybrid.in165
6 files changed, 628 insertions, 23 deletions
diff --git a/.gitignore b/.gitignore
index d4724d89..7657c3d0 100644
--- a/.gitignore
+++ b/.gitignore
@@ -34,6 +34,7 @@
/linux/syslinux
/linux/syslinux-nomtools
/utils/gethostip
+/utils/isohybrid
/utils/mkdiskimage
/version.h
/version.mk
diff --git a/core/isolinux.asm b/core/isolinux.asm
index f93b24e6..060a3cf8 100644
--- a/core/isolinux.asm
+++ b/core/isolinux.asm
@@ -118,10 +118,13 @@ ImageDwords resd 1 ; isolinux.bin size, dwords
InitStack resd 1 ; Initial stack pointer (SS:SP)
DiskSys resw 1 ; Last INT 13h call
ImageSectors resw 1 ; isolinux.bin size, sectors
+GetlinsecPtr resw 1 ; The sector-read pointer
DiskError resb 1 ; Error code for disk I/O
-DriveNumber resb 1 ; CD-ROM BIOS drive number
+DriveNumber resb 1 ; CD-ROM BIOS drive number
ISOFlags resb 1 ; Flags for ISO directory search
RetryCount resb 1 ; Used for disk access retries
+bsSecPerTrack resw 1 ; Used in hybrid mode
+bsHeads resw 1 ; Used in hybrid mode
_spec_start equ $
@@ -225,12 +228,46 @@ bi_file: dd 0 ; LBA of boot file
bi_length: dd 0xdeadbeef ; Length of boot file
bi_csum: dd 0xdeadbeef ; Checksum of boot file
bi_reserved: times 10 dd 0xdeadbeef ; Reserved
+bi_end:
+
+ ; Custom entry point for the hybrid-mode disk.
+ ; The following values will have been pushed onto the
+ ; entry stack:
+ ; - CBIOS Heads
+ ; - CBIOS Sectors
+ ; - CBIOS flag
+ ; - DX (including drive number)
+ ; - DI
+ ; - ES
+ ; (top of stack)
+%ifndef DEBUG_MESSAGES
+_hybrid_signature:
+ dd 0x7078c0fb ; An arbitrary number...
+
+_start_hybrid:
+ pop ax
+ mov si,getlinsec_ebios
+ and ax,ax
+ jnz .ebios
+ mov si,getlinsec_cbios
+.ebios:
+ pop word [cs:bsSecPerTrack]
+ pop word [cs:bsHeads]
+
+ pop dx
+ pop di
+ pop es
+ jmp _start_common
+%endif
-_start1: mov [cs:InitStack],sp ; Save initial stack pointer
+_start1:
+ mov si,getlinsec_cdrom
+_start_common:
+ mov [cs:InitStack],sp ; Save initial stack pointer
mov [cs:InitStack+2],ss
xor ax,ax
mov ss,ax
- mov sp,StackBuf ; Set up stack
+ mov sp,StackBuf ; Set up stack
push es ; Save initial ES:DI -> $PnP pointer
push di
mov ds,ax
@@ -238,8 +275,10 @@ _start1: mov [cs:InitStack],sp ; Save initial stack pointer
mov fs,ax
mov gs,ax
sti
-
cld
+
+ mov [GetlinsecPtr],si
+
; Show signs of life
mov si,syslinux_banner
call writestr_early
@@ -253,7 +292,7 @@ _start1: mov [cs:InitStack],sp ; Save initial stack pointer
; 64-2048
;
initial_csum: xor edi,edi
- mov si,_start1
+ mov si,bi_end
mov cx,(SECTOR_SIZE-64) >> 2
.loop: lodsd
add edi,eax
@@ -285,6 +324,10 @@ initial_csum: xor edi,edi
; Other nonzero fields
inc word [dsp_sectors]
+ ; Are we just pretending to be a CD-ROM?
+ cmp word [GetlinsecPtr],getlinsec_cdrom
+ jne found_drive ; If so, no spec packet...
+
; Now figure out what we're actually doing
; Note: use passed-in DL value rather than 7Fh because
; at least some BIOSes will get the wrong value otherwise
@@ -333,7 +376,7 @@ found_drive:
%endif
; No such luck. Get the Boot Record Volume, assuming single
- ; session disk, and that we're the first entry in the chain
+ ; session disk, and that we're the first entry in the chain.
mov eax,17 ; Assumed address of BRV
mov bx,trackbuf
call getonesec
@@ -356,7 +399,7 @@ set_file:
found_file:
; Set up boot file sizes
mov eax,[bi_length]
- sub eax,SECTOR_SIZE-3
+ sub eax,SECTOR_SIZE-3 ; ... minus sector loaded
shr eax,2 ; bytes->dwords
mov [ImageDwords],eax ; boot file dwords
add eax,(2047 >> 2)
@@ -685,16 +728,191 @@ getonesec:
;
; Get linear sectors - EBIOS LBA addressing, 2048-byte sectors.
;
-; Note that we can't always do this as a single request, because at least
-; Phoenix BIOSes has a 127-sector limit. To be on the safe side, stick
-; to 32 sectors (64K) per request.
-;
; Input:
; EAX - Linear sector number
; ES:BX - Target buffer
; BP - Sector count
;
-getlinsec:
+getlinsec: jmp word [cs:GetlinsecPtr]
+
+%ifndef DEBUG_MESSAGES
+
+;
+; First, the variants that we use when actually loading off a disk
+; (hybrid mode.) These are adapted versions of the equivalent routines
+; in ldlinux.asm.
+;
+
+;
+; getlinsec_ebios:
+;
+; getlinsec implementation for floppy/HDD EBIOS (EDD)
+;
+getlinsec_ebios:
+ shl eax,2 ; Convert to HDD sectors
+ shl bp,2
+
+.loop:
+ push bp ; Sectors left
+.retry2:
+ call maxtrans ; Enforce maximum transfer size
+ movzx edi,bp ; Sectors we are about to read
+ mov cx,retry_count
+.retry:
+
+ ; Form DAPA on stack
+ push edx
+ push eax
+ push es
+ push bx
+ push di
+ push word 16
+ mov si,sp
+ pushad
+ mov dl,[DriveNumber]
+ push ds
+ push ss
+ pop ds ; DS <- SS
+ mov ah,42h ; Extended Read
+ int 13h
+ pop ds
+ popad
+ lea sp,[si+16] ; Remove DAPA
+ jc .error
+ pop bp
+ add eax,edi ; Advance sector pointer
+ sub bp,di ; Sectors left
+ shl di,9 ; 512-byte sectors
+ add bx,di ; Advance buffer pointer
+ and bp,bp
+ jnz .loop
+
+ ret
+
+.error:
+ ; Some systems seem to get "stuck" in an error state when
+ ; using EBIOS. Doesn't happen when using CBIOS, which is
+ ; good, since some other systems get timeout failures
+ ; waiting for the floppy disk to spin up.
+
+ pushad ; Try resetting the device
+ xor ax,ax
+ mov dl,[DriveNumber]
+ int 13h
+ popad
+ loop .retry ; CX-- and jump if not zero
+
+ ;shr word [MaxTransfer],1 ; Reduce the transfer size
+ ;jnz .retry2
+
+ ; Total failure. Try falling back to CBIOS.
+ mov word [GetlinsecPtr], getlinsec_cbios
+ ;mov byte [MaxTransfer],63 ; Max possibe CBIOS transfer
+
+ pop bp
+ jmp getlinsec_cbios.loop
+
+;
+; getlinsec_cbios:
+;
+; getlinsec implementation for legacy CBIOS
+;
+getlinsec_cbios:
+ shl eax,2 ; Convert to HDD sectors
+ shl bp,2
+
+.loop:
+ push edx
+ push eax
+ push bp
+ push bx
+
+ movzx esi,word [bsSecPerTrack]
+ movzx edi,word [bsHeads]
+ ;
+ ; Dividing by sectors to get (track,sector): we may have
+ ; up to 2^18 tracks, so we need to use 32-bit arithmetric.
+ ;
+ div esi
+ xor cx,cx
+ xchg cx,dx ; CX <- sector index (0-based)
+ ; EDX <- 0
+ ; eax = track #
+ div edi ; Convert track to head/cyl
+
+ ; We should test this, but it doesn't fit...
+ ; cmp eax,1023
+ ; ja .error
+
+ ;
+ ; Now we have AX = cyl, DX = head, CX = sector (0-based),
+ ; BP = sectors to transfer, SI = bsSecPerTrack,
+ ; ES:BX = data target
+ ;
+
+ call maxtrans ; Enforce maximum transfer size
+
+ ; Must not cross track boundaries, so BP <= SI-CX
+ sub si,cx
+ cmp bp,si
+ jna .bp_ok
+ mov bp,si
+.bp_ok:
+
+ shl ah,6 ; Because IBM was STOOPID
+ ; and thought 8 bits were enough
+ ; then thought 10 bits were enough...
+ inc cx ; Sector numbers are 1-based, sigh
+ or cl,ah
+ mov ch,al
+ mov dh,dl
+ mov dl,[DriveNumber]
+ xchg ax,bp ; Sector to transfer count
+ mov ah,02h ; Read sectors
+ mov bp,retry_count
+.retry:
+ pushad
+ int 13h
+ popad
+ jc .error
+.resume:
+ movzx ecx,al ; ECX <- sectors transferred
+ shl ax,9 ; Convert sectors in AL to bytes in AX
+ pop bx
+ add bx,ax
+ pop bp
+ pop eax
+ pop edx
+ add eax,ecx
+ sub bp,cx
+ jnz .loop
+ ret
+
+.error:
+ dec bp
+ jnz .retry
+
+ xchg ax,bp ; Sectors transferred <- 0
+ shr word [MaxTransfer],1
+ jnz .resume
+ jmp disk_error
+
+;
+; Truncate BP to MaxTransfer
+;
+maxtrans:
+ cmp bp,[MaxTransfer]
+ jna .ok
+ mov bp,[MaxTransfer]
+.ok: ret
+
+%endif
+
+;
+; This is the variant we use for real CD-ROMs:
+; LBA, 2K sectors, some special error handling.
+;
+getlinsec_cdrom:
mov si,dapa ; Load up the DAPA
mov [si+4],bx
mov bx,es
@@ -774,6 +992,7 @@ xint13: mov byte [RetryCount],retry_count
; kaboom: write a message and bail out. Wait for a user keypress,
; then do a hard reboot.
;
+disk_error:
kaboom:
RESET_STACK_AND_SEGS AX
mov si,err_bootfailed
@@ -833,11 +1052,13 @@ MaxTransfer dw 32 ; Max sectors per transfer
rl_checkpt equ $ ; Must be <= 800h
rl_checkpt_off equ ($-$$)
-;%ifndef DEPEND
-;%if rl_checkpt_off > 0x800
-;%error "Sector 0 overflow"
-;%endif
-;%endif
+%ifndef DEPEND
+%if rl_checkpt_off > 0x800
+; This only works for NASM 2.03+, but it's really nice then...
+%assign SPILL rl_checkpt_off-0x800
+%error Sector 0 overflow by SPILL bytes
+%endif
+%endif
; ----------------------------------------------------------------------------
; End of code and data that have to be in the first sector
diff --git a/mbr/Makefile b/mbr/Makefile
index 7952e60b..a57b3946 100644
--- a/mbr/Makefile
+++ b/mbr/Makefile
@@ -17,23 +17,25 @@
topdir = ..
include $(topdir)/MCONFIG.embedded
-all: mbr.bin gptmbr.bin
+all: mbr.bin gptmbr.bin isohdpfx.bin
.PRECIOUS: %.o
%.o: %.S
$(CC) $(SFLAGS) -Wa,-a=$*.lst -c -o $@ $<
-mbr.elf: mbr.o mbr.ld
+.PRECIOUS: %.elf
+%.elf: %.o mbr.ld
$(LD) $(LDFLAGS) -T mbr.ld -e _start -o $@ $<
mbr.bin: mbr.elf checksize.pl
$(OBJCOPY) -O binary $< $@
$(PERL) checksize.pl mbr.bin 440
-mbr_bin.c: mbr.bin
+isohdpfx.bin: isohdpfx.elf checksize.pl
+ $(OBJCOPY) -O binary $< $@
+ $(PERL) checksize.pl mbr.bin 432
-gptmbr.elf: gptmbr.o mbr.ld
- $(LD) $(LDFLAGS) -T mbr.ld -e _start -o $@ $<
+mbr_bin.c: mbr.bin
gptmbr.bin: gptmbr.elf checksize.pl
$(OBJCOPY) -O binary $< $@
diff --git a/mbr/isohdpfx.S b/mbr/isohdpfx.S
new file mode 100644
index 00000000..f2bc7dc6
--- /dev/null
+++ b/mbr/isohdpfx.S
@@ -0,0 +1,212 @@
+/* -----------------------------------------------------------------------
+ *
+ * Copyright 2007-2008 H. Peter Anvin - All Rights Reserved
+ *
+ * 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.
+ *
+ * ----------------------------------------------------------------------- */
+
+/*
+ * Modified MBR code used on an ISO image in hybrid mode.
+ *
+ * This doesn't follow the El Torito spec at all -- it is just a stub
+ * loader of a hard-coded offset, but that's good enough to load
+ * ISOLINUX.
+ */
+
+ .code16
+ .text
+
+HYBRID_MAGIC = 0x7078c0fb
+isolinux_hybrid_signature = 0x7c00+64
+isolinux_start_hybrid = 0x7c00+64+4
+
+ .globl bootsec
+/* Important: the top 6 words on the stack are passed to isolinux.bin */
+stack = 0x7c00
+driveno = (stack-6)
+sectors = (stack-8)
+heads = (stack-10)
+secpercyl = (stack-14)
+
+BIOS_page = 0x462
+
+ /* gas/ld has issues with doing this as absolute addresses... */
+ .section ".bootsec", "a", @nobits
+ .globl bootsec
+bootsec:
+ .space 512
+
+ .text
+ .globl _start
+_start:
+
+ cli
+ xorw %ax, %ax
+ movw %ax, %ds
+ movw %ax, %ss
+ movw $stack, %sp
+ movw %sp, %si
+ pushw %es /* es:di -> $PnP header */
+ pushw %di
+ pushw %dx /* dl -> drive number */
+ movw %ax, %es
+ sti
+ cld
+
+ /* Copy down to 0:0x600 */
+ movw $_start, %di
+ movw $(512/2), %cx
+ rep; movsw
+
+ ljmpw $0, $next
+
+next:
+ /* Check to see if we have EBIOS */
+ pushw %dx /* drive number */
+ movb $0x41, %ah /* %al == 0 already */
+ movw $0x55aa, %bx
+ xorw %cx, %cx
+ xorb %dh, %dh
+ stc
+ int $0x13
+ jc 1f
+ cmpw $0xaa55, %bx
+ jne 1f
+ shrw %cx /* Bit 0 = fixed disk subset */
+ jnc 1f
+
+ /* We have EBIOS; patch in the following code at
+ read_sector_cbios: movb $0x42, %ah ; jmp read_common */
+ movl $0xeb42b4+((read_common-read_sector_cbios-4) << 24), \
+ (read_sector_cbios)
+
+1:
+ popw %dx
+
+ /* Get (C)HS geometry */
+ movb $0x08, %ah
+ int $0x13
+ andw $0x3f, %cx /* Sector count */
+ pushw %cx /* Save sectors on the stack */
+ movzbw %dh, %ax /* dh = max head */
+ incw %ax /* From 0-based max to count */
+ pushw %ax /* Save heads on the stack */
+ mulw %cx /* Heads*sectors -> sectors per cylinder */
+
+ /* Save sectors/cylinder on the stack */
+ pushw %dx /* High word */
+ pushw %ax /* Low word */
+
+ /*
+ * Load sectors. We do this one at a time mostly to avoid
+ * pitfalls and to share code with the stock MBR code.
+ */
+ movw $0x7c00, %bx
+ movw $4, %cx /* Sector count */
+ movl (lba_offset), %eax
+
+2:
+ call read_sector
+ jc disk_error
+ incl %eax
+ addw $512, %bx
+ loopw 2b
+
+ /*
+ * Okay, that actually worked... update the stack pointer
+ * and jump into isolinux.bin...
+ */
+ cmpl $HYBRID_MAGIC,(isolinux_hybrid_signature)
+ jne bad_signature
+
+ cli
+ movw $heads, %sp
+ jmp isolinux_start_hybrid
+
+bad_signature:
+ call error
+ .ascii "isolinux.bin missing or corrupt.\r\n"
+
+/*
+ * read_sector: read a single sector pointed to by %eax to %es:%bx.
+ * CF is set on error. All registers saved.
+ */
+read_sector:
+ pushal
+ xorl %edx, %edx
+ pushl %edx /* MSW of LBA */
+ pushl %eax /* LSW of LBA */
+ pushw %es /* Buffer segment */
+ pushw %bx /* Buffer offset */
+ pushw $1 /* Sector count */
+ pushw $16 /* Size of packet */
+ movw %sp, %si
+
+ /* This chunk is skipped if we have ebios */
+ /* Do not clobber %eax before this chunk! */
+ /* This also relies on %bx and %edx as set up above. */
+read_sector_cbios:
+ divl (secpercyl)
+ shlb $6, %ah
+ movb %ah, %cl
+ movb %al, %ch
+ xchgw %dx, %ax
+ divb (sectors)
+ movb %al, %dh
+ orb %ah, %cl
+ incw %cx /* Sectors are 1-based */
+ movw $0x0201, %ax
+
+read_common:
+ movb (driveno), %dl
+ int $0x13
+ addw $16, %sp /* Drop DAPA */
+ popal
+ ret
+
+disk_error:
+ call error
+ .ascii "Operating system load error.\r\n"
+
+/*
+ * Print error messages. This is invoked with "call", with the
+ * error message at the return address.
+ */
+error:
+ popw %si
+2:
+ lodsb
+ movb $0x0e, %ah
+ movb (BIOS_page), %bh
+ movb $0x07, %bl
+ int $0x10 /* May destroy %bp */
+ cmpb $10, %al /* Newline? */
+ jne 2b
+
+ int $0x18 /* Boot failure */
+die:
+ hlt
+ jmp die
+
+ /* Address of pointer to isolinux.bin */
+lba_offset = _start+432
diff --git a/utils/Makefile b/utils/Makefile
index 64f8236f..de8c9992 100644
--- a/utils/Makefile
+++ b/utils/Makefile
@@ -20,7 +20,7 @@ include $(topdir)/MCONFIG
CFLAGS = -W -Wall -Os -fomit-frame-pointer -D_FILE_OFFSET_BITS=64
LDFLAGS = -O2 -s
-TARGETS = mkdiskimage gethostip
+TARGETS = mkdiskimage isohybrid gethostip
ASIS = keytab-lilo lss16toppm md5pass ppmtolss16 sha1pass syslinux2ansi
all: $(TARGETS)
@@ -32,6 +32,10 @@ mkdiskimage: mkdiskimage.in ../mbr/mbr.bin bin2hex.pl
$(PERL) bin2hex.pl < ../mbr/mbr.bin | cat mkdiskimage.in - > $@
chmod a+x $@
+isohybrid: isohybrid.in ../mbr/isohdpfx.bin bin2hex.pl
+ $(PERL) bin2hex.pl < ../mbr/isohdpfx.bin | cat isohybrid.in - > $@
+ chmod a+x $@
+
gethostip: gethostip.o
$(CC) $(LDFLAGS) -o $@ $^
diff --git a/utils/isohybrid.in b/utils/isohybrid.in
new file mode 100644
index 00000000..a0487ca0
--- /dev/null
+++ b/utils/isohybrid.in
@@ -0,0 +1,165 @@
+#!/usr/bin/perl
+## -----------------------------------------------------------------------
+##
+## Copyright 2002-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., 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.
+##
+## -----------------------------------------------------------------------
+
+#
+# Post-process an ISO 9660 image generated with mkisofs/genisoimage
+# to allow "hybrid booting" as a CD-ROM or as a hard disk.
+#
+
+use bytes;
+use integer;
+use Fcntl;
+use Errno;
+use Cwd;
+use IO::Handle; # For flush()
+
+# Use this fake geometry (zipdrive-style...)
+$h = 64; $s = 32;
+
+sub get_random() {
+ # Get a 32-bit random number
+ my $rfd, $rnd;
+ my $rid;
+
+ if (sysopen($rfd, '/dev/urandom', O_RDONLY) &&
+ sysread($rfd, $rnd, 4) == 4) {
+ $rid = unpack("V", $rnd);
+ }
+
+ close($rfd) if (defined($rfd));
+ return $rid if (defined($rid));
+
+ # This sucks but is better than nothing...
+ return ($$+time()) & 0xffffffff;
+}
+
+
+($file) = @ARGV;
+open(FILE, "+< $file\0") or die "$0: cannot open $file: $!\n";
+binmode FILE;
+
+#
+# First, actually figure out where mkisofs hid isolinux.bin
+#
+seek(FILE, 17*2048, SEEK_SET) or die "$0: $file: $!\n";
+read(FILE, $boot_record, 2048) == 2048 or die "$0: $file: read error\n";
+($br_sign, $br_cat_offset) = unpack("a71V", $boot_record);
+if ($br_sign ne ("\0CD001\1EL TORITO SPECIFICATION" . ("\0" x 41))) {
+ die "$0: $file: no boot record found\n";
+}
+seek(FILE, $br_cat_offset*2048, SEEK_SET) or die "$0: $file: $!\n";
+read(FILE, $boot_cat, 2048) == 2048 or die "$0: $file: read error\n";
+
+# We must have a Validation Entry followed by a Default Entry...
+# no fanciness allowed for the Hybrid mode [XXX: might relax this later]
+@ve = unpack("v16", $boot_cat);
+$cs = 0;
+for ($i = 0; $i < 16; $i++) {
+ $cs += $ve[$i];
+}
+if ($ve[0] != 0x0001 || $ve[15] != 0xaa55 || $cs & 0xffff) {
+ die "$0: $file: invalid boot catalog\n";
+}
+($de_boot, $de_media, $de_seg, $de_sys, $de_mbz1, $de_count,
+ $de_lba, $de_mbz2) = unpack("CCvCCvVv", substr($boot_cat, 32, 32));
+if ($de_boot != 0x88 || $de_media != 0 ||
+ ($de_segment != 0 && $de_segment != 0x7c0) || $de_count != 4) {
+ die "$0: $file: unexpected boot catalog parameters\n";
+}
+
+# Get the total size of the image
+(@imgstat = stat(FILE)) or die "$0: $file: $!\n";
+$imgsize = $imgstat[7];
+if (!$imgsize) {
+ die "$0: $file: cannot determine length of file\n";
+}
+# Target image size: round up to a multiple of $h*$s*512
+$cylsize = $h*$s*512;
+$frac = $imgsize % $cylsize;
+$padding = ($frac > 0) ? $cylsize - $frac : 0;
+$imgsize += $padding;
+$c = $imgsize/$cylsize;
+if ($c > 1024) {
+ print STDERR "Warning: more than 1024 cylinders ($c).\n";
+ print STDERR "Not all BIOSes will be able to boot this device.\n";
+ $cc = 1024;
+} else {
+ $cc = $c;
+}
+
+# Now $de_lba should contain the CD sector number for isolinux.bin
+seek(FILE, 0, SEEK_SET) or die "$0: $file: $!\n";
+
+# Print the MBR and partition table
+$mbr = '';
+while ( $line = <DATA> ) {
+ chomp $line;
+ foreach $byte ( split(/\s+/, $line) ) {
+ $mbr .= chr(hex($byte));
+ }
+}
+if ( length($mbr) > 432 ) {
+ die "$0: Bad MBR code\n";
+}
+
+$mbr .= "\0" x (432 - length($mbr));
+
+$mbr .= pack("VV", $de_lba*4, 0); # Offset 432: LBA of isolinux.bin
+if (defined($id)) {
+ $id = to_int($id);
+} else {
+ $id = get_random();
+}
+$mbr .= pack("V", $id); # Offset 440: MBR ID
+$mbr .= "\0\0"; # Offset 446: actual partition table
+
+# Print partition table
+$psize = $c*$h*$s-$s;
+$bhead = 0;
+$bsect = 1;
+$bcyl = 0;
+$ehead = $h-1;
+$esect = $s + ((($cc-1) & 0x300) >> 2);
+$ecyl = ($cc-1) & 0xff;
+$fstype = 0x83; # Linux (any better ideas?)
+$pentry = 1;
+if ( $c > 1024 ) {
+ $fstype = 0x0e;
+} elsif ( $psize > 65536 ) {
+ $fstype = 0x06;
+} else {
+ $fstype = 0x04;
+}
+for ( $i = 1 ; $i <= 4 ; $i++ ) {
+ if ( $i == $pentry ) {
+ $mbr .= pack("CCCCCCCCVV", 0x80, $bhead, $bsect, $bcyl, $fstype,
+ $ehead, $esect, $ecyl, $s, $psize);
+ } else {
+ $mbr .= "\0" x 16;
+ }
+}
+$mbr .= "\x55\xaa";
+
+print FILE $mbr;
+
+# Pad the image to a fake cylinder boundary
+seek(FILE, $imgstat[7], SEEK_SET) or die "$0: $file: $!\n";
+if ($padding) {
+ print FILE "\0" x $padding;
+}
+
+# Done...
+close(FILE);
+
+exit 0;
+__END__