diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 173d139..1b9125c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -19,7 +19,8 @@ of that after merging! The process to release a new version is: -1. Update the version in `setup.py` +1. Update the version in `VERSION` +2. Run `make VERSION.h` 2. Update the Debian changelog with `dch -v {new version}`. 3. Update the `wget` url in the README to point to the new version. 4. Commit the changes and tag the commit like `v1.0.0`. diff --git a/MANIFEST.in b/MANIFEST.in index 30a7054..2c9dd09 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1 +1,3 @@ include dumb-init.c +include VERSION +include VERSION.h diff --git a/Makefile b/Makefile index a535518..1fc329b 100644 --- a/Makefile +++ b/Makefile @@ -48,9 +48,14 @@ DOCKER_TOX_TEST := sh -uexc ' \ && tox \ ' .PHONY: build -build: +build: VERSION.h $(CC) $(CFLAGS) -o dumb-init dumb-init.c +VERSION.h: VERSION + echo '// THIS FILE IS AUTOMATICALLY GENERATED' > VERSION.h + echo '// Run `make VERSION.h` to update it after modifying VERSION.' >> VERSION.h + xxd -i VERSION >> VERSION.h + .PHONY: clean clean: clean-tox rm -rf dumb-init dist/ *.deb diff --git a/VERSION b/VERSION new file mode 100644 index 0000000..1d0ba9e --- /dev/null +++ b/VERSION @@ -0,0 +1 @@ +0.4.0 diff --git a/VERSION.h b/VERSION.h new file mode 100644 index 0000000..5fdfb08 --- /dev/null +++ b/VERSION.h @@ -0,0 +1,6 @@ +// THIS FILE IS AUTOMATICALLY GENERATED +// Run `make VERSION.h` to update it after modifying VERSION. +unsigned char VERSION[] = { + 0x30, 0x2e, 0x34, 0x2e, 0x30, 0x0a +}; +unsigned int VERSION_len = 6; diff --git a/dumb-init.c b/dumb-init.c index b270eb1..f734eb7 100644 --- a/dumb-init.c +++ b/dumb-init.c @@ -5,11 +5,12 @@ * Usage: * ./dumb-init python -c 'while True: pass' * - * To get debug output on stderr, run with DUMB_INIT_DEBUG=1 + * To get debug output on stderr, run with '-v'. */ #include #include +#include #include #include #include @@ -17,6 +18,7 @@ #include #include #include +#include "VERSION.h" #define PRINTERR(...) do { \ fprintf(stderr, "[dumb-init] " __VA_ARGS__); \ @@ -99,50 +101,72 @@ void handle_signal(int signum) { void print_help(char *argv[]) { fprintf(stderr, - "Usage: %s COMMAND [[ARG] ...]\n" + "dumb-init v%s" + "Usage: %s [option] command [[arg] ...]\n" "\n" - "dumb-init is a simple process designed to run as PID 1 inside Docker\n" - "containers and proxy signals to child processes.\n" + "dumb-init is a simple process supervisor that forwards signals to children.\n" + "It is designed to run as PID1 in minimal container environments.\n" "\n" - "Docker runs your processes as PID1. The kernel doesn't apply default signal\n" - "handling to PID1 processes, so if your process doesn't register a custom\n" - "signal handler, signals like TERM will just bounce off your process.\n" + "Optional arguments:\n" + " -c, --single-child Run in single-child mode.\n" + " In this mode, signals are only proxies to the\n" + " direct child and not any of its ancestors.\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" - "This can result in cases where sending signals to a `docker run` process\n" - "results in the run process exiting, but the container continuing in the\n" - "background.\n" - "\n" - "A workaround is to wrap your script in this proxy, which runs as PID1. Your\n" - "process then runs as some other PID, and the kernel won't treat the signals\n" - "that are proxied to them specially.\n" - "\n" - "The proxy dies when your process dies, so it must not double-fork or do other\n" - "weird things (this is basically a requirement for doing things sanely in\n" - "Docker anyway).\n" - "\n" - "By default, dumb-init starts a process group (and session, see: man 2 setsid)\n" - "and signals all processes in it. This is usually useful behavior, but if for\n" - "some reason you wish to disable it, run with DUMB_INIT_SETSID=0.\n", + "Full help is available online at https://github.com/Yelp/dumb-init\n", + VERSION, argv[0] ); } int main(int argc, char *argv[]) { - int signum; - char *debug_env, *setsid_env; + int signum, opt; - if (argc < 2) { - print_help(argv); - return 1; + struct option long_options[] = { + {"help", no_argument, NULL, 'h'}, + {"single-child", no_argument, NULL, 'c'}, + {"verbose", no_argument, NULL, 'v'}, + {"version", no_argument, NULL, 'V'}, + }; + while ((opt = getopt_long(argc, argv, "+hvVc", long_options, NULL)) != -1) { + switch (opt) { + case 'h': + print_help(argv); + return 0; + case 'v': + debug = 1; + break; + case 'V': + fprintf(stderr, "dumb-init v%s", VERSION); + return 0; + case 'c': + use_setsid = 0; + break; + default: + return 1; + } } - debug_env = getenv("DUMB_INIT_DEBUG"); + if (optind >= argc) { + fprintf( + stderr, + "Usage: %s [option] program [args]\n" + "Try %s --help for full usage.\n", + argv[0], argv[0] + ); + return 1; + } + char **cmd = &argv[optind]; + + char *debug_env = getenv("DUMB_INIT_DEBUG"); if (debug_env && strcmp(debug_env, "1") == 0) { debug = 1; DEBUG("Running in debug mode.\n"); } - setsid_env = getenv("DUMB_INIT_SETSID"); + char *setsid_env = getenv("DUMB_INIT_SETSID"); if (setsid_env && strcmp(setsid_env, "0") == 0) { use_setsid = 0; DEBUG("Not running in setsid mode.\n"); @@ -181,7 +205,7 @@ int main(int argc, char *argv[]) { DEBUG("setsid complete.\n"); } - execvp(argv[1], &argv[1]); + execvp(cmd[0], &cmd[0]); // if this point is reached, exec failed, so we should exit nonzero PRINTERR("%s: %s\n", argv[1], strerror(errno)); diff --git a/setup.py b/setup.py index 0193048..7222aa5 100644 --- a/setup.py +++ b/setup.py @@ -87,10 +87,9 @@ class build_cexe(Command): setup( name='dumb-init', description='Simple wrapper script which proxies signals to a child', - version='0.4.0', + version=open('VERSION').read().strip(), author='Yelp', platforms='linux', - c_executables=[ Extension( 'dumb-init', diff --git a/tests/help_message_test.py b/tests/help_message_test.py index 55850b9..bccf982 100644 --- a/tests/help_message_test.py +++ b/tests/help_message_test.py @@ -1,6 +1,8 @@ from subprocess import PIPE from subprocess import Popen +import pytest + def test_exit_status(both_debug_modes, both_setsid_modes): """dumb-init should say something useful when called with no arguments, and @@ -9,4 +11,29 @@ def test_exit_status(both_debug_modes, both_setsid_modes): proc = Popen(('dumb-init'), stderr=PIPE) _, stderr = proc.communicate() assert proc.returncode != 0 + assert stderr == ( + b'Usage: dumb-init [option] program [args]\n' + b'Try dumb-init --help for full usage.\n' + ) + + +@pytest.mark.parametrize('flag', ['-h', '--help']) +def test_help_message(flag, both_debug_modes, both_setsid_modes): + """dumb-init should say something useful when called with the help flag, + and exit zero. + """ + proc = Popen(('dumb-init', flag), stderr=PIPE) + _, stderr = proc.communicate() + assert proc.returncode == 0 assert len(stderr) >= 50 + + +@pytest.mark.parametrize('flag', ['-V', '--version']) +def test_version_message(flag, both_debug_modes, both_setsid_modes): + """dumb-init should print its version when asked to.""" + version = open('VERSION', 'rb').read() + + proc = Popen(('dumb-init', flag), stderr=PIPE) + _, stderr = proc.communicate() + assert proc.returncode == 0 + assert stderr == b'dumb-init v' + version