[DTrace-devel] [PATCH v4 10/25] port: add daemonization support code
Kris Van Hees
kris.van.hees at oracle.com
Sat Oct 8 03:19:57 UTC 2022
On Fri, Oct 07, 2022 at 11:25:09AM +0100, Nick Alcock via DTrace-devel wrote:
> 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 at oracle.com>
Reviewed-by: Kris Van Hees <kris.van.hees at oracle.com>
> ---
> Makeconfig | 1 +
> include/port.h | 13 +++-
> libport/Build | 8 +-
> libport/close_range.c | 82 +++++++++++++++++++++
> libport/daemonize.c | 168 ++++++++++++++++++++++++++++++++++++++++++
> 5 files changed, 269 insertions(+), 3 deletions(-)
> create mode 100644 libport/close_range.c
> create mode 100644 libport/daemonize.c
>
> diff --git a/Makeconfig b/Makeconfig
> index fd2645f9ed10..52d72661f383 100644
> --- a/Makeconfig
> +++ b/Makeconfig
> @@ -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))
> diff --git a/include/port.h b/include/port.h
> index bdf181be4501..50f818717aa6 100644
> --- a/include/port.h
> +++ b/include/port.h
> @@ -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.
> */
> @@ -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>
> @@ -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
> @@ -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.
> */
> diff --git a/libport/Build b/libport/Build
> index c644faad762f..1b4fca0c52dd 100644
> --- a/libport/Build
> +++ b/libport/Build
> @@ -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.
>
> @@ -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
> diff --git a/libport/close_range.c b/libport/close_range.c
> new file mode 100644
> index 000000000000..2e6928f94ea0
> --- /dev/null
> +++ b/libport/close_range.c
> @@ -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;
> +}
> diff --git a/libport/daemonize.c b/libport/daemonize.c
> new file mode 100644
> index 000000000000..f8ac0f1b43ca
> --- /dev/null
> +++ b/libport/daemonize.c
> @@ -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];
> +}
> --
> 2.35.1
>
>
> _______________________________________________
> DTrace-devel mailing list
> DTrace-devel at oss.oracle.com
> https://oss.oracle.com/mailman/listinfo/dtrace-devel
More information about the DTrace-devel
mailing list