// extracted from https://raw.githubusercontent.com/Yelp/dumb-init/master/dumb-init.c #include #include #include #include #include #include #include #include #include #include #include #define PRINTERR(...) do { \ fprintf(stderr, "[dumb-init] " __VA_ARGS__); \ } while (0) #define DEBUG(...) do { \ if (debug) { \ PRINTERR(__VA_ARGS__); \ } \ } while (0) // Signals we care about are numbered from 1 to 31, inclusive. // (32 and above are real-time signals.) // TODO: this is likely not portable outside of Linux, or on strange architectures #define MAXSIG 31 // Indices are one-indexed (signal 1 is at index 1). Index zero is unused. int signal_rewrite[MAXSIG + 1] = {[0 ... MAXSIG] = -1}; pid_t child_pid = -1; char debug = 0; char use_setsid = 1; int translate_signal(int signum) { if (signum <= 0 || signum > MAXSIG) { return signum; } else { int translated = signal_rewrite[signum]; if (translated == -1) { return signum; } else { DEBUG("Translating signal %d to %d.\n", signum, translated); return translated; } } } void forward_signal(int signum) { signum = translate_signal(signum); if (signum != 0) { kill(use_setsid ? -child_pid : child_pid, signum); DEBUG("Forwarded signal %d to children.\n", signum); } else { DEBUG("Not forwarding signal %d to children (ignored).\n", signum); } } /* * The dumb-init signal handler. * * The main job of this signal handler is to forward signals along to our child * process(es). In setsid mode, this means signaling the entire process group * rooted at our child. In non-setsid mode, this is just signaling the primary * child. * * In most cases, simply proxying the received signal is sufficient. If we * receive a job control signal, however, we should not only forward it, but * also sleep dumb-init itself. * * This allows users to run foreground processes using dumb-init and to * control them using normal shell job control features (e.g. Ctrl-Z to * generate a SIGTSTP and suspend the process). * * The libc manual is useful: * https://www.gnu.org/software/libc/manual/html_node/Job-Control-Signals.html * */ void handle_signal(int signum) { DEBUG("Received signal %d.\n", signum); if (signum == SIGCHLD) { int status, exit_status; pid_t killed_pid; while ((killed_pid = waitpid(-1, &status, WNOHANG)) > 0) { if (WIFEXITED(status)) { exit_status = WEXITSTATUS(status); DEBUG("A child with PID %d exited with exit status %d.\n", killed_pid, exit_status); } else { assert(WIFSIGNALED(status)); exit_status = 128 + WTERMSIG(status); DEBUG("A child with PID %d was terminated by signal %d.\n", killed_pid, exit_status - 128); } if (killed_pid == child_pid) { forward_signal(SIGTERM); // send SIGTERM to any remaining children DEBUG("Child exited with status %d. Goodbye.\n", exit_status); exit(exit_status); } } } else { forward_signal(signum); if (signum == SIGTSTP || signum == SIGTTOU || signum == SIGTTIN) { DEBUG("Suspending self due to TTY signal.\n"); kill(getpid(), SIGSTOP); } } } void set_rewrite_to_sigstop_if_not_defined(int signum) { if (signal_rewrite[signum] == -1) signal_rewrite[signum] = SIGSTOP; } // A dummy signal handler used for signals we care about. // On the FreeBSD kernel, ignored signals cannot be waited on by `sigwait` (but // they can be on Linux). We must provide a dummy handler. // https://lists.freebsd.org/pipermail/freebsd-ports/2009-October/057340.html void dummy(int signum) {} // ------------------------------------------------------------------------ // Go entry point // void handleSignals() { sigset_t all_signals; sigfillset(&all_signals); sigprocmask(SIG_BLOCK, &all_signals, NULL); int i = 0; for (i = 1; i <= MAXSIG; i++) signal(i, dummy); for (;;) { int signum; sigwait(&all_signals, &signum); handle_signal(signum); } }