From 1c50fbd315a04c298637a3d6710b970c52325972 Mon Sep 17 00:00:00 2001 From: yenru0 Date: Mon, 24 Nov 2025 03:03:35 +0900 Subject: [PATCH] complement lab03 --- labs/03_shlab/tsh.c | 623 +++++++++++++++++++++++++------------------- 1 file changed, 351 insertions(+), 272 deletions(-) diff --git a/labs/03_shlab/tsh.c b/labs/03_shlab/tsh.c index 82443dc..8b986a8 100644 --- a/labs/03_shlab/tsh.c +++ b/labs/03_shlab/tsh.c @@ -4,21 +4,21 @@ * Name: * Student id: */ +#include +#include +#include #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 */ +#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 */ @@ -37,22 +37,21 @@ */ /* 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 */ +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 { /* 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 */ /*---------------------------------------------------------------------------- @@ -94,68 +93,67 @@ 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) */ +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 + /* Redirect stderr to stdout (so that driver will get all output * on the pipe connected to stdout) */ - dup2(1, 2); + 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(); - } - } + /* 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 */ + /* 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 */ + /* 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); + /* This one provides a clean way to kill the shell */ + Signal(SIGQUIT, sigquit_handler); - /* Initialize the job list */ - initjobs(jobs); + /* Initialize the job list */ + initjobs(jobs); - /* Execute the shell's read/eval loop */ - while (1) { + /* 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); - } + /* 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); - } + /* Evaluate the command line */ + eval(cmdline); + fflush(stdout); + fflush(stdout); + } - exit(0); /* control never reaches here */ + exit(0); /* control never reaches here */ } /* @@ -169,9 +167,30 @@ int main(int argc, char **argv) * background children don't receive SIGINT (SIGTSTP) from the kernel * when we type ctrl-c (ctrl-z) at the keyboard. */ -void eval(char *cmdline) -{ - return; +void eval(char *cmdline) { + pid_t pid; + char *argv[MAXARGS]; + + int bg = parseline(cmdline, argv); + + if (argv[0] && !builtin_cmd(argv)) { + if ((pid = fork()) == 0) {// Child process + setpgid(0, 0); // Set new process group + + if (execve(argv[0], argv, environ) < 0) { + printf("%s: Command not found\n", argv[0]); + exit(0); + } + } + + if (!bg) {// Foreground job + addjob(jobs, pid, FG, cmdline); + waitfg(pid); + } else {// Background job + addjob(jobs, pid, BG, cmdline); + fprintf(stderr, "[%d] (%d) %s", pid2jid(pid), pid, cmdline); + } + } } /* @@ -181,79 +200,133 @@ void eval(char *cmdline) * 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? */ +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? */ - strcpy(buf, cmdline); - buf[strlen(buf)-1] = ' '; /* replace trailing '\n' with space */ - while (*buf && (*buf == ' ')) /* ignore leading spaces */ - buf++; + 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, ' '); - } + /* 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++; + 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 (*buf == '\'') { + buf++; + delim = strchr(buf, '\''); + } else { + delim = strchr(buf, ' '); + } + } + argv[argc] = NULL; - if (argc == 0) /* ignore blank line */ - return 1; + 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; + /* 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. */ -int builtin_cmd(char **argv) -{ - return 0; /* not a builtin command */ +int builtin_cmd(char **argv) { + // Built-in-commands: jobs, bg, fg, quit + + if (!strcmp(argv[0], "quit")) { + exit(0); + } else if (!strcmp(argv[0], "jobs")) { + listjobs(jobs); + return 1; + } else if (!strcmp(argv[0], "bg") || !strcmp(argv[0], "fg")) { + do_bgfg(argv); + return 1; + } + return 0; } /* * do_bgfg - Execute the builtin bg and fg commands */ -void do_bgfg(char **argv) -{ - return; +void do_bgfg(char **argv) { + struct job_t *job = NULL; + pid_t pid; + int jid; + + if (argv[1] == NULL) { + printf("%s command requires PID or %%jobid argument\n", argv[0]); + return; + } + + if (argv[1][0] == '%') { + if (sscanf(&argv[1][1], "%d", &jid) != 1) { + printf("%s: argument must be a PID or %%jobid\n", argv[0]); + return; + } + job = getjobjid(jobs, jid); + if (job == NULL) { + printf("%s: No such job\n", argv[1]); + return; + } + } else if (isdigit((unsigned char) argv[1][0])) { + if (sscanf(argv[1], "%d", &pid) != 1) { + printf("%s: argument must be a PID or %%jobid\n", argv[0]); + return; + } + job = getjobpid(jobs, pid); + if (job == NULL) { + printf("(%s): No such process\n", argv[1]); + return; + } + } else { + printf("%s: argument must be a PID or %%jobid\n", argv[0]); + return; + } + + pid = job->pid; + + if (!strcmp(argv[0], "bg")) { + job->state = BG; + if (kill(-pid, SIGCONT) < 0) + unix_error("kill (SIGCONT) error"); + printf("[%d] (%d) %s", job->jid, job->pid, job->cmdline); + } else { /* fg */ + job->state = FG; + if (kill(-pid, SIGCONT) < 0) + unix_error("kill (SIGCONT) error"); + waitfg(pid); + } } /* * waitfg - Block until process pid is no longer the foreground process */ -void waitfg(pid_t pid) -{ - return; +void waitfg(pid_t pid) { + while (pid == fgpid(jobs)) { + sleep(1); + } + return; } /***************** @@ -267,9 +340,27 @@ void waitfg(pid_t pid) * available zombie children, but doesn't wait for any other * currently running children to terminate. */ -void sigchld_handler(int sig) -{ - return; +void sigchld_handler(int sig) { + int olderrno = errno; + pid_t pid; + int status; + + while ((pid = waitpid(-1, &status, WNOHANG | WUNTRACED)) > 0) { + if (WIFEXITED(status)) { + deletejob(jobs, pid); + } else if (WIFSIGNALED(status)) { + fprintf(stderr, "Job [%d] (%d) terminated by signal %d\n", pid2jid(pid), pid, WTERMSIG(status)); + deletejob(jobs, pid); + } else if (WIFSTOPPED(status)) { + struct job_t *job = getjobpid(jobs, pid); + if (job != NULL) { + job->state = ST; + fprintf(stderr, "Job [%d] (%d) stopped by signal %d\n", job->jid, pid, WSTOPSIG(status)); + } + } + } + + errno = olderrno; } /* @@ -277,9 +368,11 @@ void sigchld_handler(int sig) * user types ctrl-c at the keyboard. Catch it and send it along * to the foreground job. */ -void sigint_handler(int sig) -{ - return; +void sigint_handler(int sig) { + pid_t pid; + if ((pid = fgpid(jobs)) > 0) { + kill(-pid, SIGINT); + } } /* @@ -287,9 +380,11 @@ void sigint_handler(int sig) * 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) -{ - return; +void sigtstp_handler(int sig) { + pid_t pid; + if ((pid = fgpid(jobs)) > 0) { + kill(-pid, SIGTSTP); + } } /********************* @@ -301,160 +396,149 @@ void sigtstp_handler(int sig) **********************************************/ /* 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'; +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; +void initjobs(struct job_t *jobs) { + int i; - for (i = 0; i < MAXJOBS; i++) - clearjob(&jobs[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; +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; + 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; +int addjob(struct job_t *jobs, pid_t pid, int state, char *cmdline) { + int i; - if (pid < 1) - return 0; + 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; - strcpy(jobs[i].cmdline, cmdline); - 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; + 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); + } + 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; +int deletejob(struct job_t *jobs, pid_t pid) { + int i; - if (pid < 1) - return 0; + 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; + 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; +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; + 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; +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; + 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; +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; + 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; +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; + 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; +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); - } - } + 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 ***********************/ @@ -462,55 +546,50 @@ void listjobs(struct job_t *jobs) /* * 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); +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); +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); +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; +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 */ + 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); + 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); +void sigquit_handler(int sig) { + printf("Terminating after receipt of SIGQUIT signal\n"); + exit(1); }