commit 9f8a7b6c89cceb3edebdff8e6775c20f48db12a1 Author: TuDatTr Date: Mon Feb 1 10:58:57 2021 +0100 Initial commit diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..447d132 --- /dev/null +++ b/Makefile @@ -0,0 +1,88 @@ +# Makefile for the CS:APP Shell Lab + +TEAM = NOBODY +VERSION = 1 +DRIVER = ./sdriver.pl +TSH = ./tsh +TSHREF = ./tshref +TSHARGS = "-p" +CC = gcc +CFLAGS = -Wall -O2 +FILES = $(TSH) ./myspin ./mysplit ./mystop ./myint + +all: $(FILES) + +################## +# Regression tests +################## + +# Run tests using the student's shell program +test01: + $(DRIVER) -t trace01.txt -s $(TSH) -a $(TSHARGS) +test02: + $(DRIVER) -t trace02.txt -s $(TSH) -a $(TSHARGS) +test03: + $(DRIVER) -t trace03.txt -s $(TSH) -a $(TSHARGS) +test04: + $(DRIVER) -t trace04.txt -s $(TSH) -a $(TSHARGS) +test05: + $(DRIVER) -t trace05.txt -s $(TSH) -a $(TSHARGS) +test06: + $(DRIVER) -t trace06.txt -s $(TSH) -a $(TSHARGS) +test07: + $(DRIVER) -t trace07.txt -s $(TSH) -a $(TSHARGS) +test08: + $(DRIVER) -t trace08.txt -s $(TSH) -a $(TSHARGS) +test09: + $(DRIVER) -t trace09.txt -s $(TSH) -a $(TSHARGS) +test10: + $(DRIVER) -t trace10.txt -s $(TSH) -a $(TSHARGS) +test11: + $(DRIVER) -t trace11.txt -s $(TSH) -a $(TSHARGS) +test12: + $(DRIVER) -t trace12.txt -s $(TSH) -a $(TSHARGS) +test13: + $(DRIVER) -t trace13.txt -s $(TSH) -a $(TSHARGS) +test14: + $(DRIVER) -t trace14.txt -s $(TSH) -a $(TSHARGS) +test15: + $(DRIVER) -t trace15.txt -s $(TSH) -a $(TSHARGS) + +# Run the tests using the reference shell program +rtest01: + $(DRIVER) -t trace01.txt -s $(TSHREF) -a $(TSHARGS) +rtest02: + $(DRIVER) -t trace02.txt -s $(TSHREF) -a $(TSHARGS) +rtest03: + $(DRIVER) -t trace03.txt -s $(TSHREF) -a $(TSHARGS) +rtest04: + $(DRIVER) -t trace04.txt -s $(TSHREF) -a $(TSHARGS) +rtest05: + $(DRIVER) -t trace05.txt -s $(TSHREF) -a $(TSHARGS) +rtest06: + $(DRIVER) -t trace06.txt -s $(TSHREF) -a $(TSHARGS) +rtest07: + $(DRIVER) -t trace07.txt -s $(TSHREF) -a $(TSHARGS) +rtest08: + $(DRIVER) -t trace08.txt -s $(TSHREF) -a $(TSHARGS) +rtest09: + $(DRIVER) -t trace09.txt -s $(TSHREF) -a $(TSHARGS) +rtest10: + $(DRIVER) -t trace10.txt -s $(TSHREF) -a $(TSHARGS) +rtest11: + $(DRIVER) -t trace11.txt -s $(TSHREF) -a $(TSHARGS) +rtest12: + $(DRIVER) -t trace12.txt -s $(TSHREF) -a $(TSHARGS) +rtest13: + $(DRIVER) -t trace13.txt -s $(TSHREF) -a $(TSHARGS) +rtest14: + $(DRIVER) -t trace14.txt -s $(TSHREF) -a $(TSHARGS) +rtest15: + $(DRIVER) -t trace15.txt -s $(TSHREF) -a $(TSHARGS) + + +# clean up +clean: + rm -f $(FILES) *.o *~ + + diff --git a/README b/README new file mode 100644 index 0000000..a5add38 --- /dev/null +++ b/README @@ -0,0 +1,22 @@ +################ +CS:APP Shell Lab +################ + +Files: + +Makefile # Compiles your shell program and runs the tests +README # This file +tsh.c # The shell program that you will write and hand in +tshref # The reference shell binary. + +# The remaining files are used to test your shell +sdriver.pl # The trace-driven shell driver +trace*.txt # The 15 trace files that control the shell driver +tshref.out # Example output of the reference shell on all 15 traces + +# Little C programs that are called by the trace files +myspin.c # Takes argument and spins for seconds +mysplit.c # Forks a child that spins for seconds +mystop.c # Spins for seconds and sends SIGTSTP to itself +myint.c # Spins for seconds and sends SIGINT to itself + diff --git a/myint.c b/myint.c new file mode 100644 index 0000000..ea5bee1 --- /dev/null +++ b/myint.c @@ -0,0 +1,36 @@ +/* + * myint.c - Another handy routine for testing your tiny shell + * + * usage: myint + * Sleeps for seconds and sends SIGINT to itself. + * + */ +#include +#include +#include +#include +#include +#include + +int main(int argc, char **argv) +{ + int i, secs; + pid_t pid; + + if (argc != 2) { + fprintf(stderr, "Usage: %s \n", argv[0]); + exit(0); + } + secs = atoi(argv[1]); + + for (i=0; i < secs; i++) + sleep(1); + + pid = getpid(); + + if (kill(pid, SIGINT) < 0) + fprintf(stderr, "kill (int) error"); + + exit(0); + +} diff --git a/myspin.c b/myspin.c new file mode 100644 index 0000000..08c3e0c --- /dev/null +++ b/myspin.c @@ -0,0 +1,24 @@ +/* + * myspin.c - A handy program for testing your tiny shell + * + * usage: myspin + * Sleeps for seconds in 1-second chunks. + * + */ +#include +#include +#include + +int main(int argc, char **argv) +{ + int i, secs; + + if (argc != 2) { + fprintf(stderr, "Usage: %s \n", argv[0]); + exit(0); + } + secs = atoi(argv[1]); + for (i=0; i < secs; i++) + sleep(1); + exit(0); +} diff --git a/mysplit.c b/mysplit.c new file mode 100644 index 0000000..9ac016e --- /dev/null +++ b/mysplit.c @@ -0,0 +1,35 @@ +/* + * mysplit.c - Another handy routine for testing your tiny shell + * + * usage: mysplit + * Fork a child that spins for seconds in 1-second chunks. + */ +#include +#include +#include +#include +#include +#include + +int main(int argc, char **argv) +{ + int i, secs; + + if (argc != 2) { + fprintf(stderr, "Usage: %s \n", argv[0]); + exit(0); + } + secs = atoi(argv[1]); + + + if (fork() == 0) { /* child */ + for (i=0; i < secs; i++) + sleep(1); + exit(0); + } + + /* parent waits for child to terminate */ + wait(NULL); + + exit(0); +} diff --git a/mystop.c b/mystop.c new file mode 100644 index 0000000..12eee5f --- /dev/null +++ b/mystop.c @@ -0,0 +1,36 @@ +/* + * mystop.c - Another handy routine for testing your tiny shell + * + * usage: mystop + * Sleeps for seconds and sends SIGTSTP to itself. + * + */ +#include +#include +#include +#include +#include +#include + +int main(int argc, char **argv) +{ + int i, secs; + pid_t pid; + + if (argc != 2) { + fprintf(stderr, "Usage: %s \n", argv[0]); + exit(0); + } + secs = atoi(argv[1]); + + for (i=0; i < secs; i++) + sleep(1); + + pid = getpid(); + + if (kill(-pid, SIGTSTP) < 0) + fprintf(stderr, "kill (tstp) error"); + + exit(0); + +} 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 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; diff --git a/trace01.txt b/trace01.txt new file mode 100644 index 0000000..9c8561e --- /dev/null +++ b/trace01.txt @@ -0,0 +1,5 @@ +# +# trace01.txt - Properly terminate on EOF. +# +CLOSE +WAIT diff --git a/trace02.txt b/trace02.txt new file mode 100644 index 0000000..e97643b --- /dev/null +++ b/trace02.txt @@ -0,0 +1,5 @@ +# +# trace02.txt - Process builtin quit command. +# +quit +WAIT diff --git a/trace03.txt b/trace03.txt new file mode 100644 index 0000000..be8869c --- /dev/null +++ b/trace03.txt @@ -0,0 +1,5 @@ +# +# trace03.txt - Run a foreground job. +# +/bin/echo tsh> quit +quit diff --git a/trace04.txt b/trace04.txt new file mode 100644 index 0000000..554df0e --- /dev/null +++ b/trace04.txt @@ -0,0 +1,5 @@ +# +# trace04.txt - Run a background job. +# +/bin/echo -e tsh> ./myspin 1 \046 +./myspin 1 & diff --git a/trace05.txt b/trace05.txt new file mode 100644 index 0000000..03c1876 --- /dev/null +++ b/trace05.txt @@ -0,0 +1,11 @@ +# +# trace05.txt - Process jobs builtin command. +# +/bin/echo -e tsh> ./myspin 2 \046 +./myspin 2 & + +/bin/echo -e tsh> ./myspin 3 \046 +./myspin 3 & + +/bin/echo tsh> jobs +jobs diff --git a/trace06.txt b/trace06.txt new file mode 100644 index 0000000..bfa3890 --- /dev/null +++ b/trace06.txt @@ -0,0 +1,8 @@ +# +# trace06.txt - Forward SIGINT to foreground job. +# +/bin/echo -e tsh> ./myspin 4 +./myspin 4 + +SLEEP 2 +INT diff --git a/trace07.txt b/trace07.txt new file mode 100644 index 0000000..259a285 --- /dev/null +++ b/trace07.txt @@ -0,0 +1,14 @@ +# +# trace07.txt - Forward SIGINT only to foreground job. +# +/bin/echo -e tsh> ./myspin 4 \046 +./myspin 4 & + +/bin/echo -e tsh> ./myspin 5 +./myspin 5 + +SLEEP 2 +INT + +/bin/echo tsh> jobs +jobs diff --git a/trace08.txt b/trace08.txt new file mode 100644 index 0000000..49be19f --- /dev/null +++ b/trace08.txt @@ -0,0 +1,14 @@ +# +# trace08.txt - Forward SIGTSTP only to foreground job. +# +/bin/echo -e tsh> ./myspin 4 \046 +./myspin 4 & + +/bin/echo -e tsh> ./myspin 5 +./myspin 5 + +SLEEP 2 +TSTP + +/bin/echo tsh> jobs +jobs diff --git a/trace09.txt b/trace09.txt new file mode 100644 index 0000000..340d998 --- /dev/null +++ b/trace09.txt @@ -0,0 +1,20 @@ +# +# trace09.txt - Process bg builtin command +# +/bin/echo -e tsh> ./myspin 4 \046 +./myspin 4 & + +/bin/echo -e tsh> ./myspin 5 +./myspin 5 + +SLEEP 2 +TSTP + +/bin/echo tsh> jobs +jobs + +/bin/echo tsh> bg %2 +bg %2 + +/bin/echo tsh> jobs +jobs diff --git a/trace10.txt b/trace10.txt new file mode 100644 index 0000000..e7de996 --- /dev/null +++ b/trace10.txt @@ -0,0 +1,22 @@ +# +# trace10.txt - Process fg builtin command. +# +/bin/echo -e tsh> ./myspin 4 \046 +./myspin 4 & + +SLEEP 1 +/bin/echo tsh> fg %1 +fg %1 + +SLEEP 1 +TSTP + +/bin/echo tsh> jobs +jobs + +/bin/echo tsh> fg %1 +fg %1 + +/bin/echo tsh> jobs +jobs + diff --git a/trace11.txt b/trace11.txt new file mode 100644 index 0000000..d2f8663 --- /dev/null +++ b/trace11.txt @@ -0,0 +1,12 @@ +# +# trace11.txt - Forward SIGINT to every process in foreground process group +# +/bin/echo -e tsh> ./mysplit 4 +./mysplit 4 + +SLEEP 2 +INT + +/bin/echo tsh> /bin/ps a +/bin/ps a + diff --git a/trace12.txt b/trace12.txt new file mode 100644 index 0000000..88455e3 --- /dev/null +++ b/trace12.txt @@ -0,0 +1,17 @@ +# +# trace12.txt - Forward SIGTSTP to every process in foreground process group +# +/bin/echo -e tsh> ./mysplit 4 +./mysplit 4 + +SLEEP 2 +TSTP + +/bin/echo tsh> jobs +jobs + +/bin/echo tsh> /bin/ps a +/bin/ps a + + + diff --git a/trace13.txt b/trace13.txt new file mode 100644 index 0000000..d734cbc --- /dev/null +++ b/trace13.txt @@ -0,0 +1,23 @@ +# +# trace13.txt - Restart every stopped process in process group +# +/bin/echo -e tsh> ./mysplit 4 +./mysplit 4 + +SLEEP 2 +TSTP + +/bin/echo tsh> jobs +jobs + +/bin/echo tsh> /bin/ps a +/bin/ps a + +/bin/echo tsh> fg %1 +fg %1 + +/bin/echo tsh> /bin/ps a +/bin/ps a + + + diff --git a/trace14.txt b/trace14.txt new file mode 100644 index 0000000..8086580 --- /dev/null +++ b/trace14.txt @@ -0,0 +1,47 @@ +# +# trace14.txt - Simple error handling +# +/bin/echo tsh> ./bogus +./bogus + +/bin/echo -e tsh> ./myspin 4 \046 +./myspin 4 & + +/bin/echo tsh> fg +fg + +/bin/echo tsh> bg +bg + +/bin/echo tsh> fg a +fg a + +/bin/echo tsh> bg a +bg a + +/bin/echo tsh> fg 9999999 +fg 9999999 + +/bin/echo tsh> bg 9999999 +bg 9999999 + +/bin/echo tsh> fg %2 +fg %2 + +/bin/echo tsh> fg %1 +fg %1 + +SLEEP 2 +TSTP + +/bin/echo tsh> bg %2 +bg %2 + +/bin/echo tsh> bg %1 +bg %1 + +/bin/echo tsh> jobs +jobs + + + diff --git a/trace15.txt b/trace15.txt new file mode 100644 index 0000000..2cc780e --- /dev/null +++ b/trace15.txt @@ -0,0 +1,46 @@ +# +# trace15.txt - Putting it all together +# + +/bin/echo tsh> ./bogus +./bogus + +/bin/echo tsh> ./myspin 10 +./myspin 10 + +SLEEP 2 +INT + +/bin/echo -e tsh> ./myspin 3 \046 +./myspin 3 & + +/bin/echo -e tsh> ./myspin 4 \046 +./myspin 4 & + +/bin/echo tsh> jobs +jobs + +/bin/echo tsh> fg %1 +fg %1 + +SLEEP 2 +TSTP + +/bin/echo tsh> jobs +jobs + +/bin/echo tsh> bg %3 +bg %3 + +/bin/echo tsh> bg %1 +bg %1 + +/bin/echo tsh> jobs +jobs + +/bin/echo tsh> fg %1 +fg %1 + +/bin/echo tsh> quit +quit + diff --git a/tsh.c b/tsh.c new file mode 100644 index 0000000..4e39200 --- /dev/null +++ b/tsh.c @@ -0,0 +1,509 @@ +/* + * tsh - A tiny shell program with job control + * + * Schlittenhardt, Anna und Tran,Tuan-Dat + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Misc manifest constants */ +#define MAXLINE 1024 /* max line size */ +#define MAXARGS 128 /* max args on a command line */ +#define MAXJOBS 16 /* max jobs at any point in time */ +#define MAXJID 1<<16 /* max job ID */ + +/* Job states */ +#define UNDEF 0 /* undefined */ +#define FG 1 /* running in foreground */ +#define BG 2 /* running in background */ +#define ST 3 /* stopped */ + +/* + * Jobs states: FG (foreground), BG (background), ST (stopped) + * Job state transitions and enabling actions: + * FG -> ST : ctrl-z + * ST -> FG : fg command + * ST -> BG : bg command + * BG -> FG : fg command + * At most 1 job can be in the FG state. + */ + +/* Global variables */ +extern char **environ; /* defined in libc */ +char prompt[] = "tsh> "; /* command line prompt (DO NOT CHANGE) */ +int verbose = 0; /* if true, print additional output */ +int nextjid = 1; /* next job ID to allocate */ +char sbuf[MAXLINE]; /* for composing sprintf messages */ + +struct job_t { /* The job struct */ + pid_t pid; /* job PID */ + int jid; /* job ID [1, 2, ...] */ + int state; /* UNDEF, BG, FG, or ST */ + char cmdline[MAXLINE]; /* command line */ +}; +struct job_t jobs[MAXJOBS]; /* The job list */ +/* End global variables */ + + +/* Function prototypes */ + +/* Here are the functions that you will implement */ +void eval(char *cmdline); +int builtin_cmd(char **argv); +void do_bgfg(char **argv); +void waitfg(pid_t pid); + +void sigchld_handler(int sig); +void sigtstp_handler(int sig); +void sigint_handler(int sig); + +/* Here are helper routines that we've provided for you */ +int parseline(const char *cmdline, char **argv); +void sigquit_handler(int sig); + +void clearjob(struct job_t *job); +void initjobs(struct job_t *jobs); +int maxjid(struct job_t *jobs); +int addjob(struct job_t *jobs, pid_t pid, int state, char *cmdline); +int deletejob(struct job_t *jobs, pid_t pid); +pid_t fgpid(struct job_t *jobs); +struct job_t *getjobpid(struct job_t *jobs, pid_t pid); +struct job_t *getjobjid(struct job_t *jobs, int jid); +int pid2jid(pid_t pid); +void listjobs(struct job_t *jobs); + +void usage(void); +void unix_error(char *msg); +void app_error(char *msg); +typedef void handler_t(int); +handler_t *Signal(int signum, handler_t *handler); + +/* + * main - The shell's main routine + */ +int main(int argc, char **argv) +{ + char c; + char cmdline[MAXLINE]; + int emit_prompt = 1; /* emit prompt (default) */ + + /* Redirect stderr to stdout (so that driver will get all output + * on the pipe connected to stdout) */ + dup2(1, 2); + + /* Parse the command line */ + while ((c = getopt(argc, argv, "hvp")) != EOF) { + switch (c) { + case 'h': /* print help message */ + usage(); + break; + case 'v': /* emit additional diagnostic info */ + verbose = 1; + break; + case 'p': /* don't print a prompt */ + emit_prompt = 0; /* handy for automatic testing */ + break; + default: + usage(); + } + } + + /* Install the signal handlers */ + + /* These are the ones you will need to implement */ + Signal(SIGINT, sigint_handler); /* ctrl-c */ + Signal(SIGTSTP, sigtstp_handler); /* ctrl-z */ + Signal(SIGCHLD, sigchld_handler); /* Terminated or stopped child */ + + /* This one provides a clean way to kill the shell */ + Signal(SIGQUIT, sigquit_handler); + + /* Initialize the job list */ + initjobs(jobs); + + /* Execute the shell's read/eval loop */ + while (1) { + + /* Read command line */ + if (emit_prompt) { + printf("%s", prompt); + fflush(stdout); + } + if ((fgets(cmdline, MAXLINE, stdin) == NULL) && ferror(stdin)) + app_error("fgets error"); + if (feof(stdin)) { /* End of file (ctrl-d) */ + fflush(stdout); + exit(0); + } + + /* Evaluate the command line */ + eval(cmdline); + fflush(stdout); + fflush(stdout); + } + + exit(0); /* control never reaches here */ +} + +/* + * eval - Evaluate the command line that the user has just typed in + * + * If the user has requested a built-in command (quit, jobs, bg or fg) + * then execute it immediately. Otherwise, fork a child process and + * run the job in the context of the child. If the job is running in + * the foreground, wait for it to terminate and then return. Note: + * each child process must have a unique process group ID so that our + * background children don't receive SIGINT (SIGTSTP) from the kernel + * when we type ctrl-c (ctrl-z) at the keyboard. +*/ +void eval(char *cmdline) +{ + return; +} + +/* + * parseline - Parse the command line and build the argv array. + * + * Characters enclosed in single quotes are treated as a single + * argument. Return true if the user has requested a BG job, false if + * the user has requested a FG job. + */ +int parseline(const char *cmdline, char **argv) +{ + static char array[MAXLINE]; /* holds local copy of command line */ + char *buf = array; /* ptr that traverses command line */ + char *delim; /* points to first space delimiter */ + int argc; /* number of args */ + int bg; /* background job? */ + + strcpy(buf, cmdline); + buf[strlen(buf)-1] = ' '; /* replace trailing '\n' with space */ + while (*buf && (*buf == ' ')) /* ignore leading spaces */ + buf++; + + /* Build the argv list */ + argc = 0; + if (*buf == '\'') { + buf++; + delim = strchr(buf, '\''); + } + else { + delim = strchr(buf, ' '); + } + + while (delim) { + argv[argc++] = buf; + *delim = '\0'; + buf = delim + 1; + while (*buf && (*buf == ' ')) /* ignore spaces */ + buf++; + + if (*buf == '\'') { + buf++; + delim = strchr(buf, '\''); + } + else { + delim = strchr(buf, ' '); + } + } + argv[argc] = NULL; + + if (argc == 0) /* ignore blank line */ + return 1; + + /* should the job run in the background? */ + if ((bg = (*argv[argc-1] == '&')) != 0) { + argv[--argc] = NULL; + } + return bg; +} + +/* + * builtin_cmd - If the user has typed a built-in command then execute + * it immediately. + */ +int builtin_cmd(char **argv) +{ + return 0; /* not a builtin command */ +} + +/* + * do_bgfg - Execute the builtin bg and fg commands + */ +void do_bgfg(char **argv) +{ + return; +} + +/* + * waitfg - Block until process pid is no longer the foreground process + */ +void waitfg(pid_t pid) +{ + return; +} + +/***************** + * Signal handlers + *****************/ + +/* + * sigchld_handler - The kernel sends a SIGCHLD to the shell whenever + * a child job terminates (becomes a zombie), or stops because it + * received a SIGSTOP or SIGTSTP signal. The handler reaps all + * available zombie children, but doesn't wait for any other + * currently running children to terminate. + */ +void sigchld_handler(int sig) +{ + return; +} + +/* + * sigint_handler - The kernel sends a SIGINT to the shell whenver the + * user types ctrl-c at the keyboard. Catch it and send it along + * to the foreground job. + */ +void sigint_handler(int sig) +{ + return; +} + +/* + * sigtstp_handler - The kernel sends a SIGTSTP to the shell whenever + * the user types ctrl-z at the keyboard. Catch it and suspend the + * foreground job by sending it a SIGTSTP. + */ +void sigtstp_handler(int sig) +{ + return; +} + +/********************* + * End signal handlers + *********************/ + +/*********************************************** + * Helper routines that manipulate the job list + **********************************************/ + +/* clearjob - Clear the entries in a job struct */ +void clearjob(struct job_t *job) { + job->pid = 0; + job->jid = 0; + job->state = UNDEF; + job->cmdline[0] = '\0'; +} + +/* initjobs - Initialize the job list */ +void initjobs(struct job_t *jobs) { + int i; + + for (i = 0; i < MAXJOBS; i++) + clearjob(&jobs[i]); +} + +/* maxjid - Returns largest allocated job ID */ +int maxjid(struct job_t *jobs) +{ + int i, max=0; + + for (i = 0; i < MAXJOBS; i++) + if (jobs[i].jid > max) + max = jobs[i].jid; + return max; +} + +/* addjob - Add a job to the job list */ +int addjob(struct job_t *jobs, pid_t pid, int state, char *cmdline) +{ + int i; + + if (pid < 1) + return 0; + + for (i = 0; i < MAXJOBS; i++) { + if (jobs[i].pid == 0) { + jobs[i].pid = pid; + jobs[i].state = state; + jobs[i].jid = nextjid++; + if (nextjid > MAXJOBS) + nextjid = 1; + strcpy(jobs[i].cmdline, cmdline); + if(verbose){ + printf("Added job [%d] %d %s\n", jobs[i].jid, jobs[i].pid, jobs[i].cmdline); + } + return 1; + } + } + printf("Tried to create too many jobs\n"); + return 0; +} + +/* deletejob - Delete a job whose PID=pid from the job list */ +int deletejob(struct job_t *jobs, pid_t pid) +{ + int i; + + if (pid < 1) + return 0; + + for (i = 0; i < MAXJOBS; i++) { + if (jobs[i].pid == pid) { + clearjob(&jobs[i]); + nextjid = maxjid(jobs)+1; + return 1; + } + } + return 0; +} + +/* fgpid - Return PID of current foreground job, 0 if no such job */ +pid_t fgpid(struct job_t *jobs) { + int i; + + for (i = 0; i < MAXJOBS; i++) + if (jobs[i].state == FG) + return jobs[i].pid; + return 0; +} + +/* getjobpid - Find a job (by PID) on the job list */ +struct job_t *getjobpid(struct job_t *jobs, pid_t pid) { + int i; + + if (pid < 1) + return NULL; + for (i = 0; i < MAXJOBS; i++) + if (jobs[i].pid == pid) + return &jobs[i]; + return NULL; +} + +/* getjobjid - Find a job (by JID) on the job list */ +struct job_t *getjobjid(struct job_t *jobs, int jid) +{ + int i; + + if (jid < 1) + return NULL; + for (i = 0; i < MAXJOBS; i++) + if (jobs[i].jid == jid) + return &jobs[i]; + return NULL; +} + +/* pid2jid - Map process ID to job ID */ +int pid2jid(pid_t pid) +{ + int i; + + if (pid < 1) + return 0; + for (i = 0; i < MAXJOBS; i++) + if (jobs[i].pid == pid) { + return jobs[i].jid; + } + return 0; +} + +/* listjobs - Print the job list */ +void listjobs(struct job_t *jobs) +{ + int i; + + for (i = 0; i < MAXJOBS; i++) { + if (jobs[i].pid != 0) { + printf("[%d] (%d) ", jobs[i].jid, jobs[i].pid); + switch (jobs[i].state) { + case BG: + printf("Running "); + break; + case FG: + printf("Foreground "); + break; + case ST: + printf("Stopped "); + break; + default: + printf("listjobs: Internal error: job[%d].state=%d ", + i, jobs[i].state); + } + printf("%s", jobs[i].cmdline); + } + } +} +/****************************** + * end job list helper routines + ******************************/ + + +/*********************** + * Other helper routines + ***********************/ + +/* + * usage - print a help message + */ +void usage(void) +{ + printf("Usage: shell [-hvp]\n"); + printf(" -h print this message\n"); + printf(" -v print additional diagnostic information\n"); + printf(" -p do not emit a command prompt\n"); + exit(1); +} + +/* + * unix_error - unix-style error routine + */ +void unix_error(char *msg) +{ + fprintf(stdout, "%s: %s\n", msg, strerror(errno)); + exit(1); +} + +/* + * app_error - application-style error routine + */ +void app_error(char *msg) +{ + fprintf(stdout, "%s\n", msg); + exit(1); +} + +/* + * Signal - wrapper for the sigaction function + */ +handler_t *Signal(int signum, handler_t *handler) +{ + struct sigaction action, old_action; + + action.sa_handler = handler; + sigemptyset(&action.sa_mask); /* block sigs of type being handled */ + action.sa_flags = SA_RESTART; /* restart syscalls if possible */ + + if (sigaction(signum, &action, &old_action) < 0) + unix_error("Signal error"); + return (old_action.sa_handler); +} + +/* + * sigquit_handler - The driver program can gracefully terminate the + * child shell by sending it a SIGQUIT signal. + */ +void sigquit_handler(int sig) +{ + printf("Terminating after receipt of SIGQUIT signal\n"); + exit(1); +} + + + diff --git a/tshref b/tshref new file mode 100755 index 0000000..eae17f8 Binary files /dev/null and b/tshref differ diff --git a/tshref.out b/tshref.out new file mode 100644 index 0000000..9093559 --- /dev/null +++ b/tshref.out @@ -0,0 +1,220 @@ +make[1]: Entering directory `/afs/cs.cmu.edu/project/ics/im/labs/shlab/src' +./sdriver.pl -t trace01.txt -s ./tsh -a "-p" +# +# trace01.txt - Properly terminate on EOF. +# +./sdriver.pl -t trace02.txt -s ./tsh -a "-p" +# +# trace02.txt - Process builtin quit command. +# +./sdriver.pl -t trace03.txt -s ./tsh -a "-p" +# +# trace03.txt - Run a foreground job. +# +tsh> quit +./sdriver.pl -t trace04.txt -s ./tsh -a "-p" +# +# trace04.txt - Run a background job. +# +tsh> ./myspin 1 & +[1] (26252) ./myspin 1 & +./sdriver.pl -t trace05.txt -s ./tsh -a "-p" +# +# trace05.txt - Process jobs builtin command. +# +tsh> ./myspin 2 & +[1] (26256) ./myspin 2 & +tsh> ./myspin 3 & +[2] (26258) ./myspin 3 & +tsh> jobs +[1] (26256) Running ./myspin 2 & +[2] (26258) Running ./myspin 3 & +./sdriver.pl -t trace06.txt -s ./tsh -a "-p" +# +# trace06.txt - Forward SIGINT to foreground job. +# +tsh> ./myspin 4 +Job [1] (26263) terminated by signal 2 +./sdriver.pl -t trace07.txt -s ./tsh -a "-p" +# +# trace07.txt - Forward SIGINT only to foreground job. +# +tsh> ./myspin 4 & +[1] (26267) ./myspin 4 & +tsh> ./myspin 5 +Job [2] (26269) terminated by signal 2 +tsh> jobs +[1] (26267) Running ./myspin 4 & +./sdriver.pl -t trace08.txt -s ./tsh -a "-p" +# +# trace08.txt - Forward SIGTSTP only to foreground job. +# +tsh> ./myspin 4 & +[1] (26274) ./myspin 4 & +tsh> ./myspin 5 +Job [2] (26276) stopped by signal 20 +tsh> jobs +[1] (26274) Running ./myspin 4 & +[2] (26276) Stopped ./myspin 5 +./sdriver.pl -t trace09.txt -s ./tsh -a "-p" +# +# trace09.txt - Process bg builtin command +# +tsh> ./myspin 4 & +[1] (26281) ./myspin 4 & +tsh> ./myspin 5 +Job [2] (26283) stopped by signal 20 +tsh> jobs +[1] (26281) Running ./myspin 4 & +[2] (26283) Stopped ./myspin 5 +tsh> bg %2 +[2] (26283) ./myspin 5 +tsh> jobs +[1] (26281) Running ./myspin 4 & +[2] (26283) Running ./myspin 5 +./sdriver.pl -t trace10.txt -s ./tsh -a "-p" +# +# trace10.txt - Process fg builtin command. +# +tsh> ./myspin 4 & +[1] (26290) ./myspin 4 & +tsh> fg %1 +Job [1] (26290) stopped by signal 20 +tsh> jobs +[1] (26290) Stopped ./myspin 4 & +tsh> fg %1 +tsh> jobs +./sdriver.pl -t trace11.txt -s ./tsh -a "-p" +# +# trace11.txt - Forward SIGINT to every process in foreground process group +# +tsh> ./mysplit 4 +Job [1] (26298) terminated by signal 2 +tsh> /bin/ps a + PID TTY STAT TIME COMMAND +25181 pts/3 S 0:00 -usr/local/bin/tcsh -i +26239 pts/3 S 0:00 make tshrefout +26240 pts/3 S 0:00 /bin/sh -c make tests > tshref.out 2>&1 +26241 pts/3 S 0:00 make tests +26295 pts/3 S 0:00 perl ./sdriver.pl -t trace11.txt -s ./tsh -a -p +26296 pts/3 S 0:00 ./tsh -p +26301 pts/3 R 0:00 /bin/ps a +./sdriver.pl -t trace12.txt -s ./tsh -a "-p" +# +# trace12.txt - Forward SIGTSTP to every process in foreground process group +# +tsh> ./mysplit 4 +Job [1] (26305) stopped by signal 20 +tsh> jobs +[1] (26305) Stopped ./mysplit 4 +tsh> /bin/ps a + PID TTY STAT TIME COMMAND +25181 pts/3 S 0:00 -usr/local/bin/tcsh -i +26239 pts/3 S 0:00 make tshrefout +26240 pts/3 S 0:00 /bin/sh -c make tests > tshref.out 2>&1 +26241 pts/3 S 0:00 make tests +26302 pts/3 S 0:00 perl ./sdriver.pl -t trace12.txt -s ./tsh -a -p +26303 pts/3 S 0:00 ./tsh -p +26305 pts/3 T 0:00 ./mysplit 4 +26306 pts/3 T 0:00 ./mysplit 4 +26309 pts/3 R 0:00 /bin/ps a +./sdriver.pl -t trace13.txt -s ./tsh -a "-p" +# +# trace13.txt - Restart every stopped process in process group +# +tsh> ./mysplit 4 +Job [1] (26313) stopped by signal 20 +tsh> jobs +[1] (26313) Stopped ./mysplit 4 +tsh> /bin/ps a + PID TTY STAT TIME COMMAND +25181 pts/3 S 0:00 -usr/local/bin/tcsh -i +26239 pts/3 S 0:00 make tshrefout +26240 pts/3 S 0:00 /bin/sh -c make tests > tshref.out 2>&1 +26241 pts/3 S 0:00 make tests +26310 pts/3 S 0:00 perl ./sdriver.pl -t trace13.txt -s ./tsh -a -p +26311 pts/3 S 0:00 ./tsh -p +26313 pts/3 T 0:00 ./mysplit 4 +26314 pts/3 T 0:00 ./mysplit 4 +26317 pts/3 R 0:00 /bin/ps a +tsh> fg %1 +tsh> /bin/ps a + PID TTY STAT TIME COMMAND +25181 pts/3 S 0:00 -usr/local/bin/tcsh -i +26239 pts/3 S 0:00 make tshrefout +26240 pts/3 S 0:00 /bin/sh -c make tests > tshref.out 2>&1 +26241 pts/3 S 0:00 make tests +26310 pts/3 S 0:00 perl ./sdriver.pl -t trace13.txt -s ./tsh -a -p +26311 pts/3 S 0:00 ./tsh -p +26320 pts/3 R 0:00 /bin/ps a +./sdriver.pl -t trace14.txt -s ./tsh -a "-p" +# +# trace14.txt - Simple error handling +# +tsh> ./bogus +./bogus: Command not found +tsh> ./myspin 4 & +[1] (26326) ./myspin 4 & +tsh> fg +fg command requires PID or %jobid argument +tsh> bg +bg command requires PID or %jobid argument +tsh> fg a +fg: argument must be a PID or %jobid +tsh> bg a +bg: argument must be a PID or %jobid +tsh> fg 9999999 +(9999999): No such process +tsh> bg 9999999 +(9999999): No such process +tsh> fg %2 +%2: No such job +tsh> fg %1 +Job [1] (26326) stopped by signal 20 +tsh> bg %2 +%2: No such job +tsh> bg %1 +[1] (26326) ./myspin 4 & +tsh> jobs +[1] (26326) Running ./myspin 4 & +./sdriver.pl -t trace15.txt -s ./tsh -a "-p" +# +# trace15.txt - Putting it all together +# +tsh> ./bogus +./bogus: Command not found +tsh> ./myspin 10 +Job [1] (26343) terminated by signal 2 +tsh> ./myspin 3 & +[1] (26345) ./myspin 3 & +tsh> ./myspin 4 & +[2] (26347) ./myspin 4 & +tsh> jobs +[1] (26345) Running ./myspin 3 & +[2] (26347) Running ./myspin 4 & +tsh> fg %1 +Job [1] (26345) stopped by signal 20 +tsh> jobs +[1] (26345) Stopped ./myspin 3 & +[2] (26347) Running ./myspin 4 & +tsh> bg %3 +%3: No such job +tsh> bg %1 +[1] (26345) ./myspin 3 & +tsh> jobs +[1] (26345) Running ./myspin 3 & +[2] (26347) Running ./myspin 4 & +tsh> fg %1 +tsh> quit +./sdriver.pl -t trace16.txt -s ./tsh -a "-p" +# +# trace16.txt - Tests whether the shell can handle SIGTSTP and SIGINT +# signals that come from other processes instead of the terminal. +# +tsh> ./mystop 2 +Job [1] (26359) stopped by signal 20 +tsh> jobs +[1] (26359) Stopped ./mystop 2 +tsh> ./myint 2 +Job [2] (26362) terminated by signal 2 +make[1]: Leaving directory `/afs/cs.cmu.edu/project/ics/im/labs/shlab/src'