+# add_file(\@list, $file)
+#
+# Adds [$file] to @list, which should be @puts or @gets.
+# Sets $as_ref to point to the added element.
+sub add_file {
+ my ($list, $file) = @_;
+ $as_ref = [$file];
+ push (@$list, $as_ref);
+}
+
+# Sets the guest/host name for the previous put/get.
+sub set_as {
+ my ($as) = @_;
+ die "-a (or --as) is only allowed after -p or -g\n" if !defined $as_ref;
+ die "Only one -a (or --as) is allowed after -p or -g\n"
+ if defined $as_ref->[1];
+ $as_ref->[1] = $as;
+}
+
+# Sets $disk as a disk to be included in the VM to run.
+sub set_disk {
+ my ($disk) = @_;
+
+ push (@disks, $disk);
+
+ my (%pt) = read_partition_table ($disk);
+ for my $role (keys %pt) {
+ die "can't have two sources for \L$role\E partition"
+ if exists $parts{$role};
+ $parts{$role}{DISK} = $disk;
+ $parts{$role}{START} = $pt{$role}{START};
+ $parts{$role}{SECTORS} = $pt{$role}{SECTORS};
+ }
+}
+\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;
+ }