Start work on partition support.
[pintos-anon] / src / utils / pintos
1 #! /usr/bin/perl -w
2
3 use strict;
4 use POSIX;
5
6 our ($mem) = 4;
7 our ($serial_out) = 1;
8 our ($disk) = "pintos.dsk";
9 our (@part_files) = ("boot.part", "filesys.part", "scratch.part", "swap.part");
10 our ($sim);
11 our ($debug);
12 our ($vga);
13 our ($jitter, $realtime);
14
15 our (%role2type) = (0 => 0x20, 1 => 0x21, 2 => 0x22, 3 => 0x23);
16
17 use Getopt::Long qw(:config require_order bundling);
18 unshift (@ARGV, split (' ', $ENV{PINTOSOPTS}))
19   if defined $ENV{PINTOSOPTS};
20 GetOptions ("sim=s" => sub { set_sim (@_) },
21             "bochs" => sub { set_sim ("bochs") },
22             "qemu" => sub { set_sim ("qemu") },
23             "gsx" => sub { set_sim ("gsx") },
24
25             "debug=s" => sub { set_debug (@_) },
26             "no-debug" => sub { set_debug ("no-debug") },
27             "monitor" => sub { set_debug ("monitor") },
28             "gdb" => sub { set_debug ("gdb") },
29
30             "run|get|put|assemble|disassemble" => \&cmd_option,
31
32             "m|memory=i" => \$mem,
33             "j|jitter=i" => sub { set_jitter (@_) },
34             "r|realtime" => sub { set_realtime () },
35
36             "v|no-vga" => sub { set_vga ('none'); },
37             "s|no-serial" => sub { $serial_out = 0; },
38             "t|terminal" => sub { set_vga ('terminal'); },
39
40             "h|help" => sub { usage (0); },
41
42             "disk=s" => \$disk,
43
44             "boot-partition=s" => \$part_files[0],
45             "fs-partition|filesys-partition=s" => \$part_files[1],
46             "scratch-partition=s" => \$part_files[2],
47             "swap-partition=s" => \$part_files[3]
48            )
49   or exit 1;
50
51 $sim = "bochs" if !defined $sim;
52 $debug = "no-debug" if !defined $debug;
53 $vga = "window" if !defined $vga;
54
55 sub set_sim {
56     my ($new_sim) = @_;
57     die "--$new_sim conflicts with --$sim\n"
58       if defined ($sim) && $sim ne $new_sim;
59     $sim = $new_sim;
60 }
61
62 sub set_debug {
63     my ($new_debug) = @_;
64     die "--$new_debug conflicts with --$debug\n"
65       if defined ($debug) && $debug ne $new_debug;
66     $debug = $new_debug;
67 }
68
69 sub set_vga {
70     my ($new_vga) = @_;
71     if (defined ($vga) && $vga ne $new_vga) {
72         print "warning: conflicting vga display options\n";
73     }
74     $vga = $new_vga;
75 }
76
77 sub set_jitter {
78     my ($new_jitter) = @_;
79     die "--realtime conflicts with --jitter\n" if defined $realtime;
80     die "different --jitter already defined\n"
81       if defined $jitter && $jitter != $new_jitter;
82     $jitter = $new_jitter;
83 }
84
85 sub set_realtime {
86     die "--realtime conflicts with --jitter\n" if defined $jitter;
87     $realtime = 1;
88 }
89
90 sub cmd_option {
91     # Force an end to option processing, as with --.
92     die ("!FINISH");
93 }
94
95 die "no command specified; use --help for help\n"
96   if @ARGV < 1;
97 my ($cmd) = shift @ARGV;
98 if ($cmd eq 'run') {
99     run_vm (@ARGV);
100 } elsif ($cmd eq 'put') {
101     # Take a -f option to combine formatting with putting.
102     my ($format) = 0;
103     if (@ARGV > 0 && $ARGV[0] eq '-f') {
104         shift @ARGV;
105         $format = 1;
106     }
107
108     usage () if @ARGV != 1 && @ARGV != 2;
109     my ($hostfn, $guestfn) = @ARGV;
110     $guestfn = $hostfn if !defined $guestfn;
111
112     # Disassemble.
113     @part_files = ("part0.tmp", "part1.tmp", "part2.tmp", "part3.tmp");
114     disassemble ($part_files[0], $part_files[1], undef, $part_files[3]);
115     die "missing file system partition\n" if ! -e $part_files[1];
116
117     # Create scratch disk from file.
118     open (FILE, "<$hostfn") or die "$hostfn: open: $!\n";
119     my ($scratchfn) = $part_files[1];
120     open (SCRATCH, ">$scratchfn") or die "$scratchfn: create: $!\n";
121     my ($size) = 0;
122     for (;;) {
123         my ($buf);
124         my ($amt) = sysread (FILE, $buf, 65536);
125         die "$hostfn: read: $!\n" if $amt < 0;
126         last if $amt == 0;
127         syswrite (FILE, $buf, $amt) == $amt or die "$scratchfn: write: $!\n";
128         $size += $amt;
129     }
130     my ($zeros) = 512 - $size % 512;
131     syswrite (FILE, "\0" x $zeros) == $zeros or die "$scratchfn: write: $!\n";
132     close (SCRATCH);
133     close (FILE);
134
135     # Reassemble.
136     assemble ();
137     unlink (@part_files);
138
139     # Do copy.
140     my (@cmd) = ("-ci", $guestfn, $size, "-q");
141     unshift (@cmd, "-f") if $format;
142     run_vm (@cmd);
143 } elsif ($cmd eq 'get') {
144     usage () if @ARGV != 1 && @ARGV != 2;
145     my ($guestfn, $hostfn) = @ARGV;
146     $hostfn = $guestfn if !defined $hostfn;
147     die "$hostfn: already exists\n" if -e $hostfn;
148
149     # Disassemble.
150     @part_files = ("part0.tmp", "part1.tmp", undef, "part3.tmp");
151     disassemble (@part_files);
152
153     # Create scratch disk big enough for any file in the file system
154     # (modulo sparse files).
155     die "missing file system partition\n" if ! -e $part_files[1];
156     my ($fs_size) = -s _;
157     my ($approx_mb) = (16 * 63 * 512) * 2;
158     $part_files[2] = sprintf ("%d",
159                               int (($fs_size + $approx_mb - 1) / $approx_mb));
160     assemble (@part_files);
161
162     # Do copy.
163     run_vm ("-co", $guestfn, "-q");
164
165     # FIXME: we could just read the parttbl, then copy directly.
166     # Disassemble.
167     my ($scratchfn) = "part2.tmp";
168     disassemble (undef, undef, $scratchfn, undef);
169
170     # Read out scratch disk.
171     print "copying $guestfn from $scratchfn to $hostfn...\n";
172     open (SRC, "<$scratchfn") or die "$scratchfn: open: $!\n";
173     open (DST, ">$hostfn") or die "$hostfn: create: $!\n";
174     my ($input);
175     read (SRC, $input, 512) == 512 or die "$scratchfn: read error\n";
176     my ($size) = unpack ("V", $input);
177     $size != 0xffffffff or die "$guestfn: too big for $scratchfn?";
178     my ($src);
179     read (SRC, $src, $size) == $size or die "$scratchfn: read error\n";
180     print DST $src or die "$hostfn: write error\n";
181     close (DST);
182     close (SRC);
183 } elsif ($cmd eq 'assemble') {
184     die "$part_files[0] not found ($!), but a boot partition is required\n"
185       if ! -e $part_files[0];
186     do { $_ = undef if ! -e $_ } foreach @part_files[1...3];
187     assemble ();
188 } elsif ($cmd eq 'help') {
189     usage (0);
190 } else {
191     die "unknown command `$cmd'; use --help for help\n";
192 }
193 exit 0;
194
195 sub usage {
196     my ($exitcode) = @_;
197     $exitcode = 1 unless defined $exitcode;
198     print "pintos, a utility for invoking Pintos in a simulator\n";
199     print "Usage: pintos [OPTION...] COMMAND [ARG...]\n";
200     print "where COMMAND is one of the following:\n";
201     print "  run [CMDLINE...]        run a VM in the simulator\n";
202     print "  put HOSTFN [GUESTFN]    copy HOSTFN into VM (as GUESTFN)\n";
203     print "  get GUESTFN [HOSTFN]    copy GUESTFN out of VM (to HOSTFN)\n";
204     print "  assemble                assemble a VM disk from partitions\n";
205     print "  disassemble             disassemble a VM disk into partitions\n";
206     print "  help                    print this help message and exit\n";
207     print "Simulator options:\n";
208     print "  --bochs          (default) Use Bochs as simulator\n";
209     print "  --qemu           Use qemu as simulator\n";
210     print "  --gsx            Use VMware GSX Server 3.x as simulator\n";
211     print "Debugger options:\n";
212     print "  --no-debug       (default) No debugger\n";
213     print "  --monitor        Debug with simulator's monitor\n";
214     print "  --gdb            Debug with gdb\n";
215     print "Display options: (default is VGA + serial)\n";
216     print "  -v, --no-vga     No VGA display\n";
217     print "  -s, --no-serial  No serial output\n";
218     print "  -t, --terminal   Display VGA in terminal (Bochs only)\n";
219     print "VM options:\n";
220     print "  -d, --disk=DISK  File holding VM's disk (default: pintos.dsk)\n";
221     print "  -j SEED          Randomize timer interrupts (Bochs only)\n";
222     print "  -r, --realtime   Use realistic, but not reproducible, timings\n";
223     print "  -m, --mem=MB     Run VM with MB megabytes of physical memory\n";
224     print "Assemble/disassemble partitions (default names in parentheses):\n";
225     print "(SOURCE is a file or a size in MB, for a blank partition.)\n";
226     print "  --boot=FILE      Boot partition (boot.part)\n";
227     print "  --filesys=SOURCE File system partition (filesys.part)\n";
228     print "  --scratch=SOURCE Scratch partition (scratch.part)\n";
229     print "  --swap=SOURCE    Swap partition (swap.part)\n";
230     exit $exitcode;
231 }
232
233 sub copy_pad {
234     my ($src, $dst, $blocksize) = @_;
235     run_command ("dd", "if=$src", "of=$dst", "bs=$blocksize", "conv=sync");
236 }
237
238 sub create_disk {
239     my ($disk, $kb) = @_;
240     run_command ("dd", "if=/dev/zero", "of=$disk", "bs=1024", "count=$kb");
241 }
242
243 sub run_vm {
244     my (@args) = @_;
245
246     our ($disk);
247     die "$disk: can't find OS disk\n" if ! -e $disk;
248
249     # FIXME
250     #     if (my ($project) = `pwd` =~ /\b(threads|userprog|vm|filesys)\b/) {
251     #   if ((grep ($project eq $_, qw (userprog vm filesys)))
252     #       && !defined ($disks[1])) {
253     #       print STDERR "warning: it looks like you're running the $project ";
254     #       print STDERR "project, but no file system disk is present\n";
255     #   }
256     #   if ($project eq 'vm' && !defined $disks[3]) {
257     #       print STDERR "warning: it looks like you're running the $project ";
258     #       print STDERR "project, but no swap disk is present\n";
259     #   }
260     #     }
261
262     write_cmd_line ($disks[0], @args);
263
264     if ($sim eq 'bochs') {
265         my ($bin);
266         if ($debug eq 'no-debug') {
267             $bin = 'bochs';
268         } elsif ($debug eq 'monitor') {
269             $bin = 'bochs-dbg';
270         } elsif ($debug eq 'gdb') {
271             $bin = 'bochs-gdb';
272         }
273
274         my ($bochsbin) = search_path ($bin);
275         my ($bochsshare) = "$bochsbin/../share/bochs";
276
277         open (BOCHSRC, ">bochsrc.txt") or die "bochsrc.txt: create: $!\n";
278         print BOCHSRC "romimage: file=$bochsshare/BIOS-bochs-latest, "
279           . "address=0xf0000\n";
280         print BOCHSRC "vgaromimage: $bochsshare/VGABIOS-lgpl-latest\n";
281         print BOCHSRC bochs_disk_line ("ata0-master", $disks[0]);
282         print BOCHSRC bochs_disk_line ("ata0-slave", $disks[1]);
283         print BOCHSRC "ata1: enabled=1, ioaddr1=0x170, ioaddr2=0x370, irq=15\n"
284           if defined ($disks[2]) || defined ($disks[3]);
285         print BOCHSRC bochs_disk_line ("ata1-master", $disks[2]);
286         print BOCHSRC bochs_disk_line ("ata1-slave", $disks[3]);
287         print BOCHSRC "boot: c\n";
288         print BOCHSRC "ips: 1000000\n";
289         if (!$realtime) {
290             print BOCHSRC "clock: sync=none, time0=0\n";
291         } else {
292             print BOCHSRC "clock: sync=realtime, time0=0\n";
293         }
294         print BOCHSRC "megs: $mem\n";
295         print BOCHSRC "log: bochsout.txt\n";
296         if ($vga ne 'terminal') {
297             print BOCHSRC "com1: enabled=1, dev=/dev/stdout\n"
298               if $serial_out;
299             print BOCHSRC "display_library: nogui\n"
300               if $vga eq 'none';
301         } else {
302             print BOCHSRC "display_library: term\n";
303         }
304         close (BOCHSRC);
305
306         my (@cmd) = ($bin, '-q');
307         push (@cmd, '-j', $jitter) if defined $jitter;
308         print join (' ', @cmd), "\n";
309         my ($exit) = xsystem (@cmd);
310         if (WIFEXITED ($exit)) {
311             # Bochs exited normally.
312             # Ignore the exit code; Bochs normally exits with status 1,
313             # which is weird.
314         } elsif (WIFSIGNALED ($exit)) {
315             die "Bochs died with signal ", WTERMSIG ($exit), "\n";
316         } else {
317             die "Bochs died: code $exit\n";
318         }
319     } elsif ($sim eq 'qemu') {
320         print "warning: qemu doesn't support --terminal\n"
321           if $vga eq 'terminal';
322         print "warning: qemu doesn't support jitter\n"
323           if defined $jitter;
324         my (@cmd) = ('qemu');
325         push (@cmd, '-hda', $disks[0]) if defined $disks[0];
326         push (@cmd, '-hdb', $disks[1]) if defined $disks[1];
327         push (@cmd, '-hdc', $disks[2]) if defined $disks[2];
328         push (@cmd, '-hdd', $disks[3]) if defined $disks[3];
329         push (@cmd, '-m', $mem);
330         push (@cmd, '-nographic') if $vga eq 'none';
331         push (@cmd, '-serial', 'stdio') if $serial_out && $vga ne 'none';
332         push (@cmd, '-S') if $debug eq 'monitor';
333         push (@cmd, '-s') if $debug eq 'gdb';
334         run_command (@cmd);
335     } elsif ($sim eq 'gsx') {
336         print "warning: VMware GSX Server doesn't support --$debug\n"
337           if $debug ne 'no-debug';
338         print "warning: VMware GSX Server doesn't support --no-vga\n"
339           if $vga eq 'none';
340         print "warning: VMware GSX Server doesn't support --terminal\n"
341           if $vga eq 'terminal';
342         print "warning: VMware GSX Server doesn't support jitter\n"
343           if defined $jitter;
344
345         open (VMX, ">pintos.vmx") or die "pintos.vmx: create: $!\n";
346         chmod 0777 & ~umask, "pintos.vmx";
347         print VMX "#! /usr/bin/vmware -G\n";
348         print VMX "config.version = 6\n";
349         print VMX "guestOS = \"linux\"\n";
350         print VMX "floppy0.present = FALSE\n";
351
352         unlink ("pintos.out");
353         print VMX "serial0.present = TRUE\n";
354         print VMX "serial0.fileType = \"file\"\n";
355         print VMX "serial0.fileName = \"pintos.out\"\n";
356
357         if (! -e 'null.bin') {
358             open (NULL, ">null.bin") or die "null.bin: create: $!\n";
359             close (NULL);
360         }
361
362         for (my ($i) = 0; $i < 4; $i++) {
363             my ($dsk) = $disks[$i];
364             next if !defined $dsk;
365
366             my ($pln) = $dsk;
367             $pln =~ s/\.dsk//;
368             $pln .= ".pln";
369
370             my ($device) = "ide" . int ($i / 2) . ":" . ($i % 2);
371             print VMX "\n$device.present = TRUE\n";
372             print VMX "$device.deviceType = \"plainDisk\"\n";
373             print VMX "$device.fileName = \"$pln\"\n";
374
375             my (%geom) = disk_geometry ($dsk);
376             open (PLN, ">$pln") or die "$pln: create: $!\n";
377             print PLN "DRIVETYPE        ide\n";
378             print PLN "#vm|VERSION      2\n";
379             print PLN "#vm|TOOLSVERSION 2\n";
380             print PLN "CYLINDERS        $geom{C}\n";
381             print PLN "HEADS            $geom{H}\n";
382             print PLN "SECTORS          $geom{S}\n";
383             print PLN "#vm|CAPACITY     $geom{CAPACITY}\n";
384             print PLN "ACCESS \"$dsk\" 0 $geom{CAPACITY}\n";
385             close (PLN);
386         }
387         close (VMX);
388
389         my ($vmx) = getcwd () . "/pintos.vmx";
390         system ("vmware-cmd -s register $vmx >&/dev/null");
391         system ("vmware-cmd $vmx stop hard >&/dev/null");
392         system ("vmware -l -G -x -q $vmx");
393         system ("vmware-cmd $vmx stop hard >&/dev/null");
394     }
395 }
396
397 sub relay_signal {
398     my ($pid, $signal) = @_;
399     kill $signal, $pid;
400     $SIG{$signal} = 'DEFAULT';
401     kill $signal, getpid ();
402 }
403
404 sub xsystem {
405     my ($pid) = fork;
406     if (!defined ($pid)) {
407         # Fork failed.
408         die "fork: $!\n";
409     } elsif (!$pid) {
410         # Running in child process.
411         exec (@_);
412         exit (1);
413     } else {
414         # Running in parent process.
415         local $SIG{INT} = sub { relay_signal ($pid, "INT"); };
416         local $SIG{TERM} = sub { relay_signal ($pid, "TERM"); };
417         waitpid ($pid, 0);
418         return $?;
419     }
420 }
421
422 sub write_cmd_line {
423     my ($disk, @args) = @_;
424
425     die "command line includes empty string" if grep (/^$/, @args);
426     my ($args) = join ("\0", @args) . "\0\0";
427     die "command line exceeds 128 bytes" if length ($args) > 128;
428     $args .= "\0" x (128 - length ($args));
429
430     print "writing command line to $disk...\n";
431     open (DISK, "+<$disk") or die "$disk: open: $!\n";
432     seek (DISK, 0x17e, 0) or die "$disk: seek: $!\n";
433     syswrite (DISK, $args) or die "$disk: write: $!\n";
434     close (DISK) or die "$disk: close: $!\n";
435 }
436
437 sub run_command {
438     print join (' ', @_), "\n";
439     die "command failed\n" if xsystem (@_);
440 }
441
442 sub search_path {
443     my ($target) = @_;
444     for my $dir (split (':', $ENV{PATH})) {
445         return $dir if -e "$dir/$target";
446     }
447     die "$target not in PATH\n";
448 }
449
450 sub bochs_disk_line {
451     my ($device, $file) = @_;
452     return "" if !defined $file;
453     my (%geom) = disk_geometry ($file);
454     return ("$device: type=disk, path=$file, mode=flat, "
455             . "cylinders=$geom{C}, heads=$geom{H}, spt=$geom{S}, "
456             . "translation=none\n");
457 }
458
459 sub disk_geometry {
460     my ($file) = @_;
461     my ($size) = -s $file;
462     die "$file: stat: $!\n" if !defined $size;
463     die "$file: size not a multiple of 512 bytes\n" if $size % 512;
464     my ($cylinders) = int ($size / (512 * 16 * 63));
465     $cylinders++ if $size % (512 * 16 * 63);
466
467     return (CAPACITY => $size / 512,
468             C => $cylinders,
469             H => 16,
470             S => 63);
471 }
472 \f
473 sub assemble {
474     my ($files) = @_;
475
476     my (@parts);
477
478     my (@part_names) = ("boot", "file system", "scratch", "swap");
479     my ($next_start) = 1;
480     for my $i (0..3) {
481         my (%part);
482
483         my ($name) = $part_names[$i];
484         my ($file) = $files[$i];
485         my ($size);
486         if (-e $file) {
487             $size = -s _;
488         } else {
489             if (($mb) = $file =~ /^\d+(\.\d+)?|\.\d+$/) {
490                 $size = $mb * 63 * 16 * 512;
491                 undef $file;
492             } else {
493                 die ("$file: stat: $!\n");
494             }
495         }
496
497         die "$name: not a multiple of 512 bytes in size\n"
498           if $size % 512;
499         my ($sector_cnt) = $size / 512;
500         my ($start) = $next_start;
501         $next_start += $sector_cnt;
502
503         push (@parts,
504               {ROLE => $i,
505                FILE => $file,
506                START = $start,
507                SECTORS => $sector_cnt});
508     }
509     die "Sorry, disk size (", ($sector_cnt * 512) / 1024 / 1024, " MB) "
510       . "exceeds limit (approx. 503 MB)\n"
511         if $sector_cnt > 1023 * 63 * 16;
512
513     my ($part_tbl) = "\0" x 446;
514     for my $p (@parts) {
515         my ($bootable) = $p->{ROLE} == 0 ? 0x80 : 0x00;
516         my (@start_chs) = linear_to_chs ($p->{START});
517         my ($type) = $role2type{$p->{ROLE}};
518         my (@end_chs) = linear_to_chs ($p->{START} + $p->{SECTORS} - 1);
519
520         my ($part_tbl_entry) = pack ("C CCC C CCC V V",
521                                      $bootable,
522                                      pack_chs (@start_chs),
523                                      $type,
524                                      pack_chs (@end_chs),
525                                      $p->{START}, $p->{SECTORS});
526         length ($part_tbl_entry) == 16 or die;
527         $part_tbl .= $part_tbl_entry;
528     }
529     $part_tbl .= "\0" x 16 while length ($part_tbl) < 510;
530     $part_tbl .= pack ("v", 0xaa55);
531     length ($part_tbl) == 512 or die;
532
533     our ($disk);
534     open (DISK, ">$disk") or die "$disk: create: $!\n";
535     syswrite (DISK, $part_tbl) == 512 or die "$disk: write: $!\n";
536
537     for my $p (@parts) {
538         $from_file = defined ($p->{FILE});
539         open (PART, "<$p->{FILE}") or die "$p->{FILE}: open: $!\n"
540           if $from_file;
541
542         my ($buf);
543         for (my ($ofs) = 0; $ofs < $p->{SECTORS}; $ofs += length ($buf)) {
544             my ($bytes_left) = ($p->{SECTORS} - $ofs) * 512;
545             my ($read_bytes) = $bytes_left > 16384 ? 16384 : $bytes_left;
546
547             if ($from_file) {
548                 my ($ret) = sysread (PART, $buf, $read_bytes);
549                 die "$p->{FILE}: read: $!\n" if $ret < 0;
550                 die "$p->{FILE}: unexpected end of file\n"
551                   if $ret != $read_bytes;
552             } else {
553                 $buf = "\0" x $read_bytes;
554             }
555
556             syswrite (DISK, $buf) == length ($buf)
557               or die "$p->{FILE}: write: $!\n"
558         }
559
560         close (PART) if $from_file;
561     }
562
563     close (DISK) or die "$disk: close: $!\n";
564 }
565
566 sub linear_to_chs {
567     my ($linear) = @_;
568
569     # We maintain these as constants.
570     my ($heads) = 16;
571     my ($sectors) = 63;
572     my ($sectors_per_cylinder) = $heads * sectors;
573
574     # Calculate C, H, S.
575     my ($c) = int ($linear / $sectors_per_cylinder);
576     my ($cylinder_ofs) = $linear % $sectors_per_cylinder;
577     my ($h) = int ($cylinder_ofs / $sectors);
578     my ($s) = $cylinder_ofs % $sectors;
579
580     die if $c > 1023 || $h > 15 || $s > 63;
581
582     return ($c, $h, $s);
583 }
584
585 sub pack_chs {
586     my ($c, $h, $s) = @_;
587     die if $c > 1023 || $h > 15 || $s > 63;
588
589     my ($pc, $ph, $ps) = ($h, $s | (($c & 0x300) >> 2), $c & 0xff);
590     die if $pc > 255 || $ph > 255 || ps > 255;
591
592     return ($pc, $ph, $ps);
593 }
594
595 sub read_part_tbl {
596     my ($part_tbl);
597     open (DISK, "<$disk") or die "$disk: open: $!\n";
598     sysread (DISK, $part_tbl, 512) == 512 or die "$disk: read: $!\n";
599     close (DISK);
600
601     my ($loader, @partitions, $signature);
602     ($loader, @partitions[0..3], $signature)
603       = unpack ("a446 (a16)4 v", $part_tbl);
604
605     die "$disk: invalid partition table signature\n" if $signature != 0xaa55;
606
607     my (@parts);
608     for my $partition (@partitions) {
609         my ($bootable, @start_chs_packed, $type, @end_chs_packed,
610             $start, $sector_cnt);
611         ($bootable, $start_chs_packed[0...2], $type, @end_chs_packed[0...2],
612          $start, $sector_cnt)
613           = unpack ("C CCC C CCC V V", $partition) or die;
614
615         my ($role) = (reverse (%role2type{$type})){$type};
616         next if !defined ($role);
617
618         push (@parts,
619               {ROLE => $1,
620                START => $start,
621                SECTORS => $sector_cnt});
622     }
623
624     return @parts;
625 }
626
627 sub disassemble {
628     my ($files) = @_;
629
630     open (DISK, "<$disk") or die "$disk: open: $!\n";
631     for my $p (read_part_tbl ()) {
632         use Fcntl 'SEEK_CUR';
633
634         my ($file) = $files[$p->{ROLE}];
635         next if !defined $file;
636
637         open (PART, ">$file") or die "$file: create: $!\n";
638         sysseek (DISK, $p->{START} * 512, SEEK_CUR) or die "$disk: seek: $!\n";
639
640         my ($buf);
641         for (my ($ofs) = 0; $ofs < $p->{SECTORS}; $ofs += length ($buf)) {
642             my ($bytes_left) = ($p->{SECTORS} - $ofs) * 512;
643             my ($read_bytes) = $bytes_left > 16384 ? 16384 : $bytes_left;
644
645             my ($ret) = sysread (DISK, $buf, $read_bytes);
646             die "$p->{FILE}: read: $!\n" if $ret < 0;
647             die "$p->{FILE}: unexpected end of file\n"
648               if $ret != $read_bytes;
649
650             syswrite (PART, $buf) == length ($buf)
651               or die "$p->{FILE}: write: $!\n";
652         }
653
654         close (PART) or die "$file: close: $!\n";
655     }
656     close (DISK);
657 }