[DTrace-devel] [PATCH v4 10/25] port: add daemonization support code

Nick Alcock nick.alcock at oracle.com
Fri Oct 7 10:25:09 UTC 2022


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>
---
 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




More information about the DTrace-devel mailing list