From dc8926f97bcd9e885ac56b329de5e8631692e947 Mon Sep 17 00:00:00 2001 From: ff <_ff@tuta.io> Date: Sun, 3 Dec 2017 12:17:11 +0100 Subject: [PATCH] Use process groups in interactive mode When minish spawns a new process, with fork(), this process puts itself in a new group just for itself and declares this group as foreground. When the child process terminates, minish declare its own process group as foreground. --- Devember/log03.txt | 40 ++++++++++++++++++++++++++++++++++ src/minish.c | 63 ++++++++++++++++++++++++++++++++++++++---------------- 2 files changed, 84 insertions(+), 19 deletions(-) create mode 100644 Devember/log03.txt diff --git a/Devember/log03.txt b/Devember/log03.txt new file mode 100644 index 0000000..6474a20 --- /dev/null +++ b/Devember/log03.txt @@ -0,0 +1,40 @@ +When minish is called without arguments, it reads from stdin. +This is interactive mode. +When it is called with an argument, it reads from the file thus named. +This is script mode. + +In interactive mode, +minish should not terminate when Ctrl+C is pressed +and a command is executing. +There are two ways of doing this: +ignoring SIGINT, +or avoid being in the foreground process group so to not receive it. +Yesterday minish used the first way, but today I use the second. + +When minish spawns a new process, with fork(), +this process puts itself in a new group just for itself +and declares this group as foreground. +When the child process terminates, +minish declare its own process group as foreground. + +tcsetpgrp(fd, gpid) puts the gpid process group in the foreground +of the fd terminal. +If gpid is in the background, it receives the signal SIGTTOU; +it means “stay put: you can do any output right now” +and its default action is to suspend, but it can be ignored. +So, the child must: +1. create its own process group; +2. disable the handling of SIGTTOU; +3. puts its process group in the foreground; +4. re-enable the handling of SIGTTOU. + +Using process groups paves the way to job control. +Without process groups, the shell could ignore SIGINT anyway, +but other background processes cannot: +if they did, we would be unable to stop them. + +Enough for today. + +Suggested readings: +https://vidarholen.net/contents/blog/?p=34 +https://gnu.org/software/bash/manual/html_node/Job-Control-Basics.html diff --git a/src/minish.c b/src/minish.c index 13a93c3..d1b236c 100644 --- a/src/minish.c +++ b/src/minish.c @@ -1,8 +1,8 @@ #define _POSIX_SOURCE 200809L -#include // ENOMEM +#include // EACCES,ENOMEM,errno #include // LC_ALL,setlocale -#include // SIGINT,SIG_DFL,SIG_IGN,sig_atomic_t,sigaction +#include // SIGINT,sig_atomic_t,sigaction #include /* FILE, * ferror,fopen,fputs,perror,setbuf,stderr,stdin */ @@ -11,7 +11,7 @@ #include // noreturn #include // pid_t,ssize_t #include // waitpid,WUNTRACED -#include // execv,fork,pid_t +#include // execv,fork,pid_t,setpgid,tcsetpgrp typedef struct { size_t size; @@ -29,12 +29,14 @@ static void set_sigint_handler(void (* const handler)(int)) { struct sigaction const action = { . sa_handler = handler, }; - sigaction(SIGINT, &action, NULL); + if (sigaction(SIGINT, &action, NULL) == -1) { + pexit("sigaction()"); + } } static noreturn void oom(void) { errno = ENOMEM; - pexit(NULL); + pexit("realloc()"); } static void add_word(Words* const ws, char* const w) { @@ -60,25 +62,28 @@ static void sigint_handler(int const signal) { } static FILE* choose_input(char const* const filename) { - if (filename) { - FILE* const input = fopen(filename, "r"); - if (!input) { - pexit(filename); - } - set_sigint_handler(sigint_handler); - return input; + if (!filename) { + return stdin; } - set_sigint_handler(SIG_IGN); - return stdin; + FILE* const input = fopen(filename, "r"); + if (!input) { + pexit(filename); + } + set_sigint_handler(sigint_handler); + return input; } static void wait_child(pid_t const pid) { int s; - waitpid(pid, &s, WUNTRACED); + if (waitpid(pid, &s, WUNTRACED) == -1) { + pexit("waitpid()"); + } if (received_sigint) { if (WIFSIGNALED(s) && WTERMSIG(s) == SIGINT) { set_sigint_handler(SIG_DFL); raise(SIGINT); // NOTE cause termination + // NOTE raise can fail, but does not set errno + exit(EXIT_FAILURE); } received_sigint = 0; } @@ -86,7 +91,8 @@ static void wait_child(pid_t const pid) { int main(int argc, char** argv) { setlocale(LC_ALL, ""); - FILE* const in = choose_input(argc ? argv[1] : NULL); + char const* const filename = argc ? argv[1] : NULL; + FILE* const in = choose_input(filename); setbuf(in, NULL); // NOTE forking would duplicate a buffer char* line = NULL; size_t size = 0u; @@ -108,17 +114,36 @@ int main(int argc, char** argv) { add_word(&words, NULL); pid_t const pid = fork(); switch (pid) { - case -1: pexit(NULL); + case -1: pexit("fork()"); case 0: - set_sigint_handler(SIG_DFL); + if (!filename) { + if (argc <= 1 && setpgid(0, 0) == -1) { + pexit("setpgid()"); + } + signal(SIGTTOU, SIG_IGN); + // FIXME 0 could have been redirected + if (tcsetpgrp(0, getpid()) == -1) { + pexit("tcsetpgrp()"); + } + signal(SIGTTOU, SIG_DFL); + } execv(line, words.data); pexit(line); default: + sleep(1); wait_child(pid); + if (!filename) { + signal(SIGTTOU, SIG_IGN); + // FIXME 0 could have been redirected + if (tcsetpgrp(0, getpid()) == -1) { + pexit("tcsetpgrp()"); + } + signal(SIGTTOU, SIG_DFL); + } words.len = 0u; } } if (ferror(in)) { - pexit(argc > 1 ? argv[1] : NULL); + pexit(filename); } } -- 2.11.4.GIT