pax_global_header00006660000000000000000000000064132665643340014526gustar00rootroot0000000000000052 comment=fec3683b971d9c3ef73f284f176672c44b448662 tini-0.18.0/000077500000000000000000000000001326656433400125575ustar00rootroot00000000000000tini-0.18.0/.dockerignore000066400000000000000000000000071326656433400152300ustar00rootroot00000000000000./dist tini-0.18.0/.gitignore000066400000000000000000000000231326656433400145420ustar00rootroot00000000000000dist sign.key .env tini-0.18.0/.travis.yml000066400000000000000000000030471326656433400146740ustar00rootroot00000000000000sudo: required services: - docker language: generic env: matrix: - ARCH_SUFFIX= CC=gcc ARCH_NATIVE=1 MINIMAL= - ARCH_SUFFIX=amd64 CC=gcc ARCH_NATIVE=1 MINIMAL= - ARCH_SUFFIX=amd64 CC=gcc ARCH_NATIVE=1 MINIMAL=1 - ARCH_SUFFIX=arm64 CC=aarch64-linux-gnu-gcc ARCH_NATIVE= MINIMAL= - ARCH_SUFFIX=armel CC=arm-linux-gnueabi-gcc ARCH_NATIVE= MINIMAL= - ARCH_SUFFIX=armhf CC=arm-linux-gnueabihf-gcc ARCH_NATIVE= MINIMAL= - ARCH_SUFFIX=i386 CFLAGS="-m32" ARCH_NATIVE= MINIMAL= - ARCH_SUFFIX=muslc-amd64 CC=musl-gcc ARCH_NATIVE=1 MINIMAL= - ARCH_SUFFIX=ppc64el CC=powerpc64le-linux-gnu-gcc ARCH_NATIVE= MINIMAL= - ARCH_SUFFIX=s390x CC=s390x-linux-gnu-gcc ARCH_NATIVE= MINIMAL= global: - secure: "RKF9Z9gLxp6k/xITqn7ma1E9HfpYcDXuJFf4862WeH9EMnK9lDq+TWnGsQfkIlqh8h9goe7U+BvRiTibj9MiD5u7eluLo3dlwsLxPpYtyswYeLeC1wKKdT5LPGAXbRKomvBalRYMI+dDnGIM4w96mHgGGvx2zZXGkiAQhm6fJ3k=" - DIST_DIR="${PWD}/dist" before_install: - openssl aes-256-cbc -K $encrypted_2893fd5649e7_key -iv $encrypted_2893fd5649e7_iv -in sign.key.enc -out sign.key -d || echo "Encrypted signing key unavailable" script: - ./ddist.sh "$ARCH_SUFFIX" - ls -lah "$DIST_DIR" - git diff --exit-code deploy: provider: releases api_key: secure: Yk90ANpSPv1iJy8QDXCPwfaSmEr/WIJ3bzhQ6X8JvZjfrwTosbh0HrUzQyeac3nyvNwj7YJRssolOFc21IBKPpCFTZqYxSkuLPU6ysG4HGHgN6YJhOMm4mG4KKJ6741q3DJendhZpalBhCEi+NcZK/PCSD97Vl4OqRjBUged0fs= file: "${DIST_DIR}/*" file_glob: true skip_cleanup: true on: repo: krallin/tini tags: true condition: '-z "$MINIMAL"' tini-0.18.0/CMakeLists.txt000066400000000000000000000065431326656433400153270ustar00rootroot00000000000000cmake_minimum_required (VERSION 2.8.0) project (tini C) # Config set (tini_VERSION_MAJOR 0) set (tini_VERSION_MINOR 18) set (tini_VERSION_PATCH 0) # Build options option(MINIMAL "Disable argument parsing and verbose output" OFF) if(MINIMAL) add_definitions(-DTINI_MINIMAL=1) endif() # Extract git version and dirty-ness execute_process ( COMMAND git --git-dir "${PROJECT_SOURCE_DIR}/.git" --work-tree "${PROJECT_SOURCE_DIR}" log -n 1 --date=local --pretty=format:%h WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}" RESULT_VARIABLE git_version_check_ret OUTPUT_VARIABLE tini_VERSION_GIT ) execute_process( COMMAND git --git-dir "${PROJECT_SOURCE_DIR}/.git" --work-tree "${PROJECT_SOURCE_DIR}" status --porcelain --untracked-files=no WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}" OUTPUT_VARIABLE git_dirty_check_out ) if("${git_version_check_ret}" EQUAL 0) set(tini_VERSION_GIT " - git.${tini_VERSION_GIT}") if(NOT "${git_dirty_check_out}" STREQUAL "") set(tini_VERSION_GIT "${tini_VERSION_GIT}-dirty") endif() else() set(tini_VERSION_GIT "") endif() # Flags include(CheckCSourceCompiles) check_c_source_compiles(" #ifndef _FORTIFY_SOURCE #error \"Not defined: _FORTIFY_SOURCE\" #endif int main(void) { return 0; } " HAS_BUILTIN_FORTIFY) # Flags if(NOT HAS_BUILTIN_FORTIFY) add_definitions(-D_FORTIFY_SOURCE=2) endif() set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=gnu99 -Werror -Wextra -Wall -pedantic-errors -O2 -fstack-protector --param=ssp-buffer-size=4 -Wformat") set (CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,-Bsymbolic-functions -Wl,-z,relro -Wl,-s") # Build configure_file ( "${PROJECT_SOURCE_DIR}/src/tiniConfig.h.in" "${PROJECT_BINARY_DIR}/tiniConfig.h" @ONLY ) configure_file ( "${PROJECT_SOURCE_DIR}/tpl/README.md.in" "${PROJECT_SOURCE_DIR}/README.md" @ONLY ) configure_file ( "${PROJECT_SOURCE_DIR}/tpl/VERSION.in" "${PROJECT_BINARY_DIR}/VERSION" @ONLY ) include_directories ("${PROJECT_BINARY_DIR}") add_executable (tini src/tini.c) add_executable (tini-static src/tini.c) set_target_properties (tini-static PROPERTIES LINK_FLAGS "-Wl,--no-export-dynamic -static") # Installation install (TARGETS tini DESTINATION bin) install (TARGETS tini-static DESTINATION bin) # Packaging include (InstallRequiredSystemLibraries) set (CPACK_PACKAGE_DESCRIPTION_SUMMARY "A tiny but valid init process for containers") set (CPACK_PACKAGE_VENDOR "Thomas Orozco") set (CPACK_PACKAGE_CONTACT "thomas@orozco.fr") set (CPACK_PACKAGE_DESCRIPTION_FILE "${CMAKE_CURRENT_SOURCE_DIR}/README.md") set (CPACK_RESOURCE_FILE_LICENSE "${CMAKE_CURRENT_SOURCE_DIR}/LICENSE") set (CPACK_PACKAGE_VERSION_MAJOR "${tini_VERSION_MAJOR}") set (CPACK_PACKAGE_VERSION_MINOR "${tini_VERSION_MINOR}") set (CPACK_PACKAGE_VERSION_PATCH "${tini_VERSION_PATCH}") set (CPACK_PACKAGE_EXECUTABLES "${CMAKE_PROJECT_NAME}") set (CPACK_PACKAGE_NAME "${CMAKE_PROJECT_NAME}") set (CPACK_PACKAGE_FILE_NAME "${CMAKE_PROJECT_NAME}_${tini_VERSION_MAJOR}.${tini_VERSION_MINOR}.${tini_VERSION_PATCH}") set (CPACK_PACKAGE_VERSION "${tini_VERSION_MAJOR}.${tini_VERSION_MINOR}.${tini_VERSION_PATCH}") set (CPACK_DEBIAN_PACKAGE_ARCHITECTURE "amd64") # TODO set (CPACK_DEBIAN_PACKAGE_DEPENDS "libc6 (>= 2.3.4)") set (CPACK_RPM_PACKAGE_ARCHITECTURE "x86_64") set (CPACK_GENERATOR "DEB" "RPM") include (CPack) tini-0.18.0/Dockerfile000066400000000000000000000006111326656433400145470ustar00rootroot00000000000000FROM ubuntu:xenial ARG ARCH_SUFFIX COPY ci/install_deps.sh /install_deps.sh RUN /install_deps.sh # Pre-install those here for faster local builds. RUN CFLAGS="-DPR_SET_CHILD_SUBREAPER=36 -DPR_GET_CHILD_SUBREAPER=37" pip install psutil python-prctl bitmap ARG ARCH_NATIVE ARG CC # Persist ARGs into the image ENV ARCH_SUFFIX="$ARCH_SUFFIX" \ ARCH_NATIVE="$ARCH_NATIVE" \ CC="$CC" tini-0.18.0/LICENSE000066400000000000000000000021131326656433400135610ustar00rootroot00000000000000The MIT License (MIT) Copyright (c) 2015 Thomas Orozco Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. tini-0.18.0/README.md000066400000000000000000000212341326656433400140400ustar00rootroot00000000000000 Tini - A tiny but valid `init` for containers ============================================= [![Build Status](https://travis-ci.org/krallin/tini.svg?branch=master)](https://travis-ci.org/krallin/tini) Tini is the simplest `init` you could think of. All Tini does is spawn a single child (Tini is meant to be run in a container), and wait for it to exit all the while reaping zombies and performing signal forwarding. Why Tini? --------- Using Tini has several benefits: - It protects you from software that accidentally creates zombie processes, which can (over time!) starve your entire system for PIDs (and make it unusable). - It ensures that the *default signal handlers* work for the software you run in your Docker image. For example, with Tini, `SIGTERM` properly terminates your process even if you didn't explicitly install a signal handler for it. - It does so completely transparently! Docker images that work without Tini will work with Tini without any changes. If you'd like more detail on why this is useful, review this issue discussion: [What is advantage of Tini?][0]. Using Tini ---------- *NOTE: If you are using Docker 1.13 or greater, Tini is included in Docker itself. This includes all versions of Docker CE. To enable Tini, just [pass the `--init` flag to `docker run`][5].* *NOTE: There are [pre-built Docker images available for Tini][10]. If you're currently using an Ubuntu or CentOS image as your base, you can use one of those as a drop-in replacement.* *NOTE: There are Tini packages for Alpine Linux and NixOS. See below for installation instructions.* Add Tini to your container, and make it executable. Then, just invoke Tini and pass your program and its arguments as arguments to Tini. 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.18.0 ADD https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini /tini RUN chmod +x /tini ENTRYPOINT ["/tini", "--"] # Run your program under Tini CMD ["/your/program", "-and", "-its", "arguments"] # or docker run your-image /your/program ... Note that you *can* skip the `--` under certain conditions, but you might as well always include it to be safe. If you see an error message that looks like `tini: invalid option -- 'c'`, then you *need* to add the `--`. Arguments for Tini itself should be passed like `-v` in the following example: `/tini -v -- /your/program`. *NOTE: The binary linked above is a 64-bit dynamically-linked binary.* ### Signed binaries ### The `tini` and `tini-static` binaries are signed using the key `595E85A6B1B4779EA4DAAEC70B588DFF0527A9B7`. You can verify their signatures using `gpg` (which you may install using your package manager): ENV TINI_VERSION v0.18.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 hkp://p80.pool.sks-keyservers.net:80 --recv-keys 595E85A6B1B4779EA4DAAEC70B588DFF0527A9B7 \ && gpg --verify /tini.asc ### Alpine Linux Package ### On Alpine Linux, you can use the following command to install Tini: RUN apk add --no-cache tini # Tini is now available at /sbin/tini ENTRYPOINT ["/sbin/tini", "--"] ### NixOS ### Using Nix, you can use the following command to install Tini: nix-env --install tini ### Other Platforms ### ARM and 32-bit binaries are available! You can find the complete list of available binaries under [the releases tab][11]. Options ------- ### Verbosity ### The `-v` argument can be used for extra verbose output (you can pass it up to 3 times, e.g. `-vvv`). ### Subreaping ### By default, Tini needs to run as PID 1 so that it can reap zombies (by running as PID 1, zombies get re-parented to Tini). If for some reason, you cannot run Tini as PID 1, you should register Tini as a process subreaper instead (only in Linux >= 3.4), by either: + Passing the `-s` argument to Tini (`tini -s -- ...`) + Setting the environment variable `TINI_SUBREAPER` (e.g. `export TINI_SUBREAPER=`). This will ensure that zombies get re-parented to Tini despite Tini not running as PID 1. *NOTE: Tini will issue a warning if it detects that it isn't running as PID 1 and isn't registered as a subreaper. If you don't see a warning, you're fine.* ### Remapping exit codes ### Tini will reuse the child's exit code when exiting, but occasionally, this may not be exactly what you want (e.g. if your child exits with 143 after receiving SIGTERM). Notably, this can be an issue with Java apps. In this case, you can use the `-e` flag to remap an arbitrary exit code to 0. You can pass the flag multiple times if needed. For example: ``` tini -e 143 -- ... ``` ### Process group killing ### By default, Tini only kills its immediate child process. This can be inconvenient if sending a signal to that process does not have the desired effect. For example, if you do docker run krallin/ubuntu-tini sh -c 'sleep 10' and ctrl-C it, nothing happens: SIGINT is sent to the 'sh' process, but that shell won't react to it while it is waiting for the 'sleep' to finish. With the `-g` option, Tini kills the child process group , so that every process in the group gets the signal. This corresponds more closely to what happens when you do ctrl-C etc. in a terminal: The signal is sent to the foreground process group. ### Parent Death Signal ### Tini can set its parent death signal, which is the signal Tini should receive when *its* parent exits. To set the parent death signal, use the `-p` flag with the name of the signal Tini should receive when its parent exits: ``` tini -p SIGTERM -- ... ``` *NOTE: See [this PR discussion][12] to learn more about the parent death signal and use cases.* More ---- ### Existing Entrypoint ### Tini can also be used with an existing entrypoint in your container! Assuming your entrypoint was `/docker-entrypoint.sh`, then you would use: ENTRYPOINT ["/tini", "--", "/docker-entrypoint.sh"] ### Statically-Linked Version ### Tini has very few dependencies (it only depends on libc), but if your container fails to start, you might want to consider using the statically-built version instead: ADD https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini-static /tini ### Size Considerations ### Tini is a very small file (in the 10KB range), so it doesn't add much weight to your container. The statically-linked version is bigger, but still < 1M. Building Tini ------------- If you'd rather not download the binary, you can build Tini by running `cmake . && make`. Before building, you probably also want to run: export CFLAGS="-DPR_SET_CHILD_SUBREAPER=36 -DPR_GET_CHILD_SUBREAPER=37" This ensure that even if you're building on a system that has old Linux Kernel headers (< 3.4), Tini will be built with child subreaper support. This is usually what you want if you're going to use Tini with Docker (if your host Kernel supports Docker, it should also support child subreapers). Understanding Tini ------------------ After spawning your process, Tini will wait for signals and forward those to the child process, and periodically reap zombie processes that may be created within your container. When the "first" child process exits (`/your/program` in the examples above), Tini exits as well, with the exit code of the child process (so you can check your container's exit code to know whether the child exited successfully). Debugging --------- If something isn't working just like you expect, consider increasing the verbosity level (up to 3): tini -v -- bash -c 'exit 1' tini -vv -- true tini -vvv -- pwd Authors ======= Maintainer: + [Thomas Orozco][20] Contributors: + [Tianon Gravi][30] + [David Wragg][31] + [Michael Crosby][32] + [Wyatt Preul][33] + [Patrick Steinhardt][34] Special thanks to: + [Danilo Bürger][40] for packaging Tini for Alpine + [Asko Soukka][41] for packaging Tini for Nix [0]: https://github.com/krallin/tini/issues/8 [5]: https://docs.docker.com/engine/reference/commandline/run/ [10]: https://github.com/krallin/tini-images [11]: https://github.com/krallin/tini/releases [12]: https://github.com/krallin/tini/pull/114 [20]: https://github.com/krallin/ [30]: https://github.com/tianon [31]: https://github.com/dpw [32]: https://github.com/crosbymichael [33]: https://github.com/geek [34]: https://github.com/pks-t [40]: https://github.com/danilobuerger [41]: https://github.com/datakurre tini-0.18.0/ci/000077500000000000000000000000001326656433400131525ustar00rootroot00000000000000tini-0.18.0/ci/install_deps.sh000077500000000000000000000017251326656433400161770ustar00rootroot00000000000000#!/bin/bash set -o errexit set -o nounset set -o xtrace DEPS=( build-essential git gdb valgrind cmake rpm file libcap-dev python-dev python-pip python-setuptools hardening-includes gnupg ) case "${ARCH_SUFFIX-}" in amd64|'') ;; arm64) DEPS+=(gcc-aarch64-linux-gnu binutils-aarch64-linux-gnu libc6-dev-arm64-cross) ;; armel) DEPS+=(gcc-arm-linux-gnueabi binutils-arm-linux-gnueabi libc6-dev-armel-cross) ;; armhf) DEPS+=(gcc-arm-linux-gnueabihf binutils-arm-linux-gnueabihf libc6-dev-armhf-cross) ;; i386) DEPS+=(libc6-dev-i386 gcc-multilib) ;; muslc-amd64) DEPS+=(musl-tools) ;; ppc64el) DEPS+=(gcc-powerpc64le-linux-gnu binutils-powerpc64le-linux-gnu libc6-dev-ppc64el-cross) ;; s390x) DEPS+=(gcc-s390x-linux-gnu binutils-s390x-linux-gnu libc6-dev-s390x-cross) ;; *) echo "Unknown ARCH_SUFFIX=${ARCH_SUFFIX-}"; exit 1 ;; esac apt-get update apt-get install --no-install-recommends --yes "${DEPS[@]}" rm -rf /var/lib/apt/lists/* pip install virtualenv tini-0.18.0/ci/run_build.sh000077500000000000000000000173411326656433400155020ustar00rootroot00000000000000#!/bin/bash # Should be run from the root dir, or SOURCE_DIR should be set. set -o errexit set -o nounset set -o pipefail # Default compiler : ${CC:="gcc"} # Paths : ${SOURCE_DIR:="."} : ${DIST_DIR:="${SOURCE_DIR}/dist"} : ${BUILD_DIR:="/tmp/build"} # GPG Configuration : ${GPG_PASSPHRASE:=""} # Make those paths absolute, and export them for the Python tests to consume. export SOURCE_DIR="$(readlink -f "${SOURCE_DIR}")" export DIST_DIR="$(readlink -f "${DIST_DIR}")" export BUILD_DIR="$(readlink -f "${BUILD_DIR}")" # Configuration : ${FORCE_SUBREAPER:="1"} export FORCE_SUBREAPER # 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 CFLAGS="${CFLAGS-} -DPR_SET_CHILD_SUBREAPER=36 -DPR_GET_CHILD_SUBREAPER=37" if [[ "${FORCE_SUBREAPER}" -eq 1 ]]; then # If FORCE_SUBREAPER is requested, then we set those CFLAGS for the Tini build export CFLAGS fi echo "CC=${CC}" echo "CFLAGS=${CFLAGS}" echo "MINIMAL=${MINIMAL-}" echo "ARCH_SUFFIX=${ARCH_SUFFIX-}" echo "ARCH_NATIVE=${ARCH_NATIVE-}" # Ensure Python output is not buffered (to make tests output clearer) export PYTHONUNBUFFERED=1 # Set path to prioritize our utils export REAL_PATH="${PATH}" export PATH="${SOURCE_DIR}/ci/util:${PATH}" # Build CMAKE_ARGS=(-B"${BUILD_DIR}" -H"${SOURCE_DIR}") if [[ -n "${MINIMAL:-}" ]]; then CMAKE_ARGS+=(-DMINIMAL=ON) fi cmake "${CMAKE_ARGS[@]}" pushd "${BUILD_DIR}" make clean make if [[ -n "${ARCH_NATIVE-}" ]]; then make package fi popd pkg_version="$(cat "${BUILD_DIR}/VERSION")" if [[ -n "${ARCH_NATIVE-}" ]]; then echo "Built native package (ARCH_NATIVE=${ARCH_NATIVE-})" echo "Running smoke and internal tests" BIN_TEST_DIR="${BUILD_DIR}/bin-test" mkdir -p "$BIN_TEST_DIR" export PATH="${BIN_TEST_DIR}:${PATH}" # 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 -q "tini version" echo "Testing ${tini} without arguments exits with 1" ! "$tini" 2>/dev/null 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 echo "Testing $tini with: false" if "${tini}" false; 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 w x; do bin="-${flag}" echo "Testing $tini can run binary: ${bin}" cp "$(which true)" "${BIN_TEST_DIR}/${bin}" "$tini" "$bin" done echo "Testing $tini can run binary --version if args are given" cp "$(which true)" "${BIN_TEST_DIR}/--version" if "$tini" "--version" --foo | grep -q "tini version"; then exit 1 fi else echo "Testing ${tini} -h" "${tini}" -h echo "Testing $tini for license" "$tini" -l | diff - "${SOURCE_DIR}/LICENSE" echo "Testing $tini with: true" "${tini}" -vvv true echo "Testing $tini with: false" if "${tini}" -vvv false; then exit 1 fi echo "Testing ${tini} references options that exist" { ! "$tini" 2>&1 } | grep -q "more verbose" echo "Testing $tini with: -- true (should succeed)" "${tini}" -vvv -- true echo "Testing $tini with: -- -- true (should fail)" if "${tini}" -vvv -- -- true; then exit 1 fi 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" hardening_skip=(--nopie --nostackprotector --nobindnow) if [[ "$CC" == "musl-gcc" ]]; then # FORTIFY_SOURCE is a glibc thing hardening_skip=("${hardening_skip[@]}" --nofortify) fi hardening-check "${hardening_skip[@]}" "${tini}" done # Quick package audit if which rpm >/dev/null; then echo "Contents for RPM:" rpm -qlp "${BUILD_DIR}/tini_${pkg_version}.rpm" echo "--" fi if which dpkg >/dev/null; then echo "Contents for DEB:" dpkg --contents "${BUILD_DIR}/tini_${pkg_version}.deb" echo "--" fi # Compile test code "${CC}" -o "${BUILD_DIR}/sigconf-test" "${SOURCE_DIR}/test/sigconf/sigconf-test.c" # Create virtual environment to run tests. # Accept system site packages for faster local builds. VENV="${BUILD_DIR}/venv" virtualenv --system-site-packages "${VENV}" # Don't use activate because it does not play nice with nounset export PATH="${VENV}/bin:${PATH}" export CFLAGS # We need them to build our test suite, regardless of FORCE_SUBREAPER # Install test dependencies CC=gcc pip install psutil python-prctl bitmap # Run tests python "${SOURCE_DIR}/test/run_inner_tests.py" else if [[ ! -n "${ARCH_SUFFIX-}" ]]; then echo "Built cross package, but $ARCH_SUFFIX is empty!" exit 1 fi echo "Built cross package (ARCH_SUFFIX=${ARCH_SUFFIX})" echo "Skipping smoke and internal tests" fi # Now, copy over files to DIST_DIR, with appropriate names depending on the # architecture. # Handle the DEB / RPM mkdir -p "${DIST_DIR}" TINIS=() for tini in tini tini-static; do if [[ -n "${ARCH_SUFFIX-}" ]]; then to="${DIST_DIR}/${tini}-${ARCH_SUFFIX}" TINIS+=("$to") cp "${BUILD_DIR}/${tini}" "$to" else to="${DIST_DIR}/${tini}" TINIS+=("$to") cp "${BUILD_DIR}/${tini}" "$to" fi done if [[ -n "${ARCH_NATIVE-}" ]]; then for pkg_format in deb rpm; do src="${BUILD_DIR}/tini_${pkg_version}.${pkg_format}" if [[ -n "${ARCH_SUFFIX-}" ]]; then to="${DIST_DIR}/tini_${pkg_version}-${ARCH_SUFFIX}.${pkg_format}" TINIS+=("$to") cp "$src" "$to" else to="${DIST_DIR}/tini_${pkg_version}.${pkg_format}" TINIS+=("$to") cp "$src" "$to" fi done fi echo "Tinis: ${TINIS[*]}" for tini in "${TINIS[@]}"; do echo "${tini}:" sha1sum "$tini" sha256sum "$tini" file "$tini" echo "--" done # If a signing key and passphrase are made available, then use it to sign the # binaries if [[ -n "$GPG_PASSPHRASE" ]] && [[ -f "${SOURCE_DIR}/sign.key" ]]; then echo "Signing tinis" GPG_SIGN_HOMEDIR="${BUILD_DIR}/gpg-sign" GPG_VERIFY_HOMEDIR="${BUILD_DIR}/gpg-verify" PGP_KEY_FINGERPRINT="595E85A6B1B4779EA4DAAEC70B588DFF0527A9B7" PGP_KEYSERVER="ha.pool.sks-keyservers.net" mkdir "${GPG_SIGN_HOMEDIR}" "${GPG_VERIFY_HOMEDIR}" chmod 700 "${GPG_SIGN_HOMEDIR}" "${GPG_VERIFY_HOMEDIR}" gpg --homedir "${GPG_SIGN_HOMEDIR}" --import "${SOURCE_DIR}/sign.key" gpg --homedir "${GPG_VERIFY_HOMEDIR}" --keyserver "$PGP_KEYSERVER" --recv-keys "$PGP_KEY_FINGERPRINT" for tini in "${TINIS[@]}"; do echo "${GPG_PASSPHRASE}" | gpg --homedir "${GPG_SIGN_HOMEDIR}" --passphrase-fd 0 --armor --detach-sign "${tini}" gpg --homedir "${GPG_VERIFY_HOMEDIR}" --verify "${tini}.asc" done fi tini-0.18.0/ci/util/000077500000000000000000000000001326656433400141275ustar00rootroot00000000000000tini-0.18.0/ci/util/rpmbuild000077500000000000000000000011201326656433400156650ustar00rootroot00000000000000#!/bin/bash # Wrapper for rpm build that first removes files that should be in there # We need this for compatibility with CMake <= 2.8.10 (which is the only version we have in Travis) # See: http://www.cmake.org/pipermail/cmake-commits/2013-April/014818.html set -o nounset set -o errexit set -o pipefail # Remove PATH hack so we can find the real rpmbuild export PATH="${REAL_PATH}" echo "Using local rpmbuild" specFile="${!#}" # Last argument for removeLine in '"/usr"' '"/usr/bin"'; do sed -i "s|${removeLine}||g" "${specFile}" done # Passthrough to rpmbuild exec rpmbuild "${@}" tini-0.18.0/ddist.sh000077500000000000000000000016251326656433400142310ustar00rootroot00000000000000#!/bin/bash set -o errexit set -o nounset REL_HERE=$(dirname "${BASH_SOURCE}") HERE=$(cd "${REL_HERE}"; pwd) IMG="tini-build" if [[ -n "${ARCH_SUFFIX-}" ]]; then IMG="${IMG}_${ARCH_SUFFIX}" fi if [[ -n "${ARCH_NATIVE-}" ]]; then IMG="${IMG}_native" fi if [[ -n "${CC-}" ]]; then IMG="${IMG}_${CC}" fi # Cleanup the build dir rm -f "${HERE}/dist"/* # Create the build image echo "build: ${IMG}" docker build \ --build-arg "ARCH_SUFFIX=${ARCH_SUFFIX-}" \ --build-arg "ARCH_NATIVE=${ARCH_NATIVE-}" \ --build-arg "CC=${CC-gcc}" \ -t "${IMG}" \ . # Build new Tini SRC="/tini" docker run -it --rm \ --volume="${HERE}:${SRC}" \ -e BUILD_DIR=/tmp/tini-build \ -e SOURCE_DIR="${SRC}" \ -e FORCE_SUBREAPER="${FORCE_SUBREAPER-1}" \ -e GPG_PASSPHRASE="${GPG_PASSPHRASE-}" \ -e CFLAGS="${CFLAGS-}" \ -e MINIMAL="${MINIMAL-}" \ -u "$(id -u):$(id -g)" \ "${IMG}" "${SRC}/ci/run_build.sh" tini-0.18.0/dtest.sh000077500000000000000000000003131326656433400142360ustar00rootroot00000000000000#!/bin/bash set -o errexit set -o nounset IMG="tini" if [[ "$#" != 1 ]]; then echo "Usage: $0 ARCH_SUFFIX" exit 1 fi suffix="$1" IMG="tini-build-${suffix}" python test/run_outer_tests.py "${IMG}" tini-0.18.0/run_tests.sh000077500000000000000000000004171326656433400151460ustar00rootroot00000000000000#!/bin/bash set -o errexit set -o nounset REL_HERE=$(dirname "${BASH_SOURCE}") HERE=$(cd "${REL_HERE}"; pwd) for i in $(seq 0 1); do export FORCE_SUBREAPER="${i}" echo "Testing with FORCE_SUBREAPER=${FORCE_SUBREAPER}" "${HERE}/ddist.sh" "${HERE}/dtest.sh" done tini-0.18.0/sign.key.enc000066400000000000000000000100001326656433400147640ustar00rootroot00000000000000(=F)ǥksp뚗k`͚THz {-6Lфֈ1T(:4#,Y ءISIAn;[hJ ie0"ކdJ֚XhD#<]X4tsfIRNC]'jFŨ^HeBx]57'|-8jƫ P֎jmajɓ*@&je:zxH2b(;3 3x1\U¯ YL$Є >`4N/1\Pd$܇!7cgUľ =^0HB+slt^[{t⠌;jrT!ȹ(PT"f5;'$T.+,= ATv)Js=Y UHfL`{\9 buqhQRxnKi *һ-71G1B~^1^a tƍ0 &xH :0T^b* - ԣ;j ɸ6SU@aNiR`]Rʆ N!Ͱ T=fBq'JijwΡ7P}]DwqD|hkG{Gs$E78r^;)Jԫbh-ߘ#{x{aY4e1R9WKw0L-7=Ufj#_&#hQ%a%|wUbM|rD)BeE0`E{I.mܻ$b4NGqaںh7t@TBhKsg~yp<,.npJߗD3D%v;Dنhdw5|*gn?2@+wfFҦPы.FRc k!g$ [)mt`kL̔s#*P#~ xu'Ev=GE%+ ًF @x42t;Fu4Is5oJiP%Җ#/|cd%jG ;NRhr؅][]Kv+CٶO!`0&s7\(JvV~n,zmaoFǝQ3/H!R{+4nze`n Ğ )]A1>aMlT &C s yE!H>P4<#g0Ī*K[4vQ'ybO~ V_0w!mRѻ IWUUCtߛ;L`4(JS4=w!8n-XゾSzCд)㎟Qg4vJإ1`zkcB߹)qC誩tq*eܛQB7q|鏷10SrQDkۖvS)}3yjr4b!z~좡hDXpF6ݧԫCW=sٵpb~:.gZᤂ E54Ol'Έ?΂cv GD־EbqʣUt )aQh9ϐsaa@-~ޕ P v^˗cRPwPpew6CjW`ck؇߽~j !42 ?|_Z3lJJ;_4P L'vjN;8K@@2anI \S$Zd|mg7Y>;V~حјʍvy<uJR>"&PK#*ЉZ.E-bE4Q:cQWpSVVZEI}J< ȏ?\H׶wW$ZB-6"X?f䋳_?mk,.pr`O._26\qi.zvs5rH[=Cö:JND'x]L Ś.mx%бJWE6 Ŋ+xfZk]"Nfѥ>+{{6e%P2.w4lgfP88Ī.|Fm!0G!Ү1Pp*8gJTN׵'d[Ю.dq v2&k%D""MޗDE ސ6{2yΠ\:Oġ2wCz/ЁyοI-/?:u#D4Sm̉kW^#oC7:,}°t`WXb&&ޚ h; F4>Pe5@?Uzz 䜮H+kQ4{ 4aaj7/Q5@qfkkvC2ăp;SZ˟~%ate#C$1)鲄1 A)0A5?ںblRl1R--m>VMm"D\=e/S$tI=\X'iZsZ_kf^fTSc!lq/"IX.K_e󱚂sc'L=t Yg{Ԏ(]rkT"I)Lp?1Etini-0.18.0/src/000077500000000000000000000000001326656433400133465ustar00rootroot00000000000000tini-0.18.0/src/tini.c000066400000000000000000000453441326656433400144670ustar00rootroot00000000000000/* See LICENSE file for copyright and license details. */ #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #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])) #define INT32_BITFIELD_SET(F, i) ( F[(i / 32)] |= (1 << (i % 32)) ) #define INT32_BITFIELD_CLEAR(F, i) ( F[(i / 32)] &= ~(1 << (i % 32)) ) #define INT32_BITFIELD_TEST(F, i) ( F[(i / 32)] & (1 << (i % 32)) ) #define INT32_BITFIELD_CHECK_BOUNDS(F, i) do { assert(i >= 0); assert(ARRAY_LEN(F) > (uint) (i / 32)); } while(0) #define STATUS_MAX 255 #define STATUS_MIN 0 typedef struct { sigset_t* const sigmask_ptr; struct sigaction* const sigttin_action_ptr; struct sigaction* const sigttou_action_ptr; } signal_configuration_t; static const struct { char *const name; int number; } signal_names[] = { { "SIGHUP", SIGHUP }, { "SIGINT", SIGINT }, { "SIGQUIT", SIGQUIT }, { "SIGILL", SIGILL }, { "SIGTRAP", SIGTRAP }, { "SIGABRT", SIGABRT }, { "SIGBUS", SIGBUS }, { "SIGFPE", SIGFPE }, { "SIGKILL", SIGKILL }, { "SIGUSR1", SIGUSR1 }, { "SIGSEGV", SIGSEGV }, { "SIGUSR2", SIGUSR2 }, { "SIGPIPE", SIGPIPE }, { "SIGALRM", SIGALRM }, { "SIGTERM", SIGTERM }, { "SIGCHLD", SIGCHLD }, { "SIGCONT", SIGCONT }, { "SIGSTOP", SIGSTOP }, { "SIGTSTP", SIGTSTP }, { "SIGTTIN", SIGTTIN }, { "SIGTTOU", SIGTTOU }, { "SIGURG", SIGURG }, { "SIGXCPU", SIGXCPU }, { "SIGXFSZ", SIGXFSZ }, { "SIGVTALRM", SIGVTALRM }, { "SIGPROF", SIGPROF }, { "SIGWINCH", SIGWINCH }, { "SIGSYS", SIGSYS }, }; static unsigned int verbosity = DEFAULT_VERBOSITY; static int32_t expect_status[(STATUS_MAX - STATUS_MIN + 1) / 32]; #ifdef PR_SET_CHILD_SUBREAPER #define HAS_SUBREAPER 1 #define OPT_STRING "p:hvwgle:s" #define SUBREAPER_ENV_VAR "TINI_SUBREAPER" #else #define HAS_SUBREAPER 0 #define OPT_STRING "p:hvwgle:" #endif #define VERBOSITY_ENV_VAR "TINI_VERBOSITY" #define KILL_PROCESS_GROUP_GROUP_ENV_VAR "TINI_KILL_PROCESS_GROUP" #define TINI_VERSION_STRING "tini version " TINI_VERSION TINI_GIT #if HAS_SUBREAPER static unsigned int subreaper = 0; #endif static unsigned int parent_death_signal = 0; static unsigned int kill_process_group = 0; static unsigned int warn_on_reap = 0; static struct timespec ts = { .tv_sec = 1, .tv_nsec = 0 }; 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, " #if HAS_SUBREAPER #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."; int restore_signals(const signal_configuration_t* const sigconf_ptr) { if (sigprocmask(SIG_SETMASK, sigconf_ptr->sigmask_ptr, NULL)) { PRINT_FATAL("Restoring child signal mask failed: '%s'", strerror(errno)); return 1; } if (sigaction(SIGTTIN, sigconf_ptr->sigttin_action_ptr, NULL)) { PRINT_FATAL("Restoring SIGTTIN handler failed: '%s'", strerror((errno))); return 1; } if (sigaction(SIGTTOU, sigconf_ptr->sigttou_action_ptr, NULL)) { PRINT_FATAL("Restoring SIGTTOU handler failed: '%s'", strerror((errno))); return 1; } return 0; } int isolate_child() { // Put the child into a new process group. if (setpgid(0, 0) < 0) { PRINT_FATAL("setpgid failed: %s", strerror(errno)); return 1; } // If there is a tty, allocate it to this new process group. We // can do this in the child process because we're blocking // SIGTTIN / SIGTTOU. // Doing it in the child process avoids a race condition scenario // if Tini is calling Tini (in which case the grandparent may make the // parent the foreground process group, and the actual child ends up... // in the background!) if (tcsetpgrp(STDIN_FILENO, getpgrp())) { if (errno == ENOTTY) { PRINT_DEBUG("tcsetpgrp failed: no tty (ok to proceed)"); } else if (errno == ENXIO) { // can occur on lx-branded zones PRINT_DEBUG("tcsetpgrp failed: no such device (ok to proceed)"); } else { PRINT_FATAL("tcsetpgrp failed: %s", strerror(errno)); return 1; } } return 0; } int spawn(const signal_configuration_t* const sigconf_ptr, char* const argv[], int* const child_pid_ptr) { pid_t pid; // TODO: check if tini was a foreground process to begin with (it's not OK to "steal" the foreground!") pid = fork(); if (pid < 0) { PRINT_FATAL("fork failed: %s", strerror(errno)); return 1; } else if (pid == 0) { // Put the child in a process group and make it the foreground process if there is a tty. if (isolate_child()) { return 1; } // Restore all signal handlers to the way they were before we touched them. if (restore_signals(sigconf_ptr)) { return 1; } execvp(argv[0], argv); // 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); *child_pid_ptr = pid; return 0; } } void print_usage(char* const name, FILE* const file) { fprintf(file, "%s (%s)\n", basename(name), TINI_VERSION_STRING); #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"); #endif fprintf(file, " -p SIGNAL: Trigger SIGNAL when parent dies, e.g. \"-p SIGKILL\".\n"); fprintf(file, " -v: Generate more verbose output. Repeat up to 3 times.\n"); fprintf(file, " -w: Print a warning when processes are getting reaped.\n"); fprintf(file, " -g: Send signals to the child's process group.\n"); fprintf(file, " -e EXIT_CODE: Remap EXIT_CODE (from 0 to 255) to 0.\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, " %s: Send signals to the child's process group.\n", KILL_PROCESS_GROUP_GROUP_ENV_VAR); fprintf(file, "\n"); } void print_license(FILE* const file) { if(LICENSE_len > fwrite(LICENSE, sizeof(char), LICENSE_len, file)) { // Don't handle this error for now, since parse_args won't care // about the return value. We do need to check it to compile with // older glibc, though. // See: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=25509 // See: http://sourceware.org/bugzilla/show_bug.cgi?id=11959 } } int set_pdeathsig(char* const arg) { size_t i; for (i = 0; i < ARRAY_LEN(signal_names); i++) { if (strcmp(signal_names[i].name, arg) == 0) { /* Signals start at value "1" */ parent_death_signal = signal_names[i].number; return 0; } } return 1; } int add_expect_status(char* arg) { long status = 0; char* endptr = NULL; status = strtol(arg, &endptr, 10); if ((endptr == NULL) || (*endptr != 0)) { return 1; } if ((status < STATUS_MIN) || (status > STATUS_MAX)) { return 1; } INT32_BITFIELD_CHECK_BOUNDS(expect_status, status); INT32_BITFIELD_SET(expect_status, status); return 0; } int parse_args(const int argc, char* const argv[], char* (**child_args_ptr_ptr)[], int* const parse_fail_exitcode_ptr) { char* name = argv[0]; // We handle --version if it's the *only* argument provided. if (argc == 2 && strcmp("--version", argv[1]) == 0) { *parse_fail_exitcode_ptr = 0; fprintf(stdout, "%s\n", TINI_VERSION_STRING); return 1; } #ifndef TINI_MINIMAL int c; while ((c = getopt(argc, argv, OPT_STRING)) != -1) { switch (c) { case 'h': print_usage(name, stdout); *parse_fail_exitcode_ptr = 0; return 1; #if HAS_SUBREAPER case 's': subreaper++; break; #endif case 'p': if (set_pdeathsig(optarg)) { PRINT_FATAL("Not a valid option for -p: %s", optarg); *parse_fail_exitcode_ptr = 1; return 1; } break; case 'v': verbosity++; break; case 'w': warn_on_reap++; break; case 'g': kill_process_group++; break; case 'e': if (add_expect_status(optarg)) { PRINT_FATAL("Not a valid option for -e: %s", optarg); *parse_fail_exitcode_ptr = 1; return 1; } break; case 'l': print_license(stdout); *parse_fail_exitcode_ptr = 0; return 1; case '?': print_usage(name, stderr); return 1; default: /* Should never happen */ return 1; } } #endif *child_args_ptr_ptr = calloc(argc-optind+1, sizeof(char*)); if (*child_args_ptr_ptr == NULL) { PRINT_FATAL("Failed to allocate memory for child args: '%s'", strerror(errno)); return 1; } int i; for (i = 0; i < argc - optind; i++) { (**child_args_ptr_ptr)[i] = argv[optind+i]; } (**child_args_ptr_ptr)[i] = NULL; if (i == 0) { /* User forgot to provide args! */ print_usage(name, stderr); return 1; } return 0; } int parse_env() { #if HAS_SUBREAPER if (getenv(SUBREAPER_ENV_VAR) != NULL) { subreaper++; } #endif if (getenv(KILL_PROCESS_GROUP_GROUP_ENV_VAR) != NULL) { kill_process_group++; } char* env_verbosity = getenv(VERBOSITY_ENV_VAR); if (env_verbosity != NULL) { verbosity = atoi(env_verbosity); } return 0; } #if HAS_SUBREAPER int register_subreaper () { if (subreaper > 0) { if (prctl(PR_SET_CHILD_SUBREAPER, 1)) { 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(reaper_warning); } int configure_signals(sigset_t* const parent_sigset_ptr, const signal_configuration_t* const sigconf_ptr) { /* Block all signals that are meant to be collected by the main loop */ if (sigfillset(parent_sigset_ptr)) { PRINT_FATAL("sigfillset failed: '%s'", strerror(errno)); return 1; } // These ones shouldn't be collected by the main loop uint i; int signals_for_tini[] = {SIGFPE, SIGILL, SIGSEGV, SIGBUS, SIGABRT, SIGTRAP, SIGSYS, SIGTTIN, SIGTTOU}; for (i = 0; i < ARRAY_LEN(signals_for_tini); i++) { if (sigdelset(parent_sigset_ptr, signals_for_tini[i])) { PRINT_FATAL("sigdelset failed: '%i'", signals_for_tini[i]); return 1; } } if (sigprocmask(SIG_SETMASK, parent_sigset_ptr, sigconf_ptr->sigmask_ptr)) { PRINT_FATAL("sigprocmask failed: '%s'", strerror(errno)); return 1; } // Handle SIGTTIN and SIGTTOU separately. Since Tini makes the child process group // the foreground process group, there's a chance Tini can end up not controlling the tty. // If TOSTOP is set on the tty, this could block Tini on writing debug messages. We don't // want that. Ignore those signals. struct sigaction ign_action; memset(&ign_action, 0, sizeof ign_action); ign_action.sa_handler = SIG_IGN; sigemptyset(&ign_action.sa_mask); if (sigaction(SIGTTIN, &ign_action, sigconf_ptr->sigttin_action_ptr)) { PRINT_FATAL("Failed to ignore SIGTTIN"); return 1; } if (sigaction(SIGTTOU, &ign_action, sigconf_ptr->sigttou_action_ptr)) { PRINT_FATAL("Failed to ignore SIGTTOU"); return 1; } return 0; } int wait_and_forward_signal(sigset_t const* const parent_sigset_ptr, pid_t const child_pid) { siginfo_t sig; if (sigtimedwait(parent_sigset_ptr, &sig, &ts) == -1) { switch (errno) { case EAGAIN: break; case EINTR: break; default: PRINT_FATAL("Unexpected error in sigtimedwait: '%s'", strerror(errno)); return 1; } } else { /* There is a signal to handle here */ switch (sig.si_signo) { case SIGCHLD: /* Special-cased, as we don't forward SIGCHLD. Instead, we'll * fallthrough to reaping processes. */ PRINT_DEBUG("Received SIGCHLD"); break; default: PRINT_DEBUG("Passing signal: '%s'", strsignal(sig.si_signo)); /* Forward anything else */ if (kill(kill_process_group ? -child_pid : child_pid, sig.si_signo)) { if (errno == ESRCH) { PRINT_WARNING("Child was dead when forwarding signal"); } else { PRINT_FATAL("Unexpected error when forwarding signal: '%s'", strerror(errno)); return 1; } } break; } } return 0; } int reap_zombies(const pid_t child_pid, int* const child_exitcode_ptr) { pid_t current_pid; int current_status; while (1) { current_pid = waitpid(-1, ¤t_status, WNOHANG); switch (current_pid) { case -1: if (errno == ECHILD) { 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"); break; default: /* A child was reaped. Check whether it's the main one. If it is, then * set the exit_code, which will cause us to exit once we've reaped everyone else. */ PRINT_DEBUG("Reaped child with pid: '%i'", current_pid); if (current_pid == child_pid) { if (WIFEXITED(current_status)) { /* Our process exited normally. */ PRINT_INFO("Main child exited normally (with status '%i')", WEXITSTATUS(current_status)); *child_exitcode_ptr = WEXITSTATUS(current_status); } else if (WIFSIGNALED(current_status)) { /* Our process was terminated. Emulate what sh / bash * would do, which is to return 128 + signal number. */ 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"); return 1; } // Be safe, ensure the status code is indeed between 0 and 255. *child_exitcode_ptr = *child_exitcode_ptr % (STATUS_MAX - STATUS_MIN + 1); // If this exitcode was remapped, then set it to 0. INT32_BITFIELD_CHECK_BOUNDS(expect_status, *child_exitcode_ptr); if (INT32_BITFIELD_TEST(expect_status, *child_exitcode_ptr)) { *child_exitcode_ptr = 0; } } else if (warn_on_reap > 0) { PRINT_WARNING("Reaped zombie process with pid=%i", current_pid); } // Check if other childs have been reaped. continue; } /* If we make it here, that's because we did not continue in the switch case. */ break; } return 0; } int main(int argc, char *argv[]) { pid_t child_pid; // Those are passed to functions to get an exitcode back. 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; } /* Parse environment */ if (parse_env()) { return 1; } /* Configure signals */ sigset_t parent_sigset, child_sigset; struct sigaction sigttin_action, sigttou_action; memset(&sigttin_action, 0, sizeof sigttin_action); memset(&sigttou_action, 0, sizeof sigttou_action); signal_configuration_t child_sigconf = { .sigmask_ptr = &child_sigset, .sigttin_action_ptr = &sigttin_action, .sigttou_action_ptr = &sigttou_action, }; if (configure_signals(&parent_sigset, &child_sigconf)) { return 1; } /* Trigger signal on this process when the parent process exits. */ if (parent_death_signal && prctl(PR_SET_PDEATHSIG, parent_death_signal)) { PRINT_FATAL("Failed to set up parent death signal"); return 1; } #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 */ int spawn_ret = spawn(&child_sigconf, *child_args_ptr, &child_pid); if (spawn_ret) { return spawn_ret; } free(child_args_ptr); while (1) { /* Wait for one signal, and forward it */ if (wait_and_forward_signal(&parent_sigset, child_pid)) { return 1; } /* Now, reap zombies */ if (reap_zombies(child_pid, &child_exitcode)) { return 1; } if (child_exitcode != -1) { PRINT_TRACE("Exiting: child has exited"); return child_exitcode; } } } tini-0.18.0/src/tiniConfig.h.in000066400000000000000000000002001326656433400162050ustar00rootroot00000000000000#define TINI_VERSION "@tini_VERSION_MAJOR@.@tini_VERSION_MINOR@.@tini_VERSION_PATCH@" #define TINI_GIT "@tini_VERSION_GIT@" tini-0.18.0/src/tiniLicense.h000066400000000000000000000152711326656433400157730ustar00rootroot00000000000000unsigned char LICENSE[] = { 0x54, 0x68, 0x65, 0x20, 0x4d, 0x49, 0x54, 0x20, 0x4c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x20, 0x28, 0x4d, 0x49, 0x54, 0x29, 0x0a, 0x0a, 0x43, 0x6f, 0x70, 0x79, 0x72, 0x69, 0x67, 0x68, 0x74, 0x20, 0x28, 0x63, 0x29, 0x20, 0x32, 0x30, 0x31, 0x35, 0x20, 0x54, 0x68, 0x6f, 0x6d, 0x61, 0x73, 0x20, 0x4f, 0x72, 0x6f, 0x7a, 0x63, 0x6f, 0x20, 0x3c, 0x74, 0x68, 0x6f, 0x6d, 0x61, 0x73, 0x40, 0x6f, 0x72, 0x6f, 0x7a, 0x63, 0x6f, 0x2e, 0x66, 0x72, 0x3e, 0x0a, 0x0a, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x20, 0x69, 0x73, 0x20, 0x68, 0x65, 0x72, 0x65, 0x62, 0x79, 0x20, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x65, 0x64, 0x2c, 0x20, 0x66, 0x72, 0x65, 0x65, 0x20, 0x6f, 0x66, 0x20, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x2c, 0x20, 0x74, 0x6f, 0x20, 0x61, 0x6e, 0x79, 0x20, 0x70, 0x65, 0x72, 0x73, 0x6f, 0x6e, 0x20, 0x6f, 0x62, 0x74, 0x61, 0x69, 0x6e, 0x69, 0x6e, 0x67, 0x20, 0x61, 0x20, 0x63, 0x6f, 0x70, 0x79, 0x0a, 0x6f, 0x66, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x73, 0x6f, 0x66, 0x74, 0x77, 0x61, 0x72, 0x65, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x61, 0x73, 0x73, 0x6f, 0x63, 0x69, 0x61, 0x74, 0x65, 0x64, 0x20, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x20, 0x28, 0x74, 0x68, 0x65, 0x20, 0x22, 0x53, 0x6f, 0x66, 0x74, 0x77, 0x61, 0x72, 0x65, 0x22, 0x29, 0x2c, 0x20, 0x74, 0x6f, 0x20, 0x64, 0x65, 0x61, 0x6c, 0x0a, 0x69, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x53, 0x6f, 0x66, 0x74, 0x77, 0x61, 0x72, 0x65, 0x20, 0x77, 0x69, 0x74, 0x68, 0x6f, 0x75, 0x74, 0x20, 0x72, 0x65, 0x73, 0x74, 0x72, 0x69, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x2c, 0x20, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x69, 0x6e, 0x67, 0x20, 0x77, 0x69, 0x74, 0x68, 0x6f, 0x75, 0x74, 0x20, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x72, 0x69, 0x67, 0x68, 0x74, 0x73, 0x0a, 0x74, 0x6f, 0x20, 0x75, 0x73, 0x65, 0x2c, 0x20, 0x63, 0x6f, 0x70, 0x79, 0x2c, 0x20, 0x6d, 0x6f, 0x64, 0x69, 0x66, 0x79, 0x2c, 0x20, 0x6d, 0x65, 0x72, 0x67, 0x65, 0x2c, 0x20, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x2c, 0x20, 0x64, 0x69, 0x73, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x2c, 0x20, 0x73, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x2c, 0x20, 0x61, 0x6e, 0x64, 0x2f, 0x6f, 0x72, 0x20, 0x73, 0x65, 0x6c, 0x6c, 0x0a, 0x63, 0x6f, 0x70, 0x69, 0x65, 0x73, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 0x53, 0x6f, 0x66, 0x74, 0x77, 0x61, 0x72, 0x65, 0x2c, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x74, 0x6f, 0x20, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x74, 0x20, 0x70, 0x65, 0x72, 0x73, 0x6f, 0x6e, 0x73, 0x20, 0x74, 0x6f, 0x20, 0x77, 0x68, 0x6f, 0x6d, 0x20, 0x74, 0x68, 0x65, 0x20, 0x53, 0x6f, 0x66, 0x74, 0x77, 0x61, 0x72, 0x65, 0x20, 0x69, 0x73, 0x0a, 0x66, 0x75, 0x72, 0x6e, 0x69, 0x73, 0x68, 0x65, 0x64, 0x20, 0x74, 0x6f, 0x20, 0x64, 0x6f, 0x20, 0x73, 0x6f, 0x2c, 0x20, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x20, 0x74, 0x6f, 0x20, 0x74, 0x68, 0x65, 0x20, 0x66, 0x6f, 0x6c, 0x6c, 0x6f, 0x77, 0x69, 0x6e, 0x67, 0x20, 0x63, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x3a, 0x0a, 0x0a, 0x54, 0x68, 0x65, 0x20, 0x61, 0x62, 0x6f, 0x76, 0x65, 0x20, 0x63, 0x6f, 0x70, 0x79, 0x72, 0x69, 0x67, 0x68, 0x74, 0x20, 0x6e, 0x6f, 0x74, 0x69, 0x63, 0x65, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x20, 0x6e, 0x6f, 0x74, 0x69, 0x63, 0x65, 0x20, 0x73, 0x68, 0x61, 0x6c, 0x6c, 0x20, 0x62, 0x65, 0x20, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x64, 0x20, 0x69, 0x6e, 0x0a, 0x61, 0x6c, 0x6c, 0x20, 0x63, 0x6f, 0x70, 0x69, 0x65, 0x73, 0x20, 0x6f, 0x72, 0x20, 0x73, 0x75, 0x62, 0x73, 0x74, 0x61, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x20, 0x70, 0x6f, 0x72, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 0x53, 0x6f, 0x66, 0x74, 0x77, 0x61, 0x72, 0x65, 0x2e, 0x0a, 0x0a, 0x54, 0x48, 0x45, 0x20, 0x53, 0x4f, 0x46, 0x54, 0x57, 0x41, 0x52, 0x45, 0x20, 0x49, 0x53, 0x20, 0x50, 0x52, 0x4f, 0x56, 0x49, 0x44, 0x45, 0x44, 0x20, 0x22, 0x41, 0x53, 0x20, 0x49, 0x53, 0x22, 0x2c, 0x20, 0x57, 0x49, 0x54, 0x48, 0x4f, 0x55, 0x54, 0x20, 0x57, 0x41, 0x52, 0x52, 0x41, 0x4e, 0x54, 0x59, 0x20, 0x4f, 0x46, 0x20, 0x41, 0x4e, 0x59, 0x20, 0x4b, 0x49, 0x4e, 0x44, 0x2c, 0x20, 0x45, 0x58, 0x50, 0x52, 0x45, 0x53, 0x53, 0x20, 0x4f, 0x52, 0x0a, 0x49, 0x4d, 0x50, 0x4c, 0x49, 0x45, 0x44, 0x2c, 0x20, 0x49, 0x4e, 0x43, 0x4c, 0x55, 0x44, 0x49, 0x4e, 0x47, 0x20, 0x42, 0x55, 0x54, 0x20, 0x4e, 0x4f, 0x54, 0x20, 0x4c, 0x49, 0x4d, 0x49, 0x54, 0x45, 0x44, 0x20, 0x54, 0x4f, 0x20, 0x54, 0x48, 0x45, 0x20, 0x57, 0x41, 0x52, 0x52, 0x41, 0x4e, 0x54, 0x49, 0x45, 0x53, 0x20, 0x4f, 0x46, 0x20, 0x4d, 0x45, 0x52, 0x43, 0x48, 0x41, 0x4e, 0x54, 0x41, 0x42, 0x49, 0x4c, 0x49, 0x54, 0x59, 0x2c, 0x0a, 0x46, 0x49, 0x54, 0x4e, 0x45, 0x53, 0x53, 0x20, 0x46, 0x4f, 0x52, 0x20, 0x41, 0x20, 0x50, 0x41, 0x52, 0x54, 0x49, 0x43, 0x55, 0x4c, 0x41, 0x52, 0x20, 0x50, 0x55, 0x52, 0x50, 0x4f, 0x53, 0x45, 0x20, 0x41, 0x4e, 0x44, 0x20, 0x4e, 0x4f, 0x4e, 0x49, 0x4e, 0x46, 0x52, 0x49, 0x4e, 0x47, 0x45, 0x4d, 0x45, 0x4e, 0x54, 0x2e, 0x20, 0x49, 0x4e, 0x20, 0x4e, 0x4f, 0x20, 0x45, 0x56, 0x45, 0x4e, 0x54, 0x20, 0x53, 0x48, 0x41, 0x4c, 0x4c, 0x20, 0x54, 0x48, 0x45, 0x0a, 0x41, 0x55, 0x54, 0x48, 0x4f, 0x52, 0x53, 0x20, 0x4f, 0x52, 0x20, 0x43, 0x4f, 0x50, 0x59, 0x52, 0x49, 0x47, 0x48, 0x54, 0x20, 0x48, 0x4f, 0x4c, 0x44, 0x45, 0x52, 0x53, 0x20, 0x42, 0x45, 0x20, 0x4c, 0x49, 0x41, 0x42, 0x4c, 0x45, 0x20, 0x46, 0x4f, 0x52, 0x20, 0x41, 0x4e, 0x59, 0x20, 0x43, 0x4c, 0x41, 0x49, 0x4d, 0x2c, 0x20, 0x44, 0x41, 0x4d, 0x41, 0x47, 0x45, 0x53, 0x20, 0x4f, 0x52, 0x20, 0x4f, 0x54, 0x48, 0x45, 0x52, 0x0a, 0x4c, 0x49, 0x41, 0x42, 0x49, 0x4c, 0x49, 0x54, 0x59, 0x2c, 0x20, 0x57, 0x48, 0x45, 0x54, 0x48, 0x45, 0x52, 0x20, 0x49, 0x4e, 0x20, 0x41, 0x4e, 0x20, 0x41, 0x43, 0x54, 0x49, 0x4f, 0x4e, 0x20, 0x4f, 0x46, 0x20, 0x43, 0x4f, 0x4e, 0x54, 0x52, 0x41, 0x43, 0x54, 0x2c, 0x20, 0x54, 0x4f, 0x52, 0x54, 0x20, 0x4f, 0x52, 0x20, 0x4f, 0x54, 0x48, 0x45, 0x52, 0x57, 0x49, 0x53, 0x45, 0x2c, 0x20, 0x41, 0x52, 0x49, 0x53, 0x49, 0x4e, 0x47, 0x20, 0x46, 0x52, 0x4f, 0x4d, 0x2c, 0x0a, 0x4f, 0x55, 0x54, 0x20, 0x4f, 0x46, 0x20, 0x4f, 0x52, 0x20, 0x49, 0x4e, 0x20, 0x43, 0x4f, 0x4e, 0x4e, 0x45, 0x43, 0x54, 0x49, 0x4f, 0x4e, 0x20, 0x57, 0x49, 0x54, 0x48, 0x20, 0x54, 0x48, 0x45, 0x20, 0x53, 0x4f, 0x46, 0x54, 0x57, 0x41, 0x52, 0x45, 0x20, 0x4f, 0x52, 0x20, 0x54, 0x48, 0x45, 0x20, 0x55, 0x53, 0x45, 0x20, 0x4f, 0x52, 0x20, 0x4f, 0x54, 0x48, 0x45, 0x52, 0x20, 0x44, 0x45, 0x41, 0x4c, 0x49, 0x4e, 0x47, 0x53, 0x20, 0x49, 0x4e, 0x0a, 0x54, 0x48, 0x45, 0x20, 0x53, 0x4f, 0x46, 0x54, 0x57, 0x41, 0x52, 0x45, 0x2e, 0x0a }; unsigned int LICENSE_len = 1099; tini-0.18.0/test/000077500000000000000000000000001326656433400135365ustar00rootroot00000000000000tini-0.18.0/test/pdeathsignal/000077500000000000000000000000001326656433400162015ustar00rootroot00000000000000tini-0.18.0/test/pdeathsignal/stage_1.py000077500000000000000000000007221326656433400201020ustar00rootroot00000000000000#!/usr/bin/env python from __future__ import print_function import os import sys import subprocess def main(): pid = os.getpid() tini = sys.argv[1] ret = sys.argv[2] stage_2 = os.path.join(os.path.dirname(__file__), "stage_2.py") cmd = [ tini, "-vvv", "-p", "SIGUSR1", "--", stage_2, str(pid), ret ] subprocess.Popen(cmd).wait() if __name__ == "__main__": main() tini-0.18.0/test/pdeathsignal/stage_2.py000077500000000000000000000006331326656433400201040ustar00rootroot00000000000000#!/usr/bin/env python from __future__ import print_function import os import sys import signal import time def main(): ret = sys.argv[2] def handler(*args): with open(ret, "w") as f: f.write("ok") sys.exit(0) signal.signal(signal.SIGUSR1, handler) pid = int(sys.argv[1]) os.kill(pid, signal.SIGKILL) time.sleep(5) if __name__ == "__main__": main() tini-0.18.0/test/pgroup/000077500000000000000000000000001326656433400150525ustar00rootroot00000000000000tini-0.18.0/test/pgroup/stage_1.py000077500000000000000000000005261326656433400167550ustar00rootroot00000000000000#!/usr/bin/env python import os import subprocess import signal def reset_sig_handler(): signal.signal(signal.SIGUSR1, signal.SIG_DFL) if __name__ == "__main__": signal.signal(signal.SIGUSR1, signal.SIG_IGN) p = subprocess.Popen( ["sleep", "1000"], preexec_fn=reset_sig_handler ) p.wait() tini-0.18.0/test/reaping/000077500000000000000000000000001326656433400151635ustar00rootroot00000000000000tini-0.18.0/test/reaping/stage_1.py000077500000000000000000000033761326656433400170740ustar00rootroot00000000000000#!/usr/bin/env python from __future__ import print_function import os import sys import subprocess import time import psutil def in_group_or_reaped(pid): try: return os.getpgid(pid) == os.getpgid(0) except OSError: return True def main(): stage_2 = os.path.join(os.path.dirname(__file__), "stage_2.py") subprocess.Popen([stage_2]).wait() # In tests, we assume this process is the direct child of init this_process = psutil.Process(os.getpid()) init_process = this_process.parent() print("Reaping test: stage_1 is pid{0}, init is pid{1}".format( this_process.pid, init_process.pid)) # The only child PID that should persist is this one. expected_pids = [this_process.pid] print("Expecting pids to remain: {0}".format( ", ".join(str(pid) for pid in expected_pids))) while 1: pids = [p.pid for p in init_process.children(recursive=True)] print("Has pids: {0}".format(", ".join(str(pid) for pid in pids))) for pid in pids: assert in_group_or_reaped(pid), "Child had unexpected pgid" if set(pids) == set(expected_pids): break time.sleep(1) # Now, check if there are any zombies. For each of the potential zombies, # we check that the pgid is ours. NOTE: We explicitly test that this test # fails if subreaping is disabled, so we can be confident this doesn't turn # a failure into a success. for process in psutil.process_iter(): if process.pid == this_process.pid: continue if not in_group_or_reaped(process.pid): continue print("Not reaped: pid {0}: {1}".format(process.pid, process.name())) sys.exit(1) sys.exit(0) if __name__ == "__main__": main() tini-0.18.0/test/reaping/stage_2.py000077500000000000000000000004321326656433400170630ustar00rootroot00000000000000#!/usr/bin/env python from __future__ import print_function import subprocess import os import random if __name__ == "__main__": # Spawn lots of process for i in range(0, 100): cmd = ["sleep", str(1 + i % 2 + random.random())] proc = subprocess.Popen(cmd) tini-0.18.0/test/run_inner_tests.py000077500000000000000000000172221326656433400173400ustar00rootroot00000000000000#!/usr/bin/env python #coding:utf-8 import os import sys import signal import subprocess import time import psutil import bitmap import re import itertools import tempfile DEVNULL = open(os.devnull, 'wb') SIGNUM_TO_SIGNAME = dict((v, k) for k,v in signal.__dict__.items() if re.match("^SIG[A-Z]+$", k)) def busy_wait(condition_callable, timeout): checks = 100 increment = float(timeout) / checks for _ in xrange(checks): if condition_callable(): return time.sleep(increment) assert False, "Condition was never met" def main(): src = os.environ["SOURCE_DIR"] build = os.environ["BUILD_DIR"] args_disabled = os.environ.get("MINIMAL") proxy = os.path.join(src, "test", "subreaper-proxy.py") tini = os.path.join(build, "tini") subreaper_support = bool(int(os.environ["FORCE_SUBREAPER"])) # Run the exit code test. We use POSIXLY_CORRECT here to not need -- # until that's the default in Tini anyways. if not args_disabled: print "Running exit code test for {0}".format(tini) for code in range(0, 256): p = subprocess.Popen( [tini, '-e', str(code), '--', 'sh', '-c', "exit {0}".format(code)], stdout=DEVNULL, stderr=DEVNULL ) ret = p.wait() assert ret == 0, "Inclusive exit code test failed for %s, exit: %s" % (code, ret) other_codes = [x for x in range(0, 256) if x != code] args = list(itertools.chain(*[['-e', str(x)] for x in other_codes])) p = subprocess.Popen( [tini] + args + ['sh', '-c', "exit {0}".format(code)], env=dict(os.environ, POSIXLY_CORRECT="1"), stdout=DEVNULL, stderr=DEVNULL ) ret = p.wait() assert ret == code, "Exclusive exit code test failed for %s, exit: %s" % (code, ret) tests = [([proxy, tini], {}),] if subreaper_support: if not args_disabled: tests.append(([tini, "-s"], {})) tests.append(([tini], {"TINI_SUBREAPER": ""})) for target, env in tests: # Run the reaping test print "Running reaping test ({0} with env {1})".format(" ".join(target), env) p = subprocess.Popen(target + [os.path.join(src, "test", "reaping", "stage_1.py")], env=dict(os.environ, **env), stdout=subprocess.PIPE, stderr=subprocess.PIPE) out, err = p.communicate() if subreaper_support: # If subreaper support sin't available, Tini won't looku p its subreaper bit # and will output the error message here. assert "zombie reaping won't work" not in err, "Warning message was output!" ret = p.wait() assert "Reaped zombie process with pid=" not in err, "Warning message was output!" assert ret == 0, "Reaping test failed!\nOUT: %s\nERR: %s" % (out, err) if not args_disabled: print "Running reaping display test ({0} with env {1})".format(" ".join(target), env) p = subprocess.Popen(target + ["-w", os.path.join(src, "test", "reaping", "stage_1.py")], env=dict(os.environ, **env), stdout=subprocess.PIPE, stderr=subprocess.PIPE) out, err = p.communicate() ret = p.wait() assert "Reaped zombie process with pid=" in err, "Warning message was output!" assert ret == 0, "Reaping display test failed!\nOUT: %s\nERR: %s" % (out, err) # Run the signals test for signum in [signal.SIGTERM, signal.SIGUSR1, signal.SIGUSR2]: print "running signal test for: {0} ({1} with env {2})".format(signum, " ".join(target), env) p = subprocess.Popen(target + [os.path.join(src, "test", "signals", "test.py")], env=dict(os.environ, **env)) busy_wait(lambda: len(psutil.Process(p.pid).children(recursive=True)) > 1, 10) p.send_signal(signum) ret = p.wait() assert ret == 128 + signum, "Signals test failed (ret was {0}, expected {1})".format(ret, 128 + signum) # Run the process group test # This test has Tini spawn a process that ignores SIGUSR1 and spawns a child that doesn't (and waits on the child) # We send SIGUSR1 to Tini, and expect the grand-child to terminate, then the child, and then Tini. if not args_disabled: print "Running process group test (arguments)" p = subprocess.Popen([tini, '-g', os.path.join(src, "test", "pgroup", "stage_1.py")], stdout=subprocess.PIPE, stderr=subprocess.PIPE) busy_wait(lambda: len(psutil.Process(p.pid).children(recursive=True)) == 2, 10) p.send_signal(signal.SIGUSR1) busy_wait(lambda: p.poll() is not None, 10) print "Running process group test (environment variable)" p = subprocess.Popen( [tini, os.path.join(src, "test", "pgroup", "stage_1.py")], stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=dict(os.environ, TINI_KILL_PROCESS_GROUP="1") ) busy_wait(lambda: len(psutil.Process(p.pid).children(recursive=True)) == 2, 10) p.send_signal(signal.SIGUSR1) busy_wait(lambda: p.poll() is not None, 10) # 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, env={'TINI_VERBOSITY': '1'} ) out, err = p.communicate() assert "zombie reaping won't work" in err, "No warning message was output!" ret = p.wait() assert ret == 1, "Reaping test succeeded (it should have failed)!" # Test that the signals are properly in place here. print "Running signal configuration test" p = subprocess.Popen([os.path.join(build, "sigconf-test"), tini, "cat", "/proc/self/status"], stdout=subprocess.PIPE, stderr=subprocess.PIPE) out, err = p.communicate() # Extract the signal properties, and add a zero at the end. props = [line.split(":") for line in out.splitlines()] props = [(k.strip(), v.strip()) for (k, v) in props] props = [(k, bitmap.BitMap.fromstring(bin(int(v, 16))[2:].zfill(32))) for (k, v) in props if k in ["SigBlk", "SigIgn", "SigCgt"]] props = dict(props) # Print actual handling configuration for k, bmp in props.items(): print "{0}: {1}".format(k, ", ".join(["{0} ({1})".format(SIGNUM_TO_SIGNAME[n+1], n+1) for n in bmp.nonzero()])) for signal_set_name, signals_to_test_for in [ ("SigIgn", [signal.SIGTTOU, signal.SIGSEGV, signal.SIGINT,]), ("SigBlk", [signal.SIGTTIN, signal.SIGILL, signal.SIGTERM,]), ]: for signum in signals_to_test_for: # Use signum - 1 because the bitmap is 0-indexed but represents signals strting at 1 assert (signum - 1) in props[signal_set_name].nonzero(), "{0} ({1}) is missing in {2}!".format(SIGNUM_TO_SIGNAME[signum], signum, signal_set_name) # Test parent death signal handling. if not args_disabled: print "Running parent death signal test" f = tempfile.NamedTemporaryFile() try: p = subprocess.Popen( [os.path.join(src, "test", "pdeathsignal", "stage_1.py"), tini, f.name], stdout=DEVNULL, stderr=DEVNULL ) p.wait() busy_wait(lambda: open(f.name).read() == "ok", 10) finally: f.close() print "---------------------------" print "All done, tests as expected" print "---------------------------" if __name__ == "__main__": main() tini-0.18.0/test/run_outer_tests.py000077500000000000000000000135101326656433400173570ustar00rootroot00000000000000#coding:utf-8 import os import sys import time import pipes import subprocess import threading import pexpect import signal class ReturnContainer(): def __init__(self): self.value = None class Command(object): def __init__(self, cmd, fail_cmd, post_cmd=None, post_delay=0): self.cmd = cmd self.fail_cmd = fail_cmd self.post_cmd = post_cmd self.post_delay = post_delay self.proc = None def run(self, timeout=None, retcode=0): print "Testing '{0}'...".format(" ".join(pipes.quote(s) for s in self.cmd)), sys.stdout.flush() err = None pipe_kwargs = {"stdout": subprocess.PIPE, "stderr": subprocess.PIPE, "stdin": subprocess.PIPE} def target(): self.proc = subprocess.Popen(self.cmd, **pipe_kwargs) self.stdout, self.stderr = self.proc.communicate() thread = threading.Thread(target=target) thread.daemon = True thread.start() if self.post_cmd is not None: time.sleep(self.post_delay) subprocess.check_call(self.post_cmd, **pipe_kwargs) thread.join(timeout - self.post_delay if timeout is not None else timeout) # Checks if thread.is_alive(): subprocess.check_call(self.fail_cmd, **pipe_kwargs) err = Exception("Test failed with timeout!") elif self.proc.returncode != retcode: err = Exception("Test failed with unexpected returncode (expected {0}, got {1})".format(retcode, self.proc.returncode)) if err is not None: print "FAIL" print "--- STDOUT ---" print getattr(self, "stdout", "no stdout") print "--- STDERR ---" print getattr(self, "stderr", "no stderr") print "--- ... ---" raise err else: print "OK" 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): print "Testing TTY handling (using container command '{0}' and exit function '{1}')".format(container_command, exit_function.__name__) rc = ReturnContainer() shell_ready_event = threading.Event() def spawn(): 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() thread = threading.Thread(target=spawn) thread.daemon = True thread.start() if not shell_ready_event.wait(2): raise Exception("Timeout waiting for shell to spawn") exit_function(name) thread.join(timeout=2) if thread.is_alive(): subprocess.check_call(fail_cmd) raise Exception("Timeout waiting for container to exit!") if rc.value != expect_exit_code: raise Exception("Return code is: {0} (expected {1})".format(rc.value, expect_exit_code)) 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__), "..")) base_cmd = [ "docker", "run", "--rm", "--volume={0}:/tini".format(root), "--name={0}".format(name), ] fail_cmd = ["docker", "kill", "-s", "KILL", name] # Funtional tests for entrypoint in ["/tini/dist/tini", "/tini/dist/tini-static"]: functional_base_cmd = base_cmd + [ "--entrypoint={0}".format(entrypoint), "-e", "TINI_VERBOSITY=3", img, ] # Reaping test Command(functional_base_cmd + ["/tini/test/reaping/stage_1.py"], fail_cmd).run(timeout=10) # Signals test for sig, retcode in [("TERM", 143), ("USR1", 138), ("USR2", 140)]: Command( 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=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) Command(functional_base_cmd + ["-w"], fail_cmd).run(retcode=127 if args_disabled else 0) # 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(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 [ ["ubuntu:precise", "dpkg", "deb"], ["ubuntu:trusty", "dpkg", "deb"], ["centos:6", "rpm", "rpm"], ["centos:7", "rpm", "rpm"], ]: Command(base_cmd + [image, "sh", "-c", "{0} -i /tini/dist/*.{1} && /usr/bin/tini true".format(pkg_manager, extension)], fail_cmd).run() if __name__ == "__main__": main() tini-0.18.0/test/sigconf/000077500000000000000000000000001326656433400151665ustar00rootroot00000000000000tini-0.18.0/test/sigconf/sigconf-test.c000066400000000000000000000020351326656433400177370ustar00rootroot00000000000000/* Test program to: + Ignore a few signals + Block a few signals + Exec whatever the test runner asked for */ #include #include #include int main(int argc, char *argv[]) { // Signals to ignore signal(SIGTTOU, SIG_IGN); // This one should still be in SigIgn (Tini touches it to ignore it, and should restore it) signal(SIGSEGV, SIG_IGN); // This one should still be in SigIgn (Tini shouldn't touch it) signal(SIGINT, SIG_IGN); // This one should still be in SigIgn (Tini should block it to forward it, and restore it) // Signals to block sigset_t set; sigemptyset(&set); sigaddset(&set, SIGTTIN); // This one should still be in SigIgn (Tini touches it to ignore it, and should restore it) sigaddset(&set, SIGILL); // This one should still be in SigIgn (Tini shouldn't touch it) sigaddset(&set, SIGTERM); // This one should still be in SigIgn (Tini should block it to forward it, and restore it) sigprocmask(SIG_BLOCK, &set, NULL); // Run whatever we were asked to run execvp(argv[1], argv+1); } tini-0.18.0/test/signals/000077500000000000000000000000001326656433400151765ustar00rootroot00000000000000tini-0.18.0/test/signals/test.py000077500000000000000000000004241326656433400165320ustar00rootroot00000000000000#!/usr/bin/env python import signal import os def main(): signal.signal(signal.SIGTERM, signal.SIG_DFL) signal.signal(signal.SIGUSR1, signal.SIG_DFL) signal.signal(signal.SIGUSR2, signal.SIG_DFL) os.system("sleep 100") if __name__ == "__main__": main() tini-0.18.0/test/subreaper-proxy.py000077500000000000000000000004211326656433400172570ustar00rootroot00000000000000#!/usr/bin/env python #coding:utf-8 import os import sys import prctl def main(): args = sys.argv[1:] print "subreaper-proxy: running '%s'" % (" ".join(args)) prctl.set_child_subreaper(1) os.execv(args[0], args) if __name__ == '__main__': main() tini-0.18.0/tpl/000077500000000000000000000000001326656433400133565ustar00rootroot00000000000000tini-0.18.0/tpl/README.md.in000066400000000000000000000214141326656433400152440ustar00rootroot00000000000000 Tini - A tiny but valid `init` for containers ============================================= [![Build Status](https://travis-ci.org/krallin/tini.svg?branch=master)](https://travis-ci.org/krallin/tini) Tini is the simplest `init` you could think of. All Tini does is spawn a single child (Tini is meant to be run in a container), and wait for it to exit all the while reaping zombies and performing signal forwarding. Why Tini? --------- Using Tini has several benefits: - It protects you from software that accidentally creates zombie processes, which can (over time!) starve your entire system for PIDs (and make it unusable). - It ensures that the *default signal handlers* work for the software you run in your Docker image. For example, with Tini, `SIGTERM` properly terminates your process even if you didn't explicitly install a signal handler for it. - It does so completely transparently! Docker images that work without Tini will work with Tini without any changes. If you'd like more detail on why this is useful, review this issue discussion: [What is advantage of Tini?][0]. Using Tini ---------- *NOTE: If you are using Docker 1.13 or greater, Tini is included in Docker itself. This includes all versions of Docker CE. To enable Tini, just [pass the `--init` flag to `docker run`][5].* *NOTE: There are [pre-built Docker images available for Tini][10]. If you're currently using an Ubuntu or CentOS image as your base, you can use one of those as a drop-in replacement.* *NOTE: There are Tini packages for Alpine Linux and NixOS. See below for installation instructions.* Add Tini to your container, and make it executable. Then, just invoke Tini and pass your program and its arguments as arguments to Tini. 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 v@tini_VERSION_MAJOR@.@tini_VERSION_MINOR@.@tini_VERSION_PATCH@ ADD https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini /tini RUN chmod +x /tini ENTRYPOINT ["/tini", "--"] # Run your program under Tini CMD ["/your/program", "-and", "-its", "arguments"] # or docker run your-image /your/program ... Note that you *can* skip the `--` under certain conditions, but you might as well always include it to be safe. If you see an error message that looks like `tini: invalid option -- 'c'`, then you *need* to add the `--`. Arguments for Tini itself should be passed like `-v` in the following example: `/tini -v -- /your/program`. *NOTE: The binary linked above is a 64-bit dynamically-linked binary.* ### Signed binaries ### The `tini` and `tini-static` binaries are signed using the key `595E85A6B1B4779EA4DAAEC70B588DFF0527A9B7`. You can verify their signatures using `gpg` (which you may install using your package manager): ENV TINI_VERSION v@tini_VERSION_MAJOR@.@tini_VERSION_MINOR@.@tini_VERSION_PATCH@ 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 hkp://p80.pool.sks-keyservers.net:80 --recv-keys 595E85A6B1B4779EA4DAAEC70B588DFF0527A9B7 \ && gpg --verify /tini.asc ### Alpine Linux Package ### On Alpine Linux, you can use the following command to install Tini: RUN apk add --no-cache tini # Tini is now available at /sbin/tini ENTRYPOINT ["/sbin/tini", "--"] ### NixOS ### Using Nix, you can use the following command to install Tini: nix-env --install tini ### Other Platforms ### ARM and 32-bit binaries are available! You can find the complete list of available binaries under [the releases tab][11]. Options ------- ### Verbosity ### The `-v` argument can be used for extra verbose output (you can pass it up to 3 times, e.g. `-vvv`). ### Subreaping ### By default, Tini needs to run as PID 1 so that it can reap zombies (by running as PID 1, zombies get re-parented to Tini). If for some reason, you cannot run Tini as PID 1, you should register Tini as a process subreaper instead (only in Linux >= 3.4), by either: + Passing the `-s` argument to Tini (`tini -s -- ...`) + Setting the environment variable `TINI_SUBREAPER` (e.g. `export TINI_SUBREAPER=`). This will ensure that zombies get re-parented to Tini despite Tini not running as PID 1. *NOTE: Tini will issue a warning if it detects that it isn't running as PID 1 and isn't registered as a subreaper. If you don't see a warning, you're fine.* ### Remapping exit codes ### Tini will reuse the child's exit code when exiting, but occasionally, this may not be exactly what you want (e.g. if your child exits with 143 after receiving SIGTERM). Notably, this can be an issue with Java apps. In this case, you can use the `-e` flag to remap an arbitrary exit code to 0. You can pass the flag multiple times if needed. For example: ``` tini -e 143 -- ... ``` ### Process group killing ### By default, Tini only kills its immediate child process. This can be inconvenient if sending a signal to that process does not have the desired effect. For example, if you do docker run krallin/ubuntu-tini sh -c 'sleep 10' and ctrl-C it, nothing happens: SIGINT is sent to the 'sh' process, but that shell won't react to it while it is waiting for the 'sleep' to finish. With the `-g` option, Tini kills the child process group , so that every process in the group gets the signal. This corresponds more closely to what happens when you do ctrl-C etc. in a terminal: The signal is sent to the foreground process group. ### Parent Death Signal ### Tini can set its parent death signal, which is the signal Tini should receive when *its* parent exits. To set the parent death signal, use the `-p` flag with the name of the signal Tini should receive when its parent exits: ``` tini -p SIGTERM -- ... ``` *NOTE: See [this PR discussion][12] to learn more about the parent death signal and use cases.* More ---- ### Existing Entrypoint ### Tini can also be used with an existing entrypoint in your container! Assuming your entrypoint was `/docker-entrypoint.sh`, then you would use: ENTRYPOINT ["/tini", "--", "/docker-entrypoint.sh"] ### Statically-Linked Version ### Tini has very few dependencies (it only depends on libc), but if your container fails to start, you might want to consider using the statically-built version instead: ADD https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini-static /tini ### Size Considerations ### Tini is a very small file (in the 10KB range), so it doesn't add much weight to your container. The statically-linked version is bigger, but still < 1M. Building Tini ------------- If you'd rather not download the binary, you can build Tini by running `cmake . && make`. Before building, you probably also want to run: export CFLAGS="-DPR_SET_CHILD_SUBREAPER=36 -DPR_GET_CHILD_SUBREAPER=37" This ensure that even if you're building on a system that has old Linux Kernel headers (< 3.4), Tini will be built with child subreaper support. This is usually what you want if you're going to use Tini with Docker (if your host Kernel supports Docker, it should also support child subreapers). Understanding Tini ------------------ After spawning your process, Tini will wait for signals and forward those to the child process, and periodically reap zombie processes that may be created within your container. When the "first" child process exits (`/your/program` in the examples above), Tini exits as well, with the exit code of the child process (so you can check your container's exit code to know whether the child exited successfully). Debugging --------- If something isn't working just like you expect, consider increasing the verbosity level (up to 3): tini -v -- bash -c 'exit 1' tini -vv -- true tini -vvv -- pwd Authors ======= Maintainer: + [Thomas Orozco][20] Contributors: + [Tianon Gravi][30] + [David Wragg][31] + [Michael Crosby][32] + [Wyatt Preul][33] + [Patrick Steinhardt][34] Special thanks to: + [Danilo Bürger][40] for packaging Tini for Alpine + [Asko Soukka][41] for packaging Tini for Nix [0]: https://github.com/krallin/tini/issues/8 [5]: https://docs.docker.com/engine/reference/commandline/run/ [10]: https://github.com/krallin/tini-images [11]: https://github.com/krallin/tini/releases [12]: https://github.com/krallin/tini/pull/114 [20]: https://github.com/krallin/ [30]: https://github.com/tianon [31]: https://github.com/dpw [32]: https://github.com/crosbymichael [33]: https://github.com/geek [34]: https://github.com/pks-t [40]: https://github.com/danilobuerger [41]: https://github.com/datakurre tini-0.18.0/tpl/VERSION.in000066400000000000000000000000771326656433400150370ustar00rootroot00000000000000@tini_VERSION_MAJOR@.@tini_VERSION_MINOR@.@tini_VERSION_PATCH@