diff options
Diffstat (limited to 'sdriver.pl')
| -rwxr-xr-x | sdriver.pl | 210 |
1 files changed, 210 insertions, 0 deletions
diff --git a/sdriver.pl b/sdriver.pl new file mode 100755 index 0000000..852bf3d --- /dev/null +++ b/sdriver.pl @@ -0,0 +1,210 @@ +#!/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 <n> Sleep for <n> seconds +# +###################################################################### + +# +# usage - print help message and terminate +# +sub usage +{ + printf STDERR "$_[0]\n"; + printf STDERR "Usage: $0 [-hv] -t <trace> -s <shellprog> -a <args>\n"; + printf STDERR "Options:\n"; + printf STDERR " -h Print this message\n"; + printf STDERR " -v Be more verbose\n"; + printf STDERR " -t <trace> Trace file\n"; + printf STDERR " -s <shell> Shell program to test\n"; + printf STDERR " -a <args> 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 (<INFILE>) { + $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 = <Reader>) { + print $line; +} +close Reader; + +# Finally, parent reaps child +wait; + +if ($verbose) { + print "$0: Shell terminated\n"; +} + +exit; |
