#include #include #include #include #include #include #include #include #include #include // For reference // ~cpen212/Public/lab5/crash-ref #define MAXJOBS 32 #define MAXLINE 1024 char **environ; struct job { pid_t pid; char cmd[MAXLINE]; bool terminated; bool foreground; }; struct job job_list[MAXJOBS]; int num_jobs = 0; void int_to_str(int num, char* str); char* string_combine(char* jid_str, char* pid_str, char* status, char* cmd); void handle_sigchld(int sig) { int status, pid; while ((pid = waitpid(-1, &status, WNOHANG | WUNTRACED)) > 0) { for (int i = 0; i < num_jobs; i++) { if (job_list[i].pid == pid && job_list[i].foreground == false) { // Convert the job ID and PID to strings using a helper function char jid_str[16], pid_str[16]; int_to_str(i+1, jid_str); int_to_str(job_list[i].pid, pid_str); if (WIFEXITED(status)) { char* msg = string_combine(jid_str, pid_str, "finished", job_list[i].cmd); int len = strlen(msg); write(STDOUT_FILENO, msg, len); job_list[i].terminated = true; } else if (WIFSIGNALED(status)) { if (WCOREDUMP(status)) { char* msg = string_combine(jid_str, pid_str, "killed (core dumped)", job_list[i].cmd); int len = strlen(msg); write(STDOUT_FILENO, msg, len); } else { char* msg = string_combine(jid_str, pid_str, "killed", job_list[i].cmd); int len = strlen(msg); write(STDOUT_FILENO, msg, len); } job_list[i].terminated = true; } } } } } void handle_sigtstp(int sig) { // TODO } void handle_sigint(int sig) { pid_t fg_pgid = tcgetpgrp(STDIN_FILENO); if (sig == SIGINT) { for (int i = 0; i < num_jobs; i++) { if (job_list[i].foreground == true && job_list[i].terminated == false) { char jid_str[16], pid_str[16]; int_to_str(i+1, jid_str); int_to_str(job_list[i].pid, pid_str); char* msg = string_combine(jid_str, pid_str, "killed", job_list[i].cmd); int len = strlen(msg); write(STDOUT_FILENO, msg, len); job_list[i].foreground = false; } } } } void handle_sigquit(int sig) { pid_t fg_pgid = tcgetpgrp(STDIN_FILENO); if (sig == SIGQUIT) { for (int i = 0; i < num_jobs; i++) { if (job_list[i].foreground == true && job_list[i].terminated == false) { char jid_str[16], pid_str[16]; int_to_str(i+1, jid_str); int_to_str(job_list[i].pid, pid_str); char* msg = string_combine(jid_str, pid_str, "killed (core dumped)", job_list[i].cmd); int len = strlen(msg); write(STDOUT_FILENO, msg, len); job_list[i].foreground = false; } } } } void install_signal_handlers() { struct sigaction sa_chld, sa_tstp, sa_int, sa_quit; sa_chld.sa_handler = handle_sigchld; sa_tstp.sa_handler = handle_sigtstp; sa_int.sa_handler = handle_sigint; sa_quit.sa_handler = handle_sigquit; sigemptyset(&sa_chld.sa_mask); sigemptyset(&sa_tstp.sa_mask); sigemptyset(&sa_int.sa_mask); sigemptyset(&sa_quit.sa_mask); sa_chld.sa_flags = SA_RESTART; sa_tstp.sa_flags = SA_RESTART; sa_int.sa_flags = SA_RESTART; sa_quit.sa_flags = SA_RESTART; sigaction(SIGCHLD, &sa_chld, NULL); sigaction(SIGTSTP, &sa_tstp, NULL); sigaction(SIGINT, &sa_int, NULL); sigaction(SIGQUIT, &sa_quit, NULL); } void spawn(const char **toks, bool bg) { // bg is true iff command ended with & pid_t pid = fork(); // create a new process int status; if (pid < 0) { fprintf(stderr, "ERROR: failed to fork\n"); exit(1); } else if (pid == 0) { // child process execvp(toks[0], (char * const *)toks); // execute the command fprintf(stderr, "ERROR: cannot run %s\n", toks[0]); // executes only if execvp fails exit(1); } else { // parent process if (num_jobs == MAXJOBS) { // fprintf(stderr, "ERROR: too many jobs\n"); } else { job_list[num_jobs].pid = pid; strncpy(job_list[num_jobs].cmd, *toks, MAXLINE); num_jobs++; if (bg) { printf("[%d] (%d) running %s\n", num_jobs, job_list[num_jobs - 1].pid, job_list[num_jobs - 1].cmd); } } if (!bg) { // waits if running in foreground job_list[num_jobs - 1].foreground = true; waitpid(pid, &status, 0); job_list[num_jobs - 1].terminated = true; } } } void cmd_jobs(const char **toks) { int term_counter = 0; if (toks[1] != NULL) { fprintf(stderr, "ERROR: jobs takes no arguments\n"); } else { for (int i = 0; i < num_jobs; i++) { if (job_list[i].terminated) { continue; } printf("[%d] (%d) running %s\n", i + 1, job_list[i].pid, job_list[i].cmd); } } } void cmd_fg(const char **toks) { // Check if there is currently no foreground job running pid_t fg_pid = tcgetpgrp(STDIN_FILENO); if (fg_pid != getpgrp()) { fprintf(stderr, "ERROR: There is already a foreground job running\n"); return; } if (toks[1] == NULL || toks[2] != NULL) { fprintf(stderr, "ERROR: fg takes exactly one argument\n"); } else if (toks[1][0] == '%') { // by job char *endptr; int job_id = strtol(&toks[1][1], &endptr, 10); if (*endptr != '\0' || job_id < 0) { fprintf(stderr, "ERROR: bad argument for fg: %s\n", toks[1]); } else if (job_id > 0 && job_id < MAXJOBS && job_list[job_id - 1].pid > 0) { tcsetpgrp(STDIN_FILENO, job_list[job_id - 1].pid); job_list[job_id - 1].foreground = true; // Wait for the foreground job to complete while (job_list[job_id - 1].terminated == false) { struct timespec ts; ts.tv_sec = 0; ts.tv_nsec = 1000000; // sleep for 1 millisecond nanosleep(&ts, NULL); // Check if the job has completed int status; pid_t result = waitpid(job_list[job_id - 1].pid, &status, WNOHANG); if (result == job_list[job_id - 1].pid) { job_list[job_id - 1].terminated = true; job_list[job_id - 1].foreground = false; } } } else { fprintf(stderr, "ERROR: no job %s\n", toks[1]); } } else if (atoi(toks[1]) > 0) { // by PID int temp_pid = atoi(toks[1]); int no_fg = 1; for (int i = 0; i < num_jobs; i++) { if (job_list[i].pid == temp_pid) { tcsetpgrp(STDIN_FILENO, job_list[i].pid); job_list[i].foreground = true; // Wait for the foreground job to complete while (job_list[i].terminated == false) { struct timespec ts; ts.tv_sec = 0; ts.tv_nsec = 1000000; // sleep for 1 ms nanosleep(&ts, NULL); // Check if the job has completed int status; pid_t result = waitpid(job_list[i].pid, &status, WNOHANG); if (result == job_list[i].pid) { job_list[i].terminated = true; job_list[i].foreground = false; } } no_fg = 0; } } if (no_fg) { fprintf(stderr, "ERROR: no PID %s\n", toks[1]); } } else { fprintf(stderr, "ERROR: bad argument for fg: %s\n", toks[1]); } } void cmd_bg(const char **toks) { // TODO } void cmd_nuke(const char **toks) { if (toks[2] != NULL) { fprintf(stderr, "ERROR: nuke takes exactly one argument\n"); } else if (toks[1] == NULL) { // kill all for (int i = 0; i < num_jobs; i++) { kill(job_list[i].pid, SIGKILL); } } else if (toks[1][0] == '%') { // kill by job char *endptr; int job_id = strtol(&toks[1][1], &endptr, 10); if (*endptr != '\0' || job_id < 0) { fprintf(stderr, "ERROR: bad argument for nuke: %s\n", toks[1]); } else if (job_id > 0 && job_id < MAXJOBS && job_list[job_id - 1].pid > 0) { kill(job_list[job_id - 1].pid, SIGKILL); } else { fprintf(stderr, "ERROR: no job %s\n", toks[1]); } } else if (atoi(toks[1]) > 0) { // kill by PID int temp_pid = atoi(toks[1]); int no_kill = 1; for (int i = 0; i < num_jobs; i++) { if (job_list[i].pid == temp_pid) { kill(job_list[i].pid, SIGKILL); no_kill = 0; } } if (no_kill) { fprintf(stderr, "ERROR: no PID %s\n", toks[1]); } } else { fprintf(stderr, "ERROR: bad argument for nuke: %s\n", toks[1]); } } void cmd_quit(const char **toks) { if (toks[1] != NULL) { fprintf(stderr, "ERROR: quit takes no arguments\n"); } else { exit(0); } } void eval(const char **toks, bool bg) { // bg is true iff command ended with & assert(toks); if (*toks == NULL) return; if (strcmp(toks[0], "quit") == 0) { cmd_quit(toks); } else if (strcmp(toks[0], "jobs") == 0) { cmd_jobs(toks); } else if (strcmp(toks[0], "nuke") == 0) { cmd_nuke(toks); } else if (strcmp(toks[0], "fg") == 0) { cmd_fg(toks); } else { spawn(toks, bg); } } // you don't need to touch this unless you want to add debugging void parse_and_eval(char *s) { assert(s); const char *toks[MAXLINE + 1]; while (*s != '\0') { bool end = false; bool bg = false; int t = 0; while (*s != '\0' && !end) { while (*s == '\n' || *s == '\t' || *s == ' ') ++s; if (*s != ';' && *s != '&' && *s != '\0') toks[t++] = s; while (strchr("&;\n\t ", *s) == NULL) ++s; switch (*s) { case '&': bg = true; end = true; break; case ';': end = true; break; } if (*s) *s++ = '\0'; } toks[t] = NULL; eval(toks, bg); } } // you don't need to touch this unless you want to add debugging void prompt() { printf("crash> "); fflush(stdout); } // you don't need to touch this unless you want to add debugging int repl() { char *buf = NULL; size_t len = 0; while (prompt(), getline(&buf, &len, stdin) != -1) { parse_and_eval(buf); } if (buf != NULL) free(buf); if (ferror(stdin)) { perror("ERROR"); return 1; } return 0; } // you don't need to touch this unless you want to add debugging options int main(int argc, char **argv) { install_signal_handlers(); return repl(); } void int_to_str(int num, char* str) { int i = 0; // Build string in reverse order while (num > 0) { str[i++] = (num % 10) + '0'; num /= 10; } str[i] = '\0'; // Reverse string int len = strlen(str); for (int j = 0; j < len / 2; j++) { char temp = str[j]; str[j] = str[len - j - 1]; str[len - j - 1] = temp; } } char* string_combine(char* jid_str, char* pid_str, char* status, char* cmd) { char* strings[] = {"[", jid_str, "] (", pid_str, ") ", status, " ", cmd, "\n"}; int total_len = 0; for(int i = 0; i < 9; i++) { total_len += strlen(strings[i]); } char* result = (char*) malloc(total_len + 1); // +1 for null terminator result[0] = '\0'; for(int i = 0; i < 9; i++) { strcat(result, strings[i]); } return result; }