| #!/usr/bin/env python |
| # Copyright 2018-2019 Gentoo Authors |
| # Distributed under the terms of the GNU General Public License v2 |
| |
| import errno |
| import functools |
| import os |
| import platform |
| import signal |
| import subprocess |
| import sys |
| |
| |
| if sys.version_info.major < 3 or platform.python_implementation() != 'CPython': |
| def signal_disposition_preexec(): |
| for signum in ( |
| signal.SIGHUP, |
| signal.SIGINT, |
| signal.SIGPIPE, |
| signal.SIGQUIT, |
| signal.SIGTERM, |
| ): |
| signal.signal(signum, signal.SIG_DFL) |
| else: |
| # CPython >= 3 subprocess.Popen handles this internally. |
| signal_disposition_preexec = None |
| |
| |
| KILL_SIGNALS = ( |
| signal.SIGINT, |
| signal.SIGTERM, |
| signal.SIGHUP, |
| ) |
| |
| |
| def forward_kill_signal(main_child_pid, signum, frame): |
| os.kill(main_child_pid, signum) |
| |
| |
| def main(argv): |
| if len(argv) < 2: |
| return 'Usage: {} <main-child-pid> or <pass_fds> <binary> <argv0> [arg]..'.format(argv[0]) |
| |
| if len(argv) == 2: |
| # The child process is init (pid 1) in a child pid namespace, and |
| # the current process supervises from within the global pid namespace |
| # (forwarding signals to init and forwarding exit status to the parent |
| # process). |
| main_child_pid = int(argv[1]) |
| proc = None |
| else: |
| # The current process is init (pid 1) in a child pid namespace. |
| pass_fds, binary, args = tuple(int(fd) for fd in argv[1].split(',')), argv[2], argv[3:] |
| |
| popen_kwargs = {} |
| if sys.version_info.major > 2: |
| popen_kwargs['pass_fds'] = pass_fds |
| proc = subprocess.Popen(args, executable=binary, |
| preexec_fn=signal_disposition_preexec, **popen_kwargs) |
| main_child_pid = proc.pid |
| |
| sig_handler = functools.partial(forward_kill_signal, main_child_pid) |
| for signum in KILL_SIGNALS: |
| signal.signal(signum, sig_handler) |
| |
| # wait for child processes |
| while True: |
| try: |
| pid, status = os.wait() |
| except OSError as e: |
| if e.errno == errno.EINTR: |
| continue |
| raise |
| if pid == main_child_pid: |
| if proc is not None: |
| # Suppress warning messages like this: |
| # ResourceWarning: subprocess 1234 is still running |
| proc.returncode = 0 |
| |
| if os.WIFEXITED(status): |
| return os.WEXITSTATUS(status) |
| elif os.WIFSIGNALED(status): |
| signal.signal(os.WTERMSIG(status), signal.SIG_DFL) |
| os.kill(os.getpid(), os.WTERMSIG(status)) |
| # go to the unreachable place |
| break |
| |
| # this should never be reached |
| return 127 |
| |
| |
| if __name__ == '__main__': |
| sys.exit(main(sys.argv)) |