Add support for "keyboard" input over the serial port.
[pintos-anon] / src / utils / pintos
index 31f4a38dc867950e0a52b8906d65e103a88d5504..c30633c93de2b9b175fb898c709ffb287c61208d 100755 (executable)
@@ -11,11 +11,12 @@ our ($start_time) = time ();
 our ($sim);                    # Simulator: bochs, qemu, or gsx.
 our ($debug) = "none";         # Debugger: none, monitor, or gdb.
 our ($mem) = 4;                        # Physical RAM in MB.
-our ($serial_out) = 1;         # Send output to serial port?
+our ($serial) = 1;             # Use serial port for input and output?
 our ($vga);                    # VGA output: window, terminal, or none.
 our ($jitter);                 # Seed for random timer interrupts, if set.
 our ($realtime);               # Synchronize timer interrupts with real time?
 our ($timeout);                        # Maximum runtime in seconds, if set.
+our ($kill_on_failure);                # Abort quickly on test failure?
 our (@puts);                   # Files to copy into the VM.
 our (@gets);                   # Files to copy out of the VM.
 our ($as_ref);                 # Reference to last addition to @gets or @puts.
@@ -58,10 +59,12 @@ sub parse_command_line {
                    "m|memory=i" => \$mem,
                    "j|jitter=i" => sub { set_jitter (@_) },
                    "r|realtime" => sub { set_realtime () },
+
                    "T|timeout=i" => \$timeout,
+                   "k|kill-on-failure" => \$kill_on_failure,
 
                    "v|no-vga" => sub { set_vga ('none'); },
-                   "s|no-serial" => sub { $serial_out = 0; },
+                   "s|no-serial" => sub { $serial = 0; },
                    "t|terminal" => sub { set_vga ('terminal'); },
 
                    "p|put-file=s" => sub { add_file (\@puts, $_[1]); },
@@ -86,8 +89,11 @@ sub parse_command_line {
     $debug = "none" if !defined $debug;
     $vga = "window" if !defined $vga;
 
-    print "warning: -T or --timeout should not be used with --$debug\n"
+    undef $timeout, print "warning: disabling timeout with --$debug\n"
       if defined ($timeout) && $debug ne 'none';
+
+    print "warning: enabling serial port for -k or --kill-on-failure\n"
+      if $kill_on_failure && !$serial;
 }
 
 # usage($exitcode).
@@ -109,14 +115,17 @@ Debugger selection:
   --monitor                Debug with simulator's monitor
   --gdb                    Debug with gdb
 Display options: (default is both VGA and serial)
-  -v, --no-vga             No VGA display
-  -s, --no-serial          No serial output
+  -v, --no-vga             No VGA display or keyboard
+  -s, --no-serial          No serial input or output
   -t, --terminal           Display VGA in terminal (Bochs only)
 Timing options: (Bochs only)
   -j SEED                  Randomize timer interrupts
   -r, --realtime           Use realistic, not reproducible, timings
+Testing options:
   -T, --timeout=N          Kill Pintos after N seconds CPU time or N*load_avg
                            seconds wall-clock time (whichever comes first)
+  -k, --kill-on-failure    Kill Pintos a few seconds after a kernel or user
+                           panic, test failure, or triple fault
 Configuration options:
   -m, --mem=N              Give Pintos N MB physical RAM (default: 4)
 File system commands (for `run' command):
@@ -369,6 +378,15 @@ sub run_bochs {
     # Select Bochs binary based on the chosen debugger.
     my ($bin) = $debug eq 'monitor' ? 'bochs-dbg' : 'bochs';
 
+    my ($squish_pty);
+    if ($serial) {
+       for my $dir (split (':', $ENV{PATH})) {
+           $squish_pty = "$dir/squish-pty", last if -x "$dir/squish-pty";
+       }
+       print "warning: can't find squish-pty, so terminal input will fail\n"
+         if !defined $squish_pty;
+    }
+
     # Write bochsrc.txt configuration file.
     open (BOCHSRC, ">", "bochsrc.txt") or die "bochsrc.txt: create: $!\n";
     print BOCHSRC <<EOF;
@@ -393,8 +411,10 @@ EOF
        print_bochs_disk_line ("ata1-slave", 3);
     }
     if ($vga ne 'terminal') {
-       print BOCHSRC "com1: enabled=1, mode=file, dev=/dev/stdout\n"
-         if $serial_out;
+       if ($serial) {
+           my $mode = defined ($squish_pty) ? "term" : "file";
+           print BOCHSRC "com1: enabled=1, mode=$mode, dev=/dev/stdout\n";
+       }
        print BOCHSRC "display_library: nogui\n" if $vga eq 'none';
     } else {
        print BOCHSRC "display_library: term\n";
@@ -403,6 +423,7 @@ EOF
 
     # Compose Bochs command line.
     my (@cmd) = ($bin, '-q');
+    unshift (@cmd, $squish_pty) if defined $squish_pty;
     push (@cmd, '-j', $jitter) if defined $jitter;
 
     # Run Bochs.
@@ -448,8 +469,9 @@ sub run_qemu {
          if defined $disks_by_iface[$iface]{FILE_NAME};
     }
     push (@cmd, '-m', $mem);
+    push (@cmd, '-net', 'none');
     push (@cmd, '-nographic') if $vga eq 'none';
-    push (@cmd, '-serial', 'stdio') if $serial_out && $vga ne 'none';
+    push (@cmd, '-serial', 'stdio') if $serial && $vga ne 'none';
     push (@cmd, '-S') if $debug eq 'monitor';
     push (@cmd, '-s', '-S') if $debug eq 'gdb';
     push (@cmd, '-monitor', 'null') if $vga eq 'none' && $debug eq 'none';
@@ -470,6 +492,7 @@ sub run_gsx {
     gsx_unsup ("--no-vga") if $vga eq 'none';
     gsx_unsup ("--terminal") if $vga eq 'terminal';
     gsx_unsup ("--jitter") if defined $jitter;
+    gsx_unsup ("--kill-on-failure") if defined $kill_on_failure;
 
     unlink ("pintos.out");
 
@@ -481,7 +504,9 @@ config.version = 6
 guestOS = "linux"
 floppy0.present = FALSE
 memsize = $mem
+EOF
 
+    print VMX <<EOF if $serial;
 serial0.present = TRUE
 serial0.fileType = "file"
 serial0.fileName = "pintos.out"
@@ -655,21 +680,76 @@ sub run_command {
 # Relays common signals to the subprocess.
 # If $timeout is set then the subprocess will be killed after that long.
 sub xsystem {
+    # qemu turns off local echo and does not restore it if killed by a signal.
+    # We compensate by restoring it ourselves.
+    my $cleanup = sub {};
+    if (isatty (0)) {
+       my $termios = POSIX::Termios->new;
+       $termios->getattr (0);
+       $cleanup = sub { $termios->setattr (0, &POSIX::TCSANOW); }
+    }
+
+    # Create pipe for filtering output.
+    pipe (my $in, my $out) or die "pipe: $!\n" if $kill_on_failure;
+
     my ($pid) = fork;
     if (!defined ($pid)) {
        # Fork failed.
        die "fork: $!\n";
     } elsif (!$pid) {
        # Running in child process.
+       dup2 (fileno ($out), STDOUT_FILENO) or die "dup2: $!\n"
+         if $kill_on_failure;
        exec_setitimer (@_);
     } else {
        # Running in parent process.
-       local $SIG{ALRM} = sub { timeout ($pid); };
-       local $SIG{INT} = sub { relay_signal ($pid, "INT"); };
-       local $SIG{TERM} = sub { relay_signal ($pid, "TERM"); };
+       close $out if $kill_on_failure;
+
+       my ($cause);
+       local $SIG{ALRM} = sub { timeout ($pid, $cause, $cleanup); };
+       local $SIG{INT} = sub { relay_signal ($pid, "INT", $cleanup); };
+       local $SIG{TERM} = sub { relay_signal ($pid, "TERM", $cleanup); };
        alarm ($timeout * get_load_average () + 1) if defined ($timeout);
-       waitpid ($pid, 0);
+
+       if ($kill_on_failure) {
+           # Filter output.
+           my ($buf) = "";
+           my ($boots) = 0;
+           local ($|) = 1;
+           for (;;) {
+               if (waitpid ($pid, WNOHANG) != 0) {
+                   # Subprocess died.  Pass through any remaining data.
+                   print $buf while sysread ($in, $buf, 4096) > 0;
+                   last;
+               }
+
+               # Read and print out pipe data.
+               my ($len) = length ($buf);
+               waitpid ($pid, 0), last
+                 if sysread ($in, $buf, 4096, $len) <= 0;
+               print substr ($buf, $len);
+
+               # Remove full lines from $buf and scan them for keywords.
+               while ((my $idx = index ($buf, "\n")) >= 0) {
+                   local $_ = substr ($buf, 0, $idx + 1, '');
+                   next if defined ($cause);
+                   if (/(Kernel PANIC|User process ABORT)/ ) {
+                       $cause = "\L$1\E";
+                       alarm (5);
+                   } elsif (/Pintos booting/ && ++$boots > 1) {
+                       $cause = "triple fault";
+                       alarm (5);
+                   } elsif (/FAILED/) {
+                       $cause = "test failure";
+                       alarm (5);
+                   }
+               }
+           }
+       } else {
+           waitpid ($pid, 0);
+       }
        alarm (0);
+       &$cleanup ();
 
        if (WIFSIGNALED ($?) && WTERMSIG ($?) == SIGVTALRM ()) {
            seek (STDOUT, 0, 2);
@@ -681,31 +761,38 @@ sub xsystem {
     }
 }
 
-# relay_signal($pid, $signal)
+# relay_signal($pid, $signal, &$cleanup)
 #
 # Relays $signal to $pid and then reinvokes it for us with the default
-# handler.  Also cleans up temporary files.
+# handler.  Also cleans up temporary files and invokes $cleanup.
 sub relay_signal {
-    my ($pid, $signal) = @_;
+    my ($pid, $signal, $cleanup) = @_;
     kill $signal, $pid;
-    File::Temp::cleanup();
+    eval { File::Temp::cleanup() };    # Not defined in old File::Temp.
+    &$cleanup ();
     $SIG{$signal} = 'DEFAULT';
     kill $signal, getpid ();
 }
 
-# timeout($pid)
+# timeout($pid, $cause, &$cleanup)
 #
-# Interrupts $pid and dies with a timeout error message.
+# Interrupts $pid and dies with a timeout error message,
+# after invoking $cleanup.
 sub timeout {
-    my ($pid) = @_;
+    my ($pid, $cause, $cleanup) = @_;
     kill "INT", $pid;
     waitpid ($pid, 0);
+    &$cleanup ();
     seek (STDOUT, 0, 2);
-    my ($load_avg) = `uptime` =~ /(load average:.*)$/i;
-    print "\nTIMEOUT after ", time () - $start_time,
-      " seconds of wall-clock time";
-    print  " - $load_avg" if defined $load_avg;
-    print "\n";
+    if (!defined ($cause)) {
+       my ($load_avg) = `uptime` =~ /(load average:.*)$/i;
+       print "\nTIMEOUT after ", time () - $start_time,
+         " seconds of wall-clock time";
+       print  " - $load_avg" if defined $load_avg;
+       print "\n";
+    } else {
+       print "Simulation terminated due to $cause.\n";
+    }
     exit 0;
 }