inits/cmd/dkl-system-init/dumb-init.c
2018-07-06 19:07:37 +11:00

140 lines
4.3 KiB
C

// extracted from https://raw.githubusercontent.com/Yelp/dumb-init/master/dumb-init.c
#include <assert.h>
#include <errno.h>
#include <getopt.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#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);
}
}