+ 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) = @_;
+
+ print "Copying $get_file_name out of $disk_file_name...\n";
+
+ # Read ustar header sector.
+ my ($header) = read_fully ($disk_handle, $disk_file_name, 512);
+ return "scratch disk tar archive ends unexpectedly"
+ if $header eq ("\0" x 512);
+
+ # Verify magic numbers.
+ return "corrupt ustar signature" if substr ($header, 257, 6) ne "ustar\0";
+ return "invalid ustar version" if substr ($header, 263, 2) ne '00';
+
+ # Verify checksum.
+ my ($chksum) = oct (unpack ("Z*", substr ($header, 148, 8)));
+ my ($correct_chksum) = calc_ustar_chksum ($header);
+ return "checksum mismatch" if $chksum != $correct_chksum;
+
+ # Get type.
+ my ($typeflag) = substr ($header, 156, 1);
+ return "not a regular file" if $typeflag ne '0' && $typeflag ne "\0";
+
+ # Get size.
+ my ($size) = oct (unpack ("Z*", substr ($header, 124, 12)));
+ return "bad size $size\n" if $size < 0;
+
+ # Copy file data.
+ my ($get_handle);
+ sysopen ($get_handle, $get_file_name, O_WRONLY | O_CREAT, 0666)
+ or die "$get_file_name: create: $!\n";
+ copy_file ($disk_handle, $disk_file_name, $get_handle, $get_file_name,
+ $size);
+ close ($get_handle);
+
+ # Skip forward in disk up to beginning of next sector.
+ read_fully ($disk_handle, $disk_file_name, 512 - $size % 512)
+ if $size % 512;
+
+ return 0;
+}
+\f
+# Running simulators.
+
+# Runs the selected simulator.
+sub run_vm {
+ if ($sim eq 'bochs') {
+ run_bochs ();
+ } elsif ($sim eq 'qemu') {
+ run_qemu ();
+ } elsif ($sim eq 'player') {
+ run_player ();
+ } else {
+ die "unknown simulator `$sim'\n";