shlab-handout/mystop.c
/*
* 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);
}
shlab-handout/trace01.txt
#
# trace01.txt - Process builtin quit command.
#
quit
WAIT
shlab-handout/trace09.txt
#
# trace09.txt - Forward SIGTSTP only to foreground job.
#
./myspin 4 &
./myspin 5
SLEEP 2
TSTP
jobs
shlab-handout/trace20.txt
#
# trace20.txt - I/O redirection, both input and output
#
./myread 40 < myread.c > /dev/null
shlab-handout/trace15.txt
#
# trace15.txt - Simple error handling
#
./bogus
./myspin 4 &
SLEEP 1
fg
bg
fg a
bg a
fg 9999999
bg 9999999
fg %2
fg %1
SLEEP 1
TSTP
bg %2
bg %1
jobs
shlab-handout/trace02.txt
#
# trace02.txt - Run a foreground job several times.
#
/bin/ls
SLEEP 1
/bin/ls
SLEEP 1
/bin/ls
shlab-handout/trace03.txt
#
# trace03.txt - Run a background job.
#
./myspin 1 &
/bin/ls
shlab-handout/checktsh.py
#!/usr/bin/python3
# Autolab autograder for CSCI 2467 (M. Toups / Fall 2018)
import subprocess # to launch tests
import re # regular expressions please
import difflib # to show differences
import signal # to make sure grading script doesn't get stopped by SIGTSTP
import sys
# list of trace files to test on
tracefiles = ["trace01", "trace02", "trace03",
"trace04", "trace05", "trace06",
"trace07", "trace08", "trace09",
"trace10", "trace11", "trace12",
"trace13", "trace14", "trace15",
"trace16", "trace17", "trace18",
"trace19", "trace20"]
grades = {} # use a dict for results
test_max_time = 25 # maximum number of seconds for each test before timeout
signal.signal(signal.SIGTSTP, signal.SIG_IGN) # ignore SIGTSTP (does not affect children, but helps if their buggy code sends us a SIGTSTP)
for trace in tracefiles:
try:
studentoutput = subprocess.check_output(['./sdriver.pl -t ' + str(trace) + ".txt -s ./tsh -a '-p' "], timeout=test_max_time, shell=True)
refoutput = subprocess.check_output(['./sdriver.pl -t ' + str(trace) + ".txt -s ./tshref -a '-p' "], timeout=test_max_time, shell=True)
sout_mod = re.sub("[\(].*?[\)]", "(PID)", studentoutput.decode("utf-8")) # get rid of PIDS
sout_mod = re.sub(r'\s+$', '', sout_mod, flags=re.M) # remove any trailing whitespace
rout_mod = re.sub("[\(].*?[\)]", "(PID)", refoutput.decode("utf-8")) # get rid of PIDS
rout_mod = re.sub(r'\s+$', '', rout_mod, flags=re.M) # remove any trailing whitespace
if trace == "trace12" or trace == "trace13" or trace == "trace14": # for these tests, remove PIDs from ps output
sout_mod = re.sub("(.\d+\s+\d+)\s+(\w.+\s+\w.\s+.*)", r" PID PID \2 ", sout_mod, flags=re.M) # add a dot in case autograder user gets truncated with a +
rout_mod = re.sub("(.\d+\s+\d+)\s+(\w.+\s+\w.\s+.*)", r" PID PID \2 ", rout_mod, flags=re.M)
if trace == "trace15" or trace == "trace16": # ignore trailing period or trailing space
sout_mod = re.sub("\.*\s*$","",sout_mod,flags=re.M)
rout_mod = re.sub("\.*\s*$","",rout_mod,flags=re.M)
if sout_mod.lower() == rout_mod.lower(): # compare them case-insensitively
print(trace + " outputs matched (2 points)") # SUCCESS
grades[trace] = 2
else:
diffobj = difflib.Differ() # make a nice diff-style output for students to see what didn't match
diff = diffobj.compare(sout_mod.splitlines(keepends=True), rout_mod.splitlines(keepends=True))
grades[trace] = 0
print("------------ ERROR: " + trace + " outputs DIFFER (0 points) ------------------------")
sys.stdout.writelines(list(diff))
print("\n------------ end differences for " + trace + " ------------------------------------")
except subprocess.CalledProcessError as e:
grades[trace] = 0
print('Failed test: ' + str(trace))
print(e.output.decode("utf-8"))
except subprocess.TimeoutExpired:
grades[trace] = 0
print('Failed test: ' + str(trace) + ' (Test timed out)')
print("\n\n==== SHELL LAB SCORE SUMMARY ====\n")
for trace in tracefiles:
if grades[trace] == 2:
print("[*PASSED*] (2) " + trace)
else:
print("[ FAILED ] (0) " + trace)
print("\n\n=== SHELL LAB SCORE: " + str(sum(grades.values())) + " ===\n")
shlab-handout/myread.c
/*
* myread.c - A handy program for testing your tiny shell
*
* usage: myread
* Read a maximum of n bytes from standard input.
*
* (Total bytes read will be either the max value n, or
* the number of bytes of input, whichever is lower.)
*
*/
#include
#include
#include
int main(int argc, char **argv)
{
int i, n;
char c;
if (argc != 2) {
fprintf(stderr, "Usage: %s \n", argv[0]);
exit(0);
}
n = atoi(argv[1]);
for (i=0; (c=fgetc(stdin))!=EOF && n>0; i++, n--)
printf(" character read: %c \n", c);
fprintf(stderr, "Read %d bytes from standard input.\n", i);
exit(0);
}
shlab-handout/trace08.txt
#
# trace08.txt - Forward SIGINT only to foreground job.
#
./myspin 4 &
./myspin 5
SLEEP 2
INT
jobs
shlab-handout/trace04.txt
#
# trace04.txt - Process jobs builtin command.
#
./myspin 2 &
./myspin 3 &
jobs
shlab-handout/fork.o
shlab-handout/trace13.txt
#
# trace13.txt - Forward SIGTSTP to every process in foreground process group
#
./mysplit 4
SLEEP 2
TSTP
jobs
/bin/ps -o pid,ppid,ruser,stat,args -C mysplit
shlab-handout/trace11.txt
#
# trace11.txt - Process fg builtin command.
#
./myspin 4 &
SLEEP 1
fg %1
SLEEP 1
TSTP
jobs
fg %1
jobs
shlab-handout/trace17.txt
#
# trace17.txt - Tests whether the shell can handle SIGTSTP and SIGINT
# signals that come from other processes instead of the terminal.
#
./mystop 2
SLEEP 3
jobs
./myint 2
shlab-handout/trace12.txt
#
# trace12.txt - Forward SIGINT to every process in foreground process group
#
./mysplit 4
SLEEP 2
INT
/bin/ps -o pid,ppid,ruser,stat,args -C mysplit
shlab-handout/tsh.c
/*
* tsh - A tiny shell program with job control
*
*
*/
#include
#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 do_redirect(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 */
/* Ignoring these signals simplifies reading from stdin/stdout */
Signal(SIGTTIN, SIG_IGN); /* ignore SIGTTIN */
Signal(SIGTTOU, SIG_IGN); /* ignore SIGTTOU */
/* 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...