[
  {
    "path": ".dockerignore",
    "content": ".tox\n.git\n"
  },
  {
    "path": ".github/workflows/ci.yaml",
    "content": "name: CI\non: push\njobs:\n    build-and-test:\n        runs-on: ubuntu-20.04\n        strategy:\n            fail-fast: false\n            matrix:\n                include:\n                    - arch: amd64\n                      manylinux_arch: x86_64\n                      docker_image: debian:buster\n\n                    - arch: arm64\n                      manylinux_arch: aarch64\n                      docker_image: arm64v8/debian:buster\n\n                    - arch: ppc64le\n                      manylinux_arch: ppc64le\n                      docker_image: ppc64le/debian:buster\n\n                    - arch: s390x\n                      manylinux_arch: s390x\n                      docker_image: s390x/debian:buster\n\n        env:\n            BASE_IMAGE: ${{ matrix.docker_image }}\n\n        steps:\n            - uses: actions/checkout@v2\n\n            - name: Set up QEMU\n              id: qemu\n              uses: docker/setup-qemu-action@v1\n              if: ${{ matrix.arch != 'amd64' }}\n              with:\n                image: tonistiigi/binfmt:latest\n\n            - name: Build Docker image\n              run: make docker-image\n\n            - name: Run python tests\n              run: docker run --rm -v $(pwd):/mnt:rw dumb-init-build /mnt/ci/docker-python-test\n\n            - name: Build Debian package\n              run: docker run --init --rm -v $(pwd):/mnt:rw dumb-init-build make -C /mnt builddeb\n\n            - name: Test built Debian package\n              # XXX: This uses the clean base image (not the build one) to make\n              # sure it installs in a clean image without any hidden dependencies.\n              run: docker run --rm -v $(pwd):/mnt:rw ${{ matrix.docker_image }} /mnt/ci/docker-deb-test\n\n            - name: Build wheels\n              run: sudo make python-dists-${{ matrix.manylinux_arch }}\n\n            - name: Upload build artifacts\n              uses: actions/upload-artifact@v2\n              with:\n                  name: ${{ matrix.arch }}\n                  path: dist\n"
  },
  {
    "path": ".gitignore",
    "content": "*.deb\n*.egg-info\n*.gc*\n*.o\n*.py[cod]\n.pytest_cache\n.tox\n__pycache__/\nbuild/\ndist/\ndumb-init\n"
  },
  {
    "path": ".pre-commit-config.yaml",
    "content": "repos:\n-   repo: https://github.com/pre-commit/pre-commit-hooks\n    rev: v4.4.0\n    hooks:\n    -   id: check-added-large-files\n    -   id: check-docstring-first\n    -   id: check-executables-have-shebangs\n    -   id: check-merge-conflict\n    -   id: check-yaml\n    -   id: debug-statements\n    -   id: detect-private-key\n    -   id: double-quote-string-fixer\n    -   id: end-of-file-fixer\n    -   id: name-tests-test\n    -   id: requirements-txt-fixer\n    -   id: trailing-whitespace\n-   repo: https://github.com/pre-commit/mirrors-autopep8\n    rev: v2.0.0\n    hooks:\n    -   id: autopep8\n-   repo: https://github.com/pycqa/flake8\n    rev: 6.0.0\n    hooks:\n    -   id: flake8\n-   repo: https://github.com/asottile/reorder_python_imports\n    rev: v3.9.0\n    hooks:\n    -   id: reorder-python-imports\n        args: ['--py3-plus']\n-   repo: https://github.com/Lucas-C/pre-commit-hooks\n    rev: v1.3.1\n    hooks:\n    -   id: remove-tabs\n-   repo: https://github.com/asottile/pyupgrade\n    rev: v3.3.0\n    hooks:\n    -   id: pyupgrade\n        args: ['--py3-plus']\n-   repo: https://github.com/asottile/add-trailing-comma\n    rev: v2.3.0\n    hooks:\n    -   id: add-trailing-comma\n        args: ['--py36-plus']\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "Contributing to dumb-init\n========\n\n`dumb-init` is primarily developed by [Yelp](https://yelp.github.io/), but\ncontributions are welcome from everyone!\n\nCode is reviewed using GitHub pull requests. To make a contribution, you should:\n\n1. Fork the GitHub repository\n2. Push code to a branch on your fork\n3. Create a pull request and wait for it to be reviewed\n\nWe aim to have all dumb-init behavior covered by tests. If you make a change in\nbehavior, please add a test to ensure it doesn't regress. We're also happy to\nhelp with suggestions on testing!\n\n\n## Releasing new versions\n\n`dumb-init` uses [semantic versioning](http://semver.org/). If you're making a\ncontribution, please don't bump the version number yourself—we'll take care\nof that after merging!\n\nThe process to release a new version is:\n\n1. Update the version in `VERSION` and run `make VERSION.h`\n2. Update the Debian changelog with `dch -v {new version}`.\n3. Update the two `wget` urls in the README to point to the new version.\n4. Commit the changes and tag the commit like `v1.0.0`.\n5. `git push --tags origin master`\n6. Wait for Travis to run, then find and download the binary and Debian\n   packages for all architectures; there will be links printed at the\n   end of the Travis output. Put these into your `dist` directory.\n7. Run `make release`\n8. Run `twine upload --skip-existing dist/*.tar.gz dist/*.whl` to upload the\n   new version to PyPI\n9. Upload the resulting Debian packages, binaries, and sha256sums file (all\n   inside the `dist` directory) to a new [GitHub\n   release](https://github.com/Yelp/dumb-init/releases)\n"
  },
  {
    "path": "Dockerfile",
    "content": "ARG BASE_IMAGE=debian:buster\nFROM $BASE_IMAGE\n\nLABEL maintainer=\"Chris Kuehl <ckuehl@yelp.com>\"\n\n# Install the bare minimum dependencies necessary for working with Debian\n# packages. Build dependencies should be added under \"Build-Depends\" inside\n# debian/control instead.\nRUN : \\\n    && apt-get update \\\n    && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \\\n        build-essential \\\n        devscripts \\\n        equivs \\\n        lintian \\\n        python3-distutils \\\n        python3-setuptools \\\n        python3-pip \\\n    && apt-get clean \\\n    && rm -rf /var/lib/apt/lists/*\nWORKDIR /tmp/mnt\n\nCOPY debian/control /control\nRUN : \\\n    && apt-get update \\\n    && mk-build-deps --install --tool 'apt-get -y --no-install-recommends' /control \\\n    && apt-get clean \\\n    && rm -rf /var/lib/apt/lists/*\n"
  },
  {
    "path": "LICENSE",
    "content": "The MIT License (MIT)\n\nCopyright (c) 2015 Yelp, Inc.\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n"
  },
  {
    "path": "MANIFEST.in",
    "content": "include dumb-init.c\ninclude VERSION\ninclude VERSION.h\n"
  },
  {
    "path": "Makefile",
    "content": "SHELL=bash\nCFLAGS=-std=gnu99 -static -s -Wall -Werror -O3\n\nTEST_PACKAGE_DEPS := build-essential python python-pip procps python-dev python-setuptools\n\nDOCKER_RUN_TEST := docker run -v $(PWD):/mnt:ro\nVERSION = $(shell cat VERSION)\n\n.PHONY: build\nbuild: VERSION.h\n\t$(CC) $(CFLAGS) -o dumb-init dumb-init.c\n\nVERSION.h: VERSION\n\techo '// THIS FILE IS AUTOMATICALLY GENERATED' > VERSION.h\n\techo '// Run `make VERSION.h` to update it after modifying VERSION.' >> VERSION.h\n\txxd -i VERSION >> VERSION.h\n\n.PHONY: clean\nclean: clean-tox\n\trm -rf dumb-init dist/ *.deb\n\n.PHONY: clean-tox\nclean-tox:\n\trm -rf .tox\n\n.PHONY: release\nrelease: python-dists\n\tcd dist && \\\n\t\tsha256sum --binary dumb-init_$(VERSION)_amd64.deb dumb-init_$(VERSION)_x86_64 dumb-init_$(VERSION)_ppc64el.deb dumb-init_$(VERSION)_ppc64le dumb-init_$(VERSION)_s390x.deb dumb-init_$(VERSION)_s390x dumb-init_$(VERSION)_arm64.deb dumb-init_$(VERSION)_aarch64 \\\n\t\t> sha256sums\n\n.PHONY: python-dists\npython-dists: python-dists-x86_64 python-dists-aarch64 python-dists-ppc64le python-dists-s390x\n\n.PHONY: python-dists-%\npython-dists-%: VERSION.h\n\tpython setup.py sdist\n\tdocker run \\\n\t\t--user $$(id -u):$$(id -g) \\\n\t\t-v `pwd`/dist:/dist:rw \\\n\t\tquay.io/pypa/manylinux2014_$*:latest \\\n\t\tbash -exc ' \\\n\t\t\t/opt/python/cp38-cp38/bin/pip wheel --wheel-dir /tmp /dist/*.tar.gz && \\\n\t\t\tauditwheel repair --wheel-dir /dist /tmp/*.whl --wheel-dir /dist \\\n\t\t'\n\n.PHONY: builddeb\nbuilddeb:\n\tdebuild --set-envvar=CC=musl-gcc -us -uc -b\n\tmkdir -p dist\n\tmv ../dumb-init_*.deb dist/\n\t# Extract the built binary from the Debian package\n\tdpkg-deb --fsys-tarfile dist/dumb-init_$(VERSION)_$(shell dpkg --print-architecture).deb | \\\n\t\ttar -C dist --strip=3 -xvf - ./usr/bin/dumb-init\n\tmv dist/dumb-init dist/dumb-init_$(VERSION)_$(shell uname -m)\n\n.PHONY: builddeb-docker\nbuilddeb-docker: docker-image\n\tmkdir -p dist\n\tdocker run --init --user $$(id -u):$$(id -g) -v $(PWD):/tmp/mnt dumb-init-build make builddeb\n\n.PHONY: docker-image\ndocker-image:\n\tdocker build $(if $(BASE_IMAGE),--build-arg BASE_IMAGE=$(BASE_IMAGE)) -t dumb-init-build .\n\n.PHONY: test\ntest:\n\ttox\n\ttox -e pre-commit\n\n.PHONY: install-hooks\ninstall-hooks:\n\ttox -e pre-commit -- install -f --install-hooks\n"
  },
  {
    "path": "README.md",
    "content": "dumb-init\n========\n\n[![PyPI version](https://badge.fury.io/py/dumb-init.svg)](https://pypi.python.org/pypi/dumb-init)\n\n\n**dumb-init** is a simple process supervisor and init system designed to run as\nPID 1 inside minimal container environments (such as [Docker][docker]). It is\ndeployed as a small, statically-linked binary written in C.\n\nLightweight containers have popularized the idea of running a single process or\nservice without normal init systems like [systemd][systemd] or\n[sysvinit][sysvinit]. However, omitting an init system often leads to incorrect\nhandling of processes and signals, and can result in problems such as\ncontainers which can't be gracefully stopped, or leaking containers which\nshould have been destroyed.\n\n`dumb-init` enables you to simply prefix your command with `dumb-init`. It acts\nas PID 1 and immediately spawns your command as a child process, taking care to\nproperly handle and forward signals as they are received.\n\n\n## Why you need an init system\n\nNormally, when you launch a Docker container, the process you're executing\nbecomes PID 1, giving it the quirks and responsibilities that come with being\nthe init system for the container.\n\nThere are two common issues this presents:\n\n1. In most cases, signals won't be handled properly.\n\n   The Linux kernel applies special signal handling to processes which run as\n   PID 1.\n\n   When processes are sent a signal on a normal Linux system, the kernel will\n   first check for any custom handlers the process has registered for that\n   signal, and otherwise fall back to default behavior (for example, killing\n   the process on `SIGTERM`).\n\n   However, if the process receiving the signal is PID 1, it gets special\n   treatment by the kernel; if it hasn't registered a handler for the signal,\n   the kernel won't fall back to default behavior, and nothing happens. In\n   other words, if your process doesn't explicitly handle these signals,\n   sending it `SIGTERM` will have no effect at all.\n\n   A common example is CI jobs that do `docker run my-container script`: sending\n   `SIGTERM` to the `docker run` process will typically kill the `docker run` command,\n   but leave the container running in the background.\n\n2. Orphaned zombie processes aren't properly reaped.\n\n   A process becomes a zombie when it exits, and remains a zombie until its\n   parent calls some variation of the `wait()` system call on it. It remains in\n   the process table as a \"defunct\" process. Typically, a parent process will\n   call `wait()` immediately and avoid long-living zombies.\n\n   If a parent exits before its child, the child is \"orphaned\", and is\n   re-parented under PID 1. The init system is thus responsible for\n   `wait()`-ing on orphaned zombie processes.\n\n   Of course, most processes *won't* `wait()` on random processes that happen\n   to become attached to them, so containers often end with dozens of zombies\n   rooted at PID 1.\n\n\n## What `dumb-init` does\n\n`dumb-init` runs as PID 1, acting like a simple init system. It launches a\nsingle process and then proxies all received signals to a session rooted at\nthat child process.\n\nSince your actual process is no longer PID 1, when it receives signals from\n`dumb-init`, the default signal handlers will be applied, and your process will\nbehave as you would expect. If your process dies, `dumb-init` will also die,\ntaking care to clean up any other processes that might still remain.\n\n\n### Session behavior\n\nIn its default mode, `dumb-init` establishes a\n[session](http://man7.org/linux/man-pages/man2/setsid.2.html) rooted at the\nchild, and sends signals to the entire process group. This is useful if you\nhave a poorly-behaving child (such as a shell script) which won't normally\nsignal its children before dying.\n\nThis can actually be useful outside of Docker containers in regular process\nsupervisors like [daemontools][daemontools] or [supervisord][supervisord] for\nsupervising shell scripts. Normally, a signal like `SIGTERM` received by a\nshell isn't forwarded to subprocesses; instead, only the shell process dies.\nWith dumb-init, you can just write shell scripts with dumb-init in the shebang:\n\n    #!/usr/bin/dumb-init /bin/sh\n    my-web-server &  # launch a process in the background\n    my-other-server  # launch another process in the foreground\n\nOrdinarily, a `SIGTERM` sent to the shell would kill the shell but leave those\nprocesses running (both the background and foreground!).  With dumb-init, your\nsubprocesses will receive the same signals your shell does.\n\nIf you'd like for signals to only be sent to the direct child, you can run with\nthe `--single-child` argument, or set the environment variable\n`DUMB_INIT_SETSID=0` when running `dumb-init`. In this mode, dumb-init is\ncompletely transparent; you can even string multiple together (like `dumb-init\ndumb-init echo 'oh, hi'`).\n\n\n### Signal rewriting\n\ndumb-init allows rewriting incoming signals before proxying them. This is\nuseful in cases where you have a Docker supervisor (like Mesos or Kubernetes)\nwhich always sends a standard signal (e.g. SIGTERM). Some apps require a\ndifferent stop signal in order to do graceful cleanup.\n\nFor example, to rewrite the signal SIGTERM (number 15) to SIGQUIT (number 3),\njust add `--rewrite 15:3` on the command line.\n\nTo drop a signal entirely, you can rewrite it to the special number `0`.\n\n\n#### Signal rewriting special case\n\nWhen running in setsid mode, it is not sufficient to forward\n`SIGTSTP`/`SIGTTIN`/`SIGTTOU` in most cases, since if the process has not added\na custom signal handler for these signals, then the kernel will not apply\ndefault signal handling behavior (which would be suspending the process) since\nit is a member of an orphaned process group. For this reason, we set default\nrewrites to `SIGSTOP` from those three signals. You can opt out of this\nbehavior by rewriting the signals back to their original values, if desired.\n\nOne caveat with this feature: for job control signals (`SIGTSTP`, `SIGTTIN`,\n`SIGTTOU`), dumb-init will always suspend itself after receiving the signal,\neven if you rewrite it to something else.\n\n\n## Installing inside Docker containers\n\nYou have a few options for using `dumb-init`:\n\n\n### Option 1: Installing from your distro's package repositories (Debian, Ubuntu, etc.)\n\nMany popular Linux distributions (including Debian (since `stretch`) and Debian\nderivatives such as Ubuntu (since `bionic`)) now contain dumb-init packages in\ntheir official repositories.\n\nOn Debian-based distributions, you can run `apt install dumb-init` to install\ndumb-init, just like you'd install any other package.\n\n*Note:* Most distro-provided versions of dumb-init are not statically-linked,\nunlike the versions we provide (see the other options below). This is normally\nperfectly fine, but means that these versions of dumb-init generally won't work\nwhen copied to other Linux distros, unlike the statically-linked versions we\nprovide.\n\n\n### Option 2: Installing via an internal apt server (Debian/Ubuntu)\n\nIf you have an internal apt server, uploading the `.deb` to your server is the\nrecommended way to use `dumb-init`. In your Dockerfiles, you can simply\n`apt install dumb-init` and it will be available.\n\nDebian packages are available from the [GitHub Releases tab][gh-releases], or\nyou can run `make builddeb` yourself.\n\n\n### Option 3: Installing the `.deb` package manually (Debian/Ubuntu)\n\nIf you don't have an internal apt server, you can use `dpkg -i` to install the\n`.deb` package. You can choose how you get the `.deb` onto your container\n(mounting a directory or `wget`-ing it are some options).\n\nOne possibility is with the following commands in your Dockerfile:\n\n```Dockerfile\nRUN wget https://github.com/Yelp/dumb-init/releases/download/v1.2.5/dumb-init_1.2.5_amd64.deb\nRUN dpkg -i dumb-init_*.deb\n```\n\n\n### Option 4: Downloading the binary directly\n\nSince dumb-init is released as a statically-linked binary, you can usually just\nplop it into your images. Here's an example of doing that in a Dockerfile:\n\n```Dockerfile\nRUN wget -O /usr/local/bin/dumb-init https://github.com/Yelp/dumb-init/releases/download/v1.2.5/dumb-init_1.2.5_x86_64\nRUN chmod +x /usr/local/bin/dumb-init\n```\n\n\n### Option 5: Installing from PyPI\n\nThough `dumb-init` is written entirely in C, we also provide a Python package\nwhich compiles and installs the binary. It can be installed [from\nPyPI](https://pypi.python.org/pypi/dumb-init) using `pip`. You'll want to first\ninstall a C compiler (on Debian/Ubuntu, `apt-get install gcc` is sufficient),\nthen just `pip install dumb-init`.\n\nAs of 1.2.0, the package at PyPI is available as a pre-built wheel archive and does not\nneed to be compiled on common Linux distributions.\n\n\n## Usage\n\nOnce installed inside your Docker container, simply prefix your commands with\n`dumb-init` (and make sure that you're using [the recommended JSON\nsyntax][docker-cmd-json]).\n\nWithin a Dockerfile, it's a good practice to use dumb-init as your container's\nentrypoint. An \"entrypoint\" is a partial command that gets prepended to your\n`CMD` instruction, making it a great fit for dumb-init:\n\n```Dockerfile\n# Runs \"/usr/bin/dumb-init -- /my/script --with --args\"\nENTRYPOINT [\"/usr/bin/dumb-init\", \"--\"]\n\n# or if you use --rewrite or other cli flags\n# ENTRYPOINT [\"dumb-init\", \"--rewrite\", \"2:3\", \"--\"]\n\nCMD [\"/my/script\", \"--with\", \"--args\"]\n```\n\nIf you declare an entrypoint in a base image, any images that descend from it\ndon't need to also declare dumb-init. They can just set a `CMD` as usual.\n\nFor interactive one-off usage, you can just prepend it manually:\n\n    $ docker run my_container dumb-init python -c 'while True: pass'\n\nRunning this same command without `dumb-init` would result in being unable to\nstop the container without `SIGKILL`, but with `dumb-init`, you can send it\nmore humane signals like `SIGTERM`.\n\nIt's important that you use [the JSON syntax][docker-cmd-json] for `CMD` and\n`ENTRYPOINT`. Otherwise, Docker invokes a shell to run your command, resulting\nin the shell as PID 1 instead of dumb-init.\n\n\n### Using a shell for pre-start hooks\n\nOften containers want to do some pre-start work which can't be done during\nbuild time. For example, you might want to template out some config files based\non environment variables.\n\nThe best way to integrate that with dumb-init is like this:\n\n```Dockerfile\nENTRYPOINT [\"/usr/bin/dumb-init\", \"--\"]\nCMD [\"bash\", \"-c\", \"do-some-pre-start-thing && exec my-server\"]\n```\n\nBy still using dumb-init as the entrypoint, you always have a proper init\nsystem in place.\n\nThe `exec` portion of the bash command is important because it [replaces the\nbash process][exec] with your server, so that the shell only exists momentarily\nat start.\n\n\n## Building dumb-init\n\nBuilding the dumb-init binary requires a working compiler and libc headers and\ndefaults to glibc.\n\n    $ make\n\n\n### Building with musl\n\nStatically compiled dumb-init is over 700KB due to glibc, but musl is now an\noption. On Debian/Ubuntu `apt-get install musl-tools` to install the source and\nwrappers, then just:\n\n    $ CC=musl-gcc make\n\nWhen statically compiled with musl the binary size is around 20KB.\n\n\n### Building the Debian package\n\nWe use the standard Debian conventions for specifying build dependencies (look\nin `debian/control`). An easy way to get started is to `apt-get install\nbuild-essential devscripts equivs`, and then `sudo mk-build-deps -i --remove`\nto install all of the missing build dependencies automatically. You can then\nuse `make builddeb` to build dumb-init Debian packages.\n\nIf you prefer an automated Debian package build using Docker, just run `make\nbuilddeb-docker`. This is easier, but requires you to have Docker running on\nyour machine.\n\n\n## See also\n\n* [Docker and the PID 1 zombie reaping problem (Phusion Blog)](https://blog.phusion.nl/2015/01/20/docker-and-the-pid-1-zombie-reaping-problem/)\n* [Trapping signals in Docker containers (@gchudnov)](https://medium.com/@gchudnov/trapping-signals-in-docker-containers-7a57fdda7d86)\n* [tini](https://github.com/krallin/tini), an alternative to dumb-init\n* [pid1](https://github.com/fpco/pid1), an alternative to dumb-init, written in Haskell\n\n\n[daemontools]: http://cr.yp.to/daemontools.html\n[docker-cmd-json]: https://docs.docker.com/engine/reference/builder/#run\n[docker]: https://www.docker.com/\n[exec]: https://en.wikipedia.org/wiki/Exec_(system_call)\n[gh-releases]: https://github.com/Yelp/dumb-init/releases\n[supervisord]: http://supervisord.org/\n[systemd]: https://wiki.freedesktop.org/www/Software/systemd/\n[sysvinit]: https://wiki.archlinux.org/index.php/SysVinit\n"
  },
  {
    "path": "VERSION",
    "content": "1.2.5\n"
  },
  {
    "path": "VERSION.h",
    "content": "// THIS FILE IS AUTOMATICALLY GENERATED\n// Run `make VERSION.h` to update it after modifying VERSION.\nunsigned char VERSION[] = {\n  0x31, 0x2e, 0x32, 0x2e, 0x35, 0x0a\n};\nunsigned int VERSION_len = 6;\n"
  },
  {
    "path": "ci/artifact-upload",
    "content": "#!/bin/bash\nset -euo pipefail\n\nif [ -n \"${ARTIFACTS_SECRET:-}\" ]; then\n    # Travis has built-in support for artifact uploading, but it's broken on ppc64le:\n    # https://github.com/travis-ci/travis-ci/issues/9710\n    pip install --user awscli\n\n    ARTIFACTS_PATH=\"dumb-init/${TRAVIS_BUILD_NUMBER}/${ITEST_TARGET}-${TRAVIS_OS_NAME}\"\n    echo 'Uploading artifacts:'\n    for f in dist/*; do\n        AWS_ACCESS_KEY_ID=$ARTIFACTS_KEY AWS_SECRET_ACCESS_KEY=$ARTIFACTS_SECRET ~/.local/bin/aws \\\n            s3 --region $ARTIFACTS_REGION \\\n            cp \"$f\" s3://$ARTIFACTS_BUCKET/$ARTIFACTS_PATH/$(dirname \"$f\")/\n       echo \"* https://${ARTIFACTS_BUCKET}.s3.amazonaws.com/$ARTIFACTS_PATH/$f\"\n    done\nfi\n"
  },
  {
    "path": "ci/docker-deb-test",
    "content": "#!/bin/bash -eux\nset -o pipefail\n\napt-get update\napt-get -y --no-install-recommends install python3-pip procps\n\ncd /mnt\ndpkg -i dist/*.deb\npip3 install -r requirements-dev.txt\npytest tests/\n\nexec dumb-init /mnt/tests/test-zombies\n"
  },
  {
    "path": "ci/docker-python-test",
    "content": "#!/bin/bash -eux\nset -euo pipefail\n\ncd /mnt\n\npython3 setup.py clean\npython3 setup.py sdist\npip3 install -vv dist/*.tar.gz\npip3 install -r requirements-dev.txt\npytest-3 -vv tests/\n\nexec dumb-init /mnt/tests/test-zombies\n"
  },
  {
    "path": "ci/gcov-build",
    "content": "#!/usr/bin/env bash\nset -euo pipefail\nenvbindir=\"$1\"\ncc -c dumb-init.c -o dumb-init.o -g --coverage\ncc dumb-init.o -o \"${envbindir}/dumb-init\" -g --coverage\n"
  },
  {
    "path": "ci/gcov-report",
    "content": "#!/usr/bin/env bash\nset -euo pipefail\ngcov -f dumb-init.c\ngrep '#####' dumb-init.c.gcov | cut -d':' -f2 | xargs echo \"Missing:\"\n"
  },
  {
    "path": "circle.yml",
    "content": "machine:\n    services:\n        - docker\n\ndependencies:\n    # Without overriding, Circle CI infers that it should run `python setup.py\n    # install` on the host, which we don't want (instead we run all our tests\n    # in Docker containers).\n    #\n    # Overriding with an empty list or list with an empty string doesn't seem\n    # to work, so we use a little hackery.\n    override:\n        - /bin/true\n\ntest:\n    override:\n        - ci/circle:\n            parallel: true\n"
  },
  {
    "path": "debian/.gitignore",
    "content": "/*.log\n/*.substvars\n/files\n/substvars\n/dumb-init/\n/dumb-init.1\n/debhelper-build-stamp\n"
  },
  {
    "path": "debian/changelog",
    "content": "dumb-init (1.2.5) unstable; urgency=medium\n\n  * Change the working directory in the parent process to \"/\" after forking.\n\n    https://github.com/Yelp/dumb-init/pull/210\n\n    Thanks to @Villemoes for the patch!\n\n -- Chris Kuehl <ckuehl@yelp.com>  Thu, 10 Dec 2020 10:54:47 -0800\n\ndumb-init (1.2.4) unstable; urgency=medium\n\n  * Actually fix the bug that can cause `--help` or `--version` to crash in\n    some scenarios.\n\n    https://github.com/Yelp/dumb-init/pull/215\n\n    Thanks to @suve for the patch!\n\n -- Chris Kuehl <ckuehl@yelp.com>  Mon, 07 Dec 2020 11:58:06 -0800\n\ndumb-init (1.2.3) unstable; urgency=medium\n\n  * Fix a bug that can cause `--help` or `--version` to crash in some\n    scenarios.\n\n    https://github.com/Yelp/dumb-init/pull/213\n\n    Thanks to @suve for the patch!\n\n -- Chris Kuehl <ckuehl@yelp.com>  Wed, 02 Dec 2020 10:43:02 -0800\n\ndumb-init (1.2.2) unstable; urgency=medium\n\n  * Fix a race condition which can cause the child to receive SIGHUP and\n    SIGCONT very shortly after start (#174).\n\n    In general this was very rare, but some environments (especially some\n    container and virtualization environments) appear to encounter it at a\n    much higher rate, possibly due to scheduler quirks.\n\n -- Chris Kuehl <ckuehl@yelp.com>  Wed, 01 Aug 2018 16:36:22 -0700\n\ndumb-init (1.2.1) unstable; urgency=medium\n\n  * Fix verbose debug logging for ignored signals.\n\n    Before this patch, they were reported in the verbose log as \"forwarded\n    signal 0 to children\" instead of \"not forwarding signal to children\".\n\n    Since signal 0 is a noop, there is no actual behavior change here.\n\n    Thanks @kpengboy for the patch!\n\n -- Chris Kuehl <ckuehl@yelp.com>  Fri, 01 Dec 2017 10:00:27 -0800\n\ndumb-init (1.2.0) unstable; urgency=medium\n\n  * Hand the controlling TTY to the child process, if we have one (#122).\n\n    This fixes warnings that are printed when running a typical command like:\n        docker run -ti <image> dumb-init bash\n    ...as well as allowing you to use job control.\n\n    Thanks to @ehlers for the patch, and @alhafoudh (and several others) for\n    reporting the issue and providing details!\n\n -- Chris Kuehl <ckuehl@yelp.com>  Mon, 10 Oct 2016 14:09:57 -0700\n\ndumb-init (1.1.3) unstable; urgency=low\n\n  * Add support for FreeBSD kernel. Thanks @onlyjob for bringing this to our\n    attention.\n\n -- Chris Kuehl <ckuehl@yelp.com>  Tue, 02 Aug 2016 10:57:11 -0700\n\ndumb-init (1.1.2) unstable; urgency=low\n\n  * Fix race when the child exits very quickly that leads to dumb-init not\n    reaping the child. This should be pretty rare and most likely to happen\n    when dumb-init's child fails to exec (such as when you try to run a file\n    that doesn't exist).\n\n -- Chris Kuehl <ckuehl@yelp.com>  Mon, 25 Jul 2016 22:46:40 -0700\n\ndumb-init (1.1.1) unstable; urgency=medium\n\n  * Fix segfault when passing unknown arguments (thanks @asottile for\n    noticing!) (#88).\n\n -- Chris Kuehl <ckuehl@yelp.com>  Fri, 17 Jun 2016 12:11:22 -0700\n\ndumb-init (1.1.0) unstable; urgency=medium\n\n  * Add ability to rewrite incoming signals before proxying via the --rewrite\n    flag. Thanks @mcclurmc for the PR (#83)!\n  * Add ability to not rewrite incoming \"suspend\" job control signals in\n    setsid mode (#85).\n  * Add ability to ignore (not proxy) incoming signals (#86).\n\n -- Chris Kuehl <ckuehl@yelp.com>  Tue, 14 Jun 2016 11:45:50 -0700\n\ndumb-init (1.0.3) unstable; urgency=medium\n\n  * Fix incorrect error message when calling with bad arguments (e.g. a\n    command which did not exist) and passing flags (#82).\n  * Tag Python packages of dumb-init properly (#75).\n\n -- Chris Kuehl <ckuehl@yelp.com>  Wed, 01 Jun 2016 20:25:24 -0400\n\ndumb-init (1.0.2) unstable; urgency=low\n\n  * Rewrite signal handling to process signals synchronously (#72).\n    This shouldn't change behavior, but does eliminate some undefined behavior\n    and potential edge cases (especially race conditions).\n    Thanks @msavage20 for the bug report (again)!\n\n -- Chris Kuehl <ckuehl@yelp.com>  Mon, 02 May 2016 10:59:14 -0700\n\ndumb-init (1.0.1) unstable; urgency=low\n\n  * Fix exit status for processes which exit due to signal (#59).\n    Thanks @msavage20 for the bug report!\n\n -- Chris Kuehl <ckuehl@yelp.com>  Fri, 11 Mar 2016 14:05:58 -0800\n\ndumb-init (1.0.0) unstable; urgency=low\n\n  * Compile dumb-init with musl and strip the binary of unnecessary symbols\n    (thanks Konrad Scherer)\n  * Fix some typos (thanks Anthony Sottile)\n  * Add a manpage to the Debian package\n\n -- Chris Kuehl <ckuehl@yelp.com>  Thu, 07 Jan 2016 15:00:19 -0800\n\ndumb-init (0.5.0) unstable; urgency=low\n\n  * Add command-line option parsing (supplements existing environment\n    variables).\n  * Prefix debug output with '[dumb-init]'\n\n -- Chris Kuehl <ckuehl@yelp.com>  Fri, 02 Oct 2015 18:12:51 -0700\n\ndumb-init (0.4.0) unstable; urgency=medium\n\n  * Properly respond to job control signals (SIGTSTP, SIGTTIN, SIGTTOU).\n\n    This makes it possible to suspend dumb-init (and its child process) by\n    hitting ^Z in an interactive shell session.\n\n -- Chris Kuehl <ckuehl@ocf.berkeley.edu>  Tue, 29 Sep 2015 13:45:06 -0700\n\ndumb-init (0.3.1) unstable; urgency=medium\n\n  * Exit nonzero if exec() fails (such as trying to run a nonexistent process)\n\n -- Chris Kuehl <ckuehl@yelp.com>  Tue, 29 Sep 2015 11:18:12 -0700\n\ndumb-init (0.3.0) unstable; urgency=low\n\n  * Send TERM to all processes in the session when the primary child dies when\n    running in setsid mode.\n\n -- Chris Kuehl <ckuehl@yelp.com>  Fri, 18 Sep 2015 11:08:05 -0700\n\ndumb-init (0.2.0) unstable; urgency=low\n\n  * Use setsid for process-group behavior. This fixes tty interaction.\n  * Properly reap zombie processes.\n\n -- Kent Wills <rkwills@yelp.com>  Thu, 10 Sep 2015 13:33:25 -0700\n\ndumb-init (0.1.0) unstable; urgency=low\n\n  * Add process group support\n\n -- Chris Kuehl <ckuehl@yelp.com>  Thu, 03 Sep 2015 17:55:44 -0700\n\ndumb-init (0.0.2) unstable; urgency=low\n\n  * Exit with the same exit status as the process we call.\n  * Print a more useful help message when called with no arguments.\n\n -- Chris Kuehl <ckuehl@yelp.com>  Thu, 06 Aug 2015 13:51:38 -0700\n\ndumb-init (0.0.1) unstable; urgency=low\n\n  * Initial release.\n\n -- Chris Kuehl <ckuehl@yelp.com>  Thu, 06 Aug 2015 13:51:38 -0700\n"
  },
  {
    "path": "debian/clean",
    "content": "tests/*.pyc\ntests/*/*.pyc\n"
  },
  {
    "path": "debian/compat",
    "content": "9\n"
  },
  {
    "path": "debian/control",
    "content": "Source: dumb-init\nSection: utils\nPriority: extra\nMaintainer: Chris Kuehl <ckuehl@yelp.com>\nBuild-Depends:\n debhelper (>= 9),\n help2man,\n musl-tools,\n## Tests:\n procps,\n python3,\n python3-pytest,\nStandards-Version: 3.9.7\nHomepage: https://github.com/Yelp/dumb-init\nVcs-Browser: https://github.com/Yelp/dumb-init\nVcs-Git: https://github.com/Yelp/dumb-init.git\n\nPackage: dumb-init\nArchitecture: any\nDepends: ${misc:Depends}\nDescription: wrapper script which proxies signals to a child\n dumb-init is a simple process supervisor and init system designed to run\n as PID 1 inside minimal container environments (such as Docker).\n .\n Lightweight containers have popularized the idea of running a single\n process or service without normal init systems like systemd or sysvinit.\n However, omitting an init system often leads to incorrect handling of\n processes and signals, and can result in problems such as containers\n which can't be gracefully stopped, or leaking containers which should\n have been destroyed.\n .\n dumb-init acts as PID 1 and immediately spawns your command as a child\n process, taking care to properly handle and forward signals as they are\n received.\n"
  },
  {
    "path": "debian/copyright",
    "content": "Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/\nUpstream-Name: dumb-init\nSource: https://github.com/Yelp/dumb-init/\n\nFiles: *\nCopyright:\n    2015, 2016 Yelp, Inc.\nLicense: Expat\n\nFiles: debian/*\nCopyright:\n    2015, 2016 Yelp, Inc.\n    2016 Dmitry Smirnov <onlyjob@debian.org>\nLicense: Expat\n\nLicense: Expat\n Permission is hereby granted, free of charge, to any person obtaining a\n copy of this software and associated documentation files (the \"Software\"),\n to deal in the Software without restriction, including without limitation\n the rights to use, copy, modify, merge, publish, distribute, sublicense,\n and/or sell copies of the Software, and to permit persons to whom the\n Software is furnished to do so, subject to the following conditions:\n .\n The above copyright notice and this permission notice shall be included in\n all copies or substantial portions of the Software.\n .\n THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL\n THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n DEALINGS IN THE SOFTWARE.\n"
  },
  {
    "path": "debian/docs",
    "content": "README*\n"
  },
  {
    "path": "debian/help2man",
    "content": "[authors]\n.B dumb-init\nwas primarily developed at Yelp.\n.br\n.br\nFor a full list of contributors, see\nhttps://github.com/Yelp/dumb-init/graphs/contributors\n"
  },
  {
    "path": "debian/install",
    "content": "dumb-init /usr/bin/\n"
  },
  {
    "path": "debian/lintian-overrides",
    "content": "dumb-init binary: statically-linked-binary\n"
  },
  {
    "path": "debian/manpages",
    "content": "debian/dumb-init.1\n"
  },
  {
    "path": "debian/rules",
    "content": "#!/usr/bin/make -f\nexport DEB_BUILD_MAINT_OPTIONS = hardening=+all\n\n%:\n\tdh $@\n\nMAN=debian/dumb-init.1\n\noverride_dh_clean:\n\t$(RM) -rv .cache\n\tdh_clean $(MAN)\n\noverride_dh_auto_clean:\n\t@true\n\n$(MAN):\n\thelp2man --name 'a minimal init system for Linux containers' \\\n\t\t--no-discard-stderr \\\n\t\t--include debian/help2man \\\n\t\t--no-info \\\n\t\t./dumb-init > $@\n\noverride_dh_installman: $(MAN)\n\tdh_installman\n\noverride_dh_builddeb:\n\t# Use gzip instead of xz to support older Debian/Ubuntu releases which\n\t# might install our debs.\n\tdh_builddeb -- -Zgzip\n\noverride_dh_auto_test:\n\tfind . -name '*.pyc' -delete\n\tfind . -name '__pycache__' -delete\n\tPATH=.:$$PATH timeout --signal=KILL 60 pytest-3 -vv tests/\n"
  },
  {
    "path": "debian/source/format",
    "content": "3.0 (native)\n"
  },
  {
    "path": "dumb-init.c",
    "content": "/*\n * dumb-init is a simple wrapper program designed to run as PID 1 and pass\n * signals to its children.\n *\n * Usage:\n *   ./dumb-init python -c 'while True: pass'\n *\n * To get debug output on stderr, run with '-v'.\n */\n\n#include <assert.h>\n#include <errno.h>\n#include <getopt.h>\n#include <signal.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include <sys/ioctl.h>\n#include <sys/types.h>\n#include <sys/wait.h>\n#include <unistd.h>\n#include \"VERSION.h\"\n\n#define PRINTERR(...) do { \\\n    fprintf(stderr, \"[dumb-init] \" __VA_ARGS__); \\\n} while (0)\n\n#define DEBUG(...) do { \\\n    if (debug) { \\\n        PRINTERR(__VA_ARGS__); \\\n    } \\\n} while (0)\n\n// Signals we care about are numbered from 1 to 31, inclusive.\n// (32 and above are real-time signals.)\n// TODO: this is likely not portable outside of Linux, or on strange architectures\n#define MAXSIG 31\n\n// Indices are one-indexed (signal 1 is at index 1). Index zero is unused.\n// User-specified signal rewriting.\nint signal_rewrite[MAXSIG + 1] = {[0 ... MAXSIG] = -1};\n// One-time ignores due to TTY quirks. 0 = no skip, 1 = skip the next-received signal.\nchar signal_temporary_ignores[MAXSIG + 1] = {[0 ... MAXSIG] = 0};\n\npid_t child_pid = -1;\nchar debug = 0;\nchar use_setsid = 1;\n\nint translate_signal(int signum) {\n    if (signum <= 0 || signum > MAXSIG) {\n        return signum;\n    } else {\n        int translated = signal_rewrite[signum];\n        if (translated == -1) {\n            return signum;\n        } else {\n            DEBUG(\"Translating signal %d to %d.\\n\", signum, translated);\n            return translated;\n        }\n    }\n}\n\nvoid forward_signal(int signum) {\n    signum = translate_signal(signum);\n    if (signum != 0) {\n        kill(use_setsid ? -child_pid : child_pid, signum);\n        DEBUG(\"Forwarded signal %d to children.\\n\", signum);\n    } else {\n        DEBUG(\"Not forwarding signal %d to children (ignored).\\n\", signum);\n    }\n}\n\n/*\n * The dumb-init signal handler.\n *\n * The main job of this signal handler is to forward signals along to our child\n * process(es). In setsid mode, this means signaling the entire process group\n * rooted at our child. In non-setsid mode, this is just signaling the primary\n * child.\n *\n * In most cases, simply proxying the received signal is sufficient. If we\n * receive a job control signal, however, we should not only forward it, but\n * also sleep dumb-init itself.\n *\n * This allows users to run foreground processes using dumb-init and to\n * control them using normal shell job control features (e.g. Ctrl-Z to\n * generate a SIGTSTP and suspend the process).\n *\n * The libc manual is useful:\n * https://www.gnu.org/software/libc/manual/html_node/Job-Control-Signals.html\n *\n*/\nvoid handle_signal(int signum) {\n    DEBUG(\"Received signal %d.\\n\", signum);\n\n    if (signal_temporary_ignores[signum] == 1) {\n        DEBUG(\"Ignoring tty hand-off signal %d.\\n\", signum);\n        signal_temporary_ignores[signum] = 0;\n    } else if (signum == SIGCHLD) {\n        int status, exit_status;\n        pid_t killed_pid;\n        while ((killed_pid = waitpid(-1, &status, WNOHANG)) > 0) {\n            if (WIFEXITED(status)) {\n                exit_status = WEXITSTATUS(status);\n                DEBUG(\"A child with PID %d exited with exit status %d.\\n\", killed_pid, exit_status);\n            } else {\n                assert(WIFSIGNALED(status));\n                exit_status = 128 + WTERMSIG(status);\n                DEBUG(\"A child with PID %d was terminated by signal %d.\\n\", killed_pid, exit_status - 128);\n            }\n\n            if (killed_pid == child_pid) {\n                forward_signal(SIGTERM);  // send SIGTERM to any remaining children\n                DEBUG(\"Child exited with status %d. Goodbye.\\n\", exit_status);\n                exit(exit_status);\n            }\n        }\n    } else {\n        forward_signal(signum);\n        if (signum == SIGTSTP || signum == SIGTTOU || signum == SIGTTIN) {\n            DEBUG(\"Suspending self due to TTY signal.\\n\");\n            kill(getpid(), SIGSTOP);\n        }\n    }\n}\n\nvoid print_help(char *argv[]) {\n    fprintf(stderr,\n        \"dumb-init v%.*s\"\n        \"Usage: %s [option] command [[arg] ...]\\n\"\n        \"\\n\"\n        \"dumb-init is a simple process supervisor that forwards signals to children.\\n\"\n        \"It is designed to run as PID1 in minimal container environments.\\n\"\n        \"\\n\"\n        \"Optional arguments:\\n\"\n        \"   -c, --single-child   Run in single-child mode.\\n\"\n        \"                        In this mode, signals are only proxied to the\\n\"\n        \"                        direct child and not any of its descendants.\\n\"\n        \"   -r, --rewrite s:r    Rewrite received signal s to new signal r before proxying.\\n\"\n        \"                        To ignore (not proxy) a signal, rewrite it to 0.\\n\"\n        \"                        This option can be specified multiple times.\\n\"\n        \"   -v, --verbose        Print debugging information to stderr.\\n\"\n        \"   -h, --help           Print this help message and exit.\\n\"\n        \"   -V, --version        Print the current version and exit.\\n\"\n        \"\\n\"\n        \"Full help is available online at https://github.com/Yelp/dumb-init\\n\",\n        VERSION_len, VERSION,\n        argv[0]\n    );\n}\n\nvoid print_rewrite_signum_help() {\n    fprintf(\n        stderr,\n        \"Usage: -r option takes <signum>:<signum>, where <signum> \"\n        \"is between 1 and %d.\\n\"\n        \"This option can be specified multiple times.\\n\"\n        \"Use --help for full usage.\\n\",\n        MAXSIG\n    );\n    exit(1);\n}\n\nvoid parse_rewrite_signum(char *arg) {\n    int signum, replacement;\n    if (\n        sscanf(arg, \"%d:%d\", &signum, &replacement) == 2 &&\n        (signum >= 1 && signum <= MAXSIG) &&\n        (replacement >= 0 && replacement <= MAXSIG)\n    ) {\n        signal_rewrite[signum] = replacement;\n    } else {\n        print_rewrite_signum_help();\n    }\n}\n\nvoid set_rewrite_to_sigstop_if_not_defined(int signum) {\n    if (signal_rewrite[signum] == -1) {\n        signal_rewrite[signum] = SIGSTOP;\n    }\n}\n\nchar **parse_command(int argc, char *argv[]) {\n    int opt;\n    struct option long_options[] = {\n        {\"help\",         no_argument,       NULL, 'h'},\n        {\"single-child\", no_argument,       NULL, 'c'},\n        {\"rewrite\",      required_argument, NULL, 'r'},\n        {\"verbose\",      no_argument,       NULL, 'v'},\n        {\"version\",      no_argument,       NULL, 'V'},\n        {NULL,                     0,       NULL,   0},\n    };\n    while ((opt = getopt_long(argc, argv, \"+hvVcr:\", long_options, NULL)) != -1) {\n        switch (opt) {\n            case 'h':\n                print_help(argv);\n                exit(0);\n            case 'v':\n                debug = 1;\n                break;\n            case 'V':\n                fprintf(stderr, \"dumb-init v%.*s\", VERSION_len, VERSION);\n                exit(0);\n            case 'c':\n                use_setsid = 0;\n                break;\n            case 'r':\n                parse_rewrite_signum(optarg);\n                break;\n            default:\n                exit(1);\n        }\n    }\n\n    if (optind >= argc) {\n        fprintf(\n            stderr,\n            \"Usage: %s [option] program [args]\\n\"\n            \"Try %s --help for full usage.\\n\",\n            argv[0], argv[0]\n        );\n        exit(1);\n    }\n\n    char *debug_env = getenv(\"DUMB_INIT_DEBUG\");\n    if (debug_env && strcmp(debug_env, \"1\") == 0) {\n        debug = 1;\n        DEBUG(\"Running in debug mode.\\n\");\n    }\n\n    char *setsid_env = getenv(\"DUMB_INIT_SETSID\");\n    if (setsid_env && strcmp(setsid_env, \"0\") == 0) {\n        use_setsid = 0;\n        DEBUG(\"Not running in setsid mode.\\n\");\n    }\n\n    if (use_setsid) {\n        set_rewrite_to_sigstop_if_not_defined(SIGTSTP);\n        set_rewrite_to_sigstop_if_not_defined(SIGTTOU);\n        set_rewrite_to_sigstop_if_not_defined(SIGTTIN);\n    }\n\n    return &argv[optind];\n}\n\n// A dummy signal handler used for signals we care about.\n// On the FreeBSD kernel, ignored signals cannot be waited on by `sigwait` (but\n// they can be on Linux). We must provide a dummy handler.\n// https://lists.freebsd.org/pipermail/freebsd-ports/2009-October/057340.html\nvoid dummy(int signum) {}\n\nint main(int argc, char *argv[]) {\n    char **cmd = parse_command(argc, argv);\n    sigset_t all_signals;\n    sigfillset(&all_signals);\n    sigprocmask(SIG_BLOCK, &all_signals, NULL);\n\n    int i = 0;\n    for (i = 1; i <= MAXSIG; i++) {\n        signal(i, dummy);\n    }\n\n    /*\n     * Detach dumb-init from controlling tty, so that the child's session can\n     * attach to it instead.\n     *\n     * We want the child to be able to be the session leader of the TTY so that\n     * it can do normal job control.\n     */\n    if (use_setsid) {\n        if (ioctl(STDIN_FILENO, TIOCNOTTY) == -1) {\n            DEBUG(\n                \"Unable to detach from controlling tty (errno=%d %s).\\n\",\n                errno,\n                strerror(errno)\n            );\n        } else {\n            /*\n             * When the session leader detaches from its controlling tty via\n             * TIOCNOTTY, the kernel sends SIGHUP and SIGCONT to the process\n             * group. We need to be careful not to forward these on to the\n             * dumb-init child so that it doesn't receive a SIGHUP and\n             * terminate itself (#136).\n             */\n            if (getsid(0) == getpid()) {\n                DEBUG(\"Detached from controlling tty, ignoring the first SIGHUP and SIGCONT we receive.\\n\");\n                signal_temporary_ignores[SIGHUP] = 1;\n                signal_temporary_ignores[SIGCONT] = 1;\n            } else {\n                DEBUG(\"Detached from controlling tty, but was not session leader.\\n\");\n            }\n        }\n    }\n\n    child_pid = fork();\n    if (child_pid < 0) {\n        PRINTERR(\"Unable to fork. Exiting.\\n\");\n        return 1;\n    } else if (child_pid == 0) {\n        /* child */\n        sigprocmask(SIG_UNBLOCK, &all_signals, NULL);\n        if (use_setsid) {\n            if (setsid() == -1) {\n                PRINTERR(\n                    \"Unable to setsid (errno=%d %s). Exiting.\\n\",\n                    errno,\n                    strerror(errno)\n                );\n                exit(1);\n            }\n\n            if (ioctl(STDIN_FILENO, TIOCSCTTY, 0) == -1) {\n                DEBUG(\n                    \"Unable to attach to controlling tty (errno=%d %s).\\n\",\n                    errno,\n                    strerror(errno)\n                );\n            }\n            DEBUG(\"setsid complete.\\n\");\n        }\n        execvp(cmd[0], &cmd[0]);\n\n        // if this point is reached, exec failed, so we should exit nonzero\n        PRINTERR(\"%s: %s\\n\", cmd[0], strerror(errno));\n        return 2;\n    } else {\n        /* parent */\n        DEBUG(\"Child spawned with PID %d.\\n\", child_pid);\n        if (chdir(\"/\") == -1) {\n             DEBUG(\"Unable to chdir(\\\"/\\\") (errno=%d %s)\\n\",\n                   errno,\n                   strerror(errno));\n        }\n        for (;;) {\n            int signum;\n            sigwait(&all_signals, &signum);\n            handle_signal(signum);\n        }\n    }\n}\n"
  },
  {
    "path": "pytest.ini",
    "content": "[pytest]\ntimeout = 20\n"
  },
  {
    "path": "requirements-dev.txt",
    "content": "pre-commit>=0.5.0\npytest\n# TODO: This pin is to work around an issue where the system pytest is too old.\n# We should fix this by not depending on the system pytest/python packages at\n# some point.\npytest-timeout<2.0.0\n"
  },
  {
    "path": "setup.py",
    "content": "import os.path\nimport subprocess\nimport tempfile\n\nfrom distutils.command.build import build as orig_build\nfrom distutils.core import Command\nfrom setuptools import Distribution\nfrom setuptools import Extension\nfrom setuptools import setup\nfrom setuptools.command.install import install as orig_install\n\n\ntry:\n    from wheel.bdist_wheel import bdist_wheel as _bdist_wheel\n\n    class bdist_wheel(_bdist_wheel):\n\n        def finalize_options(self):\n            _bdist_wheel.finalize_options(self)\n            # Mark us as not a pure python package\n            self.root_is_pure = False\n\n        def get_tag(self):\n            python, abi, plat = _bdist_wheel.get_tag(self)\n            # We don't contain any python source\n            python, abi = 'py2.py3', 'none'\n            return python, abi, plat\nexcept ImportError:\n    bdist_wheel = None\n\n\nclass ExeDistribution(Distribution):\n    c_executables = ()\n\n\nclass build(orig_build):\n    sub_commands = orig_build.sub_commands + [\n        ('build_cexe', None),\n    ]\n\n\nclass install(orig_install):\n    sub_commands = orig_install.sub_commands + [\n        ('install_cexe', None),\n    ]\n\n\nclass install_cexe(Command):\n    description = 'install C executables'\n    outfiles = ()\n\n    def initialize_options(self):\n        self.build_dir = self.install_dir = None\n\n    def finalize_options(self):\n        # this initializes attributes based on other commands' attributes\n        self.set_undefined_options('build', ('build_scripts', 'build_dir'))\n        self.set_undefined_options(\n            'install', ('install_scripts', 'install_dir'),\n        )\n\n    def run(self):\n\n        self.outfiles = self.copy_tree(self.build_dir, self.install_dir)\n\n    def get_outputs(self):\n        return self.outfiles\n\n\nclass build_cexe(Command):\n    description = 'build C executables'\n\n    def initialize_options(self):\n        self.build_scripts = None\n        self.build_temp = None\n\n    def finalize_options(self):\n        self.set_undefined_options(\n            'build',\n            ('build_scripts', 'build_scripts'),\n            ('build_temp', 'build_temp'),\n        )\n\n    def run(self):\n        # stolen and simplified from distutils.command.build_ext\n        from distutils.ccompiler import new_compiler\n\n        compiler = new_compiler(verbose=True)\n\n        print('supports -static... ', end='')\n        with tempfile.NamedTemporaryFile(mode='w', suffix='.c') as f:\n            f.write('int main(void){}\\n')\n            f.flush()\n            cmd = compiler.linker_exe + [f.name, '-static', '-o', os.devnull]\n            with open(os.devnull, 'wb') as devnull:\n                if not subprocess.call(cmd, stderr=devnull):\n                    print('yes')\n                    link_args = ['-static']\n                else:\n                    print('no')\n                    link_args = []\n\n        for exe in self.distribution.c_executables:\n            objects = compiler.compile(exe.sources, output_dir=self.build_temp)\n            compiler.link_executable(\n                objects,\n                exe.name,\n                output_dir=self.build_scripts,\n                extra_postargs=link_args,\n            )\n\n    def get_outputs(self):\n        return [\n            os.path.join(self.build_scripts, exe.name)\n            for exe in self.distribution.c_executables\n        ]\n\n\nsetup(\n    name='dumb-init',\n    description='Simple wrapper script which proxies signals to a child',\n    version=open('VERSION').read().strip(),\n    author='Yelp',\n    url='https://github.com/Yelp/dumb-init/',\n    platforms='linux',\n    packages=[],\n    c_executables=[Extension('dumb-init', ['dumb-init.c'])],\n    cmdclass={\n        'bdist_wheel': bdist_wheel,\n        'build': build,\n        'build_cexe': build_cexe,\n        'install': install,\n        'install_cexe': install_cexe,\n    },\n    distclass=ExeDistribution,\n)\n"
  },
  {
    "path": "testing/__init__.py",
    "content": "import errno\nimport os\nimport re\nimport signal\nimport sys\nimport time\nfrom contextlib import contextmanager\nfrom subprocess import PIPE\nfrom subprocess import Popen\n\n# these signals cause dumb-init to suspend itself\nSUSPEND_SIGNALS = frozenset([\n    signal.SIGTSTP,\n    signal.SIGTTOU,\n    signal.SIGTTIN,\n])\n\nNORMAL_SIGNALS = frozenset(\n    set(range(1, 32)) -\n    {signal.SIGKILL, signal.SIGSTOP, signal.SIGCHLD} -\n    SUSPEND_SIGNALS,\n)\n\n\n@contextmanager\ndef print_signals(args=()):\n    \"\"\"Start print_signals and yield dumb-init process and print_signals PID.\"\"\"\n    proc = Popen(\n        (\n            ('dumb-init',) +\n            tuple(args) +\n            (sys.executable, '-m', 'testing.print_signals')\n        ),\n        stdout=PIPE,\n    )\n    line = proc.stdout.readline()\n    m = re.match(b'^ready \\\\(pid: ([0-9]+)\\\\)\\n$', line)\n    assert m, line\n\n    yield proc, m.group(1).decode('ascii')\n\n    for pid in pid_tree(proc.pid):\n        os.kill(pid, signal.SIGKILL)\n\n\ndef child_pids(pid):\n    \"\"\"Return a list of direct child PIDs for the given PID.\"\"\"\n    children = set()\n    for p in os.listdir('/proc'):\n        try:\n            with open(os.path.join('/proc', p, 'stat')) as f:\n                stat = f.read()\n            m = re.match(\n                r'^\\d+ \\(.+?\\) '\n                # This field, state, is normally a single letter, but can be\n                # \"0\" if there are some unusual security settings that prevent\n                # reading the process state (happens under GitHub Actions with\n                # QEMU for some reason).\n                '[0a-zA-Z] '\n                r'(\\d+) ',\n                stat,\n            )\n            assert m, stat\n            ppid = int(m.group(1))\n            if ppid == pid:\n                children.add(int(p))\n        except OSError:\n            # Happens when the process exits after listing it, or between\n            # opening stat and reading it.\n            pass\n    return children\n\n\ndef pid_tree(pid):\n    \"\"\"Return a list of all descendant PIDs for the given PID.\"\"\"\n    children = child_pids(pid)\n    return {\n        pid\n        for child in children\n        for pid in pid_tree(child)\n    } | children\n\n\ndef is_alive(pid):\n    \"\"\"Return whether a process is running with the given PID.\"\"\"\n    return os.path.isdir(os.path.join('/proc', str(pid)))\n\n\ndef process_state(pid):\n    \"\"\"Return a process' state, such as \"stopped\" or \"running\".\"\"\"\n    with open(os.path.join('/proc', str(pid), 'status')) as f:\n        status = f.read()\n    m = re.search(r'^State:\\s+[A-Z] \\(([a-z]+)\\)$', status, re.MULTILINE)\n    return m.group(1)\n\n\ndef sleep_until(fn, timeout=1.5):\n    \"\"\"Sleep until fn succeeds, or we time out.\"\"\"\n    interval = 0.01\n    so_far = 0\n    while True:\n        try:\n            fn()\n        except Exception:\n            if so_far >= timeout:\n                raise\n        else:\n            break\n        time.sleep(interval)\n        so_far += interval\n\n\ndef kill_if_alive(pid, signum=signal.SIGKILL):\n    \"\"\"Kill a process, ignoring \"no such process\" errors.\"\"\"\n    try:\n        os.kill(pid, signum)\n    except OSError as ex:\n        if ex.errno != errno.ESRCH:  # No such process\n            raise\n"
  },
  {
    "path": "testing/print_signals.py",
    "content": "#!/usr/bin/env python\n\"\"\"Print received signals to stdout.\n\nSince all signals are printed and otherwise ignored, you'll need to send\nSIGKILL (kill -9) to this process to actually end it.\n\"\"\"\nimport os\nimport signal\nimport sys\nimport time\n\n\nCATCHABLE_SIGNALS = frozenset(\n    set(range(1, 32)) - {signal.SIGKILL, signal.SIGSTOP, signal.SIGCHLD},\n)\n\n\nprint_queue = []\nlast_signal = None\n\n\ndef unbuffered_print(line):\n    sys.stdout.write('{}\\n'.format(line))\n    sys.stdout.flush()\n\n\ndef print_signal(signum, _):\n    print_queue.append(signum)\n\n\nif __name__ == '__main__':\n    for signum in CATCHABLE_SIGNALS:\n        signal.signal(signum, print_signal)\n\n    unbuffered_print('ready (pid: {})'.format(os.getpid()))\n\n    # loop forever just printing signals\n    while True:\n        if print_queue:\n            signum = print_queue.pop()\n            unbuffered_print(signum)\n\n            if signum == signal.SIGINT and last_signal == signal.SIGINT:\n                print('Received SIGINT twice, exiting.')\n                exit(0)\n            last_signal = signum\n\n        time.sleep(0.01)\n"
  },
  {
    "path": "tests/__init__.py",
    "content": ""
  },
  {
    "path": "tests/child_processes_test.py",
    "content": "import os\nimport re\nimport signal\nimport sys\nfrom subprocess import PIPE\nfrom subprocess import Popen\n\nimport pytest\n\nfrom testing import is_alive\nfrom testing import kill_if_alive\nfrom testing import pid_tree\nfrom testing import sleep_until\n\n\ndef spawn_and_kill_pipeline():\n    proc = Popen((\n        'dumb-init',\n        'sh', '-c',\n        \"yes 'oh, hi' | tail & yes error | tail >&2\",\n    ))\n\n    def assert_living_pids():\n        assert len(living_pids(pid_tree(os.getpid()))) == 6\n\n    sleep_until(assert_living_pids)\n\n    pids = pid_tree(os.getpid())\n    proc.send_signal(signal.SIGTERM)\n    proc.wait()\n    return pids\n\n\ndef living_pids(pids):\n    return {pid for pid in pids if is_alive(pid)}\n\n\n@pytest.mark.usefixtures('both_debug_modes', 'setsid_enabled')\ndef test_setsid_signals_entire_group():\n    \"\"\"When dumb-init is running in setsid mode, it should signal the entire\n    process group rooted at it.\n    \"\"\"\n    pids = spawn_and_kill_pipeline()\n\n    def assert_no_living_pids():\n        assert len(living_pids(pids)) == 0\n\n    sleep_until(assert_no_living_pids)\n\n\n@pytest.mark.usefixtures('both_debug_modes', 'setsid_disabled')\ndef test_no_setsid_doesnt_signal_entire_group():\n    \"\"\"When dumb-init is not running in setsid mode, it should only signal its\n    immediate child.\n    \"\"\"\n    pids = spawn_and_kill_pipeline()\n\n    def assert_four_living_pids():\n        assert len(living_pids(pids)) == 4\n\n    sleep_until(assert_four_living_pids)\n\n    for pid in living_pids(pids):\n        kill_if_alive(pid)\n\n\ndef spawn_process_which_dies_with_children():\n    \"\"\"Spawn a process which spawns some children and then dies without\n    signaling them, wrapped in dumb-init.\n\n    Returns a tuple (child pid, child stdout pipe), where the child is\n    print_signals. This is useful because you can signal the PID and see if\n    anything gets printed onto the stdout pipe.\n    \"\"\"\n    proc = Popen(\n        (\n            'dumb-init',\n            'sh', '-c',\n\n            # we need to sleep before the shell exits, or dumb-init might send\n            # TERM to print_signals before it has had time to register custom\n            # signal handlers\n            '{python} -m testing.print_signals & sleep 1'.format(\n                python=sys.executable,\n            ),\n        ),\n        stdout=PIPE,\n    )\n    proc.wait()\n    assert proc.returncode == 0\n\n    # read a line from print_signals, figure out its pid\n    line = proc.stdout.readline()\n    match = re.match(b'ready \\\\(pid: ([0-9]+)\\\\)\\n', line)\n    assert match, line\n    child_pid = int(match.group(1))\n\n    # at this point, the shell and dumb-init have both exited, but\n    # print_signals may or may not still be running (depending on whether\n    # setsid mode is enabled)\n\n    return child_pid, proc.stdout\n\n\n@pytest.mark.usefixtures('both_debug_modes', 'setsid_enabled')\ndef test_all_processes_receive_term_on_exit_if_setsid():\n    \"\"\"If the child exits for some reason, dumb-init should send TERM to all\n    processes in its session if setsid mode is enabled.\"\"\"\n    child_pid, child_stdout = spawn_process_which_dies_with_children()\n\n    # print_signals should have received TERM\n    assert child_stdout.readline() == b'15\\n'\n\n    os.kill(child_pid, signal.SIGKILL)\n\n\n@pytest.mark.usefixtures('both_debug_modes', 'setsid_disabled')\ndef test_processes_dont_receive_term_on_exit_if_no_setsid():\n    \"\"\"If the child exits for some reason, dumb-init should not send TERM to\n    any other processes if setsid mode is disabled.\"\"\"\n    child_pid, child_stdout = spawn_process_which_dies_with_children()\n\n    # print_signals should not have received TERM; to test this, we send it\n    # some other signals and ensure they were received (and TERM wasn't)\n    for signum in [1, 2, 3]:\n        os.kill(child_pid, signum)\n        assert child_stdout.readline() == str(signum).encode('ascii') + b'\\n'\n\n    os.kill(child_pid, signal.SIGKILL)\n\n\n@pytest.mark.parametrize(\n    'args', [\n        ('/doesnotexist',),\n        ('--', '/doesnotexist'),\n        ('-c', '/doesnotexist'),\n        ('--single-child', '--', '/doesnotexist'),\n    ],\n)\n@pytest.mark.usefixtures('both_debug_modes', 'both_setsid_modes')\ndef test_fails_nonzero_with_bad_exec(args):\n    \"\"\"If dumb-init can't exec as requested, it should exit nonzero.\"\"\"\n    proc = Popen(('dumb-init',) + args, stderr=PIPE)\n    _, stderr = proc.communicate()\n    assert proc.returncode != 0\n    assert (\n        b'[dumb-init] /doesnotexist: No such file or directory\\n'\n        in stderr\n    )\n"
  },
  {
    "path": "tests/cli_test.py",
    "content": "\"\"\"Sanity checks for command-line options.\"\"\"\nimport re\nimport signal\nfrom subprocess import PIPE\nfrom subprocess import Popen\n\nimport pytest\n\n\n@pytest.fixture\ndef current_version():\n    return open('VERSION').read().strip()\n\n\ndef normalize_stderr(stderr):\n    # dumb-init prints out argv[0] in its usage message. This should always be\n    # just \"dumb-init\" under regular test scenarios here since that is how we\n    # call it, but in CI the use of QEMU causes the argv[0] to be replaced with\n    # the full path.\n    #\n    # This is supposed to be avoidable by:\n    #   1) Setting the \"P\" flag in the binfmt register:\n    #      https://en.wikipedia.org/wiki/Binfmt_misc#Registration\n    #      This can be done by setting the QEMU_PRESERVE_PARENT env var when\n    #      calling binfmt.\n    #\n    #   2) Setting the \"QEMU_ARGV0\" env var to empty string for *all*\n    #      processes:\n    #      https://bugs.launchpad.net/qemu/+bug/1835839\n    #\n    # I can get it working properly in CI outside of Docker, but for some\n    # reason not during Docker builds. This doesn't affect the built executable\n    # so I decided to just punt on it.\n    return re.sub(rb'(^|(?<=\\s))[a-z/.]+/dumb-init', b'dumb-init', stderr)\n\n\n@pytest.mark.usefixtures('both_debug_modes', 'both_setsid_modes')\ndef test_no_arguments_prints_usage():\n    proc = Popen(('dumb-init'), stderr=PIPE)\n    _, stderr = proc.communicate()\n    assert proc.returncode != 0\n    assert normalize_stderr(stderr) == (\n        b'Usage: dumb-init [option] program [args]\\n'\n        b'Try dumb-init --help for full usage.\\n'\n    )\n\n\n@pytest.mark.usefixtures('both_debug_modes', 'both_setsid_modes')\ndef test_exits_invalid_with_invalid_args():\n    proc = Popen(('dumb-init', '--yolo', '/bin/true'), stderr=PIPE)\n    _, stderr = proc.communicate()\n    assert proc.returncode == 1\n    assert normalize_stderr(stderr) in (\n        b\"dumb-init: unrecognized option '--yolo'\\n\",  # glibc\n        b'dumb-init: unrecognized option: yolo\\n',  # musl\n    )\n\n\n@pytest.mark.parametrize('flag', ['-h', '--help'])\n@pytest.mark.usefixtures('both_debug_modes', 'both_setsid_modes')\ndef test_help_message(flag, current_version):\n    \"\"\"dumb-init should say something useful when called with the help flag,\n    and exit zero.\n    \"\"\"\n    proc = Popen(('dumb-init', flag), stderr=PIPE)\n    _, stderr = proc.communicate()\n    assert proc.returncode == 0\n    assert normalize_stderr(stderr) == (\n        b'dumb-init v' + current_version.encode('ascii') + b'\\n'\n        b'Usage: dumb-init [option] command [[arg] ...]\\n'\n        b'\\n'\n        b'dumb-init is a simple process supervisor that forwards signals to children.\\n'\n        b'It is designed to run as PID1 in minimal container environments.\\n'\n        b'\\n'\n        b'Optional arguments:\\n'\n        b'   -c, --single-child   Run in single-child mode.\\n'\n        b'                        In this mode, signals are only proxied to the\\n'\n        b'                        direct child and not any of its descendants.\\n'\n        b'   -r, --rewrite s:r    Rewrite received signal s to new signal r before proxying.\\n'\n        b'                        To ignore (not proxy) a signal, rewrite it to 0.\\n'\n        b'                        This option can be specified multiple times.\\n'\n        b'   -v, --verbose        Print debugging information to stderr.\\n'\n        b'   -h, --help           Print this help message and exit.\\n'\n        b'   -V, --version        Print the current version and exit.\\n'\n        b'\\n'\n        b'Full help is available online at https://github.com/Yelp/dumb-init\\n'\n    )\n\n\n@pytest.mark.parametrize('flag', ['-V', '--version'])\n@pytest.mark.usefixtures('both_debug_modes', 'both_setsid_modes')\ndef test_version_message(flag, current_version):\n    \"\"\"dumb-init should print its version when asked to.\"\"\"\n\n    proc = Popen(('dumb-init', flag), stderr=PIPE)\n    _, stderr = proc.communicate()\n    assert proc.returncode == 0\n    assert stderr == b'dumb-init v' + current_version.encode('ascii') + b'\\n'\n\n\n@pytest.mark.parametrize('flag', ['-v', '--verbose'])\ndef test_verbose(flag):\n    \"\"\"dumb-init should print debug output when asked to.\"\"\"\n    proc = Popen(('dumb-init', flag, 'echo', 'oh,', 'hi'), stdout=PIPE, stderr=PIPE)\n    stdout, stderr = proc.communicate()\n    assert proc.returncode == 0\n    assert stdout == b'oh, hi\\n'\n\n    # child/parent race to print output after the fork(), can't guarantee exact order\n    assert re.search(b'(^|\\n)\\\\[dumb-init\\\\] setsid complete\\\\.\\n', stderr), stderr  # child\n    assert re.search(  # parent\n        (\n            '(^|\\n)\\\\[dumb-init\\\\] Child spawned with PID [0-9]+\\\\.\\n'\n            '.*'  # child might print here\n            '\\\\[dumb-init\\\\] Received signal {signal.SIGCHLD}\\\\.\\n'\n            '\\\\[dumb-init\\\\] A child with PID [0-9]+ exited with exit status 0.\\n'\n            '\\\\[dumb-init\\\\] Forwarded signal 15 to children\\\\.\\n'\n            '\\\\[dumb-init\\\\] Child exited with status 0\\\\. Goodbye\\\\.\\n$'\n        ).format(signal=signal).encode('utf8'),\n        stderr,\n        re.DOTALL,\n    ), stderr\n\n\n@pytest.mark.parametrize('flag1', ['-v', '--verbose'])\n@pytest.mark.parametrize('flag2', ['-c', '--single-child'])\ndef test_verbose_and_single_child(flag1, flag2):\n    \"\"\"dumb-init should print debug output when asked to.\"\"\"\n    proc = Popen(('dumb-init', flag1, flag2, 'echo', 'oh,', 'hi'), stdout=PIPE, stderr=PIPE)\n    stdout, stderr = proc.communicate()\n    assert proc.returncode == 0\n    assert stdout == b'oh, hi\\n'\n    assert re.match(\n        (\n            '^\\\\[dumb-init\\\\] Child spawned with PID [0-9]+\\\\.\\n'\n            '\\\\[dumb-init\\\\] Received signal {signal.SIGCHLD}\\\\.\\n'\n            '\\\\[dumb-init\\\\] A child with PID [0-9]+ exited with exit status 0.\\n'\n            '\\\\[dumb-init\\\\] Forwarded signal 15 to children\\\\.\\n'\n            '\\\\[dumb-init\\\\] Child exited with status 0\\\\. Goodbye\\\\.\\n$'\n        ).format(signal=signal).encode('utf8'),\n        stderr,\n    )\n\n\n@pytest.mark.parametrize(\n    'extra_args', [\n        ('-r',),\n        ('-r', ''),\n        ('-r', 'herp'),\n        ('-r', 'herp:derp'),\n        ('-r', '15'),\n        ('-r', '15::12'),\n        ('-r', '15:derp'),\n        ('-r', '15:12', '-r'),\n        ('-r', '15:12', '-r', '0'),\n        ('-r', '15:12', '-r', '1:32'),\n    ],\n)\n@pytest.mark.usefixtures('both_debug_modes', 'both_setsid_modes')\ndef test_rewrite_errors(extra_args):\n    proc = Popen(\n        ('dumb-init',) + extra_args + ('echo', 'oh,', 'hi'),\n        stdout=PIPE, stderr=PIPE,\n    )\n    stdout, stderr = proc.communicate()\n    assert proc.returncode == 1\n    assert stderr == (\n        b'Usage: -r option takes <signum>:<signum>, where <signum> '\n        b'is between 1 and 31.\\n'\n        b'This option can be specified multiple times.\\n'\n        b'Use --help for full usage.\\n'\n    )\n"
  },
  {
    "path": "tests/conftest.py",
    "content": "import os\nfrom unittest import mock\n\nimport pytest\n\n\n@pytest.fixture(autouse=True, scope='function')\ndef clean_environment():\n    \"\"\"Ensure all tests start with a clean environment.\n\n    Even if tests properly clean up after themselves, we still need this in\n    case the user runs tests with an already-polluted environment.\n    \"\"\"\n    with mock.patch.dict(\n        os.environ,\n        {'DUMB_INIT_DEBUG': '', 'DUMB_INIT_SETSID': ''},\n    ):\n        yield\n\n\n@pytest.fixture(params=['1', '0'])\ndef both_debug_modes(request):\n    with mock.patch.dict(os.environ, {'DUMB_INIT_DEBUG': request.param}):\n        yield\n\n\n@pytest.fixture\ndef debug_disabled():\n    with mock.patch.dict(os.environ, {'DUMB_INIT_DEBUG': '0'}):\n        yield\n\n\n@pytest.fixture(params=['1', '0'])\ndef both_setsid_modes(request):\n    with mock.patch.dict(os.environ, {'DUMB_INIT_SETSID': request.param}):\n        yield\n\n\n@pytest.fixture\ndef setsid_enabled():\n    with mock.patch.dict(os.environ, {'DUMB_INIT_SETSID': '1'}):\n        yield\n\n\n@pytest.fixture\ndef setsid_disabled():\n    with mock.patch.dict(os.environ, {'DUMB_INIT_SETSID': '0'}):\n        yield\n"
  },
  {
    "path": "tests/cwd_test.py",
    "content": "import os\nimport shutil\nfrom subprocess import PIPE\nfrom subprocess import run\n\nimport pytest\n\n\n@pytest.mark.usefixtures('both_debug_modes', 'both_setsid_modes')\ndef test_working_directories():\n    \"\"\"The child process must start in the working directory in which\n    dumb-init was invoked, but dumb-init itself should not keep a\n    reference to that.\"\"\"\n\n    # We need absolute path to dumb-init since we pass cwd=/tmp to get\n    # predictable output - so we can't rely on dumb-init being found\n    # in the \".\" directory.\n    dumb_init = os.path.realpath(shutil.which('dumb-init'))\n    proc = run(\n        (\n            dumb_init,\n            'sh', '-c', 'readlink /proc/$PPID/cwd && readlink /proc/$$/cwd',\n        ),\n        cwd='/tmp', stdout=PIPE, stderr=PIPE,\n    )\n\n    assert proc.returncode == 0\n    assert proc.stdout == b'/\\n/tmp\\n'\n"
  },
  {
    "path": "tests/exit_status_test.py",
    "content": "import signal\nimport sys\nfrom subprocess import Popen\n\nimport pytest\n\n\n@pytest.mark.parametrize('exit_status', [0, 1, 2, 32, 64, 127, 254, 255])\n@pytest.mark.usefixtures('both_debug_modes', 'both_setsid_modes')\ndef test_exit_status_regular_exit(exit_status):\n    \"\"\"dumb-init should exit with the same exit status as the process that it\n    supervises when that process exits normally.\n    \"\"\"\n    proc = Popen(('dumb-init', 'sh', '-c', 'exit {}'.format(exit_status)))\n    proc.wait()\n    assert proc.returncode == exit_status\n\n\n@pytest.mark.parametrize(\n    'signal', [\n        signal.SIGTERM,\n        signal.SIGHUP,\n        signal.SIGQUIT,\n        signal.SIGKILL,\n    ],\n)\n@pytest.mark.usefixtures('both_debug_modes', 'both_setsid_modes')\ndef test_exit_status_terminated_by_signal(signal):\n    \"\"\"dumb-init should exit with status 128 + signal when the child process is\n    terminated by a signal.\n    \"\"\"\n    # We use Python because sh is \"dash\" on Debian and \"bash\" on others.\n    # https://github.com/Yelp/dumb-init/issues/115\n    proc = Popen((\n        'dumb-init', sys.executable, '-c', 'import os; os.kill(os.getpid(), {})'.format(\n            signal,\n        ),\n    ))\n    proc.wait()\n    assert proc.returncode == 128 + signal\n"
  },
  {
    "path": "tests/proxies_signals_test.py",
    "content": "import os\nimport signal\nfrom itertools import chain\n\nimport pytest\n\nfrom testing import NORMAL_SIGNALS\nfrom testing import print_signals\nfrom testing import process_state\n\n\n@pytest.mark.usefixtures('both_debug_modes', 'both_setsid_modes')\ndef test_proxies_signals():\n    \"\"\"Ensure dumb-init proxies regular signals to its child.\"\"\"\n    with print_signals() as (proc, _):\n        for signum in NORMAL_SIGNALS:\n            proc.send_signal(signum)\n            assert proc.stdout.readline() == '{}\\n'.format(signum).encode('ascii')\n\n\ndef _rewrite_map_to_args(rewrite_map):\n    return chain.from_iterable(\n        ('-r', '{}:{}'.format(src, dst)) for src, dst in rewrite_map.items()\n    )\n\n\n@pytest.mark.parametrize(\n    'rewrite_map,sequence,expected', [\n        (\n            {},\n            [signal.SIGTERM, signal.SIGQUIT, signal.SIGCONT, signal.SIGINT],\n            [signal.SIGTERM, signal.SIGQUIT, signal.SIGCONT, signal.SIGINT],\n        ),\n\n        (\n            {signal.SIGTERM: signal.SIGINT},\n            [signal.SIGTERM, signal.SIGQUIT, signal.SIGCONT, signal.SIGINT],\n            [signal.SIGINT, signal.SIGQUIT, signal.SIGCONT, signal.SIGINT],\n        ),\n\n        (\n            {\n                signal.SIGTERM: signal.SIGINT,\n                signal.SIGINT: signal.SIGTERM,\n                signal.SIGQUIT: signal.SIGQUIT,\n            },\n            [signal.SIGTERM, signal.SIGQUIT, signal.SIGCONT, signal.SIGINT],\n            [signal.SIGINT, signal.SIGQUIT, signal.SIGCONT, signal.SIGTERM],\n        ),\n\n        # Lowest possible and highest possible signals.\n        (\n            {1: 31, 31: 1},\n            [1, 31],\n            [31, 1],\n        ),\n    ],\n)\n@pytest.mark.usefixtures('both_debug_modes', 'both_setsid_modes')\ndef test_proxies_signals_with_rewrite(rewrite_map, sequence, expected):\n    \"\"\"Ensure dumb-init can rewrite signals.\"\"\"\n    with print_signals(_rewrite_map_to_args(rewrite_map)) as (proc, _):\n        for send, expect_receive in zip(sequence, expected):\n            proc.send_signal(send)\n            assert proc.stdout.readline() == '{}\\n'.format(expect_receive).encode('ascii')\n\n\n@pytest.mark.usefixtures('both_debug_modes', 'setsid_enabled')\ndef test_default_rewrites_can_be_overriden_with_setsid_enabled():\n    \"\"\"In setsid mode, dumb-init should allow overwriting the default\n    rewrites (but still suspend itself).\n    \"\"\"\n    rewrite_map = {\n        signal.SIGTTIN: signal.SIGTERM,\n        signal.SIGTTOU: signal.SIGINT,\n        signal.SIGTSTP: signal.SIGHUP,\n    }\n    with print_signals(_rewrite_map_to_args(rewrite_map)) as (proc, _):\n        for send, expect_receive in rewrite_map.items():\n            assert process_state(proc.pid) in ['running', 'sleeping']\n            proc.send_signal(send)\n\n            assert proc.stdout.readline() == '{}\\n'.format(expect_receive).encode('ascii')\n            os.waitpid(proc.pid, os.WUNTRACED)\n            assert process_state(proc.pid) == 'stopped'\n\n            proc.send_signal(signal.SIGCONT)\n            assert proc.stdout.readline() == '{}\\n'.format(signal.SIGCONT).encode('ascii')\n            assert process_state(proc.pid) in ['running', 'sleeping']\n\n\n@pytest.mark.usefixtures('both_debug_modes', 'both_setsid_modes')\ndef test_ignored_signals_are_not_proxied():\n    \"\"\"Ensure dumb-init can ignore signals.\"\"\"\n    rewrite_map = {\n        signal.SIGTERM: signal.SIGQUIT,\n        signal.SIGINT: 0,\n        signal.SIGWINCH: 0,\n    }\n    with print_signals(_rewrite_map_to_args(rewrite_map)) as (proc, _):\n        proc.send_signal(signal.SIGTERM)\n        proc.send_signal(signal.SIGINT)\n        assert proc.stdout.readline() == '{}\\n'.format(signal.SIGQUIT).encode('ascii')\n\n        proc.send_signal(signal.SIGWINCH)\n        proc.send_signal(signal.SIGHUP)\n        assert proc.stdout.readline() == '{}\\n'.format(signal.SIGHUP).encode('ascii')\n"
  },
  {
    "path": "tests/shell_background_test.py",
    "content": "import os\nfrom signal import SIGCONT\n\nimport pytest\n\nfrom testing import print_signals\nfrom testing import process_state\nfrom testing import sleep_until\nfrom testing import SUSPEND_SIGNALS\n\n\n@pytest.mark.usefixtures('both_debug_modes', 'setsid_enabled')\ndef test_shell_background_support_setsid():\n    \"\"\"In setsid mode, dumb-init should suspend itself and its children when it\n    receives SIGTSTP, SIGTTOU, or SIGTTIN.\n    \"\"\"\n    with print_signals() as (proc, pid):\n        for signum in SUSPEND_SIGNALS:\n            # both dumb-init and print_signals should be running or sleeping\n            assert process_state(pid) in ['running', 'sleeping']\n            assert process_state(proc.pid) in ['running', 'sleeping']\n\n            # both should now suspend\n            proc.send_signal(signum)\n\n            def assert_both_stopped():\n                assert process_state(proc.pid) == process_state(pid) == 'stopped'\n\n            sleep_until(assert_both_stopped)\n\n            # and then both wake up again\n            proc.send_signal(SIGCONT)\n            assert (\n                proc.stdout.readline() == '{}\\n'.format(SIGCONT).encode('ascii')\n            )\n            assert process_state(pid) in ['running', 'sleeping']\n            assert process_state(proc.pid) in ['running', 'sleeping']\n\n\n@pytest.mark.usefixtures('both_debug_modes', 'setsid_disabled')\ndef test_shell_background_support_without_setsid():\n    \"\"\"In non-setsid mode, dumb-init should forward the signals SIGTSTP,\n    SIGTTOU, and SIGTTIN, and then suspend itself.\n    \"\"\"\n    with print_signals() as (proc, _):\n        for signum in SUSPEND_SIGNALS:\n            assert process_state(proc.pid) in ['running', 'sleeping']\n            proc.send_signal(signum)\n            assert proc.stdout.readline() == '{}\\n'.format(signum).encode('ascii')\n            os.waitpid(proc.pid, os.WUNTRACED)\n            assert process_state(proc.pid) == 'stopped'\n\n            proc.send_signal(SIGCONT)\n            assert (\n                proc.stdout.readline() == '{}\\n'.format(SIGCONT).encode('ascii')\n            )\n            assert process_state(proc.pid) in ['running', 'sleeping']\n"
  },
  {
    "path": "tests/test-zombies",
    "content": "#!/bin/bash -eux\n# Spawn a zombie process, and ensure it gets reaped.\n# This test is only useful when run on an empty container with\n# dumb-init as PID1.\n#\n# We run it as the last step of the integration tests inside our Docker\n# containers. Since dumb-init must run as PID 1, we don't use pytest and\n# instead write it in bash (which gets executed by PID1 dumb-init).\nset -o pipefail\n\nbash -euxc \"bash -euxc 'echo i am a zombie' &\" &\n\nsleep 1\nnum_zombies=$(ps -A -o state | (grep 'Z' || true) | wc -l)\n\nif [ \"$num_zombies\" -ne 0 ]; then\n    echo \"Expected no zombies, but instead there were ${num_zombies}.\"\n    exit 1\nfi\n"
  },
  {
    "path": "tests/tty_test.py",
    "content": "import os\nimport pty\nimport re\nimport signal\nimport termios\nimport time\n\nimport pytest\n\n\nEOF = b'\\x04'\n\n\ndef ttyflags(fd):\n    \"\"\"normalize tty i/o for testing\"\"\"\n    # see:\n    # http://www.gnu.org/software/libc/manual/html_mono/libc.html#Output-Modes\n    attrs = termios.tcgetattr(fd)\n    attrs[1] &= ~termios.OPOST  # don't munge output\n    attrs[3] &= ~termios.ECHO  # don't echo input\n    termios.tcsetattr(fd, termios.TCSANOW, attrs)\n\n\ndef readall(fd):\n    \"\"\"read until EOF\"\"\"\n    result = b''\n    while True:\n        try:\n            chunk = os.read(fd, 1 << 10)\n        except OSError as error:\n            if error.errno == 5:  # linux pty EOF\n                return result\n            else:\n                raise\n        if chunk == b'':\n            return result\n        else:\n            result += chunk\n\n\n# disable debug output so it doesn't break our assertion\n@pytest.mark.usefixtures('debug_disabled')\ndef test_tty():\n    \"\"\"Ensure processes under dumb-init can write successfully, given a tty.\"\"\"\n    pid, fd = pty.fork()\n    if pid == 0:\n        os.execvp('dumb-init', ('dumb-init', 'tac'))\n    else:\n        # write to tac via the pty and verify its output\n        ttyflags(fd)\n        assert os.write(fd, b'1\\n2\\n3\\n') == 6\n        assert os.write(fd, EOF * 2) == 2\n        output = readall(fd)\n        assert os.waitpid(pid, 0) == (pid, 0)\n\n        assert output == b'3\\n2\\n1\\n', repr(output)\n\n\n@pytest.mark.usefixtures('both_debug_modes')\n@pytest.mark.usefixtures('both_setsid_modes')\ndef test_child_gets_controlling_tty_if_we_had_one():\n    \"\"\"If dumb-init has a controlling TTY, it should give it to the child.\n\n    To test this, we make a new TTY then exec \"dumb-init bash\" and ensure that\n    the shell has working job control.\n    \"\"\"\n    pid, sfd = pty.fork()\n    if pid == 0:\n        os.execvp('dumb-init', ('dumb-init', 'bash', '-m'))\n    else:\n        ttyflags(sfd)\n\n        # We might get lots of extra output from the shell, so print something\n        # we can match on easily.\n        assert os.write(sfd, b'echo \"flags are: [[$-]]\"\\n') == 25\n        assert os.write(sfd, b'exit 0\\n') == 7\n        output = readall(sfd)\n        assert os.waitpid(pid, 0) == (pid, 0), output\n\n        m = re.search(b'flags are: \\\\[\\\\[([a-zA-Z]+)\\\\]\\\\]\\n', output)\n        assert m, output\n\n        # \"m\" is job control\n        flags = m.group(1)\n        assert b'm' in flags\n\n\ndef test_sighup_sigcont_ignored_if_was_session_leader():\n    \"\"\"The first SIGHUP/SIGCONT should be ignored if dumb-init is the session leader.\n\n    Due to TTY quirks (#136), when dumb-init is the session leader and forks,\n    it needs to avoid forwarding the first SIGHUP and SIGCONT to the child.\n    Otherwise, the child might receive the SIGHUP post-exec and terminate\n    itself.\n\n    You can \"force\" this race by adding a `sleep(1)` before the signal handling\n    loop in dumb-init's code, but it's hard to reproduce the race reliably in a\n    test otherwise. Because of this, we're stuck just asserting debug messages.\n    \"\"\"\n    pid, fd = pty.fork()\n    if pid == 0:\n        # child\n        os.execvp('dumb-init', ('dumb-init', '-v', 'sleep', '20'))\n    else:\n        # parent\n        ttyflags(fd)\n\n        # send another SIGCONT to make sure only the first is ignored\n        time.sleep(0.5)\n        os.kill(pid, signal.SIGHUP)\n\n        output = readall(fd).decode('UTF-8')\n\n        assert 'Ignoring tty hand-off signal {}.'.format(signal.SIGHUP) in output\n        assert 'Ignoring tty hand-off signal {}.'.format(signal.SIGCONT) in output\n\n        assert '[dumb-init] Forwarded signal {} to children.'.format(signal.SIGHUP) in output\n        assert '[dumb-init] Forwarded signal {} to children.'.format(signal.SIGCONT) not in output\n"
  },
  {
    "path": "tox.ini",
    "content": "[tox]\nenvlist = py38,gcov\n\n[testenv]\ndeps = -r{toxinidir}/requirements-dev.txt\ncommands =\n    pytest\n\n[testenv:gcov]\nskip_install = True\nbasepython = /usr/bin/python3.8\ncommands =\n    {toxinidir}/ci/gcov-build {envbindir}\n    {[testenv]commands}\n    {toxinidir}/ci/gcov-report\n\n[testenv:pre-commit]\nbasepython = /usr/bin/python3.8\ncommands = pre-commit {posargs:run --all-files}\n\n[flake8]\nmax-line-length = 119\n\n[pep8]\n# autopep8 will rewrite lines to be shorter, even though we raised the length\nignore = E501\n"
  }
]