]>
Commit | Line | Data |
---|---|---|
db9ecf05 | 1 | /* SPDX-License-Identifier: LGPL-2.1-or-later */ |
bd3fa1d2 | 2 | /*** |
96b2fb93 | 3 | Copyright © 2010 ProFUSION embedded systems |
bd3fa1d2 LP |
4 | ***/ |
5 | ||
bd3fa1d2 | 6 | #include <errno.h> |
07630cea LP |
7 | #include <signal.h> |
8 | #include <sys/wait.h> | |
aaf7eb81 | 9 | #include <unistd.h> |
bd3fa1d2 | 10 | |
b5efdb8a | 11 | #include "alloc-util.h" |
28db6fbf | 12 | #include "constants.h" |
8fb3f009 | 13 | #include "dirent-util.h" |
559214cb | 14 | #include "errno-util.h" |
3ffd4af2 | 15 | #include "fd-util.h" |
f97b34a6 | 16 | #include "format-util.h" |
baa6a42d | 17 | #include "initrd-util.h" |
3ffd4af2 | 18 | #include "killall.h" |
6bedfcbb | 19 | #include "parse-util.h" |
0b452006 | 20 | #include "process-util.h" |
07630cea | 21 | #include "set.h" |
fbc39784 | 22 | #include "stdio-util.h" |
07630cea | 23 | #include "string-util.h" |
288a74cc | 24 | #include "terminal-util.h" |
bd3fa1d2 | 25 | |
559214cb | 26 | static bool argv_has_at(pid_t pid) { |
31885cd5 | 27 | _cleanup_fclose_ FILE *f = NULL; |
b68fa010 | 28 | const char *p; |
e45154c7 | 29 | char c = 0; |
559214cb LB |
30 | |
31 | p = procfs_file_alloca(pid, "cmdline"); | |
32 | f = fopen(p, "re"); | |
33 | if (!f) { | |
34 | log_debug_errno(errno, "Failed to open %s, ignoring: %m", p); | |
35 | return true; /* not really, but has the desired effect */ | |
36 | } | |
37 | ||
38 | /* Try to read the first character of the command line. If the cmdline is empty (which might be the case for | |
39 | * kernel threads but potentially also other stuff), this line won't do anything, but we don't care much, as | |
40 | * actual kernel threads are already filtered out above. */ | |
41 | (void) fread(&c, 1, 1, f); | |
42 | ||
43 | /* Processes with argv[0][0] = '@' we ignore from the killing spree. | |
44 | * | |
45 | * https://systemd.io/ROOT_STORAGE_DAEMONS */ | |
46 | return c == '@'; | |
47 | } | |
48 | ||
2d790175 | 49 | static bool is_survivor_cgroup(const PidRef *pid) { |
559214cb LB |
50 | _cleanup_free_ char *cgroup_path = NULL; |
51 | int r; | |
52 | ||
2d790175 LP |
53 | assert(pidref_is_set(pid)); |
54 | ||
55 | r = cg_pidref_get_path(/* root= */ NULL, pid, &cgroup_path); | |
559214cb | 56 | if (r < 0) { |
2d790175 | 57 | log_warning_errno(r, "Failed to get cgroup path of process " PID_FMT ", ignoring: %m", pid->pid); |
559214cb LB |
58 | return false; |
59 | } | |
60 | ||
bd1791b5 | 61 | r = cg_get_xattr_bool(cgroup_path, "user.survive_final_kill_signal"); |
559214cb LB |
62 | /* user xattr support was added to kernel v5.7, try with the trusted namespace as a fallback */ |
63 | if (ERRNO_IS_NEG_XATTR_ABSENT(r)) | |
bd1791b5 | 64 | r = cg_get_xattr_bool(cgroup_path, "trusted.survive_final_kill_signal"); |
6fd38dc8 | 65 | if (r < 0 && !ERRNO_IS_NEG_XATTR_ABSENT(r)) |
559214cb LB |
66 | log_debug_errno(r, |
67 | "Failed to get survive_final_kill_signal xattr of %s, ignoring: %m", | |
68 | cgroup_path); | |
69 | ||
70 | return r > 0; | |
71 | } | |
72 | ||
2d790175 | 73 | static bool ignore_proc(const PidRef *pid, bool warn_rootfs) { |
bd3fa1d2 LP |
74 | uid_t uid; |
75 | int r; | |
76 | ||
2d790175 LP |
77 | assert(pidref_is_set(pid)); |
78 | ||
bd3fa1d2 | 79 | /* We are PID 1, let's not commit suicide */ |
2d790175 | 80 | if (pid->pid == 1) |
bd3fa1d2 LP |
81 | return true; |
82 | ||
e45154c7 | 83 | /* Ignore kernel threads */ |
2d790175 | 84 | r = pidref_is_kernel_thread(pid); |
e45154c7 LP |
85 | if (r != 0) |
86 | return true; /* also ignore processes where we can't determine this */ | |
87 | ||
559214cb LB |
88 | /* Ignore processes that are part of a cgroup marked with the user.survive_final_kill_signal xattr */ |
89 | if (is_survivor_cgroup(pid)) | |
90 | return true; | |
91 | ||
2d790175 | 92 | r = pidref_get_uid(pid, &uid); |
bd3fa1d2 LP |
93 | if (r < 0) |
94 | return true; /* not really, but better safe than sorry */ | |
95 | ||
96 | /* Non-root processes otherwise are always subject to be killed */ | |
97 | if (uid != 0) | |
98 | return false; | |
99 | ||
2d790175 | 100 | if (!argv_has_at(pid->pid)) |
9e615117 ZJS |
101 | return false; |
102 | ||
103 | if (warn_rootfs && | |
374c29fc | 104 | pid_from_same_root_fs(pid->pid) > 0) { |
1359fffa | 105 | |
9e615117 | 106 | _cleanup_free_ char *comm = NULL; |
1359fffa | 107 | |
2d790175 | 108 | (void) pidref_get_comm(pid, &comm); |
1359fffa | 109 | |
9e615117 ZJS |
110 | log_notice("Process " PID_FMT " (%s) has been marked to be excluded from killing. It is " |
111 | "running from the root file system, and thus likely to block re-mounting of the " | |
112 | "root file system to read-only. Please consider moving it into an initrd file " | |
2d790175 | 113 | "system instead.", pid->pid, strna(comm)); |
9e615117 | 114 | } |
bd3fa1d2 | 115 | |
9e615117 | 116 | return true; |
bd3fa1d2 LP |
117 | } |
118 | ||
bbfb9edc | 119 | static void log_children_not_yet_killed(Set *pids) { |
2c32f4f4 | 120 | _cleanup_free_ char *lst_child = NULL; |
94ce42bc | 121 | int r; |
2c32f4f4 | 122 | |
bbfb9edc | 123 | void *p; |
90e74a66 | 124 | SET_FOREACH(p, pids) { |
2c32f4f4 | 125 | _cleanup_free_ char *s = NULL; |
60cd3676 | 126 | |
d7d74854 | 127 | if (pid_get_comm(PTR_TO_PID(p), &s) >= 0) |
bbfb9edc | 128 | r = strextendf_with_separator(&lst_child, ", ", PID_FMT " (%s)", PTR_TO_PID(p), s); |
94ce42bc | 129 | else |
bbfb9edc | 130 | r = strextendf_with_separator(&lst_child, ", ", PID_FMT, PTR_TO_PID(p)); |
94ce42bc | 131 | if (r < 0) |
2d790175 | 132 | return (void) log_oom_warning(); |
2c32f4f4 BR |
133 | } |
134 | ||
60cd3676 LP |
135 | if (isempty(lst_child)) |
136 | return; | |
137 | ||
bbfb9edc | 138 | log_warning("Waiting for process: %s", lst_child); |
2c32f4f4 BR |
139 | } |
140 | ||
a012f9f7 | 141 | static int wait_for_children(Set *pids, sigset_t *mask, usec_t timeout) { |
60cd3676 | 142 | usec_t until, date_log_child, n; |
bd3fa1d2 LP |
143 | |
144 | assert(mask); | |
145 | ||
a012f9f7 BR |
146 | /* Return the number of children remaining in the pids set: That correspond to the number |
147 | * of processes still "alive" after the timeout */ | |
148 | ||
aaf7eb81 | 149 | if (set_isempty(pids)) |
a012f9f7 | 150 | return 0; |
aaf7eb81 | 151 | |
2c32f4f4 BR |
152 | n = now(CLOCK_MONOTONIC); |
153 | until = usec_add(n, timeout); | |
154 | date_log_child = usec_add(n, 10u * USEC_PER_SEC); | |
155 | if (date_log_child > until) | |
156 | date_log_child = usec_add(n, timeout / 2u); | |
157 | ||
bd3fa1d2 LP |
158 | for (;;) { |
159 | struct timespec ts; | |
160 | int k; | |
aaf7eb81 | 161 | void *p; |
bd3fa1d2 | 162 | |
aaf7eb81 LP |
163 | /* First, let the kernel inform us about killed |
164 | * children. Most processes will probably be our | |
165 | * children, but some are not (might be our | |
166 | * grandchildren instead...). */ | |
bd3fa1d2 | 167 | for (;;) { |
aaf7eb81 | 168 | pid_t pid; |
bd3fa1d2 | 169 | |
aaf7eb81 | 170 | pid = waitpid(-1, NULL, WNOHANG); |
bd3fa1d2 LP |
171 | if (pid == 0) |
172 | break; | |
aaf7eb81 LP |
173 | if (pid < 0) { |
174 | if (errno == ECHILD) | |
175 | break; | |
bd3fa1d2 | 176 | |
a012f9f7 | 177 | return log_error_errno(errno, "waitpid() failed: %m"); |
aaf7eb81 LP |
178 | } |
179 | ||
fea72cc0 | 180 | (void) set_remove(pids, PID_TO_PTR(pid)); |
aaf7eb81 | 181 | } |
bd3fa1d2 | 182 | |
aaf7eb81 LP |
183 | /* Now explicitly check who might be remaining, who |
184 | * might not be our child. */ | |
90e74a66 | 185 | SET_FOREACH(p, pids) { |
aaf7eb81 | 186 | |
3448a969 AJ |
187 | /* kill(pid, 0) sends no signal, but it tells |
188 | * us whether the process still exists. */ | |
189 | if (kill(PTR_TO_PID(p), 0) == 0) | |
aaf7eb81 LP |
190 | continue; |
191 | ||
192 | if (errno != ESRCH) | |
193 | continue; | |
194 | ||
195 | set_remove(pids, p); | |
bd3fa1d2 LP |
196 | } |
197 | ||
aaf7eb81 | 198 | if (set_isempty(pids)) |
a012f9f7 | 199 | return 0; |
aaf7eb81 | 200 | |
bd3fa1d2 | 201 | n = now(CLOCK_MONOTONIC); |
2c32f4f4 | 202 | if (date_log_child > 0 && n >= date_log_child) { |
bbfb9edc | 203 | log_children_not_yet_killed(pids); |
2c32f4f4 BR |
204 | /* Log the children not yet killed only once */ |
205 | date_log_child = 0; | |
206 | } | |
207 | ||
bd3fa1d2 | 208 | if (n >= until) |
a012f9f7 | 209 | return set_size(pids); |
bd3fa1d2 | 210 | |
2c32f4f4 BR |
211 | if (date_log_child > 0) |
212 | timespec_store(&ts, MIN(until - n, date_log_child - n)); | |
213 | else | |
214 | timespec_store(&ts, until - n); | |
215 | ||
aaf7eb81 LP |
216 | k = sigtimedwait(mask, NULL, &ts); |
217 | if (k != SIGCHLD) { | |
bd3fa1d2 | 218 | |
a012f9f7 BR |
219 | if (k < 0 && errno != EAGAIN) |
220 | return log_error_errno(errno, "sigtimedwait() failed: %m"); | |
bd3fa1d2 LP |
221 | |
222 | if (k >= 0) | |
223 | log_warning("sigtimedwait() returned unexpected signal."); | |
224 | } | |
225 | } | |
226 | } | |
227 | ||
0bee65f0 | 228 | static int killall(int sig, Set *pids, bool send_sighup) { |
aaf7eb81 | 229 | _cleanup_closedir_ DIR *dir = NULL; |
2d790175 | 230 | int n_killed = 0, r; |
a012f9f7 BR |
231 | |
232 | /* Send the specified signal to all remaining processes, if not excluded by ignore_proc(). | |
233 | * Returns the number of processes to which the specified signal was sent */ | |
bd3fa1d2 | 234 | |
2d790175 LP |
235 | r = proc_dir_open(&dir); |
236 | if (r < 0) | |
bbfb9edc | 237 | return log_warning_errno(r, "Failed to open /proc/: %m"); |
bd3fa1d2 | 238 | |
2d790175 LP |
239 | for (;;) { |
240 | _cleanup_(pidref_done) PidRef pidref = PIDREF_NULL; | |
bd3fa1d2 | 241 | |
2d790175 LP |
242 | r = proc_dir_read_pidref(dir, &pidref); |
243 | if (r < 0) | |
bbfb9edc | 244 | return log_warning_errno(r, "Failed to enumerate /proc/: %m"); |
2d790175 LP |
245 | if (r == 0) |
246 | break; | |
bd3fa1d2 | 247 | |
2d790175 | 248 | if (ignore_proc(&pidref, sig == SIGKILL && !in_initrd())) |
bd3fa1d2 LP |
249 | continue; |
250 | ||
df758e98 | 251 | if (sig == SIGKILL) { |
3e09eb5c | 252 | _cleanup_free_ char *s = NULL; |
df758e98 | 253 | |
2d790175 LP |
254 | (void) pidref_get_comm(&pidref, &s); |
255 | log_notice("Sending SIGKILL to PID "PID_FMT" (%s).", pidref.pid, strna(s)); | |
df758e98 KS |
256 | } |
257 | ||
2d790175 LP |
258 | r = pidref_kill(&pidref, sig); |
259 | if (r < 0) { | |
6f7936cf | 260 | if (r != -ESRCH) |
2d790175 LP |
261 | log_warning_errno(errno, "Could not kill " PID_FMT ", ignoring: %m", pidref.pid); |
262 | } else { | |
a012f9f7 | 263 | n_killed++; |
60053efb | 264 | if (pids) { |
2d790175 | 265 | r = set_put(pids, PID_TO_PTR(pidref.pid)); |
60053efb | 266 | if (r < 0) |
2d790175 | 267 | (void) log_oom_warning(); |
60053efb | 268 | } |
2d790175 | 269 | } |
0bee65f0 LP |
270 | |
271 | if (send_sighup) { | |
2d790175 LP |
272 | /* Optionally, also send a SIGHUP signal, but only if the process has a controlling |
273 | * tty. This is useful to allow handling of shells which ignore SIGTERM but react to | |
274 | * SIGHUP. We do not send this to processes that have no controlling TTY since we | |
275 | * don't want to trigger reloads of daemon processes. Also we make sure to only send | |
276 | * this after SIGTERM so that SIGTERM is always first in the queue. */ | |
277 | ||
278 | if (get_ctty_devnr(pidref.pid, NULL) >= 0) | |
5c5d9f26 | 279 | /* it's OK if the process is gone, just ignore the result */ |
2d790175 | 280 | (void) pidref_kill(&pidref, SIGHUP); |
0bee65f0 | 281 | } |
bd3fa1d2 LP |
282 | } |
283 | ||
a012f9f7 | 284 | return n_killed; |
bd3fa1d2 LP |
285 | } |
286 | ||
a012f9f7 BR |
287 | int broadcast_signal(int sig, bool wait_for_exit, bool send_sighup, usec_t timeout) { |
288 | int n_children_left; | |
bd3fa1d2 | 289 | sigset_t mask, oldmask; |
e1d75803 | 290 | _cleanup_set_free_ Set *pids = NULL; |
aaf7eb81 | 291 | |
763e7b5d BR |
292 | /* Send the specified signal to all remaining processes, if not excluded by ignore_proc(). |
293 | * Return: | |
294 | * - The number of processes still "alive" after the timeout (that should have been killed) | |
295 | * if the function needs to wait for the end of the processes (wait_for_exit). | |
296 | * - Otherwise, the number of processes to which the specified signal was sent */ | |
297 | ||
aaf7eb81 | 298 | if (wait_for_exit) |
d5099efc | 299 | pids = set_new(NULL); |
bd3fa1d2 LP |
300 | |
301 | assert_se(sigemptyset(&mask) == 0); | |
302 | assert_se(sigaddset(&mask, SIGCHLD) == 0); | |
303 | assert_se(sigprocmask(SIG_BLOCK, &mask, &oldmask) == 0); | |
304 | ||
305 | if (kill(-1, SIGSTOP) < 0 && errno != ESRCH) | |
56f64d95 | 306 | log_warning_errno(errno, "kill(-1, SIGSTOP) failed: %m"); |
bd3fa1d2 | 307 | |
a012f9f7 | 308 | n_children_left = killall(sig, pids, send_sighup); |
bd3fa1d2 LP |
309 | |
310 | if (kill(-1, SIGCONT) < 0 && errno != ESRCH) | |
56f64d95 | 311 | log_warning_errno(errno, "kill(-1, SIGCONT) failed: %m"); |
bd3fa1d2 | 312 | |
a012f9f7 BR |
313 | if (wait_for_exit && n_children_left > 0) |
314 | n_children_left = wait_for_children(pids, &mask, timeout); | |
aaf7eb81 LP |
315 | |
316 | assert_se(sigprocmask(SIG_SETMASK, &oldmask, NULL) == 0); | |
a012f9f7 BR |
317 | |
318 | return n_children_left; | |
bd3fa1d2 | 319 | } |