/*
* COMP 321 Project 4: Shell
*
* This program implements a tiny shell with job control.
*
* <Put your name(s) and NetID(s) here>
*/
#include <sys/types.h>
#include <sys/wait.h>
#include <assert.h>
#include <ctype.h>
#include <errno.h>
#include <signal.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
// You may assume that these constants are large enough.
#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
// The job states are:
#define UNDEF 0 // undefined
#define FG 1 // running in foreground
#define BG 2 // running in background
#define ST 3 // stopped
/*
* The job state transitions and enabling actions are:
* FG -> ST : ctrl-z
* ST -> FG : fg command
* ST -> BG : bg command
* BG -> FG : fg command
* At most one job can be in the FG state.
*/
struct Job {
pid_t pid; // job PID
int jid; // job ID [1, 2, ...]
int state; // UNDEF, FG, BG, or ST
char cmdline[MAXLINE]; // command line
};
typedef volatile struct Job *JobP;
/*
* Define the jobs list using the "volatile" qualifier because it is accessed
* by a signal handler (as well as the main program).
*/
static volatile struct Job jobs[MAXJOBS];
static int nextjid = 1; // next job ID to allocate
extern char **environ; // defined by libc
static char prompt[] = "tsh> "; // command line prompt (DO NOT CHANGE)
static bool verbose = false; // If true, print additional output.
/*
* The following array can be used to map a signal number to its name.
* This mapping is valid for x86(-64)/Linux systems, such as CLEAR.
* The mapping for other versions of Unix, such as FreeBSD, Mac OS X, or
* Solaris, differ!
*/
static const char *const signame[NSIG] = {
"Signal 0",
"HUP", /* SIGHUP */
"INT", /* SIGINT */
"QUIT", /* SIGQUIT */
"ILL", /* SIGILL */
"TRAP", /* SIGTRAP */
"ABRT", /* SIGABRT */
"BUS", /* SIGBUS */
"FPE", /* SIGFPE */
"KILL", /* SIGKILL */
"USR1", /* SIGUSR1 */
"SEGV", /* SIGSEGV */
"USR2", /* SIGUSR2 */
"PIPE", /* SIGPIPE */
"ALRM", /* SIGALRM */
"TERM", /* SIGTERM */
"STKFLT", /* SIGSTKFLT */
"CHLD", /* SIGCHLD */
"CONT", /* SIGCONT */
"STOP", /* SIGSTOP */
"TSTP", /* SIGTSTP */
"TTIN", /* SIGTTIN */
"TTOU", /* SIGTTOU */
"URG", /* SIGURG */
"XCPU", /* SIGXCPU */
"XFSZ", /* SIGXFSZ */
"VTALRM", /* SIGVTALRM */
"PROF", /* SIGPROF */
"WINCH", /* SIGWINCH */
"IO", /* SIGIO */
"PWR", /* SIGPWR */
"Signal 31"
};
// You must implement the following functions:
static int builtin_cmd(char **argv);
static void do_bgfg(char **argv);
static void eval(const char *cmdline);
static void initpath(const char *pathstr);
static void waitfg(pid_t pid);
static void sigchld_handler(int signum);
static void sigint_handler(int signum);
static void sigtstp_handler(int signum);
// We are providing the following functions to you:
static int parseline(const char *cmdline, char **argv);
static void sigquit_handler(int signum);
static int addjob(JobP jobs, pid_t pid, int state, const char *cmdline);
static void clearjob(JobP job);
static int deletejob(JobP jobs, pid_t pid);
static pid_t fgpid(JobP jobs);
static JobP getjobjid(JobP jobs, int jid);
static JobP getjobpid(JobP jobs, pid_t pid);
static void initjobs(JobP jobs);
static void listjobs(JobP jobs);
static int maxjid(JobP jobs);
static int pid2jid(pid_t pid);
static void app_error(const char *msg);
static void unix_error(const char *msg);
static void usage(void);
typedef void handler_t(int);
static handler_t *Signal(int signum, handler_t *handler);
static void Sio_error(char s[]);
static ssize_t Sio_putl(long v);
static ssize_t Sio_puts(char s[]);
static void sio_error(char s[]);
static void sio_ltoa(long v, char s[], int b);
static ssize_t sio_putl(long v);
static ssize_t sio_puts(char s[]);
static void sio_reverse(char s[]);
static size_t sio_strlen(char s[]);
/*
* main - The shell's main routine
*
* Requires:
* <???>
*
* Effects:
* <???>
*/
int
main(int argc, char **argv)
{
int c;
char cmdline[MAXLINE];
char *path = NULL;
bool emit_prompt = true; // Emit a prompt by 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")) != -1) {
switch (c) {
case 'h': // Print a help message.
usage();
break;
case 'v': // Emit additional diagnostic info.
verbose = true;
break;
case 'p': // Don't print a prompt.
// This is handy for automatic testing.
emit_prompt = false;
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 search path.
initpath(path);
// Initialize the jobs list.
initjobs(jobs);
// Execute the shell's read/eval loop.
while (true) {
// Read the command line.
if (emit_prompt) {
}
if ((fgets(cmdline
, MAXLINE
, stdin
) == NULL
) && ferror(stdin
)) app_error("fgets error");
if (feof(stdin
)) { // End of file (ctrl-d) }
// Evaluate the command line.
eval(cmdline);
}
// 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.
*
* Requires:
* <???>
*
* Effects:
* <???>
*/
static void
eval(const char *cmdline)
{
// Prevent an "unused parameter" warning. REMOVE THIS STATEMENT!
(void)cmdline;
}
/*
* parseline - Parse the command line and build the argv array.
*
* Requires:
* "cmdline" is a NUL ('\0') terminated string with a trailing
* '\n' character. "cmdline" must contain less than MAXARGS
* arguments.
*
* Effects:
* Builds "argv" array from space delimited arguments on the command line.
* The final element of "argv" is set to NULL. Characters enclosed in
* single quotes are treated as a single argument. Returns true if
* the user has requested a BG job and false if the user has requested
* a FG job.
*/
static int
parseline(const char *cmdline, char **argv)
{
int argc; // number of args
int bg; // background job?
static char array[MAXLINE]; // local copy of command line
char *buf = array; // ptr that traverses command line
char *delim; // points to first space delimiter
// Replace trailing '\n' with space.
// Ignore leading spaces.
while (*buf != '\0' && *buf == ' ')
buf++;
// Build the argv list.
argc = 0;
if (*buf == '\'') {
buf++;
} else
while (delim != NULL) {
argv[argc++] = buf;
*delim = '\0';
buf = delim + 1;
while (*buf != '\0' && *buf == ' ') // Ignore spaces.
buf++;
if (*buf == '\'') {
buf++;
} else
}
argv[argc] = NULL;
// Ignore blank line.
if (argc == 0)
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.
*
* Requires:
* <???>
*
* Effects:
* <???>
*/
static int
builtin_cmd(char **argv)
{
// Prevent an "unused parameter" warning. REMOVE THIS STATEMENT!
(void)argv;
return (0); // This is not a built-in command.
}
/*
* do_bgfg - Execute the built-in bg and fg commands.
*
* Requires:
* <???>
*
* Effects:
* <???>
*/
static void
do_bgfg(char **argv)
{
// Prevent an "unused parameter" warning. REMOVE THIS STATEMENT!
(void)argv;
}
/*
* waitfg - Block until process pid is no longer the foreground process.
*
* Requires:
* <???>
*
* Effects:
* <???>
*/
static void
waitfg(pid_t pid)
{
// Prevent an "unused parameter" warning. REMOVE THIS STATEMENT!
(void)pid;
}
/*
* initpath - Perform all necessary initialization of the search path,
* which may be simply saving the path.
*
* Requires:
* "pathstr" is a valid search path.
*
* Effects:
* <???>
*/
static void
initpath(const char *pathstr)
{
// Prevent an "unused parameter" warning. REMOVE THIS STATEMENT!
(void)pathstr;
}
/*
* The signal handlers follow.
*/
/*
* 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.
*
* Requires:
* <???>
*
* Effects:
* <???>
*/
static void
sigchld_handler(int signum)
{
// Prevent an "unused parameter" warning.
(void)signum;
}
/*
* sigint_handler - The kernel sends a SIGINT to the shell whenever the
* user types ctrl-c at the keyboard. Catch it and send it along
* to the foreground job.
*
* Requires:
* <???>
*
* Effects:
* <???>
*/
static void
sigint_handler(int signum)
{
// Prevent an "unused parameter" warning.
(void)signum;
}
/*
* 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.
*
* Requires:
* <???>
*
* Effects:
* <???>
*/
static void
sigtstp_handler(int signum)
{
// Prevent an "unused parameter" warning.
(void)signum;
}
/*
* sigquit_handler - The driver program can gracefully terminate the
* child shell by sending it a SIGQUIT signal.
*
* Requires:
* "signum" is SIGQUIT.
*
* Effects:
* Terminates the program.
*/
static void
sigquit_handler(int signum)
{
// Prevent an "unused parameter" warning.
(void)signum;
Sio_puts("Terminating after receipt of SIGQUIT signal\n");
_exit(1);
}
/*
* This comment marks the end of the signal handlers.
*/
/*
* The following helper routines manipulate the jobs list.
*/
/*
* Requires:
* "job" points to a job structure.
*
* Effects:
* Clears the fields in the referenced job structure.
*/
static void
clearjob(JobP job)
{
job->pid = 0;
job->jid = 0;
job->state = UNDEF;
job->cmdline[0] = '\0';
}
/*
* Requires:
* "jobs" points to an array of MAXJOBS job structures.
*
* Effects:
* Initializes the jobs list to an empty state.
*/
static void
initjobs(JobP jobs)
{
int i;
for (i = 0; i < MAXJOBS; i++)
clearjob(&jobs[i]);
}
/*
* Requires:
* "jobs" points to an array of MAXJOBS job structures.
*
* Effects:
* Returns the largest allocated job ID.
*/
static int
maxjid(JobP jobs)
{
int i, max = 0;
for (i = 0; i < MAXJOBS; i++)
if (jobs[i].jid > max)
max = jobs[i].jid;
return (max);
}
/*
* Requires:
* "jobs" points to an array of MAXJOBS job structures, and "cmdline" is
* a properly terminated string.
*
* Effects:
* Adds a job to the jobs list.
*/
static int
addjob(JobP jobs, pid_t pid, int state, const 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;
// Remove the "volatile" qualifier using a cast.
strcpy((char *)jobs
[i
].
cmdline, cmdline
); if (verbose) {
printf("Added job [%d] %d %s\n", jobs
[i
].
jid, (int)jobs[i].pid, jobs[i].cmdline);
}
return (1);
}
}
printf("Tried to create too many jobs\n"); return (0);
}
/*
* Requires:
* "jobs" points to an array of MAXJOBS job structures.
*
* Effects:
* Deletes a job from the jobs list whose PID equals "pid".
*/
static int
deletejob(JobP 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);
}
/*
* Requires:
* "jobs" points to an array of MAXJOBS job structures.
*
* Effects:
* Returns the PID of the current foreground job or 0 if no foreground
* job exists.
*/
static pid_t
fgpid(JobP jobs)
{
int i;
for (i = 0; i < MAXJOBS; i++)
if (jobs[i].state == FG)
return (jobs[i].pid);
return (0);
}
/*
* Requires:
* "jobs" points to an array of MAXJOBS job structures.
*
* Effects:
* Returns a pointer to the job structure with process ID "pid" or NULL if
* no such job exists.
*/
static JobP
getjobpid(JobP 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);
}
/*
* Requires:
* "jobs" points to an array of MAXJOBS job structures.
*
* Effects:
* Returns a pointer to the job structure with job ID "jid" or NULL if no
* such job exists.
*/
static JobP
getjobjid(JobP 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);
}
/*
* Requires:
* Nothing.
*
* Effects:
* Returns the job ID for the job with process ID "pid" or 0 if no such
* job exists.
*/
static 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);
}
/*
* Requires:
* "jobs" points to an array of MAXJOBS job structures.
*
* Effects:
* Prints the jobs list.
*/
static void
listjobs(JobP jobs)
{
int i;
for (i = 0; i < MAXJOBS; i++) {
if (jobs[i].pid != 0) {
printf("[%d] (%d) ", jobs
[i
].
jid, (int)jobs
[i
].
pid); switch (jobs[i].state) {
case BG:
break;
case FG:
break;
case ST:
break;
default:
printf("listjobs: Internal error: " "job[%d].state=%d ", i, jobs[i].state);
}
printf("%s", jobs
[i
].
cmdline); }
}
}
/*
* This comment marks the end of the jobs list helper routines.
*/
/*
* Other helper routines follow.
*/
/*
* Requires:
* Nothing.
*
* Effects:
* Prints a help message.
*/
static 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"); }
/*
* Requires:
* "msg" is a properly terminated string.
*
* Effects:
* Prints a Unix-style error message and terminates the program.
*/
static void
unix_error(const char *msg)
{
}
/*
* Requires:
* "msg" is a properly terminated string.
*
* Effects:
* Prints "msg" and terminates the program.
*/
static void
app_error(const char *msg)
{
}
/*
* Requires:
* "signum" is a valid signal number, and "handler" is a valid signal
* handler.
*
* Effects:
* Registers a new handler for the specified signal, returning the old
* handler. Behaves similarly to "sigset" but restarts system calls
* when possible.
*/
static 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);
}
/*
* Requires:
* The character array "s" is sufficiently large to store the ASCII
* representation of the long "v" in base "b".
*
* Effects:
* Converts a long "v" to a base "b" string, storing that string in the
* character array "s" (from K&R). This function can be safely called by
* a signal handler.
*/
static void
sio_ltoa(long v, char s[], int b)
{
int c, i = 0;
do
s[i++] = (c = v % b) < 10 ? c + '0' : c - 10 + 'a';
while ((v /= b) > 0);
s[i] = '\0';
sio_reverse(s);
}
/*
* Requires:
* "s" is a properly terminated string.
*
* Effects:
* Reverses a string (from K&R). This function can be safely called by a
* signal handler.
*/
static void
sio_reverse(char s[])
{
int c, i, j;
for (i = 0, j = sio_strlen(s) - 1; i < j; i++, j--) {
c = s[i];
s[i] = s[j];
s[j] = c;
}
}
/*
* Requires:
* "s" is a properly terminated string.
*
* Effects:
* Computes and returns the length of the string "s". This function can be
* safely called by a signal handler.
*/
static size_t
sio_strlen(char s[])
{
size_t i = 0;
while (s[i] != '\0')
i++;
return (i);
}
/*
* Requires:
* None.
*
* Effects:
* Prints the long "v" to stdout using only functions that can be safely
* called by a signal handler, and returns either the number of characters
* printed or -1 if the long could not be printed.
*/
static ssize_t
sio_putl(long v)
{
char s[128];
sio_ltoa(v, s, 10);
return (sio_puts(s));
}
/*
* Requires:
* "s" is a properly terminated string.
*
* Effects:
* Prints the string "s" to stdout using only functions that can be safely
* called by a signal handler, and returns either the number of characters
* printed or -1 if the string could not be printed.
*/
static ssize_t
sio_puts(char s[])
{
return (write(STDOUT_FILENO, s, sio_strlen(s)));
}
/*
* Requires:
* "s" is a properly terminated string.
*
* Effects:
* Prints the string "s" to stdout using only functions that can be safely
* called by a signal handler, and exits the program.
*/
static void
sio_error(char s[])
{
sio_puts(s);
_exit(1);
}
/*
* Requires:
* None.
*
* Effects:
* Prints the long "v" to stdout using only functions that can be safely
* called by a signal handler. Either returns the number of characters
* printed or exits if the long could not be printed.
*/
static ssize_t
Sio_putl(long v)
{
ssize_t n;
if ((n = sio_putl(v)) < 0)
sio_error("Sio_putl error");
return (n);
}
/*
* Requires:
* "s" is a properly terminated string.
*
* Effects:
* Prints the string "s" to stdout using only functions that can be safely
* called by a signal handler. Either returns the number of characters
* printed or exits if the string could not be printed.
*/
static ssize_t
Sio_puts(char s[])
{
ssize_t n;
if ((n = sio_puts(s)) < 0)
sio_error("Sio_puts error");
return (n);
}
/*
* Requires:
* "s" is a properly terminated string.
*
* Effects:
* Prints the string "s" to stdout using only functions that can be safely
* called by a signal handler, and exits the program.
*/
static void
Sio_error(char s[])
{
sio_error(s);
}
// Prevent "unused function" and "unused variable" warnings.
static const void *dummy_ref[] = { Sio_error, Sio_putl, addjob, builtin_cmd,
deletejob, do_bgfg, dummy_ref, fgpid, getjobjid, getjobpid, listjobs,
parseline, pid2jid, signame, waitfg };
/*
* The last lines of this file configure the behavior of the "Tab" key in
* emacs. Emacs has a rudimentary understanding of C syntax and style. In
* particular, depressing the "Tab" key once at the start of a new line will
* insert as many tabs and/or spaces as are needed for proper indentation.
*/
/* Local Variables: */
/* mode: c */
/* c-default-style: "bsd" */
/* c-basic-offset: 8 */
/* c-continued-statement-offset: 4 */
/* indent-tabs-mode: t */
/* End: */