On Sun, 13 Jan 2019 16:27:21 -0800 Zac Medico <zmed...@gentoo.org> wrote:
> Execute pid-ns-init as the first fork after unshare, as > required for it to have pid 1 and become the default reaper > of orphaned descendant processes. In _exec, exec a separate > pid-ns-init process to behave as a supervisor which will > forward signals to init and forward exit status to the parent > process. > > Fixes: a75d5546e3a4 ("Introduce a tiny init replacement for inside > pid namespace") Bug: https://bugs.gentoo.org/670484 > --- > bin/pid-ns-init | 44 > ++++++++++++++++++++++++++++++++++++++---- lib/portage/process.py | > 26 ++++++++++++++++++------- 2 files changed, 59 insertions(+), 11 > deletions(-) > > diff --git a/bin/pid-ns-init b/bin/pid-ns-init > index 843257b70..3792eeaa4 100644 > --- a/bin/pid-ns-init > +++ b/bin/pid-ns-init > @@ -1,23 +1,59 @@ > #!/usr/bin/env python > -# Copyright 2018 Gentoo Authors > +# Copyright 2018-2019 Gentoo Authors > # Distributed under the terms of the GNU General Public License v2 > > +import functools > import os > +import signal > import sys > > > +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>'.format(argv[0]) > - main_child_pid = int(argv[1]) > + return 'Usage: {} <main-child-pid> or <binary> > argv0..'.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]) > + else: > + # The current process is init (pid 1) in a child pid > namespace. > + binary = argv[1] > + args = argv[2:] > + > + main_child_pid = os.fork() > + if main_child_pid == 0: > + os.execv(binary, args) > + > + 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: > - pid, status = os.wait() > + try: > + pid, status = os.wait() > + except OSError as e: > + if e.errno == errno.EINTR: > + continue > + raise > if pid == main_child_pid: > 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 > diff --git a/lib/portage/process.py b/lib/portage/process.py > index 7103b6b31..3e07f806c 100644 > --- a/lib/portage/process.py > +++ b/lib/portage/process.py > @@ -564,15 +564,27 @@ def _exec(binary, mycommand, opt_name, fd_pipes, > noiselevel=-1) > else: > if unshare_pid: > - # pid > namespace requires us to become init > - fork_ret = > os.fork() > - if > fork_ret != 0: > - > os.execv(portage._python_interpreter, [ > + > main_child_pid = os.fork() > + if > main_child_pid == 0: > + # > pid namespace requires us to become init > + > binary, myargs = portage._python_interpreter, [ > + > portage._python_interpreter, > + > os.path.join(portage._bin_path, > + > 'pid-ns-init')] > + [binary] + myargs > + else: > + # > Execute a supervisor process which will forward > + # > signals to init and forward exit status to the > + # > parent process. The supervisor process runs in > + # > the global pid namespace, so skip /proc remount > + # > and other setup that's intended only for the > + # > init process. > + > binary, myargs = portage._python_interpreter, > [ portage._python_interpreter, os.path.join(portage._bin_path, > - > 'pid-ns-init'), > - '%s' > % fork_ret, > - ]) > + > 'pid-ns-init'), > str(main_child_pid)] + > + > os.execve(binary, myargs, env) + > if unshare_mount: > # mark the > whole filesystem as slave to avoid # mounts escaping the namespace Looks fine, but I defer to floppym, mgorny since this affects systemd