+\f
+# Locates the files used to back each of the virtual disks,
+# and creates temporary disks.
+sub find_disks {
+ # Find kernel, if we don't already have one.
+ if (!exists $parts{KERNEL}) {
+ my $name = find_file ('kernel.bin');
+ die "Cannot find kernel\n" if !defined $name;
+ do_set_part ('KERNEL', 'file', $name);
+ }
+
+ # Try to find file system and swap disks, if we don't already have
+ # partitions.
+ if (!exists $parts{FILESYS}) {
+ my $name = find_file ('filesys.dsk');
+ set_disk ($name) if defined $name;
+ }
+ if (!exists $parts{SWAP}) {
+ my $name = find_file ('swap.dsk');
+ set_disk ($name) if defined $name;
+ }
+
+ # Warn about (potentially) missing partitions.
+ if (my ($project) = `pwd` =~ /\b(threads|userprog|vm|filesys)\b/) {
+ if ((grep ($project eq $_, qw (userprog vm filesys)))
+ && !defined $parts{FILESYS}) {
+ print STDERR "warning: it looks like you're running the $project ";
+ print STDERR "project, but no file system partition is present\n";
+ }
+ if ($project eq 'vm' && !defined $parts{SWAP}) {
+ print STDERR "warning: it looks like you're running the $project ";
+ print STDERR "project, but no swap partition is present\n";
+ }
+ }
+
+ # Open disk handle.
+ my ($handle);
+ if (!defined $make_disk) {
+ ($handle, $make_disk) = tempfile (UNLINK => $tmp_disk,
+ SUFFIX => '.dsk');
+ } else {
+ die "$make_disk: already exists\n" if -e $make_disk;
+ open ($handle, '>', $make_disk) or die "$make_disk: create: $!\n";
+ }
+
+ # Prepare the arguments to pass to the Pintos kernel.
+ my (@args);
+ push (@args, shift (@kernel_args))
+ while @kernel_args && $kernel_args[0] =~ /^-/;
+ push (@args, 'extract') if @puts;
+ push (@args, @kernel_args);
+ push (@args, 'append', $_->[0]) foreach @gets;
+
+ # Make disk.
+ my (%disk);
+ our (@role_order);
+ for my $role (@role_order) {
+ my $p = $parts{$role};
+ next if !defined $p;
+ next if exists $p->{DISK};
+ $disk{$role} = $p;
+ }
+ $disk{DISK} = $make_disk;
+ $disk{HANDLE} = $handle;
+ $disk{ALIGN} = $align;
+ $disk{GEOMETRY} = %geometry;
+ $disk{FORMAT} = 'partitioned';
+ $disk{LOADER} = read_loader ($loader_fn);
+ $disk{ARGS} = \@args;
+ assemble_disk (%disk);
+
+ # Put the disk at the front of the list of disks.
+ unshift (@disks, $make_disk);
+ die "can't use more than " . scalar (@disks) . "disks\n" if @disks > 4;
+}
+\f
+# Prepare the scratch disk for gets and puts.
+sub prepare_scratch_disk {
+ return if !@gets && !@puts;
+
+ my ($p) = $parts{SCRATCH};
+ # Create temporary partition and write the files to put to it,
+ # then write an end-of-archive marker.
+ my ($part_handle, $part_fn) = tempfile (UNLINK => 1, SUFFIX => '.part');
+ put_scratch_file ($_->[0], defined $_->[1] ? $_->[1] : $_->[0],
+ $part_handle, $part_fn)
+ foreach @puts;
+ write_fully ($part_handle, $part_fn, "\0" x 1024);
+
+ # Make sure the scratch disk is big enough to get big files
+ # and at least as big as any requested size.
+ my ($size) = round_up (max (@gets * 1024 * 1024, $p->{BYTES} || 0), 512);
+ extend_file ($part_handle, $part_fn, $size);
+ close ($part_handle);
+
+ if (exists $p->{DISK}) {
+ # Copy the scratch partition to the disk.
+ die "$p->{DISK}: scratch partition too small\n"
+ if $p->{SECTORS} * 512 < $size;
+
+ my ($disk_handle);
+ open ($part_handle, '<', $part_fn) or die "$part_fn: open: $!\n";
+ open ($disk_handle, '+<', $p->{DISK}) or die "$p->{DISK}: open: $!\n";
+ my ($start) = $p->{START} * 512;
+ sysseek ($disk_handle, $start, SEEK_SET) == $start
+ or die "$p->{DISK}: seek: $!\n";
+ copy_file ($part_handle, $part_fn, $disk_handle, $p->{DISK}, $size);
+ close ($disk_handle) or die "$p->{DISK}: close: $!\n";
+ close ($part_handle) or die "$part_fn: close: $!\n";
+ } else {
+ # Set $part_fn as the source for the scratch partition.
+ do_set_part ('SCRATCH', 'file', $part_fn);
+ }
+}
+
+# Read "get" files from the scratch disk.
+sub finish_scratch_disk {
+ return if !@gets;
+
+ # Open scratch partition.
+ my ($p) = $parts{SCRATCH};
+ my ($part_handle);
+ my ($part_fn) = $p->{DISK};
+ open ($part_handle, '<', $part_fn) or die "$part_fn: open: $!\n";
+ sysseek ($part_handle, $p->{START} * 512, SEEK_SET) == $p->{START} * 512
+ or die "$part_fn: seek: $!\n";
+
+ # Read each file.
+ # If reading fails, delete that file and all subsequent files, but
+ # don't die with an error, because that's a guest error not a host
+ # error. (If we do exit with an error code, it fouls up the
+ # grading process.) Instead, just make sure that the host file(s)
+ # we were supposed to retrieve is unlinked.
+ my ($ok) = 1;
+ my ($part_end) = ($p->{START} + $p->{SECTORS}) * 512;
+ foreach my $get (@gets) {
+ my ($name) = defined ($get->[1]) ? $get->[1] : $get->[0];
+ if ($ok) {
+ my ($error) = get_scratch_file ($name, $part_handle, $part_fn);
+ if (!$error && sysseek ($part_handle, 0, SEEK_CUR) > $part_end) {
+ $error = "$part_fn: scratch data overflows partition";
+ }
+ if ($error) {
+ print STDERR "getting $name failed ($error)\n";
+ $ok = 0;
+ }
+ }
+ die "$name: unlink: $!\n" if !$ok && !unlink ($name) && !$!{ENOENT};
+ }
+}
+
+# mk_ustar_field($number, $size)
+#
+# Returns $number in a $size-byte numeric field in the format used by
+# the standard ustar archive header.
+sub mk_ustar_field {
+ my ($number, $size) = @_;
+ my ($len) = $size - 1;
+ my ($out) = sprintf ("%0${len}o", $number) . "\0";
+ die "$number: too large for $size-byte octal ustar field\n"
+ if length ($out) != $size;
+ return $out;
+}
+
+# calc_ustar_chksum($s)
+#
+# Calculates and returns the ustar checksum of 512-byte ustar archive
+# header $s.
+sub calc_ustar_chksum {
+ my ($s) = @_;
+ die if length ($s) != 512;
+ substr ($s, 148, 8, ' ' x 8);
+ return unpack ("%32a*", $s);
+}
+
+# put_scratch_file($src_file_name, $dst_file_name,
+# $disk_handle, $disk_file_name).
+#
+# Copies $src_file_name into $disk_handle for extraction as
+# $dst_file_name. $disk_file_name is used for error messages.
+sub put_scratch_file {
+ my ($src_file_name, $dst_file_name, $disk_handle, $disk_file_name) = @_;
+
+ print "Copying $src_file_name to scratch partition...\n";
+
+ # ustar format supports up to 100 characters for a file name, and
+ # even longer names given some common properties, but our code in
+ # the Pintos kernel only supports at most 99 characters.
+ die "$dst_file_name: name too long (max 99 characters)\n"
+ if length ($dst_file_name) > 99;
+
+ # Compose and write ustar header.
+ stat $src_file_name or die "$src_file_name: stat: $!\n";
+ my ($size) = -s _;
+ my ($header) = (pack ("a100", $dst_file_name) # name
+ . mk_ustar_field (0644, 8) # mode
+ . mk_ustar_field (0, 8) # uid
+ . mk_ustar_field (0, 8) # gid
+ . mk_ustar_field ($size, 12) # size
+ . mk_ustar_field (1136102400, 12) # mtime
+ . (' ' x 8) # chksum
+ . '0' # typeflag
+ . ("\0" x 100) # linkname
+ . "ustar\0" # magic
+ . "00" # version
+ . "root" . ("\0" x 28) # uname
+ . "root" . ("\0" x 28) # gname
+ . "\0" x 8 # devmajor
+ . "\0" x 8 # devminor
+ . ("\0" x 155)) # prefix
+ . "\0" x 12; # pad to 512 bytes
+ substr ($header, 148, 8) = mk_ustar_field (calc_ustar_chksum ($header), 8);
+ write_fully ($disk_handle, $disk_file_name, $header);
+
+ # Copy file data.
+ my ($put_handle);
+ sysopen ($put_handle, $src_file_name, O_RDONLY)
+ or die "$src_file_name: open: $!\n";
+ copy_file ($put_handle, $src_file_name, $disk_handle, $disk_file_name,
+ $size);
+ die "$src_file_name: changed size while being read\n"
+ if $size != -s $put_handle;
+ close ($put_handle);
+
+ # Round up disk data to beginning of next sector.
+ write_fully ($disk_handle, $disk_file_name, "\0" x (512 - $size % 512))
+ if $size % 512;
+}
+
+# get_scratch_file($get_file_name, $disk_handle, $disk_file_name)
+#
+# Copies from $disk_handle to $get_file_name (which is created).
+# $disk_file_name is used for error messages.
+# Returns 1 if successful, 0 on failure.
+sub get_scratch_file {
+ my ($get_file_name, $disk_handle, $disk_file_name) = @_;