Skip to content

Commit

Permalink
port: add daemonization support code
Browse files Browse the repository at this point in the history
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
nickalcock authored and kvanhees committed Oct 27, 2022
1 parent d2ea2f7 commit 64a37fb
Show file tree
Hide file tree
Showing 5 changed files with 269 additions and 3 deletions.
1 change: 1 addition & 0 deletions Makeconfig
Original file line number Diff line number Diff line change
Expand Up @@ -72,3 +72,4 @@ $(eval $(call check-symbol-rule,ELF_GETSHDRSTRNDX,elf_getshdrstrndx,elf))
$(eval $(call check-symbol-rule,LIBCTF,ctf_open,ctf))
$(eval $(call check-symbol-rule,STRRSTR,strrstr,c))
$(eval $(call check-symbol-rule,WAITFD,waitfd,c))
$(eval $(call check-header-symbol-rule,CLOSE_RANGE,close_range(3,~0U,0),c,unistd))
13 changes: 12 additions & 1 deletion include/port.h
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/*
* Oracle Linux DTrace.
* Copyright (c) 2011, 2016, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2011, 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.
*/
Expand All @@ -10,6 +10,8 @@

#include <pthread.h>
#include <mutex.h>
#include <unistd.h>
#include <sys/compiler.h>
#include <sys/types.h>
#include <sys/dtrace_types.h>
#include <sys/ptrace.h>
Expand All @@ -26,6 +28,11 @@ int p_online(int cpun);

int mutex_init(mutex_t *m, int flags1, void *ptr);

int daemonize(int close_fds);

_dt_noreturn_ void daemon_err(int fd, const char *err);
_dt_noreturn_ void daemon_perr(int fd, const char *err, int err_no);

unsigned long linux_version_code(void);

#ifndef HAVE_ELF_GETSHDRSTRNDX
Expand All @@ -37,6 +44,10 @@ unsigned long linux_version_code(void);
int waitfd(int which, pid_t upid, int options, int flags);
#endif

#ifndef HAVE_CLOSE_RANGE
int close_range(unsigned int first, unsigned int last, unsigned int flags);
#endif

/*
* New open() flags not supported in OL6 glibc.
*/
Expand Down
8 changes: 6 additions & 2 deletions libport/Build
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Oracle Linux DTrace.
# Copyright (c) 2011, 2016, Oracle and/or its affiliates. All rights reserved.
# Copyright (c) 2011, 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.

Expand All @@ -8,6 +8,10 @@ LIBS += libport

libport_TARGET = libport
libport_DIR := $(current-dir)
libport_SOURCES = gmatch.c linux_version_code.c strlcat.c strlcpy.c p_online.c time.c $(ARCHINC)/waitfd.c
ifdef HAVE_CLOSE_RANGE
libport_SOURCES = gmatch.c linux_version_code.c strlcat.c strlcpy.c p_online.c time.c daemonize.c $(ARCHINC)/waitfd.c
else
libport_SOURCES = gmatch.c linux_version_code.c strlcat.c strlcpy.c p_online.c time.c daemonize.c close_range.c $(ARCHINC)/waitfd.c
endif
libport_LIBSOURCES := libport
libport_CPPFLAGS := -Ilibdtrace
82 changes: 82 additions & 0 deletions libport/close_range.c
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;
}
168 changes: 168 additions & 0 deletions libport/daemonize.c
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];
}

0 comments on commit 64a37fb

Please sign in to comment.