diff options
author | Franciszek Malinka <franciszek.malinka@gmail.com> | 2021-12-03 22:41:34 +0100 |
---|---|---|
committer | Franciszek Malinka <franciszek.malinka@gmail.com> | 2021-12-03 22:41:34 +0100 |
commit | dc4c955e6fb39597abb43e59aecc9e64e78c87d1 (patch) | |
tree | a421e94a6139ae287f9a6156b465be6122809a72 | |
parent | b0bef722a4b903f12a98858d2dc26ccb492797b3 (diff) |
first try
-rw-r--r-- | command.c | 17 | ||||
-rw-r--r-- | jobs.c | 186 | ||||
-rw-r--r-- | shell.c | 134 |
3 files changed, 322 insertions, 15 deletions
@@ -113,6 +113,23 @@ noreturn void external_command(char **argv) { if (!index(argv[0], '/') && path) { /* TODO: For all paths in PATH construct an absolute path and execve it. */ #ifdef STUDENT + const char *dir = path; + const char *dir_end; + char *absolute_path = alloca(strlen(path) + strlen(argv[0])); + /* MY-TODO: this is ugly. UPDATE: BUT IT WORKS! */ + do { + dir_end = index(dir, ':'); + int pnt = 0; + while (*dir && dir != dir_end) { + absolute_path[pnt++] = *dir++; + } + if (dir_end) + dir_end++; + absolute_path[pnt] = 0; + strcat(absolute_path, "/"); + strcat(absolute_path, argv[0]); + (void)execve(absolute_path, argv, environ); + } while ((dir = dir_end)); #endif /* !STUDENT */ } else { (void)execve(argv[0], argv, environ); @@ -27,8 +27,82 @@ static void sigchld_handler(int sig) { /* TODO: Change state (FINISHED, RUNNING, STOPPED) of processes and jobs. * Bury all children that finished saving their status in jobs. */ #ifdef STUDENT - (void)status; - (void)pid; + /* MY-TODO: we waitpid all processes, but what if there is nothing to wait + * for and some child dies in some code after the while? Will we reap it? */ + + job_t *cur_job; + proc_t *cur_proc; + // msg("sig> Hello from signal!\n"); + /* Change state of each process */ + while (true) { + pid = waitpid(-1, &status, WCONTINUED | WUNTRACED | WNOHANG); + /* MY-TODO: Maybe some other error can occur? */ + // msg("sig> XD %d\n", pid); + if (pid == 0 || (pid < 0 && errno == ECHILD)) + break; + for (int j = 0; j < njobmax; j++) { + cur_job = jobs + j; + if (!cur_job->pgid) + continue; + for (int p = 0; p < cur_job->nproc; p++) { + cur_proc = &cur_job->proc[p]; + if (cur_proc->pid == pid) { + // msg("What happened to the child?\n"); + if (WIFSTOPPED(status)) { + cur_proc->state = STOPPED; + // msg("Stopped\n"); + } else if (WIFCONTINUED(status)) { + // msg("Continued\n"); + cur_proc->state = RUNNING; + } else { + cur_proc->state = FINISHED; + if (WIFSIGNALED(status)) + cur_proc->exitcode = WTERMSIG(status) + 128; + else + cur_proc->exitcode = WEXITSTATUS(status); + } + } + } + } + } + + /* Go through jobs list and change jobs' states only if + * every process in given job has the same state + * OR if all states other than finished are the same! + */ + + /* MY-TODO: this is not correct: what if two processes are finished, + * and one is suspended in a job? Then the job should change state + * to suspended for sure + * Maybe move this logic to jobstate? + * Nope + * Should be correct now! + */ + for (int j = 0; j < njobmax; j++) { + cur_job = jobs + j; + if (!cur_job->pgid) + continue; + bool is_running = false; + bool is_stopped = false; + for (int p = 0; p < cur_job->nproc; p++) { + cur_proc = &cur_job->proc[p]; + if (cur_proc->state == RUNNING) + is_running = true; + if (cur_proc->state == STOPPED) { + // msg("Henlo :)\n"); + is_stopped = true; + } + } + if (is_running) { + cur_job->state = RUNNING; + } else if (is_stopped) { + // msg("Henlo\n"); + cur_job->state = STOPPED; + } else { + // msg("Henlo????\n"); + cur_job->state = FINISHED; + } + } #endif /* !STUDENT */ errno = old_errno; } @@ -117,7 +191,11 @@ static int jobstate(int j, int *statusp) { /* TODO: Handle case where job has finished. */ #ifdef STUDENT - (void)exitcode; + /* MY-TODO: shouldn't we go through the processes list and check the state? */ + if (state == FINISHED) { + *statusp = exitcode(job); + deljob(job); + } #endif /* !STUDENT */ return state; @@ -142,7 +220,18 @@ bool resumejob(int j, int bg, sigset_t *mask) { /* TODO: Continue stopped job. Possibly move job to foreground slot. */ #ifdef STUDENT - (void)movejob; + job_t *job = &jobs[j]; + if (job->pgid == 0) + return false; + + printf("[%d] continue \'%s\'\n", j, job->command); + Kill(job->pgid, SIGCONT); + /* MY-TODO: what if there is some job in the foreground? + * I think it shouldn't happen, but what if? */ + if (bg == FG) { + movejob(j, FG); + monitorjob(mask); + } #endif /* !STUDENT */ return true; @@ -156,6 +245,15 @@ bool killjob(int j) { /* TODO: I love the smell of napalm in the morning. */ #ifdef STUDENT + /* MY-TODO: shouldn't we check if jobs[j].pgid != 0? + * answer: yeah, we should! + * kontr-answer: or do we? + */ + if (jobs[j].pgid == 0) + return false; + /* If job is stopped, then it won't handle the SIGTERM */ + Kill(jobs[j].pgid, SIGCONT); + Kill(jobs[j].pgid, SIGTERM); #endif /* !STUDENT */ return true; @@ -169,7 +267,38 @@ void watchjobs(int which) { /* TODO: Report job number, state, command and exit code or signal. */ #ifdef STUDENT - (void)deljob; + /* MY-TODO: Maybe this can be done cleaner? */ + #define KILLED 3 + const char *state_to_name[] = {"exited", "running", "suspended", "killed"}; + sigset_t mask; + + /* MY-TODO: shouldn't we do this outside of the loop? Ask Cahir */ + Sigprocmask(SIG_BLOCK, &sigchld_mask, &mask); + + job_t *job = &jobs[j]; + int status = 0; + int state = job->state; + if (which == ALL || state == which) { + /* Potentially deleting this job, so we have to copy the command */ + if (state == FINISHED) { + status = exitcode(job); + if (status > 128) { + state = KILLED; + } + } + + printf("[%d] %s \'%s\'", j, state_to_name[state], job->command); + if (state == KILLED) { + printf(" by signal %d", status - 128); + deljob(job); + } + if (state == FINISHED) { + printf(", status=%d", status); + deljob(job); + } + printf("\n"); + } + Sigprocmask(SIG_SETMASK, &mask, NULL); #endif /* !STUDENT */ } } @@ -181,9 +310,32 @@ int monitorjob(sigset_t *mask) { /* TODO: Following code requires use of Tcsetpgrp of tty_fd. */ #ifdef STUDENT - (void)jobstate; - (void)exitcode; - (void)state; + /* MY-TODO: don't we have to check for jobs[FG] existance first? */ + /* MY-TODO: use state somehow */ + /* MY-TODO: we probably want to block sigchld, that's what mask is for */ + + job_t *j = &jobs[FG]; + Tcsetpgrp(tty_fd, j->pgid); + Tcsetattr(tty_fd, TCSADRAIN, &j->tmodes); + + do { + // msg("> Suspedning\n"); + Sigsuspend(mask); + // msg("> Resumed\n"); + state = jobstate(FG, &exitcode); + } while (state == RUNNING); + + // msg("> State: %d\n", state); + if (state == STOPPED) { + int new_j = addjob(0, BG); + j = &jobs[new_j]; + movejob(FG, new_j); + } + + Tcgetattr(tty_fd, &j->tmodes); + Tcsetattr(tty_fd, TCSADRAIN, &shell_tmodes); + Tcsetpgrp(tty_fd, getpid()); + #endif /* !STUDENT */ return exitcode; @@ -224,6 +376,24 @@ void shutdownjobs(void) { /* TODO: Kill remaining jobs and wait for them to finish. */ #ifdef STUDENT + /* MY-TODO: if job is stopped, then we cannot kill it :P */ + for (int j = 0; j < njobmax; j++) { + + if (!jobs[j].pgid) { + continue; + } + /* MY-TODO: Calling killjob? Not sure about that */ + killjob(j); + // Kill(jobs[j].pgid, SIGKILL); + } + + /* MY-TODO: what if some job get the signal after the wait? + * We shouldn't wait here i think :/ + * Nope, we should. Just wait for every specific job. + */ + int ret; + while ((ret = wait(NULL)) > 0) + ; #endif /* !STUDENT */ watchjobs(FINISHED); @@ -31,8 +31,30 @@ static int do_redir(token_t *token, int ntokens, int *inputp, int *outputp) { for (int i = 0; i < ntokens; i++) { /* TODO: Handle tokens and open files as requested. */ #ifdef STUDENT - (void)mode; - (void)MaybeClose; + /* MY-TODO: I assume that tokens are only WORD, T_INPUT, T_OUTPUT. */ + /* MY-TODO: MaybeClose? What for? */ + // msg("do_redir: ntokens: %d ", ntokens); + // for (int i = 0; i < ntokens; i++) { + // msg("%s ", token[i]); + // } + // msg("\n"); + mode = token[i]; + if (mode == T_INPUT) { + // MaybeClose(STDIN_FILENO); + *inputp = Open(token[i + 1], O_RDONLY, 0); + token[i] = T_NULL; + token[i + 1] = T_NULL; + } else if (mode == T_OUTPUT) { + // MaybeClose(STDOUT_FILENO); + *outputp = Open(token[i + 1], O_WRONLY | O_TRUNC | O_CREAT, 0644); + token[i] = T_NULL; + token[i + 1] = T_NULL; + } else if (mode != T_NULL) { + token[n] = token[i]; + if (i != n) + token[i] = T_NULL; + n++; + } #endif /* !STUDENT */ } @@ -58,6 +80,45 @@ static int do_job(token_t *token, int ntokens, bool bg) { /* TODO: Start a subprocess, create a job and monitor it. */ #ifdef STUDENT + /* MY-TODO: we have to copy the tokens to some other array */ + // msg("do_job after do_redir: %d ", ntokens); + // for (int i = 0; i < ntokens; i++) { + // msg("%s ", token[i]); + // } + // msg("\n"); + // msg("Whadupp, sigchld is blocked!\n"); + pid_t pid = Fork(); + if (pid) { /* shell */ + /* This can result with an error: child can not yet execute + * and therefore setpgid will error with errno set to EPERM + */ + setpgid(pid, pid); + MaybeClose(&input); + MaybeClose(&output); + int j = addjob(pid, bg); + addproc(j, pid, token); + if (bg == FG) + exitcode = monitorjob(&mask); + else { + watchjobs(RUNNING); + } + } else { /* job */ + /* Reset signal mask */ + Signal(SIGINT, SIG_DFL); + Signal(SIGTSTP, SIG_DFL); + Signal(SIGTTIN, SIG_DFL); + Signal(SIGTTOU, SIG_DFL); + if (input != -1) { + Dup2(input, STDIN_FILENO); + MaybeClose(&input); + } + if (output != -1) { + Dup2(output, STDOUT_FILENO); + MaybeClose(&output); + } + Setpgid(0, 0); + external_command(token); + } #endif /* !STUDENT */ Sigprocmask(SIG_SETMASK, &mask, NULL); @@ -76,6 +137,32 @@ static pid_t do_stage(pid_t pgid, sigset_t *mask, int input, int output, /* TODO: Start a subprocess and make sure it's moved to a process group. */ pid_t pid = Fork(); #ifdef STUDENT + /* if pgid == 0, then this is the first process in the pipe, + * so let the pipeline be in the group of this process */ + if (pid) { /* shell */ + if (pgid == 0) + pgid = pid; + setpgid(pid, pgid); + MaybeClose(&input); + MaybeClose(&output); + } else { /* job */ + if (pgid == 0) + pgid = getpid(); + Setpgid(pid, pgid); + Signal(SIGINT, SIG_DFL); + Signal(SIGTSTP, SIG_DFL); + Signal(SIGTTIN, SIG_DFL); + Signal(SIGTTOU, SIG_DFL); + if (input != -1) { + Dup2(input, STDIN_FILENO); + MaybeClose(&input); + } + if (output != -1) { + Dup2(output, STDOUT_FILENO); + MaybeClose(&output); + } + external_command(token); + } #endif /* !STUDENT */ return pid; @@ -107,11 +194,44 @@ static int do_pipeline(token_t *token, int ntokens, bool bg) { /* TODO: Start pipeline subprocesses, create a job and monitor it. * Remember to close unused pipe ends! */ #ifdef STUDENT - (void)input; - (void)job; - (void)pid; - (void)pgid; - (void)do_stage; + // (void)input; + // (void)job; + // (void)pid; + // (void)pgid; + // (void)do_stage; + int stage_begin = 0; + int pipe_where = 0; + while (stage_begin < ntokens) { + while (pipe_where < ntokens && token[pipe_where] != T_PIPE) + pipe_where++; + if (pipe_where < ntokens) { + token[pipe_where] = T_NULL; + } + /* If this is the last job in the pipeline, then don't redir the output */ + if (pipe_where >= ntokens) { + MaybeClose(&output); + output = -1; + } + pid = do_stage(pgid, &mask, input, output, token + stage_begin, + pipe_where - stage_begin, bg); + /* MY-TODO: close those fds somehow, but how?? */ + // MaybeClose(&input); + // MaybeClose(&output); + if (!pgid) { + pgid = pid; + job = addjob(pgid, bg); + } + addproc(job, pid, token + stage_begin); + stage_begin = ++pipe_where; + input = next_input; + mkpipe(&next_input, &output); + } + MaybeClose(&next_input); + MaybeClose(&output); + MaybeClose(&input); + if (bg == FG) { + monitorjob(&mask); + } #endif /* !STUDENT */ Sigprocmask(SIG_SETMASK, &mask, NULL); |