From 6d1619802269db2f62c6b51b03a049c3d3a23a2c Mon Sep 17 00:00:00 2001 From: Frantisek Kluknavsky Date: Mon, 21 Aug 2017 16:44:19 +0200 Subject: [PATCH] rhel6 features --- dumb-init.c | 199 +++++++++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 171 insertions(+), 28 deletions(-) diff --git a/dumb-init.c b/dumb-init.c index 65e69ef..3893b8c 100644 --- a/dumb-init.c +++ b/dumb-init.c @@ -19,6 +19,8 @@ #include #include #include +#include +#include #include "VERSION.h" #define PRINTERR(...) do { \ @@ -34,14 +36,18 @@ // 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 +// EDIT: we actually care about some real-time signals as well +// SIGRTMAX is not a constant, use 64 +#define MAXSIG 64 // Indices are one-indexed (signal 1 is at index 1). Index zero is unused. int signal_rewrite[MAXSIG + 1] = {[0 ... MAXSIG] = -1}; +char *signal_action[MAXSIG + 1] = {[0 ... MAXSIG] = ""}; pid_t child_pid = -1; char debug = 0; char use_setsid = 1; +static char survive_bereaving = 0; int translate_signal(int signum) { if (signum <= 0 || signum > MAXSIG) { @@ -57,17 +63,75 @@ int translate_signal(int signum) { } } +void do_action(int signum) { + DEBUG("Action for signal %d: running %s\n", signum, signal_action[signum]); + int child_pid = fork(); + if (child_pid < 0) { + PRINTERR("Unable to fork. Exiting.\n"); + exit(1); + } else if (child_pid == 0) { + /* child */ + sigset_t all_signals; + sigfillset(&all_signals); + sigprocmask(SIG_UNBLOCK, &all_signals, NULL); + execlp("/bin/bash", "/bin/bash", "-c", signal_action[signum], (char *)NULL); + + // if this point is reached, exec failed, so we should exit nonzero + PRINTERR("Could not exec %s: %s\n", signal_action[signum], strerror(errno)); + exit(1); + } +} + void forward_signal(int signum) { - signum = translate_signal(signum); - if (signum != -1) { - kill(use_setsid ? -child_pid : child_pid, signum); - DEBUG("Forwarded signal %d to children.\n", signum); + int new_signum = translate_signal(signum); + if (new_signum == -2) { + do_action(signum); + } else if (new_signum != -1) { + kill(use_setsid ? -child_pid : child_pid, new_signum); + DEBUG("Forwarded signal %d to children.\n", new_signum); } else { - DEBUG("Not forwarding signal %d to children (ignored).\n", signum); + DEBUG("Not forwarding signal %d to children (ignored).\n", new_signum); } } /* + * Read /proc and see if there are processes except init(PIDs) + */ +signed int process_count() { + DIR *dp; + struct dirent *ep; + char nonnumber; + signed int count = 0; + + dp = opendir ("/proc"); + if (dp != NULL) + { + while ((ep = readdir (dp)) != NULL) { + nonnumber = 0; + for (int i = 0; ep->d_name[i] != 0; ++i) { + if (!isdigit(ep->d_name[i])) { + nonnumber = 1; + break; + } + } + if (!nonnumber) { + DEBUG("/proc/%s is a process\n", ep->d_name); + ++count; + if (count > 1) { + closedir(dp); + return 2; //2 is enough, do not count further + } + } + } + closedir(dp); + } else { + PRINTERR("Could not open /proc.\n"); + return -1; + } + return count; +} + +/* * The dumb-init signal handler. * * The main job of this signal handler is to forward signals along to our child @@ -88,6 +152,7 @@ void forward_signal(int signum) { * */ void handle_signal(int signum) { + static char bereaved = 0; DEBUG("Received signal %d.\n", signum); if (signum == SIGCHLD) { int status, exit_status; @@ -103,11 +168,26 @@ void handle_signal(int signum) { } 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); + bereaved = 1; + if (!survive_bereaving) { + forward_signal(SIGTERM); // send SIGTERM to any remaining children + DEBUG("Child exited with status %d. Goodbye.\n", exit_status); + exit(exit_status); + } else { + DEBUG("Child exited with status %d. Stay alive for your grandchildren.\n", exit_status); + } } } + + if ((bereaved == 1) && survive_bereaving) { + signed int pc = process_count(); + DEBUG("Process count: %d\n", pc); + if (pc <= 1) { + DEBUG("No process left, exitting.\n"); + exit(0); + } + } + } else { forward_signal(signum); if (signum == SIGTSTP || signum == SIGTTOU || signum == SIGTTIN) { @@ -126,15 +206,21 @@ void print_help(char *argv[]) { "It is designed to run as PID1 in minimal container environments.\n" "\n" "Optional arguments:\n" - " -c, --single-child Run in single-child mode.\n" - " In this mode, signals are only proxied to the\n" - " direct child and not any of its descendants.\n" - " -r, --rewrite s:r Rewrite received signal s to new signal r before proxying.\n" - " To ignore (not proxy) a signal, rewrite it to 0.\n" - " This option can be specified multiple times.\n" - " -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" + " -c, --single-child Run in single-child mode.\n" + " In this mode, signals are only proxied to the\n" + " direct child and not any of its descendants.\n" + " -b, --survive-bereaving Do not quit when the direct child dies.\n" + " -r, --rewrite s:r Rewrite received signal s to new signal r before proxying.\n" + " To ignore (not proxy) a signal, rewrite it to 0.\n" + " To rewrite all signals, rewrite (otherwise nonexistent) signal 0.\n" + " (Useful to ignore all signals, use '--rewrite 0:0').\n" + " This option can be specified multiple times.\n" + " -a, --action s:exe Run exe after receiving sinal s.\n" + " For example, -a '2:echo hi there'.\n" + " This option can be specified multiple times.\n" + " -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" "\n" "Full help is available online at https://github.com/Yelp/dumb-init\n", VERSION, @@ -146,11 +232,24 @@ void print_rewrite_signum_help() { fprintf( stderr, "Usage: -r option takes :, where " + "is between 0 and %d.\n" + "This option can be specified multiple times.\n" + "Use --help for full usage.\n", + MAXSIG + ); + exit(1); +} + +void print_action_help() { + fprintf( + stderr, + "Usage: -a option takes :, where " "is between 1 and %d.\n" "This option can be specified multiple times.\n" "Use --help for full usage.\n", MAXSIG ); + exit(1); } @@ -158,15 +257,36 @@ void parse_rewrite_signum(char *arg) { int signum, replacement; if ( sscanf(arg, "%d:%d", &signum, &replacement) == 2 && - (signum >= 1 && signum <= MAXSIG) && + (signum >= 0 && signum <= MAXSIG) && (replacement >= 0 && replacement <= MAXSIG) ) { - signal_rewrite[signum] = replacement; + if (signum == 0) { + for (int i = 0; i <= MAXSIG; ++i) { + signal_rewrite[i] = replacement; + } + } else { + signal_rewrite[signum] = replacement; + } } else { print_rewrite_signum_help(); } } +void parse_action(char *arg) { + int signum; + int status; + int position; + if ( + (status = sscanf(arg, "%d:%n", &signum, &position)) == 1 && + (signum >= 0 && signum <= MAXSIG) + ) { + DEBUG("signal action: %d, position %d\n", signum, position); + signal_action[signum] = &(arg[position]); + signal_rewrite[signum] = -2; + } else { + print_action_help(); + } +} void set_rewrite_to_sigstop_if_not_defined(int signum) { if (signal_rewrite[signum] == -1) signal_rewrite[signum] = SIGSTOP; @@ -175,14 +295,16 @@ void set_rewrite_to_sigstop_if_not_defined(int signum) { char **parse_command(int argc, char *argv[]) { int opt; struct option long_options[] = { - {"help", no_argument, NULL, 'h'}, - {"single-child", no_argument, NULL, 'c'}, - {"rewrite", required_argument, NULL, 'r'}, - {"verbose", no_argument, NULL, 'v'}, - {"version", no_argument, NULL, 'V'}, + {"help", no_argument, NULL, 'h'}, + {"single-child", no_argument, NULL, 'c'}, + {"rewrite", required_argument, NULL, 'r'}, + {"verbose", no_argument, NULL, 'v'}, + {"version", no_argument, NULL, 'V'}, + {"survive-bereaving",no_argument, NULL, 'b'}, + {"action", required_argument, NULL, 'a'}, {NULL, 0, NULL, 0}, }; - while ((opt = getopt_long(argc, argv, "+hvVcr:", long_options, NULL)) != -1) { + while ((opt = getopt_long(argc, argv, "+hvVcbr:a:", long_options, NULL)) != -1) { switch (opt) { case 'h': print_help(argv); @@ -199,7 +321,14 @@ char **parse_command(int argc, char *argv[]) { case 'r': parse_rewrite_signum(optarg); break; + case 'a': + parse_action(optarg); + break; + case 'b': + survive_bereaving = 1; + break; default: + PRINTERR("Error while parsing arguments.\n"); exit(1); } } @@ -295,8 +424,22 @@ int main(int argc, char *argv[]) { /* parent */ DEBUG("Child spawned with PID %d.\n", child_pid); for (;;) { - int signum; - sigwait(&all_signals, &signum); + struct timespec timeout = {1, 0}; + int signum = sigtimedwait(&all_signals, NULL, &timeout); + if (signum == -1) { + switch (errno) { + case EINVAL: + PRINTERR("Invalid timeout, report this as a bug!\n"); + exit(1); + case EINTR: + PRINTERR("Wait interrupted by a signal. This should never happen. Report this as a bug!\n"); + exit(1); + case EAGAIN: + //pretend timeout to be SIGCHLD, check if we want to continue running + signum = SIGCHLD; + DEBUG("Heartbeat...\n"); + } + } handle_signal(signum); } } -- 2.13.3