+\f
+sub assemble {
+ my ($files) = @_;
+
+ my (@parts);
+
+ my (@part_names) = ("boot", "file system", "scratch", "swap");
+ my ($next_start) = 1;
+ for my $i (0..3) {
+ my (%part);
+
+ my ($name) = $part_names[$i];
+ my ($file) = $files[$i];
+ my ($size);
+ if (-e $file) {
+ $size = -s _;
+ } else {
+ if (($mb) = $file =~ /^\d+(\.\d+)?|\.\d+$/) {
+ $size = $mb * 63 * 16 * 512;
+ undef $file;
+ } else {
+ die ("$file: stat: $!\n");
+ }
+ }
+
+ die "$name: not a multiple of 512 bytes in size\n"
+ if $size % 512;
+ my ($sector_cnt) = $size / 512;
+ my ($start) = $next_start;
+ $next_start += $sector_cnt;
+
+ push (@parts,
+ {ROLE => $i,
+ FILE => $file,
+ START = $start,
+ SECTORS => $sector_cnt});
+ }
+ die "Sorry, disk size (", ($sector_cnt * 512) / 1024 / 1024, " MB) "
+ . "exceeds limit (approx. 503 MB)\n"
+ if $sector_cnt > 1023 * 63 * 16;
+
+ my ($part_tbl) = "\0" x 446;
+ for my $p (@parts) {
+ my ($bootable) = $p->{ROLE} == 0 ? 0x80 : 0x00;
+ my (@start_chs) = linear_to_chs ($p->{START});
+ my ($type) = $role2type{$p->{ROLE}};
+ my (@end_chs) = linear_to_chs ($p->{START} + $p->{SECTORS} - 1);
+
+ my ($part_tbl_entry) = pack ("C CCC C CCC V V",
+ $bootable,
+ pack_chs (@start_chs),
+ $type,
+ pack_chs (@end_chs),
+ $p->{START}, $p->{SECTORS});
+ length ($part_tbl_entry) == 16 or die;
+ $part_tbl .= $part_tbl_entry;
+ }
+ $part_tbl .= "\0" x 16 while length ($part_tbl) < 510;
+ $part_tbl .= pack ("v", 0xaa55);
+ length ($part_tbl) == 512 or die;
+
+ our ($disk);
+ open (DISK, ">$disk") or die "$disk: create: $!\n";
+ syswrite (DISK, $part_tbl) == 512 or die "$disk: write: $!\n";
+
+ for my $p (@parts) {
+ $from_file = defined ($p->{FILE});
+ open (PART, "<$p->{FILE}") or die "$p->{FILE}: open: $!\n"
+ if $from_file;
+
+ my ($buf);
+ for (my ($ofs) = 0; $ofs < $p->{SECTORS}; $ofs += length ($buf)) {
+ my ($bytes_left) = ($p->{SECTORS} - $ofs) * 512;
+ my ($read_bytes) = $bytes_left > 16384 ? 16384 : $bytes_left;
+
+ if ($from_file) {
+ my ($ret) = sysread (PART, $buf, $read_bytes);
+ die "$p->{FILE}: read: $!\n" if $ret < 0;
+ die "$p->{FILE}: unexpected end of file\n"
+ if $ret != $read_bytes;
+ } else {
+ $buf = "\0" x $read_bytes;
+ }
+
+ syswrite (DISK, $buf) == length ($buf)
+ or die "$p->{FILE}: write: $!\n"
+ }
+
+ close (PART) if $from_file;
+ }
+
+ close (DISK) or die "$disk: close: $!\n";
+}
+
+sub linear_to_chs {
+ my ($linear) = @_;
+
+ # We maintain these as constants.
+ my ($heads) = 16;
+ my ($sectors) = 63;
+ my ($sectors_per_cylinder) = $heads * sectors;
+
+ # Calculate C, H, S.
+ my ($c) = int ($linear / $sectors_per_cylinder);
+ my ($cylinder_ofs) = $linear % $sectors_per_cylinder;
+ my ($h) = int ($cylinder_ofs / $sectors);
+ my ($s) = $cylinder_ofs % $sectors;
+
+ die if $c > 1023 || $h > 15 || $s > 63;
+
+ return ($c, $h, $s);
+}
+
+sub pack_chs {
+ my ($c, $h, $s) = @_;
+ die if $c > 1023 || $h > 15 || $s > 63;
+
+ my ($pc, $ph, $ps) = ($h, $s | (($c & 0x300) >> 2), $c & 0xff);
+ die if $pc > 255 || $ph > 255 || ps > 255;
+
+ return ($pc, $ph, $ps);
+}
+
+sub read_part_tbl {
+ my ($part_tbl);
+ open (DISK, "<$disk") or die "$disk: open: $!\n";
+ sysread (DISK, $part_tbl, 512) == 512 or die "$disk: read: $!\n";
+ close (DISK);
+
+ my ($loader, @partitions, $signature);
+ ($loader, @partitions[0..3], $signature)
+ = unpack ("a446 (a16)4 v", $part_tbl);
+
+ die "$disk: invalid partition table signature\n" if $signature != 0xaa55;
+
+ my (@parts);
+ for my $partition (@partitions) {
+ my ($bootable, @start_chs_packed, $type, @end_chs_packed,
+ $start, $sector_cnt);
+ ($bootable, $start_chs_packed[0...2], $type, @end_chs_packed[0...2],
+ $start, $sector_cnt)
+ = unpack ("C CCC C CCC V V", $partition) or die;
+
+ my ($role) = (reverse (%role2type{$type})){$type};
+ next if !defined ($role);
+
+ push (@parts,
+ {ROLE => $1,
+ START => $start,
+ SECTORS => $sector_cnt});
+ }
+
+ return @parts;
+}
+
+sub disassemble {
+ my ($files) = @_;
+
+ open (DISK, "<$disk") or die "$disk: open: $!\n";
+ for my $p (read_part_tbl ()) {
+ use Fcntl 'SEEK_CUR';
+
+ my ($file) = $files[$p->{ROLE}];
+ next if !defined $file;
+
+ open (PART, ">$file") or die "$file: create: $!\n";
+ sysseek (DISK, $p->{START} * 512, SEEK_CUR) or die "$disk: seek: $!\n";
+
+ my ($buf);
+ for (my ($ofs) = 0; $ofs < $p->{SECTORS}; $ofs += length ($buf)) {
+ my ($bytes_left) = ($p->{SECTORS} - $ofs) * 512;
+ my ($read_bytes) = $bytes_left > 16384 ? 16384 : $bytes_left;
+
+ my ($ret) = sysread (DISK, $buf, $read_bytes);
+ die "$p->{FILE}: read: $!\n" if $ret < 0;
+ die "$p->{FILE}: unexpected end of file\n"
+ if $ret != $read_bytes;
+
+ syswrite (PART, $buf) == length ($buf)
+ or die "$p->{FILE}: write: $!\n";
+ }
+
+ close (PART) or die "$file: close: $!\n";
+ }
+ close (DISK);
+}