├── .dockerignore ├── .gitignore ├── .travis.yml ├── CMakeLists.txt ├── Dockerfile ├── LICENSE ├── README.md ├── ci ├── install_deps.sh ├── run_build.sh └── util │ └── rpmbuild ├── ddist.sh ├── dtest.sh ├── run_tests.sh ├── sign.key.enc ├── src ├── tini.c ├── tiniConfig.h.in └── tiniLicense.h ├── test ├── pdeathsignal │ ├── stage_1.py │ └── stage_2.py ├── pgroup │ └── stage_1.py ├── reaping │ ├── stage_1.py │ └── stage_2.py ├── run_inner_tests.py ├── run_outer_tests.py ├── sigconf │ └── sigconf-test.c ├── signals │ └── test.py └── subreaper-proxy.py └── tpl ├── README.md.in └── VERSION.in /.dockerignore: -------------------------------------------------------------------------------- 1 | ./dist 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | sign.key 3 | .env 4 | .mypy_cache 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: required 2 | services: 3 | - docker 4 | 5 | language: generic 6 | 7 | env: 8 | matrix: 9 | - ARCH_SUFFIX= CC=gcc ARCH_NATIVE=1 MINIMAL= 10 | - ARCH_SUFFIX=amd64 CC=gcc ARCH_NATIVE=1 MINIMAL= 11 | - ARCH_SUFFIX=amd64 CC=gcc ARCH_NATIVE=1 MINIMAL=1 12 | - ARCH_SUFFIX=x86_64 CC=gcc ARCH_NATIVE=1 MINIMAL= 13 | - ARCH_SUFFIX=x86_64 CC=gcc ARCH_NATIVE=1 MINIMAL=1 14 | - ARCH_SUFFIX=arm64 CC=aarch64-linux-gnu-gcc ARCH_NATIVE= MINIMAL= 15 | - ARCH_SUFFIX=armel CC=arm-linux-gnueabi-gcc ARCH_NATIVE= MINIMAL= 16 | - ARCH_SUFFIX=armhf CC=arm-linux-gnueabihf-gcc ARCH_NATIVE= MINIMAL= 17 | - ARCH_SUFFIX=i386 CFLAGS="-m32" ARCH_NATIVE= MINIMAL= 18 | - ARCH_SUFFIX=muslc-amd64 CC=musl-gcc ARCH_NATIVE=1 MINIMAL= 19 | - ARCH_SUFFIX=ppc64el CC=powerpc64le-linux-gnu-gcc ARCH_NATIVE= MINIMAL= 20 | - ARCH_SUFFIX=ppc64le CC=powerpc64le-linux-gnu-gcc ARCH_NATIVE= MINIMAL= 21 | - ARCH_SUFFIX=s390x CC=s390x-linux-gnu-gcc ARCH_NATIVE= MINIMAL= 22 | - ARCH_SUFFIX=mips64el CC=mips64el-linux-gnuabi64-gcc-5 ARCH_NATIVE= MINIMAL= 23 | global: 24 | - secure: "RKF9Z9gLxp6k/xITqn7ma1E9HfpYcDXuJFf4862WeH9EMnK9lDq+TWnGsQfkIlqh8h9goe7U+BvRiTibj9MiD5u7eluLo3dlwsLxPpYtyswYeLeC1wKKdT5LPGAXbRKomvBalRYMI+dDnGIM4w96mHgGGvx2zZXGkiAQhm6fJ3k=" 25 | - DIST_DIR="${PWD}/dist" 26 | 27 | before_install: 28 | - 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" 29 | 30 | script: 31 | - ./ddist.sh 32 | - ls -lah "$DIST_DIR" 33 | - git diff --exit-code 34 | 35 | deploy: 36 | provider: releases 37 | api_key: 38 | secure: VaYWmhdyhPYNvUy0tlGBYdjsdHIGHh/jwYzC96DBLS9BYIErtBkm21sdCLPKuNI1mGOwqoUjY+WywV2zJaBG10iBQCuFLpA9bblnN4fi257m79z7zqMbCvM145Up9x2jMQ0v03avd6pxCfQsr8WC9fnhWVYaD68Ir/hTpjZ60u8= 39 | file: "${DIST_DIR}/*" 40 | file_glob: true 41 | skip_cleanup: true 42 | on: 43 | repo: krallin/tini 44 | tags: true 45 | condition: '-z "$MINIMAL"' 46 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required (VERSION 2.8.12...3.10) 2 | project (tini C) 3 | 4 | # Config 5 | set (tini_VERSION_MAJOR 0) 6 | set (tini_VERSION_MINOR 19) 7 | set (tini_VERSION_PATCH 0) 8 | 9 | # Build options 10 | option(MINIMAL "Disable argument parsing and verbose output" OFF) 11 | 12 | if(MINIMAL) 13 | add_definitions(-DTINI_MINIMAL=1) 14 | endif() 15 | 16 | # Extract git version and dirty-ness 17 | execute_process ( 18 | COMMAND git --git-dir "${PROJECT_SOURCE_DIR}/.git" --work-tree "${PROJECT_SOURCE_DIR}" log -n 1 --date=local --pretty=format:%h 19 | WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}" 20 | RESULT_VARIABLE git_version_check_ret 21 | OUTPUT_VARIABLE tini_VERSION_GIT 22 | ) 23 | 24 | execute_process( 25 | COMMAND git --git-dir "${PROJECT_SOURCE_DIR}/.git" --work-tree "${PROJECT_SOURCE_DIR}" status --porcelain --untracked-files=no 26 | WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}" 27 | OUTPUT_VARIABLE git_dirty_check_out 28 | ) 29 | 30 | if("${git_version_check_ret}" EQUAL 0) 31 | set(tini_VERSION_GIT " - git.${tini_VERSION_GIT}") 32 | if(NOT "${git_dirty_check_out}" STREQUAL "") 33 | set(tini_VERSION_GIT "${tini_VERSION_GIT}-dirty") 34 | endif() 35 | else() 36 | set(tini_VERSION_GIT "") 37 | endif() 38 | 39 | # Flags 40 | include(CheckCSourceCompiles) 41 | 42 | check_c_source_compiles(" 43 | #ifndef _FORTIFY_SOURCE 44 | #error \"Not defined: _FORTIFY_SOURCE\" 45 | #endif 46 | int main(void) { 47 | return 0; 48 | } 49 | " HAS_BUILTIN_FORTIFY) 50 | 51 | # Flags 52 | if(NOT HAS_BUILTIN_FORTIFY) 53 | add_definitions(-D_FORTIFY_SOURCE=2) 54 | endif() 55 | 56 | set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=gnu99 -Werror -Wextra -Wall -pedantic-errors -O2 -fstack-protector --param=ssp-buffer-size=4 -Wformat") 57 | set (CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,-Bsymbolic-functions -Wl,-z,relro -Wl,-s") 58 | 59 | # Build 60 | 61 | configure_file ( 62 | "${PROJECT_SOURCE_DIR}/src/tiniConfig.h.in" 63 | "${PROJECT_BINARY_DIR}/tiniConfig.h" 64 | @ONLY 65 | ) 66 | 67 | configure_file ( 68 | "${PROJECT_SOURCE_DIR}/tpl/README.md.in" 69 | "${PROJECT_SOURCE_DIR}/README.md" 70 | @ONLY 71 | ) 72 | 73 | configure_file ( 74 | "${PROJECT_SOURCE_DIR}/tpl/VERSION.in" 75 | "${PROJECT_BINARY_DIR}/VERSION" 76 | @ONLY 77 | ) 78 | 79 | 80 | include_directories ("${PROJECT_BINARY_DIR}") 81 | 82 | add_executable (tini src/tini.c) 83 | 84 | add_executable (tini-static src/tini.c) 85 | set_target_properties (tini-static PROPERTIES LINK_FLAGS "-Wl,--no-export-dynamic -static") 86 | 87 | # Installation 88 | install (TARGETS tini DESTINATION bin) 89 | install (TARGETS tini-static DESTINATION bin) 90 | 91 | # Packaging 92 | include (InstallRequiredSystemLibraries) 93 | set (CPACK_PACKAGE_DESCRIPTION_SUMMARY "A tiny but valid init process for containers") 94 | set (CPACK_PACKAGE_VENDOR "Thomas Orozco") 95 | set (CPACK_PACKAGE_CONTACT "thomas@orozco.fr") 96 | set (CPACK_PACKAGE_DESCRIPTION_FILE "${CMAKE_CURRENT_SOURCE_DIR}/README.md") 97 | set (CPACK_RESOURCE_FILE_LICENSE "${CMAKE_CURRENT_SOURCE_DIR}/LICENSE") 98 | set (CPACK_PACKAGE_VERSION_MAJOR "${tini_VERSION_MAJOR}") 99 | set (CPACK_PACKAGE_VERSION_MINOR "${tini_VERSION_MINOR}") 100 | set (CPACK_PACKAGE_VERSION_PATCH "${tini_VERSION_PATCH}") 101 | set (CPACK_PACKAGE_EXECUTABLES "${CMAKE_PROJECT_NAME}") 102 | set (CPACK_PACKAGE_NAME "${CMAKE_PROJECT_NAME}") 103 | set (CPACK_PACKAGE_FILE_NAME "${CMAKE_PROJECT_NAME}_${tini_VERSION_MAJOR}.${tini_VERSION_MINOR}.${tini_VERSION_PATCH}") 104 | set (CPACK_PACKAGE_VERSION "${tini_VERSION_MAJOR}.${tini_VERSION_MINOR}.${tini_VERSION_PATCH}") 105 | 106 | set (CPACK_DEBIAN_PACKAGE_ARCHITECTURE "amd64") # TODO 107 | set (CPACK_DEBIAN_PACKAGE_DEPENDS "libc6 (>= 2.3.4)") 108 | 109 | set (CPACK_RPM_PACKAGE_ARCHITECTURE "x86_64") 110 | 111 | set (CPACK_GENERATOR "DEB" "RPM") 112 | 113 | include (CPack) 114 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:xenial 2 | 3 | ARG ARCH_SUFFIX 4 | 5 | COPY ci/install_deps.sh /install_deps.sh 6 | RUN /install_deps.sh 7 | 8 | # Pre-install those here for faster local builds. 9 | RUN CFLAGS="-DPR_SET_CHILD_SUBREAPER=36 -DPR_GET_CHILD_SUBREAPER=37" python3 -m pip install psutil python-prctl bitmap 10 | 11 | ARG ARCH_NATIVE 12 | ARG CC 13 | 14 | # Persist ARGs into the image 15 | 16 | ENV ARCH_SUFFIX="$ARCH_SUFFIX" \ 17 | ARCH_NATIVE="$ARCH_NATIVE" \ 18 | CC="$CC" 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Thomas Orozco 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 9 | 10 | 11 | Tini - A tiny but valid `init` for containers 12 | ============================================= 13 | 14 | [![Build Status](https://travis-ci.org/krallin/tini.svg?branch=master)](https://travis-ci.org/krallin/tini) 15 | 16 | Tini is the simplest `init` you could think of. 17 | 18 | All Tini does is spawn a single child (Tini is meant to be run in a container), 19 | and wait for it to exit all the while reaping zombies and performing 20 | signal forwarding. 21 | 22 | 23 | Why Tini? 24 | --------- 25 | 26 | Using Tini has several benefits: 27 | 28 | - It protects you from software that accidentally creates zombie processes, 29 | which can (over time!) starve your entire system for PIDs (and make it 30 | unusable). 31 | - It ensures that the *default signal handlers* work for the software you run 32 | in your Docker image. For example, with Tini, `SIGTERM` properly terminates 33 | your process even if you didn't explicitly install a signal handler for it. 34 | - It does so completely transparently! Docker images that work without Tini 35 | will work with Tini without any changes. 36 | 37 | If you'd like more detail on why this is useful, review this issue discussion: 38 | [What is advantage of Tini?][0]. 39 | 40 | 41 | Using Tini 42 | ---------- 43 | 44 | *NOTE: If you are using Docker 1.13 or greater, Tini is included in Docker 45 | itself. This includes all versions of Docker CE. To enable Tini, just [pass the 46 | `--init` flag to `docker run`][5].* 47 | 48 | *NOTE: There are [pre-built Docker images available for Tini][10]. If 49 | you're currently using an Ubuntu or CentOS image as your base, you can use 50 | one of those as a drop-in replacement.* 51 | 52 | *NOTE: There are Tini packages for Alpine Linux and NixOS. See below for 53 | installation instructions.* 54 | 55 | Add Tini to your container, and make it executable. Then, just invoke Tini 56 | and pass your program and its arguments as arguments to Tini. 57 | 58 | In Docker, you will want to use an entrypoint so you don't have to remember 59 | to manually invoke Tini: 60 | 61 | # Add Tini 62 | ENV TINI_VERSION=v0.19.0 63 | ADD https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini /tini 64 | RUN chmod +x /tini 65 | ENTRYPOINT ["/tini", "--"] 66 | 67 | # Run your program under Tini 68 | CMD ["/your/program", "-and", "-its", "arguments"] 69 | # or docker run your-image /your/program ... 70 | 71 | Note that you *can* skip the `--` under certain conditions, but you might 72 | as well always include it to be safe. If you see an error message that 73 | looks like `tini: invalid option -- 'c'`, then you *need* to add the `--`. 74 | 75 | Arguments for Tini itself should be passed like `-v` in the following example: 76 | `/tini -v -- /your/program`. 77 | 78 | *NOTE: The binary linked above is a 64-bit dynamically-linked binary.* 79 | 80 | 81 | ### Signed binaries ### 82 | 83 | The `tini` and `tini-static` binaries are signed using the key `595E85A6B1B4779EA4DAAEC70B588DFF0527A9B7`. 84 | 85 | You can verify their signatures using `gpg` (which you may install using 86 | your package manager): 87 | 88 | ENV TINI_VERSION v0.19.0 89 | ADD https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini /tini 90 | ADD https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini.asc /tini.asc 91 | RUN gpg --batch --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 595E85A6B1B4779EA4DAAEC70B588DFF0527A9B7 \ 92 | && gpg --batch --verify /tini.asc /tini 93 | RUN chmod +x /tini 94 | 95 | 96 | ### Verifying binaries via checksum ### 97 | 98 | The `tini` and `tini-static` binaries have generated checksums (`SHA1` and `SHA256`). 99 | 100 | You can verify their checksums using `sha1sum` and `sha256sum` (which you may install using 101 | your package manager): 102 | 103 | ENV TINI_VERSION v0.19.0 104 | RUN wget --no-check-certificate --no-cookies --quiet https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini-amd64 \ 105 | && wget --no-check-certificate --no-cookies --quiet https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini-amd64.sha256sum \ 106 | && echo "$(cat tini-amd64.sha256sum)" | sha256sum -c 107 | 108 | 109 | ### Alpine Linux Package ### 110 | 111 | On Alpine Linux, you can use the following command to install Tini: 112 | 113 | RUN apk add --no-cache tini 114 | # Tini is now available at /sbin/tini 115 | ENTRYPOINT ["/sbin/tini", "--"] 116 | 117 | 118 | ### NixOS ### 119 | 120 | Using Nix, you can use the following command to install Tini: 121 | 122 | nix-env --install tini 123 | 124 | 125 | ### Debian ### 126 | 127 | On Debian (Buster or newer), you can use the following command to install Tini: 128 | 129 | apt-get install tini 130 | 131 | Note that this installs `/usr/bin/tini` (and `/usr/bin/tini-static`), not `/tini`. 132 | 133 | 134 | ### Arch Linux ### 135 | 136 | On Arch Linux, there is a package available on the [AUR](https://wiki.archlinux.org/index.php/Arch_User_Repository). 137 | Install using the [official instructions](https://wiki.archlinux.org/index.php/Arch_User_Repository#Installing_packages) 138 | or use an [AUR helper](https://wiki.archlinux.org/index.php/AUR_helpers): 139 | 140 | pacaur -S tini 141 | 142 | 143 | ### Other Platforms ### 144 | 145 | ARM and 32-bit binaries are available! You can find the complete list of 146 | available binaries under [the releases tab][11]. 147 | 148 | 149 | Options 150 | ------- 151 | 152 | ### Verbosity ### 153 | 154 | The `-v` argument can be used for extra verbose output (you can pass it up to 155 | 3 times, e.g. `-vvv`). 156 | 157 | 158 | ### Subreaping ### 159 | 160 | By default, Tini needs to run as PID 1 so that it can reap zombies (by 161 | running as PID 1, zombies get re-parented to Tini). 162 | 163 | If for some reason, you cannot run Tini as PID 1, you should register Tini as 164 | a process subreaper instead (only in Linux >= 3.4), by either: 165 | 166 | + Passing the `-s` argument to Tini (`tini -s -- ...`) 167 | + Setting the environment variable `TINI_SUBREAPER` 168 | (e.g. `export TINI_SUBREAPER=`). 169 | 170 | This will ensure that zombies get re-parented to Tini despite Tini not running 171 | as PID 1. 172 | 173 | *NOTE: Tini will issue a warning if it detects that it isn't running as PID 1 174 | and isn't registered as a subreaper. If you don't see a warning, you're fine.* 175 | 176 | 177 | ### Remapping exit codes ### 178 | 179 | Tini will reuse the child's exit code when exiting, but occasionally, this may 180 | not be exactly what you want (e.g. if your child exits with 143 after receiving 181 | SIGTERM). Notably, this can be an issue with Java apps. 182 | 183 | In this case, you can use the `-e` flag to remap an arbitrary exit code to 0. 184 | You can pass the flag multiple times if needed. 185 | 186 | For example: 187 | 188 | ``` 189 | tini -e 143 -- ... 190 | ``` 191 | 192 | 193 | ### Process group killing ### 194 | 195 | By default, Tini only kills its immediate child process. This can be 196 | inconvenient if sending a signal to that process does not have the desired 197 | effect. For example, if you do 198 | 199 | docker run --rm krallin/ubuntu-tini sh -c 'sleep 10' 200 | 201 | and ctrl-C it, nothing happens: SIGINT is sent to the 'sh' process, 202 | but that shell won't react to it while it is waiting for the 'sleep' 203 | to finish. 204 | 205 | You can configure Tini to kill the child process group, so that every process 206 | in the group gets the signal, by either: 207 | 208 | + Passing the `-g` argument to Tini (`tini -g -- ...`) 209 | + Setting the environment variable `TINI_KILL_PROCESS_GROUP` 210 | (e.g. `export TINI_KILL_PROCESS_GROUP=`). 211 | 212 | This corresponds more closely to what happens when you do ctrl-C etc. in a 213 | terminal: The signal is sent to the foreground process group. 214 | 215 | docker run --rm --entrypoint tini krallin/ubuntu-tini -g -- sh -c 'sleep 10' 216 | 217 | ### Parent Death Signal ### 218 | 219 | Tini can set its parent death signal, which is the signal Tini should receive 220 | when *its* parent exits. To set the parent death signal, use the `-p` flag with 221 | the name of the signal Tini should receive when its parent exits: 222 | 223 | ``` 224 | tini -p SIGTERM -- ... 225 | ``` 226 | 227 | *NOTE: See [this PR discussion][12] to learn more about the parent death signal 228 | and use cases.* 229 | 230 | 231 | More 232 | ---- 233 | 234 | ### Existing Entrypoint ### 235 | 236 | Tini can also be used with an existing entrypoint in your container! 237 | 238 | Assuming your entrypoint was `/docker-entrypoint.sh`, then you would use: 239 | 240 | ENTRYPOINT ["/tini", "--", "/docker-entrypoint.sh"] 241 | 242 | 243 | ### Statically-Linked Version ### 244 | 245 | Tini has very few dependencies (it only depends on libc), but if your 246 | container fails to start, you might want to consider using the statically-built 247 | version instead: 248 | 249 | ADD https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini-static /tini 250 | 251 | 252 | ### Size Considerations ### 253 | 254 | Tini is a very small file (in the 10KB range), so it doesn't add much weight 255 | to your container. 256 | 257 | The statically-linked version is bigger, but still < 1M. 258 | 259 | 260 | Building Tini 261 | ------------- 262 | 263 | If you'd rather not download the binary, you can build Tini by running 264 | `cmake . && make`. 265 | 266 | Before building, you probably also want to run: 267 | 268 | export CFLAGS="-DPR_SET_CHILD_SUBREAPER=36 -DPR_GET_CHILD_SUBREAPER=37" 269 | 270 | This ensure that even if you're building on a system that has old Linux Kernel 271 | headers (< 3.4), Tini will be built with child subreaper support. This is 272 | usually what you want if you're going to use Tini with Docker (if your host 273 | Kernel supports Docker, it should also support child subreapers). 274 | 275 | 276 | Understanding Tini 277 | ------------------ 278 | 279 | After spawning your process, Tini will wait for signals and forward those 280 | to the child process, and periodically reap zombie processes that may be 281 | created within your container. 282 | 283 | When the "first" child process exits (`/your/program` in the examples above), 284 | Tini exits as well, with the exit code of the child process (so you can 285 | check your container's exit code to know whether the child exited 286 | successfully). 287 | 288 | 289 | Debugging 290 | --------- 291 | 292 | If something isn't working just like you expect, consider increasing the 293 | verbosity level (up to 3): 294 | 295 | tini -v -- bash -c 'exit 1' 296 | tini -vv -- true 297 | tini -vvv -- pwd 298 | 299 | 300 | Authors 301 | ======= 302 | 303 | Maintainer: 304 | 305 | + [Thomas Orozco][20] 306 | 307 | Contributors: 308 | 309 | + [Tianon Gravi][30] 310 | + [David Wragg][31] 311 | + [Michael Crosby][32] 312 | + [Wyatt Preul][33] 313 | + [Patrick Steinhardt][34] 314 | 315 | Special thanks to: 316 | 317 | + [Danilo Bürger][40] for packaging Tini for Alpine 318 | + [Asko Soukka][41] for packaging Tini for Nix 319 | + [nfnty][42] for packaging Tini for Arch Linux 320 | 321 | 322 | [0]: https://github.com/krallin/tini/issues/8 323 | [5]: https://docs.docker.com/engine/reference/commandline/run/ 324 | [10]: https://github.com/krallin/tini-images 325 | [11]: https://github.com/krallin/tini/releases 326 | [12]: https://github.com/krallin/tini/pull/114 327 | [20]: https://github.com/krallin/ 328 | [30]: https://github.com/tianon 329 | [31]: https://github.com/dpw 330 | [32]: https://github.com/crosbymichael 331 | [33]: https://github.com/geek 332 | [34]: https://github.com/pks-t 333 | [40]: https://github.com/danilobuerger 334 | [41]: https://github.com/datakurre 335 | [42]: https://github.com/nfnty/pkgbuilds/tree/master/tini/tini 336 | -------------------------------------------------------------------------------- /ci/install_deps.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -o errexit 3 | set -o nounset 4 | set -o xtrace 5 | 6 | DEPS=( 7 | build-essential git gdb valgrind cmake rpm file 8 | libcap-dev python3-dev python3-pip python3-setuptools 9 | hardening-includes gnupg 10 | ) 11 | 12 | case "${ARCH_SUFFIX-}" in 13 | amd64|x86_64|'') ;; 14 | arm64) DEPS+=(gcc-aarch64-linux-gnu binutils-aarch64-linux-gnu libc6-dev-arm64-cross) ;; 15 | armel) DEPS+=(gcc-arm-linux-gnueabi binutils-arm-linux-gnueabi libc6-dev-armel-cross) ;; 16 | armhf) DEPS+=(gcc-arm-linux-gnueabihf binutils-arm-linux-gnueabihf libc6-dev-armhf-cross) ;; 17 | i386) DEPS+=(libc6-dev-i386 gcc-multilib) ;; 18 | muslc-amd64) DEPS+=(musl-tools) ;; 19 | ppc64el|ppc64le) DEPS+=(gcc-powerpc64le-linux-gnu binutils-powerpc64le-linux-gnu libc6-dev-ppc64el-cross) ;; 20 | s390x) DEPS+=(gcc-s390x-linux-gnu binutils-s390x-linux-gnu libc6-dev-s390x-cross) ;; 21 | mips64el) DEPS+=(gcc-5-mips64el-linux-gnuabi64 binutils-mips64el-linux-gnuabi64 libc6-dev-mips64el-cross) ;; 22 | *) echo "Unknown ARCH_SUFFIX=${ARCH_SUFFIX-}"; exit 1 ;; 23 | esac 24 | 25 | apt-get update 26 | apt-get install --no-install-recommends --yes "${DEPS[@]}" 27 | rm -rf /var/lib/apt/lists/* 28 | 29 | python3 -m pip install --upgrade pip 30 | python3 -m pip install virtualenv 31 | -------------------------------------------------------------------------------- /ci/run_build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Should be run from the root dir, or SOURCE_DIR should be set. 3 | set -o errexit 4 | set -o nounset 5 | set -o pipefail 6 | 7 | # Default compiler 8 | : ${CC:="gcc"} 9 | 10 | 11 | # Paths 12 | : ${SOURCE_DIR:="."} 13 | : ${DIST_DIR:="${SOURCE_DIR}/dist"} 14 | : ${BUILD_DIR:="/tmp/build"} 15 | 16 | # GPG Configuration 17 | : ${GPG_PASSPHRASE:=""} 18 | 19 | 20 | # Make those paths absolute, and export them for the Python tests to consume. 21 | export SOURCE_DIR="$(readlink -f "${SOURCE_DIR}")" 22 | export DIST_DIR="$(readlink -f "${DIST_DIR}")" 23 | export BUILD_DIR="$(readlink -f "${BUILD_DIR}")" 24 | 25 | # Configuration 26 | : ${FORCE_SUBREAPER:="1"} 27 | export FORCE_SUBREAPER 28 | 29 | 30 | # Our build platform doesn't have those newer Linux flags, but we want Tini to have subreaper support 31 | # We also use those in our tests 32 | CFLAGS="${CFLAGS-} -DPR_SET_CHILD_SUBREAPER=36 -DPR_GET_CHILD_SUBREAPER=37" 33 | if [[ "${FORCE_SUBREAPER}" -eq 1 ]]; then 34 | # If FORCE_SUBREAPER is requested, then we set those CFLAGS for the Tini build 35 | export CFLAGS 36 | fi 37 | 38 | echo "CC=${CC}" 39 | echo "CFLAGS=${CFLAGS}" 40 | echo "MINIMAL=${MINIMAL-}" 41 | echo "ARCH_SUFFIX=${ARCH_SUFFIX-}" 42 | echo "ARCH_NATIVE=${ARCH_NATIVE-}" 43 | 44 | # Ensure Python output is not buffered (to make tests output clearer) 45 | export PYTHONUNBUFFERED=1 46 | 47 | # Set path to prioritize our utils 48 | export REAL_PATH="${PATH}" 49 | export PATH="${SOURCE_DIR}/ci/util:${PATH}" 50 | 51 | # Build 52 | CMAKE_ARGS=(-B"${BUILD_DIR}" -H"${SOURCE_DIR}") 53 | if [[ -n "${MINIMAL:-}" ]]; then 54 | CMAKE_ARGS+=(-DMINIMAL=ON) 55 | fi 56 | cmake "${CMAKE_ARGS[@]}" 57 | 58 | pushd "${BUILD_DIR}" 59 | make clean 60 | make 61 | if [[ -n "${ARCH_NATIVE-}" ]]; then 62 | make package 63 | fi 64 | popd 65 | 66 | pkg_version="$(cat "${BUILD_DIR}/VERSION")" 67 | 68 | if [[ -n "${ARCH_NATIVE-}" ]]; then 69 | echo "Built native package (ARCH_NATIVE=${ARCH_NATIVE-})" 70 | echo "Running smoke and internal tests" 71 | 72 | BIN_TEST_DIR="${BUILD_DIR}/bin-test" 73 | mkdir -p "$BIN_TEST_DIR" 74 | export PATH="${BIN_TEST_DIR}:${PATH}" 75 | 76 | # Smoke tests (actual tests need Docker to run; they don't run within the CI environment) 77 | for tini in "${BUILD_DIR}/tini" "${BUILD_DIR}/tini-static"; do 78 | echo "Smoke test for ${tini}" 79 | "$tini" --version 80 | 81 | echo "Testing ${tini} --version" 82 | "$tini" --version | grep -q "tini version" 83 | 84 | echo "Testing ${tini} without arguments exits with 1" 85 | ! "$tini" 2>/dev/null 86 | 87 | echo "Testing ${tini} shows help message" 88 | { 89 | ! "$tini" 2>&1 90 | } | grep -q "supervision of a valid init process" 91 | 92 | if [[ -n "${MINIMAL:-}" ]]; then 93 | echo "Testing $tini with: true" 94 | "${tini}" true 95 | 96 | echo "Testing $tini with: false" 97 | if "${tini}" false; then 98 | exit 1 99 | fi 100 | 101 | echo "Testing ${tini} does not reference options that don't exist" 102 | ! { 103 | ! "$tini" 2>&1 104 | } | grep -q "more verbose" 105 | 106 | # We try running binaries named after flags (both valid and invalid 107 | # flags) and test that they run. 108 | for flag in h s w x; do 109 | bin="-${flag}" 110 | echo "Testing $tini can run binary: ${bin}" 111 | cp "$(which true)" "${BIN_TEST_DIR}/${bin}" 112 | "$tini" "$bin" 113 | done 114 | 115 | echo "Testing $tini can run binary --version if args are given" 116 | cp "$(which true)" "${BIN_TEST_DIR}/--version" 117 | if "$tini" "--version" --foo | grep -q "tini version"; then 118 | exit 1 119 | fi 120 | else 121 | echo "Testing ${tini} -h" 122 | "${tini}" -h 123 | 124 | echo "Testing $tini for license" 125 | "$tini" -l | diff - "${SOURCE_DIR}/LICENSE" 126 | 127 | echo "Testing $tini with: true" 128 | "${tini}" -vvv true 129 | 130 | echo "Testing $tini with: false" 131 | if "${tini}" -vvv false; then 132 | exit 1 133 | fi 134 | 135 | echo "Testing ${tini} references options that exist" 136 | { 137 | ! "$tini" 2>&1 138 | } | grep -q "more verbose" 139 | 140 | echo "Testing $tini with: -- true (should succeed)" 141 | "${tini}" -vvv -- true 142 | 143 | echo "Testing $tini with: -- -- true (should fail)" 144 | if "${tini}" -vvv -- -- true; then 145 | exit 1 146 | fi 147 | fi 148 | 149 | echo "Testing ${tini} supports TINI_VERBOSITY" 150 | TINI_VERBOSITY=3 "$tini" true 2>&1 | grep -q 'Received SIGCHLD' 151 | 152 | echo "Testing ${tini} exits with 127 if the command does not exist" 153 | "$tini" foobar123 && rc="$?" || rc="$?" 154 | if [[ "$rc" != 127 ]]; then 155 | echo "Exit code was: ${rc}" 156 | exit 1 157 | fi 158 | 159 | echo "Testing ${tini} exits with 126 if the command is not executable" 160 | "$tini" /etc && rc="$?" || rc="$?" 161 | if [[ "$rc" != 126 ]]; then 162 | echo "Exit code was: ${rc}" 163 | exit 1 164 | fi 165 | 166 | # Test stdin / stdout are handed over to child 167 | echo "Testing ${tini} does not break pipes" 168 | echo "exit 0" | "${tini}" sh 169 | if [[ ! "$?" -eq "0" ]]; then 170 | echo "Pipe test failed" 171 | exit 1 172 | fi 173 | 174 | echo "Checking hardening on $tini" 175 | hardening_skip=(--nopie --nostackprotector --nobindnow) 176 | if [[ "$CC" == "musl-gcc" ]]; then 177 | # FORTIFY_SOURCE is a glibc thing 178 | hardening_skip=("${hardening_skip[@]}" --nofortify) 179 | fi 180 | hardening-check "${hardening_skip[@]}" "${tini}" 181 | done 182 | 183 | # Quick package audit 184 | if which rpm >/dev/null; then 185 | echo "Contents for RPM:" 186 | rpm -qlp "${BUILD_DIR}/tini_${pkg_version}.rpm" 187 | echo "--" 188 | fi 189 | 190 | if which dpkg >/dev/null; then 191 | echo "Contents for DEB:" 192 | dpkg --contents "${BUILD_DIR}/tini_${pkg_version}.deb" 193 | echo "--" 194 | fi 195 | 196 | # Compile test code 197 | "${CC}" -o "${BUILD_DIR}/sigconf-test" "${SOURCE_DIR}/test/sigconf/sigconf-test.c" 198 | 199 | # Create virtual environment to run tests. 200 | # Accept system site packages for faster local builds. 201 | VENV="${BUILD_DIR}/venv" 202 | virtualenv --system-site-packages "${VENV}" 203 | 204 | # Don't use activate because it does not play nice with nounset 205 | export PATH="${VENV}/bin:${PATH}" 206 | export CFLAGS # We need them to build our test suite, regardless of FORCE_SUBREAPER 207 | 208 | # Install test dependencies 209 | CC=gcc python3 -m pip install psutil python-prctl bitmap 210 | 211 | # Run tests 212 | python3 "${SOURCE_DIR}/test/run_inner_tests.py" 213 | else 214 | echo "Not a native build, skipping smoke and internal tests" 215 | fi 216 | 217 | # Now, copy over files to DIST_DIR, with appropriate names depending on the 218 | # architecture. 219 | # Handle the DEB / RPM 220 | mkdir -p "${DIST_DIR}" 221 | 222 | DIST_TINIS=() 223 | 224 | SUFFIX="" 225 | if [[ -n "$ARCH_SUFFIX" ]]; then 226 | SUFFIX="-${ARCH_SUFFIX}" 227 | elif [[ -z "$ARCH_NATIVE" ]]; then 228 | echo "Refusing to publish a non-native build without suffix!" 229 | exit 1 230 | fi 231 | 232 | for build_tini in tini tini-static; do 233 | dist_tini="${build_tini}${SUFFIX}" 234 | cp "${BUILD_DIR}/${build_tini}" "${DIST_DIR}/${dist_tini}" 235 | DIST_TINIS+=("$dist_tini") 236 | done 237 | 238 | if [[ -n "${ARCH_NATIVE-}" ]]; then 239 | for pkg_format in deb rpm; do 240 | build_tini="tini_${pkg_version}.${pkg_format}" 241 | dist_tini="tini_${pkg_version}${SUFFIX}.${pkg_format}" 242 | cp "${BUILD_DIR}/${build_tini}" "${DIST_DIR}/${dist_tini}" 243 | DIST_TINIS+=("$dist_tini") 244 | done 245 | fi 246 | 247 | echo "Tinis: ${DIST_TINIS[*]}" 248 | 249 | pushd "$DIST_DIR" 250 | 251 | for tini in "${DIST_TINIS[@]}"; do 252 | echo "${tini}:" 253 | 254 | for sum in sha1sum sha256sum; do 255 | "$sum" "$tini" | tee "${tini}.${sum}" 256 | done 257 | 258 | file "$tini" 259 | echo "--" 260 | done 261 | 262 | # If a signing key and passphrase are made available, then use it to sign the 263 | # binaries 264 | if [[ -n "$GPG_PASSPHRASE" ]] && [[ -f "${SOURCE_DIR}/sign.key" ]]; then 265 | echo "Signing tinis" 266 | GPG_SIGN_HOMEDIR="${BUILD_DIR}/gpg-sign" 267 | GPG_VERIFY_HOMEDIR="${BUILD_DIR}/gpg-verify" 268 | PGP_KEY_FINGERPRINT="595E85A6B1B4779EA4DAAEC70B588DFF0527A9B7" 269 | PGP_KEYSERVER="ha.pool.sks-keyservers.net" 270 | 271 | mkdir "${GPG_SIGN_HOMEDIR}" "${GPG_VERIFY_HOMEDIR}" 272 | chmod 700 "${GPG_SIGN_HOMEDIR}" "${GPG_VERIFY_HOMEDIR}" 273 | 274 | gpg --homedir "${GPG_SIGN_HOMEDIR}" --import "${SOURCE_DIR}/sign.key" 275 | gpg --homedir "${GPG_VERIFY_HOMEDIR}" --keyserver "$PGP_KEYSERVER" --recv-keys "$PGP_KEY_FINGERPRINT" 276 | 277 | for tini in "${DIST_TINIS[@]}"; do 278 | echo "${GPG_PASSPHRASE}" | gpg --homedir "${GPG_SIGN_HOMEDIR}" --passphrase-fd 0 --armor --detach-sign "${tini}" 279 | gpg --homedir "${GPG_VERIFY_HOMEDIR}" --verify "${tini}.asc" 280 | done 281 | fi 282 | 283 | popd 284 | -------------------------------------------------------------------------------- /ci/util/rpmbuild: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Wrapper for rpm build that first removes files that should be in there 3 | # We need this for compatibility with CMake <= 2.8.10 (which is the only version we have in Travis) 4 | # See: http://www.cmake.org/pipermail/cmake-commits/2013-April/014818.html 5 | set -o nounset 6 | set -o errexit 7 | set -o pipefail 8 | 9 | # Remove PATH hack so we can find the real rpmbuild 10 | export PATH="${REAL_PATH}" 11 | 12 | echo "Using local rpmbuild" 13 | 14 | specFile="${!#}" # Last argument 15 | 16 | for removeLine in '"/usr"' '"/usr/bin"'; do 17 | sed -i "s|${removeLine}||g" "${specFile}" 18 | done 19 | 20 | # Passthrough to rpmbuild 21 | exec rpmbuild "${@}" 22 | -------------------------------------------------------------------------------- /ddist.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -o errexit 3 | set -o nounset 4 | 5 | REL_HERE=$(dirname "${BASH_SOURCE}") 6 | HERE=$(cd "${REL_HERE}"; pwd) 7 | 8 | IMG="tini-build" 9 | 10 | if [[ -n "${ARCH_SUFFIX-}" ]]; then 11 | IMG="${IMG}_${ARCH_SUFFIX}" 12 | fi 13 | 14 | if [[ -n "${ARCH_NATIVE-}" ]]; then 15 | IMG="${IMG}_native" 16 | fi 17 | 18 | if [[ -n "${CC-}" ]]; then 19 | IMG="${IMG}_${CC}" 20 | fi 21 | 22 | # Cleanup the build dir 23 | rm -f "${HERE}/dist"/* 24 | 25 | # Create the build image 26 | echo "build: ${IMG}" 27 | 28 | docker build \ 29 | --build-arg "ARCH_SUFFIX=${ARCH_SUFFIX-}" \ 30 | --build-arg "ARCH_NATIVE=${ARCH_NATIVE-}" \ 31 | --build-arg "CC=${CC-gcc}" \ 32 | -t "${IMG}" \ 33 | . 34 | 35 | # Build new Tini 36 | SRC="/tini" 37 | 38 | docker run -it --rm \ 39 | --volume="${HERE}:${SRC}" \ 40 | -e BUILD_DIR=/tmp/tini-build \ 41 | -e SOURCE_DIR="${SRC}" \ 42 | -e FORCE_SUBREAPER="${FORCE_SUBREAPER-1}" \ 43 | -e GPG_PASSPHRASE="${GPG_PASSPHRASE-}" \ 44 | -e CFLAGS="${CFLAGS-}" \ 45 | -e MINIMAL="${MINIMAL-}" \ 46 | -u "$(id -u):$(id -g)" \ 47 | "${IMG}" "${SRC}/ci/run_build.sh" 48 | -------------------------------------------------------------------------------- /dtest.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -o errexit 3 | set -o nounset 4 | 5 | IMG="tini" 6 | 7 | if [[ "$#" != 1 ]]; then 8 | echo "Usage: $0 ARCH_SUFFIX" 9 | exit 1 10 | fi 11 | suffix="$1" 12 | 13 | IMG="tini-build-${suffix}" 14 | python test/run_outer_tests.py "${IMG}" 15 | -------------------------------------------------------------------------------- /run_tests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -o errexit 3 | set -o nounset 4 | 5 | REL_HERE=$(dirname "${BASH_SOURCE}") 6 | HERE=$(cd "${REL_HERE}"; pwd) 7 | 8 | for i in $(seq 0 1); do 9 | export FORCE_SUBREAPER="${i}" 10 | echo "Testing with FORCE_SUBREAPER=${FORCE_SUBREAPER}" 11 | "${HERE}/ddist.sh" 12 | "${HERE}/dtest.sh" 13 | done 14 | -------------------------------------------------------------------------------- /sign.key.enc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/krallin/tini/369448a167e8b3da4ca5bca0b3307500c3371828/sign.key.enc -------------------------------------------------------------------------------- /src/tini.c: -------------------------------------------------------------------------------- 1 | /* See LICENSE file for copyright and license details. */ 2 | #define _GNU_SOURCE 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | #include "tiniConfig.h" 20 | #include "tiniLicense.h" 21 | 22 | #if TINI_MINIMAL 23 | #define PRINT_FATAL(...) fprintf(stderr, __VA_ARGS__); fprintf(stderr, "\n"); 24 | #define PRINT_WARNING(...) if (verbosity > 0) { fprintf(stderr, __VA_ARGS__); fprintf(stderr, "\n"); } 25 | #define PRINT_INFO(...) if (verbosity > 1) { fprintf(stdout, __VA_ARGS__); fprintf(stdout, "\n"); } 26 | #define PRINT_DEBUG(...) if (verbosity > 2) { fprintf(stdout, __VA_ARGS__); fprintf(stdout, "\n"); } 27 | #define PRINT_TRACE(...) if (verbosity > 3) { fprintf(stdout, __VA_ARGS__); fprintf(stdout, "\n"); } 28 | #define DEFAULT_VERBOSITY 0 29 | #else 30 | #define PRINT_FATAL(...) fprintf(stderr, "[FATAL tini (%i)] ", getpid()); fprintf(stderr, __VA_ARGS__); fprintf(stderr, "\n"); 31 | #define PRINT_WARNING(...) if (verbosity > 0) { fprintf(stderr, "[WARN tini (%i)] ", getpid()); fprintf(stderr, __VA_ARGS__); fprintf(stderr, "\n"); } 32 | #define PRINT_INFO(...) if (verbosity > 1) { fprintf(stdout, "[INFO tini (%i)] ", getpid()); fprintf(stdout, __VA_ARGS__); fprintf(stdout, "\n"); } 33 | #define PRINT_DEBUG(...) if (verbosity > 2) { fprintf(stdout, "[DEBUG tini (%i)] ", getpid()); fprintf(stdout, __VA_ARGS__); fprintf(stdout, "\n"); } 34 | #define PRINT_TRACE(...) if (verbosity > 3) { fprintf(stdout, "[TRACE tini (%i)] ", getpid()); fprintf(stdout, __VA_ARGS__); fprintf(stdout, "\n"); } 35 | #define DEFAULT_VERBOSITY 1 36 | #endif 37 | 38 | #define ARRAY_LEN(x) (sizeof(x) / sizeof((x)[0])) 39 | 40 | #define INT32_BITFIELD_SET(F, i) ( F[(i / 32)] |= (1 << (i % 32)) ) 41 | #define INT32_BITFIELD_CLEAR(F, i) ( F[(i / 32)] &= ~(1 << (i % 32)) ) 42 | #define INT32_BITFIELD_TEST(F, i) ( F[(i / 32)] & (1 << (i % 32)) ) 43 | #define INT32_BITFIELD_CHECK_BOUNDS(F, i) do { assert(i >= 0); assert(ARRAY_LEN(F) > (uint) (i / 32)); } while(0) 44 | 45 | #define STATUS_MAX 255 46 | #define STATUS_MIN 0 47 | 48 | typedef struct { 49 | sigset_t* const sigmask_ptr; 50 | struct sigaction* const sigttin_action_ptr; 51 | struct sigaction* const sigttou_action_ptr; 52 | } signal_configuration_t; 53 | 54 | static const struct { 55 | char *const name; 56 | int number; 57 | } signal_names[] = { 58 | { "SIGHUP", SIGHUP }, 59 | { "SIGINT", SIGINT }, 60 | { "SIGQUIT", SIGQUIT }, 61 | { "SIGILL", SIGILL }, 62 | { "SIGTRAP", SIGTRAP }, 63 | { "SIGABRT", SIGABRT }, 64 | { "SIGBUS", SIGBUS }, 65 | { "SIGFPE", SIGFPE }, 66 | { "SIGKILL", SIGKILL }, 67 | { "SIGUSR1", SIGUSR1 }, 68 | { "SIGSEGV", SIGSEGV }, 69 | { "SIGUSR2", SIGUSR2 }, 70 | { "SIGPIPE", SIGPIPE }, 71 | { "SIGALRM", SIGALRM }, 72 | { "SIGTERM", SIGTERM }, 73 | { "SIGCHLD", SIGCHLD }, 74 | { "SIGCONT", SIGCONT }, 75 | { "SIGSTOP", SIGSTOP }, 76 | { "SIGTSTP", SIGTSTP }, 77 | { "SIGTTIN", SIGTTIN }, 78 | { "SIGTTOU", SIGTTOU }, 79 | { "SIGURG", SIGURG }, 80 | { "SIGXCPU", SIGXCPU }, 81 | { "SIGXFSZ", SIGXFSZ }, 82 | { "SIGVTALRM", SIGVTALRM }, 83 | { "SIGPROF", SIGPROF }, 84 | { "SIGWINCH", SIGWINCH }, 85 | { "SIGSYS", SIGSYS }, 86 | }; 87 | 88 | static unsigned int verbosity = DEFAULT_VERBOSITY; 89 | 90 | static int32_t expect_status[(STATUS_MAX - STATUS_MIN + 1) / 32]; 91 | 92 | #ifdef PR_SET_CHILD_SUBREAPER 93 | #define HAS_SUBREAPER 1 94 | #define OPT_STRING "p:hvwgle:s" 95 | #define SUBREAPER_ENV_VAR "TINI_SUBREAPER" 96 | #else 97 | #define HAS_SUBREAPER 0 98 | #define OPT_STRING "p:hvwgle:" 99 | #endif 100 | 101 | #define VERBOSITY_ENV_VAR "TINI_VERBOSITY" 102 | #define KILL_PROCESS_GROUP_GROUP_ENV_VAR "TINI_KILL_PROCESS_GROUP" 103 | 104 | #define TINI_VERSION_STRING "tini version " TINI_VERSION TINI_GIT 105 | 106 | 107 | #if HAS_SUBREAPER 108 | static unsigned int subreaper = 0; 109 | #endif 110 | static unsigned int parent_death_signal = 0; 111 | static unsigned int kill_process_group = 0; 112 | 113 | static unsigned int warn_on_reap = 0; 114 | 115 | static struct timespec ts = { .tv_sec = 1, .tv_nsec = 0 }; 116 | 117 | static const char reaper_warning[] = "Tini is not running as PID 1 " 118 | #if HAS_SUBREAPER 119 | "and isn't registered as a child subreaper" 120 | #endif 121 | ".\n\ 122 | Zombie processes will not be re-parented to Tini, so zombie reaping won't work.\n\ 123 | To fix the problem, " 124 | #if HAS_SUBREAPER 125 | #ifndef TINI_MINIMAL 126 | "use the -s option or " 127 | #endif 128 | "set the environment variable " SUBREAPER_ENV_VAR " to register Tini as a child subreaper, or " 129 | #endif 130 | "run Tini as PID 1."; 131 | 132 | int restore_signals(const signal_configuration_t* const sigconf_ptr) { 133 | if (sigprocmask(SIG_SETMASK, sigconf_ptr->sigmask_ptr, NULL)) { 134 | PRINT_FATAL("Restoring child signal mask failed: '%s'", strerror(errno)); 135 | return 1; 136 | } 137 | 138 | if (sigaction(SIGTTIN, sigconf_ptr->sigttin_action_ptr, NULL)) { 139 | PRINT_FATAL("Restoring SIGTTIN handler failed: '%s'", strerror((errno))); 140 | return 1; 141 | } 142 | 143 | if (sigaction(SIGTTOU, sigconf_ptr->sigttou_action_ptr, NULL)) { 144 | PRINT_FATAL("Restoring SIGTTOU handler failed: '%s'", strerror((errno))); 145 | return 1; 146 | } 147 | 148 | return 0; 149 | } 150 | 151 | int isolate_child(void) { 152 | // Put the child into a new process group. 153 | if (setpgid(0, 0) < 0) { 154 | PRINT_FATAL("setpgid failed: %s", strerror(errno)); 155 | return 1; 156 | } 157 | 158 | // If there is a tty, allocate it to this new process group. We 159 | // can do this in the child process because we're blocking 160 | // SIGTTIN / SIGTTOU. 161 | 162 | // Doing it in the child process avoids a race condition scenario 163 | // if Tini is calling Tini (in which case the grandparent may make the 164 | // parent the foreground process group, and the actual child ends up... 165 | // in the background!) 166 | if (tcsetpgrp(STDIN_FILENO, getpgrp())) { 167 | if (errno == ENOTTY) { 168 | PRINT_DEBUG("tcsetpgrp failed: no tty (ok to proceed)"); 169 | } else if (errno == ENXIO) { 170 | // can occur on lx-branded zones 171 | PRINT_DEBUG("tcsetpgrp failed: no such device (ok to proceed)"); 172 | } else { 173 | PRINT_FATAL("tcsetpgrp failed: %s", strerror(errno)); 174 | return 1; 175 | } 176 | } 177 | 178 | return 0; 179 | } 180 | 181 | 182 | int spawn(const signal_configuration_t* const sigconf_ptr, char* const argv[], int* const child_pid_ptr) { 183 | pid_t pid; 184 | 185 | // TODO: check if tini was a foreground process to begin with (it's not OK to "steal" the foreground!") 186 | 187 | pid = fork(); 188 | if (pid < 0) { 189 | PRINT_FATAL("fork failed: %s", strerror(errno)); 190 | return 1; 191 | } else if (pid == 0) { 192 | 193 | // Put the child in a process group and make it the foreground process if there is a tty. 194 | if (isolate_child()) { 195 | return 1; 196 | } 197 | 198 | // Restore all signal handlers to the way they were before we touched them. 199 | if (restore_signals(sigconf_ptr)) { 200 | return 1; 201 | } 202 | 203 | execvp(argv[0], argv); 204 | 205 | // execvp will only return on an error so make sure that we check the errno 206 | // and exit with the correct return status for the error that we encountered 207 | // See: http://www.tldp.org/LDP/abs/html/exitcodes.html#EXITCODESREF 208 | int status = 1; 209 | switch (errno) { 210 | case ENOENT: 211 | status = 127; 212 | break; 213 | case EACCES: 214 | status = 126; 215 | break; 216 | } 217 | PRINT_FATAL("exec %s failed: %s", argv[0], strerror(errno)); 218 | return status; 219 | } else { 220 | // Parent 221 | PRINT_INFO("Spawned child process '%s' with pid '%i'", argv[0], pid); 222 | *child_pid_ptr = pid; 223 | return 0; 224 | } 225 | } 226 | 227 | void print_usage(char* const name, FILE* const file) { 228 | char *dirc, *bname; 229 | 230 | dirc = strdup(name); 231 | bname = basename(dirc); 232 | 233 | fprintf(file, "%s (%s)\n", bname, TINI_VERSION_STRING); 234 | 235 | #if TINI_MINIMAL 236 | fprintf(file, "Usage: %s PROGRAM [ARGS] | --version\n\n", bname); 237 | #else 238 | fprintf(file, "Usage: %s [OPTIONS] PROGRAM -- [ARGS] | --version\n\n", bname); 239 | #endif 240 | fprintf(file, "Execute a program under the supervision of a valid init process (%s)\n\n", bname); 241 | 242 | fprintf(file, "Command line options:\n\n"); 243 | 244 | fprintf(file, " --version: Show version and exit.\n"); 245 | 246 | #if TINI_MINIMAL 247 | #else 248 | fprintf(file, " -h: Show this help message and exit.\n"); 249 | #if HAS_SUBREAPER 250 | fprintf(file, " -s: Register as a process subreaper (requires Linux >= 3.4).\n"); 251 | #endif 252 | fprintf(file, " -p SIGNAL: Trigger SIGNAL when parent dies, e.g. \"-p SIGKILL\".\n"); 253 | fprintf(file, " -v: Generate more verbose output. Repeat up to 3 times.\n"); 254 | fprintf(file, " -w: Print a warning when processes are getting reaped.\n"); 255 | fprintf(file, " -g: Send signals to the child's process group.\n"); 256 | fprintf(file, " -e EXIT_CODE: Remap EXIT_CODE (from 0 to 255) to 0 (can be repeated).\n"); 257 | fprintf(file, " -l: Show license and exit.\n"); 258 | #endif 259 | 260 | fprintf(file, "\n"); 261 | 262 | fprintf(file, "Environment variables:\n\n"); 263 | #if HAS_SUBREAPER 264 | fprintf(file, " %s: Register as a process subreaper (requires Linux >= 3.4).\n", SUBREAPER_ENV_VAR); 265 | #endif 266 | fprintf(file, " %s: Set the verbosity level (default: %d).\n", VERBOSITY_ENV_VAR, DEFAULT_VERBOSITY); 267 | fprintf(file, " %s: Send signals to the child's process group.\n", KILL_PROCESS_GROUP_GROUP_ENV_VAR); 268 | 269 | fprintf(file, "\n"); 270 | free(dirc); 271 | } 272 | 273 | void print_license(FILE* const file) { 274 | if(LICENSE_len > fwrite(LICENSE, sizeof(char), LICENSE_len, file)) { 275 | // Don't handle this error for now, since parse_args won't care 276 | // about the return value. We do need to check it to compile with 277 | // older glibc, though. 278 | // See: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=25509 279 | // See: http://sourceware.org/bugzilla/show_bug.cgi?id=11959 280 | } 281 | } 282 | 283 | int set_pdeathsig(char* const arg) { 284 | size_t i; 285 | 286 | for (i = 0; i < ARRAY_LEN(signal_names); i++) { 287 | if (strcmp(signal_names[i].name, arg) == 0) { 288 | /* Signals start at value "1" */ 289 | parent_death_signal = signal_names[i].number; 290 | return 0; 291 | } 292 | } 293 | 294 | return 1; 295 | } 296 | 297 | int add_expect_status(char* arg) { 298 | long status = 0; 299 | char* endptr = NULL; 300 | status = strtol(arg, &endptr, 10); 301 | 302 | if ((endptr == NULL) || (*endptr != 0)) { 303 | return 1; 304 | } 305 | 306 | if ((status < STATUS_MIN) || (status > STATUS_MAX)) { 307 | return 1; 308 | } 309 | 310 | INT32_BITFIELD_CHECK_BOUNDS(expect_status, status); 311 | INT32_BITFIELD_SET(expect_status, status); 312 | return 0; 313 | } 314 | 315 | int parse_args(const int argc, char* const argv[], char* (**child_args_ptr_ptr)[], int* const parse_fail_exitcode_ptr) { 316 | char* name = argv[0]; 317 | 318 | // We handle --version if it's the *only* argument provided. 319 | if (argc == 2 && strcmp("--version", argv[1]) == 0) { 320 | *parse_fail_exitcode_ptr = 0; 321 | fprintf(stdout, "%s\n", TINI_VERSION_STRING); 322 | return 1; 323 | } 324 | 325 | #ifndef TINI_MINIMAL 326 | int c; 327 | while ((c = getopt(argc, argv, OPT_STRING)) != -1) { 328 | switch (c) { 329 | case 'h': 330 | print_usage(name, stdout); 331 | *parse_fail_exitcode_ptr = 0; 332 | return 1; 333 | #if HAS_SUBREAPER 334 | case 's': 335 | subreaper++; 336 | break; 337 | #endif 338 | case 'p': 339 | if (set_pdeathsig(optarg)) { 340 | PRINT_FATAL("Not a valid option for -p: %s", optarg); 341 | *parse_fail_exitcode_ptr = 1; 342 | return 1; 343 | } 344 | break; 345 | 346 | case 'v': 347 | verbosity++; 348 | break; 349 | 350 | case 'w': 351 | warn_on_reap++; 352 | break; 353 | 354 | case 'g': 355 | kill_process_group++; 356 | break; 357 | 358 | case 'e': 359 | if (add_expect_status(optarg)) { 360 | PRINT_FATAL("Not a valid option for -e: %s", optarg); 361 | *parse_fail_exitcode_ptr = 1; 362 | return 1; 363 | } 364 | break; 365 | 366 | case 'l': 367 | print_license(stdout); 368 | *parse_fail_exitcode_ptr = 0; 369 | return 1; 370 | 371 | case '?': 372 | print_usage(name, stderr); 373 | return 1; 374 | default: 375 | /* Should never happen */ 376 | return 1; 377 | } 378 | } 379 | #endif 380 | 381 | *child_args_ptr_ptr = calloc(argc-optind+1, sizeof(char*)); 382 | if (*child_args_ptr_ptr == NULL) { 383 | PRINT_FATAL("Failed to allocate memory for child args: '%s'", strerror(errno)); 384 | return 1; 385 | } 386 | 387 | int i; 388 | for (i = 0; i < argc - optind; i++) { 389 | (**child_args_ptr_ptr)[i] = argv[optind+i]; 390 | } 391 | (**child_args_ptr_ptr)[i] = NULL; 392 | 393 | if (i == 0) { 394 | /* User forgot to provide args! */ 395 | print_usage(name, stderr); 396 | return 1; 397 | } 398 | 399 | return 0; 400 | } 401 | 402 | int parse_env(void) { 403 | #if HAS_SUBREAPER 404 | if (getenv(SUBREAPER_ENV_VAR) != NULL) { 405 | subreaper++; 406 | } 407 | #endif 408 | 409 | if (getenv(KILL_PROCESS_GROUP_GROUP_ENV_VAR) != NULL) { 410 | kill_process_group++; 411 | } 412 | 413 | char* env_verbosity = getenv(VERBOSITY_ENV_VAR); 414 | if (env_verbosity != NULL) { 415 | verbosity = atoi(env_verbosity); 416 | } 417 | 418 | return 0; 419 | } 420 | 421 | 422 | #if HAS_SUBREAPER 423 | int register_subreaper (void) { 424 | if (subreaper > 0) { 425 | if (prctl(PR_SET_CHILD_SUBREAPER, 1)) { 426 | if (errno == EINVAL) { 427 | PRINT_FATAL("PR_SET_CHILD_SUBREAPER is unavailable on this platform. Are you using Linux >= 3.4?") 428 | } else { 429 | PRINT_FATAL("Failed to register as child subreaper: %s", strerror(errno)) 430 | } 431 | return 1; 432 | } else { 433 | PRINT_TRACE("Registered as child subreaper"); 434 | } 435 | } 436 | return 0; 437 | } 438 | #endif 439 | 440 | 441 | void reaper_check (void) { 442 | /* Check that we can properly reap zombies */ 443 | #if HAS_SUBREAPER 444 | int bit = 0; 445 | #endif 446 | 447 | if (getpid() == 1) { 448 | return; 449 | } 450 | 451 | #if HAS_SUBREAPER 452 | if (prctl(PR_GET_CHILD_SUBREAPER, &bit)) { 453 | PRINT_DEBUG("Failed to read child subreaper attribute: %s", strerror(errno)); 454 | } else if (bit == 1) { 455 | return; 456 | } 457 | #endif 458 | 459 | PRINT_WARNING(reaper_warning); 460 | } 461 | 462 | 463 | int configure_signals(sigset_t* const parent_sigset_ptr, const signal_configuration_t* const sigconf_ptr) { 464 | /* Block all signals that are meant to be collected by the main loop */ 465 | if (sigfillset(parent_sigset_ptr)) { 466 | PRINT_FATAL("sigfillset failed: '%s'", strerror(errno)); 467 | return 1; 468 | } 469 | 470 | // These ones shouldn't be collected by the main loop 471 | uint i; 472 | int signals_for_tini[] = {SIGFPE, SIGILL, SIGSEGV, SIGBUS, SIGABRT, SIGTRAP, SIGSYS, SIGTTIN, SIGTTOU}; 473 | for (i = 0; i < ARRAY_LEN(signals_for_tini); i++) { 474 | if (sigdelset(parent_sigset_ptr, signals_for_tini[i])) { 475 | PRINT_FATAL("sigdelset failed: '%i'", signals_for_tini[i]); 476 | return 1; 477 | } 478 | } 479 | 480 | if (sigprocmask(SIG_SETMASK, parent_sigset_ptr, sigconf_ptr->sigmask_ptr)) { 481 | PRINT_FATAL("sigprocmask failed: '%s'", strerror(errno)); 482 | return 1; 483 | } 484 | 485 | // Handle SIGTTIN and SIGTTOU separately. Since Tini makes the child process group 486 | // the foreground process group, there's a chance Tini can end up not controlling the tty. 487 | // If TOSTOP is set on the tty, this could block Tini on writing debug messages. We don't 488 | // want that. Ignore those signals. 489 | struct sigaction ign_action; 490 | memset(&ign_action, 0, sizeof ign_action); 491 | 492 | ign_action.sa_handler = SIG_IGN; 493 | sigemptyset(&ign_action.sa_mask); 494 | 495 | if (sigaction(SIGTTIN, &ign_action, sigconf_ptr->sigttin_action_ptr)) { 496 | PRINT_FATAL("Failed to ignore SIGTTIN"); 497 | return 1; 498 | } 499 | 500 | if (sigaction(SIGTTOU, &ign_action, sigconf_ptr->sigttou_action_ptr)) { 501 | PRINT_FATAL("Failed to ignore SIGTTOU"); 502 | return 1; 503 | } 504 | 505 | return 0; 506 | } 507 | 508 | int wait_and_forward_signal(sigset_t const* const parent_sigset_ptr, pid_t const child_pid) { 509 | siginfo_t sig; 510 | 511 | if (sigtimedwait(parent_sigset_ptr, &sig, &ts) == -1) { 512 | switch (errno) { 513 | case EAGAIN: 514 | break; 515 | case EINTR: 516 | break; 517 | default: 518 | PRINT_FATAL("Unexpected error in sigtimedwait: '%s'", strerror(errno)); 519 | return 1; 520 | } 521 | } else { 522 | /* There is a signal to handle here */ 523 | switch (sig.si_signo) { 524 | case SIGCHLD: 525 | /* Special-cased, as we don't forward SIGCHLD. Instead, we'll 526 | * fallthrough to reaping processes. 527 | */ 528 | PRINT_DEBUG("Received SIGCHLD"); 529 | break; 530 | default: 531 | PRINT_DEBUG("Passing signal: '%s'", strsignal(sig.si_signo)); 532 | /* Forward anything else */ 533 | if (kill(kill_process_group ? -child_pid : child_pid, sig.si_signo)) { 534 | if (errno == ESRCH) { 535 | PRINT_WARNING("Child was dead when forwarding signal"); 536 | } else { 537 | PRINT_FATAL("Unexpected error when forwarding signal: '%s'", strerror(errno)); 538 | return 1; 539 | } 540 | } 541 | break; 542 | } 543 | } 544 | 545 | return 0; 546 | } 547 | 548 | int reap_zombies(const pid_t child_pid, int* const child_exitcode_ptr) { 549 | pid_t current_pid; 550 | int current_status; 551 | 552 | while (1) { 553 | current_pid = waitpid(-1, ¤t_status, WNOHANG); 554 | 555 | switch (current_pid) { 556 | 557 | case -1: 558 | if (errno == ECHILD) { 559 | PRINT_TRACE("No child to wait"); 560 | break; 561 | } 562 | PRINT_FATAL("Error while waiting for pids: '%s'", strerror(errno)); 563 | return 1; 564 | 565 | case 0: 566 | PRINT_TRACE("No child to reap"); 567 | break; 568 | 569 | default: 570 | /* A child was reaped. Check whether it's the main one. If it is, then 571 | * set the exit_code, which will cause us to exit once we've reaped everyone else. 572 | */ 573 | PRINT_DEBUG("Reaped child with pid: '%i'", current_pid); 574 | if (current_pid == child_pid) { 575 | if (WIFEXITED(current_status)) { 576 | /* Our process exited normally. */ 577 | PRINT_INFO("Main child exited normally (with status '%i')", WEXITSTATUS(current_status)); 578 | *child_exitcode_ptr = WEXITSTATUS(current_status); 579 | } else if (WIFSIGNALED(current_status)) { 580 | /* Our process was terminated. Emulate what sh / bash 581 | * would do, which is to return 128 + signal number. 582 | */ 583 | PRINT_INFO("Main child exited with signal (with signal '%s')", strsignal(WTERMSIG(current_status))); 584 | *child_exitcode_ptr = 128 + WTERMSIG(current_status); 585 | } else { 586 | PRINT_FATAL("Main child exited for unknown reason"); 587 | return 1; 588 | } 589 | 590 | // Be safe, ensure the status code is indeed between 0 and 255. 591 | *child_exitcode_ptr = *child_exitcode_ptr % (STATUS_MAX - STATUS_MIN + 1); 592 | 593 | // If this exitcode was remapped, then set it to 0. 594 | INT32_BITFIELD_CHECK_BOUNDS(expect_status, *child_exitcode_ptr); 595 | if (INT32_BITFIELD_TEST(expect_status, *child_exitcode_ptr)) { 596 | *child_exitcode_ptr = 0; 597 | } 598 | } else if (warn_on_reap > 0) { 599 | PRINT_WARNING("Reaped zombie process with pid=%i", current_pid); 600 | } 601 | 602 | // Check if other childs have been reaped. 603 | continue; 604 | } 605 | 606 | /* If we make it here, that's because we did not continue in the switch case. */ 607 | break; 608 | } 609 | 610 | return 0; 611 | } 612 | 613 | 614 | int main(int argc, char *argv[]) { 615 | pid_t child_pid; 616 | 617 | // Those are passed to functions to get an exitcode back. 618 | int child_exitcode = -1; // This isn't a valid exitcode, and lets us tell whether the child has exited. 619 | int parse_exitcode = 1; // By default, we exit with 1 if parsing fails. 620 | 621 | /* Parse command line arguments */ 622 | char* (*child_args_ptr)[]; 623 | int parse_args_ret = parse_args(argc, argv, &child_args_ptr, &parse_exitcode); 624 | if (parse_args_ret) { 625 | return parse_exitcode; 626 | } 627 | 628 | /* Parse environment */ 629 | if (parse_env()) { 630 | return 1; 631 | } 632 | 633 | /* Configure signals */ 634 | sigset_t parent_sigset, child_sigset; 635 | struct sigaction sigttin_action, sigttou_action; 636 | memset(&sigttin_action, 0, sizeof sigttin_action); 637 | memset(&sigttou_action, 0, sizeof sigttou_action); 638 | 639 | signal_configuration_t child_sigconf = { 640 | .sigmask_ptr = &child_sigset, 641 | .sigttin_action_ptr = &sigttin_action, 642 | .sigttou_action_ptr = &sigttou_action, 643 | }; 644 | 645 | if (configure_signals(&parent_sigset, &child_sigconf)) { 646 | return 1; 647 | } 648 | 649 | /* Trigger signal on this process when the parent process exits. */ 650 | if (parent_death_signal && prctl(PR_SET_PDEATHSIG, parent_death_signal)) { 651 | PRINT_FATAL("Failed to set up parent death signal"); 652 | return 1; 653 | } 654 | 655 | #if HAS_SUBREAPER 656 | /* If available and requested, register as a subreaper */ 657 | if (register_subreaper()) { 658 | return 1; 659 | }; 660 | #endif 661 | 662 | /* Are we going to reap zombies properly? If not, warn. */ 663 | reaper_check(); 664 | 665 | /* Go on */ 666 | int spawn_ret = spawn(&child_sigconf, *child_args_ptr, &child_pid); 667 | if (spawn_ret) { 668 | return spawn_ret; 669 | } 670 | free(child_args_ptr); 671 | 672 | while (1) { 673 | /* Wait for one signal, and forward it */ 674 | if (wait_and_forward_signal(&parent_sigset, child_pid)) { 675 | return 1; 676 | } 677 | 678 | /* Now, reap zombies */ 679 | if (reap_zombies(child_pid, &child_exitcode)) { 680 | return 1; 681 | } 682 | 683 | if (child_exitcode != -1) { 684 | PRINT_TRACE("Exiting: child has exited"); 685 | return child_exitcode; 686 | } 687 | } 688 | } 689 | -------------------------------------------------------------------------------- /src/tiniConfig.h.in: -------------------------------------------------------------------------------- 1 | #define TINI_VERSION "@tini_VERSION_MAJOR@.@tini_VERSION_MINOR@.@tini_VERSION_PATCH@" 2 | #define TINI_GIT "@tini_VERSION_GIT@" 3 | -------------------------------------------------------------------------------- /src/tiniLicense.h: -------------------------------------------------------------------------------- 1 | unsigned char LICENSE[] = { 2 | 0x54, 0x68, 0x65, 0x20, 0x4d, 0x49, 0x54, 0x20, 0x4c, 0x69, 0x63, 0x65, 3 | 0x6e, 0x73, 0x65, 0x20, 0x28, 0x4d, 0x49, 0x54, 0x29, 0x0a, 0x0a, 0x43, 4 | 0x6f, 0x70, 0x79, 0x72, 0x69, 0x67, 0x68, 0x74, 0x20, 0x28, 0x63, 0x29, 5 | 0x20, 0x32, 0x30, 0x31, 0x35, 0x20, 0x54, 0x68, 0x6f, 0x6d, 0x61, 0x73, 6 | 0x20, 0x4f, 0x72, 0x6f, 0x7a, 0x63, 0x6f, 0x20, 0x3c, 0x74, 0x68, 0x6f, 7 | 0x6d, 0x61, 0x73, 0x40, 0x6f, 0x72, 0x6f, 0x7a, 0x63, 0x6f, 0x2e, 0x66, 8 | 0x72, 0x3e, 0x0a, 0x0a, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 9 | 0x6f, 0x6e, 0x20, 0x69, 0x73, 0x20, 0x68, 0x65, 0x72, 0x65, 0x62, 0x79, 10 | 0x20, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x65, 0x64, 0x2c, 0x20, 0x66, 0x72, 11 | 0x65, 0x65, 0x20, 0x6f, 0x66, 0x20, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 12 | 0x2c, 0x20, 0x74, 0x6f, 0x20, 0x61, 0x6e, 0x79, 0x20, 0x70, 0x65, 0x72, 13 | 0x73, 0x6f, 0x6e, 0x20, 0x6f, 0x62, 0x74, 0x61, 0x69, 0x6e, 0x69, 0x6e, 14 | 0x67, 0x20, 0x61, 0x20, 0x63, 0x6f, 0x70, 0x79, 0x0a, 0x6f, 0x66, 0x20, 15 | 0x74, 0x68, 0x69, 0x73, 0x20, 0x73, 0x6f, 0x66, 0x74, 0x77, 0x61, 0x72, 16 | 0x65, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x61, 0x73, 0x73, 0x6f, 0x63, 0x69, 17 | 0x61, 0x74, 0x65, 0x64, 0x20, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 18 | 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x66, 0x69, 0x6c, 0x65, 0x73, 19 | 0x20, 0x28, 0x74, 0x68, 0x65, 0x20, 0x22, 0x53, 0x6f, 0x66, 0x74, 0x77, 20 | 0x61, 0x72, 0x65, 0x22, 0x29, 0x2c, 0x20, 0x74, 0x6f, 0x20, 0x64, 0x65, 21 | 0x61, 0x6c, 0x0a, 0x69, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x53, 0x6f, 22 | 0x66, 0x74, 0x77, 0x61, 0x72, 0x65, 0x20, 0x77, 0x69, 0x74, 0x68, 0x6f, 23 | 0x75, 0x74, 0x20, 0x72, 0x65, 0x73, 0x74, 0x72, 0x69, 0x63, 0x74, 0x69, 24 | 0x6f, 0x6e, 0x2c, 0x20, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x69, 0x6e, 25 | 0x67, 0x20, 0x77, 0x69, 0x74, 0x68, 0x6f, 0x75, 0x74, 0x20, 0x6c, 0x69, 26 | 0x6d, 0x69, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x74, 0x68, 0x65, 27 | 0x20, 0x72, 0x69, 0x67, 0x68, 0x74, 0x73, 0x0a, 0x74, 0x6f, 0x20, 0x75, 28 | 0x73, 0x65, 0x2c, 0x20, 0x63, 0x6f, 0x70, 0x79, 0x2c, 0x20, 0x6d, 0x6f, 29 | 0x64, 0x69, 0x66, 0x79, 0x2c, 0x20, 0x6d, 0x65, 0x72, 0x67, 0x65, 0x2c, 30 | 0x20, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x2c, 0x20, 0x64, 0x69, 31 | 0x73, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x2c, 0x20, 0x73, 0x75, 32 | 0x62, 0x6c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x2c, 0x20, 0x61, 0x6e, 33 | 0x64, 0x2f, 0x6f, 0x72, 0x20, 0x73, 0x65, 0x6c, 0x6c, 0x0a, 0x63, 0x6f, 34 | 0x70, 0x69, 0x65, 0x73, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 35 | 0x53, 0x6f, 0x66, 0x74, 0x77, 0x61, 0x72, 0x65, 0x2c, 0x20, 0x61, 0x6e, 36 | 0x64, 0x20, 0x74, 0x6f, 0x20, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x74, 0x20, 37 | 0x70, 0x65, 0x72, 0x73, 0x6f, 0x6e, 0x73, 0x20, 0x74, 0x6f, 0x20, 0x77, 38 | 0x68, 0x6f, 0x6d, 0x20, 0x74, 0x68, 0x65, 0x20, 0x53, 0x6f, 0x66, 0x74, 39 | 0x77, 0x61, 0x72, 0x65, 0x20, 0x69, 0x73, 0x0a, 0x66, 0x75, 0x72, 0x6e, 40 | 0x69, 0x73, 0x68, 0x65, 0x64, 0x20, 0x74, 0x6f, 0x20, 0x64, 0x6f, 0x20, 41 | 0x73, 0x6f, 0x2c, 0x20, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x20, 42 | 0x74, 0x6f, 0x20, 0x74, 0x68, 0x65, 0x20, 0x66, 0x6f, 0x6c, 0x6c, 0x6f, 43 | 0x77, 0x69, 0x6e, 0x67, 0x20, 0x63, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 44 | 0x6f, 0x6e, 0x73, 0x3a, 0x0a, 0x0a, 0x54, 0x68, 0x65, 0x20, 0x61, 0x62, 45 | 0x6f, 0x76, 0x65, 0x20, 0x63, 0x6f, 0x70, 0x79, 0x72, 0x69, 0x67, 0x68, 46 | 0x74, 0x20, 0x6e, 0x6f, 0x74, 0x69, 0x63, 0x65, 0x20, 0x61, 0x6e, 0x64, 47 | 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 48 | 0x73, 0x69, 0x6f, 0x6e, 0x20, 0x6e, 0x6f, 0x74, 0x69, 0x63, 0x65, 0x20, 49 | 0x73, 0x68, 0x61, 0x6c, 0x6c, 0x20, 0x62, 0x65, 0x20, 0x69, 0x6e, 0x63, 50 | 0x6c, 0x75, 0x64, 0x65, 0x64, 0x20, 0x69, 0x6e, 0x0a, 0x61, 0x6c, 0x6c, 51 | 0x20, 0x63, 0x6f, 0x70, 0x69, 0x65, 0x73, 0x20, 0x6f, 0x72, 0x20, 0x73, 52 | 0x75, 0x62, 0x73, 0x74, 0x61, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x20, 0x70, 53 | 0x6f, 0x72, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x20, 0x6f, 0x66, 0x20, 0x74, 54 | 0x68, 0x65, 0x20, 0x53, 0x6f, 0x66, 0x74, 0x77, 0x61, 0x72, 0x65, 0x2e, 55 | 0x0a, 0x0a, 0x54, 0x48, 0x45, 0x20, 0x53, 0x4f, 0x46, 0x54, 0x57, 0x41, 56 | 0x52, 0x45, 0x20, 0x49, 0x53, 0x20, 0x50, 0x52, 0x4f, 0x56, 0x49, 0x44, 57 | 0x45, 0x44, 0x20, 0x22, 0x41, 0x53, 0x20, 0x49, 0x53, 0x22, 0x2c, 0x20, 58 | 0x57, 0x49, 0x54, 0x48, 0x4f, 0x55, 0x54, 0x20, 0x57, 0x41, 0x52, 0x52, 59 | 0x41, 0x4e, 0x54, 0x59, 0x20, 0x4f, 0x46, 0x20, 0x41, 0x4e, 0x59, 0x20, 60 | 0x4b, 0x49, 0x4e, 0x44, 0x2c, 0x20, 0x45, 0x58, 0x50, 0x52, 0x45, 0x53, 61 | 0x53, 0x20, 0x4f, 0x52, 0x0a, 0x49, 0x4d, 0x50, 0x4c, 0x49, 0x45, 0x44, 62 | 0x2c, 0x20, 0x49, 0x4e, 0x43, 0x4c, 0x55, 0x44, 0x49, 0x4e, 0x47, 0x20, 63 | 0x42, 0x55, 0x54, 0x20, 0x4e, 0x4f, 0x54, 0x20, 0x4c, 0x49, 0x4d, 0x49, 64 | 0x54, 0x45, 0x44, 0x20, 0x54, 0x4f, 0x20, 0x54, 0x48, 0x45, 0x20, 0x57, 65 | 0x41, 0x52, 0x52, 0x41, 0x4e, 0x54, 0x49, 0x45, 0x53, 0x20, 0x4f, 0x46, 66 | 0x20, 0x4d, 0x45, 0x52, 0x43, 0x48, 0x41, 0x4e, 0x54, 0x41, 0x42, 0x49, 67 | 0x4c, 0x49, 0x54, 0x59, 0x2c, 0x0a, 0x46, 0x49, 0x54, 0x4e, 0x45, 0x53, 68 | 0x53, 0x20, 0x46, 0x4f, 0x52, 0x20, 0x41, 0x20, 0x50, 0x41, 0x52, 0x54, 69 | 0x49, 0x43, 0x55, 0x4c, 0x41, 0x52, 0x20, 0x50, 0x55, 0x52, 0x50, 0x4f, 70 | 0x53, 0x45, 0x20, 0x41, 0x4e, 0x44, 0x20, 0x4e, 0x4f, 0x4e, 0x49, 0x4e, 71 | 0x46, 0x52, 0x49, 0x4e, 0x47, 0x45, 0x4d, 0x45, 0x4e, 0x54, 0x2e, 0x20, 72 | 0x49, 0x4e, 0x20, 0x4e, 0x4f, 0x20, 0x45, 0x56, 0x45, 0x4e, 0x54, 0x20, 73 | 0x53, 0x48, 0x41, 0x4c, 0x4c, 0x20, 0x54, 0x48, 0x45, 0x0a, 0x41, 0x55, 74 | 0x54, 0x48, 0x4f, 0x52, 0x53, 0x20, 0x4f, 0x52, 0x20, 0x43, 0x4f, 0x50, 75 | 0x59, 0x52, 0x49, 0x47, 0x48, 0x54, 0x20, 0x48, 0x4f, 0x4c, 0x44, 0x45, 76 | 0x52, 0x53, 0x20, 0x42, 0x45, 0x20, 0x4c, 0x49, 0x41, 0x42, 0x4c, 0x45, 77 | 0x20, 0x46, 0x4f, 0x52, 0x20, 0x41, 0x4e, 0x59, 0x20, 0x43, 0x4c, 0x41, 78 | 0x49, 0x4d, 0x2c, 0x20, 0x44, 0x41, 0x4d, 0x41, 0x47, 0x45, 0x53, 0x20, 79 | 0x4f, 0x52, 0x20, 0x4f, 0x54, 0x48, 0x45, 0x52, 0x0a, 0x4c, 0x49, 0x41, 80 | 0x42, 0x49, 0x4c, 0x49, 0x54, 0x59, 0x2c, 0x20, 0x57, 0x48, 0x45, 0x54, 81 | 0x48, 0x45, 0x52, 0x20, 0x49, 0x4e, 0x20, 0x41, 0x4e, 0x20, 0x41, 0x43, 82 | 0x54, 0x49, 0x4f, 0x4e, 0x20, 0x4f, 0x46, 0x20, 0x43, 0x4f, 0x4e, 0x54, 83 | 0x52, 0x41, 0x43, 0x54, 0x2c, 0x20, 0x54, 0x4f, 0x52, 0x54, 0x20, 0x4f, 84 | 0x52, 0x20, 0x4f, 0x54, 0x48, 0x45, 0x52, 0x57, 0x49, 0x53, 0x45, 0x2c, 85 | 0x20, 0x41, 0x52, 0x49, 0x53, 0x49, 0x4e, 0x47, 0x20, 0x46, 0x52, 0x4f, 86 | 0x4d, 0x2c, 0x0a, 0x4f, 0x55, 0x54, 0x20, 0x4f, 0x46, 0x20, 0x4f, 0x52, 87 | 0x20, 0x49, 0x4e, 0x20, 0x43, 0x4f, 0x4e, 0x4e, 0x45, 0x43, 0x54, 0x49, 88 | 0x4f, 0x4e, 0x20, 0x57, 0x49, 0x54, 0x48, 0x20, 0x54, 0x48, 0x45, 0x20, 89 | 0x53, 0x4f, 0x46, 0x54, 0x57, 0x41, 0x52, 0x45, 0x20, 0x4f, 0x52, 0x20, 90 | 0x54, 0x48, 0x45, 0x20, 0x55, 0x53, 0x45, 0x20, 0x4f, 0x52, 0x20, 0x4f, 91 | 0x54, 0x48, 0x45, 0x52, 0x20, 0x44, 0x45, 0x41, 0x4c, 0x49, 0x4e, 0x47, 92 | 0x53, 0x20, 0x49, 0x4e, 0x0a, 0x54, 0x48, 0x45, 0x20, 0x53, 0x4f, 0x46, 93 | 0x54, 0x57, 0x41, 0x52, 0x45, 0x2e, 0x0a 94 | }; 95 | unsigned int LICENSE_len = 1099; 96 | -------------------------------------------------------------------------------- /test/pdeathsignal/stage_1.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from __future__ import print_function 3 | 4 | import os 5 | import sys 6 | import subprocess 7 | 8 | 9 | def main(): 10 | pid = os.getpid() 11 | 12 | tini = sys.argv[1] 13 | ret = sys.argv[2] 14 | stage_2 = os.path.join(os.path.dirname(__file__), "stage_2.py") 15 | 16 | cmd = [ 17 | tini, 18 | "-vvv", 19 | "-p", 20 | "SIGUSR1", 21 | "--", 22 | stage_2, 23 | str(pid), 24 | ret 25 | ] 26 | 27 | subprocess.Popen(cmd).wait() 28 | 29 | if __name__ == "__main__": 30 | main() 31 | -------------------------------------------------------------------------------- /test/pdeathsignal/stage_2.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from __future__ import print_function 3 | 4 | import os 5 | import sys 6 | import signal 7 | import time 8 | 9 | 10 | def main(): 11 | ret = sys.argv[2] 12 | 13 | def handler(*args): 14 | with open(ret, "w") as f: 15 | f.write("ok") 16 | sys.exit(0) 17 | 18 | signal.signal(signal.SIGUSR1, handler) 19 | pid = int(sys.argv[1]) 20 | 21 | os.kill(pid, signal.SIGKILL) 22 | time.sleep(5) 23 | 24 | if __name__ == "__main__": 25 | main() 26 | -------------------------------------------------------------------------------- /test/pgroup/stage_1.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import subprocess 4 | import signal 5 | 6 | 7 | def reset_sig_handler(): 8 | signal.signal(signal.SIGUSR1, signal.SIG_DFL) 9 | 10 | 11 | if __name__ == "__main__": 12 | signal.signal(signal.SIGUSR1, signal.SIG_IGN) 13 | p = subprocess.Popen( 14 | ["sleep", "1000"], 15 | preexec_fn=reset_sig_handler 16 | ) 17 | p.wait() 18 | 19 | -------------------------------------------------------------------------------- /test/reaping/stage_1.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import os 3 | import sys 4 | import subprocess 5 | import time 6 | 7 | import psutil 8 | 9 | 10 | def in_group_or_reaped(pid): 11 | try: 12 | return os.getpgid(pid) == os.getpgid(0) 13 | except OSError: 14 | return True 15 | 16 | 17 | def main(): 18 | stage_2 = os.path.join(os.path.dirname(__file__), "stage_2.py") 19 | subprocess.Popen([stage_2]).wait() 20 | 21 | # In tests, we assume this process is the direct child of init 22 | this_process = psutil.Process(os.getpid()) 23 | init_process = this_process.parent() 24 | 25 | print("Reaping test: stage_1 is pid{0}, init is pid{1}".format( 26 | this_process.pid, init_process.pid)) 27 | 28 | # The only child PID that should persist is this one. 29 | expected_pids = [this_process.pid] 30 | 31 | print("Expecting pids to remain: {0}".format( 32 | ", ".join(str(pid) for pid in expected_pids))) 33 | 34 | while 1: 35 | pids = [p.pid for p in init_process.children(recursive=True)] 36 | print("Has pids: {0}".format(", ".join(str(pid) for pid in pids))) 37 | for pid in pids: 38 | assert in_group_or_reaped(pid), "Child had unexpected pgid" 39 | if set(pids) == set(expected_pids): 40 | break 41 | time.sleep(1) 42 | 43 | # Now, check if there are any zombies. For each of the potential zombies, 44 | # we check that the pgid is ours. NOTE: We explicitly test that this test 45 | # fails if subreaping is disabled, so we can be confident this doesn't turn 46 | # a failure into a success. 47 | for process in psutil.process_iter(): 48 | if process.pid == this_process.pid: 49 | continue 50 | if not in_group_or_reaped(process.pid): 51 | continue 52 | print("Not reaped: pid {0}: {1}".format(process.pid, process.name())) 53 | sys.exit(1) 54 | 55 | sys.exit(0) 56 | 57 | 58 | if __name__ == "__main__": 59 | main() 60 | -------------------------------------------------------------------------------- /test/reaping/stage_2.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from __future__ import print_function 3 | import subprocess 4 | import os 5 | import random 6 | 7 | 8 | if __name__ == "__main__": 9 | # Spawn lots of process 10 | for i in range(0, 100): 11 | cmd = ["sleep", str(1 + i % 2 + random.random())] 12 | proc = subprocess.Popen(cmd) 13 | -------------------------------------------------------------------------------- /test/run_inner_tests.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # coding:utf-8 3 | import os 4 | import sys 5 | import signal 6 | import subprocess 7 | import time 8 | import psutil 9 | import bitmap 10 | import re 11 | import itertools 12 | import tempfile 13 | 14 | DEVNULL = open(os.devnull, "wb") 15 | 16 | SIGNUM_TO_SIGNAME = dict( 17 | (v, k) for k, v in signal.__dict__.items() if re.match("^SIG[A-Z]+$", k) 18 | ) 19 | 20 | 21 | def busy_wait(condition_callable, timeout): 22 | checks = 100 23 | increment = float(timeout) / checks 24 | 25 | for _ in range(checks): 26 | if condition_callable(): 27 | return 28 | time.sleep(increment) 29 | 30 | assert False, "Condition was never met" 31 | 32 | 33 | def main(): 34 | src = os.environ["SOURCE_DIR"] 35 | build = os.environ["BUILD_DIR"] 36 | 37 | args_disabled = os.environ.get("MINIMAL") 38 | 39 | proxy = os.path.join(src, "test", "subreaper-proxy.py") 40 | tini = os.path.join(build, "tini") 41 | 42 | subreaper_support = bool(int(os.environ["FORCE_SUBREAPER"])) 43 | 44 | # Run the exit code test. We use POSIXLY_CORRECT here to not need -- 45 | # until that's the default in Tini anyways. 46 | if not args_disabled: 47 | print("Running exit code test for {0}".format(tini)) 48 | for code in range(0, 256): 49 | p = subprocess.Popen( 50 | [tini, "-e", str(code), "--", "sh", "-c", "exit {0}".format(code)], 51 | stdout=DEVNULL, 52 | stderr=DEVNULL, 53 | universal_newlines=True, 54 | ) 55 | ret = p.wait() 56 | assert ret == 0, "Inclusive exit code test failed for %s, exit: %s" % ( 57 | code, 58 | ret, 59 | ) 60 | 61 | other_codes = [x for x in range(0, 256) if x != code] 62 | args = list(itertools.chain(*[["-e", str(x)] for x in other_codes])) 63 | 64 | p = subprocess.Popen( 65 | [tini] + args + ["sh", "-c", "exit {0}".format(code)], 66 | env=dict(os.environ, POSIXLY_CORRECT="1"), 67 | stdout=DEVNULL, 68 | stderr=DEVNULL, 69 | universal_newlines=True, 70 | ) 71 | ret = p.wait() 72 | assert ret == code, "Exclusive exit code test failed for %s, exit: %s" % ( 73 | code, 74 | ret, 75 | ) 76 | 77 | tests = [([proxy, tini], {})] 78 | 79 | if subreaper_support: 80 | if not args_disabled: 81 | tests.append(([tini, "-s"], {})) 82 | tests.append(([tini], {"TINI_SUBREAPER": ""})) 83 | 84 | for target, env in tests: 85 | # Run the reaping test 86 | print("Running reaping test ({0} with env {1})".format(" ".join(target), env)) 87 | p = subprocess.Popen( 88 | target + [os.path.join(src, "test", "reaping", "stage_1.py")], 89 | env=dict(os.environ, **env), 90 | stdout=subprocess.PIPE, 91 | stderr=subprocess.PIPE, 92 | universal_newlines=True, 93 | ) 94 | 95 | out, err = p.communicate() 96 | 97 | if subreaper_support: 98 | # If subreaper support sin't available, Tini won't looku p its subreaper bit 99 | # and will output the error message here. 100 | assert "zombie reaping won't work" not in err, "Warning message was output!" 101 | ret = p.wait() 102 | assert ( 103 | "Reaped zombie process with pid=" not in err 104 | ), "Warning message was output!" 105 | assert ret == 0, "Reaping test failed!\nOUT: %s\nERR: %s" % (out, err) 106 | 107 | if not args_disabled: 108 | print( 109 | "Running reaping display test ({0} with env {1})".format( 110 | " ".join(target), env 111 | ) 112 | ) 113 | p = subprocess.Popen( 114 | target + ["-w", os.path.join(src, "test", "reaping", "stage_1.py")], 115 | env=dict(os.environ, **env), 116 | stdout=subprocess.PIPE, 117 | stderr=subprocess.PIPE, 118 | universal_newlines=True, 119 | ) 120 | 121 | out, err = p.communicate() 122 | ret = p.wait() 123 | assert ( 124 | "Reaped zombie process with pid=" in err 125 | ), "Warning message was output!" 126 | assert ret == 0, "Reaping display test failed!\nOUT: %s\nERR: %s" % ( 127 | out, 128 | err, 129 | ) 130 | 131 | # Run the signals test 132 | for signum in [signal.SIGTERM, signal.SIGUSR1, signal.SIGUSR2]: 133 | print( 134 | "running signal test for: {0} ({1} with env {2})".format( 135 | signum, " ".join(target), env 136 | ) 137 | ) 138 | p = subprocess.Popen( 139 | target + [os.path.join(src, "test", "signals", "test.py")], 140 | env=dict(os.environ, **env), 141 | universal_newlines=True, 142 | ) 143 | busy_wait( 144 | lambda: len(psutil.Process(p.pid).children(recursive=True)) > 1, 10 145 | ) 146 | p.send_signal(signum) 147 | ret = p.wait() 148 | assert ( 149 | ret == 128 + signum 150 | ), "Signals test failed (ret was {0}, expected {1})".format( 151 | ret, 128 + signum 152 | ) 153 | 154 | # Run the process group test 155 | # This test has Tini spawn a process that ignores SIGUSR1 and spawns a child that doesn't (and waits on the child) 156 | # We send SIGUSR1 to Tini, and expect the grand-child to terminate, then the child, and then Tini. 157 | if not args_disabled: 158 | print("Running process group test (arguments)") 159 | p = subprocess.Popen( 160 | [tini, "-g", os.path.join(src, "test", "pgroup", "stage_1.py")], 161 | stdout=subprocess.PIPE, 162 | stderr=subprocess.PIPE, 163 | universal_newlines=True, 164 | ) 165 | 166 | busy_wait(lambda: len(psutil.Process(p.pid).children(recursive=True)) == 2, 10) 167 | p.send_signal(signal.SIGUSR1) 168 | busy_wait(lambda: p.poll() is not None, 10) 169 | 170 | print("Running process group test (environment variable)") 171 | p = subprocess.Popen( 172 | [tini, os.path.join(src, "test", "pgroup", "stage_1.py")], 173 | stdout=subprocess.PIPE, 174 | stderr=subprocess.PIPE, 175 | env=dict(os.environ, TINI_KILL_PROCESS_GROUP="1"), 176 | universal_newlines=True, 177 | ) 178 | 179 | busy_wait(lambda: len(psutil.Process(p.pid).children(recursive=True)) == 2, 10) 180 | p.send_signal(signal.SIGUSR1) 181 | busy_wait(lambda: p.poll() is not None, 10) 182 | 183 | # Run failing test. Force verbosity to 1 so we see the subreaper warning 184 | # regardless of whether MINIMAL is set. 185 | print("Running zombie reaping failure test (Tini should warn)") 186 | p = subprocess.Popen( 187 | [tini, os.path.join(src, "test", "reaping", "stage_1.py")], 188 | stdout=subprocess.PIPE, 189 | stderr=subprocess.PIPE, 190 | env=dict(os.environ, TINI_VERBOSITY="1"), 191 | universal_newlines=True, 192 | ) 193 | out, err = p.communicate() 194 | assert "zombie reaping won't work" in err, "No warning message was output!" 195 | ret = p.wait() 196 | assert ret == 1, "Reaping test succeeded (it should have failed)!" 197 | 198 | # Test that the signals are properly in place here. 199 | print("Running signal configuration test") 200 | 201 | p = subprocess.Popen( 202 | [os.path.join(build, "sigconf-test"), tini, "cat", "/proc/self/status"], 203 | stdout=subprocess.PIPE, 204 | stderr=subprocess.PIPE, 205 | universal_newlines=True, 206 | ) 207 | out, err = p.communicate() 208 | 209 | # Extract the signal properties, and add a zero at the end. 210 | props = [line.split(":") for line in out.splitlines()] 211 | props = [(k.strip(), v.strip()) for (k, v) in props] 212 | props = [ 213 | (k, bitmap.BitMap.fromstring(bin(int(v, 16))[2:].zfill(32))) 214 | for (k, v) in props 215 | if k in ["SigBlk", "SigIgn", "SigCgt"] 216 | ] 217 | props = dict(props) 218 | 219 | # Print actual handling configuration 220 | for k, bmp in props.items(): 221 | print( 222 | "{0}: {1}".format( 223 | k, 224 | ", ".join( 225 | [ 226 | "{0} ({1})".format(SIGNUM_TO_SIGNAME[n + 1], n + 1) 227 | for n in bmp.nonzero() 228 | ] 229 | ), 230 | ) 231 | ) 232 | 233 | for signal_set_name, signals_to_test_for in [ 234 | ("SigIgn", [signal.SIGTTOU, signal.SIGSEGV, signal.SIGINT]), 235 | ("SigBlk", [signal.SIGTTIN, signal.SIGILL, signal.SIGTERM]), 236 | ]: 237 | for signum in signals_to_test_for: 238 | # Use signum - 1 because the bitmap is 0-indexed but represents signals strting at 1 239 | assert (signum - 1) in props[ 240 | signal_set_name 241 | ].nonzero(), "{0} ({1}) is missing in {2}!".format( 242 | SIGNUM_TO_SIGNAME[signum], signum, signal_set_name 243 | ) 244 | 245 | # Test parent death signal handling. 246 | if not args_disabled: 247 | print("Running parent death signal test") 248 | f = tempfile.NamedTemporaryFile() 249 | try: 250 | p = subprocess.Popen( 251 | [os.path.join(src, "test", "pdeathsignal", "stage_1.py"), tini, f.name], 252 | stdout=DEVNULL, 253 | stderr=DEVNULL, 254 | universal_newlines=True, 255 | ) 256 | p.wait() 257 | 258 | busy_wait(lambda: open(f.name).read() == "ok", 10) 259 | finally: 260 | f.close() 261 | 262 | print("---------------------------") 263 | print("All done, tests as expected") 264 | print("---------------------------") 265 | 266 | 267 | if __name__ == "__main__": 268 | main() 269 | -------------------------------------------------------------------------------- /test/run_outer_tests.py: -------------------------------------------------------------------------------- 1 | #coding:utf-8 2 | import os 3 | import sys 4 | import time 5 | import pipes 6 | import subprocess 7 | import threading 8 | import pexpect 9 | import signal 10 | 11 | 12 | class ReturnContainer(): 13 | def __init__(self): 14 | self.value = None 15 | 16 | 17 | class Command(object): 18 | def __init__(self, cmd, fail_cmd, post_cmd=None, post_delay=0): 19 | self.cmd = cmd 20 | self.fail_cmd = fail_cmd 21 | self.post_cmd = post_cmd 22 | self.post_delay = post_delay 23 | self.proc = None 24 | 25 | def run(self, timeout=None, retcode=0): 26 | print "Testing '{0}'...".format(" ".join(pipes.quote(s) for s in self.cmd)), 27 | sys.stdout.flush() 28 | 29 | err = None 30 | pipe_kwargs = {"stdout": subprocess.PIPE, "stderr": subprocess.PIPE, "stdin": subprocess.PIPE} 31 | 32 | def target(): 33 | self.proc = subprocess.Popen(self.cmd, **pipe_kwargs) 34 | self.stdout, self.stderr = self.proc.communicate() 35 | 36 | thread = threading.Thread(target=target) 37 | thread.daemon = True 38 | 39 | thread.start() 40 | 41 | if self.post_cmd is not None: 42 | time.sleep(self.post_delay) 43 | subprocess.check_call(self.post_cmd, **pipe_kwargs) 44 | 45 | thread.join(timeout - self.post_delay if timeout is not None else timeout) 46 | 47 | # Checks 48 | if thread.is_alive(): 49 | subprocess.check_call(self.fail_cmd, **pipe_kwargs) 50 | err = Exception("Test failed with timeout!") 51 | 52 | elif self.proc.returncode != retcode: 53 | err = Exception("Test failed with unexpected returncode (expected {0}, got {1})".format(retcode, self.proc.returncode)) 54 | 55 | if err is not None: 56 | print "FAIL" 57 | print "--- STDOUT ---" 58 | print getattr(self, "stdout", "no stdout") 59 | print "--- STDERR ---" 60 | print getattr(self, "stderr", "no stderr") 61 | print "--- ... ---" 62 | raise err 63 | else: 64 | print "OK" 65 | 66 | 67 | def attach_and_type_exit_0(name): 68 | print "Attaching to {0} to exit 0".format(name) 69 | p = pexpect.spawn("docker attach {0}".format(name)) 70 | p.sendline('') 71 | p.sendline('exit 0') 72 | p.close() 73 | 74 | 75 | def attach_and_issue_ctrl_c(name): 76 | print "Attaching to {0} to CTRL+C".format(name) 77 | p = pexpect.spawn("docker attach {0}".format(name)) 78 | p.expect_exact('#') 79 | p.sendintr() 80 | p.close() 81 | 82 | 83 | def test_tty_handling(img, name, base_cmd, fail_cmd, container_command, exit_function, expect_exit_code): 84 | print "Testing TTY handling (using container command '{0}' and exit function '{1}')".format(container_command, exit_function.__name__) 85 | rc = ReturnContainer() 86 | 87 | shell_ready_event = threading.Event() 88 | 89 | def spawn(): 90 | cmd = base_cmd + ["--tty", "--interactive", img, "/tini/dist/tini"] 91 | if os.environ.get("MINIMAL") is None: 92 | cmd.append("--") 93 | cmd.append(container_command) 94 | p = pexpect.spawn(" ".join(cmd)) 95 | p.expect_exact("#") 96 | shell_ready_event.set() 97 | rc.value = p.wait() 98 | 99 | thread = threading.Thread(target=spawn) 100 | thread.daemon = True 101 | 102 | thread.start() 103 | 104 | if not shell_ready_event.wait(2): 105 | raise Exception("Timeout waiting for shell to spawn") 106 | 107 | exit_function(name) 108 | 109 | thread.join(timeout=2) 110 | 111 | if thread.is_alive(): 112 | subprocess.check_call(fail_cmd) 113 | raise Exception("Timeout waiting for container to exit!") 114 | 115 | if rc.value != expect_exit_code: 116 | raise Exception("Return code is: {0} (expected {1})".format(rc.value, expect_exit_code)) 117 | 118 | 119 | 120 | def main(): 121 | img = sys.argv[1] 122 | name = "{0}-test".format(img) 123 | args_disabled = os.environ.get("MINIMAL") 124 | 125 | root = os.path.abspath(os.path.join(os.path.dirname(__file__), "..")) 126 | 127 | base_cmd = [ 128 | "docker", 129 | "run", 130 | "--rm", 131 | "--volume={0}:/tini".format(root), 132 | "--name={0}".format(name), 133 | ] 134 | 135 | fail_cmd = ["docker", "kill", "-s", "KILL", name] 136 | 137 | # Funtional tests 138 | for entrypoint in ["/tini/dist/tini", "/tini/dist/tini-static"]: 139 | functional_base_cmd = base_cmd + [ 140 | "--entrypoint={0}".format(entrypoint), 141 | "-e", "TINI_VERBOSITY=3", 142 | img, 143 | ] 144 | 145 | # Reaping test 146 | Command(functional_base_cmd + ["/tini/test/reaping/stage_1.py"], fail_cmd).run(timeout=10) 147 | 148 | # Signals test 149 | for sig, retcode in [("TERM", 143), ("USR1", 138), ("USR2", 140)]: 150 | Command( 151 | functional_base_cmd + ["/tini/test/signals/test.py"], 152 | fail_cmd, 153 | ["docker", "kill", "-s", sig, name], 154 | 2 155 | ).run(timeout=10, retcode=retcode) 156 | 157 | # Exit code test 158 | Command(functional_base_cmd + ["-z"], fail_cmd).run(retcode=127 if args_disabled else 1) 159 | Command(functional_base_cmd + ["-h"], fail_cmd).run(retcode=127 if args_disabled else 0) 160 | Command(functional_base_cmd + ["zzzz"], fail_cmd).run(retcode=127) 161 | Command(functional_base_cmd + ["-w"], fail_cmd).run(retcode=127 if args_disabled else 0) 162 | 163 | # Valgrind test (we only run this on the dynamic version, because otherwise Valgrind may bring up plenty of errors that are 164 | # actually from libc) 165 | Command(base_cmd + [img, "valgrind", "--leak-check=full", "--error-exitcode=1", "/tini/dist/tini", "ls"], fail_cmd).run() 166 | 167 | # Test tty handling 168 | test_tty_handling(img, name, base_cmd, fail_cmd, "dash", attach_and_type_exit_0, 0) 169 | 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) 170 | 171 | # Installation tests (sh -c is used for globbing and &&) 172 | for image, pkg_manager, extension in [ 173 | ["ubuntu:precise", "dpkg", "deb"], 174 | ["ubuntu:trusty", "dpkg", "deb"], 175 | ["centos:6", "rpm", "rpm"], 176 | ["centos:7", "rpm", "rpm"], 177 | ]: 178 | Command(base_cmd + [image, "sh", "-c", "{0} -i /tini/dist/*.{1} && /usr/bin/tini true".format(pkg_manager, extension)], fail_cmd).run() 179 | 180 | 181 | if __name__ == "__main__": 182 | main() 183 | -------------------------------------------------------------------------------- /test/sigconf/sigconf-test.c: -------------------------------------------------------------------------------- 1 | /* 2 | Test program to: 3 | + Ignore a few signals 4 | + Block a few signals 5 | + Exec whatever the test runner asked for 6 | */ 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | 13 | int main(int argc, char *argv[]) { 14 | // Signals to ignore 15 | signal(SIGTTOU, SIG_IGN); // This one should still be in SigIgn (Tini touches it to ignore it, and should restore it) 16 | signal(SIGSEGV, SIG_IGN); // This one should still be in SigIgn (Tini shouldn't touch it) 17 | signal(SIGINT, SIG_IGN); // This one should still be in SigIgn (Tini should block it to forward it, and restore it) 18 | 19 | // Signals to block 20 | sigset_t set; 21 | sigemptyset(&set); 22 | sigaddset(&set, SIGTTIN); // This one should still be in SigIgn (Tini touches it to ignore it, and should restore it) 23 | sigaddset(&set, SIGILL); // This one should still be in SigIgn (Tini shouldn't touch it) 24 | sigaddset(&set, SIGTERM); // This one should still be in SigIgn (Tini should block it to forward it, and restore it) 25 | sigprocmask(SIG_BLOCK, &set, NULL); 26 | 27 | // Run whatever we were asked to run 28 | execvp(argv[1], argv+1); 29 | } 30 | -------------------------------------------------------------------------------- /test/signals/test.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import signal 3 | import os 4 | 5 | 6 | def main(): 7 | signal.signal(signal.SIGTERM, signal.SIG_DFL) 8 | signal.signal(signal.SIGUSR1, signal.SIG_DFL) 9 | signal.signal(signal.SIGUSR2, signal.SIG_DFL) 10 | os.system("sleep 100") 11 | 12 | if __name__ == "__main__": 13 | main() 14 | -------------------------------------------------------------------------------- /test/subreaper-proxy.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | #coding:utf-8 3 | import os 4 | import sys 5 | 6 | import prctl 7 | 8 | 9 | def main(): 10 | args = sys.argv[1:] 11 | 12 | print("subreaper-proxy: running '%s'" % (" ".join(args))) 13 | 14 | prctl.set_child_subreaper(1) 15 | os.execv(args[0], args) 16 | 17 | 18 | if __name__ == '__main__': 19 | main() 20 | -------------------------------------------------------------------------------- /tpl/README.md.in: -------------------------------------------------------------------------------- 1 | 9 | 10 | 11 | Tini - A tiny but valid `init` for containers 12 | ============================================= 13 | 14 | [![Build Status](https://travis-ci.org/krallin/tini.svg?branch=master)](https://travis-ci.org/krallin/tini) 15 | 16 | Tini is the simplest `init` you could think of. 17 | 18 | All Tini does is spawn a single child (Tini is meant to be run in a container), 19 | and wait for it to exit all the while reaping zombies and performing 20 | signal forwarding. 21 | 22 | 23 | Why Tini? 24 | --------- 25 | 26 | Using Tini has several benefits: 27 | 28 | - It protects you from software that accidentally creates zombie processes, 29 | which can (over time!) starve your entire system for PIDs (and make it 30 | unusable). 31 | - It ensures that the *default signal handlers* work for the software you run 32 | in your Docker image. For example, with Tini, `SIGTERM` properly terminates 33 | your process even if you didn't explicitly install a signal handler for it. 34 | - It does so completely transparently! Docker images that work without Tini 35 | will work with Tini without any changes. 36 | 37 | If you'd like more detail on why this is useful, review this issue discussion: 38 | [What is advantage of Tini?][0]. 39 | 40 | 41 | Using Tini 42 | ---------- 43 | 44 | *NOTE: If you are using Docker 1.13 or greater, Tini is included in Docker 45 | itself. This includes all versions of Docker CE. To enable Tini, just [pass the 46 | `--init` flag to `docker run`][5].* 47 | 48 | *NOTE: There are [pre-built Docker images available for Tini][10]. If 49 | you're currently using an Ubuntu or CentOS image as your base, you can use 50 | one of those as a drop-in replacement.* 51 | 52 | *NOTE: There are Tini packages for Alpine Linux and NixOS. See below for 53 | installation instructions.* 54 | 55 | Add Tini to your container, and make it executable. Then, just invoke Tini 56 | and pass your program and its arguments as arguments to Tini. 57 | 58 | In Docker, you will want to use an entrypoint so you don't have to remember 59 | to manually invoke Tini: 60 | 61 | # Add Tini 62 | ENV TINI_VERSION=v@tini_VERSION_MAJOR@.@tini_VERSION_MINOR@.@tini_VERSION_PATCH@ 63 | ADD https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini /tini 64 | RUN chmod +x /tini 65 | ENTRYPOINT ["/tini", "--"] 66 | 67 | # Run your program under Tini 68 | CMD ["/your/program", "-and", "-its", "arguments"] 69 | # or docker run your-image /your/program ... 70 | 71 | Note that you *can* skip the `--` under certain conditions, but you might 72 | as well always include it to be safe. If you see an error message that 73 | looks like `tini: invalid option -- 'c'`, then you *need* to add the `--`. 74 | 75 | Arguments for Tini itself should be passed like `-v` in the following example: 76 | `/tini -v -- /your/program`. 77 | 78 | *NOTE: The binary linked above is a 64-bit dynamically-linked binary.* 79 | 80 | 81 | ### Signed binaries ### 82 | 83 | The `tini` and `tini-static` binaries are signed using the key `595E85A6B1B4779EA4DAAEC70B588DFF0527A9B7`. 84 | 85 | You can verify their signatures using `gpg` (which you may install using 86 | your package manager): 87 | 88 | ENV TINI_VERSION v@tini_VERSION_MAJOR@.@tini_VERSION_MINOR@.@tini_VERSION_PATCH@ 89 | ADD https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini /tini 90 | ADD https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini.asc /tini.asc 91 | RUN gpg --batch --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 595E85A6B1B4779EA4DAAEC70B588DFF0527A9B7 \ 92 | && gpg --batch --verify /tini.asc /tini 93 | RUN chmod +x /tini 94 | 95 | 96 | ### Verifying binaries via checksum ### 97 | 98 | The `tini` and `tini-static` binaries have generated checksums (`SHA1` and `SHA256`). 99 | 100 | You can verify their checksums using `sha1sum` and `sha256sum` (which you may install using 101 | your package manager): 102 | 103 | ENV TINI_VERSION v@tini_VERSION_MAJOR@.@tini_VERSION_MINOR@.@tini_VERSION_PATCH@ 104 | RUN wget --no-check-certificate --no-cookies --quiet https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini-amd64 \ 105 | && wget --no-check-certificate --no-cookies --quiet https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini-amd64.sha256sum \ 106 | && echo "$(cat tini-amd64.sha256sum)" | sha256sum -c 107 | 108 | 109 | ### Alpine Linux Package ### 110 | 111 | On Alpine Linux, you can use the following command to install Tini: 112 | 113 | RUN apk add --no-cache tini 114 | # Tini is now available at /sbin/tini 115 | ENTRYPOINT ["/sbin/tini", "--"] 116 | 117 | 118 | ### NixOS ### 119 | 120 | Using Nix, you can use the following command to install Tini: 121 | 122 | nix-env --install tini 123 | 124 | 125 | ### Debian ### 126 | 127 | On Debian (Buster or newer), you can use the following command to install Tini: 128 | 129 | apt-get install tini 130 | 131 | Note that this installs `/usr/bin/tini` (and `/usr/bin/tini-static`), not `/tini`. 132 | 133 | 134 | ### Arch Linux ### 135 | 136 | On Arch Linux, there is a package available on the [AUR](https://wiki.archlinux.org/index.php/Arch_User_Repository). 137 | Install using the [official instructions](https://wiki.archlinux.org/index.php/Arch_User_Repository#Installing_packages) 138 | or use an [AUR helper](https://wiki.archlinux.org/index.php/AUR_helpers): 139 | 140 | pacaur -S tini 141 | 142 | 143 | ### Other Platforms ### 144 | 145 | ARM and 32-bit binaries are available! You can find the complete list of 146 | available binaries under [the releases tab][11]. 147 | 148 | 149 | Options 150 | ------- 151 | 152 | ### Verbosity ### 153 | 154 | The `-v` argument can be used for extra verbose output (you can pass it up to 155 | 3 times, e.g. `-vvv`). 156 | 157 | 158 | ### Subreaping ### 159 | 160 | By default, Tini needs to run as PID 1 so that it can reap zombies (by 161 | running as PID 1, zombies get re-parented to Tini). 162 | 163 | If for some reason, you cannot run Tini as PID 1, you should register Tini as 164 | a process subreaper instead (only in Linux >= 3.4), by either: 165 | 166 | + Passing the `-s` argument to Tini (`tini -s -- ...`) 167 | + Setting the environment variable `TINI_SUBREAPER` 168 | (e.g. `export TINI_SUBREAPER=`). 169 | 170 | This will ensure that zombies get re-parented to Tini despite Tini not running 171 | as PID 1. 172 | 173 | *NOTE: Tini will issue a warning if it detects that it isn't running as PID 1 174 | and isn't registered as a subreaper. If you don't see a warning, you're fine.* 175 | 176 | 177 | ### Remapping exit codes ### 178 | 179 | Tini will reuse the child's exit code when exiting, but occasionally, this may 180 | not be exactly what you want (e.g. if your child exits with 143 after receiving 181 | SIGTERM). Notably, this can be an issue with Java apps. 182 | 183 | In this case, you can use the `-e` flag to remap an arbitrary exit code to 0. 184 | You can pass the flag multiple times if needed. 185 | 186 | For example: 187 | 188 | ``` 189 | tini -e 143 -- ... 190 | ``` 191 | 192 | 193 | ### Process group killing ### 194 | 195 | By default, Tini only kills its immediate child process. This can be 196 | inconvenient if sending a signal to that process does not have the desired 197 | effect. For example, if you do 198 | 199 | docker run --rm krallin/ubuntu-tini sh -c 'sleep 10' 200 | 201 | and ctrl-C it, nothing happens: SIGINT is sent to the 'sh' process, 202 | but that shell won't react to it while it is waiting for the 'sleep' 203 | to finish. 204 | 205 | You can configure Tini to kill the child process group, so that every process 206 | in the group gets the signal, by either: 207 | 208 | + Passing the `-g` argument to Tini (`tini -g -- ...`) 209 | + Setting the environment variable `TINI_KILL_PROCESS_GROUP` 210 | (e.g. `export TINI_KILL_PROCESS_GROUP=`). 211 | 212 | This corresponds more closely to what happens when you do ctrl-C etc. in a 213 | terminal: The signal is sent to the foreground process group. 214 | 215 | docker run --rm --entrypoint tini krallin/ubuntu-tini -g -- sh -c 'sleep 10' 216 | 217 | ### Parent Death Signal ### 218 | 219 | Tini can set its parent death signal, which is the signal Tini should receive 220 | when *its* parent exits. To set the parent death signal, use the `-p` flag with 221 | the name of the signal Tini should receive when its parent exits: 222 | 223 | ``` 224 | tini -p SIGTERM -- ... 225 | ``` 226 | 227 | *NOTE: See [this PR discussion][12] to learn more about the parent death signal 228 | and use cases.* 229 | 230 | 231 | More 232 | ---- 233 | 234 | ### Existing Entrypoint ### 235 | 236 | Tini can also be used with an existing entrypoint in your container! 237 | 238 | Assuming your entrypoint was `/docker-entrypoint.sh`, then you would use: 239 | 240 | ENTRYPOINT ["/tini", "--", "/docker-entrypoint.sh"] 241 | 242 | 243 | ### Statically-Linked Version ### 244 | 245 | Tini has very few dependencies (it only depends on libc), but if your 246 | container fails to start, you might want to consider using the statically-built 247 | version instead: 248 | 249 | ADD https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini-static /tini 250 | 251 | 252 | ### Size Considerations ### 253 | 254 | Tini is a very small file (in the 10KB range), so it doesn't add much weight 255 | to your container. 256 | 257 | The statically-linked version is bigger, but still < 1M. 258 | 259 | 260 | Building Tini 261 | ------------- 262 | 263 | If you'd rather not download the binary, you can build Tini by running 264 | `cmake . && make`. 265 | 266 | Before building, you probably also want to run: 267 | 268 | export CFLAGS="-DPR_SET_CHILD_SUBREAPER=36 -DPR_GET_CHILD_SUBREAPER=37" 269 | 270 | This ensure that even if you're building on a system that has old Linux Kernel 271 | headers (< 3.4), Tini will be built with child subreaper support. This is 272 | usually what you want if you're going to use Tini with Docker (if your host 273 | Kernel supports Docker, it should also support child subreapers). 274 | 275 | 276 | Understanding Tini 277 | ------------------ 278 | 279 | After spawning your process, Tini will wait for signals and forward those 280 | to the child process, and periodically reap zombie processes that may be 281 | created within your container. 282 | 283 | When the "first" child process exits (`/your/program` in the examples above), 284 | Tini exits as well, with the exit code of the child process (so you can 285 | check your container's exit code to know whether the child exited 286 | successfully). 287 | 288 | 289 | Debugging 290 | --------- 291 | 292 | If something isn't working just like you expect, consider increasing the 293 | verbosity level (up to 3): 294 | 295 | tini -v -- bash -c 'exit 1' 296 | tini -vv -- true 297 | tini -vvv -- pwd 298 | 299 | 300 | Authors 301 | ======= 302 | 303 | Maintainer: 304 | 305 | + [Thomas Orozco][20] 306 | 307 | Contributors: 308 | 309 | + [Tianon Gravi][30] 310 | + [David Wragg][31] 311 | + [Michael Crosby][32] 312 | + [Wyatt Preul][33] 313 | + [Patrick Steinhardt][34] 314 | 315 | Special thanks to: 316 | 317 | + [Danilo Bürger][40] for packaging Tini for Alpine 318 | + [Asko Soukka][41] for packaging Tini for Nix 319 | + [nfnty][42] for packaging Tini for Arch Linux 320 | 321 | 322 | [0]: https://github.com/krallin/tini/issues/8 323 | [5]: https://docs.docker.com/engine/reference/commandline/run/ 324 | [10]: https://github.com/krallin/tini-images 325 | [11]: https://github.com/krallin/tini/releases 326 | [12]: https://github.com/krallin/tini/pull/114 327 | [20]: https://github.com/krallin/ 328 | [30]: https://github.com/tianon 329 | [31]: https://github.com/dpw 330 | [32]: https://github.com/crosbymichael 331 | [33]: https://github.com/geek 332 | [34]: https://github.com/pks-t 333 | [40]: https://github.com/danilobuerger 334 | [41]: https://github.com/datakurre 335 | [42]: https://github.com/nfnty/pkgbuilds/tree/master/tini/tini 336 | -------------------------------------------------------------------------------- /tpl/VERSION.in: -------------------------------------------------------------------------------- 1 | @tini_VERSION_MAJOR@.@tini_VERSION_MINOR@.@tini_VERSION_PATCH@ 2 | --------------------------------------------------------------------------------