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 20use bytes; 21use 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 58sub 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) 74sub doh($) { 75 my($n) = @_; 76 return ($n =~ /^0/) ? oct $n : $n+0; 77} 78 79sub 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 95sub 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 108while ($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 129if (!defined($file)) { 130 usage(); 131} 132 133open(FILE, "+< $file\0") or die "$0: cannot open $file: $!\n"; 134binmode FILE; 135 136# 137# First, actually figure out where mkisofs hid isolinux.bin 138# 139seek(FILE, 17*2048, SEEK_SET) or die "$0: $file: $!\n"; 140read(FILE, $boot_record, 2048) == 2048 or die "$0: $file: read error\n"; 141($br_sign, $br_cat_offset) = unpack("a71V", $boot_record); 142if ($br_sign ne ("\0CD001\1EL TORITO SPECIFICATION" . ("\0" x 41))) { 143 die "$0: $file: no boot record found\n"; 144} 145seek(FILE, $br_cat_offset*2048, SEEK_SET) or die "$0: $file: $!\n"; 146read(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; 152for ($i = 0; $i < 16; $i++) { 153 $cs += $ve[$i]; 154} 155if ($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)); 160if ($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 166seek(FILE, $de_lba*2048+0x40, SEEK_SET) or die "$0: $file: $!\n"; 167read(FILE, $ibsig, 4); 168if ($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]; 176if (!$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); 187if ($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 196if (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 207seek(FILE, 0, SEEK_SET) or die "$0: $file: $!\n"; 208 209for ($i = 0; $i <= $opt{'hd0'}+3*$opt{'partok'}; $i++) { 210 $mbr = get_hex_data(); 211} 212if ( 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 236for ( $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 246print FILE $mbr; 247 248# Pad the image to a fake cylinder boundary 249seek(FILE, $imgstat[7], SEEK_SET) or die "$0: $file: $!\n"; 250if ($padding) { 251 print FILE "\0" x $padding; 252} 253 254# Done... 255close(FILE); 256 257exit 0; 258__END__ 259