]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/basic/pidref.c
Merge pull request #31648 from neighbourhoodie/review-content
[thirdparty/systemd.git] / src / basic / pidref.c
CommitLineData
3bda3f17
LP
1/* SPDX-License-Identifier: LGPL-2.1-or-later */
2
3#include "errno-util.h"
4#include "fd-util.h"
5#include "missing_syscall.h"
a3f32436 6#include "missing_wait.h"
3bda3f17
LP
7#include "parse-util.h"
8#include "pidref.h"
9#include "process-util.h"
a0d1659c 10#include "signal-util.h"
2f41f10b
MY
11#include "stat-util.h"
12
13bool pidref_equal(const PidRef *a, const PidRef *b) {
14 int r;
15
16 if (pidref_is_set(a)) {
17 if (!pidref_is_set(b))
18 return false;
19
20 if (a->pid != b->pid)
21 return false;
22
23 if (a->fd < 0 || b->fd < 0)
24 return true;
25
26 /* pidfds live in their own pidfs and each process comes with a unique inode number since
27 * kernel 6.8. We can safely do this on older kernels too though, as previously anonymous
28 * inode was used and inode number was the same for all pidfds. */
29 r = fd_inode_same(a->fd, b->fd);
30 if (r < 0)
31 log_debug_errno(r, "Failed to check whether pidfds for pid " PID_FMT " are equal, assuming yes: %m",
32 a->pid);
33 return r != 0;
34 }
35
36 return !pidref_is_set(b);
37}
3bda3f17
LP
38
39int pidref_set_pid(PidRef *pidref, pid_t pid) {
40 int fd;
41
42 assert(pidref);
43
44 if (pid < 0)
45 return -ESRCH;
46 if (pid == 0)
47 pid = getpid_cached();
48
49 fd = pidfd_open(pid, 0);
50 if (fd < 0) {
51 /* Graceful fallback in case the kernel doesn't support pidfds or is out of fds */
52 if (!ERRNO_IS_NOT_SUPPORTED(errno) && !ERRNO_IS_PRIVILEGE(errno) && !ERRNO_IS_RESOURCE(errno))
53 return -errno;
54
55 fd = -EBADF;
56 }
57
58 *pidref = (PidRef) {
59 .fd = fd,
60 .pid = pid,
61 };
62
63 return 0;
64}
65
66int pidref_set_pidstr(PidRef *pidref, const char *pid) {
67 pid_t nr;
68 int r;
69
70 assert(pidref);
71
72 r = parse_pid(pid, &nr);
73 if (r < 0)
74 return r;
75
76 return pidref_set_pid(pidref, nr);
77}
78
79int pidref_set_pidfd(PidRef *pidref, int fd) {
80 int r;
81
82 assert(pidref);
83
84 if (fd < 0)
85 return -EBADF;
86
87 int fd_copy = fcntl(fd, F_DUPFD_CLOEXEC, 3);
88 if (fd_copy < 0) {
89 pid_t pid;
90
91 if (!ERRNO_IS_RESOURCE(errno))
92 return -errno;
93
94 /* Graceful fallback if we are out of fds */
95 r = pidfd_get_pid(fd, &pid);
96 if (r < 0)
97 return r;
98
dcfcea6d 99 *pidref = PIDREF_MAKE_FROM_PID(pid);
3bda3f17
LP
100 return 0;
101 }
102
103 return pidref_set_pidfd_consume(pidref, fd_copy);
104}
105
106int pidref_set_pidfd_take(PidRef *pidref, int fd) {
107 pid_t pid;
108 int r;
109
110 assert(pidref);
111
112 if (fd < 0)
113 return -EBADF;
114
115 r = pidfd_get_pid(fd, &pid);
116 if (r < 0)
117 return r;
118
119 *pidref = (PidRef) {
120 .fd = fd,
121 .pid = pid,
122 };
123
124 return 0;
125}
126
127int pidref_set_pidfd_consume(PidRef *pidref, int fd) {
128 int r;
129
130 r = pidref_set_pidfd_take(pidref, fd);
131 if (r < 0)
132 safe_close(fd);
133
134 return r;
135}
136
a1796e9b
LP
137int pidref_set_parent(PidRef *ret) {
138 _cleanup_(pidref_done) PidRef parent = PIDREF_NULL;
139 pid_t ppid;
140 int r;
141
142 assert(ret);
143
144 /* Acquires a pidref to our parent process. Deals with the fact that parent processes might exit, and
145 * we get reparented to other processes, with our old parent's PID already being recycled. */
146
147 ppid = getppid();
148 for (;;) {
149 r = pidref_set_pid(&parent, ppid);
150 if (r < 0)
151 return r;
152
153 if (parent.fd < 0) /* If pidfds are not available, then we are done */
154 break;
155
156 pid_t now_ppid = getppid();
157 if (now_ppid == ppid) /* If our ppid is still the same, then we are done */
158 break;
159
160 /* Otherwise let's try again with the new ppid */
161 ppid = now_ppid;
162 pidref_done(&parent);
163 }
164
165 *ret = TAKE_PIDREF(parent);
166 return 0;
167}
168
3bda3f17
LP
169void pidref_done(PidRef *pidref) {
170 assert(pidref);
171
172 *pidref = (PidRef) {
173 .fd = safe_close(pidref->fd),
174 };
175}
176
83765982
LP
177PidRef *pidref_free(PidRef *pidref) {
178 /* Regularly, this is an embedded structure. But sometimes we want it on the heap too */
179 if (!pidref)
180 return NULL;
181
182 pidref_done(pidref);
183 return mfree(pidref);
184}
185
232e6621 186int pidref_copy(const PidRef *pidref, PidRef *dest) {
83765982
LP
187 _cleanup_close_ int dup_fd = -EBADF;
188 pid_t dup_pid = 0;
189
232e6621 190 assert(dest);
83765982
LP
191
192 /* Allocates a new PidRef on the heap, making it a copy of the specified pidref. This does not try to
193 * acquire a pidfd if we don't have one yet!
194 *
195 * If NULL is passed we'll generate a PidRef that refers to no process. This makes it easy to copy
196 * pidref fields that might or might not reference a process yet. */
197
198 if (pidref) {
199 if (pidref->fd >= 0) {
200 dup_fd = fcntl(pidref->fd, F_DUPFD_CLOEXEC, 3);
201 if (dup_fd < 0) {
202 if (!ERRNO_IS_RESOURCE(errno))
203 return -errno;
204
205 dup_fd = -EBADF;
206 }
207 }
208
209 if (pidref->pid > 0)
210 dup_pid = pidref->pid;
211 }
212
232e6621 213 *dest = (PidRef) {
83765982
LP
214 .fd = TAKE_FD(dup_fd),
215 .pid = dup_pid,
216 };
217
232e6621
YW
218 return 0;
219}
220
221int pidref_dup(const PidRef *pidref, PidRef **ret) {
222 _cleanup_(pidref_freep) PidRef *dup_pidref = NULL;
223 int r;
224
225 assert(ret);
226
227 dup_pidref = newdup(PidRef, &PIDREF_NULL, 1);
228 if (!dup_pidref)
229 return -ENOMEM;
230
231 r = pidref_copy(pidref, dup_pidref);
232 if (r < 0)
233 return r;
234
83765982
LP
235 *ret = TAKE_PTR(dup_pidref);
236 return 0;
237}
238
239int pidref_new_from_pid(pid_t pid, PidRef **ret) {
74531a93 240 _cleanup_(pidref_freep) PidRef *n = NULL;
83765982
LP
241 int r;
242
243 assert(ret);
244
245 if (pid < 0)
246 return -ESRCH;
247
248 n = new(PidRef, 1);
249 if (!n)
250 return -ENOMEM;
251
252 *n = PIDREF_NULL;
253
254 r = pidref_set_pid(n, pid);
255 if (r < 0)
256 return r;
257
258 *ret = TAKE_PTR(n);
259 return 0;
260}
261
44c55e5a 262int pidref_kill(const PidRef *pidref, int sig) {
3bda3f17
LP
263
264 if (!pidref)
265 return -ESRCH;
266
267 if (pidref->fd >= 0)
268 return RET_NERRNO(pidfd_send_signal(pidref->fd, sig, NULL, 0));
269
270 if (pidref->pid > 0)
271 return RET_NERRNO(kill(pidref->pid, sig));
272
273 return -ESRCH;
274}
275
44c55e5a 276int pidref_kill_and_sigcont(const PidRef *pidref, int sig) {
3bda3f17
LP
277 int r;
278
279 r = pidref_kill(pidref, sig);
280 if (r < 0)
281 return r;
282
283 if (!IN_SET(sig, SIGCONT, SIGKILL))
284 (void) pidref_kill(pidref, SIGCONT);
285
286 return 0;
287}
a0d1659c 288
44c55e5a 289int pidref_sigqueue(const PidRef *pidref, int sig, int value) {
a0d1659c
LP
290
291 if (!pidref)
292 return -ESRCH;
293
294 if (pidref->fd >= 0) {
295 siginfo_t si;
296
297 /* We can't use structured initialization here, since the structure contains various unions
298 * and these fields lie in overlapping (carefully aligned) unions that LLVM is allergic to
299 * allow assignments to */
300 zero(si);
301 si.si_signo = sig;
302 si.si_code = SI_QUEUE;
303 si.si_pid = getpid_cached();
304 si.si_uid = getuid();
305 si.si_value.sival_int = value;
306
307 return RET_NERRNO(pidfd_send_signal(pidref->fd, sig, &si, 0));
308 }
309
310 if (pidref->pid > 0)
311 return RET_NERRNO(sigqueue(pidref->pid, sig, (const union sigval) { .sival_int = value }));
312
313 return -ESRCH;
314}
9cb7e49f 315
bd389293 316int pidref_verify(const PidRef *pidref) {
ec8dc835
LP
317 int r;
318
319 /* This is a helper that is supposed to be called after reading information from procfs via a
320 * PidRef. It ensures that the PID we track still matches the PIDFD we pin. If this value differs
321 * after a procfs read, we might have read the data from a recycled PID. */
322
323 if (!pidref_is_set(pidref))
324 return -ESRCH;
325
3d7ba61a
LP
326 if (pidref->pid == 1)
327 return 1; /* PID 1 can never go away, hence never be recycled to a different process → return 1 */
328
ec8dc835
LP
329 if (pidref->fd < 0)
330 return 0; /* If we don't have a pidfd we cannot validate it, hence we assume it's all OK → return 0 */
331
332 r = pidfd_verify_pid(pidref->fd, pidref->pid);
333 if (r < 0)
334 return r;
335
336 return 1; /* We have a pidfd and it still points to the PID we have, hence all is *really* OK → return 1 */
337}
338
a7a87769
LP
339bool pidref_is_self(const PidRef *pidref) {
340 if (!pidref)
341 return false;
342
343 return pidref->pid == getpid_cached();
344}
345
a3f32436
LP
346int pidref_wait(const PidRef *pidref, siginfo_t *ret, int options) {
347 int r;
348
349 if (!pidref_is_set(pidref))
350 return -ESRCH;
351
352 if (pidref->pid == 1 || pidref->pid == getpid_cached())
353 return -ECHILD;
354
355 siginfo_t si = {};
356
357 if (pidref->fd >= 0) {
358 r = RET_NERRNO(waitid(P_PIDFD, pidref->fd, &si, options));
359 if (r >= 0) {
360 if (ret)
361 *ret = si;
362 return r;
363 }
364 if (r != -EINVAL) /* P_PIDFD was added in kernel 5.4 only */
365 return r;
366 }
367
368 r = RET_NERRNO(waitid(P_PID, pidref->pid, &si, options));
369 if (r >= 0 && ret)
370 *ret = si;
371 return r;
372}
373
374int pidref_wait_for_terminate(const PidRef *pidref, siginfo_t *ret) {
375 int r;
376
377 for (;;) {
378 r = pidref_wait(pidref, ret, WEXITED);
379 if (r != -EINTR)
380 return r;
381 }
382}
383
9cb7e49f 384static void pidref_hash_func(const PidRef *pidref, struct siphash *state) {
c01a5c05 385 siphash24_compress_typesafe(pidref->pid, state);
9cb7e49f
LP
386}
387
388static int pidref_compare_func(const PidRef *a, const PidRef *b) {
389 return CMP(a->pid, b->pid);
390}
391
4c8d5f02
MY
392DEFINE_HASH_OPS(pidref_hash_ops, PidRef, pidref_hash_func, pidref_compare_func);
393
394DEFINE_HASH_OPS_WITH_KEY_DESTRUCTOR(pidref_hash_ops_free,
395 PidRef, pidref_hash_func, pidref_compare_func,
396 pidref_free);