[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