From bcb8a4b87035d78e88a15e880589403a004c1d63 Mon Sep 17 00:00:00 2001 From: Thomas Orozco Date: Mon, 4 May 2015 22:22:32 +0200 Subject: [PATCH] Allow users to enable sub-reaping This allows users that don't control PID 1 in their container to still use Tini to reap zombies. --- ci/run_build.sh | 20 ++--- src/tini.c | 98 +++++++++++++++++++--- test/0001-Add-PR_SET_CHILD_SUBREAPER.patch | 35 -------- 3 files changed, 93 insertions(+), 60 deletions(-) delete mode 100644 test/0001-Add-PR_SET_CHILD_SUBREAPER.patch diff --git a/ci/run_build.sh b/ci/run_build.sh index a21011a..a93ed68 100755 --- a/ci/run_build.sh +++ b/ci/run_build.sh @@ -14,6 +14,11 @@ export DIST_DIR="$(readlink -f "${DIST_DIR}")" export BUILD_DIR="$(readlink -f "${BUILD_DIR}")" +# Our build platform doesn't have those newer Linux flags, but we want Tini to have subreaper support +# We also use those in our tests +export CFLAGS="-DPR_SET_CHILD_SUBREAPER=36 -DPR_GET_CHILD_SUBREAPER=37" + + # Ensure Python output is not buffered (to make tests output clearer) export PYTHONUNBUFFERED=1 @@ -65,20 +70,7 @@ virtualenv "${VENV}" export PATH="${VENV}/bin:${PATH}" # Install test dependencies - -# We need a patched version because Travis only gives us Ubuntu Precise -# (whose Linux headers don't include PR_SET_CHILD_SUBREAPER), but actually -# runs a newer Linux Kernel (because we're actually in Docker) that has the -# PR_SET_CHILD_SUBREAPER prctl call. -pushd /tmp -pip install python-prctl==1.6.1 --download="." -tar -xvf /tmp/python-prctl-1.6.1.tar.gz -cd python-prctl-1.6.1 -patch -p1 < "${SOURCE_DIR}/test/0001-Add-PR_SET_CHILD_SUBREAPER.patch" -python setup.py install -popd - -pip install psutil +pip install psutil python-prctl # Run tests python "${SOURCE_DIR}/test/run_inner_tests.py" diff --git a/src/tini.c b/src/tini.c index 8b2f07e..0d2e99e 100644 --- a/src/tini.c +++ b/src/tini.c @@ -3,6 +3,7 @@ #include #include +#include #include #include @@ -24,6 +25,18 @@ #define ARRAY_LEN(x) (sizeof(x) / sizeof(x[0])) +#ifdef PR_SET_CHILD_SUBREAPER +#define HAS_SUBREAPER 1 +#define OPT_STRING "hsv" +#else +#define HAS_SUBREAPER 0 +#define OPT_STRING "hv" +#endif + + +#if HAS_SUBREAPER +static int subreaper = 0; +#endif static int verbosity = 0; static struct timespec ts = { .tv_sec = 1, .tv_nsec = 0 }; @@ -58,6 +71,9 @@ void print_usage(char* const name, FILE* const file) { fprintf(file, "Usage: %s [OPTIONS] PROGRAM -- [ARGS]\n\n", basename(name)); fprintf(file, "Execute a program under the supervision of a valid init process (%s)\n\n", basename(name)); fprintf(file, " -h: Show this help message and exit.\n"); +#if HAS_SUBREAPER + fprintf(file, " -s: Register as a process subreaper (requires Linux >= 3.4).\n"); +#endif fprintf(file, " -v: Generate more verbose output. Repeat up to 4 times.\n"); fprintf(file, "\n"); } @@ -67,13 +83,18 @@ int parse_args(const int argc, char* const argv[], char* (**child_args_ptr_ptr)[ char* name = argv[0]; int c; - while ((c = getopt (argc, argv, "hv")) != -1) { + while ((c = getopt(argc, argv, OPT_STRING)) != -1) { switch (c) { case 'h': /* TODO - Shouldn't cause exit with -1 ..*/ print_usage(name, stdout); *parse_fail_exitcode_ptr = 0; return 1; +#if HAS_SUBREAPER + case 's': + subreaper++; + break; +#endif case 'v': verbosity++; break; @@ -107,6 +128,50 @@ int parse_args(const int argc, char* const argv[], char* (**child_args_ptr_ptr)[ return 0; } + +#if HAS_SUBREAPER +int register_subreaper () { + if (subreaper > 0) { + if (prctl(PR_SET_CHILD_SUBREAPER)) { + if (errno == EINVAL) { + PRINT_FATAL("PR_SET_CHILD_SUBREAPER is unavailable on this platform. Are you using Linux >= 3.4?") + } else { + PRINT_FATAL("Failed to register as child subreaper: %s", strerror(errno)) + } + return 1; + } else { + PRINT_TRACE("Registered as child subreaper"); + } + } + return 0; +} +#endif + + +void reaper_check () { + /* Check that we can properly reap zombies */ +#if HAS_SUBREAPER + int bit = 0; +#endif + + if (getpid() == 1) { + return; + } + +#if HAS_SUBREAPER + if (prctl(PR_GET_CHILD_SUBREAPER, &bit)) { + PRINT_DEBUG("Failed to read child subreaper attribute: %s", strerror(errno)); + } else if (bit == 1) { + return; + } +#endif + + PRINT_WARNING("Tini is not running as PID 1 and isn't registered as a child subreaper.\n\ + Zombie processes will not be re-parented to Tini, so zombie reaping won't work.\n\ + Use -s or run Tini as PID 1 to fix the problem."); +} + + int prepare_sigmask(sigset_t* const parent_sigset_ptr, sigset_t* const child_sigset_ptr) { /* Prepare signals to block; make sure we don't block program error signals. */ if (sigfillset(parent_sigset_ptr)) { @@ -182,14 +247,14 @@ int reap_zombies(const pid_t child_pid, int* const child_exitcode_ptr) { case -1: if (errno == ECHILD) { - PRINT_TRACE("No child to wait."); + PRINT_TRACE("No child to wait"); break; } PRINT_FATAL("Error while waiting for pids: '%s'", strerror(errno)); return 1; case 0: - PRINT_TRACE("No child to reap."); + PRINT_TRACE("No child to reap"); break; default: @@ -209,7 +274,7 @@ int reap_zombies(const pid_t child_pid, int* const child_exitcode_ptr) { PRINT_INFO("Main child exited with signal (with signal '%s')", strsignal(WTERMSIG(current_status))); *child_exitcode_ptr = 128 + WTERMSIG(current_status); } else { - PRINT_FATAL("Main child exited for unknown reason!"); + PRINT_FATAL("Main child exited for unknown reason"); return 1; } } @@ -233,6 +298,13 @@ int main(int argc, char *argv[]) { int child_exitcode = -1; // This isn't a valid exitcode, and lets us tell whether the child has exited. int parse_exitcode = 1; // By default, we exit with 1 if parsing fails. + /* Parse command line arguments */ + char* (*child_args_ptr)[]; + int parse_args_ret = parse_args(argc, argv, &child_args_ptr, &parse_exitcode); + if (parse_args_ret) { + return parse_exitcode; + } + /* Prepare sigmask */ sigset_t parent_sigset; sigset_t child_sigset; @@ -240,13 +312,17 @@ int main(int argc, char *argv[]) { return 1; } - /* Parse command line arguments */ - char* (*child_args_ptr)[]; - int parse_args_ret = parse_args(argc, argv, &child_args_ptr, &parse_exitcode); - if (parse_args_ret) { - return parse_exitcode; - } +#if HAS_SUBREAPER + /* If available and requested, register as a subreaper */ + if (register_subreaper()) { + return 1; + }; +#endif + + /* Are we going to reap zombies properly? If not, warn. */ + reaper_check(); + /* Go on */ if (spawn(&child_sigset, *child_args_ptr, &child_pid)) { return 1; } @@ -264,7 +340,7 @@ int main(int argc, char *argv[]) { } if (child_exitcode != -1) { - PRINT_TRACE("Child has exited. Exiting"); + PRINT_TRACE("Exiting: child has exited"); return child_exitcode; } } diff --git a/test/0001-Add-PR_SET_CHILD_SUBREAPER.patch b/test/0001-Add-PR_SET_CHILD_SUBREAPER.patch deleted file mode 100644 index f2f339b..0000000 --- a/test/0001-Add-PR_SET_CHILD_SUBREAPER.patch +++ /dev/null @@ -1,35 +0,0 @@ -From b8c6ccd4575837e3901bbdee7b219ef951dc2065 Mon Sep 17 00:00:00 2001 -From: Thomas Orozco -Date: Sun, 28 Jun 2015 15:25:37 +0200 -Subject: [PATCH] Add PR_SET_CHILD_SUBREAPER - ---- - _prctlmodule.c | 12 ++++++++++++ - 1 file changed, 12 insertions(+) - -diff --git a/_prctlmodule.c b/_prctlmodule.c -index 14121c3..19ad141 100644 ---- a/_prctlmodule.c -+++ b/_prctlmodule.c -@@ -15,6 +15,18 @@ - #include - #include - -+/* Our builds run in a Docker environment that has those, but they are -+ * not in the kernel headers. Add them. -+ */ -+ -+#ifndef PR_SET_CHILD_SUBREAPER -+#define PR_SET_CHILD_SUBREAPER 36 -+#endif -+ -+#ifndef PR_GET_CHILD_SUBREAPER -+#define PR_GET_CHILD_SUBREAPER 37 -+#endif -+ - /* New in 2.6.32, but named and implemented inconsistently. The linux - * implementation has two ways of setting the policy to the default, and thus - * needs an extra argument. We ignore the first argument and always call --- -2.4.3 -