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