diff --git a/.travis.yml b/.travis.yml index d7598e6..4720717 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,10 +5,10 @@ language: generic env: matrix: - - CC=gcc ARCH_SUFFIX=amd64 ARCH_NATIVE=1 NO_ARGS= - - CC=arm-linux-gnueabihf-gcc ARCH_SUFFIX=armhf ARCH_NATIVE= NO_ARGS= - - CC=aarch64-linux-gnu-gcc ARCH_SUFFIX=arm64 ARCH_NATIVE= NO_ARGS= - - CC=gcc ARCH_SUFFIX=amd64 ARCH_NATIVE=1 NO_ARGS=1 + - CC=gcc ARCH_SUFFIX=amd64 ARCH_NATIVE=1 MINIMAL= + - CC=arm-linux-gnueabihf-gcc ARCH_SUFFIX=armhf ARCH_NATIVE= MINIMAL= + - CC=aarch64-linux-gnu-gcc ARCH_SUFFIX=arm64 ARCH_NATIVE= MINIMAL= + - CC=gcc ARCH_SUFFIX=amd64 ARCH_NATIVE=1 MINIMAL=1 global: - SIGN_BINARIES=1 - secure: "RKF9Z9gLxp6k/xITqn7ma1E9HfpYcDXuJFf4862WeH9EMnK9lDq+TWnGsQfkIlqh8h9goe7U+BvRiTibj9MiD5u7eluLo3dlwsLxPpYtyswYeLeC1wKKdT5LPGAXbRKomvBalRYMI+dDnGIM4w96mHgGGvx2zZXGkiAQhm6fJ3k=" @@ -31,4 +31,4 @@ deploy: on: repo: krallin/tini tags: true - condition: '-z "$NO_ARGS"' + condition: '-z "$MINIMAL"' diff --git a/CMakeLists.txt b/CMakeLists.txt index e664e1b..e16d8c2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,14 +3,14 @@ project (tini C) # Config set (tini_VERSION_MAJOR 0) -set (tini_VERSION_MINOR 12) +set (tini_VERSION_MINOR 13) set (tini_VERSION_PATCH 0) # Build options -option(NO_ARGS "Disable argument parsing" OFF) +option(MINIMAL "Disable argument parsing and verbose output" OFF) -if(NO_ARGS) - add_definitions(-DTINI_NO_ARGS=1) +if(MINIMAL) + add_definitions(-DTINI_MINIMAL=1) endif() # Extract git version and dirty-ness diff --git a/README.md b/README.md index ddbd4aa..d44a8b6 100644 --- a/README.md +++ b/README.md @@ -37,7 +37,7 @@ In Docker, you will want to use an entrypoint so you don't have to remember to manually invoke Tini: # Add Tini - ENV TINI_VERSION v0.12.0 + ENV TINI_VERSION v0.13.0 ADD https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini /tini RUN chmod +x /tini ENTRYPOINT ["/tini", "--"] @@ -63,7 +63,7 @@ The `tini` and `tini-static` binaries are signed using the key `595E85A6B1B4779E You can verify their signatures using `gpg` (which you may install using your package manager): - ENV TINI_VERSION v0.12.0 + ENV TINI_VERSION v0.13.0 ADD https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini /tini ADD https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini.asc /tini.asc RUN gpg --keyserver ha.pool.sks-keyservers.net --recv-keys 595E85A6B1B4779EA4DAAEC70B588DFF0527A9B7 \ @@ -214,18 +214,20 @@ Maintainer: Contributors: - + [Tianon Gravi][21] - + [David Wragg][22] + + [Tianon Gravi][30] + + [David Wragg][31] + + [Michael Crosby][32] Special thanks to: - + [Danilo Bürger][23] for packaging Tini for Alpine - + [Asko Soukka][24] for packaging Tini for Nix + + [Danilo Bürger][40] for packaging Tini for Alpine + + [Asko Soukka][41] for packaging Tini for Nix [10]: https://github.com/krallin/tini-images [20]: https://github.com/krallin/ - [21]: https://github.com/tianon - [22]: https://github.com/dpw - [23]: https://github.com/danilobuerger - [24]: https://github.com/datakurre + [30]: https://github.com/tianon + [31]: https://github.com/dpw + [32]: https://github.com/crosbymichael + [40]: https://github.com/danilobuerger + [41]: https://github.com/datakurre diff --git a/ci/run_build.sh b/ci/run_build.sh index 543aeea..728da5c 100755 --- a/ci/run_build.sh +++ b/ci/run_build.sh @@ -45,8 +45,8 @@ export PATH="${SOURCE_DIR}/ci/util:${PATH}" # Build CMAKE_ARGS=(-B"${BUILD_DIR}" -H"${SOURCE_DIR}") -if [[ -n "${NO_ARGS:-}" ]]; then - CMAKE_ARGS+=(-DNO_ARGS=ON) +if [[ -n "${MINIMAL:-}" ]]; then + CMAKE_ARGS+=(-DMINIMAL=ON) fi cmake "${CMAKE_ARGS[@]}" @@ -71,10 +71,21 @@ if [[ -n "${ARCH_NATIVE:=}" ]]; then # Smoke tests (actual tests need Docker to run; they don't run within the CI environment) for tini in "${BUILD_DIR}/tini" "${BUILD_DIR}/tini-static"; do + echo "Smoke test for ${tini}" + "$tini" --version + echo "Testing ${tini} --version" - "$tini" --version | grep "tini version" + "$tini" --version | grep -q "tini version" + + echo "Testing ${tini} without arguments exits with 1" + ! "$tini" 2>/dev/null - if [[ -n "${NO_ARGS:-}" ]]; then + echo "Testing ${tini} shows help message" + { + ! "$tini" 2>&1 + } | grep -q "supervision of a valid init process" + + if [[ -n "${MINIMAL:-}" ]]; then echo "Testing $tini with: true" "${tini}" true @@ -83,6 +94,11 @@ if [[ -n "${ARCH_NATIVE:=}" ]]; then exit 1 fi + echo "Testing ${tini} does not reference options that don't exist" + ! { + ! "$tini" 2>&1 + } | grep -q "more verbose" + # We try running binaries named after flags (both valid and invalid # flags) and test that they run. for flag in h s x; do @@ -94,15 +110,15 @@ if [[ -n "${ARCH_NATIVE:=}" ]]; then echo "Testing $tini can run binary --version if args are given" cp "$(which true)" "${BIN_TEST_DIR}/--version" - if "$tini" "--version" --foo | grep "tini version"; then + if "$tini" "--version" --foo | grep -q "tini version"; then exit 1 fi else - echo "Smoke test for $tini" + echo "Testing ${tini} -h" "${tini}" -h echo "Testing $tini for license" - "${tini}" -l | grep -i "mit license" + "${tini}" -l | grep -q -i "mit license" echo "Testing $tini with: true" "${tini}" -vvv true @@ -112,13 +128,35 @@ if [[ -n "${ARCH_NATIVE:=}" ]]; then exit 1 fi - # Test stdin / stdout are handed over to child - echo "Testing pipe" - echo "exit 0" | "${tini}" -vvv sh - if [[ ! "$?" -eq "0" ]]; then - echo "Pipe test failed" - exit 1 - fi + echo "Testing ${tini} references options that exist" + { + ! "$tini" 2>&1 + } | grep -q "more verbose" + fi + + echo "Testing ${tini} supports TINI_VERBOSITY" + TINI_VERBOSITY=3 "$tini" true 2>&1 | grep -q 'Received SIGCHLD' + + echo "Testing ${tini} exits with 127 if the command does not exist" + "$tini" foobar123 && rc="$?" || rc="$?" + if [[ "$rc" != 127 ]]; then + echo "Exit code was: ${rc}" + exit 1 + fi + + echo "Testing ${tini} exits with 126 if the command is not executable" + "$tini" /etc && rc="$?" || rc="$?" + if [[ "$rc" != 126 ]]; then + echo "Exit code was: ${rc}" + exit 1 + fi + + # Test stdin / stdout are handed over to child + echo "Testing ${tini} does not break pipes" + echo "exit 0" | "${tini}" sh + if [[ ! "$?" -eq "0" ]]; then + echo "Pipe test failed" + exit 1 fi echo "Checking hardening on $tini" diff --git a/ddist.sh b/ddist.sh index 52d9d4c..9370912 100755 --- a/ddist.sh +++ b/ddist.sh @@ -24,5 +24,5 @@ docker run -it --rm \ -e CC="${CC:=gcc}" \ -e ARCH_NATIVE="${ARCH_NATIVE-1}" \ -e ARCH_SUFFIX="${ARCH_SUFFIX-}" \ - -e NO_ARGS="${NO_ARGS-}" \ + -e MINIMAL="${MINIMAL-}" \ "${IMG}" "${SRC}/ci/run_build.sh" diff --git a/src/tini.c b/src/tini.c index fa86254..267a0de 100644 --- a/src/tini.c +++ b/src/tini.c @@ -17,11 +17,21 @@ #include "tiniConfig.h" #include "tiniLicense.h" +#if TINI_MINIMAL +#define PRINT_FATAL(...) fprintf(stderr, __VA_ARGS__); fprintf(stderr, "\n"); +#define PRINT_WARNING(...) if (verbosity > 0) { fprintf(stderr, __VA_ARGS__); fprintf(stderr, "\n"); } +#define PRINT_INFO(...) if (verbosity > 1) { fprintf(stdout, __VA_ARGS__); fprintf(stdout, "\n"); } +#define PRINT_DEBUG(...) if (verbosity > 2) { fprintf(stdout, __VA_ARGS__); fprintf(stdout, "\n"); } +#define PRINT_TRACE(...) if (verbosity > 3) { fprintf(stdout, __VA_ARGS__); fprintf(stdout, "\n"); } +#define DEFAULT_VERBOSITY 0 +#else #define PRINT_FATAL(...) fprintf(stderr, "[FATAL tini (%i)] ", getpid()); fprintf(stderr, __VA_ARGS__); fprintf(stderr, "\n"); #define PRINT_WARNING(...) if (verbosity > 0) { fprintf(stderr, "[WARN tini (%i)] ", getpid()); fprintf(stderr, __VA_ARGS__); fprintf(stderr, "\n"); } #define PRINT_INFO(...) if (verbosity > 1) { fprintf(stdout, "[INFO tini (%i)] ", getpid()); fprintf(stdout, __VA_ARGS__); fprintf(stdout, "\n"); } #define PRINT_DEBUG(...) if (verbosity > 2) { fprintf(stdout, "[DEBUG tini (%i)] ", getpid()); fprintf(stdout, __VA_ARGS__); fprintf(stdout, "\n"); } #define PRINT_TRACE(...) if (verbosity > 3) { fprintf(stdout, "[TRACE tini (%i)] ", getpid()); fprintf(stdout, __VA_ARGS__); fprintf(stdout, "\n"); } +#define DEFAULT_VERBOSITY 1 +#endif #define ARRAY_LEN(x) (sizeof(x) / sizeof(x[0])) @@ -31,6 +41,7 @@ typedef struct { struct sigaction* const sigttou_action_ptr; } signal_configuration_t; +static unsigned int verbosity = DEFAULT_VERBOSITY; #ifdef PR_SET_CHILD_SUBREAPER #define HAS_SUBREAPER 1 @@ -41,13 +52,14 @@ typedef struct { #define OPT_STRING "hvgl" #endif +#define VERBOSITY_ENV_VAR "TINI_VERBOSITY" + #define TINI_VERSION_STRING "tini version " TINI_VERSION TINI_GIT #if HAS_SUBREAPER static unsigned int subreaper = 0; #endif -static unsigned int verbosity = 1; static unsigned int kill_process_group = 0; static struct timespec ts = { .tv_sec = 1, .tv_nsec = 0 }; @@ -56,13 +68,16 @@ static const char reaper_warning[] = "Tini is not running as PID 1 " #if HAS_SUBREAPER "and isn't registered as a child subreaper" #endif - ".\n\ - Zombie processes will not be re-parented to Tini, so zombie reaping won't work.\n\ - To fix the problem, " +".\n\ +Zombie processes will not be re-parented to Tini, so zombie reaping won't work.\n\ +To fix the problem, " #if HAS_SUBREAPER - "use -s or set the environment variable " SUBREAPER_ENV_VAR " to register Tini as a child subreaper, or " +#ifndef TINI_MINIMAL +"use the -s option " +#endif +"or set the environment variable " SUBREAPER_ENV_VAR " to register Tini as a child subreaper, or " #endif - "run Tini as PID 1."; +"run Tini as PID 1."; int restore_signals(const signal_configuration_t* const sigconf_ptr) { if (sigprocmask(SIG_SETMASK, sigconf_ptr->sigmask_ptr, NULL)) { @@ -86,7 +101,7 @@ int restore_signals(const signal_configuration_t* const sigconf_ptr) { int isolate_child() { // Put the child into a new process group. if (setpgid(0, 0) < 0) { - PRINT_FATAL("setpgid failed: '%s'", strerror(errno)); + PRINT_FATAL("setpgid failed: %s", strerror(errno)); return 1; } @@ -102,7 +117,7 @@ int isolate_child() { if (errno == ENOTTY) { PRINT_DEBUG("tcsetpgrp failed: no tty (ok to proceed)") } else { - PRINT_FATAL("tcsetpgrp failed: '%s'", strerror(errno)); + PRINT_FATAL("tcsetpgrp failed: %s", strerror(errno)); return 1; } } @@ -118,7 +133,7 @@ int spawn(const signal_configuration_t* const sigconf_ptr, char* const argv[], i pid = fork(); if (pid < 0) { - PRINT_FATAL("Fork failed: '%s'", strerror(errno)); + PRINT_FATAL("fork failed: %s", strerror(errno)); return 1; } else if (pid == 0) { @@ -133,8 +148,21 @@ int spawn(const signal_configuration_t* const sigconf_ptr, char* const argv[], i } execvp(argv[0], argv); - PRINT_FATAL("Executing child process '%s' failed: '%s'", argv[0], strerror(errno)); - return 1; + + // execvp will only return on an error so make sure that we check the errno + // and exit with the correct return status for the error that we encountered + // See: http://www.tldp.org/LDP/abs/html/exitcodes.html#EXITCODESREF + int status = 1; + switch errno { + case ENOENT: + status = 127; + break; + case EACCES: + status = 126; + break; + } + PRINT_FATAL("exec %s failed: %s", argv[0], strerror(errno)); + return status; } else { // Parent PRINT_INFO("Spawned child process '%s' with pid '%i'", argv[0], pid); @@ -145,8 +173,20 @@ int spawn(const signal_configuration_t* const sigconf_ptr, char* const argv[], i void print_usage(char* const name, FILE* const file) { fprintf(file, "%s (%s)\n", basename(name), TINI_VERSION_STRING); - fprintf(file, "Usage: %s [OPTIONS] PROGRAM -- [ARGS]\n\n", basename(name)); + +#if TINI_MINIMAL + fprintf(file, "Usage: %s PROGRAM [ARGS] | --version\n\n", basename(name)); +#else + fprintf(file, "Usage: %s [OPTIONS] PROGRAM -- [ARGS] | --version\n\n", basename(name)); +#endif fprintf(file, "Execute a program under the supervision of a valid init process (%s)\n\n", basename(name)); + + fprintf(file, "Command line options:\n\n"); + + fprintf(file, " --version: Show version and exit.\n"); + +#if TINI_MINIMAL +#else 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"); @@ -154,6 +194,16 @@ void print_usage(char* const name, FILE* const file) { fprintf(file, " -v: Generate more verbose output. Repeat up to 3 times.\n"); fprintf(file, " -g: Send signals to the child's process group.\n"); fprintf(file, " -l: Show license and exit.\n"); +#endif + + fprintf(file, "\n"); + + fprintf(file, "Environment variables:\n\n"); +#if HAS_SUBREAPER + fprintf(file, " %s: Register as a process subreaper (requires Linux >= 3.4)\n", SUBREAPER_ENV_VAR); +#endif + fprintf(file, " %s: Set the verbosity level (default: %d)\n", VERBOSITY_ENV_VAR, DEFAULT_VERBOSITY); + fprintf(file, "\n"); } @@ -172,9 +222,7 @@ int parse_args(const int argc, char* const argv[], char* (**child_args_ptr_ptr)[ return 1; } -#if TINI_NO_ARGS - *parse_fail_exitcode_ptr = 0; -#else +#ifndef TINI_MINIMAL int c; while ((c = getopt(argc, argv, OPT_STRING)) != -1) { switch (c) { @@ -237,6 +285,12 @@ int parse_env() { subreaper++; } #endif + + char* env_verbosity = getenv(VERBOSITY_ENV_VAR); + if (env_verbosity != NULL) { + verbosity = atoi(env_verbosity); + } + return 0; } @@ -468,8 +522,9 @@ int main(int argc, char *argv[]) { reaper_check(); /* Go on */ - if (spawn(&child_sigconf, *child_args_ptr, &child_pid)) { - return 1; + int spawn_ret = spawn(&child_sigconf, *child_args_ptr, &child_pid); + if (spawn_ret) { + return spawn_ret; } free(child_args_ptr); diff --git a/test/run_inner_tests.py b/test/run_inner_tests.py index 3cbc66b..e79338e 100755 --- a/test/run_inner_tests.py +++ b/test/run_inner_tests.py @@ -29,7 +29,7 @@ def main(): src = os.environ["SOURCE_DIR"] build = os.environ["BUILD_DIR"] - args_disabled = os.environ.get("NO_ARGS") + args_disabled = os.environ.get("MINIMAL") proxy = os.path.join(src, "test", "subreaper-proxy.py") tini = os.path.join(build, "tini") @@ -80,10 +80,14 @@ def main(): p.send_signal(signal.SIGUSR1) busy_wait(lambda: p.poll() is not None, 10) - - # Run failing test + # Run failing test. Force verbosity to 1 so we see the subreaper warning + # regardless of whether MINIMAL is set. print "Running zombie reaping failure test (Tini should warn)" - p = subprocess.Popen([tini, os.path.join(src, "test", "reaping", "stage_1.py")], stdout=subprocess.PIPE, stderr=subprocess.PIPE) + p = subprocess.Popen( + [tini, os.path.join(src, "test", "reaping", "stage_1.py")], + stdout=subprocess.PIPE, stderr=subprocess.PIPE, + env={'TINI_VERBOSITY': '1'} + ) out, err = p.communicate() assert "zombie reaping won't work" in err, "No warning message was output!" ret = p.wait() diff --git a/test/run_outer_tests.py b/test/run_outer_tests.py index 176bca7..338c44d 100755 --- a/test/run_outer_tests.py +++ b/test/run_outer_tests.py @@ -65,15 +65,19 @@ def target(): def attach_and_type_exit_0(name): + print "Attaching to {0} to exit 0".format(name) p = pexpect.spawn("docker attach {0}".format(name)) p.sendline('') p.sendline('exit 0') + p.close() def attach_and_issue_ctrl_c(name): + print "Attaching to {0} to CTRL+C".format(name) p = pexpect.spawn("docker attach {0}".format(name)) p.expect_exact('#') p.sendintr() + p.close() def test_tty_handling(img, name, base_cmd, fail_cmd, container_command, exit_function, expect_exit_code): @@ -83,7 +87,11 @@ def test_tty_handling(img, name, base_cmd, fail_cmd, container_command, exit_fun shell_ready_event = threading.Event() def spawn(): - p = pexpect.spawn(" ".join(base_cmd + ["--tty", "--interactive", img, "/tini/dist/tini", "-vvv", "--", container_command])) + cmd = base_cmd + ["--tty", "--interactive", img, "/tini/dist/tini"] + if os.environ.get("MINIMAL") is None: + cmd.append("--") + cmd.append(container_command) + p = pexpect.spawn(" ".join(cmd)) p.expect_exact("#") shell_ready_event.set() rc.value = p.wait() @@ -112,6 +120,7 @@ def spawn(): def main(): img = sys.argv[1] name = "{0}-test".format(img) + args_disabled = os.environ.get("MINIMAL") root = os.path.abspath(os.path.join(os.path.dirname(__file__), "..")) @@ -129,8 +138,8 @@ def main(): for entrypoint in ["/tini/dist/tini", "/tini/dist/tini-static"]: functional_base_cmd = base_cmd + [ "--entrypoint={0}".format(entrypoint), + "-e", "TINI_VERBOSITY=3", img, - "-vvv", ] # Reaping test @@ -139,20 +148,24 @@ def main(): # Signals test for sig, retcode in [("INT", 1), ("TERM", 143)]: Command( - functional_base_cmd + ["--", "/tini/test/signals/test.py"], + functional_base_cmd + ["/tini/test/signals/test.py"], fail_cmd, ["docker", "kill", "-s", sig, name], 2 ).run(timeout=10, retcode=retcode) # Exit code test - Command(functional_base_cmd + ["-z"], fail_cmd).run(retcode=1) - Command(functional_base_cmd + ["--", "zzzz"], fail_cmd).run(retcode=1) - Command(functional_base_cmd + ["-h"], fail_cmd).run() + Command(functional_base_cmd + ["-z"], fail_cmd).run(retcode=127 if args_disabled else 1) + Command(functional_base_cmd + ["-h"], fail_cmd).run(retcode=127 if args_disabled else 0) + Command(functional_base_cmd + ["zzzz"], fail_cmd).run(retcode=127) # Valgrind test (we only run this on the dynamic version, because otherwise Valgrind may bring up plenty of errors that are # actually from libc) - Command(functional_base_cmd + ["--", "valgrind", "--leak-check=full", "--error-exitcode=1", "/tini/dist/tini", "-v", "--", "ls"], fail_cmd).run() + Command(base_cmd + [img, "valgrind", "--leak-check=full", "--error-exitcode=1", "/tini/dist/tini", "ls"], fail_cmd).run() + + # Test tty handling + test_tty_handling(img, name, base_cmd, fail_cmd, "dash", attach_and_type_exit_0, 0) + test_tty_handling(img, name, base_cmd, fail_cmd, "dash -c 'while true; do echo \#; sleep 0.1; done'", attach_and_issue_ctrl_c, 128 + signal.SIGINT) # Installation tests (sh -c is used for globbing and &&) for image, pkg_manager, extension in [ @@ -164,10 +177,5 @@ def main(): Command(base_cmd + [image, "sh", "-c", "{0} -i /tini/dist/*.{1} && /usr/bin/tini true".format(pkg_manager, extension)], fail_cmd).run() - # Test tty handling - test_tty_handling(img, name, base_cmd, fail_cmd, "dash", attach_and_type_exit_0, 0) - test_tty_handling(img, name, base_cmd, fail_cmd, "dash -c 'while true; do echo \#; sleep 0.1; done'", attach_and_issue_ctrl_c, 128 + signal.SIGINT) - - if __name__ == "__main__": main() diff --git a/tpl/README.md.in b/tpl/README.md.in index 4942cf6..75ec15c 100644 --- a/tpl/README.md.in +++ b/tpl/README.md.in @@ -214,18 +214,20 @@ Maintainer: Contributors: - + [Tianon Gravi][21] - + [David Wragg][22] + + [Tianon Gravi][30] + + [David Wragg][31] + + [Michael Crosby][32] Special thanks to: - + [Danilo Bürger][23] for packaging Tini for Alpine - + [Asko Soukka][24] for packaging Tini for Nix + + [Danilo Bürger][40] for packaging Tini for Alpine + + [Asko Soukka][41] for packaging Tini for Nix [10]: https://github.com/krallin/tini-images [20]: https://github.com/krallin/ - [21]: https://github.com/tianon - [22]: https://github.com/dpw - [23]: https://github.com/danilobuerger - [24]: https://github.com/datakurre + [30]: https://github.com/tianon + [31]: https://github.com/dpw + [32]: https://github.com/crosbymichael + [40]: https://github.com/danilobuerger + [41]: https://github.com/datakurre