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