[DTrace-devel] [PATCH v2 09/20] port: add daemonization support code

Nick Alcock nick.alcock at oracle.com
Wed Sep 7 12:59:57 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 needs a bit of extra Makeconfig support code 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...)

Signed-off-by: Nick Alcock <nick.alcock at oracle.com>
---
 Makeconfig            |  26 ++++++-
 include/port.h        |  13 +++-
 libport/Build         |   8 +-
 libport/close_range.c |  82 +++++++++++++++++++++
 libport/daemonize.c   | 168 ++++++++++++++++++++++++++++++++++++++++++
 5 files changed, 293 insertions(+), 4 deletions(-)
 create mode 100644 libport/close_range.c
 create mode 100644 libport/daemonize.c

diff --git a/Makeconfig b/Makeconfig
index 28761a4c45ce..52d72661f383 100644
--- a/Makeconfig
+++ b/Makeconfig
@@ -1,7 +1,7 @@
 # Determine properties of the system and write a config.h.
 #
 # Oracle Linux DTrace.
-# Copyright (c) 2011, 2013, 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.
 
@@ -33,6 +33,29 @@ $(CONFIG_H): $(objdir)/.config/config.$(1).h
 $(CONFIG_MK): $(objdir)/.config/config.$(1).mk
 endef
 
+# Generate a makefile rule to check for the presence of SYMBOL
+# in HEADER and LIBRARY and emit an appropriate header file
+# fragment into a file under $(objdir)/.config.
+#
+# The first argument must be suitable for a filename fragment,
+# for a makefile rule name and for a #define.
+#
+# Syntax: $(call check-header-symbol-rule,name,symbol,library,header)
+define check-header-symbol-rule
+$(objdir)/.config/config.$(1).h $(objdir)/.config/config.$(1).mk: $(objdir)/.config/.dir.stamp
+	if printf '#include <%s.h>\nint main(void) { %s; }' "$(4)" "$(2)" | \
+		$(CC) $(filter-out --coverage,$(CFLAGS) $(LDFLAGS)) -D_GNU_SOURCE -Werror=implicit-function-declaration -o /dev/null -x c - -l$(3) >/dev/null 2>&1; then \
+	    echo '#define HAVE_$(1) 1' > $(objdir)/.config/config.$(1).h; \
+	    echo 'HAVE_$(1)=y' > $(objdir)/.config/config.$(1).mk; \
+	else \
+	    echo '/* #undef HAVE_$(1) */' > $(objdir)/.config/config.$(1).h; \
+	    echo '# HAVE_$(1) undefined' > $(objdir)/.config/config.$(1).mk; \
+	fi
+
+$(CONFIG_H): $(objdir)/.config/config.$(1).h
+$(CONFIG_MK): $(objdir)/.config/config.$(1).mk
+endef
+
 $(objdir)/.config/.dir.stamp:
 	mkdir -p $(objdir)/.config
 	touch $(objdir)/.config/.dir.stamp
@@ -49,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.37.1.265.g363c192786.dirty




More information about the DTrace-devel mailing list