#!/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 this fake geometry (zipdrive-style...) $h = 64; $s = 32; sub get_random() { # Get a 32-bit random number my $rfd, $rnd; my $rid; if (open($rfd, "< /dev/urandom\0") && read($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"; } # Now $de_lba should contain the CD sector number for isolinux.bin seek(FILE, $de_lba*2048+0x40, SEEK_SET) or die "$0: $file: $!\n"; read(FILE, $ibsig, 4); if ($ibsig ne "\xfb\xc0\x78\x70") { die "$0: $file: bootloader is missing isolinux.bin hybrid signature\n". "Note: isolinux-debug.bin does not support hybrid booting\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; } # Print the MBR and partition table seek(FILE, 0, SEEK_SET) or die "$0: $file: $!\n"; $mbr = ''; while ( $line = ) { 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; $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; # First partition slot for ( $i = 1 ; $i <= 4 ; $i++ ) { if ( $i == $pentry ) { $mbr .= pack("CCCCCCCCVV", 0x80, $bhead, $bsect, $bcyl, $fstype, $ehead, $esect, $ecyl, 0, $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__