From 3ab9f268fb7e86d94f98785fc70a72cecf2eaaf7 Mon Sep 17 00:00:00 2001 From: Trent Huber Date: Sun, 25 May 2025 22:55:07 -0400 Subject: [PATCH] Second phase refactoring; minor feature additions --- build.c | 8 +-- src/builtins.c | 102 ++++++++++++++++++-------------------- src/history.c | 32 +++++------- src/history.h | 6 +-- src/input.c | 99 +++++++++++++++++++++---------------- src/input.h | 2 +- src/job.c | 48 ++++++++++++++++++ src/job.h | 18 +++++++ src/{cmd.c => lex.c} | 43 ++++++++-------- src/{cmd.h => lex.h} | 2 +- src/main.c | 113 +++++++++++++++++++++++-------------------- src/pg.c | 74 ---------------------------- src/pg.h | 25 ---------- src/stack.c | 26 ++++++++++ src/stack.h | 17 +++++++ src/term.c | 110 +++++++++++++++++++++++++++-------------- src/term.h | 7 ++- 17 files changed, 394 insertions(+), 338 deletions(-) create mode 100644 src/job.c create mode 100644 src/job.h rename src/{cmd.c => lex.c} (57%) rename src/{cmd.h => lex.h} (80%) delete mode 100644 src/pg.c delete mode 100644 src/pg.h create mode 100644 src/stack.c create mode 100644 src/stack.h diff --git a/build.c b/build.c index ff40a75..de2b9b1 100644 --- a/build.c +++ b/build.c @@ -3,14 +3,14 @@ #define SRCDIR "src/" #define SRC \ SRCDIR "builtins", \ - SRCDIR "cmd", \ SRCDIR "history", \ SRCDIR "input", \ + SRCDIR "job", \ + SRCDIR "lex", \ SRCDIR "main", \ - SRCDIR "pg", \ + SRCDIR "stack", \ SRCDIR "term", \ SRCDIR "utils" - #define BINDIR "bin/" #define ASH BINDIR "ash" @@ -20,7 +20,7 @@ int main(void) { build(NULL); src = (char *[]){SRC, NULL}; - cflags = (char *[]){"-ferror-limit=1", NULL}; + // cflags = (char *[]){"-ferror-limit=1", NULL}; while (*src) compile(*src++, NULL); load('x', ASH, SRC, NULL); diff --git a/src/builtins.c b/src/builtins.c index 5edcdf8..8199fbf 100644 --- a/src/builtins.c +++ b/src/builtins.c @@ -1,19 +1,24 @@ #include -#include -#include -#include -#include #include #include -#include #include +#include +#include +#include +#include +#include -#include "pg.h" +#include "job.h" +#include "stack.h" #include "term.h" -#include "wait.h" #define BUILTINSIG(name) int name(char **tokens) +struct builtin { + char *name; + BUILTINSIG((*func)); +}; + BUILTINSIG(cd) { if (!tokens[1]) return 1; if (chdir(tokens[1]) != -1) return 0; @@ -22,83 +27,72 @@ BUILTINSIG(cd) { } BUILTINSIG(fg) { - long id; - struct pg pg; + long jobid; + struct job *job; if (tokens[1]) { errno = 0; - if ((id = strtol(tokens[1], NULL, 10)) == LONG_MAX && errno || id <= 0) { + if ((jobid = strtol(tokens[1], NULL, 10)) == LONG_MAX && errno + || jobid <= 0) { warn("Invalid process group id"); return 1; } - if (find(&spgs, (pid_t)id)) pg = *spgs.c; - else if (find(&bgpgs, (pid_t)id)) pg = *bgpgs.c; - else { - warn("Unable to find process group %ld", id); + if (!(job = findjob((pid_t)jobid))) { + warnx("Unable to find process group %d", (pid_t)jobid); return 1; } - } else if (!(pg = peek(recent)).id) { + job = deletejob(); + } else if (!(job = pull(&jobs))) { warnx("No processes to bring into the foreground"); return 1; } - if (tcsetattr(STDIN_FILENO, TCSANOW, &pg.config) == -1) { - warn("Unable to reload termios state for process group %d", pg.id); - return 1; - } - if (tcsetpgrp(STDIN_FILENO, pg.id) == -1) { - warn("Unable to bring process group %d to foreground", pg.id); - if (tcsetattr(STDIN_FILENO, TCSANOW, &raw) == -1) - warn("Unable to set termios state back to raw mode"); - return 1; - } - if (killpg(pg.id, SIGCONT) == -1) { - if (tcsetpgrp(STDIN_FILENO, self) == -1) _Exit(EXIT_FAILURE); - warn("Unable to wake up suspended process group %d", pg.id); - if (tcsetattr(STDIN_FILENO, TCSANOW, &raw) == -1) - warn("Unable to set termios state back to raw mode"); - return 1; - } - if (tokens[1]) cut(recent); else pull(recent); - waitfg(pg.id); + setfg(*job); + waitfg(*job); + return 0; } BUILTINSIG(bg) { - long id; - struct pg pg; + long jobid; + struct job *job; if (tokens[1]) { errno = 0; - if ((id = strtol(tokens[1], NULL, 10)) == LONG_MAX && errno || id <= 0) { - warn("Invalid process group id"); + if ((jobid = strtol(tokens[1], NULL, 10)) == LONG_MAX && errno + || jobid <= 0) { + warn("Invalid job id"); + return 1; + } + if (!(job = findjob((pid_t)jobid))) { + warnx("Unable to find job %d", (pid_t)jobid); return 1; } - if (!find(&spgs, (pid_t)id)) { - warn("Unable to find process group %ld", id); + if (job->type == BACKGROUND) { + warnx("Job %d already in background", (pid_t)jobid); return 1; } - pg = *spgs.c; - } else if (!(pg = peek(&spgs)).id) { - warnx("No suspended processes to run in the background"); + } else + for (jobs.c = MINUSONE(jobs, t); CURRENT->type == BACKGROUND; DEC(jobs, c)) + if (jobs.c == jobs.b) { + warnx("No suspended jobs to run in background"); + return 1; + } + job = deletejob(); + + if (!(push(&jobs, job))) { + warnx("Unable to add job to background; too many jobs"); return 1; } - if (killpg(pg.id, SIGCONT) == -1) { - warn("Unable to wake up suspended process group %d", pg.id); + if (killpg(job->id, SIGCONT) == -1) { + warn("Unable to wake up suspended process group %d", job->id); return 1; } - if (tokens[1]) cut(&spgs); else pull(&spgs); - push(&bgpgs, pg); - recent = &bgpgs; + job->type = BACKGROUND; + return 0; } -struct builtin { - char *name; - int (*func)(char **tokens); -}; - #define BUILTIN(name) {#name, name} - BUILTINSIG(which); struct builtin builtins[] = { BUILTIN(cd), diff --git a/src/history.c b/src/history.c index 1509e66..fc2a351 100644 --- a/src/history.c +++ b/src/history.c @@ -1,21 +1,21 @@ +#include #include #include #include -#include #include +#include "history.h" #include "input.h" +#include "stack.h" #include "utils.h" -#include "history.h" #define HISTNAME ".ashhistory" -// TODO: This could be its own struct and use macros like the stopped process table -char history[HISTLEN][BUFLEN], (*hb)[BUFLEN], (*hc)[BUFLEN], (*ht)[BUFLEN]; -static char *histpath; +static char *histpath, histarr[HISTLEN + 1][BUFLEN + 1]; +struct stack INITSTACK(history, histarr, 1); void readhist(void) { - char *homepath; + char *homepath, buffer[BUFLEN + 1]; FILE *histfile; int e; @@ -26,23 +26,18 @@ void readhist(void) { strcat(histpath, "/"); strcat(histpath, HISTNAME); - ht = hc = hb = history; if (!(histfile = fopen(histpath, "r"))) { if (errno == ENOENT) return; err(EXIT_FAILURE, "Unable to open history file for reading"); } - while (fgets(*ht, BUFLEN, histfile)) { - *(*ht + strlen(*ht) - 1) = '\0'; // Get rid of newline - ht = history + (ht - history + 1) % HISTLEN; - if (ht == hb) hb = NULL; + while (fgets(buffer, history.size, histfile)) { + *(buffer + strlen(buffer) - 1) = '\0'; + push(&history, buffer); } - e = ferror(histfile) || !feof(histfile); + if (ferror(histfile) || !feof(histfile)) + err(EXIT_FAILURE, "Unable to read from history file"); if (fclose(histfile) == EOF) err(EXIT_FAILURE, "Unable to close history file"); - if (e) err(EXIT_FAILURE, "Unable to read from history file"); - - if (!hb) hb = history + (ht - history + 1) % HISTLEN; - hc = ht; } void writehist(void) { @@ -53,8 +48,8 @@ void writehist(void) { return; } - while (hb != ht) { - if (fputs(*hb, histfile) == EOF) { + for (history.c = history.b; history.c != history.t; INC(history, c)) { + if (fputs(history.c, histfile) == EOF) { warn("Unable to write to history file"); break; } @@ -62,7 +57,6 @@ void writehist(void) { warn("Unable to write newline to history file"); break; } - hb = history + (hb - history + 1) % HISTLEN; } if (fclose(histfile) == EOF) warn("Unable to close history stream"); diff --git a/src/history.h b/src/history.h index 54e558f..c395a45 100644 --- a/src/history.h +++ b/src/history.h @@ -1,7 +1,7 @@ -#define HISTLEN 101 -#define BUFLEN 1001 +#define HISTLEN 100 +#define BUFLEN 1000 -extern char history[HISTLEN][BUFLEN], (*hb)[BUFLEN], (*hc)[BUFLEN], (*ht)[BUFLEN]; +extern struct stack history; void readhist(void); void writehist(void); diff --git a/src/input.c b/src/input.c index 134c578..f08463a 100644 --- a/src/input.c +++ b/src/input.c @@ -5,14 +5,19 @@ #include #include "history.h" -#include "pg.h" +#include "job.h" +#include "stack.h" #define PROMPT "% " -enum { +enum character { CTRLC = '\003', CTRLD, + CLEAR = '\014', ESCAPE = '\033', + FORWARD = 'f', + BACKWARD = 'b', + ARROW = '[', UP = 'A', DOWN, RIGHT, @@ -20,19 +25,23 @@ enum { BACKSPACE = '\177', }; -int getinput(char *buffer) { +static char buffer[BUFLEN + 1]; + +char *input(void) { char *cursor, *end; int c, i; - signal(SIGCHLD, waitbg); + signal(SIGCHLD, waitbg); // TODO: Use sigaction for portability +reset: end = cursor = buffer; - *buffer = '\0'; + *history.t = *buffer = '\0'; + history.c = history.t; while (buffer == end) { fputs(PROMPT, stdout); while ((c = getchar()) != '\n') { if (c >= ' ' && c <= '~') { - if (end - buffer == BUFLEN - 1) continue; + if (end - buffer == BUFLEN) continue; memmove(cursor + 1, cursor, end - cursor); *cursor++ = c; *++end = '\0'; @@ -43,44 +52,54 @@ int getinput(char *buffer) { } else switch (c) { case CTRLC: puts("^C"); - ungetc('\n', stdin); - continue; + goto reset; case CTRLD: puts("^D"); signal(SIGCHLD, SIG_DFL); - return 0; + return NULL; + case CLEAR: + fputs("\033[H\033[J", stdout); + fputs(PROMPT, stdout); + fputs(buffer, stdout); + continue; case ESCAPE: - if ((c = getchar()) != '[') { - ungetc(c, stdin); - break; - } switch ((c = getchar())) { - case UP: - case DOWN: - if (hc == (c == UP ? hb : ht)) continue; - if (strcmp(*hc, buffer) != 0) strcpy(*ht, buffer); - - putchar('\r'); - for (i = end - buffer + strlen(PROMPT); i > 0; --i) putchar(' '); - putchar('\r'); - fputs(PROMPT, stdout); - - hc = history + ((hc - history + (c == UP ? -1 : 1)) % HISTLEN + HISTLEN) - % HISTLEN; - strcpy(buffer, *hc); - end = cursor = buffer + strlen(buffer); - - fputs(buffer, stdout); + case FORWARD: + while (cursor != end && *cursor != ' ') putchar(*cursor++); + while (cursor != end && *cursor == ' ') putchar(*cursor++); break; - case LEFT: - if (cursor > buffer) { - putchar('\b'); - --cursor; - } + case BACKWARD: + while (cursor != buffer && *(cursor - 1) == ' ') putchar((--cursor, '\b')); + while (cursor != buffer && *(cursor - 1) != ' ') putchar((--cursor, '\b')); break; - case RIGHT: - if (cursor < end) putchar(*cursor++); + case ARROW: + switch ((c = getchar())) { + case UP: + case DOWN: + if (history.c == (c == UP ? history.b : history.t)) continue; + + putchar('\r'); + for (i = end - buffer + strlen(PROMPT); i > 0; --i) putchar(' '); + putchar('\r'); + + if (strcmp(history.c, buffer) != 0) strcpy(history.t, buffer); + if (c == UP) DEC(history, c); else INC(history, c); + strcpy(buffer, history.c); + end = cursor = buffer + strlen(buffer); + + fputs(PROMPT, stdout); + fputs(buffer, stdout); + break; + case LEFT: + if (cursor > buffer) putchar((--cursor, '\b')); + break; + case RIGHT: + if (cursor < end) putchar(*cursor++); + break; + } break; + default: + ungetc(c, stdin); } break; case BACKSPACE: @@ -98,15 +117,11 @@ int getinput(char *buffer) { } } } - fpurge(stdout); - strcpy(*ht, buffer); - ht = history + (ht - history + 1) % HISTLEN; - hc = ht; - if (ht == hb) hb = history + (hb - history + 1) % HISTLEN; + push(&history, buffer); signal(SIGCHLD, SIG_DFL); - return 1; + return buffer; } diff --git a/src/input.h b/src/input.h index 6e39fb1..dee8d7a 100644 --- a/src/input.h +++ b/src/input.h @@ -1 +1 @@ -int getinput(char *buffer); +char *input(void); diff --git a/src/job.c b/src/job.c new file mode 100644 index 0000000..4125d2d --- /dev/null +++ b/src/job.c @@ -0,0 +1,48 @@ +#include +#include +#include +#include +#include +#include + +#include "job.h" +#include "stack.h" + +#define MAXJOBS 100 + +static struct job jobarray[MAXJOBS + 1]; +struct stack INITSTACK(jobs, jobarray, 0); + +void sigkill(pid_t jobid) { + if (killpg(jobid, SIGKILL) == -1) + warn("Unable to kill process group %d; manual termination may be required", + jobid); +} + +void *findjob(pid_t jobid) { + if (jobs.b == jobs.t) return NULL; + for (jobs.c = jobs.b; CURRENT->id != jobid; INC(jobs, c)) + if (jobs.c == jobs.t) return NULL; + return jobs.c; +} + +void *deletejob(void) { + memcpy(jobs.t, jobs.c, jobs.size); + memmove(jobs.c, PLUSONE(jobs, c), jobs.t - jobs.c); + return MINUSONE(jobs, t); +} + +void waitbg(int sig) { + int status; + pid_t id; + + (void)sig; + for (jobs.c = jobs.b; jobs.c != jobs.t; INC(jobs, c)) { + if (CURRENT->type != BACKGROUND) continue; + id = CURRENT->id; + while ((id = waitpid(-id, &status, WNOHANG | WUNTRACED)) > 0) + if (WIFSTOPPED(status)) CURRENT->type = SUSPENDED; + if (id == -1 && errno != ECHILD) + warn("Unable to wait on some child processes"); + } +} diff --git a/src/job.h b/src/job.h new file mode 100644 index 0000000..4034cfc --- /dev/null +++ b/src/job.h @@ -0,0 +1,18 @@ +enum jobtype { + BACKGROUND, + SUSPENDED, +}; + +struct job { + pid_t id; + struct termios config; + enum jobtype type; +}; + +#define CURRENT ((struct job *)jobs.c) +extern struct stack jobs; + +void sigkill(pid_t jobid); +void *findjob(pid_t jobid); +void *deletejob(void); +void waitbg(int sig); diff --git a/src/cmd.c b/src/lex.c similarity index 57% rename from src/cmd.c rename to src/lex.c index 74e536f..fe20f40 100644 --- a/src/cmd.c +++ b/src/lex.c @@ -1,42 +1,41 @@ #include #include "history.h" -#include "cmd.h" +#include "lex.h" -int delimiter(char c) { +static char *tokens[1 + BUFLEN + 1]; +static struct cmd cmds[1 + (BUFLEN + 1) / 2 + 1]; + +static int delimiter(char c) { return c == ' ' || c == '&' || c == '|' || c == ';' || c == '`' || c == '\0'; } -enum terminator strp2term(char **strp) { - enum terminator result; +static enum terminator strp2term(char **strp) { char *p; + enum terminator term; switch (*(p = (*strp)++)) { case '&': - result = **strp == '&' ? (++*strp, AND) : BG; + term = **strp == '&' ? (++*strp, AND) : BG; break; case '|': - result = **strp == '|' ? (++*strp, OR) : PIPE; + term = **strp == '|' ? (++*strp, OR) : PIPE; break; default: - result = SEMI; + term = SEMI; } *p = '\0'; - return result; + return term; } -struct cmd *getcmds(char *b) { - size_t i, j; +struct cmd *lex(char *b) { char **t; struct cmd *c; - enum terminator term; - static char *tokens[BUFLEN + 1]; - static struct cmd result[BUFLEN / 2 + 1]; - *tokens = NULL; + if (!b) return NULL; t = tokens + 1; - c = result; + c = cmds + 1; while (*b == ' ') ++b; c->args = t; while (*b) switch (*b) { @@ -51,13 +50,11 @@ struct cmd *getcmds(char *b) { case ';': case '&': case '|': - if (!*(t - 1)) { - strp2term(&b); - break; - } - *t++ = NULL; - c++->type = strp2term(&b); - c->args = t; + if (*(t - 1)) { + *t++ = NULL; + c++->type = strp2term(&b); + c->args = t; + } else strp2term(&b); break; case '`': *t++ = ++b; @@ -71,5 +68,5 @@ struct cmd *getcmds(char *b) { } *c = (struct cmd){0}; - return result; + return cmds; } diff --git a/src/cmd.h b/src/lex.h similarity index 80% rename from src/cmd.h rename to src/lex.h index 82850f3..d5b862d 100644 --- a/src/cmd.h +++ b/src/lex.h @@ -12,4 +12,4 @@ struct cmd { int pipe[2]; }; -struct cmd *getcmds(char *b); +struct cmd *lex(char *b); diff --git a/src/main.c b/src/main.c index 5906e06..2b00fe5 100644 --- a/src/main.c +++ b/src/main.c @@ -1,20 +1,22 @@ #include -#include // TODO: Use sigaction for portability? but this is also used with killpg, so don't remove it +#include #include #include +#include #include #include -#include +#include // DEBUG #include "builtins.h" -#include "cmd.h" +#include "stack.h" #include "history.h" #include "input.h" -#include "pg.h" +#include "lex.h" +#include "job.h" #include "term.h" #include "utils.h" -int closepipe(struct cmd *cmd) { +static int closepipe(struct cmd *cmd) { int result; if (!cmd->args) return 1; @@ -32,42 +34,37 @@ int closepipe(struct cmd *cmd) { } int main(void) { - char buffer[BUFLEN]; struct cmd *cmd, *prev; - int status; - pid_t id; + int ispipe, ispipestart, ispipeend, status; + pid_t cpid, jobid; + struct job job; initterm(); readhist(); - while (getinput(buffer)) { - prev = cmd = getcmds(buffer); - while (prev->args) ++prev; - while (cmd->args) { - if (cmd->type != PIPE && prev->type != PIPE) { - if (isbuiltin(cmd->args, &status)) break; - if ((id = fork()) == 0 && execvp(*cmd->args, cmd->args) == -1) - err(EXIT_FAILURE, "Couldn't find `%s' command", *cmd->args); - if (setpgid(id, id) == -1) { - warn("Unable to set pgid of `%s' command to %d", *cmd->args, id); - if (kill(id, SIGKILL) == -1) - warn("Unable to kill process %d; manual termination may be required", - id); - break; - } - } else { - if (cmd->type == PIPE && pipe(cmd->pipe) == -1) { + while ((cmd = lex(input()))) { + while (prev = cmd++, cmd->args) { + ispipe = cmd->type == PIPE || prev->type == PIPE; + ispipestart = ispipe && prev->type != PIPE; + ispipeend = ispipe && cmd->type != PIPE; + + if (ispipe) { + if (!ispipeend && pipe(cmd->pipe) == -1) { warn("Unable to create pipe"); - if (prev->type == PIPE) closepipe(prev); + if (!ispipestart) closepipe(prev); break; } - if ((id = fork()) == 0) { - if (dup2(prev->pipe[0], 0) == -1) - err(EXIT_FAILURE, "Unable to duplicate read end of `%s' pipe", - *prev->args); - if (!closepipe(prev)) exit(EXIT_FAILURE); - - if (cmd->type == PIPE) { + if ((jobid = cpid = fork()) == -1) { + warn("Unable to create child process"); + break; + } else if (cpid == 0) { + if (!ispipestart) { + if (dup2(prev->pipe[0], 0) == -1) + err(EXIT_FAILURE, "Unable to duplicate read end of `%s' pipe", + *prev->args); + if (!closepipe(prev)) exit(EXIT_FAILURE); + } + if (!ispipeend) { if (dup2(cmd->pipe[1], 1) == -1) err(EXIT_FAILURE, "Unable to duplicate write end of `%s' pipe", *cmd->args); @@ -78,30 +75,42 @@ int main(void) { if (execvp(*cmd->args, cmd->args) == -1) err(EXIT_FAILURE, "Couldn't find `%s' command", *cmd->args); } - if (prev->type == PIPE) closepipe(prev); - else push(&bgpgs, (struct pg){.id = id, .config = canonical,}); - if (setpgid(id, peek(&bgpgs).id) == -1) { - if (errno != ESRCH) { - warn("Unable to set pgid of `%s' command to %d", *cmd->args, peek(&bgpgs).id); - if (kill(id, SIGKILL) == -1) - warn("Unable to kill process %d; manual termination may be required", - id); - } + if (!ispipestart) { + closepipe(prev); + jobid = ((struct job *)(ispipeend ? pull : peek)(&jobs))->id; + } + } else { + if (isbuiltin(cmd->args, &status)) break; + if ((jobid = cpid = fork()) == -1) { + warn("Unable to create child process"); break; + } else if (cpid == 0 && execvp(*cmd->args, cmd->args) == -1) + err(EXIT_FAILURE, "Couldn't find `%s' command", *cmd->args); + } + if (setpgid(cpid, jobid) == -1) { + if (errno != ESRCH) { + warn("Unable to set pgid of `%s' command to %d", *cmd->args, jobid); + if (kill(cpid, SIGKILL) == -1) + warn("Unable to kill process %d; manual termination may be required", + cpid); } - if (cmd->type != PIPE) id = peek(&bgpgs).id; + break; } - if (cmd->type == BG) { - push(&bgpgs, (struct pg){.id = id, .config = canonical,}); - recent = &bgpgs; + + job = (struct job){.id = jobid, .config = canonical, .type = BACKGROUND}; + if (ispipestart || cmd->type == BG) { + if (!push(&jobs, &job)) { + warn("Unable to add command to background; " + "too many processes in the background"); + if (ispipestart) closepipe(cmd); + break; + } } else if (cmd->type != PIPE) { - if (prev->type == PIPE) pull(&bgpgs); - if (!setfg(id)) break; - status = waitfg(id); + if (!setfg(job)) break; + status = waitfg(job); + if (cmd->type == AND && status != 0) break; + if (cmd->type == OR && status == 0) break; } - if (cmd->type == AND && status != 0) break; - if (cmd->type == OR && status == 0) break; - prev = cmd++; } waitbg(0); } diff --git a/src/pg.c b/src/pg.c deleted file mode 100644 index ec3cedf..0000000 --- a/src/pg.c +++ /dev/null @@ -1,74 +0,0 @@ -#include -#include -#include -#include -#include -#include - -#include "pg.h" - -#define INITPGS(pgs) {MAXPG, (pgs).data, (pgs).data, (pgs).data} -struct pgs spgs = INITPGS(spgs), bgpgs = INITPGS(bgpgs), *recent = &spgs; - -void sigkill(pid_t pgid) { - if (killpg(pgid, SIGKILL) == -1) - warn("Unable to kill process group %d; manual termination may be required", - pgid); -} - -void push(struct pgs *pgs, struct pg pg) { - if (PLUSONE(*pgs, t) == pgs->b) { - sigkill(pgs->b->id); - INC(*pgs, b); - } - *pgs->t = pg; - INC(*pgs, t); -} - -struct pg peek(struct pgs *pgs) { - if (pgs->t == pgs->b) return (struct pg){0}; - return *MINUSONE(*pgs, t); -} - -struct pg pull(struct pgs *pgs) { - struct pg result; - - if ((result = peek(pgs)).id) DEC(*pgs, t); - return result; -} - -struct pg *find(struct pgs *pgs, pid_t pgid) { - if (pgid == 0 || pgs->t == pgs->b) return NULL; - for (pgs->c = MINUSONE(*pgs, t); pgs->c->id != pgid; DEC(*pgs, c)) - if (pgs->c == pgs->b) return NULL; - return pgs->c; -} - -void cut(struct pgs *pgs) { - if (pgs->c >= pgs->b) { - memmove(pgs->b + 1, pgs->b, sizeof(struct pg) * (pgs->c - pgs->b)); - ++pgs->b; - INC(*pgs, c); - } else { - memmove(pgs->c, pgs->c + 1, sizeof(struct pg) * (pgs->t - pgs->c - 1)); - --pgs->t; - } -} - -void waitbg(int sig) { - int status; - pid_t pid, pgid; - - (void)sig; - for (bgpgs.c = bgpgs.b; bgpgs.c != bgpgs.t; INC(bgpgs, c)) { - while ((pid = waitpid(-bgpgs.c->id, &status, WNOHANG | WUNTRACED)) > 0) - if (WIFSTOPPED(status)) { - push(&spgs, *bgpgs.c); - cut(&bgpgs); - recent = &spgs; - pid = 0; - } - if (pid == -1 && errno != ECHILD) - warn("Unable to wait on some child processes"); - } -} diff --git a/src/pg.h b/src/pg.h deleted file mode 100644 index 522f958..0000000 --- a/src/pg.h +++ /dev/null @@ -1,25 +0,0 @@ -struct pg { - pid_t id; - struct termios config; -}; - -#define PLUSONE(s, m) ((s).data + ((s).m - (s).data + 1) % (s).len) -#define INC(s, m) ((s).m = PLUSONE(s, m)) -#define MINUSONE(s, m) ((s).data + ((s).m - (s).data + (s).len - 1) % (s).len) -#define DEC(s, m) ((s).m = MINUSONE(s, m)) - -#define MAXPG 128 -struct pgs { - size_t len; - struct pg *b, *c, *t, data[MAXPG]; -}; - -extern struct pgs spgs, bgpgs, *recent; - -void sigkill(pid_t pgid); -void push(struct pgs *pgs, struct pg pg); -struct pg peek(struct pgs *pgs); -struct pg pull(struct pgs *pgs); -struct pg *find(struct pgs *pgs, pid_t pgid); -void cut(struct pgs *pgs); -void waitbg(int sig); diff --git a/src/stack.c b/src/stack.c new file mode 100644 index 0000000..4d21bd7 --- /dev/null +++ b/src/stack.c @@ -0,0 +1,26 @@ +#include + +#include "stack.h" + +int push(struct stack *s, void *d) { + int result; + + result = 0; + if (PLUSONE(*s, t) == s->b) { + if (!s->overwrite) return result; + INC(*s, b); + } else result = 1; + memmove(s->t, d, s->size); + INC(*s, t); + return result; +} + +void *peek(struct stack *s) { + if (s->t == s->b) return NULL; + return MINUSONE(*s, t); +} + +void *pull(struct stack *s) { + if (s->t == s->b) return NULL; + return DEC(*s, t); +} diff --git a/src/stack.h b/src/stack.h new file mode 100644 index 0000000..d33cbe5 --- /dev/null +++ b/src/stack.h @@ -0,0 +1,17 @@ +#define PLUSONE(s, m) ((s).data + ((s).m - (s).data + (s).size) % (s).cap) +#define MINUSONE(s, m) \ + ((s).data + ((s).m - (s).data - (s).size + (s).size * (s).cap) % (s).cap) +#define INC(s, m) ((s).m = PLUSONE(s, m)) +#define DEC(s, m) ((s).m = MINUSONE(s, m)) + +#define INITSTACK(s, d, o) \ + s = {sizeof*d, sizeof d, (char *)d, (char *)d, (char *)d, (char *)d, o} +struct stack { + size_t size, cap; // In bytes + char *b, *c, *t, *data; + int overwrite; +}; + +int push(struct stack *s, void *d); +void *peek(struct stack *s); +void *pull(struct stack *s); diff --git a/src/term.c b/src/term.c index a2bbe1c..52ec184 100644 --- a/src/term.c +++ b/src/term.c @@ -6,10 +6,21 @@ #include #include -#include "pg.h" +#include "job.h" +#include "history.h" +#include "stack.h" -struct termios canonical, raw; -pid_t self; +struct termios raw, canonical; +static pid_t self; + +static int tcsetraw(void) { + if (tcsetattr(STDIN_FILENO, TCSANOW, &raw) == -1) { + warn("Unable to set termios state to raw mode"); + return 0; + } + + return 1; +} void initterm(void) { if (tcgetattr(STDIN_FILENO, &canonical) == -1) @@ -17,8 +28,7 @@ void initterm(void) { raw = canonical; raw.c_lflag &= ~ICANON & ~ECHO & ~ISIG; raw.c_lflag |= ECHONL; - if (tcsetattr(STDIN_FILENO, TCSANOW, &raw) == -1) - err(EXIT_FAILURE, "Unable to initialize termios structure"); + if (!tcsetraw()) exit(EXIT_FAILURE); if ((self = getpgid(0)) == -1) err(EXIT_FAILURE, "Unable to get pgid of self"); } @@ -28,53 +38,81 @@ void deinitterm(void) { warn("Unable to return to initial terminal settings"); } -int setfg(pid_t pgid) { - if (tcsetattr(STDIN_FILENO, TCSANOW, &canonical) == -1) +static void tcsetself(void) { + if (signal(SIGTTOU, SIG_IGN) == SIG_ERR) { + warn("Unable to ignore SIGTTOU signal"); + goto quit; + } + if (tcsetpgrp(STDIN_FILENO, self) == -1) { + warn("Unable to set self as foreground process; exiting"); + goto quit; + } + if (signal(SIGTTOU, SIG_DFL) == SIG_ERR) warn("Ignoring signal SIGTTOU"); + + return; + +quit: + writehist(); + deinitterm(); + exit(EXIT_FAILURE); +} + +int setfg(struct job job) { + if (tcsetattr(STDIN_FILENO, TCSANOW, &job.config) == -1) warn("Unable to set termios structure"); - if (tcsetpgrp(STDIN_FILENO, pgid) == -1) { - warn("Unable to bring process group %d to foreground", pgid); - if (tcsetattr(STDIN_FILENO, TCSANOW, &raw) == -1) - warn("Unable to set termios state back to raw"); - sigkill(pgid); - if (killpg(pgid, SIGKILL) == -1) - warn("Unable to kill process group %d; manual termination required", pgid); - return 0; + if (tcsetpgrp(STDIN_FILENO, job.id) == -1) { + warn("Unable to bring process group %d to foreground", job.id); + goto setraw; } + if (killpg(job.id, SIGCONT) == -1) { + warn("Unable to wake up suspended process group %d", job.id); + goto setself; + } + return 1; + +setself: + tcsetself(); + +setraw: + tcsetraw(); + + sigkill(job.id); + + return 0; } -int waitfg(pid_t pgid) { +int waitfg(struct job job) { int status, result; - struct pg pg; - if (waitpid(pgid, &status, WUNTRACED) != -1 && !WIFSTOPPED(status)) { - while (waitpid(-pgid, NULL, 0) != -1); - if (errno != ECHILD && killpg(pgid, SIGKILL) == -1) - err(EXIT_FAILURE, "Unable to kill child process group %d", pgid); +wait: + if (waitpid(job.id, &status, WUNTRACED) != -1 && !WIFSTOPPED(status)) { + while (waitpid(-job.id, NULL, 0) != -1); + if (errno != ECHILD && killpg(job.id, SIGKILL) == -1) + err(EXIT_FAILURE, "Unable to kill child process group %d", job.id); } - if (signal(SIGTTOU, SIG_IGN) == SIG_ERR) - err(EXIT_FAILURE, "Unable to ignore SIGTTOU signal"); - if (tcsetpgrp(STDIN_FILENO, self) == -1) - err(EXIT_FAILURE, "Unable to set self back to foreground process group"); - if (signal(SIGTTOU, SIG_DFL) == SIG_ERR) warn("Ignoring signal SIGTTOU"); + + tcsetself(); if (WIFSIGNALED(status)) { putchar('\n'); result = WTERMSIG(status); } else if (WIFSTOPPED(status)) { - pg = (struct pg){ - .id = pgid, - .config = canonical, - }; - if (tcgetattr(STDIN_FILENO, &pg.config) == -1) - warn("Unable to save termios state for stopped process group %d", pgid); - push(&spgs, pg); - recent = &spgs; + if (tcgetattr(STDIN_FILENO, &job.config) == -1) + warn("Unable to save termios state for stopped process group %d", job.id); + job.type = SUSPENDED; + if (!push(&jobs, &job)) { + warnx("Unable to add process to job list; too many jobs\n" + "Press any key to continue"); + getchar(); + if (setfg(job)) goto wait; + warn("Unable to continue foreground process; terminating"); + sigkill(job.id); + } result = WSTOPSIG(status); } else result = WEXITSTATUS(status); - if (tcsetattr(STDIN_FILENO, TCSANOW, &raw) == -1) - warn("Unable to set termios state back to raw"); + tcsetraw(); return result; } diff --git a/src/term.h b/src/term.h index 0603b8e..3bc6806 100644 --- a/src/term.h +++ b/src/term.h @@ -1,7 +1,6 @@ -extern struct termios canonical, raw; -extern pid_t self; +extern struct termios raw, canonical; void initterm(void); void deinitterm(void); -int setfg(pid_t pgid); -int waitfg(pid_t pgid); +int setfg(struct job job); +int waitfg(struct job job); -- 2.51.0