Entry-point for containers that proxies signals
99fcfe19a4cbda2f4b54c115a61d87bebd39fd2d..9b65e1da122bec8a5d5097986622c0e00e12886e
2017-11-14 CentOS Sources
import dumb-init-1.2.0-1.20170802gitd283f8a.el6
9b65e1 diff | tree
4 files added
1 files deleted
424 ■■■■■ changed files
.dumb-init.metadata 1 ●●●● patch | view | raw | blame | history
.gitignore 1 ●●●● patch | view | raw | blame | history
README.md 4 ●●●● patch | view | raw | blame | history
SOURCES/initscripts.patch 322 ●●●●● patch | view | raw | blame | history
SPECS/dumb-init.spec 96 ●●●●● patch | view | raw | blame | history
.dumb-init.metadata
New file
@@ -0,0 +1 @@
16c44156c856c6fb368e60ebc233ab47025305b2 SOURCES/dumb-init-d283f8a.tar.gz
.gitignore
New file
@@ -0,0 +1 @@
SOURCES/dumb-init-d283f8a.tar.gz
README.md
File was deleted
SOURCES/initscripts.patch
New file
@@ -0,0 +1,322 @@
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
SPECS/dumb-init.spec
New file
@@ -0,0 +1,96 @@
%global provider        github
%global provider_tld    com
%global project         Yelp
%global repo            dumb-init
%global provider_prefix %{provider}.%{provider_tld}/%{project}/%{repo}
%global commit          d283f8a58bea8374c03becf8f06339670f2f07ee
%global shortcommit     %(c=%{commit}; echo ${c:0:7})
%global YYYYMMDD        20170802
Name:           %{repo}
Version:        1.2.0
Release:        1.%{YYYYMMDD}git%{shortcommit}%{?dist}
Summary:        Entry-point for containers that proxies signals
License:        MIT
URL:            https://%{provider_prefix}
Source0:        %{url}/archive/%{commit}/%{name}-%{shortcommit}.tar.gz
Patch0:         initscripts.patch
BuildRequires:  gcc, help2man
#To build in rhel6, we just run gcc and help2man.
#Containers do not work in rhel6, python is old, packages are missing, tests will not work
#the following is only for future reference:
#BuildRequires:  python, python2-pytest, python2-mock
# /bin/xxd of vim-common of is needed for non-released versions
# BuildRequires:  vim-common
%description
dumb-init is a simple process supervisor and init system designed to run as
PID 1 inside minimal container environments (such as Docker).
* It can handle orphaned zombie processes.
* It can pass signals properly for simple containers.
%prep
%setup -q -n %{name}-%{commit}
%patch0 -p1 -b .initscripts
%build
gcc -std=gnu99 %{optflags} -o %{name} dumb-init.c
#a wrapper to redirect stderr for old help2man
cat <<EOF >%{name}.wrapper
#!/bin/bash
./dumb-init "\$@" 2>&1
EOF
chmod a+x %{name}.wrapper
help2man --include debian/help2man --no-info --name '%{summary}' ./%{name}.wrapper> %{name}.1
rm %{name}.wrapper
#sadly, in rhel6, no containers, no tests
#%%check
#PATH=.:$PATH py.test tests/
%install
install -Dpm0755 %{name} %{buildroot}%{_bindir}/%{name}
install -Dpm0644 %{name}.1 %{buildroot}%{_mandir}/man1/%{name}.1
%files
%{_bindir}/%{name}
%{_mandir}/man1/%{name}.1*
%doc README.md LICENSE
%changelog
* Mon Aug 21 2017 Frantisek Kluknavsky <fkluknav@redhat.com> - 1.2.0-2.20170802gitd283f8a
- rebased and heavily adapted for rhel6
* Fri Feb 10 2017 Fedora Release Engineering <releng@fedoraproject.org> - 1.1.3-11
- Rebuilt for https://fedoraproject.org/wiki/Fedora_26_Mass_Rebuild
* Wed Aug 31 2016 Muayyad Alsadi <alsadi@gmail.com> - 1.1.3-10
- revert to python2
* Wed Aug 31 2016 Muayyad Alsadi <alsadi@gmail.com> - 1.1.3-9
- support epel
* Fri Aug 26 2016 Muayyad Alsadi <alsadi@gmail.com> - 1.1.3-8
- run tests
* Wed Aug 17 2016 Muayyad Alsadi <alsadi@gmail.com> - 1.1.3-7
- let manpage automatically marked as document
* Wed Aug 17 2016 Muayyad Alsadi <alsadi@gmail.com> - 1.1.3-6
- remove gzip after help2man
- add missing BuildRequire
* Wed Aug 17 2016 Muayyad Alsadi <alsadi@gmail.com> - 1.1.3-4
- install 644 for manpage
* Wed Aug 17 2016 Muayyad Alsadi <alsadi@gmail.com> - 1.1.3-3
- remove vim-common and use install
* Mon Aug 15 2016 Muayyad Alsadi <alsadi@gmail.com> - 1.1.3-2
- initial packaging