#include #include #include #include #include #include #include #include #include #include #include #include "yeeshell.h" /* record cmdline history */ char *history[CMDLINE_HISTORY_MAX_QUANTITY]; int cmdline_amount = 0; /* if true, print additional output */ int verbose = 0; /* next job ID to allocate */ int nextjid = 1; /* environment variable */ extern char **environ; /* The job list */ struct job_t jobs[JOBS_MAX_QUANTITY]; int main() { char *cmdline = NULL, *pwd = NULL; char *args[ARGS_MAX_QUANTITY]; int status = 1; pwd = (char *)calloc(PATH_MAX_SIZE, sizeof(char)); for (int i = 0; i < CMDLINE_HISTORY_MAX_QUANTITY; i++) { history[i] = (char *)calloc(CMDLINE_MAX_SIZE, sizeof(char)); } /* Install the signal handlers */ Signal(SIGINT, sigint_handler); /* ctrl-c */ Signal(SIGTSTP, sigtstp_handler); /* ctrl-z */ Signal(SIGCHLD, sigchld_handler); /* Terminated or stopped child */ Signal(SIGQUIT, sigquit_handler); /* initiate job list */ initjobs(jobs); /* execute the shell's read, parse and execution loop */ do { if (!getcwd(pwd, PATH_MAX_SIZE)) { printf("yeeshell: The current path cannot be obtained!\n"); exit(0); } printf("[root@yeeshell %s]# ", pwd); cmdline = readline(); strcpy(history[cmdline_amount++], cmdline); status = execute(cmdline, args); free(cmdline); } while (status); for (int i = 0; i < CMDLINE_HISTORY_MAX_QUANTITY; i++) { free(history[i]); } exit(EXIT_SUCCESS); } char *readline() { char *cmdline = NULL; ssize_t bufsize = 0; getline(&cmdline, &bufsize, stdin); return cmdline; } int parseline(char *cmdline, char **args) { static char array[CMDLINE_MAX_SIZE]; /* 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) { args[argc++] = buf; *delim = '\0'; buf = delim + 1; while (*buf && (*buf == ' ')) /* ignore spaces */ { buf++; } if (*buf == '\'') { buf++; delim = strchr(buf, '\''); } else { delim = strchr(buf, ' '); } } args[argc] = NULL; if (argc == 0) /* ignore blank line */ { return 1; } /* should the job run in the background? */ if ((bg = (*args[argc - 1] == '&')) != 0) { args[--argc] = NULL; } return bg; } int check_redirect(char **args, char *redirect_filename, char **redirect_args) { int i = 0, j = 0, redirect_flag = REDIRECT_NO; while (args[i] != NULL) { if (!strcmp(args[i], ">")) { redirect_flag = REDIRECT_OUT; break; } else if (!strcmp(args[i], "<")) { redirect_flag = REDIRECT_IN; break; } i++; } if (redirect_flag == 1) /* redirect output */ { strcpy(redirect_filename, args[i + 1]); for (j = 0; j < i; j++) { redirect_args[j] = args[j]; } } else if (redirect_flag == 2) /* redirect input */ { redirect_filename = args[0]; i++; while (args[i] != NULL) { redirect_args[j++] = args[i++]; } } return redirect_flag; } int execute(char *cmdline, char **args) { int bg = 0, i = 0, redirect_flag = 0, fd = 1; pid_t pid; char *redirect_filename = NULL; redirect_filename = (char *)calloc(32, sizeof(char)); char *redirect_args[ARGS_MAX_QUANTITY]; sigset_t mask_all, mask_prev; sigprocmask(SIG_BLOCK, NULL, &mask_all); sigaddset(&mask_all, SIGCHLD); bg = parseline(cmdline, args); redirect_flag = check_redirect(args, redirect_filename, redirect_args); if (args[0] == NULL) { return 1; } if (!built_in(args)) { /* Prevent child processes from ending between parent processes, that is, between addJob and deleteJob. */ sigprocmask(SIG_BLOCK, &mask_all, &mask_prev); /* Shield SIGCHLD */ if ((pid = fork()) == 0) /* Child process */ { if (redirect_flag == 1) { fd = open(redirect_filename, O_CREAT | O_WRONLY | O_TRUNC, S_IRUSR | S_IWUSR | S_IXUSR); close(1); dup2(1, fd); } else if (redirect_flag == 2) { fd = open(redirect_filename, O_RDONLY, S_IRUSR); close(0); dup2(0, fd); } /* The child process inherits the parent's signal mask and will inherit it after exec, so it needs to restore the signal mask before executing. */ sigprocmask(SIG_SETMASK, &mask_prev, NULL); /* Child process, unblock SIGCHLD */ /* Set the pid of the current process to the group number of the process group it belongs to. */ /* avoid being grouped with tsh */ setpgid(0, 0); if (execvp(args[0], args) <= 0) { printf("%s: Command not found\n", args[0]); free(redirect_filename); free(redirect_args); exit(0); } } else { if (bg) /* bg task */ { addjob(jobs, pid, BG, cmdline); } else /* fg task */ { addjob(jobs, pid, FG, cmdline); } sigprocmask(SIG_SETMASK, &mask_prev, NULL); if (bg) /* Don't wait for background tasks to finish */ { printf("[%d](%d)%s", pid2jid(pid), pid, cmdline); } else /* Wait for foreground tasks to finish */ { waitfg(pid); } } } close(fd); free(redirect_filename); free(redirect_args); return 1; } int built_in(char **args) { if (!strcmp(args[0], "exit")) { exit(0); } else if (!strcmp(args[0], "cd")) { return builtin_cd(args); } else if (!strcmp(args[0], "history")) { return builtin_history(args); } else if (!strcmp(args[0], "mytop")) { return builtin_mytop(args); } else if (!strcmp(args[0], "jobs")) { return builtin_jobs(args); } else { return 0; } } int builtin_cd(char **args) { if (args[1] == NULL) { return 1; } else { if (chdir(args[1]) != 0) { perror("yeeshell"); } return 1; } } int builtin_history(char **args) { int n = 0; if (args[1] == NULL) { n = cmdline_amount; } else { n = atoi(args[1]) < cmdline_amount ? atoi(args[1]) : cmdline_amount; } printf("ID\tCommandline\n"); for (int i = 0; i < n; i++) { printf("%d\t%s\n", i + 1, history[i]); } return 1; } int builtin_jobs(char **args) { /* To prevent interruptions, block all signals. */ sigset_t mask_all, mask_prev; sigfillset(&mask_all); sigprocmask(SIG_SETMASK, &mask_all, &mask_prev); for (int i = 0; i < JOBS_MAX_QUANTITY; 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); } } sigprocmask(SIG_SETMASK, &mask_prev, NULL); /* unclock */ return 1; } int builtin_mytop(char **args) { } int do_bgfg(char **args) { /* initialize variables */ struct job_t *currentJob; int jid; pid_t pid; sigset_t mask_all, mask_prev; sigfillset(&mask_all); sigprocmask(SIG_SETMASK, &mask_all, &mask_prev); /* bg or fg has the argument? */ if (args[1] == NULL) { printf("%s command requires PID or %%jobid argument\n", args[0]); return 1; } /* if process by jid, gets the corresponding Job structure*/ else if (args[1][0] == '%') { jid = atoi(args[1][1]); currentJob = getjobjid(jobs, jid); if (currentJob == NULL) { printf("%%%d: No such job\n", jid); return 1; } pid = currentJob->pid; } /* if process by pid, gets the corresponding Job structure */ else { pid = atoi(args[1]); currentJob = getjobpid(jobs, pid); if (pid <= 0) { printf("%s: argument must be a PID or %%jobid\n", args[0]); return 1; } currentJob = getjobpid(jobs, pid); if (currentJob == NULL) { printf("(%d): No such process\n", pid); return 1; } } /* bg or fg? */ if (!strcmp(args[0], "bg")) /* if bg */ { currentJob->state = BG; printf("[%d] (%d) %s", currentJob->jid, pid, currentJob->cmdline); sigprocmask(SIG_SETMASK, &mask_prev, NULL); kill(-pid, SIGCONT); /* send the SIGCONT to the pid */ return 1; } else if (!strcmp(args[0], "fg")) /* if fg */ { currentJob->state = FG; sigprocmask(SIG_SETMASK, &mask_prev, NULL); kill(-pid, SIGCONT); /* send the SIGCONT to the pid */ waitfg(currentJob->pid); /* Child process switched to FG, so wait for it to finish */ return 1; } else if (!strcmp(args[0], "kill")) { sigprocmask(SIG_SETMASK, &mask_prev, NULL); kill(-pid, SIGQUIT); return 1; } return 1; } void waitfg(pid_t pid) { sigset_t m; sigemptyset(&m); while (pid == fgpid(jobs)) { /* Wake up when there is a signal to check whether the foreground process PID change, */ /* change means that the foreground process is over. */ sigsuspend(&m); } return; } void initjobs(struct job_t *jobs) { int i; for (i = 0; i < JOBS_MAX_QUANTITY; i++) clearjob(&jobs[i]); } int addjob(struct job_t *jobs, pid_t pid, int state, char *cmdline) { int i; if (pid < 1) return 0; for (i = 0; i < JOBS_MAX_QUANTITY; i++) { if (jobs[i].pid == 0) { jobs[i].pid = pid; jobs[i].state = state; jobs[i].jid = nextjid++; if (nextjid > JOBS_MAX_QUANTITY) 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; } void listjobs(struct job_t *jobs) { for (int i = 0; i < JOBS_MAX_QUANTITY; 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); } } } int deletejob(struct job_t *jobs, pid_t pid) { int i; if (pid < 1) return 0; for (i = 0; i < JOBS_MAX_QUANTITY; i++) { if (jobs[i].pid == pid) { clearjob(&jobs[i]); nextjid = maxjid(jobs) + 1; return 1; } } return 0; } void clearjob(struct job_t *job) { job->pid = 0; job->jid = 0; job->state = UNDF; job->cmdline[0] = '\0'; } int pid2jid(pid_t pid) { int i; if (pid < 1) return 0; for (i = 0; i < JOBS_MAX_QUANTITY; i++) if (jobs[i].pid == pid) { return jobs[i].jid; } return 0; } pid_t fgpid(struct job_t *jobs) { for (int i = 0; i < JOBS_MAX_QUANTITY; i++) if (jobs[i].state == FG) return jobs[i].pid; return 0; } struct job_t *getjobpid(struct job_t *jobs, pid_t pid) { int i; if (pid < 1) return NULL; for (i = 0; i < JOBS_MAX_QUANTITY; i++) if (jobs[i].pid == pid) return &jobs[i]; return NULL; } struct job_t *getjobjid(struct job_t *jobs, int jid) { int i; if (jid < 1) return NULL; for (i = 0; i < JOBS_MAX_QUANTITY; i++) if (jobs[i].jid == jid) return &jobs[i]; return NULL; } int maxjid(struct job_t *jobs) { int i, max = 0; for (i = 0; i < JOBS_MAX_QUANTITY; i++) { if (jobs[i].jid > max) { max = jobs[i].jid; } } return max; } 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) fprintf(stdout, "%s: %s\n", 'Signal Error', strerror(errno)); return (old_action.sa_handler); } void sigchld_handler(int signal) { pid_t pid; int status; while ((pid = waitpid(-1, &status, WNOHANG | WUNTRACED)) > 0) { if (WIFEXITED(status)) /* Normal termination */ { deletejob(jobs, pid); } if (WIFSTOPPED(status)) /* Task suspension */ { struct job_t *job = getjobpid(jobs, pid); int jid = pid2jid(pid); printf("Job [%d] (%d) stopped by signal %d\n", jid, pid, WSTOPSIG(status)); job->state = ST; } if (WIFSIGNALED(status)) /* Task terminated */ { int jid = pid2jid(pid); printf("Job [%d] (%d) terminated by signal %d\n", jid, pid, WTERMSIG(status)); deletejob(jobs, pid); } } return; } void sigint_handler(int signal) { pid_t pid = fgpid(jobs); if (pid != 0) { kill(-pid, signal); } return; } void sigtstp_handler(int signal) { pid_t pid = fgpid(jobs); if (pid > 0) { kill(-pid, signal); } return; } void sigquit_handler(int signal) { printf("Terminating after receipt of SIGQUIT signal\n"); exit(1); }