-
Notifications
You must be signed in to change notification settings - Fork 17
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
port: add daemonization support code
This assists in writing reliable daemons, providing a close_range() that works on older glibc/kernel combinations, and a daemonize() which provides a synchronization pipe down which error messages can also be sent, so that errors occurring after daemonize() is called but before the daemon is fully working can be reliably reported. (This uses the check-header-symbol-rule feature just added to let us verify not only that close_range() is present in libc, but that it's present in the headers and has the right prototype. The history of close_range is a right tangled mess... as usual with glibc features, linking isn't required to work unless the header is included too, so we test both at once.) Signed-off-by: Nick Alcock <nick.alcock@oracle.com> Reviewed-by: Kris Van Hees <kris.van.hees@oracle.com>
- Loading branch information
1 parent
d2ea2f7
commit 64a37fb
Showing
5 changed files
with
269 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
/* | ||
* Oracle Linux DTrace; close a range of fds. | ||
* Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. | ||
* Licensed under the Universal Permissive License v 1.0 as shown at | ||
* http://oss.oracle.com/licenses/upl. | ||
*/ | ||
|
||
#include <sys/types.h> | ||
#include <sys/resource.h> | ||
#include <sys/syscall.h> | ||
#include <dirent.h> | ||
#include <errno.h> | ||
#include <stdlib.h> | ||
#include <unistd.h> | ||
|
||
/* | ||
* This is only used if glibc does not provide an implementation, in which case | ||
* glibc will deal with a kernel too old to implement close_range; but even if | ||
* it doesn't, if a sufficiently new kernel is in use, we should close all fds | ||
* using the syscall. | ||
*/ | ||
|
||
int | ||
close_range(unsigned int first, unsigned int last, unsigned int flags) | ||
{ | ||
DIR *fds; | ||
struct dirent *ent; | ||
|
||
#ifdef __NR_close_range | ||
int ret; | ||
|
||
ret = syscall(__NR_close_range, first, last, flags); | ||
if (ret >= 0) | ||
return ret; | ||
#endif | ||
|
||
fds = opendir("/proc/self/fd"); | ||
if (fds == NULL) { | ||
/* | ||
* No /proc/self/fd: fall back to just closing blindly. | ||
*/ | ||
struct rlimit nfiles; | ||
|
||
if (getrlimit(RLIMIT_NOFILE, &nfiles) < 0) | ||
return -1; /* errno is set for us. */ | ||
|
||
if (nfiles.rlim_max == 0) | ||
return 0; | ||
|
||
/* | ||
* Use rlim_max rather than rlim_cur because one can | ||
* lower rlim_cur after opening more than rlim_cur files, | ||
* leaving files numbered higher than the limit open. | ||
*/ | ||
if (last >= nfiles.rlim_max) | ||
last = nfiles.rlim_max - 1; | ||
|
||
while (first <= last) | ||
close(first++); | ||
|
||
return 0; | ||
} | ||
|
||
while (errno = 0, (ent = readdir(fds)) != NULL) { | ||
char *err; | ||
int fd; | ||
fd = strtol(ent->d_name, &err, 10); | ||
|
||
/* | ||
* Don't close garbage, no matter what. | ||
*/ | ||
if (*err != '\0') | ||
continue; | ||
|
||
if (fd < first || fd > last) | ||
continue; | ||
|
||
close(fd); | ||
} | ||
closedir(fds); | ||
return 0; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,168 @@ | ||
/* | ||
* Oracle Linux DTrace; become a daemon. | ||
* Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. | ||
* Licensed under the Universal Permissive License v 1.0 as shown at | ||
* http://oss.oracle.com/licenses/upl. | ||
*/ | ||
|
||
#include <sys/compiler.h> | ||
#include <sys/types.h> | ||
#include <sys/stat.h> | ||
#include <dirent.h> | ||
#include <errno.h> | ||
#include <fcntl.h> | ||
#include <signal.h> | ||
#include <stdio.h> | ||
#include <string.h> | ||
#include <unistd.h> | ||
|
||
#include <port.h> | ||
|
||
/* | ||
* Write an error return down the synchronization pipe. | ||
*/ | ||
_dt_noreturn_ | ||
void | ||
daemon_err(int fd, const char *err) | ||
{ | ||
/* | ||
* Not a paranoid write, no EINTR protection: all our errors are quite | ||
* short and are unlikely to hit EINTR. The read side, which might | ||
* block for some time, can make no such assumptions. | ||
*/ | ||
write(fd, err, strlen(err)); | ||
_exit(1); | ||
} | ||
|
||
/* | ||
* Write an error return featuring errno down the synchronization pipe. | ||
* | ||
* If fd is < 0, write to stderr instead. | ||
*/ | ||
_dt_noreturn_ | ||
void | ||
daemon_perr(int fd, const char *err, int err_no) | ||
{ | ||
char sep[] = ": "; | ||
char *errstr; | ||
|
||
/* | ||
* Not a paranoid write: see above. | ||
*/ | ||
if (fd >= 0) { | ||
write(fd, err, strlen(err)); | ||
if ((errstr = strerror(err_no)) != NULL) { | ||
write(fd, sep, strlen(sep)); | ||
write(fd, errstr, strlen(errstr)); | ||
} | ||
} else | ||
fprintf(stderr, "%s: %s\n", err, strerror(err_no)); | ||
|
||
_exit(1); | ||
} | ||
|
||
/* | ||
* On failure, returns -1 in the parent. | ||
* | ||
* On success, returns the fd of the synchronization pipe in the child: write | ||
* down it to indicate an error that should prevent daemonization, or close it | ||
* to indicate success. That error will then trigger a return in the parent. | ||
*/ | ||
|
||
int | ||
daemonize(int close_fds) | ||
{ | ||
size_t i; | ||
struct sigaction sa = { 0 }; | ||
sigset_t mask; | ||
int initialized[2]; | ||
|
||
if (close_fds) | ||
if (close_range(3, ~0U, 0) < 0) | ||
return -1; /* errno is set for us. */ | ||
|
||
if (pipe(initialized) < 0) | ||
return -1; /* errno is set for us. */ | ||
/* | ||
* Explicitly avoid checking for errors here -- not all signals can be | ||
* reset, and we can't do anything if reset fails anyway. | ||
*/ | ||
sa.sa_handler = SIG_DFL; | ||
for (i = 0; i < _NSIG; i++) | ||
(void) sigaction(i, &sa, NULL); | ||
|
||
sigemptyset(&mask); | ||
(void) sigprocmask(SIG_SETMASK, &mask, NULL); | ||
|
||
switch (fork()) { | ||
case -1: perror("cannot fork"); | ||
return -1; | ||
case 0: break; | ||
default: | ||
{ | ||
char errsync[1024]; | ||
char *errsyncp = errsync; | ||
int count = 0; | ||
|
||
memset(errsync, 0, sizeof(errsync)); | ||
close(initialized[1]); | ||
|
||
/* | ||
* Wait for successful daemonization of the child. If it | ||
* fails, print the message passed back as an error. | ||
* | ||
* Be very paranoid here: we know almost nothing about the state | ||
* of the child *or* ourselves. | ||
*/ | ||
for (errno = 0; (sizeof(errsync) - (errsyncp - errsync) >= 0) && | ||
(count > 0 || (count < 0 && errno == EINTR)); | ||
errno = 0) { | ||
|
||
count = read(initialized[0], errsyncp, | ||
sizeof(errsync) - (errsyncp - errsync)); | ||
|
||
if (count > 0) | ||
errsyncp += count; | ||
} | ||
|
||
close(initialized[0]); | ||
|
||
/* | ||
* Child successfully initialized: terminate parent. | ||
*/ | ||
if (errno == 0 && errsyncp == errsync) | ||
_exit(0); | ||
|
||
if (errno != 0) { | ||
perror("cannot synchronize with daemon: state unknown"); | ||
return -1; | ||
} | ||
|
||
fprintf(stderr, "%s\n", errsync); | ||
return -1; | ||
} | ||
} | ||
|
||
close(initialized[0]); | ||
(void) setsid(); | ||
|
||
switch (fork()) { | ||
case -1: daemon_perr(initialized[1], "cannot fork", errno); | ||
case 0: break; | ||
default: _exit(0); | ||
} | ||
|
||
close(0); | ||
if (open("/dev/null", O_RDWR) != 0) | ||
daemon_perr(initialized[1], "cannot open /dev/null", errno); | ||
|
||
if (dup2(0, 1) < 0 || | ||
dup2(0, 2) < 0) | ||
daemon_perr(initialized[1], "cannot dup2 standard fds", errno); | ||
|
||
umask(0); | ||
if (chdir("/") < 0) | ||
daemon_perr(initialized[1], "cannot chdir to /", errno); | ||
|
||
return initialized[1]; | ||
} |