aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJack Sangdahl <[email protected]>2024-12-12 21:21:41 -0700
committerJack Sangdahl <[email protected]>2024-12-12 21:21:41 -0700
commit3f9abe494bb944d16edc8af17180d38957cadb56 (patch)
treeec9938836be2767891d5c78168f4a3b669e89f6a
parent3328ae7671fa7e329880ab002e8378b7ce851245 (diff)
downloadcsci2400-3f9abe494bb944d16edc8af17180d38957cadb56.tar.gz
csci2400-3f9abe494bb944d16edc8af17180d38957cadb56.zip
Lab 5 Shell Lab
Signed-off-by: Jack Sangdahl <[email protected]>
-rw-r--r--lab5_shelllab/Makefile104
-rw-r--r--lab5_shelllab/README.md362
-rw-r--r--lab5_shelllab/globals.h17
-rw-r--r--lab5_shelllab/helper-routines.cc131
-rw-r--r--lab5_shelllab/helper-routines.h18
-rw-r--r--lab5_shelllab/jobs.cc174
-rw-r--r--lab5_shelllab/jobs.h45
-rw-r--r--lab5_shelllab/myint.cc36
-rw-r--r--lab5_shelllab/myspin.cc25
-rw-r--r--lab5_shelllab/mysplit.cc36
-rw-r--r--lab5_shelllab/mystop.cc36
-rwxr-xr-xlab5_shelllab/sdriver.pl210
-rwxr-xr-xlab5_shelllab/shellAutograder.py222
-rw-r--r--lab5_shelllab/trace01.txt5
-rw-r--r--lab5_shelllab/trace02.txt5
-rw-r--r--lab5_shelllab/trace03.txt5
-rw-r--r--lab5_shelllab/trace04.txt5
-rw-r--r--lab5_shelllab/trace05.txt11
-rw-r--r--lab5_shelllab/trace06.txt8
-rw-r--r--lab5_shelllab/trace07.txt14
-rw-r--r--lab5_shelllab/trace08.txt14
-rw-r--r--lab5_shelllab/trace09.txt20
-rw-r--r--lab5_shelllab/trace10.txt22
-rw-r--r--lab5_shelllab/trace11.txt12
-rw-r--r--lab5_shelllab/trace12.txt17
-rw-r--r--lab5_shelllab/trace13.txt23
-rw-r--r--lab5_shelllab/trace14.txt47
-rw-r--r--lab5_shelllab/trace15.txt46
-rw-r--r--lab5_shelllab/trace16.txt16
-rwxr-xr-xlab5_shelllab/tshbin0 -> 142648 bytes
-rw-r--r--lab5_shelllab/tsh.cc416
-rwxr-xr-xlab5_shelllab/tshrefbin0 -> 1040440 bytes
-rw-r--r--lab5_shelllab/tshref.out230
33 files changed, 2332 insertions, 0 deletions
diff --git a/lab5_shelllab/Makefile b/lab5_shelllab/Makefile
new file mode 100644
index 0000000..c770e55
--- /dev/null
+++ b/lab5_shelllab/Makefile
@@ -0,0 +1,104 @@
+# Makefile for the CS:APP Shell Lab
+
+TEAM = NOBODY
+DRIVER = ./sdriver.pl
+TSH = ./tsh
+TSHREF = ./tshref
+TSHARGS = "-p"
+CC = gcc
+CXX = g++
+
+##
+## Compile with no optimization because we don't care about performance and want to simplify debugging
+##
+CFLAGS = -Wall -O -g
+CXXFLAGS=$(CFLAGS)
+FILES = ./myspin ./mysplit ./mystop ./myint
+
+all: $(FILES) tsh
+
+tsh: tsh.o jobs.o helper-routines.o
+ $(CXX) -o tsh tsh.o jobs.o helper-routines.o
+
+##################
+# Regression tests
+##################
+
+tests: tsh test01 test02 test03 test04 test05 test06 test07 test08 test09 test10 test11 test12 test13 test14 test15 test16
+ @echo all time
+
+
+# Run tests using the student's shell program
+test01: tsh $(FILES)
+ $(DRIVER) -t trace01.txt -s $(TSH) -a $(TSHARGS)
+test02: tsh $(FILES)
+ $(DRIVER) -t trace02.txt -s $(TSH) -a $(TSHARGS)
+test03: tsh $(FILES)
+ $(DRIVER) -t trace03.txt -s $(TSH) -a $(TSHARGS)
+test04: tsh $(FILES)
+ $(DRIVER) -t trace04.txt -s $(TSH) -a $(TSHARGS)
+test05: tsh $(FILES)
+ $(DRIVER) -t trace05.txt -s $(TSH) -a $(TSHARGS)
+test06: tsh $(FILES)
+ $(DRIVER) -t trace06.txt -s $(TSH) -a $(TSHARGS)
+test07: tsh $(FILES)
+ $(DRIVER) -t trace07.txt -s $(TSH) -a $(TSHARGS)
+test08: tsh $(FILES)
+ $(DRIVER) -t trace08.txt -s $(TSH) -a $(TSHARGS)
+test09: tsh $(FILES)
+ $(DRIVER) -t trace09.txt -s $(TSH) -a $(TSHARGS)
+test10: tsh $(FILES)
+ $(DRIVER) -t trace10.txt -s $(TSH) -a $(TSHARGS)
+test11: tsh $(FILES)
+ $(DRIVER) -t trace11.txt -s $(TSH) -a $(TSHARGS)
+test12: tsh $(FILES)
+ $(DRIVER) -t trace12.txt -s $(TSH) -a $(TSHARGS)
+test13: tsh $(FILES)
+ $(DRIVER) -t trace13.txt -s $(TSH) -a $(TSHARGS)
+test14: tsh $(FILES)
+ $(DRIVER) -t trace14.txt -s $(TSH) -a $(TSHARGS)
+test15: tsh $(FILES)
+ $(DRIVER) -t trace15.txt -s $(TSH) -a $(TSHARGS)
+test16: tsh $(FILES)
+ $(DRIVER) -t trace16.txt -s $(TSH) -a $(TSHARGS)
+
+# Run the tests using the reference shell program
+rtests: $(TSHREF) rtest01 rtest02 rtest03 rtest04 rtest05 rtest06 rtest07 rtest08 rtest09 rtest10 rtest11 rtest12 rtest13 rtest14 rtest15 rtest16
+ @echo all time
+rtest01: $(TSHREF) $(FILES)
+ $(DRIVER) -t trace01.txt -s $(TSHREF) -a $(TSHARGS)
+rtest02: $(TSHREF) $(FILES)
+ $(DRIVER) -t trace02.txt -s $(TSHREF) -a $(TSHARGS)
+rtest03: $(TSHREF) $(FILES)
+ $(DRIVER) -t trace03.txt -s $(TSHREF) -a $(TSHARGS)
+rtest04: $(TSHREF) $(FILES)
+ $(DRIVER) -t trace04.txt -s $(TSHREF) -a $(TSHARGS)
+rtest05: $(TSHREF) $(FILES)
+ $(DRIVER) -t trace05.txt -s $(TSHREF) -a $(TSHARGS)
+rtest06: $(TSHREF) $(FILES)
+ $(DRIVER) -t trace06.txt -s $(TSHREF) -a $(TSHARGS)
+rtest07: $(TSHREF) $(FILES)
+ $(DRIVER) -t trace07.txt -s $(TSHREF) -a $(TSHARGS)
+rtest08: $(TSHREF) $(FILES)
+ $(DRIVER) -t trace08.txt -s $(TSHREF) -a $(TSHARGS)
+rtest09: $(TSHREF) $(FILES)
+ $(DRIVER) -t trace09.txt -s $(TSHREF) -a $(TSHARGS)
+rtest10: $(TSHREF) $(FILES)
+ $(DRIVER) -t trace10.txt -s $(TSHREF) -a $(TSHARGS)
+rtest11: $(TSHREF) $(FILES)
+ $(DRIVER) -t trace11.txt -s $(TSHREF) -a $(TSHARGS)
+rtest12: $(TSHREF) $(FILES)
+ $(DRIVER) -t trace12.txt -s $(TSHREF) -a $(TSHARGS)
+rtest13: $(TSHREF) $(FILES)
+ $(DRIVER) -t trace13.txt -s $(TSHREF) -a $(TSHARGS)
+rtest14: $(TSHREF) $(FILES)
+ $(DRIVER) -t trace14.txt -s $(TSHREF) -a $(TSHARGS)
+rtest15: $(TSHREF) $(FILES)
+ $(DRIVER) -t trace15.txt -s $(TSHREF) -a $(TSHARGS)
+rtest16: $(TSHREF) $(FILES)
+ $(DRIVER) -t trace16.txt -s $(TSHREF) -a $(TSHARGS)
+
+
+# clean up
+clean:
+ rm -f $(FILES) *.o *~
diff --git a/lab5_shelllab/README.md b/lab5_shelllab/README.md
new file mode 100644
index 0000000..18d31de
--- /dev/null
+++ b/lab5_shelllab/README.md
@@ -0,0 +1,362 @@
+[![Open in Codespaces](https://classroom.github.com/assets/launch-codespace-f4981d0f882b2a3f0472912d15f9806d57e124e0fc890972558857b51b24a6f9.svg)](https://classroom.github.com/open-in-codespaces?assignment_repo_id=10576266)
+
+# CS 2400 - Shell Lab: Writing Your Own Unix Shell
+
+## Introduction
+
+The purpose of this assignment is to become more familiar with the
+concepts of process control and signalling. You'll do this by writing a
+simple Unix shell program that supports job control.
+
+## Logistics
+
+You accept the assignment through Github Classroom.
+The only "hand-in" will be electronic. Any clarifications and revisions to the assignment will be posted on the course Moodle page.
+
+- You can work on this lab using any Linux-like environment (Linux, WSL2, MacOS) but grading and checking will be done on the reference Ubuntu-22.04 environment supported by [https://coding.csel.io](https://coding.csel.io)
+- Type the command *make* to compile and link some test routines.
+
+Looking at the tsh.cc (tsh stands for *tiny shell*) file, you will see that it contains
+a functional skeleton of a simple Unix shell. To help you get started,
+we have already implemented the less interesting functions; some of
+these are in the file `helper_routines.cc`, and these have been
+separated in another file to make it easier to focus on the work you
+need to do in tsh.cc. You should only have to change tsh.cc
+
+Your assignment is to complete the remaining empty functions listed
+below in tsh.cc. As a sanity check for you, we've listed the approximate
+number of lines of code for each of these functions in our reference
+solution (which includes lots of comments).
+
+* eval: Main routine that parses and interprets the command line. \[70 lines\]
+
+* builtin\_cmd: Recognizes and interprets the built-in commands: quit,
+ fg, bg, and jobs. \[25 lines\]
+
+* do\_bgfg: Implements the bg and fg built-in commands. \[50 lines\]
+
+* waitfg: Waits for a foreground job to complete. \[20 lines\]
+
+* sigchld\_handler: Catches SIGCHILD signals. 80 lines\]
+
+* sigint\_handler: Catches SIGINT (ctrl-c) signals. \[15 lines\]
+
+* sigtstp\_handler: Catches SIGTSTP (ctrl-z) signals. \[15 lines\]
+
+Each time you modify your tsh.cc file, type make to recompile it. To run
+your shell, type ./tsh to the command line:
+
+```
+unix> ./tsh
+tsh> *type commands to your shell here*
+```
+
+## General Overview of Unix Shells
+
+A *shell* is an interactive command-line interpreter that runs programs
+on behalf of the user. A shell repeatedly prints a prompt, waits for a
+*command line* on stdin, and then carries out some action, as directed
+by the contents of the command line.
+
+The command line is a sequence of ASCII text words delimited by
+whitespace. The first word in the command line is either the name of a
+built-in command or the pathname of an executable file. The remaining
+words are command-line arguments. If the first word is a built-in
+command, the shell immediately executes the command in the current
+process. Otherwise, the word is assumed to be the pathname of an
+executable program. In this case, the shell forks a child process, then
+loads and runs the program in the context of the child. The child
+processes created as a result of interpreting a single command line are
+known collectively as a *job*. In general, a job can consist of multiple
+child processes connected by Unix pipes.
+
+If the command line ends with an ampersand "&", then the job runs in the
+*background*, which means that the shell does not wait for the job to
+terminate before printing the prompt and awaiting the next command line.
+Otherwise, the job runs in the *foreground*, which means that the shell
+waits for the job to terminate before awaiting the next command line.
+Thus, at any point in time, at most one job can be running in the
+foreground. However, an arbitrary number of jobs can run in the
+background.
+
+For example, typing the command line
+```
+tsh> jobs
+```
+causes the shell to execute the built-in jobs command.
+
+### Command Line Arugments
+Typing the
+command line
+```
+tsh>/bin/ls -l -d
+```
+runs the `/bin/ls` program in the foreground. By convention, the shell ensures
+that when the program begins executing a programs `main` routine declared as such:
+```
+int main(int argc, char *argv[])
+```
+is called so that the `argc` and `argv` arguments have the following values for this example:
+
+- argc == 3,
+
+- argv\[0\] == "/bin/ls",
+
+- argv\[1\]== "-l",
+
+- argv\[2\]== "-d".
+
+Alternatively, typing the command line
+```
+tsh>/bin/ls -l -d &
+```
+runs the ls program in the background. Note that the ampersand (&) is not passed to the program in the arugments.
+
+### Job control
+Unix shells support the notion of *job control*, which allows users to
+move jobs back and forth between background and foreground, and to
+change the process state (running, stopped, or terminated) of the
+processes in a job. Typing ctrl-c causes a SIGINT signal to be delivered
+to each process in the foreground job. The default action for SIGINT is
+to terminate the process. Similarly, typing ctrl-z causes a SIGTSTP
+signal to be delivered to each process in the foreground job. The
+default action for SIGTSTP is to place a process in the stopped state,
+where it remains until it is awakened by the receipt of a SIGCONT
+signal. Unix shells also provide various built-in commands that support
+job control. For example:
+
+- jobs: List the running and stopped background jobs.
+
+- bg \<job\>: Change a stopped background job to a running background
+ job.
+
+- fg \<job\>: Change a stopped or running background job to a running
+ in the foreground.
+
+- kill \<job\>: Terminate a job.
+
+## The tsh Specification
+
+Your tsh shell should have the following features:
+
+- The prompt should be the string "tsh\> ".
+
+- The command line typed by the user should consist of a name and zero
+ or more arguments, all separated by one or more spaces. If name is a
+ built-in command, then tsh should handle it immediately and wait for
+ the next command line. Otherwise, tsh should assume that name is the
+ path of an executable file, which it loads and runs in the context
+ of an initial child process (In this context, the term *job* refers
+ to this initial child process).
+
+- tsh need not support pipes (\|) or I/O redirection (\< and \>).
+
+- Typing ctrl-c (ctrl-z) should cause a SIGINT (SIGTSTP) signal to be
+ sent to the current foreground job, as well as any descendents of
+ that job (e.g., any child processes that it forked). If there is no
+ foreground job, then the signal should have no effect.
+
+- If the command line ends with an ampersand &, then tsh should run
+ the job in the background. Otherwise, it should run the job in the
+ foreground.
+
+- Each job can be identified by either a process ID (PID) or a job ID
+ (JID), which is a positive integer assigned by tsh. Job ID's are
+ used because some scripts need to manipulate certain jobs, and the
+ process ID's change across runs. JIDs should be denoted on the
+ command line by the prefix '`%`'. For example, "%5" denotes JID 5,
+ and "5" denotes PID 5. (We have provided you with all of the
+ routines you need for manipulating the job list.)
+
+- tsh should support the following built-in commands:
+
+ - The quit command terminates the shell.
+
+ - The jobs command lists all background jobs.
+
+ - The bg \<job\> command restarts \<job\> by sending it a SIGCONT
+ signal, and then runs it in the background. The \<job\> argument
+ can be either a PID or a JID.
+
+ - The fg \<job\> command restarts \<job\> by sending it a SIGCONT
+ signal, and then runs it in the foreground. The \<job\> argument
+ can be either a PID or a JID.
+
+- tsh should reap all of its zombie children. If any job terminates
+ because it receives a signal that it didn't catch, then tsh should
+ recognize this event and print a message with the job's PID and a
+ description of the offending signal.
+
+## Checking Your Work
+
+We have provided some tools to help you check your work.
+
+**Reference solution.** The Linux executable `tshref` is the reference
+solution for the shell. Run this program to resolve any questions you
+have about how your shell should behave. *Your shell should emit output
+that is identical to the reference solution* (except for process
+identifiers (PIDs) that change from run to run).
+
+**Shell driver.** The sdriver.pl program executes a shell as a child
+process, sends it commands and signals as directed by a *trace file*,
+and captures and displays the output from the shell.
+
+Use the -h argument to find out the usage of sdriver.pl:
+```
+unix> ./sdriver.pl
+Missing required -t argument
+Usage: ./sdriver.pl [-hv] -t <trace> -s <shellprog> -a <args>
+Options:
+ -h Print this message
+ -v Be more verbose
+ -t <trace> Trace file
+ -s <shell> Shell program to test
+ -a <args> Shell arguments
+ -g Generate output for autograder
+```
+We have also provided 16 trace files ( trace{01-16}.txt) that you will
+use in conjunction with the shell driver to test the correctness of your
+shell. The lower-numbered trace files do very simple tests, and the
+higher-numbered tests do more complicated tests.
+
+You can run the shell driver on your shell using trace file trace01.txt
+(for instance) by typing:
+```
+unix>./sdriver.pl -t trace01.txt -s ./tsh -a "-p"
+```
+(the -a "-p" argument tells your shell not to emit a prompt), or
+```
+unix> make test01
+```
+Similarly, to compare your result with the reference shell, you can run
+the trace driver on the reference shell by typing:
+```
+unix>./sdriver.pl -t trace01.txt -s ./tshref -a "-p"
+```
+or
+```
+unix> make rtest01
+```
+
+For your reference, tshref.out gives the output of the reference
+solution on all races. This might be more convenient for you than
+manually running the shell driver on all trace files.
+
+
+## Hints
+
+- Read **every word** of the chapter on Exceptional Control Flow in
+ your textbook.
+
+- Use the trace files to guide the development of your shell. Starting
+ with trace01.txt, make sure that your shell produces the *identical*
+ output as the reference shell. Then move on to trace file
+ trace02.txt, and so on.
+
+- The `waitpid`, `kill`, `fork`, `execve`, `setpgid`, and `sigprocmask` functions
+ will come in very handy. The `WUNTRACED` and `WNOHANG` options to
+ waitpid will also be useful. These are described in detail in the
+ text.
+
+- When you implement your signal handlers, be sure to send `SIGINT` and
+ `SIGTSTP` signals to the **entire** foreground process group, using
+ `-pid` instead of `pid` in the argument to the `kill` function. The
+ `sdriver.pl` program tests for this error.
+
+- One of the tricky parts of the assignment is deciding on the
+ allocation of work between the waitfg and `sigchld_handler`
+ functions. We recommend the following approach:
+
+ - In `waitfg`, use a busy loop around the sleep function.
+
+ - In `sigchld_handler`, use exactly one call to `waitpid`.
+
+ While other solutions are possible, such as calling waitpid in both
+ waitfg and sigchld\_handler, these can be very confusing. It is
+ simpler to do all reaping in the handler.
+
+- In `eval`, the parent must use `sigprocmask` to block `SIGCHLD` signals
+ before it forks the child, and then unblock these signals, again
+ using `sigprocmask` after it adds the child to the job list by calling
+ `addjob`. Since children inherit the blocked vectors of their parents,
+ the child must be sure to then unblock `SIGCHLD` signals before it
+ execs the new program.
+
+ The parent needs to block the `SIGCHLD` signals in this way in order
+ to avoid the race condition where the child is reaped by
+ `sigchld_handler` (and thus removed from the job list) *before* the
+ parent calls `addjob`.
+
+- Programs such as `more`, `less`, `vi`, and `emacs` do strange things with
+ the terminal settings. Don't run these programs from your tsh.
+ Stick with simple text-based programs such as `/bin/ls`, `/bin/ps`, and
+ `/bin/echo`.
+
+- When you run your shell from the standard Unix shell, your shell is
+ running in the foreground process group. If your shell then creates
+ a child process, by default that child will also be a member of the
+ foreground process group. Since typing ctrl-c sends a `SIGINT` to
+ every process in the foreground group, typing ctrl-c will send a
+ SIGINT to your shell, as well as to every process that your shell
+ created, which obviously isn't correct.
+
+ Here is the workaround: After the `fork`, but before the `execve`, the
+ child process should call `setpgid(0, 0)`, which puts the child in a
+ new process group whose group ID is identical to the child's PID.
+ This ensures that there will be only one process, your shell, in the
+ foreground process group. When you type ctrl-c, the shell should
+ catch the resulting `SIGINT` and then forward it to the appropriate
+ foreground job (or more precisely, the process group that contains
+ the foreground job).
+
+## Evaluation
+
+The `./shellAutograder.py` program will grade your shell program; you can either run this directly yourself and it will also be used when you commit your changes.
+
+During your grading session, your solution shell will be tested for
+correctness on a Linux machine (either your virtual machine or native
+linux install) using the same shell driver and trace files that were
+included in your lab directory. Your shell should produce **identical**
+output on these traces as the reference shell, with only two exceptions:
+
+- The PIDs can (and will) be different.
+
+- The output of the /bin/ps commands in trace11.txt, trace12.txt, and
+ trace13.txt will be different from run to run. However, the running
+ states of any mysplit processes in the output of the /bin/ps command
+ should be identical.
+
+The "correctness" part of your assignment will be computed based on how
+many of the traces you correctly execute using following distribution:
+
+- 10% - traces 1-3
+
+- 30% - traces 4-5
+
+- 60% - traces 6-8
+
+- 80% - traces 9-10
+
+- 90% - traces 11-13
+
+- 100% - trace 14-16
+
+As with all assignments in CSCI 2400, you will need to be able to
+explain the function of your code in your grading meeting. Correctness
+is only part of your final grade.
+
+## Using GDB to debug your shell lab with VScode
+
+If you plan on using VScode to debug your shell lab, we need to do a little configuration. Normally, GDB "catches" the same signals your shell is supposed to catch. This stops execution and returns control to GDB when those signals occur. This means you can't just set a breakpoint in your signal handlers and expect to have the debugger notice that.
+
+To get around this, we will use a [`.gdbinit`](.gdbinit) file which is contained in this Git repo. By default, `gdb` will not read a local `.gdbinit` file because of security concerns. We can override that by adding an option to the `.gdbinit` file in your home directory to enable loading any gdbinit file.
+
+To do this, cut and paste the following line into a Terminal window
+
+```
+echo "set auto-load safe-path /" >> ~/.gdbinit
+```
+
+Following this, the local `.gdbinit` shoudld be loaded. If you want to change the local `.gdbinit` you can learn more about the signal configurations [at this GDB manual](https://sourceware.org/gdb/current/onlinedocs/gdb/Signals.html).
+
+Good luck!
diff --git a/lab5_shelllab/globals.h b/lab5_shelllab/globals.h
new file mode 100644
index 0000000..fefc0fb
--- /dev/null
+++ b/lab5_shelllab/globals.h
@@ -0,0 +1,17 @@
+//-*-c++-*-
+#ifndef _global_h_
+#define _global_h_
+
+/* 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 */
+
+/* Global variables */
+extern int verbose; // defined in tcsh.cc
+//extern char sbuf[MAXLINE]; /* for composing sprintf messages */
+/* End global variables */
+
+
+#endif
diff --git a/lab5_shelllab/helper-routines.cc b/lab5_shelllab/helper-routines.cc
new file mode 100644
index 0000000..0af221d
--- /dev/null
+++ b/lab5_shelllab/helper-routines.cc
@@ -0,0 +1,131 @@
+#include "helper-routines.h"
+#include "globals.h"
+#include <stdio.h>
+#include <strings.h>
+#include <memory.h> // strcpy and memcpy
+#include <unistd.h>
+#include <stdlib.h>
+#include <errno.h>
+
+/***********************
+ * 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(const char *msg)
+{
+ fprintf(stdout, "%s: %s\n", msg, strerror(errno));
+ exit(1);
+}
+
+/*
+ * app_error - application-style error routine
+ */
+void app_error(const 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);
+}
+
+/*
+ * 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? */
+
+ strncpy(buf, cmdline, MAXLINE);
+ 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;
+}
diff --git a/lab5_shelllab/helper-routines.h b/lab5_shelllab/helper-routines.h
new file mode 100644
index 0000000..592f9b3
--- /dev/null
+++ b/lab5_shelllab/helper-routines.h
@@ -0,0 +1,18 @@
+//-*-c++-*-
+#ifndef _helper_routines_h_
+#define _helper_routines_h_
+
+#include <signal.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+
+/* Here are helper routines that we've provided for you */
+int parseline(const char *cmdline, char **argv);
+void sigquit_handler(int sig);
+void usage(void);
+void unix_error(const char *msg);
+void app_error(const char *msg);
+typedef void handler_t(int);
+handler_t *Signal(int signum, handler_t *handler);
+
+#endif
diff --git a/lab5_shelllab/jobs.cc b/lab5_shelllab/jobs.cc
new file mode 100644
index 0000000..d35ba15
--- /dev/null
+++ b/lab5_shelllab/jobs.cc
@@ -0,0 +1,174 @@
+#include "jobs.h"
+#include <stdio.h>
+#include <strings.h>
+#include <memory.h> // strcpy and memcpy
+
+/***********************************************
+ * Helper routines that manipulate the job list
+ **********************************************/
+
+struct job_t jobs[MAXJOBS]; /* The job list */
+static int nextjid = 1; /* next job ID to allocate */
+
+/* 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;
+ strncpy(jobs[i].cmdline, cmdline, MAXLINE);
+ 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
+ ******************************/
diff --git a/lab5_shelllab/jobs.h b/lab5_shelllab/jobs.h
new file mode 100644
index 0000000..0e69d87
--- /dev/null
+++ b/lab5_shelllab/jobs.h
@@ -0,0 +1,45 @@
+//-*-c++-*-
+#ifndef _jobs_h_
+#define _jobs_h_
+
+#include <sys/types.h> // needed for pid_t
+#include "globals.h"
+
+/* 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.
+ */
+
+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 */
+};
+extern struct job_t jobs[MAXJOBS]; /* The job list */
+
+
+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);
+
+
+#endif
diff --git a/lab5_shelllab/myint.cc b/lab5_shelllab/myint.cc
new file mode 100644
index 0000000..6eda5c1
--- /dev/null
+++ b/lab5_shelllab/myint.cc
@@ -0,0 +1,36 @@
+/*
+ * myint.c - Another handy routine for testing your tiny shell
+ *
+ * usage: myint <n>
+ * Sleeps for <n> seconds and sends SIGINT to itself.
+ *
+ */
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <signal.h>
+
+int main(int argc, char **argv)
+{
+ int i, secs;
+ pid_t pid;
+
+ if (argc != 2)
+ {
+ fprintf(stderr, "Usage: %s <n>\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/lab5_shelllab/myspin.cc b/lab5_shelllab/myspin.cc
new file mode 100644
index 0000000..a33d5f5
--- /dev/null
+++ b/lab5_shelllab/myspin.cc
@@ -0,0 +1,25 @@
+/*
+ * myspin.c - A handy program for testing your tiny shell
+ *
+ * usage: myspin <n>
+ * Sleeps for <n> seconds in 1-second chunks.
+ *
+ */
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+
+int main(int argc, char **argv)
+{
+ int i, secs;
+
+ if (argc != 2)
+ {
+ fprintf(stderr, "Usage: %s <n>\n", argv[0]);
+ exit(0);
+ }
+ secs = atoi(argv[1]);
+ for (i = 0; i < secs; i++)
+ sleep(1);
+ exit(0);
+}
diff --git a/lab5_shelllab/mysplit.cc b/lab5_shelllab/mysplit.cc
new file mode 100644
index 0000000..3560404
--- /dev/null
+++ b/lab5_shelllab/mysplit.cc
@@ -0,0 +1,36 @@
+/*
+ * mysplit.c - Another handy routine for testing your tiny shell
+ *
+ * usage: mysplit <n>
+ * Fork a child that spins for <n> seconds in 1-second chunks.
+ */
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <signal.h>
+
+int main(int argc, char **argv)
+{
+ int i, secs;
+
+ if (argc != 2)
+ {
+ fprintf(stderr, "Usage: %s <n>\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/lab5_shelllab/mystop.cc b/lab5_shelllab/mystop.cc
new file mode 100644
index 0000000..ac49d1e
--- /dev/null
+++ b/lab5_shelllab/mystop.cc
@@ -0,0 +1,36 @@
+/*
+ * mystop.c - Another handy routine for testing your tiny shell
+ *
+ * usage: mystop <n>
+ * Sleeps for <n> seconds and sends SIGTSTP to itself.
+ *
+ */
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <signal.h>
+
+int main(int argc, char **argv)
+{
+ int i, secs;
+ pid_t pid;
+
+ if (argc != 2)
+ {
+ fprintf(stderr, "Usage: %s <n>\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/lab5_shelllab/sdriver.pl b/lab5_shelllab/sdriver.pl
new file mode 100755
index 0000000..852bf3d
--- /dev/null
+++ b/lab5_shelllab/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;
diff --git a/lab5_shelllab/shellAutograder.py b/lab5_shelllab/shellAutograder.py
new file mode 100755
index 0000000..222d2bb
--- /dev/null
+++ b/lab5_shelllab/shellAutograder.py
@@ -0,0 +1,222 @@
+#!/usr/bin/env python3
+from __future__ import print_function
+import subprocess
+import sys
+import time
+import re
+import io
+import os
+
+testNumber = 10
+
+##
+## Be more forgiving on output comparison - remove blank spaces and put everything in lower case
+##
+def noBS(s):
+ return s.lower().translate(str.maketrans('', '', ' \n\t\r'))
+
+##
+## This test framework had some problems for test11, test12 and test13 of the standard
+## assignment. The following subprocess.Popen runs the jobs, but does not read
+## all the input and some processes are left around such that when the second test
+## is running, those processes would appear in the 'ps' output.
+##
+## I've changed the solution to read the full input and place it into a string
+## buffer before running the second test. This removes this minor glitch.
+##
+## dirk
+
+def runCmd(cmd):
+ proc = subprocess.Popen(cmd, bufsize=0, text=True, stdout=subprocess.PIPE)
+ try:
+ stdout, stderr = proc.communicate(timeout=20)
+ except subprocess.TimeoutExpired:
+ print("Timeout running command:", cmd)
+ proc.kill()
+ stdout = ""
+ stderr = ""
+ return stdout, stderr
+
+def runTrace(testNumber):
+ success=True
+ ref_out, ref_err = runCmd(["make", "rtest%02d"%(testNumber)])
+ test_out, test_err = runCmd(["make", "test%02d"%(testNumber)])
+
+ test=io.StringIO(test_out)
+ ref=io.StringIO(ref_out)
+
+ ref_out = ref_out.splitlines() if ref_out else [""]
+ ref_err = ref_err.splitlines() if ref_err else [""]
+ test_out = test_out.splitlines() if test_out else [""]
+ test_err = test_err.splitlines() if test_err else [""]
+
+ testLines = [line.strip() for line in test_out ]
+ refLines = [ line.strip() for line in ref_out ]
+ testErr = [ line.strip() for line in test_err ]
+ refErr = [ line.strip() for line in ref_err ]
+
+ if len(testLines) != len(refLines):
+ print("Your ./tsh output has a different number of lines than the reference output")
+ print("Your ./tsh printed", len(testLines), "lines to stdout")
+ print("The reference ./tshref printed", len(refLines), "lines to stdout")
+ print("")
+ print("Your ./tsh printed these lines to stdout", testLines)
+ print("Your ./tsh printed these lines to stderr", testErr)
+ print("")
+ print("The reference ./tshref printed these lines to stdout", refLines)
+ print("Your reference ./tshref printed these lines to stderr", refErr)
+ return False
+
+ testLine = test.readline().strip()
+ refLine = ref.readline()
+
+ if (testLine != './sdriver.pl -t trace%02d.txt -s ./tsh -a "-p"'%(testNumber)):
+ print("\tERROR: Bad first line in test.")
+ print("got *%s*"%(testLine))
+ return False
+
+ refLine = ref.readline()
+ testLine = test.readline()
+ while((testLine != "") or (refLine != "")):
+ if refLine.strip() == "tsh> /bin/ps a":
+ if noBS(testLine) != noBS(refLine):
+ print("\tERROR: \"%s\" <=!=> \"%s\""%(refLine.strip(),
+ testLine.strip()))
+ success = False
+ success = psScan(ref, test) and success
+ success = normalScan(refLine, testLine) and success
+ testLine = test.readline()
+ refLine = ref.readline()
+ return success
+
+
+def normalScan(refL, testL):
+ if refL == "":
+ print("\tERROR: ./tshref output has fewer lines than ./tsh output.")
+ return False
+ elif testL == "":
+ print("\tERROR: ./tsh output has fewer lines than ./tshref output.")
+ return False
+ elif noBS(testL) != noBS(refL):
+ noPIDtest = re.sub(r'\([0-9]+\)*', '(*PID*)', testL)
+ noPIDref = re.sub(r'\([0-9]+\)*', '(*PID*)', refL)
+ if(noBS(noPIDtest) != noBS(noPIDref)):
+ print("\t\"%s\" <=!=> \"%s\""%(noPIDtest.strip(), noPIDref.strip()))
+ return False
+ return True
+
+def isProgramWeCareAbout(line):
+ if "/bin/sh" in line or "/usr/bin/perl" in line:
+ return False
+
+ if "./mysplit " in line \
+ or "./myspin " in line \
+ or "./myint "in line \
+ or "./mystop " in line \
+ or "./tsh " in line:
+ return True
+ else:
+ return False
+
+
+def psScan(ref, test):
+ #
+ # This compares the process status(ps) of the reference solution and the test solution
+ # The process status (e.g. suspended, etc) should match
+ #
+ success=True
+ #Dict stores state, and number of lines like this we saw, for each cmd.
+ refDict = {}
+ testDict = {}
+
+ #Collect ref info.
+ refL = ref.readline()
+ psRefHeader = refL.strip()
+ refL = ref.readline()
+ while(("tsh>" not in refL) and refL!=""):
+ psLine = refL.strip().split(None, 4)
+ if isProgramWeCareAbout(psLine[4]):
+ if psLine[4] in refDict:
+ refDict[psLine[4]].append(psLine[2])
+ else:
+ refDict[psLine[4]] = [psLine[2]]
+ refL = ref.readline()
+
+ #Collect test info.
+ testL = test.readline()
+ if(testL.strip()!=psRefHeader):
+ print("\tERROR: Unexpected ps header in test.")
+ success=False
+ testL = test.readline()
+ while(("tsh>" not in testL) and testL!=""):
+ psLine = testL.strip().split(None, 4)
+ if isProgramWeCareAbout(psLine[4]):
+ if psLine[4] in testDict:
+ testDict[psLine[4]].append(psLine[2])
+ else:
+ testDict[psLine[4]] = [psLine[2]]
+ testL = test.readline()
+ #Now, comparing the two dicts.
+ for key in refDict.keys():
+ try:
+ if(refDict[key] != testDict[key]):
+ print("\tERROR: Status for (%s) does not match:"%(key))
+ print("\t\tReference Statuses: %s"%(refDict[key]))
+ print("\t\tYour Statuses: %s"%(testDict[key]))
+ success = False
+ except KeyError:
+ if "./tshref" in key:
+ continue
+ print("\tERROR: \"%s\" not found in test dict."%(key))
+ success = False
+
+ return success
+
+def isExecutable( filename:str ):
+ return os.path.isfile(filename) and os.access(filename, os.X_OK)
+
+NeededBinaries = [ "tsh", "myspin", "mystop", "myint", "mysplit", "tshref" ]
+
+if not all( [ isExecutable(binary) for binary in NeededBinaries ] ):
+ print("Building ./tsh and related files")
+ runCmd(["make"])
+ if not all( [ isExecutable(binary) for binary in NeededBinaries ] ):
+ print("Unable to build tsh so can't run grader")
+ sys.exit(200)
+
+if len(sys.argv) > 1:
+ for trace in sys.argv[1:]:
+ i = int(trace)
+ print("================================================================================")
+ print("Running trace %02d..."%(i))
+ if runTrace(i):
+ print("\tPassed.")
+ else:
+ print("\tFailed.")
+ sys.exit(100)
+else:
+ numPassed = 0
+ for i in range(16):
+ print("================================================================================")
+ print("Running trace %02d..."%(i+1))
+ if runTrace(i+1):
+ numPassed+=1
+ print("\tPassed.")
+ else:
+ print("\tFailed.")
+
+ print("Total Passed: %d/16\tGrade: "%(numPassed),end="")
+ if(numPassed>=16):
+ print("100%")
+ sys.exit(0)
+ elif(numPassed>=13):
+ print("90%")
+ elif(numPassed>=10):
+ print("80%")
+ elif(numPassed>=8):
+ print("60%")
+ elif(numPassed >= 5):
+ print("30%")
+ elif(numPassed >= 3):
+ print("10%")
+ sys.exit(100)
diff --git a/lab5_shelllab/trace01.txt b/lab5_shelllab/trace01.txt
new file mode 100644
index 0000000..9c8561e
--- /dev/null
+++ b/lab5_shelllab/trace01.txt
@@ -0,0 +1,5 @@
+#
+# trace01.txt - Properly terminate on EOF.
+#
+CLOSE
+WAIT
diff --git a/lab5_shelllab/trace02.txt b/lab5_shelllab/trace02.txt
new file mode 100644
index 0000000..e97643b
--- /dev/null
+++ b/lab5_shelllab/trace02.txt
@@ -0,0 +1,5 @@
+#
+# trace02.txt - Process builtin quit command.
+#
+quit
+WAIT
diff --git a/lab5_shelllab/trace03.txt b/lab5_shelllab/trace03.txt
new file mode 100644
index 0000000..be8869c
--- /dev/null
+++ b/lab5_shelllab/trace03.txt
@@ -0,0 +1,5 @@
+#
+# trace03.txt - Run a foreground job.
+#
+/bin/echo tsh> quit
+quit
diff --git a/lab5_shelllab/trace04.txt b/lab5_shelllab/trace04.txt
new file mode 100644
index 0000000..554df0e
--- /dev/null
+++ b/lab5_shelllab/trace04.txt
@@ -0,0 +1,5 @@
+#
+# trace04.txt - Run a background job.
+#
+/bin/echo -e tsh> ./myspin 1 \046
+./myspin 1 &
diff --git a/lab5_shelllab/trace05.txt b/lab5_shelllab/trace05.txt
new file mode 100644
index 0000000..03c1876
--- /dev/null
+++ b/lab5_shelllab/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/lab5_shelllab/trace06.txt b/lab5_shelllab/trace06.txt
new file mode 100644
index 0000000..bfa3890
--- /dev/null
+++ b/lab5_shelllab/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/lab5_shelllab/trace07.txt b/lab5_shelllab/trace07.txt
new file mode 100644
index 0000000..259a285
--- /dev/null
+++ b/lab5_shelllab/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/lab5_shelllab/trace08.txt b/lab5_shelllab/trace08.txt
new file mode 100644
index 0000000..49be19f
--- /dev/null
+++ b/lab5_shelllab/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/lab5_shelllab/trace09.txt b/lab5_shelllab/trace09.txt
new file mode 100644
index 0000000..340d998
--- /dev/null
+++ b/lab5_shelllab/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/lab5_shelllab/trace10.txt b/lab5_shelllab/trace10.txt
new file mode 100644
index 0000000..e7de996
--- /dev/null
+++ b/lab5_shelllab/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/lab5_shelllab/trace11.txt b/lab5_shelllab/trace11.txt
new file mode 100644
index 0000000..d2f8663
--- /dev/null
+++ b/lab5_shelllab/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/lab5_shelllab/trace12.txt b/lab5_shelllab/trace12.txt
new file mode 100644
index 0000000..88455e3
--- /dev/null
+++ b/lab5_shelllab/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/lab5_shelllab/trace13.txt b/lab5_shelllab/trace13.txt
new file mode 100644
index 0000000..d734cbc
--- /dev/null
+++ b/lab5_shelllab/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/lab5_shelllab/trace14.txt b/lab5_shelllab/trace14.txt
new file mode 100644
index 0000000..8086580
--- /dev/null
+++ b/lab5_shelllab/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/lab5_shelllab/trace15.txt b/lab5_shelllab/trace15.txt
new file mode 100644
index 0000000..2cc780e
--- /dev/null
+++ b/lab5_shelllab/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/lab5_shelllab/trace16.txt b/lab5_shelllab/trace16.txt
new file mode 100644
index 0000000..822ba14
--- /dev/null
+++ b/lab5_shelllab/trace16.txt
@@ -0,0 +1,16 @@
+#
+# trace16.txt - Tests whether the shell can handle SIGTSTP and SIGINT
+# signals that come from other processes instead of the terminal.
+#
+
+/bin/echo tsh> ./mystop 2
+./mystop 2
+
+SLEEP 3
+
+/bin/echo tsh> jobs
+jobs
+
+/bin/echo tsh> ./myint 2
+./myint 2
+
diff --git a/lab5_shelllab/tsh b/lab5_shelllab/tsh
new file mode 100755
index 0000000..c537355
--- /dev/null
+++ b/lab5_shelllab/tsh
Binary files differ
diff --git a/lab5_shelllab/tsh.cc b/lab5_shelllab/tsh.cc
new file mode 100644
index 0000000..0456927
--- /dev/null
+++ b/lab5_shelllab/tsh.cc
@@ -0,0 +1,416 @@
+//
+// tsh - A tiny shell program with job control
+//
+// Jack Sangdahl - @waffle87/jasa7131
+//
+
+using namespace std;
+
+#include <ctype.h>
+#include <errno.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <string>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include "globals.h"
+#include "helper-routines.h"
+#include "jobs.h"
+
+//
+// Needed global variable definitions
+//
+
+static char prompt[] = "tsh> ";
+int verbose = 0;
+
+//
+// You need to implement the functions eval, builtin_cmd, do_bgfg,
+// waitfg, sigchld_handler, sigstp_handler, sigint_handler
+//
+// The code below provides the "prototypes" for those functions
+// so that earlier code can refer to them. You need to fill in the
+// function bodies below.
+//
+
+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);
+
+//
+// Wrapper Functions for Error Handling
+//
+void Sigemptyset(sigset_t *set) {
+ if (sigemptyset(set) < 0)
+ unix_error("Sigemptyset error");
+ return;
+}
+
+void Sigaddset(sigset_t *set, int signum) {
+ if (sigaddset(set, signum) < 0)
+ unix_error("Sigaddset error");
+ return;
+}
+
+void Sigprocmask(int how, const sigset_t *set, sigset_t *oldset) {
+ if (sigprocmask(how, set, oldset) < 0)
+ unix_error("Sigprocmask error");
+ return;
+}
+
+pid_t Fork(void) {
+ pid_t pid;
+ if ((pid = fork()) < 0)
+ unix_error("Fork error");
+ return pid;
+}
+
+void Setpgid(pid_t pid, pid_t pgid) {
+ int rc;
+ if ((rc = setpgid(pid, pgid)) < 0)
+ unix_error("Setpgid error");
+ return;
+}
+
+//
+// main - The shell's main routine
+//
+int main(int argc, char **argv) {
+ 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 */
+ char c;
+ 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
+ //
+ for (;;) {
+ //
+ // Read command line
+ //
+ if (emit_prompt) {
+ printf("%s", prompt);
+ fflush(stdout);
+ }
+
+ char cmdline[MAXLINE];
+
+ if ((fgets(cmdline, MAXLINE, stdin) == NULL) && ferror(stdin)) {
+ app_error("fgets error");
+ }
+ //
+ // End of file? (did user type ctrl-d?)
+ //
+ if (feof(stdin)) {
+ fflush(stdout);
+ exit(0);
+ }
+
+ //
+ // Evaluate 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) {
+ /* Parse command line */
+ //
+ // The 'argv' vector is filled in by the parseline
+ // routine below. It provides the arguments needed
+ // for the execve() routine, which you'll need to
+ // use below to launch a process.
+ //
+ char *argv[MAXARGS];
+
+ // The 'bg' variable is TRUE if the job should run
+ // in background mode or FALSE if it should run in FG
+ bool bg = parseline(cmdline, argv);
+
+ // process id
+ pid_t pid;
+
+ // mask to block/unblock signals
+ sigset_t mask;
+
+ if (!argv[0])
+ // ignore empty lines
+ return;
+
+ // gnu.org/software/libc/manual/html_node/Signal-Sets.html
+ // initialise empty signal set
+ Sigemptyset(&mask);
+
+ // add SIGCHLD to signal set
+ Sigaddset(&mask, SIGCHLD);
+
+ // handle built-in commands
+ // builtin_cmd() returns 0 if not built in command
+ if (!builtin_cmd(argv)) {
+ // gnu.org/software/libc/manual/html_node/Process-Signal-Mask.html
+ // block SIGCHLD signal
+ Sigprocmask(SIG_BLOCK, &mask, 0);
+
+ // gnu.org/software/libc/manual/html_node/Creating-a-Process.html
+ // create child process (fork returns -1 on error)
+
+ if (!(pid = Fork())) {
+ // gnu.org/software/libc/manual/html_node/Process-Group-Functions.html
+ // assign value to child process
+ Setpgid(0, 0);
+
+ // gnu.org/software/libc/manual/html_node/Executing-a-File.html
+ // execute program or exit
+ if (execvp(argv[0], argv)) { // use vp to search $PATH for executables
+ printf("%s: Command not found\n", argv[0]);
+ exit(0);
+ }
+ }
+ if (!bg)
+ // run foreground job
+ addjob(jobs, pid, FG, cmdline);
+ else
+ // run background
+ addjob(jobs, pid, BG, cmdline);
+ // unblock for child
+ Sigprocmask(SIG_UNBLOCK, &mask, 0);
+ if (!bg)
+ // wait for foreground job to complete
+ waitfg(pid);
+ else
+ // otherwise print current background job info
+ printf("[%d] (%d) %s", pid2jid(pid), pid, cmdline);
+ }
+ return;
+}
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// builtin_cmd - If the user has typed a built-in command then execute
+// it immediately. The command name would be in argv[0] and
+// is a C string. We've cast this to a C++ string type to simplify
+// string comparisons; however, the do_bgfg routine will need
+// to use the argv array as well to look for a job number.
+//
+int builtin_cmd(char **argv) {
+ string cmd(argv[0]);
+ // exit on 'quit' command
+ if (cmd == "quit")
+ exit(0);
+
+ // execute program in background or foreground on 'bg' or 'fg' command
+ else if (cmd == "bg" || cmd == "fg") {
+ do_bgfg(argv);
+ return 1;
+ }
+
+ // print current jobs on 'jobs' command
+ else if (cmd == "jobs") {
+ listjobs(jobs);
+ return 1;
+
+ // ignore background process
+ } else if (cmd == "&")
+ return 1;
+
+ else
+ // not a built in command
+ return 0;
+}
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// do_bgfg - Execute the builtin bg and fg commands
+//
+void do_bgfg(char **argv) {
+ pid_t pid;
+ struct job_t *job = NULL;
+ string cmd(argv[0]);
+ /* Ignore command if no argument */
+ if (argv[1] == NULL) {
+ printf("%s command requires PID or %%jobid argument\n", argv[0]);
+ return;
+ }
+ /* Parse the required PID or %JID arg */
+ if (isdigit(argv[1][0])) {
+ pid = atoi(argv[1]);
+ if (!(job = getjobpid(jobs, pid))) {
+ printf("(%d): no such process\n", pid);
+ return;
+ }
+ } else if (argv[1][0] == '%') {
+ int jid = atoi(&argv[1][1]);
+ if (!(job = getjobjid(jobs, jid))) {
+ printf("%s: no such job\n", argv[1]);
+ return;
+ }
+ } else {
+ printf("%s: argument must be a PID or %%jobid\n", argv[0]);
+ return;
+ }
+ if (job) {
+ pid = job->pid;
+ if (job->state == ST) { // job is stopped
+ if (cmd == "bg") {
+ printf("[%d] (%d) %s", job->jid, job->pid, job->cmdline);
+ job->state = BG;
+ kill(-pid, SIGCONT); // resume process (group)
+ }
+ if (cmd == "fg") {
+ job->state = FG;
+ kill(-pid, SIGCONT); // resume process (group)
+ waitfg(job->pid); // and only process foreground job
+ }
+ }
+ if (job->state == BG) {
+ if (cmd == "fg") {
+ job->state = FG; // "re-update" state to foreground if necessary
+ waitfg(job->pid);
+ }
+ }
+ }
+ return;
+}
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// waitfg - Block until process pid is no longer the foreground process
+//
+void waitfg(pid_t pid) {
+ struct job_t *job = getjobpid(jobs, pid);
+ while (job->state == FG)
+ sleep(1); // nothing else can run while foreground process exists
+ 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) {
+ pid_t pid;
+ int status;
+ while ((pid = waitpid(-1, &status, WNOHANG | WUNTRACED)) > 0) {
+ if (WIFEXITED(status)) // child process exited normally
+ deletejob(jobs, pid);
+ if (WIFSIGNALED(status)) { // child process was terminated via a signal
+ printf("Job [%d] (%d) terminated by signal %d\n", pid2jid(pid), pid,
+ WTERMSIG(status)); // WTERMSIG macro returns signal number
+ deletejob(jobs, pid);
+ }
+ if (WIFSTOPPED(status)) { // child process was suspended
+ getjobpid(jobs, pid)->state = ST;
+ printf("Job [%d] (%d) stopped by signal %d\n", pid2jid(pid), pid,
+ WSTOPSIG(status)); // WSTOPSIG macro returns signal number
+ }
+ }
+ if (pid && errno != ECHILD) // make sure process exists and has children
+ unix_error("sigchld_handler: waitpid error");
+ 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) {
+ pid_t pid = fgpid(jobs);
+ if (fgpid(jobs)) // check pid exists
+ kill(-pid, SIGINT); // terminate process (group)
+ 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) {
+ pid_t pid = fgpid(jobs);
+ if (pid) // check pid exists
+ kill(-pid, SIGTSTP); // stop the process (group), but don't terminate it
+ return;
+}
+
+/*********************
+ * End signal handlers
+ *********************/
diff --git a/lab5_shelllab/tshref b/lab5_shelllab/tshref
new file mode 100755
index 0000000..5355668
--- /dev/null
+++ b/lab5_shelllab/tshref
Binary files differ
diff --git a/lab5_shelllab/tshref.out b/lab5_shelllab/tshref.out
new file mode 100644
index 0000000..d4abb8b
--- /dev/null
+++ b/lab5_shelllab/tshref.out
@@ -0,0 +1,230 @@
+make[2]: Entering directory '/home/cs2400/csci-2400-labs-exams-extras/CU-labs/shlab-in-c++/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] (8654) ./myspin 1 &
+./myspin: Command not found
+./sdriver.pl -t trace05.txt -s ./tsh -a "-p"
+#
+# trace05.txt - Process jobs builtin command.
+#
+tsh> ./myspin 2 &
+[1] (8662) ./myspin 2 &
+tsh> ./myspin 3 &
+[2] (8664) ./myspin 3 &
+./myspin: Command not found
+tsh> jobs
+./myspin: Command not found
+./sdriver.pl -t trace06.txt -s ./tsh -a "-p"
+#
+# trace06.txt - Forward SIGINT to foreground job.
+#
+tsh> ./myspin 4
+./myspin: Command not found
+./sdriver.pl -t trace07.txt -s ./tsh -a "-p"
+#
+# trace07.txt - Forward SIGINT only to foreground job.
+#
+tsh> ./myspin 4 &
+[1] (9175) ./myspin 4 &
+tsh> ./myspin 5
+./myspin: Command not found
+./myspin: Command not found
+tsh> jobs
+./sdriver.pl -t trace08.txt -s ./tsh -a "-p"
+#
+# trace08.txt - Forward SIGTSTP only to foreground job.
+#
+tsh> ./myspin 4 &
+[1] (9183) ./myspin 4 &
+tsh> ./myspin 5
+./myspin: Command not found
+./myspin: Command not found
+tsh> jobs
+./sdriver.pl -t trace09.txt -s ./tsh -a "-p"
+#
+# trace09.txt - Process bg builtin command
+#
+tsh> ./myspin 4 &
+[1] (9193) ./myspin 4 &
+tsh> ./myspin 5
+./myspin: Command not found
+./myspin: Command not found
+tsh> jobs
+tsh> bg %2
+%2: No such job
+tsh> jobs
+./sdriver.pl -t trace10.txt -s ./tsh -a "-p"
+#
+# trace10.txt - Process fg builtin command.
+#
+tsh> ./myspin 4 &
+[1] (9205) ./myspin 4 &
+./myspin: Command not found
+tsh> fg %1
+%1: No such job
+tsh> jobs
+tsh> fg %1
+%1: No such job
+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
+./mysplit: Command not found
+tsh> /bin/ps a
+ PID TTY STAT TIME COMMAND
+ 1010 ttyS0 Ss+ 0:00 /sbin/agetty -o -p -- \u --keep-baud 115200,38400,9600 ttyS0 vt220
+ 1012 tty1 Ss+ 0:00 /sbin/agetty -o -p -- \u --noclear tty1 linux
+ 3993 pts/0 Ss 0:00 -bash
+ 8054 pts/0 S+ 0:00 make
+ 8066 pts/0 S+ 0:00 /bin/sh -c (cd src; make)
+ 8067 pts/0 S+ 0:00 make
+ 8603 pts/0 S+ 0:00 /bin/sh -c make tests > tshref.out 2>&1
+ 8604 pts/0 S+ 0:00 make tests
+ 9211 pts/0 S+ 0:00 /bin/sh -c ./sdriver.pl -t trace11.txt -s ./tsh -a "-p"
+ 9212 pts/0 S+ 0:00 /usr/bin/perl ./sdriver.pl -t trace11.txt -s ./tsh -a -p
+ 9213 pts/0 S+ 0:00 ./tsh -p
+ 9218 pts/0 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
+./mysplit: Command not found
+tsh> jobs
+tsh> /bin/ps a
+ PID TTY STAT TIME COMMAND
+ 1010 ttyS0 Ss+ 0:00 /sbin/agetty -o -p -- \u --keep-baud 115200,38400,9600 ttyS0 vt220
+ 1012 tty1 Ss+ 0:00 /sbin/agetty -o -p -- \u --noclear tty1 linux
+ 3993 pts/0 Ss 0:00 -bash
+ 8054 pts/0 S+ 0:00 make
+ 8066 pts/0 S+ 0:00 /bin/sh -c (cd src; make)
+ 8067 pts/0 S+ 0:00 make
+ 8603 pts/0 S+ 0:00 /bin/sh -c make tests > tshref.out 2>&1
+ 8604 pts/0 S+ 0:00 make tests
+ 9219 pts/0 S+ 0:00 /bin/sh -c ./sdriver.pl -t trace12.txt -s ./tsh -a "-p"
+ 9220 pts/0 S+ 0:00 /usr/bin/perl ./sdriver.pl -t trace12.txt -s ./tsh -a -p
+ 9221 pts/0 S+ 0:00 ./tsh -p
+ 9226 pts/0 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
+./mysplit: Command not found
+tsh> jobs
+tsh> /bin/ps a
+ PID TTY STAT TIME COMMAND
+ 1010 ttyS0 Ss+ 0:00 /sbin/agetty -o -p -- \u --keep-baud 115200,38400,9600 ttyS0 vt220
+ 1012 tty1 Ss+ 0:00 /sbin/agetty -o -p -- \u --noclear tty1 linux
+ 3993 pts/0 Ss 0:00 -bash
+ 8054 pts/0 S+ 0:00 make
+ 8066 pts/0 S+ 0:00 /bin/sh -c (cd src; make)
+ 8067 pts/0 S+ 0:00 make
+ 8603 pts/0 S+ 0:00 /bin/sh -c make tests > tshref.out 2>&1
+ 8604 pts/0 S+ 0:00 make tests
+ 9227 pts/0 S+ 0:00 /bin/sh -c ./sdriver.pl -t trace13.txt -s ./tsh -a "-p"
+ 9228 pts/0 S+ 0:00 /usr/bin/perl ./sdriver.pl -t trace13.txt -s ./tsh -a -p
+ 9229 pts/0 S+ 0:00 ./tsh -p
+ 9237 pts/0 R 0:00 /bin/ps a
+tsh> fg %1
+%1: No such job
+tsh> /bin/ps a
+ PID TTY STAT TIME COMMAND
+ 1010 ttyS0 Ss+ 0:00 /sbin/agetty -o -p -- \u --keep-baud 115200,38400,9600 ttyS0 vt220
+ 1012 tty1 Ss+ 0:00 /sbin/agetty -o -p -- \u --noclear tty1 linux
+ 3993 pts/0 Ss 0:00 -bash
+ 8054 pts/0 S+ 0:00 make
+ 8066 pts/0 S+ 0:00 /bin/sh -c (cd src; make)
+ 8067 pts/0 S+ 0:00 make
+ 8603 pts/0 S+ 0:00 /bin/sh -c make tests > tshref.out 2>&1
+ 8604 pts/0 S+ 0:00 make tests
+ 9227 pts/0 S+ 0:00 /bin/sh -c ./sdriver.pl -t trace13.txt -s ./tsh -a "-p"
+ 9228 pts/0 S+ 0:00 /usr/bin/perl ./sdriver.pl -t trace13.txt -s ./tsh -a -p
+ 9229 pts/0 S+ 0:00 ./tsh -p
+ 9240 pts/0 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] (9247) ./myspin 4 &
+tsh> fg
+fg command requires PID or %jobid argument
+./myspin: Command not found
+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
+%1: No such job
+tsh> bg %2
+%2: No such job
+tsh> bg %1
+%1: No such job
+tsh> jobs
+./sdriver.pl -t trace15.txt -s ./tsh -a "-p"
+#
+# trace15.txt - Putting it all together
+#
+tsh> ./bogus
+./bogus: Command not found
+tsh> ./myspin 10
+./myspin: Command not found
+tsh> ./myspin 3 &
+[1] (9268) ./myspin 3 &
+tsh> ./myspin 4 &
+[2] (9270) ./myspin 4 &
+./myspin: Command not found
+tsh> jobs
+[2] (9270) Running ./myspin 4 &
+./myspin: Command not found
+tsh> fg %1
+%1: No such job
+tsh> jobs
+tsh> bg %3
+%3: No such job
+tsh> bg %1
+%1: No such job
+tsh> jobs
+tsh> fg %1
+%1: No such job
+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
+./mystop: Command not found
+tsh> jobs
+tsh> ./myint 2
+./myint: Command not found
+make[2]: Leaving directory '/home/cs2400/csci-2400-labs-exams-extras/CU-labs/shlab-in-c++/src'