/* * Copy the [path] string to the [resultp] ptr. * If [path] is not an absolute path, prefix it with the current working dir. * If [resultp] is non-null, free its existing string before assignment. */ static void _zed_conf_parse_path(char **resultp, const char *path) { char buf[PATH_MAX]; assert(resultp != NULL); assert(path != NULL); if (*resultp) free(*resultp); if (path[0] == '/') { *resultp = strdup(path); } else if (!getcwd(buf, sizeof (buf))) { zed_log_die("Failed to get current working dir: %s", strerror(errno)); } else if (strlcat(buf, "/", sizeof (buf)) >= sizeof (buf)) { zed_log_die("Failed to copy path: %s", strerror(ENAMETOOLONG)); } else if (strlcat(buf, path, sizeof (buf)) >= sizeof (buf)) { zed_log_die("Failed to copy path: %s", strerror(ENAMETOOLONG)); } else { *resultp = strdup(buf); } if (!*resultp) zed_log_die("Failed to copy path: %s", strerror(ENOMEM)); }
/* * 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; /* 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(devnull, STDIN_FILENO) < 0) zed_log_die("Failed to dup /dev/null onto stdin: %s", strerror(errno)); if (dup2(devnull, STDOUT_FILENO) < 0) zed_log_die("Failed to dup /dev/null onto stdout: %s", strerror(errno)); if (dup2(devnull, STDERR_FILENO) < 0) zed_log_die("Failed to dup /dev/null onto stderr: %s", strerror(errno)); 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(); }
/* * Register signal handlers. */ static void _setup_sig_handlers(void) { struct sigaction sa; if (sigemptyset(&sa.sa_mask) < 0) zed_log_die("Failed to initialize sigset"); sa.sa_flags = SA_RESTART; sa.sa_handler = SIG_IGN; if (sigaction(SIGPIPE, &sa, NULL) < 0) zed_log_die("Failed to ignore SIGPIPE"); sa.sa_handler = _exit_handler; if (sigaction(SIGINT, &sa, NULL) < 0) zed_log_die("Failed to register SIGINT handler"); if (sigaction(SIGTERM, &sa, NULL) < 0) zed_log_die("Failed to register SIGTERM handler"); sa.sa_handler = _hup_handler; if (sigaction(SIGHUP, &sa, NULL) < 0) zed_log_die("Failed to register SIGHUP handler"); }
/* * 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)); }
/* * 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 _start_daemonize(void) { pid_t pid; 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", strerror(errno)); } else if (pid > 0) { _exit(EXIT_SUCCESS); } }
/* * 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; }
/* * Open the libzfs interface. */ void zed_event_init(struct zed_conf *zcp) { if (!zcp) zed_log_die("Failed zed_event_init: %s", strerror(EINVAL)); zcp->zfs_hdl = libzfs_init(); if (!zcp->zfs_hdl) zed_log_die("Failed to initialize libzfs"); zcp->zevent_fd = open(ZFS_DEV, O_RDWR); if (zcp->zevent_fd < 0) zed_log_die("Failed to open \"%s\": %s", ZFS_DEV, strerror(errno)); }
/* * Open the libzfs interface. */ int zed_event_init(struct zed_conf *zcp) { if (!zcp) zed_log_die("Failed zed_event_init: %s", strerror(EINVAL)); zcp->zfs_hdl = libzfs_init(); if (!zcp->zfs_hdl) { return ENODEV; } zcp->zevent_fd = open(ZFS_DEV, O_RDWR); if (zcp->zevent_fd < 0) zed_log_die("Failed to open \"%s\": %s", ZFS_DEV, strerror(errno)); return 0; }
/* * 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. */ static void _lock_memory(void) { #if HAVE_MLOCKALL int i = 0; const int max_tries = 10; for (i = 0; i < max_tries; i++) { if (mlockall(MCL_CURRENT | MCL_FUTURE) == 0) { zed_log_msg(LOG_INFO, "Locked all pages in memory"); return; } if (errno != EAGAIN) break; } zed_log_die("Failed to lock memory pages: %s", strerror(errno)); #else /* HAVE_MLOCKALL */ zed_log_die("Failed to lock memory pages: mlockall() not supported"); #endif /* HAVE_MLOCKALL */ }
/* * 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; } } }
/* * Transform the process into a daemon. */ static void _become_daemon(void) { pid_t pid; int fd; pid = fork(); if (pid < 0) { zed_log_die("Failed to create child process: %s", strerror(errno)); } else if (pid > 0) { _exit(EXIT_SUCCESS); } if (setsid() < 0) zed_log_die("Failed to create new session: %s", strerror(errno)); pid = fork(); if (pid < 0) { zed_log_die("Failed to create grandchild process: %s", strerror(errno)); } else if (pid > 0) { _exit(EXIT_SUCCESS); } fd = open("/dev/null", O_RDWR); if (fd < 0) zed_log_die("Failed to open /dev/null: %s", strerror(errno)); if (dup2(fd, STDIN_FILENO) < 0) zed_log_die("Failed to dup /dev/null onto stdin: %s", strerror(errno)); if (dup2(fd, STDOUT_FILENO) < 0) zed_log_die("Failed to dup /dev/null onto stdout: %s", strerror(errno)); if (dup2(fd, STDERR_FILENO) < 0) zed_log_die("Failed to dup /dev/null onto stderr: %s", strerror(errno)); if (close(fd) < 0) zed_log_die("Failed to close /dev/null: %s", strerror(errno)); }
/* * Close the libzfs interface. */ void zed_event_fini(struct zed_conf *zcp) { if (!zcp) zed_log_die("Failed zed_event_fini: %s", strerror(EINVAL)); if (zcp->zevent_fd >= 0) { if (close(zcp->zevent_fd) < 0) zed_log_msg(LOG_WARNING, "Failed to close \"%s\": %s", ZFS_DEV, strerror(errno)); zcp->zevent_fd = -1; } if (zcp->zfs_hdl) { libzfs_fini(zcp->zfs_hdl); zcp->zfs_hdl = NULL; } }
/* * Return a new configuration with default values. */ struct zed_conf * zed_conf_create(void) { struct zed_conf *zcp; zcp = calloc(1, sizeof (*zcp)); if (!zcp) goto nomem; zcp->syslog_facility = LOG_DAEMON; zcp->min_events = ZED_MIN_EVENTS; zcp->max_events = ZED_MAX_EVENTS; zcp->pid_fd = -1; zcp->zedlets = NULL; /* created via zed_conf_scan_dir() */ zcp->state_fd = -1; /* opened via zed_conf_open_state() */ zcp->zfs_hdl = NULL; /* opened via zed_event_init() */ zcp->zevent_fd = -1; /* opened via zed_event_init() */ if (!(zcp->conf_file = strdup(ZED_CONF_FILE))) goto nomem; if (!(zcp->pid_file = strdup(ZED_PID_FILE))) goto nomem; if (!(zcp->zedlet_dir = strdup(ZED_ZEDLET_DIR))) goto nomem; if (!(zcp->state_file = strdup(ZED_STATE_FILE))) goto nomem; return (zcp); nomem: zed_log_die("Failed to create conf: %s", strerror(errno)); return (NULL); }
/* * ZFS Event Daemon (ZED). */ int main(int argc, char *argv[]) { struct zed_conf *zcp; uint64_t saved_eid; int64_t saved_etime[2]; zed_log_init(argv[0]); zed_log_stderr_open(LOG_NOTICE); zcp = zed_conf_create(); zed_conf_parse_opts(zcp, argc, argv); if (zcp->do_verbose) zed_log_stderr_open(LOG_INFO); 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); 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(); 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); (void) zed_conf_write_pid(zcp); if (zed_conf_open_state(zcp) < 0) exit(EXIT_FAILURE); if (zed_conf_read_state(zcp, &saved_eid, saved_etime) < 0) exit(EXIT_FAILURE); zed_event_init(zcp); zed_event_seek(zcp, saved_eid, saved_etime); while (!_got_exit) { if (_got_hup) { _got_hup = 0; (void) zed_conf_scan_dir(zcp); } zed_event_service(zcp); } zed_log_msg(LOG_NOTICE, "Exiting"); zed_event_fini(zcp); zed_conf_destroy(zcp); zed_log_fini(); exit(EXIT_SUCCESS); }
/* * ZFS Event Daemon (ZED). */ int main(int argc, char *argv[]) { struct zed_conf *zcp; uint64_t saved_eid; int64_t saved_etime[2]; zed_log_init(argv[0]); zed_log_stderr_open(LOG_NOTICE); zcp = zed_conf_create(); zed_conf_parse_opts(zcp, argc, argv); if (zcp->do_verbose) zed_log_stderr_open(LOG_INFO); 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); 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(); 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); (void) zed_conf_write_pid(zcp); if (zed_conf_open_state(zcp) < 0) exit(EXIT_FAILURE); if (zed_conf_read_state(zcp, &saved_eid, saved_etime) < 0) exit(EXIT_FAILURE); retry: if (zed_event_init(zcp)) { /* * If we failed to open /dev/zfs, but force was requested, we * sleep waiting for it to come alive. This lets zed sit around * waiting for the kernel module to load. */ if (zcp->do_force) { sleep(30); if (!_got_exit) goto retry; } zed_log_die("Failed to initialize libzfs"); } zed_event_seek(zcp, saved_eid, saved_etime); while (!_got_exit) { if (_got_hup) { _got_hup = 0; (void) zed_conf_scan_dir(zcp); } if (zed_event_service(zcp)) break; } zed_log_msg(LOG_NOTICE, "Exiting"); zed_event_fini(zcp); if (zcp->do_force && !_got_exit) { goto retry; } zed_conf_destroy(zcp); zed_log_fini(); exit(EXIT_SUCCESS); }
/* * Parse the configuration file into the configuration [zcp]. * * FIXME: Not yet implemented. */ void zed_conf_parse_file(struct zed_conf *zcp) { if (!zcp) zed_log_die("Failed to parse config: %s", strerror(EINVAL)); }
/* * Parse the command-line options into the configuration [zcp]. */ void zed_conf_parse_opts(struct zed_conf *zcp, int argc, char **argv) { const char * const opts = ":hLVc:d:p:s:vfFMZ"; int opt; if (!zcp || !argv || !argv[0]) zed_log_die("Failed to parse options: Internal error"); opterr = 0; /* suppress default getopt err msgs */ while ((opt = getopt(argc, argv, opts)) != -1) { switch (opt) { case 'h': _zed_conf_display_help(argv[0], EXIT_SUCCESS); break; case 'L': _zed_conf_display_license(); break; case 'V': _zed_conf_display_version(); break; case 'c': _zed_conf_parse_path(&zcp->conf_file, optarg); break; case 'd': _zed_conf_parse_path(&zcp->zedlet_dir, optarg); break; case 'p': _zed_conf_parse_path(&zcp->pid_file, optarg); break; case 's': _zed_conf_parse_path(&zcp->state_file, optarg); break; case 'v': zcp->do_verbose = 1; break; case 'f': zcp->do_force = 1; break; case 'F': zcp->do_foreground = 1; break; case 'M': zcp->do_memlock = 1; break; case 'Z': zcp->do_zero = 1; break; case '?': default: if (optopt == '?') _zed_conf_display_help(argv[0], EXIT_SUCCESS); fprintf(stderr, "%s: %s '-%c'\n\n", argv[0], "Invalid option", optopt); _zed_conf_display_help(argv[0], EXIT_FAILURE); break; } } }