2015-07-30 02:03:55 +03:00
|
|
|
/*
|
|
|
|
* dumb-init is a simple wrapper program designed to run as PID 1 and pass
|
|
|
|
* signals to its children.
|
|
|
|
*
|
|
|
|
* Usage:
|
|
|
|
* ./dumb-init python -c 'while True: pass'
|
|
|
|
*
|
2015-10-02 23:31:35 +03:00
|
|
|
* To get debug output on stderr, run with '-v'.
|
2015-07-30 02:03:55 +03:00
|
|
|
*/
|
|
|
|
|
2015-09-05 01:19:17 +03:00
|
|
|
#include <assert.h>
|
2015-08-26 19:05:54 +03:00
|
|
|
#include <errno.h>
|
2015-10-02 23:31:35 +03:00
|
|
|
#include <getopt.h>
|
2015-07-30 02:03:55 +03:00
|
|
|
#include <signal.h>
|
|
|
|
#include <stdio.h>
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <string.h>
|
|
|
|
#include <sys/types.h>
|
|
|
|
#include <sys/wait.h>
|
|
|
|
#include <unistd.h>
|
2015-10-02 23:31:35 +03:00
|
|
|
#include "VERSION.h"
|
2015-07-30 02:03:55 +03:00
|
|
|
|
2015-10-02 20:48:23 +03:00
|
|
|
#define PRINTERR(...) do { \
|
|
|
|
fprintf(stderr, "[dumb-init] " __VA_ARGS__); \
|
|
|
|
} while (0)
|
|
|
|
|
2015-08-26 19:05:54 +03:00
|
|
|
#define DEBUG(...) do { \
|
|
|
|
if (debug) { \
|
2015-10-02 20:48:23 +03:00
|
|
|
PRINTERR(__VA_ARGS__); \
|
2015-08-26 19:05:54 +03:00
|
|
|
} \
|
|
|
|
} while (0)
|
|
|
|
|
2016-06-14 00:28:57 +03:00
|
|
|
// Signals we care about are numbered from 1 to 31, inclusive.
|
|
|
|
// (32 and above are real-time signals.)
|
|
|
|
#define MAXSIG 31
|
2016-06-10 01:05:27 +03:00
|
|
|
|
2016-06-14 00:28:57 +03:00
|
|
|
// Indices are one-indexed (signal 1 is at index 1). Index zero is unused.
|
2016-06-14 21:34:52 +03:00
|
|
|
int signal_rewrite[MAXSIG + 1] = {[0 ... MAXSIG] = -1};
|
2016-06-10 01:05:27 +03:00
|
|
|
|
2015-09-05 01:19:17 +03:00
|
|
|
pid_t child_pid = -1;
|
2015-07-30 02:03:55 +03:00
|
|
|
char debug = 0;
|
2015-09-11 00:05:05 +03:00
|
|
|
char use_setsid = 1;
|
2016-06-04 05:09:37 +03:00
|
|
|
|
|
|
|
int translate_signal(int signum) {
|
2016-06-14 00:28:57 +03:00
|
|
|
if (signum <= 0 || signum > MAXSIG) {
|
2016-06-10 01:05:27 +03:00
|
|
|
return signum;
|
|
|
|
} else {
|
2016-06-14 00:28:57 +03:00
|
|
|
int translated = signal_rewrite[signum];
|
2016-06-19 23:49:40 +03:00
|
|
|
if (translated == -1) {
|
|
|
|
return signum;
|
|
|
|
} else {
|
|
|
|
DEBUG("Translating signal %d to %d.\n", signum, translated);
|
|
|
|
return translated;
|
|
|
|
}
|
2016-06-04 05:09:37 +03:00
|
|
|
}
|
|
|
|
}
|
2015-07-30 02:03:55 +03:00
|
|
|
|
2015-09-17 05:42:09 +03:00
|
|
|
void forward_signal(int signum) {
|
2016-06-04 05:09:37 +03:00
|
|
|
signum = translate_signal(signum);
|
2016-06-14 21:34:52 +03:00
|
|
|
if (signum != -1) {
|
|
|
|
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);
|
|
|
|
}
|
2015-07-30 02:03:55 +03:00
|
|
|
}
|
|
|
|
|
2015-09-19 07:59:31 +03:00
|
|
|
/*
|
|
|
|
* 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
|
|
|
|
*
|
|
|
|
*/
|
2015-09-17 05:42:09 +03:00
|
|
|
void handle_signal(int signum) {
|
|
|
|
DEBUG("Received signal %d.\n", signum);
|
2016-04-30 02:19:06 +03:00
|
|
|
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);
|
|
|
|
}
|
2015-09-19 07:59:31 +03:00
|
|
|
|
2016-04-30 02:19:06 +03:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
2015-09-19 07:59:31 +03:00
|
|
|
} else {
|
|
|
|
forward_signal(signum);
|
2016-06-14 20:54:03 +03:00
|
|
|
if (signum == SIGTSTP || signum == SIGTTOU || signum == SIGTTIN) {
|
|
|
|
DEBUG("Suspending self due to TTY signal.\n");
|
|
|
|
kill(getpid(), SIGSTOP);
|
|
|
|
}
|
2015-09-19 07:59:31 +03:00
|
|
|
}
|
2015-09-05 01:19:17 +03:00
|
|
|
}
|
|
|
|
|
2015-08-07 00:24:27 +03:00
|
|
|
void print_help(char *argv[]) {
|
2015-08-10 19:32:56 +03:00
|
|
|
fprintf(stderr,
|
2015-10-02 23:31:35 +03:00
|
|
|
"dumb-init v%s"
|
|
|
|
"Usage: %s [option] command [[arg] ...]\n"
|
2015-08-10 19:32:56 +03:00
|
|
|
"\n"
|
2015-10-02 23:31:35 +03:00
|
|
|
"dumb-init is a simple process supervisor that forwards signals to children.\n"
|
|
|
|
"It is designed to run as PID1 in minimal container environments.\n"
|
2015-09-04 20:26:47 +03:00
|
|
|
"\n"
|
2015-10-02 23:31:35 +03:00
|
|
|
"Optional arguments:\n"
|
|
|
|
" -c, --single-child Run in single-child mode.\n"
|
2015-10-26 20:20:18 +03:00
|
|
|
" In this mode, signals are only proxied to the\n"
|
2015-10-03 08:28:40 +03:00
|
|
|
" direct child and not any of its descendants.\n"
|
2016-06-14 00:28:57 +03:00
|
|
|
" -r, --rewrite s:r Rewrite received signal s to new signal r before proxying.\n"
|
2016-06-14 21:34:52 +03:00
|
|
|
" To ignore (not proxy) a signal, rewrite it to 0.\n"
|
|
|
|
" This option can be specified multiple times.\n"
|
2015-10-02 23:31:35 +03:00
|
|
|
" -v, --verbose Print debugging information to stderr.\n"
|
|
|
|
" -h, --help Print this help message and exit.\n"
|
|
|
|
" -V, --version Print the current version and exit.\n"
|
2015-08-10 19:32:56 +03:00
|
|
|
"\n"
|
2015-10-02 23:31:35 +03:00
|
|
|
"Full help is available online at https://github.com/Yelp/dumb-init\n",
|
|
|
|
VERSION,
|
2015-08-10 19:32:56 +03:00
|
|
|
argv[0]
|
|
|
|
);
|
2015-08-07 00:24:27 +03:00
|
|
|
}
|
|
|
|
|
2016-06-10 01:05:27 +03:00
|
|
|
void print_rewrite_signum_help() {
|
|
|
|
fprintf(
|
|
|
|
stderr,
|
|
|
|
"Usage: -r option takes <signum>:<signum>, where <signum> "
|
|
|
|
"is between 1 and %d.\n"
|
2016-06-14 00:28:57 +03:00
|
|
|
"This option can be specified multiple times.\n"
|
2016-06-10 01:05:27 +03:00
|
|
|
"Use --help for full usage.\n",
|
|
|
|
MAXSIG
|
|
|
|
);
|
|
|
|
exit(1);
|
2016-06-04 05:09:37 +03:00
|
|
|
}
|
|
|
|
|
2016-06-14 00:28:57 +03:00
|
|
|
void parse_rewrite_signum(char *arg) {
|
2016-06-10 01:05:27 +03:00
|
|
|
int signum, replacement;
|
2016-06-14 00:28:57 +03:00
|
|
|
if (
|
|
|
|
sscanf(arg, "%d:%d", &signum, &replacement) == 2 &&
|
|
|
|
(signum >= 1 && signum <= MAXSIG) &&
|
2016-06-14 21:34:52 +03:00
|
|
|
(replacement >= 0 && replacement <= MAXSIG)
|
2016-06-14 00:28:57 +03:00
|
|
|
) {
|
|
|
|
signal_rewrite[signum] = replacement;
|
|
|
|
} else {
|
2016-06-10 01:05:27 +03:00
|
|
|
print_rewrite_signum_help();
|
|
|
|
}
|
|
|
|
}
|
2016-06-04 05:09:37 +03:00
|
|
|
|
2016-06-14 20:54:03 +03:00
|
|
|
void set_rewrite_to_sigstop_if_not_defined(int signum) {
|
2016-06-14 21:34:52 +03:00
|
|
|
if (signal_rewrite[signum] == -1)
|
2016-06-14 20:54:03 +03:00
|
|
|
signal_rewrite[signum] = SIGSTOP;
|
|
|
|
}
|
|
|
|
|
2016-04-30 02:19:06 +03:00
|
|
|
char **parse_command(int argc, char *argv[]) {
|
|
|
|
int opt;
|
2015-10-02 23:31:35 +03:00
|
|
|
struct option long_options[] = {
|
2016-06-04 05:09:37 +03:00
|
|
|
{"help", no_argument, NULL, 'h'},
|
|
|
|
{"single-child", no_argument, NULL, 'c'},
|
2016-06-10 01:05:27 +03:00
|
|
|
{"rewrite", required_argument, NULL, 'r'},
|
2016-06-04 05:09:37 +03:00
|
|
|
{"verbose", no_argument, NULL, 'v'},
|
|
|
|
{"version", no_argument, NULL, 'V'},
|
2016-06-17 20:13:19 +03:00
|
|
|
{NULL, 0, NULL, 0},
|
2015-10-02 23:31:35 +03:00
|
|
|
};
|
2016-06-10 01:05:27 +03:00
|
|
|
while ((opt = getopt_long(argc, argv, "+hvVcr:", long_options, NULL)) != -1) {
|
2015-10-02 23:31:35 +03:00
|
|
|
switch (opt) {
|
|
|
|
case 'h':
|
|
|
|
print_help(argv);
|
2016-04-30 02:19:06 +03:00
|
|
|
exit(0);
|
2015-10-02 23:31:35 +03:00
|
|
|
case 'v':
|
|
|
|
debug = 1;
|
|
|
|
break;
|
|
|
|
case 'V':
|
|
|
|
fprintf(stderr, "dumb-init v%s", VERSION);
|
2016-04-30 02:19:06 +03:00
|
|
|
exit(0);
|
2015-10-02 23:31:35 +03:00
|
|
|
case 'c':
|
|
|
|
use_setsid = 0;
|
|
|
|
break;
|
2016-06-10 01:05:27 +03:00
|
|
|
case 'r':
|
2016-06-14 00:28:57 +03:00
|
|
|
parse_rewrite_signum(optarg);
|
2016-06-04 05:09:37 +03:00
|
|
|
break;
|
2015-10-02 23:31:35 +03:00
|
|
|
default:
|
2016-04-30 02:19:06 +03:00
|
|
|
exit(1);
|
2015-10-02 23:31:35 +03:00
|
|
|
}
|
|
|
|
}
|
2015-07-30 02:03:55 +03:00
|
|
|
|
2015-10-02 23:31:35 +03:00
|
|
|
if (optind >= argc) {
|
|
|
|
fprintf(
|
|
|
|
stderr,
|
|
|
|
"Usage: %s [option] program [args]\n"
|
|
|
|
"Try %s --help for full usage.\n",
|
|
|
|
argv[0], argv[0]
|
|
|
|
);
|
2016-04-30 02:19:06 +03:00
|
|
|
exit(1);
|
2015-08-10 19:32:56 +03:00
|
|
|
}
|
2015-07-30 02:03:55 +03:00
|
|
|
|
2015-10-02 23:31:35 +03:00
|
|
|
char *debug_env = getenv("DUMB_INIT_DEBUG");
|
2015-08-26 19:05:54 +03:00
|
|
|
if (debug_env && strcmp(debug_env, "1") == 0) {
|
2015-08-10 19:32:56 +03:00
|
|
|
debug = 1;
|
2015-08-26 19:05:54 +03:00
|
|
|
DEBUG("Running in debug mode.\n");
|
|
|
|
}
|
2015-07-30 02:03:55 +03:00
|
|
|
|
2015-10-02 23:31:35 +03:00
|
|
|
char *setsid_env = getenv("DUMB_INIT_SETSID");
|
2015-09-11 00:05:05 +03:00
|
|
|
if (setsid_env && strcmp(setsid_env, "0") == 0) {
|
|
|
|
use_setsid = 0;
|
|
|
|
DEBUG("Not running in setsid mode.\n");
|
2015-08-26 19:05:54 +03:00
|
|
|
}
|
2015-07-30 02:03:55 +03:00
|
|
|
|
2016-06-14 20:54:03 +03:00
|
|
|
if (use_setsid) {
|
|
|
|
set_rewrite_to_sigstop_if_not_defined(SIGTSTP);
|
|
|
|
set_rewrite_to_sigstop_if_not_defined(SIGTTOU);
|
|
|
|
set_rewrite_to_sigstop_if_not_defined(SIGTTIN);
|
|
|
|
}
|
|
|
|
|
2016-04-30 02:19:06 +03:00
|
|
|
return &argv[optind];
|
|
|
|
}
|
2015-07-30 02:03:55 +03:00
|
|
|
|
2016-04-30 02:19:06 +03:00
|
|
|
int main(int argc, char *argv[]) {
|
|
|
|
char **cmd = parse_command(argc, argv);
|
2016-07-26 08:30:22 +03:00
|
|
|
sigset_t all_signals;
|
|
|
|
sigfillset(&all_signals);
|
|
|
|
sigprocmask(SIG_BLOCK, &all_signals, NULL);
|
2015-07-30 02:03:55 +03:00
|
|
|
|
2015-09-05 01:19:17 +03:00
|
|
|
child_pid = fork();
|
|
|
|
if (child_pid < 0) {
|
2015-10-02 20:48:23 +03:00
|
|
|
PRINTERR("Unable to fork. Exiting.\n");
|
2015-08-10 19:32:56 +03:00
|
|
|
return 1;
|
2016-04-30 02:19:06 +03:00
|
|
|
} else if (child_pid == 0) {
|
|
|
|
/* child */
|
2016-07-26 08:30:22 +03:00
|
|
|
sigprocmask(SIG_UNBLOCK, &all_signals, NULL);
|
2015-09-11 00:05:05 +03:00
|
|
|
if (use_setsid) {
|
2016-04-30 02:19:06 +03:00
|
|
|
if (setsid() == -1) {
|
2015-10-02 20:48:23 +03:00
|
|
|
PRINTERR(
|
2015-09-11 00:05:05 +03:00
|
|
|
"Unable to setsid (errno=%d %s). Exiting.\n",
|
2015-08-26 19:05:54 +03:00
|
|
|
errno,
|
|
|
|
strerror(errno)
|
|
|
|
);
|
|
|
|
exit(1);
|
|
|
|
}
|
2015-09-11 00:05:05 +03:00
|
|
|
DEBUG("setsid complete.\n");
|
2015-08-26 19:05:54 +03:00
|
|
|
}
|
2015-10-02 23:31:35 +03:00
|
|
|
execvp(cmd[0], &cmd[0]);
|
2015-09-29 20:33:26 +03:00
|
|
|
|
|
|
|
// if this point is reached, exec failed, so we should exit nonzero
|
2016-05-28 09:03:19 +03:00
|
|
|
PRINTERR("%s: %s\n", cmd[0], strerror(errno));
|
2016-04-30 02:19:06 +03:00
|
|
|
return 2;
|
2015-08-10 19:32:56 +03:00
|
|
|
} else {
|
2016-04-30 02:19:06 +03:00
|
|
|
/* parent */
|
2015-09-05 01:19:17 +03:00
|
|
|
DEBUG("Child spawned with PID %d.\n", child_pid);
|
2016-04-30 02:19:06 +03:00
|
|
|
for (;;) {
|
2016-07-26 08:30:22 +03:00
|
|
|
int signum;
|
2016-04-30 02:19:06 +03:00
|
|
|
sigwait(&all_signals, &signum);
|
|
|
|
handle_signal(signum);
|
2015-09-05 01:19:17 +03:00
|
|
|
}
|
2015-08-10 19:32:56 +03:00
|
|
|
}
|
2015-07-30 02:03:55 +03:00
|
|
|
}
|