1 |
Execute pid-ns-init as the first fork after unshare, as |
2 |
required for it to have pid 1 and become the default reaper |
3 |
of orphaned descendant processes. In _exec, exec a separate |
4 |
pid-ns-init process to behave as a supervisor which will |
5 |
forward signals to init and forward exit status to the parent |
6 |
process. |
7 |
|
8 |
Fixes: a75d5546e3a4 ("Introduce a tiny init replacement for inside pid namespace") |
9 |
Bug: https://bugs.gentoo.org/670484 |
10 |
--- |
11 |
bin/pid-ns-init | 44 ++++++++++++++++++++++++++++++++++++++---- |
12 |
lib/portage/process.py | 26 ++++++++++++++++++------- |
13 |
2 files changed, 59 insertions(+), 11 deletions(-) |
14 |
|
15 |
diff --git a/bin/pid-ns-init b/bin/pid-ns-init |
16 |
index 843257b70..3792eeaa4 100644 |
17 |
--- a/bin/pid-ns-init |
18 |
+++ b/bin/pid-ns-init |
19 |
@@ -1,23 +1,59 @@ |
20 |
#!/usr/bin/env python |
21 |
-# Copyright 2018 Gentoo Authors |
22 |
+# Copyright 2018-2019 Gentoo Authors |
23 |
# Distributed under the terms of the GNU General Public License v2 |
24 |
|
25 |
+import functools |
26 |
import os |
27 |
+import signal |
28 |
import sys |
29 |
|
30 |
|
31 |
+KILL_SIGNALS = ( |
32 |
+ signal.SIGINT, |
33 |
+ signal.SIGTERM, |
34 |
+ signal.SIGHUP, |
35 |
+) |
36 |
+ |
37 |
+def forward_kill_signal(main_child_pid, signum, frame): |
38 |
+ os.kill(main_child_pid, signum) |
39 |
+ |
40 |
+ |
41 |
def main(argv): |
42 |
if len(argv) < 2: |
43 |
- return 'Usage: {} <main-child-pid>'.format(argv[0]) |
44 |
- main_child_pid = int(argv[1]) |
45 |
+ return 'Usage: {} <main-child-pid> or <binary> argv0..'.format(argv[0]) |
46 |
+ |
47 |
+ if len(argv) == 2: |
48 |
+ # The child process is init (pid 1) in a child pid namespace, and |
49 |
+ # the current process supervises from within the global pid namespace |
50 |
+ # (forwarding signals to init and forwarding exit status to the parent |
51 |
+ # process). |
52 |
+ main_child_pid = int(argv[1]) |
53 |
+ else: |
54 |
+ # The current process is init (pid 1) in a child pid namespace. |
55 |
+ binary = argv[1] |
56 |
+ args = argv[2:] |
57 |
+ |
58 |
+ main_child_pid = os.fork() |
59 |
+ if main_child_pid == 0: |
60 |
+ os.execv(binary, args) |
61 |
+ |
62 |
+ sig_handler = functools.partial(forward_kill_signal, main_child_pid) |
63 |
+ for signum in KILL_SIGNALS: |
64 |
+ signal.signal(signum, sig_handler) |
65 |
|
66 |
# wait for child processes |
67 |
while True: |
68 |
- pid, status = os.wait() |
69 |
+ try: |
70 |
+ pid, status = os.wait() |
71 |
+ except OSError as e: |
72 |
+ if e.errno == errno.EINTR: |
73 |
+ continue |
74 |
+ raise |
75 |
if pid == main_child_pid: |
76 |
if os.WIFEXITED(status): |
77 |
return os.WEXITSTATUS(status) |
78 |
elif os.WIFSIGNALED(status): |
79 |
+ signal.signal(os.WTERMSIG(status), signal.SIG_DFL) |
80 |
os.kill(os.getpid(), os.WTERMSIG(status)) |
81 |
# go to the unreachable place |
82 |
break |
83 |
diff --git a/lib/portage/process.py b/lib/portage/process.py |
84 |
index 7103b6b31..3e07f806c 100644 |
85 |
--- a/lib/portage/process.py |
86 |
+++ b/lib/portage/process.py |
87 |
@@ -564,15 +564,27 @@ def _exec(binary, mycommand, opt_name, fd_pipes, |
88 |
noiselevel=-1) |
89 |
else: |
90 |
if unshare_pid: |
91 |
- # pid namespace requires us to become init |
92 |
- fork_ret = os.fork() |
93 |
- if fork_ret != 0: |
94 |
- os.execv(portage._python_interpreter, [ |
95 |
+ main_child_pid = os.fork() |
96 |
+ if main_child_pid == 0: |
97 |
+ # pid namespace requires us to become init |
98 |
+ binary, myargs = portage._python_interpreter, [ |
99 |
+ portage._python_interpreter, |
100 |
+ os.path.join(portage._bin_path, |
101 |
+ 'pid-ns-init')] + [binary] + myargs |
102 |
+ else: |
103 |
+ # Execute a supervisor process which will forward |
104 |
+ # signals to init and forward exit status to the |
105 |
+ # parent process. The supervisor process runs in |
106 |
+ # the global pid namespace, so skip /proc remount |
107 |
+ # and other setup that's intended only for the |
108 |
+ # init process. |
109 |
+ binary, myargs = portage._python_interpreter, [ |
110 |
portage._python_interpreter, |
111 |
os.path.join(portage._bin_path, |
112 |
- 'pid-ns-init'), |
113 |
- '%s' % fork_ret, |
114 |
- ]) |
115 |
+ 'pid-ns-init'), str(main_child_pid)] |
116 |
+ |
117 |
+ os.execve(binary, myargs, env) |
118 |
+ |
119 |
if unshare_mount: |
120 |
# mark the whole filesystem as slave to avoid |
121 |
# mounts escaping the namespace |
122 |
-- |
123 |
2.18.1 |