#!/usr/bin/perl #!/usr/local/bin/perl use Getopt::Std; use FileHandle; use IPC::Open2; ####################################################################### # sdriver.pl - Shell driver # # Copyright (c) 2002, R. Bryant and D. O'Hallaron, All rights reserved. # May not be used, modified, or copied without permission. # # The driver runs a student's shell program as a child, sends # commands and signals to the child as directed by a trace file, # and captures and displays the output produced by the child. # # Tracefile format: # # The tracefile consists of text lines that are either blank lines, # comment lines, driver commands, or shell commands. Blank lines are # ignored. Comment lines begin with "#" and are echo'd without change # to stdout. Driver commands are intepreted by the driver and are not # passed to the child shell. All other lines are shell commands and # are passed without modification to the shell, which reads them on # stdin. Output produced by the child on stdout/stderr is read by # the parent and printed on its stdout. # # Driver commands: # TSTP Send a SIGTSTP signal to the child # INT Send a SIGINT signal to the child # QUIT Send a SIGQUIT signal to the child # KILL Send a SIGKILL signal to the child # CLOSE Close Writer (sends EOF signal to child) # WAIT Wait() for child to terminate # SLEEP Sleep for seconds # ###################################################################### # # usage - print help message and terminate # sub usage { printf STDERR "$_[0]\n"; printf STDERR "Usage: $0 [-hv] -t -s -a \n"; printf STDERR "Options:\n"; printf STDERR " -h Print this message\n"; printf STDERR " -v Be more verbose\n"; printf STDERR " -t Trace file\n"; printf STDERR " -s Shell program to test\n"; printf STDERR " -a Shell arguments\n"; printf STDERR " -g Generate output for autograder\n"; die "\n" ; } # Parse the command line arguments getopts('hgvt:s:a:'); if ($opt_h) { usage(); } if (!$opt_t) { usage("Missing required -t argument"); } if (!$opt_s) { usage("Missing required -s argument"); } $verbose = $opt_v; $infile = $opt_t; $shellprog = $opt_s; $shellargs = $opt_a; $grade = $opt_g; # Make sure the input script exists and is readable -e $infile or die "$0: ERROR: $infile not found\n"; -r $infile or die "$0: ERROR: $infile is not readable\n"; # Make sure the shell program exists and is executable -e $shellprog or die "$0: ERROR: $shellprog not found\n"; -x $shellprog or die "$0: ERROR: $shellprog is not executable\n"; # Open the input script open INFILE, $infile or die "$0: ERROR: Couldn't open input file $infile: $!\n"; # # Fork a child, run the shell in it, and connect the parent # and child with a pair of unidirectional pipes: # parent:Writer -> child:stdin # child:stdout -> parent:Reader # $pid = open2(\*Reader, \*Writer, "$shellprog $shellargs"); Writer->autoflush(); # The autograder will want to know the child shell's pid if ($grade) { print ("pid=$pid\n"); } # # Parent reads a trace file, sends commands to the child shell. # while () { $line = $_; chomp($line); # Comment line if ($line =~ /^#/) { print "$line\n"; } # Blank line elsif ($line =~ /^\s*$/) { if ($verbose) { print "$0: Ignoring blank line\n"; } } # Send SIGTSTP (ctrl-z) elsif ($line =~ /TSTP/) { if ($verbose) { print "$0: Sending SIGTSTP signal to process $pid\n"; } kill 'TSTP', $pid; } # Send SIGINT (ctrl-c) elsif ($line =~ /INT/) { if ($verbose) { print "$0: Sending SIGINT signal to process $pid\n"; } kill 'INT', $pid; } # Send SIGQUIT (whenever we need graceful termination) elsif ($line =~ /QUIT/) { if ($verbose) { print "$0: Sending SIGQUIT signal to process $pid\n"; } kill 'QUIT', $pid; } # Send SIGKILL elsif ($line =~ /KILL/) { if ($verbose) { print "$0: Sending SIGKILL signal to process $pid\n"; } kill 'KILL', $pid; } # Close pipe (sends EOF notification to child) elsif ($line =~ /CLOSE/) { if ($verbose) { print "$0: Closing output end of pipe to child $pid\n"; } close Writer; } # Wait for child to terminate elsif ($line =~ /WAIT/) { if ($verbose) { print "$0: Waiting for child $pid\n"; } wait; if ($verbose) { print "$0: Child $pid reaped\n"; } } # Sleep elsif ($line =~ /SLEEP (\d+)/) { if ($verbose) { print "$0: Sleeping $1 secs\n"; } sleep $1; } # Unknown input else { if ($verbose) { print "$0: Sending :$line: to child $pid\n"; } print Writer "$line\n"; } } # # Parent echoes the output produced by the child. # close Writer; if ($verbose) { print "$0: Reading data from child $pid\n"; } while ($line = ) { print $line; } close Reader; # Finally, parent reaps child wait; if ($verbose) { print "$0: Shell terminated\n"; } exit;