Home | History | Annotate | Download | only in utils
      1 #!/usr/bin/perl
      2 ## -----------------------------------------------------------------------
      3 ##
      4 ##   Copyright 2002-2008 H. Peter Anvin - All Rights Reserved
      5 ##   Copyright 2009 Intel Corporation; author: H. Peter Anvin
      6 ##
      7 ##   This program is free software; you can redistribute it and/or modify
      8 ##   it under the terms of the GNU General Public License as published by
      9 ##   the Free Software Foundation, Inc., 53 Temple Place Ste 330,
     10 ##   Boston MA 02111-1307, USA; either version 2 of the License, or
     11 ##   (at your option) any later version; incorporated herein by reference.
     12 ##
     13 ## -----------------------------------------------------------------------
     14 
     15 #
     16 # Post-process an ISO 9660 image generated with mkisofs/genisoimage
     17 # to allow "hybrid booting" as a CD-ROM or as a hard disk.
     18 #
     19 
     20 use bytes;
     21 use Fcntl;
     22 
     23 # User-specifyable options
     24 %opt = (
     25     # Fake geometry (zipdrive-style...)
     26     'h'      => 64,
     27     's'      => 32,
     28     # Partition number
     29     'entry'  => 1,
     30     # Partition offset
     31     'offset' => 0,
     32     # Partition type
     33     'type'   => 0x17,		# "Windows hidden IFS"
     34     # MBR ID
     35     'id'     => undef,
     36 );
     37 
     38 %valid_range = (
     39     'h'      => [1, 256],
     40     's'      => [1, 63],
     41     'entry'  => [1, 4],
     42     'offset' => [0, 64],
     43     'type'   => [0, 255],
     44     'id'     => [0, 0xffffffff],
     45     'hd0'    => [0, 2],
     46     'partok' => [0, 1],
     47 );
     48 
     49 # Boolean options just set other options
     50 %bool_opt = (
     51     'nohd0'    => ['hd0', 0],
     52     'forcehd0' => ['hd0', 1],
     53     'ctrlhd0'  => ['hd0', 2],
     54     'nopartok' => ['partok', 0],
     55     'partok'   => ['partok', 1],
     56 );
     57 
     58 sub usage() {
     59     print STDERR "Usage: $0 [options] filename.iso\n",
     60     "Options:\n",
     61     "  -h          Number of default geometry heads\n",
     62     "  -s          Number of default geometry sectors\n",
     63     "  -entry      Specify partition entry number (1-4)\n",
     64     "  -offset     Specify partition offset (default 0)\n",
     65     "  -type       Specify partition type (default 0x17)\n",
     66     "  -id         Specify MBR ID (default random)\n",
     67     "  -forcehd0   Always assume we are loaded as disk ID 0\n",
     68     "  -ctrlhd0    Assume disk ID 0 if the Ctrl key is pressed\n",
     69     "  -partok     Allow booting from within a partition\n";
     70     exit 1;
     71 }
     72 
     73 # Parse a C-style integer (decimal/octal/hex)
     74 sub doh($) {
     75     my($n) = @_;
     76     return ($n =~ /^0/) ? oct $n : $n+0;
     77 }
     78 
     79 sub get_random() {
     80     # Get a 32-bit random number
     81     my $rfd, $rnd;
     82     my $rid;
     83 
     84     if (open($rfd, "< /dev/urandom\0") && read($rfd, $rnd, 4) == 4) {
     85 	$rid = unpack("V", $rnd);
     86     }
     87 
     88     close($rfd) if (defined($rfd));
     89     return $rid if (defined($rid));
     90 
     91     # This sucks but is better than nothing...
     92     return ($$+time()) & 0xffffffff;
     93 }
     94 
     95 sub get_hex_data() {
     96     my $mbr = '';
     97     my $line, $byte;
     98     while ( $line = <DATA> ) {
     99 	chomp $line;
    100 	last if ($line eq '*');
    101 	foreach $byte ( split(/\s+/, $line) ) {
    102 	    $mbr .= chr(hex($byte));
    103 	}
    104     }
    105     return $mbr;
    106 }
    107 
    108 while ($ARGV[0] =~ /^\-(.*)$/) {
    109     $o = $1;
    110     shift @ARGV;
    111     if (defined($bool_opt{$o})) {
    112 	($o, $v) = @{$bool_opt{$o}};
    113 	$opt{$o} = $v;
    114     } elsif (exists($opt{$o})) {
    115 	$opt{$o} = doh(shift @ARGV);
    116 	if (defined($valid_range{$o})) {
    117 	    ($l, $h) = @{$valid_range{$o}};
    118 	    if ($opt{$o} < $l || $opt{$o} > $h) {
    119 		die "$0: valid values for the -$o parameter are $l to $h\n";
    120 	    }
    121 	}
    122     } else {
    123 	usage();
    124     }
    125 }
    126 
    127 ($file) = @ARGV;
    128 
    129 if (!defined($file)) {
    130     usage();
    131 }
    132 
    133 open(FILE, "+< $file\0") or die "$0: cannot open $file: $!\n";
    134 binmode FILE;
    135 
    136 #
    137 # First, actually figure out where mkisofs hid isolinux.bin
    138 #
    139 seek(FILE, 17*2048, SEEK_SET) or die "$0: $file: $!\n";
    140 read(FILE, $boot_record, 2048) == 2048 or die "$0: $file: read error\n";
    141 ($br_sign, $br_cat_offset) = unpack("a71V", $boot_record);
    142 if ($br_sign ne ("\0CD001\1EL TORITO SPECIFICATION" . ("\0" x 41))) {
    143     die "$0: $file: no boot record found\n";
    144 }
    145 seek(FILE, $br_cat_offset*2048, SEEK_SET) or die "$0: $file: $!\n";
    146 read(FILE, $boot_cat, 2048) == 2048 or die "$0: $file: read error\n";
    147 
    148 # We must have a Validation Entry followed by a Default Entry...
    149 # no fanciness allowed for the Hybrid mode [XXX: might relax this later]
    150 @ve = unpack("v16", $boot_cat);
    151 $cs = 0;
    152 for ($i = 0; $i < 16; $i++) {
    153     $cs += $ve[$i];
    154 }
    155 if ($ve[0] != 0x0001 || $ve[15] != 0xaa55 || $cs & 0xffff) {
    156     die "$0: $file: invalid boot catalog\n";
    157 }
    158 ($de_boot, $de_media, $de_seg, $de_sys, $de_mbz1, $de_count, 
    159  $de_lba, $de_mbz2) = unpack("CCvCCvVv", substr($boot_cat, 32, 32));
    160 if ($de_boot != 0x88 || $de_media != 0 ||
    161     ($de_segment != 0 && $de_segment != 0x7c0) || $de_count != 4) {
    162     die "$0: $file: unexpected boot catalog parameters\n";
    163 }
    164 
    165 # Now $de_lba should contain the CD sector number for isolinux.bin
    166 seek(FILE, $de_lba*2048+0x40, SEEK_SET) or die "$0: $file: $!\n";
    167 read(FILE, $ibsig, 4);
    168 if ($ibsig ne "\xfb\xc0\x78\x70") {
    169     die "$0: $file: bootloader does not have a isolinux.bin hybrid signature.".
    170         "Note that isolinux-debug.bin does not support hybrid booting.\n";
    171 }
    172 
    173 # Get the total size of the image
    174 (@imgstat = stat(FILE)) or die "$0: $file: $!\n";
    175 $imgsize = $imgstat[7];
    176 if (!$imgsize) {
    177     die "$0: $file: cannot determine length of file\n";
    178 }
    179 # Target image size: round up to a multiple of $h*$s*512
    180 $h = $opt{'h'};
    181 $s = $opt{'s'};
    182 $cylsize = $h*$s*512;
    183 $frac = $imgsize % $cylsize;
    184 $padding = ($frac > 0) ? $cylsize - $frac : 0;
    185 $imgsize += $padding;
    186 $c = int($imgsize/$cylsize);
    187 if ($c > 1024) {
    188     print STDERR "Warning: more than 1024 cylinders ($c).\n";
    189     print STDERR "Not all BIOSes will be able to boot this device.\n";
    190     $cc = 1024;
    191 } else {
    192     $cc = $c;
    193 }
    194 
    195 # Preserve id when run again
    196 if (defined($opt{'id'})) {
    197     $id = pack("V", doh($opt{'id'}));
    198 } else {
    199     seek(FILE, 440, SEEK_SET) or die "$0: $file: $!\n";
    200     read(FILE, $id, 4);
    201     if ($id eq "\x00\x00\x00\x00") {
    202 	$id = pack("V", get_random());
    203     }
    204 }
    205 
    206 # Print the MBR and partition table
    207 seek(FILE, 0, SEEK_SET) or die "$0: $file: $!\n";
    208 
    209 for ($i = 0; $i <= $opt{'hd0'}+3*$opt{'partok'}; $i++) {
    210     $mbr = get_hex_data();
    211 }
    212 if ( length($mbr) > 432 ) {
    213     die "$0: Bad MBR code\n";
    214 }
    215 
    216 $mbr .= "\0" x (432 - length($mbr));
    217 
    218 $mbr .= pack("VV", $de_lba*4, 0); 	# Offset 432: LBA of isolinux.bin
    219 $mbr .= $id;				# Offset 440: MBR ID
    220 $mbr .= "\0\0";				# Offset 446: actual partition table
    221 
    222 # Print partition table
    223 $offset  = $opt{'offset'};
    224 $psize   = $c*$h*$s - $offset;
    225 $bhead   = int($offset/$s) % $h;
    226 $bsect   = ($offset % $s) + 1;
    227 $bcyl    = int($offset/($h*$s));
    228 $bsect  += ($bcyl & 0x300) >> 2;
    229 $bcyl   &= 0xff;
    230 $ehead   = $h-1;
    231 $esect   = $s + ((($cc-1) & 0x300) >> 2);
    232 $ecyl    = ($cc-1) & 0xff;
    233 $fstype  = $opt{'type'};	# Partition type
    234 $pentry  = $opt{'entry'};	# Partition slot
    235 
    236 for ( $i = 1 ; $i <= 4 ; $i++ ) {
    237     if ( $i == $pentry ) {
    238 	$mbr .= pack("CCCCCCCCVV", 0x80, $bhead, $bsect, $bcyl, $fstype,
    239 		     $ehead, $esect, $ecyl, $offset, $psize);
    240     } else {
    241 	$mbr .= "\0" x 16;
    242     }
    243 }
    244 $mbr .= "\x55\xaa";
    245 
    246 print FILE $mbr;
    247 
    248 # Pad the image to a fake cylinder boundary
    249 seek(FILE, $imgstat[7], SEEK_SET) or die "$0: $file: $!\n";
    250 if ($padding) {
    251     print FILE "\0" x $padding;
    252 }
    253 
    254 # Done...
    255 close(FILE);
    256 
    257 exit 0;
    258 __END__
    259