From 2a8a3fc37e4922772c6e18a02b0d814e1ebfb204 Mon Sep 17 00:00:00 2001 From: Trent Huber Date: Wed, 15 Oct 2025 02:14:18 -0400 Subject: [PATCH] Proper signal handling --- src/build.c | 4 +- src/builtins/bg.c | 74 ++++++++++++++++-------------- src/builtins/bg.h | 6 ++- src/builtins/fg.c | 113 +++++++++++++++++++++++++++++++++------------- src/builtins/fg.h | 6 ++- src/input.c | 30 ++++++++---- src/options.c | 8 +++- src/run.c | 7 ++- 8 files changed, 162 insertions(+), 86 deletions(-) diff --git a/src/build.c b/src/build.c index 91ca5ef..84ad1bd 100644 --- a/src/build.c +++ b/src/build.c @@ -1,7 +1,7 @@ #include "../external/cbs/cbs.c" -#define SRC1 "context", "history", "input" -#define SRC2 "main", "options", "parse", "run", "utils" +#define SRC1 "context", "history" +#define SRC2 "input", "main", "options", "parse", "run", "utils" int main(void) { char **src; diff --git a/src/builtins/bg.c b/src/builtins/bg.c index 2f78950..eec6fbd 100644 --- a/src/builtins/bg.c +++ b/src/builtins/bg.c @@ -20,10 +20,49 @@ struct bglink { static struct { struct bglink entries[MAXBG], *active, *free; } bgjobs; +struct sigaction bgaction; + +void removebg(pid_t id) { + struct bglink *p, *prev; + + for (prev = NULL, p = bgjobs.active; p; prev = p, p = p->next) + if (p->job.id == id) break; + if (!p) return; + + if (prev) prev->next = p->next; else bgjobs.active = p->next; + p->next = bgjobs.free; + bgjobs.free = p; +} + +void sigchldbghandler(int sig) { + int e, s; + struct bglink *p; + pid_t id; + + (void)sig; + + e = errno; + p = bgjobs.active; + while (p) { + while ((id = waitpid(-p->job.id, &s, WNOHANG | WUNTRACED)) > 0) + if (WIFSTOPPED(s)) { + p->job.suspended = 1; + break; + } + if (id == -1) { + id = p->job.id; + p = p->next; + removebg(id); + } else p = p->next; + } + errno = e; +} void initbg(void) { size_t i; + bgaction = (struct sigaction){.sa_handler = sigchldbghandler}; + for (i = 0; i < MAXBG - 1; ++i) bgjobs.entries[i].next = &bgjobs.entries[i + 1]; bgjobs.free = bgjobs.entries; @@ -67,41 +106,6 @@ int searchbg(pid_t id, struct bgjob *job) { return 0; } -void removebg(pid_t id) { - struct bglink *p, *prev; - - for (prev = NULL, p = bgjobs.active; p; prev = p, p = p->next) - if (p->job.id == id) break; - if (!p) return; - - if (prev) prev->next = p->next; else bgjobs.active = p->next; - p->next = bgjobs.free; - bgjobs.free = p; -} - -void waitbg(int sig) { - int e, s; - struct bglink *p; - pid_t id; - - (void)sig; - e = errno; - p = bgjobs.active; - while (p) { - while ((id = waitpid(-p->job.id, &s, WNOHANG | WUNTRACED)) > 0) - if (WIFSTOPPED(s)) { - p->job.suspended = 1; - break; - } - if (id == -1) { - id = p->job.id; - p = p->next; - removebg(id); - } else p = p->next; - } - errno = e; -} - void deinitbg(void) { struct bglink *p; diff --git a/src/builtins/bg.h b/src/builtins/bg.h index b933134..3d34bbe 100644 --- a/src/builtins/bg.h +++ b/src/builtins/bg.h @@ -4,12 +4,14 @@ struct bgjob { int suspended; }; +extern struct sigaction bgaction; + +void removebg(pid_t id); +void sigchldbghandler(int sig); void initbg(void); int bgfull(void); int pushbg(struct bgjob job); int pushbgid(pid_t id); int peekbg(struct bgjob *job); int searchbg(pid_t id, struct bgjob *job); -void removebg(pid_t id); -void waitbg(int sig); void deinitbg(void); diff --git a/src/builtins/fg.c b/src/builtins/fg.c index 6da9a07..c40db18 100644 --- a/src/builtins/fg.c +++ b/src/builtins/fg.c @@ -2,6 +2,7 @@ #include #include #include +#include #include #include #include @@ -9,26 +10,37 @@ #include "bg.h" #include "builtin.h" +#include "context.h" +#include "input.h" +#include "options.h" #include "utils.h" +int sigquit, sigint; static struct { pid_t id; int status, done; } fgjob; +static sigset_t shellsigmask; +static struct sigaction fgaction; +struct sigaction defaultaction; +static pid_t pgid; +sigset_t childsigmask; struct termios canonical; static struct termios raw; -struct sigaction actbg, actdefault; -static struct sigaction actall, actignore; -static int setconfig(struct termios *mode) { - if (tcsetattr(STDIN_FILENO, TCSANOW, mode) == -1) { - note("Unable to set termios config"); - return 0; - } - return 1; +static void sigquithandler(int sig) { + (void)sig; + + sigquit = 1; +} + +static void siginthandler(int sig) { + (void)sig; + + sigint = 1; } -static void waitall(int sig) { +static void sigchldfghandler(int sig) { int e, s; pid_t id; @@ -42,25 +54,59 @@ static void waitall(int sig) { } } if (id == -1) fgjob.done = 1; - waitbg(sig); + sigchldbghandler(sig); errno = e; } +void setsig(int sig, struct sigaction *act) { + if (sigaction(sig, act, NULL) == -1) + fatal("Unable to install %s handler", strsignal(sig)); +} + +static int setconfig(struct termios *mode) { + if (tcsetattr(STDIN_FILENO, TCSANOW, mode) == -1) { + note("Unable to configure TTY"); + return 0; + } + return 1; +} + void initfg(void) { + struct sigaction action; + pid_t pid; + + sigemptyset(&shellsigmask); + sigaddset(&shellsigmask, SIGTSTP); + sigaddset(&shellsigmask, SIGTTIN); + sigaddset(&shellsigmask, SIGTTOU); + + action = (struct sigaction){.sa_handler = sigquithandler}; + setsig(SIGHUP, &action); + setsig(SIGQUIT, &action); + setsig(SIGTERM, &action); + + action = (struct sigaction){.sa_handler = siginthandler}; + setsig(SIGINT, &action); + + fgaction = (struct sigaction){.sa_handler = sigchldfghandler}; + + defaultaction = (struct sigaction){.sa_handler = SIG_DFL}; + setsig(SIGTSTP, &defaultaction); + setsig(SIGTTOU, &defaultaction); + setsig(SIGTTIN, &defaultaction); + + pid = getpid(); + pgid = getpgrp(); + if (login && pid != pgid && setpgid(0, pgid = pid) == -1) exit(errno); + + if (sigprocmask(SIG_BLOCK, &shellsigmask, &childsigmask) == -1 + || tcsetpgrp(STDIN_FILENO, pgid) == -1) + exit(errno); + if (tcgetattr(STDIN_FILENO, &canonical) == -1) exit(errno); raw = canonical; - raw.c_lflag &= ~(ICANON | ECHO | ISIG); + raw.c_lflag &= ~(ICANON | ECHO); if (!setconfig(&raw)) exit(EXIT_FAILURE); - - actbg.sa_handler = waitbg; - actall.sa_handler = waitall; - actdefault.sa_handler = SIG_DFL; - actignore.sa_handler = SIG_IGN; -} - -void setsigchld(struct sigaction *act) { - if (sigaction(SIGCHLD, act, NULL) == -1) - fatal("Unable to install SIGCHLD handler"); } int runfg(pid_t id) { @@ -79,18 +125,23 @@ int runfg(pid_t id) { } removebg(id); - /* SIGCHLD handler is really the function that reaps the foreground process, - * the waitpid() below is just to block the current thread of execution until - * the foreground process has been reaped. */ + /* The handler in `fgaction' is really what reaps the foreground process; the + * `sigsuspend()' just blocks the current thread of execution until the + * foreground process has been reaped. */ fgjob.id = id; - setsigchld(&actall); - while (!fgjob.done) sigsuspend(&actall.sa_mask); - setsigchld(&actdefault); + setsig(SIGCHLD, &fgaction); + while (!fgjob.done) { + sigsuspend(&shellsigmask); + if (sigquit) { + deinit(); + exit(EXIT_SUCCESS); + } + if (sigint) sigint = 0; + } + setsig(SIGCHLD, &defaultaction); fgjob.done = errno = 0; - if (sigaction(SIGTTOU, &actignore, NULL) == -1 - || tcsetpgrp(STDIN_FILENO, getpgrp()) == -1 - || sigaction(SIGTTOU, &actdefault, NULL) == -1) { + if (tcsetpgrp(STDIN_FILENO, pgid) == -1) { deinit(); exit(errno); } @@ -109,7 +160,7 @@ int runfg(pid_t id) { return runfg(id); } } else status = WTERMSIG(fgjob.status); - + return 1; } diff --git a/src/builtins/fg.h b/src/builtins/fg.h index 2d65274..0866462 100644 --- a/src/builtins/fg.h +++ b/src/builtins/fg.h @@ -1,7 +1,9 @@ +extern int sigquit, sigint; +extern struct sigaction defaultaction; +extern sigset_t childsigmask; extern struct termios canonical; -extern struct sigaction actbg, actdefault; +void setsig(int sig, struct sigaction *act); void initfg(void); -void setsigchld(struct sigaction *act); int runfg(pid_t id); void deinitfg(void); diff --git a/src/input.c b/src/input.c index 53b9b9c..b9049bc 100644 --- a/src/input.c +++ b/src/input.c @@ -7,16 +7,16 @@ #include #include "context.h" +#include "fg.h" #include "history.h" #include "utils.h" enum { - CTRLC = '\003', - CTRLD, + CTRLD = '\004', CLEAR = '\014', ESCAPE = '\033', - /* See ESCAPE case in userinput() */ + /* See `ESCAPE' case in `userinput()' */ ALT = '2' + 1, UP = 'A', @@ -41,7 +41,7 @@ int stringinput(struct context *c) { end = c->string; while (*end && *end != '\n') ++end; l = end - c->string; - while (*end == '\n') ++end; /* scriptinput() repeatedly uses stringinput() */ + while (*end == '\n') ++end; /* `scriptinput()' uses `stringinput()' */ if (l > MAXCHARS) { note("Line too long, exceeds %d character limit", MAXCHARS); return 0; @@ -81,6 +81,10 @@ int scriptinput(struct context *c) { c->string = c->map; c->input = stringinput; + /* We want errors from this point forward to be reported by the script, not the + * shell. */ + arglist[0] = c->script; + return c->input(c); } @@ -114,12 +118,18 @@ int userinput(struct context *c) { for (i = end - cursor; i > 0; --i) putchar('\b'); } break; - case CTRLC: - putchar('\n'); - return quit(c); - case CTRLD: - putchar('\n'); - return 0; + case EOF: + if (sigquit) { + case CTRLD: + putchar('\n'); + return 0; + } + if (sigint) { + sigint = 0; + putchar('\n'); + prompt(); + } + break; case CLEAR: fputs("\033[H\033[J", stdout); prompt(); diff --git a/src/options.c b/src/options.c index 3276d7d..44b8035 100644 --- a/src/options.c +++ b/src/options.c @@ -10,14 +10,13 @@ int login, interactive; void options(struct context *context) { - int opt; char *p, *message = "[file | -c string] [arg ...] [-hl]\n" " [arg ...] Run script\n" " -c [arg ...] Run string\n" " -h Show this help message\n" " -l Run as a login shell"; + int opt; - opt = 0; if (arglist[0][0] == '-') { ++arglist[0]; login = 1; @@ -26,6 +25,7 @@ void options(struct context *context) { interactive = 1; context->input = userinput; + opt = 0; while (opt != 'c' && (opt = getopt(argcount, arglist, ":c:hl")) != -1) switch (opt) { case 'c': @@ -52,6 +52,10 @@ void options(struct context *context) { interactive = 0; context->script = arglist[optind]; context->input = scriptinput; + + /* Until we're able to load the script, errors need to be reported by the + * shell, not the script. */ + arglist[optind] = arglist[0]; } if (!interactive) { argcount -= optind; diff --git a/src/run.c b/src/run.c index a0452e2..4086592 100644 --- a/src/run.c +++ b/src/run.c @@ -60,6 +60,9 @@ static void redirectfiles(struct redirect *r) { static void exec(char *path, struct context *c) { redirectfiles(c->redirects); + if (sigprocmask(SIG_SETMASK, &childsigmask, NULL) == -1) + note("Unable to unblock TTY signals"); + if (isbuiltin(c->tokens)) exit(status); execve(path, c->tokens, environ); fatal("Couldn't find `%s' command", c->current.name); @@ -71,9 +74,9 @@ int run(struct context *c) { pid_t cpid, jobid; static pid_t pipeid; - setsigchld(&actbg); + setsig(SIGCHLD, &bgaction); if (!parse(c)) return 0; - setsigchld(&actdefault); + setsig(SIGCHLD, &defaultaction); islist = c->prev.term > BG || c->current.term > BG; if (c->t) { -- 2.51.0