aboutsummaryrefslogtreecommitdiffstats
path: root/cmd
diff options
context:
space:
mode:
Diffstat (limited to 'cmd')
-rw-r--r--cmd/zed/zed.c93
-rw-r--r--cmd/zed/zed_conf.c8
-rw-r--r--cmd/zed/zed_log.c95
-rw-r--r--cmd/zed/zed_log.h8
4 files changed, 183 insertions, 21 deletions
diff --git a/cmd/zed/zed.c b/cmd/zed/zed.c
index c54a59b0a..bf29c0486 100644
--- a/cmd/zed/zed.c
+++ b/cmd/zed/zed.c
@@ -93,6 +93,9 @@ _setup_sig_handlers(void)
* Lock all current and future pages in the virtual memory address space.
* Access to locked pages will never be delayed by a page fault.
* EAGAIN is tested up to max_tries in case this is a transient error.
+ * Note that memory locks are not inherited by a child created via fork()
+ * and are automatically removed during an execve(). As such, this must
+ * be called after the daemon fork()s (when running in the background).
*/
static void
_lock_memory(void)
@@ -117,25 +120,57 @@ _lock_memory(void)
}
/*
- * Transform the process into a daemon.
+ * Start daemonization of the process including the double fork().
+ * The parent process will block here until _finish_daemonize() is called
+ * (in the grandchild process), at which point the parent process will exit.
+ * This prevents the parent process from exiting until initialization is
+ * complete.
*/
static void
-_become_daemon(void)
+_start_daemonize(void)
{
pid_t pid;
- int fd;
+ struct sigaction sa;
+
+ /* Create pipe for communicating with child during daemonization. */
+ zed_log_pipe_open();
+ /* Background process and ensure child is not process group leader. */
pid = fork();
if (pid < 0) {
zed_log_die("Failed to create child process: %s",
strerror(errno));
} else if (pid > 0) {
+
+ /* Close writes since parent will only read from pipe. */
+ zed_log_pipe_close_writes();
+
+ /* Wait for notification that daemonization is complete. */
+ zed_log_pipe_wait();
+
+ zed_log_pipe_close_reads();
_exit(EXIT_SUCCESS);
}
+
+ /* Close reads since child will only write to pipe. */
+ zed_log_pipe_close_reads();
+
+ /* Create independent session and detach from terminal. */
if (setsid() < 0)
zed_log_die("Failed to create new session: %s",
strerror(errno));
+ /* Prevent child from terminating on HUP when session leader exits. */
+ if (sigemptyset(&sa.sa_mask) < 0)
+ zed_log_die("Failed to initialize sigset");
+
+ sa.sa_flags = 0;
+ sa.sa_handler = SIG_IGN;
+
+ if (sigaction(SIGHUP, &sa, NULL) < 0)
+ zed_log_die("Failed to ignore SIGHUP");
+
+ /* Ensure process cannot re-acquire terminal. */
pid = fork();
if (pid < 0) {
zed_log_die("Failed to create grandchild process: %s",
@@ -143,25 +178,40 @@ _become_daemon(void)
} else if (pid > 0) {
_exit(EXIT_SUCCESS);
}
- fd = open("/dev/null", O_RDWR);
+}
+
+/*
+ * Finish daemonization of the process by closing stdin/stdout/stderr.
+ * This must be called at the end of initialization after all external
+ * communication channels are established and accessible.
+ */
+static void
+_finish_daemonize(void)
+{
+ int devnull;
- if (fd < 0)
+ /* Preserve fd 0/1/2, but discard data to/from stdin/stdout/stderr. */
+ devnull = open("/dev/null", O_RDWR);
+ if (devnull < 0)
zed_log_die("Failed to open /dev/null: %s", strerror(errno));
- if (dup2(fd, STDIN_FILENO) < 0)
+ if (dup2(devnull, STDIN_FILENO) < 0)
zed_log_die("Failed to dup /dev/null onto stdin: %s",
strerror(errno));
- if (dup2(fd, STDOUT_FILENO) < 0)
+ if (dup2(devnull, STDOUT_FILENO) < 0)
zed_log_die("Failed to dup /dev/null onto stdout: %s",
strerror(errno));
- if (dup2(fd, STDERR_FILENO) < 0)
+ if (dup2(devnull, STDERR_FILENO) < 0)
zed_log_die("Failed to dup /dev/null onto stderr: %s",
strerror(errno));
- if (close(fd) < 0)
+ if (close(devnull) < 0)
zed_log_die("Failed to close /dev/null: %s", strerror(errno));
+
+ /* Notify parent that daemonization is complete. */
+ zed_log_pipe_close_writes();
}
/*
@@ -184,33 +234,36 @@ main(int argc, char *argv[])
if (geteuid() != 0)
zed_log_die("Must be run as root");
- (void) umask(0);
-
- _setup_sig_handlers();
-
zed_conf_parse_file(zcp);
zed_file_close_from(STDERR_FILENO + 1);
+ (void) umask(0);
+
if (chdir("/") < 0)
zed_log_die("Failed to change to root directory");
if (zed_conf_scan_dir(zcp) < 0)
exit(EXIT_FAILURE);
- if (zcp->do_memlock)
- _lock_memory();
-
if (!zcp->do_foreground) {
- _become_daemon();
+ _start_daemonize();
zed_log_syslog_open(LOG_DAEMON);
- zed_log_stderr_close();
}
- zed_log_msg(LOG_NOTICE,
- "ZFS Event Daemon %s-%s", ZFS_META_VERSION, ZFS_META_RELEASE);
+ _setup_sig_handlers();
+
+ if (zcp->do_memlock)
+ _lock_memory();
(void) zed_conf_write_pid(zcp);
+ if (!zcp->do_foreground)
+ _finish_daemonize();
+
+ zed_log_msg(LOG_NOTICE,
+ "ZFS Event Daemon %s-%s (PID %d)",
+ ZFS_META_VERSION, ZFS_META_RELEASE, (int) getpid());
+
if (zed_conf_open_state(zcp) < 0)
exit(EXIT_FAILURE);
diff --git a/cmd/zed/zed_conf.c b/cmd/zed/zed_conf.c
index 78b45e910..e6f601f3a 100644
--- a/cmd/zed/zed_conf.c
+++ b/cmd/zed/zed_conf.c
@@ -430,7 +430,13 @@ zed_conf_scan_dir(struct zed_conf *zcp)
/*
* Write the PID file specified in [zcp].
* Return 0 on success, -1 on error.
- * XXX: This must be called after fork()ing to become a daemon.
+ * This must be called after fork()ing to become a daemon (so the correct PID
+ * is recorded), but before daemonization is complete and the parent process
+ * exits (for synchronization with systemd).
+ * FIXME: Only update the PID file after verifying the PID previously stored
+ * in the PID file no longer exists or belongs to a foreign process
+ * in order to ensure the daemon cannot be started more than once.
+ * (This check is currently done by zed_conf_open_state().)
*/
int
zed_conf_write_pid(struct zed_conf *zcp)
diff --git a/cmd/zed/zed_log.c b/cmd/zed/zed_log.c
index bc432bc21..2a787e357 100644
--- a/cmd/zed/zed_log.c
+++ b/cmd/zed/zed_log.c
@@ -25,6 +25,7 @@
*/
#include <assert.h>
+#include <errno.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
@@ -40,6 +41,7 @@ static struct {
unsigned do_syslog:1;
int level;
char id[ZED_LOG_MAX_ID_LEN];
+ int pipe_fd[2];
} _ctx;
void
@@ -53,6 +55,8 @@ zed_log_init(const char *identity)
} else {
_ctx.id[0] = '\0';
}
+ _ctx.pipe_fd[0] = -1;
+ _ctx.pipe_fd[1] = -1;
}
void
@@ -63,6 +67,97 @@ zed_log_fini()
}
}
+/*
+ * Create pipe for communicating daemonization status between the parent and
+ * child processes across the double-fork().
+ */
+void
+zed_log_pipe_open(void)
+{
+ if ((_ctx.pipe_fd[0] != -1) || (_ctx.pipe_fd[1] != -1))
+ zed_log_die("Invalid use of zed_log_pipe_open in PID %d",
+ (int) getpid());
+
+ if (pipe(_ctx.pipe_fd) < 0)
+ zed_log_die("Failed to create daemonize pipe in PID %d: %s",
+ (int) getpid(), strerror(errno));
+}
+
+/*
+ * Close the read-half of the daemonize pipe.
+ * This should be called by the child after fork()ing from the parent since
+ * the child will never read from this pipe.
+ */
+void
+zed_log_pipe_close_reads(void)
+{
+ if (_ctx.pipe_fd[0] < 0)
+ zed_log_die(
+ "Invalid use of zed_log_pipe_close_reads in PID %d",
+ (int) getpid());
+
+ if (close(_ctx.pipe_fd[0]) < 0)
+ zed_log_die(
+ "Failed to close reads on daemonize pipe in PID %d: %s",
+ (int) getpid(), strerror(errno));
+
+ _ctx.pipe_fd[0] = -1;
+}
+
+/*
+ * Close the write-half of the daemonize pipe.
+ * This should be called by the parent after fork()ing its child since the
+ * parent will never write to this pipe.
+ * This should also be called by the child once initialization is complete
+ * in order to signal the parent that it can safely exit.
+ */
+void
+zed_log_pipe_close_writes(void)
+{
+ if (_ctx.pipe_fd[1] < 0)
+ zed_log_die(
+ "Invalid use of zed_log_pipe_close_writes in PID %d",
+ (int) getpid());
+
+ if (close(_ctx.pipe_fd[1]) < 0)
+ zed_log_die(
+ "Failed to close writes on daemonize pipe in PID %d: %s",
+ (int) getpid(), strerror(errno));
+
+ _ctx.pipe_fd[1] = -1;
+}
+
+/*
+ * Block on reading from the daemonize pipe until signaled by the child
+ * (via zed_log_pipe_close_writes()) that initialization is complete.
+ * This should only be called by the parent while waiting to exit after
+ * fork()ing the child.
+ */
+void
+zed_log_pipe_wait(void)
+{
+ ssize_t n;
+ char c;
+
+ if (_ctx.pipe_fd[0] < 0)
+ zed_log_die("Invalid use of zed_log_pipe_wait in PID %d",
+ (int) getpid());
+
+ for (;;) {
+ n = read(_ctx.pipe_fd[0], &c, sizeof (c));
+ if (n < 0) {
+ if (errno == EINTR)
+ continue;
+ zed_log_die(
+ "Failed to read from daemonize pipe in PID %d: %s",
+ (int) getpid(), strerror(errno));
+ }
+ if (n == 0) {
+ break;
+ }
+ }
+}
+
void
zed_log_stderr_open(int level)
{
diff --git a/cmd/zed/zed_log.h b/cmd/zed/zed_log.h
index 7ae4549fe..767c4c7db 100644
--- a/cmd/zed/zed_log.h
+++ b/cmd/zed/zed_log.h
@@ -33,6 +33,14 @@ void zed_log_init(const char *identity);
void zed_log_fini(void);
+void zed_log_pipe_open(void);
+
+void zed_log_pipe_close_reads(void);
+
+void zed_log_pipe_close_writes(void);
+
+void zed_log_pipe_wait(void);
+
void zed_log_stderr_open(int level);
void zed_log_stderr_close(void);