blob: f9b8cc4f301d7a16fee73e8b8b5a8baa43019f63 [file] [log] [blame]
#!/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))