/* * tsh - A tiny shell program with job control * * Ben Blum (bblum) */#define _213_HANDIN#include <stdio.h>#include <stdlib.h>#include <unistd.h>#include <string.h>#include <ctype.h>#include <signal.h>#include <sys/types.h>#include <sys/stat.h>#include <fcntl.h>#include <sys/wait.h>#include <errno.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 *//* 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. *//* Parsing states */#define ST_NORMAL 0x0 /* next token is an argument */#define ST_INFILE 0x1 /* next token is the input file */#define ST_OUTFILE 0x2 /* next token is the output file *//* 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 job_list[MAXJOBS]; /* The job list */struct cmdline_tokens { int argc; /* Number of arguments */ char *argv[MAXARGS]; /* The arguments list */ char *infile; /* The input file */ char *outfile; /* The output file */ enum builtins_t /* Indicates if argv[0] is a builtin command */ {none, quit, jobs, bg, fg} builtins;};/* End global variables *//* Function prototypes */void eval(char *cmdline);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, struct cmdline_tokens *tok); void sigquit_handler(int sig);void clearjob(struct job_t *job);void initjobs(struct job_t *job_list);int maxjid(struct job_t *job_list); int addjob(struct job_t *job_list, pid_t pid, int state, char *cmdline);int deletejob(struct job_t *job_list, pid_t pid); pid_t fgpid(struct job_t *job_list);struct job_t *getjobpid(struct job_t *job_list, pid_t pid);struct job_t *getjobjid(struct job_t *job_list, int jid); int pid2jid(pid_t pid); void listjobs(struct job_t *job_list, int output_fd);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);int get_outfd(char *, int *);int get_infd(char *, int *);pid_t get_pid(char *);void block_on_fg(pid_t);/* * main - The shell's main routine */int main(int argc, char **argv) { char c; char cmdline[MAXLINE]; /* cmdline for fgets */ 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 */ Signal(SIGTTIN, SIG_IGN); Signal(SIGTTOU, SIG_IGN); /* This one provides a clean way to kill the shell */ Signal(SIGQUIT, sigquit_handler); /* Initialize the job list */ initjobs(job_list); /* Execute the shell's read/eval loop */ while (1) { 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) */ printf ("\n"); fflush(stdout); fflush(stderr); exit(0); } /* Remove the trailing newline */ cmdline[strlen(cmdline)-1] = '\0'; /* Evaluate the command line */ eval(cmdline); fflush(stdout); fflush(stdout); } exit(0); /* control never reaches here */}/* * eval - Evaluate the command line that the user has just typed in * * If the user has requested a built-in command (quit, jobs, bg or fg) * then execute it immediately. Otherwise, fork a child process and * run the job in the context of the child. If the job is running in * the foreground, wait for it to terminate and then return. Note: * each child process must have a unique process group ID so that our * background children don't receive SIGINT (SIGTSTP) from the kernel * when we type ctrl-c (ctrl-z) at the keyboard. */void eval(char *cmdline) { int bg; /* should the job run in bg or fg? */ struct cmdline_tokens tok; /* int returncode; */ /* Parse command line */ bg = parseline(cmdline, &tok); if (bg == -1) return; /* parsing error */ if (tok.argv[0] == NULL) return; /* ignore empty lines */ if (tok.builtins == 1) /* quit */ { exit(0); } else if (tok.builtins == 2) /* jobs */ { int do_close; listjobs(job_list, get_outfd(tok.outfile, &do_close)); /* listjobs closes the file descriptor */ return; } else if (tok.builtins == 3) /* bg */ { pid_t pid = get_pid(tok.argv[1]); #ifdef _213_HANDIN int jid = pid2jid(pid); printf("[%d] (%d) %s\n", jid, pid, (getjobpid(job_list, pid))->cmdline); #endif (getjobpid(job_list, pid))->state = BG; kill(pid, SIGCONT); } else if (tok.builtins == 4) /* fg */ { sigset_t mask, oldmask; pid_t pid = get_pid(tok.argv[1]); (getjobpid(job_list, pid))->state = FG; /* resume the foreground process and block on it */ sigemptyset(&mask); sigaddset(&mask, SIGINT); sigaddset(&mask, SIGTSTP); sigaddset(&mask, SIGCHLD); sigprocmask(SIG_BLOCK, &mask, &oldmask); kill(pid, SIGCONT); /* wait for the job */ sigprocmask(SIG_SETMASK, &oldmask, NULL); while (fgpid(job_list)) { sigsuspend(&oldmask); } } else /* external command */ { pid_t cpid; sigset_t mask, oldmask; sigemptyset(&mask); sigaddset(&mask, SIGINT); sigaddset(&mask, SIGTSTP); sigaddset(&mask, SIGCHLD); sigprocmask(SIG_BLOCK, &mask, &oldmask); if (!(cpid = fork())) /* child */ { int do_close; /* reset all signal stuff that persists across exec */ sigprocmask(SIG_SETMASK, &oldmask, NULL); signal(SIGINT, SIG_DFL); signal(SIGQUIT, SIG_DFL); signal(SIGTSTP, SIG_DFL); signal(SIGCHLD, SIG_DFL); signal(SIGTTIN, SIG_DFL); signal(SIGTTOU, SIG_DFL); /* io redirection */ dup2(get_outfd(tok.outfile, &do_close), STDOUT_FILENO); dup2(get_outfd(tok.infile, &do_close), STDIN_FILENO); execvp(tok.argv[0], tok.argv); /* exec failed */ printf("%s: Command not found\n", tok.argv[0]); exit(-1); } else /* parent */ { /* add a job */ addjob(job_list, cpid, bg ? BG : FG, cmdline); #ifdef _213_HANDIN if (bg) { printf("[%d] (%d) %s\n", pid2jid(cpid), cpid, cmdline); } #endif /* sleep until the fg child exits, if it exists */ while (fgpid(job_list)) { sigsuspend(&oldmask); } /* reset the signals */ sigprocmask(SIG_SETMASK, &oldmask, NULL); } } return;}int get_outfd(char *filename, int *do_close){ if (filename != NULL) { *do_close = 1; return open(filename, O_WRONLY | O_TRUNC | O_CREAT, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH); } else { *do_close = 0; return STDOUT_FILENO; }}int get_infd(char *filename, int *do_close){ if (filename != NULL) { *do_close = 1; return open(filename, O_RDONLY); } else { *do_close = 0; return STDIN_FILENO; }}pid_t get_pid(char *token){ if (token[0] == '%') { return (getjobjid(job_list, atoi(token + 1)))->pid; } else return (pid_t)atoi(token);}/* * parseline - Parse the command line and build the argv array. * * Parameters: * cmdline: The command line, in the form: * * command [arguments...] [< infile] [> oufile] [&] * * tok: Pointer to a cmdline_tokens structure. The elements of this * structure will be populated with the parsed tokens. Characters * enclosed in single or double quotes are treated as a single * argument. * Returns: * 1: if the user has requested a BG job * 0: if the user has requested a FG job * -1: if cmdline is incorrectly formatted * * Note: The string elements of tok (e.g., argv[], infile, outfile) * are statically allocated inside parseline() and will be * overwritten the next time this function is invoked. */int parseline(const char *cmdline, struct cmdline_tokens *tok) { static char array[MAXLINE]; /* holds local copy of command line */ const char delims[10] = " \t\r\n"; /* argument delimiters (white-space) */ char *buf = array; /* ptr that traverses command line */ char *next; /* ptr to the end of the current arg */ char *endbuf; /* ptr to the end of the cmdline string */ int is_bg; /* background job? */ int parsing_state; /* indicates if the next token is the input or output file */ if (cmdline == NULL) { (void) fprintf(stderr, "Error: command line is NULL\n"); return -1; } (void) strncpy(buf, cmdline, MAXLINE); endbuf = buf + strlen(buf); tok->infile = NULL; tok->outfile = NULL; /* Build the argv list */ parsing_state = ST_NORMAL; tok->argc = 0; while (buf < endbuf) { /* Skip the white-spaces */ buf += strspn (buf, delims); if (buf >= endbuf) break; /* Check for I/O redirection specifiers */ if (*buf == '<') { if (tok->infile) { (void) fprintf(stderr, "Error: Ambiguous I/O redirection\n"); return -1; } parsing_state |= ST_INFILE; buf++; continue; } if (*buf == '>') { if (tok->outfile) { (void) fprintf(stderr, "Error: Ambiguous I/O redirection\n"); return -1; } parsing_state |= ST_OUTFILE; buf ++; continue; } if (*buf == '\'' || *buf == '\"') { /* Detect quoted tokens */ buf++; next = strchr (buf, *(buf-1)); } else { /* Find next delimiter */ next = buf + strcspn (buf, delims); } if (next == NULL) { /* Returned by strchr(); this means that the closing quote was not found. */ (void) fprintf (stderr, "Error: unmatched %c.\n", *(buf-1)); return -1; } /* Terminate the token */ *next = '\0'; /* Record the token as either the next argument or the input/output file */ switch (parsing_state) { case ST_NORMAL: tok->argv[tok->argc++] = buf; break; case ST_INFILE: tok->infile = buf; break; case ST_OUTFILE: tok->outfile = buf; break; default: (void) fprintf(stderr, "Error: Ambiguous I/O redirection\n"); return -1; } parsing_state = ST_NORMAL; /* Check if argv is full */ if (tok->argc >= MAXARGS-1) break; buf = next + 1; } if (parsing_state != ST_NORMAL) { (void) fprintf(stderr, "Error: must provide file name for redirection\n"); return -1; } /* The argument list must end with a NULL pointer */ tok->argv[tok->argc] = NULL; if (tok->argc == 0) /* ignore blank line */ return 1; if (!strcmp(tok->argv[0], "quit")) { /* quit command */ tok->builtins = quit; } else if (!strcmp(tok->argv[0], "jobs")) { /* jobs command */ tok->builtins = jobs; } else if (!strcmp(tok->argv[0], "bg")) { /* bg command */ tok->builtins = bg; } else if (!strcmp(tok->argv[0], "fg")) { /* fg command */ tok->builtins = fg; } else { tok->builtins = none; } /* Should the job run in the background? */ if ((is_bg = (*tok->argv[tok->argc-1] == '&')) != 0) tok->argv[--tok->argc] = NULL; return is_bg;}/***************** * 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, SIGTSTP, SIGTTIN or SIGTTOU 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; /* parents should always raep their zombie children... did I misspell that? */ while (0 < (pid = waitpid(0, &status, WNOHANG | WUNTRACED))) { #ifdef _213_HANDIN int jid = pid2jid(pid); if (WIFSIGNALED(status)) { int signal = WTERMSIG(status); printf("Job [%d] (%d) terminated by signal %d\n", jid, pid, signal); } else if (WIFSTOPPED(status)) { int signal = WSTOPSIG(status); printf("Job [%d] (%d) stopped by signal %d\n", jid, pid, signal); } #endif if (WIFSTOPPED(status)) { (getjobpid(job_list, pid))->state = ST; } /* child terminated */ else { deletejob(job_list, pid); } } 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(job_list); if (pid) { kill(pid, SIGINT); } 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(job_list); if (pid) { kill(pid, SIGTSTP); } return;}/********************* * End signal handlers *********************//*********************************************** * Helper routines that manipulate the job list **********************************************//* clearjob - Clear the entries in a job struct */void clearjob(struct job_t *job) { job->pid = 0; job->jid = 0; job->state = UNDEF; job->cmdline[0] = '\0';}/* initjobs - Initialize the job list */void initjobs(struct job_t *job_list) { int i; for (i = 0; i < MAXJOBS; i++) clearjob(&job_list[i]);}/* maxjid - Returns largest allocated job ID */int maxjid(struct job_t *job_list) { int i, max=0; for (i = 0; i < MAXJOBS; i++) if (job_list[i].jid > max) max = job_list[i].jid; return max;}/* addjob - Add a job to the job list */int addjob(struct job_t *job_list, pid_t pid, int state, char *cmdline) { int i; if (pid < 1) return 0; for (i = 0; i < MAXJOBS; i++) { if (job_list[i].pid == 0) { job_list[i].pid = pid; job_list[i].state = state; job_list[i].jid = nextjid++; if (nextjid > MAXJOBS) nextjid = 1; strcpy(job_list[i].cmdline, cmdline); if(verbose){ printf("Added job [%d] %d %s\n", job_list[i].jid, job_list[i].pid, job_list[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 *job_list, pid_t pid) { int i; if (pid < 1) return 0; for (i = 0; i < MAXJOBS; i++) { if (job_list[i].pid == pid) { clearjob(&job_list[i]); nextjid = maxjid(job_list)+1; return 1; } } return 0;}/* fgpid - Return PID of current foreground job, 0 if no such job */pid_t fgpid(struct job_t *job_list) { int i; for (i = 0; i < MAXJOBS; i++) if (job_list[i].state == FG) return job_list[i].pid; return 0;}/* getjobpid - Find a job (by PID) on the job list */struct job_t *getjobpid(struct job_t *job_list, pid_t pid) { int i; if (pid < 1) return NULL; for (i = 0; i < MAXJOBS; i++) if (job_list[i].pid == pid) return &job_list[i]; return NULL;}/* getjobjid - Find a job (by JID) on the job list */struct job_t *getjobjid(struct job_t *job_list, int jid) { int i; if (jid < 1) { return NULL; } for (i = 0; i < MAXJOBS; i++) { if (job_list[i].jid == jid) { return &job_list[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 (job_list[i].pid == pid) { return job_list[i].jid; } return 0;}/* listjobs - Print the job list */void listjobs(struct job_t *job_list, int output_fd) { int i; char buf[MAXLINE]; for (i = 0; i < MAXJOBS; i++) { memset(buf, '\0', MAXLINE); if (job_list[i].pid != 0) { sprintf(buf, "[%d] (%d) ", job_list[i].jid, job_list[i].pid); if(write(output_fd, buf, strlen(buf)) < 0) { fprintf(stderr, "Error writing to output file\n"); exit(1); } memset(buf, '\0', MAXLINE); switch (job_list[i].state) { case BG: sprintf(buf, "Running "); break; case FG: sprintf(buf, "Foreground "); break; case ST: sprintf(buf, "Stopped "); break; default: sprintf(buf, "listjobs: Internal error: job[%d].state=%d ", i, job_list[i].state); } if(write(output_fd, buf, strlen(buf)) < 0) { fprintf(stderr, "Error writing to output file\n"); exit(1); } memset(buf, '\0', MAXLINE); sprintf(buf, "%s\n", job_list[i].cmdline); if(write(output_fd, buf, strlen(buf)) < 0) { fprintf(stderr, "Error writing to output file\n"); exit(1); } } } if(output_fd != STDOUT_FILENO) close(output_fd);}/****************************** * end job list helper routines ******************************//*********************** * Other helper routines ***********************//* * usage - print a help message */void usage(void) { printf("Usage: shell [-hvp]\n"); printf(" -h print this message\n"); printf(" -v print additional diagnostic information\n"); printf(" -p do not emit a command prompt\n"); exit(1);}/* * unix_error - unix-style error routine */void unix_error(char *msg){ fprintf(stdout, "%s: %s\n", msg, strerror(errno)); exit(1);}/* * app_error - application-style error routine */void app_error(char *msg){ fprintf(stdout, "%s\n", msg); exit(1);}/* * Signal - wrapper for the sigaction function */handler_t *Signal(int signum, handler_t *handler) { struct sigaction action, old_action; action.sa_handler = handler; sigemptyset(&action.sa_mask); /* block sigs of type being handled */ action.sa_flags = SA_RESTART; /* restart syscalls if possible */ if (sigaction(signum, &action, &old_action) < 0) unix_error("Signal error"); return (old_action.sa_handler);}/* * sigquit_handler - The driver program can gracefully terminate the * child shell by sending it a SIGQUIT signal. */void sigquit_handler(int sig) { printf("Terminating after receipt of SIGQUIT signal\n"); exit(1);}