/*
* tsh - A tiny shell program with job control
*
* Lane
*/#include<stdio.h>#include<stdlib.h>#include<unistd.h>#include<string.h>#include<ctype.h>#include<signal.h>#include<sys/types.h>#include<sys/wait.h>#include<errno.h>/* Misc manifest constants */#defineMAXLINE1024/* max line size */#defineMAXARGS128/* max args on a command line */#defineMAXJOBS16/* max jobs at any point in time */#defineMAXJID1<<16/* max job ID *//* Job states */#defineUNDEF0/* undefined */#defineFG1/* running in foreground */#defineBG2/* running in background */#defineST3/* 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 */externchar**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 */structjob_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 */};structjob_t jobs[MAXJOBS];/* The job list *//* End global variables *//* Function prototypes *//* Here are the functions that you will implement */voideval(char*cmdline);intbuiltin_cmd(char**argv);voiddo_bgfg(char**argv);voidwaitfg(pid_t pid);voidsigchld_handler(int sig);voidsigtstp_handler(int sig);voidsigint_handler(int sig);/* Here are helper routines that we've provided for you */intparseline(constchar*cmdline,char**argv);voidsigquit_handler(int sig);voidclearjob(structjob_t*job);voidinitjobs(structjob_t*jobs);intmaxjid(structjob_t*jobs);intaddjob(structjob_t*jobs,pid_t pid,int state,char*cmdline);intdeletejob(structjob_t*jobs,pid_t pid);pid_tfgpid(structjob_t*jobs);structjob_t*getjobpid(structjob_t*jobs,pid_t pid);structjob_t*getjobjid(structjob_t*jobs,int jid);intpid2jid(pid_t pid);voidlistjobs(structjob_t*jobs);voidusage(void);voidunix_error(char*msg);voidapp_error(char*msg);typedefvoidhandler_t(int);handler_t*Signal(int signum,handler_t*handler);/*
* main - The shell's main routine
*/intmain(int argc,char**argv){char c;char cmdline[MAXLINE];int emit_prompt =1;/* emit prompt (default) *//* Redirect stderr to stdout (so that driver will get all output
* on the pipe connected to stdout) */dup2(1,2);/* Parse the command line */while((c =getopt(argc, argv,"hvp"))!=EOF){switch(c){case'h':/* print help message */usage();break;case'v':/* emit additional diagnostic info */
verbose =1;break;case'p':/* don't print a prompt */
emit_prompt =0;/* handy for automatic testing */break;default:usage();}}/* Install the signal handlers *//* These are the ones you will need to implement */Signal(SIGINT, sigint_handler);/* ctrl-c */Signal(SIGTSTP, sigtstp_handler);/* ctrl-z */Signal(SIGCHLD, sigchld_handler);/* Terminated or stopped child *//* This one provides a clean way to kill the shell */Signal(SIGQUIT, sigquit_handler);/* Initialize the job list */initjobs(jobs);/* Execute the shell's read/eval loop */while(1){/* Read command line */if(emit_prompt){printf("%s", prompt);fflush(stdout);}if((fgets(cmdline, MAXLINE,stdin)==NULL)&&ferror(stdin))app_error("fgets error");if(feof(stdin)){/* End of file (ctrl-d) */fflush(stdout);exit(0);}/* Evaluate the command line */eval(cmdline);fflush(stdout);fflush(stdout);}exit(0);/* control never reaches here */}/*
* eval - Evaluate the command line that the user has just typed in
*
* If the user has requested a built-in command (quit, jobs, bg or fg)
* then execute it immediately. Otherwise, fork a child process and
* run the job in the context of the child. If the job is running in
* the foreground, wait for it to terminate and then return. Note:
* each child process must have a unique process group ID so that our
* background children don't receive SIGINT (SIGTSTP) from the kernel
* when we type ctrl-c (ctrl-z) at the keyboard.
*/voideval(char*cmdline){char*argv[MAXARGS];char buf[MAXARGS];int bg;pid_t pid;strcpy(buf,cmdline);
bg=parseline(buf,argv);if(argv[0]==NULL)return;if(!builtin_cmd(argv)){sigset_t mask_all,mask_one,pre_one;sigemptyset(&mask_one);sigemptyset(&pre_one);sigfillset(&mask_all);sigaddset(&mask_one,SIGCHLD);sigprocmask(SIG_BLOCK,&mask_one,&pre_one);if((pid=fork())==0){setpgid(0,0);sigprocmask(SIG_SETMASK,&pre_one,NULL);if(execve(argv[0],argv,environ)<0){printf("%s: Command not found.\n",argv[0]);exit(0);}}else{sigprocmask(SIG_BLOCK,&mask_all,NULL);addjob(jobs,pid,bg+1,cmdline);sigprocmask(SIG_SETMASK,&mask_one,NULL);// 前台if(!bg)waitfg(pid);// 后台else{sigprocmask(SIG_BLOCK,&mask_all,NULL);int jid=pid2jid(pid);printf("[%d] (%d) %s",jid,pid,cmdline);}}sigprocmask(SIG_SETMASK,&pre_one,NULL);}return;}/*
* parseline - Parse the command line and build the argv array.
*
* Characters enclosed in single quotes are treated as a single
* argument. Return true if the user has requested a BG job, false if
* the user has requested a FG job.
*/intparseline(constchar*cmdline,char**argv){staticchar array[MAXLINE];/* holds local copy of command line */char*buf = array;/* ptr that traverses command line */char*delim;/* points to first space delimiter */int argc;/* number of args */int bg;/* background job? */strcpy(buf, cmdline);
buf[strlen(buf)-1]=' ';/* replace trailing '\n' with space */while(*buf &&(*buf ==' '))/* ignore leading spaces */
buf++;/* Build the argv list */
argc =0;if(*buf =='\''){
buf++;
delim =strchr(buf,'\'');}else{
delim =strchr(buf,' ');}while(delim){
argv[argc++]= buf;*delim ='\0';
buf = delim +1;while(*buf &&(*buf ==' '))/* ignore spaces */
buf++;if(*buf =='\''){
buf++;
delim =strchr(buf,'\'');}else{
delim =strchr(buf,' ');}}
argv[argc]=NULL;if(argc ==0)/* ignore blank line */return1;/* 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.
*/intbuiltin_cmd(char**argv){if(!strcmp(argv[0],"quit"))exit(0);if(!strcmp(argv[0],"jobs"))listjobs(jobs);elseif(!strcmp(argv[0],"bg")||!strcmp(argv[0],"fg"))do_bgfg(argv);elsereturn0;/* not a builtin command */return1;}/*
* do_bgfg - Execute the builtin bg and fg commands
*/voiddo_bgfg(char**argv){// 如果是bg 则为0int bg=strcmp(argv[0],"bg");if(!argv[1]){printf("%s command requires PID or %%jobid argument\n",argv[0]);return;}int is_pid=(argv[1][0]>='0'&& argv[1][0]<='9');int is_jid=(argv[1][0]=='%'&& argv[1][1]>='0'&& argv[1][1]<='9');if(!is_pid &&!is_jid){printf("%s: argument must be a PID or %%jobid\n",argv[0]);return;}structjob_t*job_ptr=NULL;if(is_pid){
job_ptr=getjobpid(jobs,atoi(argv[1]));if(!job_ptr){printf("(%d): No such process\n",atoi(argv[1]));return;}}else{
job_ptr=getjobjid(jobs,atoi(argv[1]+1));if(!job_ptr){printf("%s: No such job\n",argv[1]);return;}}
job_ptr->state=(bg==0?BG:FG);kill(-job_ptr->pid,SIGCONT);if(bg==0)printf("[%d] (%d) %s",job_ptr->jid,job_ptr->pid,job_ptr->cmdline);elsewaitfg(job_ptr->pid);return;}/*
* waitfg - Block until process pid is no longer the foreground process
*/voidwaitfg(pid_t pid){sigset_t mask_none,prev_all;sigemptyset(&mask_none);while(1){sigprocmask(SIG_SETMASK,&mask_none,&prev_all);if(!fgpid(jobs))break;sigprocmask(SIG_SETMASK,&prev_all,NULL);}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.
*/voidsigchld_handler(int sig){int olderrno=errno;int status;pid_t pid;sigset_t mask_all,prev_all;sigfillset(&mask_all);sigemptyset(&prev_all);while((pid=waitpid(-1,&status,WNOHANG | WUNTRACED))>0){sigprocmask(SIG_BLOCK,&mask_all,&prev_all);structjob_t*job=getjobpid(jobs,pid);if(WIFSIGNALED(status)&&WTERMSIG(status)==SIGINT && job->state!=UNDEF)printf("Job [%d] (%d) terminated by signal 2\n",job->jid,job->pid);elseif(WIFSTOPPED(status)&&WSTOPSIG(status)== SIGTSTP && job->state != ST){printf("Job [%d] (%d) stopped by signal 20\n",job->jid,job->pid);
job->state = ST;}if(job->state != ST)deletejob(jobs,pid);sigprocmask(SIG_SETMASK,&prev_all,NULL);}
errno=olderrno;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.
*/voidsigint_handler(int sig){int fg_pid=fgpid(jobs);int fg_jid=pid2jid(fg_pid);if(!fg_jid)return;sigset_t mask_all,prev_all;sigfillset(&mask_all);sigemptyset(&prev_all);sigprocmask(SIG_BLOCK,&mask_all,&prev_all);structjob_t*job=getjobpid(jobs,fg_pid);
job->state=UNDEF;kill(-fg_pid,SIGINT);printf("Job [%d] (%d) terminated by signal 2\n",fg_jid,fg_pid);sigprocmask(SIG_SETMASK,&prev_all,NULL);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.
*/voidsigtstp_handler(int sig){int fg_pid=fgpid(jobs);int fg_jid=pid2jid(fg_pid);if(!fg_jid)return;sigset_t mask_all,prev_all;sigfillset(&mask_all);sigemptyset(&prev_all);sigprocmask(SIG_BLOCK,&mask_all,&prev_all);structjob_t*job=getjobpid(jobs,fg_pid);
job->state=ST;kill(-fg_pid,SIGTSTP);printf("Job [%d] (%d) stopped by signal 20\n",fg_jid,fg_pid);sigprocmask(SIG_SETMASK,&prev_all,NULL);return;}/*********************
* End signal handlers
*********************//***********************************************
* Helper routines that manipulate the job list
**********************************************//* clearjob - Clear the entries in a job struct */voidclearjob(structjob_t*job){
job->pid =0;
job->jid =0;
job->state = UNDEF;
job->cmdline[0]='\0';}/* initjobs - Initialize the job list */voidinitjobs(structjob_t*jobs){int i;for(i =0; i < MAXJOBS; i++)clearjob(&jobs[i]);}/* maxjid - Returns largest allocated job ID */intmaxjid(structjob_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 */intaddjob(structjob_t*jobs,pid_t pid,int state,char*cmdline){int i;if(pid <1)return0;for(i =0; i < MAXJOBS; i++){if(jobs[i].pid ==0){
jobs[i].pid = pid;
jobs[i].state = state;
jobs[i].jid = nextjid++;if(nextjid > MAXJOBS)
nextjid =1;strcpy(jobs[i].cmdline, cmdline);if(verbose){printf("Added job [%d] %d %s\n", jobs[i].jid, jobs[i].pid, jobs[i].cmdline);}return1;}}printf("Tried to create too many jobs\n");return0;}/* deletejob - Delete a job whose PID=pid from the job list */intdeletejob(structjob_t*jobs,pid_t pid){int i;if(pid <1)return0;for(i =0; i < MAXJOBS; i++){if(jobs[i].pid == pid){clearjob(&jobs[i]);
nextjid =maxjid(jobs)+1;return1;}}return0;}/* fgpid - Return PID of current foreground job, 0 if no such job */pid_tfgpid(structjob_t*jobs){int i;for(i =0; i < MAXJOBS; i++)if(jobs[i].state == FG)return jobs[i].pid;return0;}/* getjobpid - Find a job (by PID) on the job list */structjob_t*getjobpid(structjob_t*jobs,pid_t pid){int i;if(pid <1)returnNULL;for(i =0; i < MAXJOBS; i++)if(jobs[i].pid == pid)return&jobs[i];returnNULL;}/* getjobjid - Find a job (by JID) on the job list */structjob_t*getjobjid(structjob_t*jobs,int jid){int i;if(jid <1)returnNULL;for(i =0; i < MAXJOBS; i++)if(jobs[i].jid == jid)return&jobs[i];returnNULL;}/* pid2jid - Map process ID to job ID */intpid2jid(pid_t pid){int i;if(pid <1)return0;for(i =0; i < MAXJOBS; i++)if(jobs[i].pid == pid){return jobs[i].jid;}return0;}/* listjobs - Print the job list */voidlistjobs(structjob_t*jobs){int i;for(i =0; i < MAXJOBS; i++){if(jobs[i].pid !=0){printf("[%d] (%d) ", jobs[i].jid, jobs[i].pid);switch(jobs[i].state){case BG:printf("Running ");break;case FG:printf("Foreground ");break;case ST:printf("Stopped ");break;default:printf("listjobs: Internal error: job[%d].state=%d ",
i, jobs[i].state);}printf("%s", jobs[i].cmdline);}}}/******************************
* end job list helper routines
******************************//***********************
* Other helper routines
***********************//*
* usage - print a help message
*/voidusage(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
*/voidunix_error(char*msg){fprintf(stdout,"%s: %s\n", msg,strerror(errno));exit(1);}/*
* app_error - application-style error routine
*/voidapp_error(char*msg){fprintf(stdout,"%s\n", msg);exit(1);}/*
* Signal - wrapper for the sigaction function
*/handler_t*Signal(int signum,handler_t*handler){structsigaction 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.
*/voidsigquit_handler(int sig){printf("Terminating after receipt of SIGQUIT signal\n");exit(1);}