#include <stdio.h>
#include <errno.h>
#include <assert.h>
#include <string.h>
#include <stdlib.h>
#include <stdbool.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <time.h>
// 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;
}