From ad20525ad9e817f35b6bdd7add21b0b51d383e0f Mon Sep 17 00:00:00 2001 From: Chris Kuehl Date: Wed, 29 Jul 2015 16:03:55 -0700 Subject: [PATCH 1/3] Initial commit --- .gitignore | 1 + Makefile | 6 ++++ debian/.gitignore | 5 +++ debian/changelog | 5 +++ debian/compat | 1 + debian/control | 26 ++++++++++++++ debian/install | 1 + debian/rules | 3 ++ debian/source/format | 1 + dumb-init.c | 80 ++++++++++++++++++++++++++++++++++++++++++++ 10 files changed, 129 insertions(+) create mode 100644 .gitignore create mode 100644 Makefile create mode 100644 debian/.gitignore create mode 100644 debian/changelog create mode 100644 debian/compat create mode 100644 debian/control create mode 100644 debian/install create mode 100755 debian/rules create mode 100644 debian/source/format create mode 100644 dumb-init.c diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1edd89c --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +dumb-init diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..eb4b37c --- /dev/null +++ b/Makefile @@ -0,0 +1,6 @@ +.PHONY: build +build: + gcc -static -Wall -Werror -o dumb-init dumb-init.c + +clean: + rm -rf dumb-init diff --git a/debian/.gitignore b/debian/.gitignore new file mode 100644 index 0000000..5571ad5 --- /dev/null +++ b/debian/.gitignore @@ -0,0 +1,5 @@ +*.log +*.substvars +files +substvars +dumb-init/ diff --git a/debian/changelog b/debian/changelog new file mode 100644 index 0000000..b4f41f5 --- /dev/null +++ b/debian/changelog @@ -0,0 +1,5 @@ +dumb-init (0.0.1) unstable; urgency=low + + * Initial release. + + -- Chris Kuehl Wed, 29 Jul 2015 15:39:11 -0700 diff --git a/debian/compat b/debian/compat new file mode 100644 index 0000000..7f8f011 --- /dev/null +++ b/debian/compat @@ -0,0 +1 @@ +7 diff --git a/debian/control b/debian/control new file mode 100644 index 0000000..793a137 --- /dev/null +++ b/debian/control @@ -0,0 +1,26 @@ +Source: dumb-init +Section: utils +Priority: extra +Maintainer: Chris Kuehl +Build-Depends: debhelper (>= 7), gcc +Standards-Version: 3.9.6 + +Package: dumb-init +Architecture: any +Depends: ${misc:Depends} +Description: Simple wrapper script which proxies signals to a child + Docker runs your processes as PID1. The kernel doesn't apply default signal + handling to PID1 processes, so if your process doesn't register a custom + signal handler, signals like TERM will just bounce off your process. + . + This can result in cases where sending signals to a `docker run` process + results in the run process exiting, but the container continuing in the + background. + . + A workaround is to wrap your script in this proxy, which runs as PID1. Your + process then runs as some other PID, and the kernel won't treat the signals + that are proxied to them specially. + . + The proxy dies when your process dies, so it must not double-fork or do other + weird things (this is basically a requirement for doing things sanely in + Docker anyway). diff --git a/debian/install b/debian/install new file mode 100644 index 0000000..4343f09 --- /dev/null +++ b/debian/install @@ -0,0 +1 @@ +dumb-init /usr/bin/ diff --git a/debian/rules b/debian/rules new file mode 100755 index 0000000..cbe925d --- /dev/null +++ b/debian/rules @@ -0,0 +1,3 @@ +#!/usr/bin/make -f +%: + dh $@ diff --git a/debian/source/format b/debian/source/format new file mode 100644 index 0000000..89ae9db --- /dev/null +++ b/debian/source/format @@ -0,0 +1 @@ +3.0 (native) diff --git a/dumb-init.c b/dumb-init.c new file mode 100644 index 0000000..1267199 --- /dev/null +++ b/dumb-init.c @@ -0,0 +1,80 @@ +/* + * 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' + * + * To get debug output on stderr, run with DUMB_INIT_DEBUG=1. + */ + +#include +#include +#include +#include +#include +#include +#include + +pid_t child = -1; +char debug = 0; + +void signal_handler(int signum) { + if (debug) + fprintf(stderr, "Received signal %d.\n", signum); + + if (child > 0) { + kill(child, signum); + + if (debug) + fprintf(stderr, "Forwarded signal to child.\n"); + } +} + +int main(int argc, char *argv[]) { + int signum; + char* debug_env; + + if (argc < 2) { + fprintf(stderr, "Try providing some arguments.\n"); + return 1; + } + + debug_env = getenv("DUMB_INIT_DEBUG"); + if (debug_env && strcmp(debug_env, "1") == 0) + debug = 1; + + + /* register signal handlers */ + for (signum = 1; signum < 32; signum++) { + if (signum == SIGKILL || signum == SIGSTOP || signum == SIGCHLD) + continue; + + if (signal(signum, signal_handler) == SIG_ERR) { + fprintf(stderr, "Error: Couldn't register signal handler for signal `%d`. Exiting.\n", signum); + return 1; + } + } + + /* launch our process */ + child = fork(); + + if (child < 0) { + fprintf(stderr, "Unable to fork. Exiting.\n"); + return 1; + } + + if (child == 0) { + execvp(argv[1], &argv[1]); + } else { + if (debug) + fprintf(stderr, "Child spawned with PID %d.\n", child); + + waitpid(child, NULL, 0); + + if (debug) + fprintf(stderr, "Child exited, goodbye.\n"); + } + + return 0; +} From f3535cb93ea278421d184cbdfb3bad4202cb4725 Mon Sep 17 00:00:00 2001 From: Chris Kuehl Date: Tue, 4 Aug 2015 17:15:08 -0700 Subject: [PATCH 2/3] Add building in Docker --- Dockerfile | 10 ++++++++++ Makefile | 23 ++++++++++++++++++++--- debian/control | 2 +- 3 files changed, 31 insertions(+), 4 deletions(-) create mode 100644 Dockerfile diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..9fd9a2c --- /dev/null +++ b/Dockerfile @@ -0,0 +1,10 @@ +FROM debian:jessie + +MAINTAINER Chris Kuehl +RUN DEBIAN_FRONTEND=noninteractive apt-get update && \ + apt-get install -y --no-install-recommends \ + build-essential devscripts equivs && \ + apt-get clean +WORKDIR /mnt + +ENTRYPOINT mk-build-deps -i && make builddeb diff --git a/Makefile b/Makefile index eb4b37c..3d7e33a 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,23 @@ -.PHONY: build +.PHONY: build clean builddeb builddeb-docker itest_lucid itest_precise itest_trusty build: - gcc -static -Wall -Werror -o dumb-init dumb-init.c + $(CC) -static -Wall -Werror -o dumb-init dumb-init.c clean: - rm -rf dumb-init + rm -rf dumb-init dist + +builddeb: + rm -rf dist && mkdir -p dist + debuild -us -uc -b + mv ../dumb-init_*.deb dist + +builddeb-docker: docker-image + docker run -v $(PWD):/mnt dumb-init-build + +docker-image: + docker build -t dumb-init-build . + +itest_lucid: docker_build + +itest_precise: docker_build + +itest_trusty: docker_build diff --git a/debian/control b/debian/control index 793a137..78b9cf5 100644 --- a/debian/control +++ b/debian/control @@ -2,7 +2,7 @@ Source: dumb-init Section: utils Priority: extra Maintainer: Chris Kuehl -Build-Depends: debhelper (>= 7), gcc +Build-Depends: debhelper (>= 7), gcc, fakeroot Standards-Version: 3.9.6 Package: dumb-init From f8b142351b79f722d20a48157ce83d5499ff74e8 Mon Sep 17 00:00:00 2001 From: Chris Kuehl Date: Wed, 5 Aug 2015 10:56:55 -0700 Subject: [PATCH 3/3] Add tests --- .gitignore | 2 ++ Makefile | 35 ++++++++++++++++++++++++++++------- test/print-signals | 14 ++++++++++++++ test/test | 37 +++++++++++++++++++++++++++++++++++++ test/testlib.sh | 4 ++++ 5 files changed, 85 insertions(+), 7 deletions(-) create mode 100755 test/print-signals create mode 100755 test/test create mode 100644 test/testlib.sh diff --git a/.gitignore b/.gitignore index 1edd89c..ac5fca5 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ dumb-init +dist/ +*.deb diff --git a/Makefile b/Makefile index 3d7e33a..9e5040f 100644 --- a/Makefile +++ b/Makefile @@ -1,23 +1,44 @@ -.PHONY: build clean builddeb builddeb-docker itest_lucid itest_precise itest_trusty +DOCKER_TEST := sh -c 'dpkg -i /mnt/dist/*.deb && cd /mnt/test && ./test' + +.PHONY: build build: $(CC) -static -Wall -Werror -o dumb-init dumb-init.c +.PHONY: clean clean: - rm -rf dumb-init dist + rm -rf dumb-init dist/ *.deb +.PHONY: builddeb builddeb: - rm -rf dist && mkdir -p dist debuild -us -uc -b - mv ../dumb-init_*.deb dist + rm -rf dist && mkdir dist + mv ../dumb-init_*.deb dist/ +.PHONY: builddeb-docker builddeb-docker: docker-image docker run -v $(PWD):/mnt dumb-init-build +.PHONY: docker-image docker-image: docker build -t dumb-init-build . -itest_lucid: docker_build +.PHONY: itest itest_lucid itest_precise itest_trusty itest_wheezy itest_jessie itest_stretch +itest: itest_lucid itest_precise itest_trusty itest_wheezy itest_jessie itest_stretch -itest_precise: docker_build +itest_lucid: builddeb-docker + docker run -v $(PWD):/mnt:ro ubuntu:lucid $(DOCKER_TEST) -itest_trusty: docker_build +itest_precise: builddeb-docker + docker run -v $(PWD):/mnt:ro ubuntu:precise $(DOCKER_TEST) + +itest_trusty: builddeb-docker + docker run -v $(PWD):/mnt:ro ubuntu:trusty $(DOCKER_TEST) + +itest_wheezy: builddeb-docker + docker run -v $(PWD):/mnt:ro debian:wheezy $(DOCKER_TEST) + +itest_jessie: builddeb-docker + docker run -v $(PWD):/mnt:ro debian:jessie $(DOCKER_TEST) + +itest_stretch: builddeb-docker + docker run -v $(PWD):/mnt:ro debian:stretch $(DOCKER_TEST) diff --git a/test/print-signals b/test/print-signals new file mode 100755 index 0000000..682dfd7 --- /dev/null +++ b/test/print-signals @@ -0,0 +1,14 @@ +#!/bin/sh -eu +# Print received signals into a file, one per line +file="$1" + +. ./testlib.sh + +for i in $(catchable_signals); do + trap "echo $i > \"$file\"" "$i" +done + +echo 'ready' > "$file" + +# loop forever +while :; do true; done diff --git a/test/test b/test/test new file mode 100755 index 0000000..8f148b5 --- /dev/null +++ b/test/test @@ -0,0 +1,37 @@ +#!/bin/sh -eum +# Try sending all signals via dumb-init to our `print-signals` script, ensure +# they were all received. +. ./testlib.sh + +# The easiest way to communicate with the background process is with a FIFO. +# (piping spawns additional subshells and makes it hard to get the right PID) +fifo=$(mktemp -u) +mkfifo -m 600 "$fifo" +dumb-init ./print-signals "$fifo" & +pid="$!" + +# Wait for `print-signals` to indicate it's ready. +head -n1 "$fifo" > /dev/null + +for expected in $(catchable_signals); do + kill -s "$expected" "$pid" + echo -n "Sent signal ${expected}... " + received=$(timeout 1 head -n1 "$fifo") || { + echo + echo "Error: Didn't receive signal within 1 second." + exit 1 + } + + echo "received signal ${received}." + + if [ "$expected" -ne "$received" ]; then + echo "Error: Received signal $received, but expected signal $expected." + exit 1 + fi +done + +# Turn off job monitoring so we don't get a spurious "[1] + Killed" printed +set +m + +kill -9 "$pid" +rm "$fifo" diff --git a/test/testlib.sh b/test/testlib.sh new file mode 100644 index 0000000..f20d67a --- /dev/null +++ b/test/testlib.sh @@ -0,0 +1,4 @@ +catchable_signals() { + # We can't handle the signals SIGKILL=9, SIGCHLD=17, SIGSTOP=19 + seq 1 31 | grep -vE '^(9|17|19)$' +}