From 6d1619802269db2f62c6b51b03a049c3d3a23a2c Mon Sep 17 00:00:00 2001
From: Frantisek Kluknavsky <fkluknav@redhat.com>
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 <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
+#include <dirent.h>
+#include <ctype.h>
#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 <signum>:<signum>, where <signum> "
+ "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 <signum>:<path>, where <signum> "
"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