]>
Commit | Line | Data |
---|---|---|
52ad194e | 1 | /* SPDX-License-Identifier: LGPL-2.1+ */ |
4c89c718 MP |
2 | |
3 | #include <sys/reboot.h> | |
4c89c718 | 4 | #include <sys/wait.h> |
2897b343 | 5 | #include <sys/prctl.h> |
81c58355 | 6 | #include <unistd.h> |
4c89c718 | 7 | |
6e866b33 MB |
8 | #include "def.h" |
9 | #include "exit-status.h" | |
4c89c718 MP |
10 | #include "fd-util.h" |
11 | #include "log.h" | |
1d42b86d | 12 | #include "missing.h" |
4c89c718 MP |
13 | #include "nspawn-stub-pid1.h" |
14 | #include "process-util.h" | |
15 | #include "signal-util.h" | |
16 | #include "time-util.h" | |
4c89c718 | 17 | |
2897b343 MP |
18 | static int reset_environ(const char *new_environment, size_t length) { |
19 | unsigned long start, end; | |
20 | ||
21 | start = (unsigned long) new_environment; | |
22 | end = start + length; | |
23 | ||
24 | if (prctl(PR_SET_MM, PR_SET_MM_ENV_START, start, 0, 0) < 0) | |
25 | return -errno; | |
26 | ||
27 | if (prctl(PR_SET_MM, PR_SET_MM_ENV_END, end, 0, 0) < 0) | |
28 | return -errno; | |
29 | ||
30 | return 0; | |
31 | } | |
32 | ||
33 | int stub_pid1(sd_id128_t uuid) { | |
4c89c718 MP |
34 | enum { |
35 | STATE_RUNNING, | |
36 | STATE_REBOOT, | |
37 | STATE_POWEROFF, | |
38 | } state = STATE_RUNNING; | |
39 | ||
40 | sigset_t fullmask, oldmask, waitmask; | |
41 | usec_t quit_usec = USEC_INFINITY; | |
42 | pid_t pid; | |
43 | int r; | |
44 | ||
2897b343 MP |
45 | /* The new environment we set up, on the stack. */ |
46 | char new_environment[] = | |
47 | "container=systemd-nspawn\0" | |
48 | "container_uuid=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"; | |
49 | ||
4c89c718 MP |
50 | /* Implements a stub PID 1, that reaps all processes and processes a couple of standard signals. This is useful |
51 | * for allowing arbitrary processes run in a container, and still have all zombies reaped. */ | |
52 | ||
53 | assert_se(sigfillset(&fullmask) >= 0); | |
54 | assert_se(sigprocmask(SIG_BLOCK, &fullmask, &oldmask) >= 0); | |
55 | ||
56 | pid = fork(); | |
57 | if (pid < 0) | |
58 | return log_error_errno(errno, "Failed to fork child pid: %m"); | |
59 | ||
60 | if (pid == 0) { | |
61 | /* Return in the child */ | |
62 | assert_se(sigprocmask(SIG_SETMASK, &oldmask, NULL) >= 0); | |
63 | setsid(); | |
64 | return 0; | |
65 | } | |
66 | ||
67 | reset_all_signal_handlers(); | |
68 | ||
69 | log_close(); | |
bb4f798a | 70 | (void) close_all_fds(NULL, 0); |
4c89c718 MP |
71 | log_open(); |
72 | ||
2897b343 MP |
73 | /* Flush out /proc/self/environ, so that we don't leak the environment from the host into the container. Also, |
74 | * set $container= and $container_uuid= so that clients in the container that query it from /proc/1/environ | |
75 | * find them set. */ | |
76 | sd_id128_to_string(uuid, new_environment + sizeof(new_environment) - SD_ID128_STRING_MAX); | |
77 | reset_environ(new_environment, sizeof(new_environment)); | |
78 | ||
1d42b86d | 79 | (void) rename_process("(sd-stubinit)"); |
4c89c718 MP |
80 | |
81 | assert_se(sigemptyset(&waitmask) >= 0); | |
82 | assert_se(sigset_add_many(&waitmask, | |
83 | SIGCHLD, /* posix: process died */ | |
84 | SIGINT, /* sysv: ctrl-alt-del */ | |
85 | SIGRTMIN+3, /* systemd: halt */ | |
86 | SIGRTMIN+4, /* systemd: poweroff */ | |
87 | SIGRTMIN+5, /* systemd: reboot */ | |
88 | SIGRTMIN+6, /* systemd: kexec */ | |
89 | SIGRTMIN+13, /* systemd: halt */ | |
90 | SIGRTMIN+14, /* systemd: poweroff */ | |
91 | SIGRTMIN+15, /* systemd: reboot */ | |
92 | SIGRTMIN+16, /* systemd: kexec */ | |
93 | -1) >= 0); | |
94 | ||
95 | /* Note that we ignore SIGTERM (sysv's reexec), SIGHUP (reload), and all other signals here, since we don't | |
96 | * support reexec/reloading in this stub process. */ | |
97 | ||
98 | for (;;) { | |
99 | siginfo_t si; | |
100 | usec_t current_usec; | |
101 | ||
102 | si.si_pid = 0; | |
103 | r = waitid(P_ALL, 0, &si, WEXITED|WNOHANG); | |
104 | if (r < 0) { | |
105 | r = log_error_errno(errno, "Failed to reap children: %m"); | |
106 | goto finish; | |
107 | } | |
108 | ||
109 | current_usec = now(CLOCK_MONOTONIC); | |
110 | ||
111 | if (si.si_pid == pid || current_usec >= quit_usec) { | |
112 | ||
113 | /* The child we started ourselves died or we reached a timeout. */ | |
114 | ||
115 | if (state == STATE_REBOOT) { /* dispatch a queued reboot */ | |
116 | (void) reboot(RB_AUTOBOOT); | |
117 | r = log_error_errno(errno, "Failed to reboot: %m"); | |
118 | goto finish; | |
119 | ||
120 | } else if (state == STATE_POWEROFF) | |
121 | (void) reboot(RB_POWER_OFF); /* if this fails, fall back to normal exit. */ | |
122 | ||
123 | if (si.si_pid == pid && si.si_code == CLD_EXITED) | |
124 | r = si.si_status; /* pass on exit code */ | |
125 | else | |
6e866b33 | 126 | r = EXIT_EXCEPTION; /* signal, coredump, timeout, … */ |
4c89c718 MP |
127 | |
128 | goto finish; | |
129 | } | |
130 | if (si.si_pid != 0) | |
131 | /* We reaped something. Retry until there's nothing more to reap. */ | |
132 | continue; | |
133 | ||
134 | if (quit_usec == USEC_INFINITY) | |
135 | r = sigwaitinfo(&waitmask, &si); | |
136 | else { | |
137 | struct timespec ts; | |
138 | r = sigtimedwait(&waitmask, &si, timespec_store(&ts, quit_usec - current_usec)); | |
139 | } | |
140 | if (r < 0) { | |
141 | if (errno == EINTR) /* strace -p attach can result in EINTR, let's handle this nicely. */ | |
142 | continue; | |
143 | if (errno == EAGAIN) /* timeout reached */ | |
144 | continue; | |
145 | ||
146 | r = log_error_errno(errno, "Failed to wait for signal: %m"); | |
147 | goto finish; | |
148 | } | |
149 | ||
150 | if (si.si_signo == SIGCHLD) | |
151 | continue; /* Let's reap this */ | |
152 | ||
153 | if (state != STATE_RUNNING) | |
154 | continue; | |
155 | ||
156 | /* Would love to use a switch() statement here, but SIGRTMIN is actually a function call, not a | |
157 | * constant… */ | |
158 | ||
159 | if (si.si_signo == SIGRTMIN+3 || | |
160 | si.si_signo == SIGRTMIN+4 || | |
161 | si.si_signo == SIGRTMIN+13 || | |
162 | si.si_signo == SIGRTMIN+14) | |
163 | ||
164 | state = STATE_POWEROFF; | |
165 | ||
166 | else if (si.si_signo == SIGINT || | |
167 | si.si_signo == SIGRTMIN+5 || | |
168 | si.si_signo == SIGRTMIN+6 || | |
169 | si.si_signo == SIGRTMIN+15 || | |
170 | si.si_signo == SIGRTMIN+16) | |
171 | ||
172 | state = STATE_REBOOT; | |
173 | else | |
174 | assert_not_reached("Got unexpected signal"); | |
175 | ||
81c58355 MB |
176 | r = kill_and_sigcont(pid, SIGTERM); |
177 | ||
178 | /* Let's send a SIGHUP after the SIGTERM, as shells tend to ignore SIGTERM but do react to SIGHUP. We | |
179 | * do it strictly in this order, so that the SIGTERM is dispatched first, and SIGHUP second for those | |
180 | * processes which handle both. That's because services tend to bind configuration reload or something | |
181 | * else to SIGHUP. */ | |
182 | ||
183 | if (r != -ESRCH) | |
184 | (void) kill(pid, SIGHUP); | |
185 | ||
4c89c718 MP |
186 | quit_usec = now(CLOCK_MONOTONIC) + DEFAULT_TIMEOUT_USEC; |
187 | } | |
188 | ||
189 | finish: | |
190 | _exit(r < 0 ? EXIT_FAILURE : r); | |
191 | } |