211 lines
5.0 KiB
Perl
Executable File
211 lines
5.0 KiB
Perl
Executable File
#!/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;
|