]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/libsystemd/sd-daemon/sd-daemon.c
license: LGPL-2.1+ -> LGPL-2.1-or-later
[thirdparty/systemd.git] / src / libsystemd / sd-daemon / sd-daemon.c
CommitLineData
db9ecf05 1/* SPDX-License-Identifier: LGPL-2.1-or-later */
8c47c732 2
abbbea81 3#include <errno.h>
916abb21 4#include <limits.h>
0ebee881 5#include <mqueue.h>
8dd4c05b 6#include <netinet/in.h>
4f07ddfa 7#include <poll.h>
8dd4c05b
LP
8#include <stdarg.h>
9#include <stddef.h>
10#include <stdio.h>
11#include <stdlib.h>
8dd4c05b
LP
12#include <sys/stat.h>
13#include <sys/un.h>
14#include <unistd.h>
abbbea81 15
07630cea
LP
16#include "sd-daemon.h"
17
b5efdb8a 18#include "alloc-util.h"
3ffd4af2 19#include "fd-util.h"
f4f15635 20#include "fs-util.h"
cb310866 21#include "io-util.h"
6bedfcbb 22#include "parse-util.h"
be8f4e9e 23#include "path-util.h"
dccca82b 24#include "process-util.h"
3cb46740 25#include "socket-util.h"
8dd4c05b 26#include "strv.h"
4f07ddfa 27#include "time-util.h"
8dd4c05b
LP
28#include "util.h"
29
a47806fa
LP
30#define SNDBUF_SIZE (8*1024*1024)
31
8dd4c05b
LP
32static void unsetenv_all(bool unset_environment) {
33
34 if (!unset_environment)
35 return;
36
37 unsetenv("LISTEN_PID");
38 unsetenv("LISTEN_FDS");
39 unsetenv("LISTEN_FDNAMES");
40}
41
0ebee881 42_public_ int sd_listen_fds(int unset_environment) {
abbbea81 43 const char *e;
046c93f8 44 int n, r, fd;
be8f4e9e 45 pid_t pid;
abbbea81 46
50425d16
MS
47 e = getenv("LISTEN_PID");
48 if (!e) {
abbbea81
LP
49 r = 0;
50 goto finish;
51 }
52
be8f4e9e
LP
53 r = parse_pid(e, &pid);
54 if (r < 0)
abbbea81 55 goto finish;
abbbea81
LP
56
57 /* Is this for us? */
df0ff127 58 if (getpid_cached() != pid) {
abbbea81
LP
59 r = 0;
60 goto finish;
61 }
62
50425d16
MS
63 e = getenv("LISTEN_FDS");
64 if (!e) {
abbbea81
LP
65 r = 0;
66 goto finish;
67 }
68
046c93f8 69 r = safe_atoi(e, &n);
be8f4e9e 70 if (r < 0)
abbbea81 71 goto finish;
8640e111 72
046c93f8
VC
73 assert_cc(SD_LISTEN_FDS_START < INT_MAX);
74 if (n <= 0 || n > INT_MAX - SD_LISTEN_FDS_START) {
75 r = -EINVAL;
76 goto finish;
77 }
78
79 for (fd = SD_LISTEN_FDS_START; fd < SD_LISTEN_FDS_START + n; fd ++) {
be8f4e9e
LP
80 r = fd_cloexec(fd, true);
81 if (r < 0)
8640e111 82 goto finish;
8640e111
LP
83 }
84
046c93f8 85 r = n;
abbbea81
LP
86
87finish:
8dd4c05b
LP
88 unsetenv_all(unset_environment);
89 return r;
90}
91
92_public_ int sd_listen_fds_with_names(int unset_environment, char ***names) {
93 _cleanup_strv_free_ char **l = NULL;
94 bool have_names;
95 int n_names = 0, n_fds;
96 const char *e;
97 int r;
98
99 if (!names)
100 return sd_listen_fds(unset_environment);
101
102 e = getenv("LISTEN_FDNAMES");
103 if (e) {
90e30d76 104 n_names = strv_split_full(&l, e, ":", EXTRACT_DONT_COALESCE_SEPARATORS);
8dd4c05b
LP
105 if (n_names < 0) {
106 unsetenv_all(unset_environment);
107 return n_names;
108 }
109
110 have_names = true;
111 } else
112 have_names = false;
113
114 n_fds = sd_listen_fds(unset_environment);
115 if (n_fds <= 0)
116 return n_fds;
117
118 if (have_names) {
119 if (n_names != n_fds)
120 return -EINVAL;
121 } else {
122 r = strv_extend_n(&l, "unknown", n_fds);
123 if (r < 0)
124 return r;
abbbea81
LP
125 }
126
ae2a15bc 127 *names = TAKE_PTR(l);
8dd4c05b
LP
128
129 return n_fds;
abbbea81 130}
7c394faa 131
0ebee881 132_public_ int sd_is_fifo(int fd, const char *path) {
7c394faa
LP
133 struct stat st_fd;
134
e6803801 135 assert_return(fd >= 0, -EBADF);
7c394faa 136
7c394faa
LP
137 if (fstat(fd, &st_fd) < 0)
138 return -errno;
139
140 if (!S_ISFIFO(st_fd.st_mode))
141 return 0;
142
143 if (path) {
144 struct stat st_path;
145
fd8bccfb 146 if (stat(path, &st_path) < 0) {
7c394faa 147
945c2931 148 if (IN_SET(errno, ENOENT, ENOTDIR))
7c394faa
LP
149 return 0;
150
151 return -errno;
152 }
153
154 return
155 st_path.st_dev == st_fd.st_dev &&
156 st_path.st_ino == st_fd.st_ino;
157 }
158
159 return 1;
160}
161
0ebee881 162_public_ int sd_is_special(int fd, const char *path) {
4160ec67
WD
163 struct stat st_fd;
164
e6803801 165 assert_return(fd >= 0, -EBADF);
4160ec67
WD
166
167 if (fstat(fd, &st_fd) < 0)
168 return -errno;
169
170 if (!S_ISREG(st_fd.st_mode) && !S_ISCHR(st_fd.st_mode))
171 return 0;
172
173 if (path) {
174 struct stat st_path;
175
176 if (stat(path, &st_path) < 0) {
177
945c2931 178 if (IN_SET(errno, ENOENT, ENOTDIR))
4160ec67
WD
179 return 0;
180
181 return -errno;
182 }
183
184 if (S_ISREG(st_fd.st_mode) && S_ISREG(st_path.st_mode))
185 return
186 st_path.st_dev == st_fd.st_dev &&
187 st_path.st_ino == st_fd.st_ino;
188 else if (S_ISCHR(st_fd.st_mode) && S_ISCHR(st_path.st_mode))
189 return st_path.st_rdev == st_fd.st_rdev;
190 else
191 return 0;
192 }
193
194 return 1;
195}
196
e6a3081a 197static int sd_is_socket_internal(int fd, int type, int listening) {
7c394faa
LP
198 struct stat st_fd;
199
e6803801 200 assert_return(fd >= 0, -EBADF);
be8f4e9e 201 assert_return(type >= 0, -EINVAL);
7c394faa
LP
202
203 if (fstat(fd, &st_fd) < 0)
204 return -errno;
205
206 if (!S_ISSOCK(st_fd.st_mode))
207 return 0;
208
209 if (type != 0) {
210 int other_type = 0;
211 socklen_t l = sizeof(other_type);
212
213 if (getsockopt(fd, SOL_SOCKET, SO_TYPE, &other_type, &l) < 0)
214 return -errno;
215
216 if (l != sizeof(other_type))
217 return -EINVAL;
218
219 if (other_type != type)
220 return 0;
221 }
222
223 if (listening >= 0) {
224 int accepting = 0;
225 socklen_t l = sizeof(accepting);
226
227 if (getsockopt(fd, SOL_SOCKET, SO_ACCEPTCONN, &accepting, &l) < 0)
228 return -errno;
229
230 if (l != sizeof(accepting))
231 return -EINVAL;
232
dde770cf 233 if (!accepting != !listening)
7c394faa
LP
234 return 0;
235 }
236
237 return 1;
238}
239
0ebee881 240_public_ int sd_is_socket(int fd, int family, int type, int listening) {
88ce42f6
LP
241 int r;
242
e6803801 243 assert_return(fd >= 0, -EBADF);
be8f4e9e 244 assert_return(family >= 0, -EINVAL);
88ce42f6 245
50425d16
MS
246 r = sd_is_socket_internal(fd, type, listening);
247 if (r <= 0)
88ce42f6
LP
248 return r;
249
250 if (family > 0) {
1c633045
ZJS
251 union sockaddr_union sockaddr = {};
252 socklen_t l = sizeof(sockaddr);
88ce42f6
LP
253
254 if (getsockname(fd, &sockaddr.sa, &l) < 0)
255 return -errno;
256
b7f42664 257 if (l < sizeof(sa_family_t))
88ce42f6
LP
258 return -EINVAL;
259
260 return sockaddr.sa.sa_family == family;
261 }
262
263 return 1;
264}
265
0ebee881 266_public_ int sd_is_socket_inet(int fd, int family, int type, int listening, uint16_t port) {
1c633045
ZJS
267 union sockaddr_union sockaddr = {};
268 socklen_t l = sizeof(sockaddr);
7c394faa
LP
269 int r;
270
e6803801 271 assert_return(fd >= 0, -EBADF);
be8f4e9e 272 assert_return(IN_SET(family, 0, AF_INET, AF_INET6), -EINVAL);
88ce42f6 273
50425d16
MS
274 r = sd_is_socket_internal(fd, type, listening);
275 if (r <= 0)
7c394faa
LP
276 return r;
277
7c394faa
LP
278 if (getsockname(fd, &sockaddr.sa, &l) < 0)
279 return -errno;
280
b7f42664 281 if (l < sizeof(sa_family_t))
7c394faa
LP
282 return -EINVAL;
283
945c2931 284 if (!IN_SET(sockaddr.sa.sa_family, AF_INET, AF_INET6))
7c394faa
LP
285 return 0;
286
be8f4e9e 287 if (family != 0)
88ce42f6
LP
288 if (sockaddr.sa.sa_family != family)
289 return 0;
290
7c394faa 291 if (port > 0) {
dfde7e8c 292 unsigned sa_port;
7c394faa 293
dfde7e8c
LP
294 r = sockaddr_port(&sockaddr.sa, &sa_port);
295 if (r < 0)
296 return r;
7c394faa 297
dfde7e8c 298 return port == sa_port;
7c394faa
LP
299 }
300
301 return 1;
302}
303
f6f372d2
ZJS
304_public_ int sd_is_socket_sockaddr(int fd, int type, const struct sockaddr* addr, unsigned addr_len, int listening) {
305 union sockaddr_union sockaddr = {};
306 socklen_t l = sizeof(sockaddr);
307 int r;
308
309 assert_return(fd >= 0, -EBADF);
310 assert_return(addr, -EINVAL);
311 assert_return(addr_len >= sizeof(sa_family_t), -ENOBUFS);
312 assert_return(IN_SET(addr->sa_family, AF_INET, AF_INET6), -EPFNOSUPPORT);
313
314 r = sd_is_socket_internal(fd, type, listening);
315 if (r <= 0)
316 return r;
317
318 if (getsockname(fd, &sockaddr.sa, &l) < 0)
319 return -errno;
320
321 if (l < sizeof(sa_family_t))
322 return -EINVAL;
323
324 if (sockaddr.sa.sa_family != addr->sa_family)
325 return 0;
326
327 if (sockaddr.sa.sa_family == AF_INET) {
328 const struct sockaddr_in *in = (const struct sockaddr_in *) addr;
329
330 if (l < sizeof(struct sockaddr_in) || addr_len < sizeof(struct sockaddr_in))
331 return -EINVAL;
332
333 if (in->sin_port != 0 &&
334 sockaddr.in.sin_port != in->sin_port)
335 return false;
336
337 return sockaddr.in.sin_addr.s_addr == in->sin_addr.s_addr;
338
339 } else {
340 const struct sockaddr_in6 *in = (const struct sockaddr_in6 *) addr;
341
342 if (l < sizeof(struct sockaddr_in6) || addr_len < sizeof(struct sockaddr_in6))
343 return -EINVAL;
344
345 if (in->sin6_port != 0 &&
346 sockaddr.in6.sin6_port != in->sin6_port)
347 return false;
348
349 if (in->sin6_flowinfo != 0 &&
350 sockaddr.in6.sin6_flowinfo != in->sin6_flowinfo)
351 return false;
352
353 if (in->sin6_scope_id != 0 &&
354 sockaddr.in6.sin6_scope_id != in->sin6_scope_id)
355 return false;
356
357 return memcmp(sockaddr.in6.sin6_addr.s6_addr, in->sin6_addr.s6_addr,
358 sizeof(in->sin6_addr.s6_addr)) == 0;
359 }
360}
361
0ebee881 362_public_ int sd_is_socket_unix(int fd, int type, int listening, const char *path, size_t length) {
1c633045
ZJS
363 union sockaddr_union sockaddr = {};
364 socklen_t l = sizeof(sockaddr);
7c394faa
LP
365 int r;
366
e6803801 367 assert_return(fd >= 0, -EBADF);
be8f4e9e 368
50425d16
MS
369 r = sd_is_socket_internal(fd, type, listening);
370 if (r <= 0)
7c394faa
LP
371 return r;
372
7c394faa
LP
373 if (getsockname(fd, &sockaddr.sa, &l) < 0)
374 return -errno;
375
b7f42664 376 if (l < sizeof(sa_family_t))
7c394faa
LP
377 return -EINVAL;
378
379 if (sockaddr.sa.sa_family != AF_UNIX)
380 return 0;
381
382 if (path) {
d1d7caee 383 if (length == 0)
7c394faa
LP
384 length = strlen(path);
385
d1d7caee 386 if (length == 0)
7c394faa 387 /* Unnamed socket */
0e098b15 388 return l == offsetof(struct sockaddr_un, sun_path);
7c394faa 389
7c394faa
LP
390 if (path[0])
391 /* Normal path socket */
cd250a39 392 return
0e098b15 393 (l >= offsetof(struct sockaddr_un, sun_path) + length + 1) &&
cd250a39 394 memcmp(path, sockaddr.un.sun_path, length+1) == 0;
7c394faa
LP
395 else
396 /* Abstract namespace socket */
cd250a39 397 return
0e098b15 398 (l == offsetof(struct sockaddr_un, sun_path) + length) &&
cd250a39 399 memcmp(path, sockaddr.un.sun_path, length) == 0;
7c394faa
LP
400 }
401
402 return 1;
403}
8c47c732 404
0ebee881 405_public_ int sd_is_mq(int fd, const char *path) {
916abb21
LP
406 struct mq_attr attr;
407
0260d1d5
ZJS
408 /* Check that the fd is valid */
409 assert_return(fcntl(fd, F_GETFD) >= 0, -errno);
916abb21 410
0260d1d5
ZJS
411 if (mq_getattr(fd, &attr) < 0) {
412 if (errno == EBADF)
413 /* A non-mq fd (or an invalid one, but we ruled that out above) */
414 return 0;
916abb21 415 return -errno;
0260d1d5 416 }
916abb21
LP
417
418 if (path) {
419 char fpath[PATH_MAX];
420 struct stat a, b;
421
be8f4e9e 422 assert_return(path_is_absolute(path), -EINVAL);
916abb21
LP
423
424 if (fstat(fd, &a) < 0)
425 return -errno;
426
427 strncpy(stpcpy(fpath, "/dev/mqueue"), path, sizeof(fpath) - 12);
428 fpath[sizeof(fpath)-1] = 0;
429
430 if (stat(fpath, &b) < 0)
431 return -errno;
432
433 if (a.st_dev != b.st_dev ||
434 a.st_ino != b.st_ino)
435 return 0;
436 }
437
438 return 1;
916abb21
LP
439}
440
9e1d021e
LP
441_public_ int sd_pid_notify_with_fds(
442 pid_t pid,
443 int unset_environment,
444 const char *state,
445 const int *fds,
446 unsigned n_fds) {
447
f36a9d59 448 union sockaddr_union sockaddr;
cb310866 449 struct iovec iovec;
a354329f
LP
450 struct msghdr msghdr = {
451 .msg_iov = &iovec,
452 .msg_iovlen = 1,
453 .msg_name = &sockaddr,
454 };
a354329f
LP
455 _cleanup_close_ int fd = -1;
456 struct cmsghdr *cmsg = NULL;
457 const char *e;
9e1d021e 458 bool send_ucred;
f36a9d59 459 int r;
8c47c732
LP
460
461 if (!state) {
462 r = -EINVAL;
463 goto finish;
464 }
465
a354329f
LP
466 if (n_fds > 0 && !fds) {
467 r = -EINVAL;
468 goto finish;
469 }
470
50425d16
MS
471 e = getenv("NOTIFY_SOCKET");
472 if (!e)
08bfb810 473 return 0;
8c47c732 474
f36a9d59
ZJS
475 r = sockaddr_un_set_path(&sockaddr.un, e);
476 if (r < 0)
638b56cd 477 goto finish;
f36a9d59 478 msghdr.msg_namelen = r;
638b56cd 479
50425d16
MS
480 fd = socket(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC, 0);
481 if (fd < 0) {
8c47c732
LP
482 r = -errno;
483 goto finish;
484 }
485
9e1d021e 486 (void) fd_inc_sndbuf(fd, SNDBUF_SIZE);
a47806fa 487
cb310866 488 iovec = IOVEC_MAKE_STRING(state);
a013bd94 489
9e1d021e
LP
490 send_ucred =
491 (pid != 0 && pid != getpid_cached()) ||
492 getuid() != geteuid() ||
493 getgid() != getegid();
d4a144fa 494
9e1d021e 495 if (n_fds > 0 || send_ucred) {
96d49011 496 /* CMSG_SPACE(0) may return value different than zero, which results in miscalculated controllen. */
c463a6f1
LP
497 msghdr.msg_controllen =
498 (n_fds > 0 ? CMSG_SPACE(sizeof(int) * n_fds) : 0) +
9e1d021e 499 (send_ucred ? CMSG_SPACE(sizeof(struct ucred)) : 0);
c463a6f1 500
40f44238 501 msghdr.msg_control = alloca0(msghdr.msg_controllen);
a354329f
LP
502
503 cmsg = CMSG_FIRSTHDR(&msghdr);
64144440
ZJS
504 if (n_fds > 0) {
505 cmsg->cmsg_level = SOL_SOCKET;
506 cmsg->cmsg_type = SCM_RIGHTS;
507 cmsg->cmsg_len = CMSG_LEN(sizeof(int) * n_fds);
a354329f 508
64144440 509 memcpy(CMSG_DATA(cmsg), fds, sizeof(int) * n_fds);
be8f4e9e 510
9e1d021e 511 if (send_ucred)
64144440
ZJS
512 assert_se(cmsg = CMSG_NXTHDR(&msghdr, cmsg));
513 }
a354329f 514
9e1d021e 515 if (send_ucred) {
64144440 516 struct ucred *ucred;
be8f4e9e 517
64144440
ZJS
518 cmsg->cmsg_level = SOL_SOCKET;
519 cmsg->cmsg_type = SCM_CREDENTIALS;
520 cmsg->cmsg_len = CMSG_LEN(sizeof(struct ucred));
be8f4e9e 521
64144440 522 ucred = (struct ucred*) CMSG_DATA(cmsg);
9e1d021e 523 ucred->pid = pid != 0 ? pid : getpid_cached();
64144440
ZJS
524 ucred->uid = getuid();
525 ucred->gid = getgid();
526 }
be8f4e9e
LP
527 }
528
529 /* First try with fake ucred data, as requested */
530 if (sendmsg(fd, &msghdr, MSG_NOSIGNAL) >= 0) {
531 r = 1;
8c47c732
LP
532 goto finish;
533 }
534
a354329f 535 /* If that failed, try with our own ucred instead */
9e1d021e 536 if (send_ucred) {
64144440
ZJS
537 msghdr.msg_controllen -= CMSG_SPACE(sizeof(struct ucred));
538 if (msghdr.msg_controllen == 0)
a354329f 539 msghdr.msg_control = NULL;
be8f4e9e
LP
540
541 if (sendmsg(fd, &msghdr, MSG_NOSIGNAL) >= 0) {
542 r = 1;
543 goto finish;
544 }
545 }
546
547 r = -errno;
8c47c732
LP
548
549finish:
550 if (unset_environment)
551 unsetenv("NOTIFY_SOCKET");
552
8c47c732 553 return r;
8c47c732
LP
554}
555
4f07ddfa
KKD
556_public_ int sd_notify_barrier(int unset_environment, uint64_t timeout) {
557 _cleanup_close_pair_ int pipe_fd[2] = { -1, -1 };
4f07ddfa
KKD
558 int r;
559
560 if (pipe2(pipe_fd, O_CLOEXEC) < 0)
561 return -errno;
562
563 r = sd_pid_notify_with_fds(0, unset_environment, "BARRIER=1", &pipe_fd[1], 1);
564 if (r <= 0)
565 return r;
566
567 pipe_fd[1] = safe_close(pipe_fd[1]);
568
0f2d351f 569 r = fd_wait_for_event(pipe_fd[0], 0 /* POLLHUP is implicit */, timeout);
4f07ddfa 570 if (r < 0)
0f2d351f 571 return r;
4f07ddfa
KKD
572 if (r == 0)
573 return -ETIMEDOUT;
574
575 return 1;
576}
577
a354329f
LP
578_public_ int sd_pid_notify(pid_t pid, int unset_environment, const char *state) {
579 return sd_pid_notify_with_fds(pid, unset_environment, state, NULL, 0);
580}
581
be8f4e9e 582_public_ int sd_notify(int unset_environment, const char *state) {
a354329f 583 return sd_pid_notify_with_fds(0, unset_environment, state, NULL, 0);
be8f4e9e
LP
584}
585
586_public_ int sd_pid_notifyf(pid_t pid, int unset_environment, const char *format, ...) {
587 _cleanup_free_ char *p = NULL;
588 int r;
589
590 if (format) {
591 va_list ap;
592
593 va_start(ap, format);
594 r = vasprintf(&p, format, ap);
595 va_end(ap);
596
597 if (r < 0 || !p)
598 return -ENOMEM;
599 }
600
601 return sd_pid_notify(pid, unset_environment, p);
602}
603
0ebee881 604_public_ int sd_notifyf(int unset_environment, const char *format, ...) {
be8f4e9e 605 _cleanup_free_ char *p = NULL;
8c47c732
LP
606 int r;
607
be8f4e9e
LP
608 if (format) {
609 va_list ap;
8c47c732 610
be8f4e9e
LP
611 va_start(ap, format);
612 r = vasprintf(&p, format, ap);
613 va_end(ap);
8c47c732 614
be8f4e9e
LP
615 if (r < 0 || !p)
616 return -ENOMEM;
617 }
8c47c732 618
be8f4e9e 619 return sd_pid_notify(0, unset_environment, p);
8c47c732 620}
40473a70 621
0ebee881 622_public_ int sd_booted(void) {
66e41181
LP
623 /* We test whether the runtime unit file directory has been
624 * created. This takes place in mount-setup.c, so is
625 * guaranteed to happen very early during boot. */
40473a70 626
21042737
YW
627 if (laccess("/run/systemd/system/", F_OK) >= 0)
628 return true;
629
630 if (errno == ENOENT)
631 return false;
632
633 return -errno;
40473a70 634}
09812eb7 635
0ebee881 636_public_ int sd_watchdog_enabled(int unset_environment, uint64_t *usec) {
a9becdd6 637 const char *s, *p = ""; /* p is set to dummy value to do unsetting */
be8f4e9e 638 uint64_t u;
a9becdd6 639 int r = 0;
09812eb7 640
a9becdd6
ZJS
641 s = getenv("WATCHDOG_USEC");
642 if (!s)
09812eb7 643 goto finish;
09812eb7 644
a9becdd6 645 r = safe_atou64(s, &u);
be8f4e9e 646 if (r < 0)
09812eb7 647 goto finish;
caffe412 648 if (u <= 0 || u >= USEC_INFINITY) {
09812eb7
LP
649 r = -EINVAL;
650 goto finish;
651 }
652
a9becdd6
ZJS
653 p = getenv("WATCHDOG_PID");
654 if (p) {
655 pid_t pid;
656
657 r = parse_pid(p, &pid);
658 if (r < 0)
659 goto finish;
660
661 /* Is this for us? */
df0ff127 662 if (getpid_cached() != pid) {
a9becdd6
ZJS
663 r = 0;
664 goto finish;
665 }
09812eb7
LP
666 }
667
668 if (usec)
be8f4e9e 669 *usec = u;
09812eb7
LP
670
671 r = 1;
672
673finish:
a9becdd6 674 if (unset_environment && s)
09812eb7 675 unsetenv("WATCHDOG_USEC");
a9becdd6
ZJS
676 if (unset_environment && p)
677 unsetenv("WATCHDOG_PID");
09812eb7
LP
678
679 return r;
09812eb7 680}