summaryrefslogtreecommitdiffstats
path: root/cmd
diff options
context:
space:
mode:
Diffstat (limited to 'cmd')
-rw-r--r--cmd/Makefile.am2
-rw-r--r--cmd/zed/.gitignore1
-rw-r--r--cmd/zed/Makefile.am62
-rw-r--r--cmd/zed/zed.c236
-rwxr-xr-xcmd/zed/zed.d/all-debug.sh17
-rwxr-xr-xcmd/zed/zed.d/all-syslog.sh11
l---------cmd/zed/zed.d/checksum-email.sh1
-rwxr-xr-xcmd/zed/zed.d/data-email.sh81
-rwxr-xr-xcmd/zed/zed.d/generic-email.sh59
-rwxr-xr-xcmd/zed/zed.d/io-email.sh86
l---------cmd/zed/zed.d/resilver.finish-email.sh1
-rwxr-xr-xcmd/zed/zed.d/scrub.finish-email.sh73
-rw-r--r--cmd/zed/zed.d/zed.rc28
-rw-r--r--cmd/zed/zed.h70
-rw-r--r--cmd/zed/zed_conf.c673
-rw-r--r--cmd/zed/zed_conf.h71
-rw-r--r--cmd/zed/zed_event.c829
-rw-r--r--cmd/zed/zed_event.h41
-rw-r--r--cmd/zed/zed_exec.c207
-rw-r--r--cmd/zed/zed_exec.h36
-rw-r--r--cmd/zed/zed_file.c316
-rw-r--r--cmd/zed/zed_file.h49
-rw-r--r--cmd/zed/zed_log.c171
-rw-r--r--cmd/zed/zed_log.h48
-rw-r--r--cmd/zed/zed_strings.c200
-rw-r--r--cmd/zed/zed_strings.h44
26 files changed, 3412 insertions, 1 deletions
diff --git a/cmd/Makefile.am b/cmd/Makefile.am
index 4c3e4bbb2..968c6c181 100644
--- a/cmd/Makefile.am
+++ b/cmd/Makefile.am
@@ -1,2 +1,2 @@
SUBDIRS = zfs zpool zdb zhack zinject zstreamdump ztest zpios
-SUBDIRS += mount_zfs fsck_zfs zvol_id vdev_id arcstat dbufstat
+SUBDIRS += mount_zfs fsck_zfs zvol_id vdev_id arcstat dbufstat zed
diff --git a/cmd/zed/.gitignore b/cmd/zed/.gitignore
new file mode 100644
index 000000000..76557bb6b
--- /dev/null
+++ b/cmd/zed/.gitignore
@@ -0,0 +1 @@
+/zed
diff --git a/cmd/zed/Makefile.am b/cmd/zed/Makefile.am
new file mode 100644
index 000000000..8e4efe919
--- /dev/null
+++ b/cmd/zed/Makefile.am
@@ -0,0 +1,62 @@
+include $(top_srcdir)/config/Rules.am
+
+DEFAULT_INCLUDES += \
+ -I$(top_srcdir)/include \
+ -I$(top_srcdir)/lib/libspl/include
+
+sbin_PROGRAMS = zed
+
+zed_SOURCES = \
+ $(top_srcdir)/cmd/zed/zed.c \
+ $(top_srcdir)/cmd/zed/zed.h \
+ $(top_srcdir)/cmd/zed/zed_conf.c \
+ $(top_srcdir)/cmd/zed/zed_conf.h \
+ $(top_srcdir)/cmd/zed/zed_event.c \
+ $(top_srcdir)/cmd/zed/zed_event.h \
+ $(top_srcdir)/cmd/zed/zed_exec.c \
+ $(top_srcdir)/cmd/zed/zed_exec.h \
+ $(top_srcdir)/cmd/zed/zed_file.c \
+ $(top_srcdir)/cmd/zed/zed_file.h \
+ $(top_srcdir)/cmd/zed/zed_log.c \
+ $(top_srcdir)/cmd/zed/zed_log.h \
+ $(top_srcdir)/cmd/zed/zed_strings.c \
+ $(top_srcdir)/cmd/zed/zed_strings.h
+
+zed_LDADD = \
+ $(top_builddir)/lib/libavl/libavl.la \
+ $(top_builddir)/lib/libnvpair/libnvpair.la \
+ $(top_builddir)/lib/libspl/libspl.la \
+ $(top_builddir)/lib/libzfs/libzfs.la
+
+zedconfdir = $(sysconfdir)/zfs/zed.d
+
+dist_zedconf_DATA = \
+ $(top_srcdir)/cmd/zed/zed.d/zed.rc
+
+zedexecdir = $(libexecdir)/zfs/zed.d
+
+dist_zedexec_SCRIPTS = \
+ $(top_srcdir)/cmd/zed/zed.d/all-debug.sh \
+ $(top_srcdir)/cmd/zed/zed.d/all-syslog.sh \
+ $(top_srcdir)/cmd/zed/zed.d/checksum-email.sh \
+ $(top_srcdir)/cmd/zed/zed.d/data-email.sh \
+ $(top_srcdir)/cmd/zed/zed.d/generic-email.sh \
+ $(top_srcdir)/cmd/zed/zed.d/io-email.sh \
+ $(top_srcdir)/cmd/zed/zed.d/resilver.finish-email.sh \
+ $(top_srcdir)/cmd/zed/zed.d/scrub.finish-email.sh
+
+zedconfdefaults = \
+ all-syslog.sh \
+ checksum-email.sh \
+ data-email.sh \
+ io-email.sh \
+ resilver.finish-email.sh \
+ scrub.finish-email.sh
+
+install-data-local:
+ $(MKDIR_P) "$(DESTDIR)$(zedconfdir)"
+ for f in $(zedconfdefaults); do \
+ test -f "$(DESTDIR)$(zedconfdir)/$${f}" -o \
+ -L "$(DESTDIR)$(zedconfdir)/$${f}" || \
+ ln -s "$(zedexecdir)/$${f}" "$(DESTDIR)$(zedconfdir)"; \
+ done
diff --git a/cmd/zed/zed.c b/cmd/zed/zed.c
new file mode 100644
index 000000000..d2fc0e899
--- /dev/null
+++ b/cmd/zed/zed.c
@@ -0,0 +1,236 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License (the "License").
+ * You may not use this file except in compliance with the License.
+ *
+ * You can obtain a copy of the license from the top-level
+ * OPENSOLARIS.LICENSE or <http://opensource.org/licenses/CDDL-1.0>.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each file
+ * and include the License file from the top-level OPENSOLARIS.LICENSE.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information: Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ */
+
+/*
+ * Developed at Lawrence Livermore National Laboratory (LLNL-CODE-403049).
+ * Copyright (C) 2013-2014 Lawrence Livermore National Security, LLC.
+ */
+
+#include <errno.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include "zed.h"
+#include "zed_conf.h"
+#include "zed_event.h"
+#include "zed_file.h"
+#include "zed_log.h"
+
+static volatile sig_atomic_t _got_exit = 0;
+static volatile sig_atomic_t _got_hup = 0;
+
+/*
+ * Signal handler for SIGINT & SIGTERM.
+ */
+static void
+_exit_handler(int signum)
+{
+ _got_exit = 1;
+}
+
+/*
+ * Signal handler for SIGHUP.
+ */
+static void
+_hup_handler(int signum)
+{
+ _got_hup = 1;
+}
+
+/*
+ * 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");
+}
+
+/*
+ * 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 ! _POSIX_MEMLOCK
+ zed_log_die("Failed to lock memory pages: mlockall() not supported");
+
+#else /* _POSIX_MEMLOCK */
+ 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));
+
+#endif /* _POSIX_MEMLOCK */
+}
+
+/*
+ * 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));
+}
+
+/*
+ * 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);
+}
diff --git a/cmd/zed/zed.d/all-debug.sh b/cmd/zed/zed.d/all-debug.sh
new file mode 100755
index 000000000..ae64e0a79
--- /dev/null
+++ b/cmd/zed/zed.d/all-debug.sh
@@ -0,0 +1,17 @@
+#!/bin/sh
+#
+# Log all environment variables to ZED_DEBUG_LOG.
+#
+test -f "${ZED_SCRIPT_DIR}/zed.rc" && . "${ZED_SCRIPT_DIR}/zed.rc"
+
+# Override the default umask to restrict access to a newly-created logfile.
+umask 077
+
+# Append stdout to the logfile after obtaining an advisory lock.
+exec >> "${ZED_DEBUG_LOG:=/tmp/zed.debug.log}"
+flock -x 1
+
+printenv | sort
+echo
+
+exit 0
diff --git a/cmd/zed/zed.d/all-syslog.sh b/cmd/zed/zed.d/all-syslog.sh
new file mode 100755
index 000000000..b8bd307a1
--- /dev/null
+++ b/cmd/zed/zed.d/all-syslog.sh
@@ -0,0 +1,11 @@
+#!/bin/sh
+#
+# Log the zevent via syslog.
+#
+test -f "${ZED_SCRIPT_DIR}/zed.rc" && . "${ZED_SCRIPT_DIR}/zed.rc"
+
+logger -t "${ZED_SYSLOG_TAG:=zed}" -p "${ZED_SYSLOG_PRIORITY:=daemon.notice}" \
+ eid="${ZEVENT_EID}" class="${ZEVENT_SUBCLASS}" \
+ "${ZEVENT_POOL:+pool=$ZEVENT_POOL}"
+
+exit 0
diff --git a/cmd/zed/zed.d/checksum-email.sh b/cmd/zed/zed.d/checksum-email.sh
new file mode 120000
index 000000000..f95bec215
--- /dev/null
+++ b/cmd/zed/zed.d/checksum-email.sh
@@ -0,0 +1 @@
+io-email.sh \ No newline at end of file
diff --git a/cmd/zed/zed.d/data-email.sh b/cmd/zed/zed.d/data-email.sh
new file mode 100755
index 000000000..9f8316149
--- /dev/null
+++ b/cmd/zed/zed.d/data-email.sh
@@ -0,0 +1,81 @@
+#!/bin/sh
+#
+# Send email to ZED_EMAIL in response to a DATA zevent.
+# Only one message per ZED_EMAIL_INTERVAL_SECS will be sent for a given
+# class/pool combination. This protects against spamming the recipient
+# should multiple events occur together in time for the same pool.
+# Exit codes:
+# 0: email sent
+# 1: email failed
+# 2: email suppressed
+# 3: missing executable
+# 4: unsupported event class
+# 5: internal error
+# State File Format:
+# POOL:TIME_OF_LAST_EMAIL
+#
+test -f "${ZED_SCRIPT_DIR}/zed.rc" && . "${ZED_SCRIPT_DIR}/zed.rc"
+
+test -n "${ZEVENT_POOL}" || exit 5
+test -n "${ZEVENT_SUBCLASS}" || exit 5
+
+if test "${ZEVENT_SUBCLASS}" != "data"; then \
+ logger -t "${ZED_SYSLOG_TAG:=zed}" \
+ -p "${ZED_SYSLOG_PRIORITY:=daemon.warning}" \
+ `basename "$0"`: unsupported event class \"${ZEVENT_SUBCLASS}\"
+ exit 4
+fi
+
+# Only send email if ZED_EMAIL has been configured.
+test -n "${ZED_EMAIL}" || exit 2
+
+# Ensure requisite executables are installed.
+if ! command -v "${MAIL:=mail}" >/dev/null 2>&1; then
+ logger -t "${ZED_SYSLOG_TAG:=zed}" \
+ -p "${ZED_SYSLOG_PRIORITY:=daemon.warning}" \
+ `basename "$0"`: "${MAIL}" not installed
+ exit 3
+fi
+
+NAME="zed.${ZEVENT_SUBCLASS}.email"
+LOCKFILE="${ZED_LOCKDIR:=/var/lock}/${NAME}.lock"
+STATEFILE="${ZED_RUNDIR:=/var/run}/${NAME}.state"
+
+# Obtain lock to ensure mutual exclusion for accessing state.
+exec 8> "${LOCKFILE}"
+flock -x 8
+
+# Query state for last time email was sent for this pool.
+TIME_NOW=`date +%s`
+TIME_LAST=`egrep "^${ZEVENT_POOL}:" "${STATEFILE}" 2>/dev/null | cut -d: -f2`
+if test -n "${TIME_LAST}"; then
+ TIME_DELTA=`expr "${TIME_NOW}" - "${TIME_LAST}"`
+ if test "${TIME_DELTA}" -lt "${ZED_EMAIL_INTERVAL_SECS:=3600}"; then
+ exit 2
+ fi
+fi
+
+"${MAIL}" -s "ZFS ${ZEVENT_SUBCLASS} error for ${ZEVENT_POOL} on `hostname`" \
+ "${ZED_EMAIL}" <<EOF
+A ZFS ${ZEVENT_SUBCLASS} error has been detected:
+
+ eid: ${ZEVENT_EID}
+ host: `hostname`
+ time: ${ZEVENT_TIME_STRING}
+ pool: ${ZEVENT_POOL}
+EOF
+MAIL_STATUS=$?
+
+# Update state.
+egrep -v "^${ZEVENT_POOL}:" "${STATEFILE}" 2>/dev/null > "${STATEFILE}.$$"
+echo "${ZEVENT_POOL}:${TIME_NOW}" >> "${STATEFILE}.$$"
+mv -f "${STATEFILE}.$$" "${STATEFILE}"
+
+if test "${MAIL_STATUS}" -ne 0; then
+ logger -t "${ZED_SYSLOG_TAG:=zed}" \
+ -p "${ZED_SYSLOG_PRIORITY:=daemon.warning}" \
+ `basename "$0"`: "${MAIL}" exit="${MAIL_STATUS}"
+ exit 1
+fi
+
+exit 0
diff --git a/cmd/zed/zed.d/generic-email.sh b/cmd/zed/zed.d/generic-email.sh
new file mode 100755
index 000000000..16bbdb197
--- /dev/null
+++ b/cmd/zed/zed.d/generic-email.sh
@@ -0,0 +1,59 @@
+#!/bin/sh
+#
+# Send email to ZED_EMAIL in response to a given zevent.
+# This is a generic script than can be symlinked to a file in the zed
+# enabled-scripts directory in order to have email sent when a particular
+# class of zevents occurs. The symlink filename must begin with the zevent
+# (sub)class string (eg, "probe_failure-email.sh" for the "probe_failure"
+# subclass). Refer to the zed(8) manpage for details.
+# Exit codes:
+# 0: email sent
+# 1: email failed
+# 2: email suppressed
+# 3: missing executable
+#
+test -f "${ZED_SCRIPT_DIR}/zed.rc" && . "${ZED_SCRIPT_DIR}/zed.rc"
+
+# Only send email if ZED_EMAIL has been configured.
+test -n "${ZED_EMAIL}" || exit 2
+
+# Ensure requisite executables are installed.
+if ! command -v "${MAIL:=mail}" >/dev/null 2>&1; then
+ logger -t "${ZED_SYSLOG_TAG:=zed}" \
+ -p "${ZED_SYSLOG_PRIORITY:=daemon.warning}" \
+ `basename "$0"`: "${MAIL}" not installed
+ exit 3
+fi
+
+# Override the default umask to restrict access to the msgbody tmpfile.
+umask 077
+
+SUBJECT="ZFS ${ZEVENT_SUBCLASS} event"
+test -n "${ZEVENT_POOL}" && SUBJECT="${SUBJECT} for ${ZEVENT_POOL}"
+SUBJECT="${SUBJECT} on `hostname`"
+
+MSGBODY="${TMPDIR:=/tmp}/`basename \"$0\"`.$$"
+{
+ echo "A ZFS ${ZEVENT_SUBCLASS} event has been posted:"
+ echo
+ echo " eid: ${ZEVENT_EID}"
+ echo " host: `hostname`"
+ echo " time: ${ZEVENT_TIME_STRING}"
+ test -n "${ZEVENT_VDEV_TYPE}" -a -n "${ZEVENT_VDEV_PATH}" && \
+ echo " vdev: ${ZEVENT_VDEV_TYPE}:${ZEVENT_VDEV_PATH}"
+ test -n "${ZEVENT_POOL}" -a -x "${ZPOOL}" && \
+ "${ZPOOL}" status "${ZEVENT_POOL}"
+} > "${MSGBODY}"
+
+test -f "${MSGBODY}" && "${MAIL}" -s "${SUBJECT}" "${ZED_EMAIL}" < "${MSGBODY}"
+MAIL_STATUS=$?
+rm -f "${MSGBODY}"
+
+if test "${MAIL_STATUS}" -ne 0; then
+ logger -t "${ZED_SYSLOG_TAG:=zed}" \
+ -p "${ZED_SYSLOG_PRIORITY:=daemon.warning}" \
+ `basename "$0"`: "${MAIL}" exit="${MAIL_STATUS}"
+ exit 1
+fi
+
+exit 0
diff --git a/cmd/zed/zed.d/io-email.sh b/cmd/zed/zed.d/io-email.sh
new file mode 100755
index 000000000..6cfe3c7f7
--- /dev/null
+++ b/cmd/zed/zed.d/io-email.sh
@@ -0,0 +1,86 @@
+#!/bin/sh
+#
+# Send email to ZED_EMAIL in response to a CHECKSUM or IO zevent.
+# Only one message per ZED_EMAIL_INTERVAL_SECS will be sent for a given
+# class/pool/vdev combination. This protects against spamming the recipient
+# should multiple events occur together in time for the same pool/device.
+# Exit codes:
+# 0: email sent
+# 1: email failed
+# 2: email suppressed
+# 3: missing executable
+# 4: unsupported event class
+# 5: internal error
+# State File Format:
+# POOL:VDEV_PATH:TIME_OF_LAST_EMAIL
+#
+test -f "${ZED_SCRIPT_DIR}/zed.rc" && . "${ZED_SCRIPT_DIR}/zed.rc"
+
+test -n "${ZEVENT_POOL}" || exit 5
+test -n "${ZEVENT_SUBCLASS}" || exit 5
+test -n "${ZEVENT_VDEV_PATH}" || exit 5
+
+if test "${ZEVENT_SUBCLASS}" != "checksum" \
+ -a "${ZEVENT_SUBCLASS}" != "io"; then
+ logger -t "${ZED_SYSLOG_TAG:=zed}" \
+ -p "${ZED_SYSLOG_PRIORITY:=daemon.warning}" \
+ `basename "$0"`: unsupported event class \"${ZEVENT_SUBCLASS}\"
+ exit 4
+fi
+
+# Only send email if ZED_EMAIL has been configured.
+test -n "${ZED_EMAIL}" || exit 2
+
+# Ensure requisite executables are installed.
+if ! command -v "${MAIL:=mail}" >/dev/null 2>&1; then
+ logger -t "${ZED_SYSLOG_TAG:=zed}" \
+ -p "${ZED_SYSLOG_PRIORITY:=daemon.warning}" \
+ `basename "$0"`: "${MAIL}" not installed
+ exit 3
+fi
+
+NAME="zed.${ZEVENT_SUBCLASS}.email"
+LOCKFILE="${ZED_LOCKDIR:=/var/lock}/${NAME}.lock"
+STATEFILE="${ZED_RUNDIR:=/var/run}/${NAME}.state"
+
+# Obtain lock to ensure mutual exclusion for accessing state.
+exec 8> "${LOCKFILE}"
+flock -x 8
+
+# Query state for last time email was sent for this pool/vdev.
+TIME_NOW=`date +%s`
+TIME_LAST=`egrep "^${ZEVENT_POOL}:${ZEVENT_VDEV_PATH}:" "${STATEFILE}" \
+ 2>/dev/null | cut -d: -f3`
+if test -n "${TIME_LAST}"; then
+ TIME_DELTA=`expr "${TIME_NOW}" - "${TIME_LAST}"`
+ if test "${TIME_DELTA}" -lt "${ZED_EMAIL_INTERVAL_SECS:=3600}"; then
+ exit 2
+ fi
+fi
+
+"${MAIL}" -s "ZFS ${ZEVENT_SUBCLASS} error for ${ZEVENT_POOL} on `hostname`" \
+ "${ZED_EMAIL}" <<EOF
+A ZFS ${ZEVENT_SUBCLASS} error has been detected:
+
+ eid: ${ZEVENT_EID}
+ host: `hostname`
+ time: ${ZEVENT_TIME_STRING}
+ pool: ${ZEVENT_POOL}
+ vdev: ${ZEVENT_VDEV_TYPE}:${ZEVENT_VDEV_PATH}
+EOF
+MAIL_STATUS=$?
+
+# Update state.
+egrep -v "^${ZEVENT_POOL}:${ZEVENT_VDEV_PATH}:" "${STATEFILE}" \
+ 2>/dev/null > "${STATEFILE}.$$"
+echo "${ZEVENT_POOL}:${ZEVENT_VDEV_PATH}:${TIME_NOW}" >> "${STATEFILE}.$$"
+mv -f "${STATEFILE}.$$" "${STATEFILE}"
+
+if test "${MAIL_STATUS}" -ne 0; then
+ logger -t "${ZED_SYSLOG_TAG:=zed}" \
+ -p "${ZED_SYSLOG_PRIORITY:=daemon.warning}" \
+ `basename "$0"`: "${MAIL}" exit="${MAIL_STATUS}"
+ exit 1
+fi
+
+exit 0
diff --git a/cmd/zed/zed.d/resilver.finish-email.sh b/cmd/zed/zed.d/resilver.finish-email.sh
new file mode 120000
index 000000000..1afad3258
--- /dev/null
+++ b/cmd/zed/zed.d/resilver.finish-email.sh
@@ -0,0 +1 @@
+scrub.finish-email.sh \ No newline at end of file
diff --git a/cmd/zed/zed.d/scrub.finish-email.sh b/cmd/zed/zed.d/scrub.finish-email.sh
new file mode 100755
index 000000000..b5ce3f74d
--- /dev/null
+++ b/cmd/zed/zed.d/scrub.finish-email.sh
@@ -0,0 +1,73 @@
+#!/bin/sh
+#
+# Send email to ZED_EMAIL in response to a RESILVER.FINISH or SCRUB.FINISH.
+# By default, "zpool status" output will only be included in the email for
+# a scrub.finish zevent if the pool is not healthy; to always include its
+# output, set ZED_EMAIL_VERBOSE=1.
+# Exit codes:
+# 0: email sent
+# 1: email failed
+# 2: email suppressed
+# 3: missing executable
+# 4: unsupported event class
+# 5: internal error
+#
+test -f "${ZED_SCRIPT_DIR}/zed.rc" && . "${ZED_SCRIPT_DIR}/zed.rc"
+
+test -n "${ZEVENT_POOL}" || exit 5
+test -n "${ZEVENT_SUBCLASS}" || exit 5
+
+if test "${ZEVENT_SUBCLASS}" = "resilver.finish"; then
+ ACTION="resilvering"
+elif test "${ZEVENT_SUBCLASS}" = "scrub.finish"; then
+ ACTION="scrubbing"
+else
+ logger -t "${ZED_SYSLOG_TAG:=zed}" \
+ -p "${ZED_SYSLOG_PRIORITY:=daemon.warning}" \
+ `basename "$0"`: unsupported event class \"${ZEVENT_SUBCLASS}\"
+ exit 4
+fi
+
+# Only send email if ZED_EMAIL has been configured.
+test -n "${ZED_EMAIL}" || exit 2
+
+# Ensure requisite executables are installed.
+if ! command -v "${MAIL:=mail}" >/dev/null 2>&1; then
+ logger -t "${ZED_SYSLOG_TAG:=zed}" \
+ -p "${ZED_SYSLOG_PRIORITY:=daemon.warning}" \
+ `basename "$0"`: "${MAIL}" not installed
+ exit 3
+fi
+if ! test -x "${ZPOOL}"; then
+ logger -t "${ZED_SYSLOG_TAG:=zed}" \
+ -p "${ZED_SYSLOG_PRIORITY:=daemon.warning}" \
+ `basename "$0"`: "${ZPOOL}" not installed
+ exit 3
+fi
+
+# For scrub, suppress email if pool is healthy and verbosity is not enabled.
+if test "${ZEVENT_SUBCLASS}" = "scrub.finish"; then
+ HEALTHY=`"${ZPOOL}" status -x "${ZEVENT_POOL}" | \
+ grep "'${ZEVENT_POOL}' is healthy"`
+ test -n "${HEALTHY}" -a "${ZED_EMAIL_VERBOSE:=0}" = 0 && exit 2
+fi
+
+"${MAIL}" -s "ZFS ${ZEVENT_SUBCLASS} event for ${ZEVENT_POOL} on `hostname`" \
+ "${ZED_EMAIL}" <<EOF
+A ZFS pool has finished ${ACTION}:
+
+ eid: ${ZEVENT_EID}
+ host: `hostname`
+ time: ${ZEVENT_TIME_STRING}
+`"${ZPOOL}" status "${ZEVENT_POOL}"`
+EOF
+MAIL_STATUS=$?
+
+if test "${MAIL_STATUS}" -ne 0; then
+ logger -t "${ZED_SYSLOG_TAG:=zed}" \
+ -p "${ZED_SYSLOG_PRIORITY:=daemon.warning}" \
+ `basename "$0"`: "${MAIL}" exit="${MAIL_STATUS}"
+ exit 1
+fi
+
+exit 0
diff --git a/cmd/zed/zed.d/zed.rc b/cmd/zed/zed.d/zed.rc
new file mode 100644
index 000000000..57c969c89
--- /dev/null
+++ b/cmd/zed/zed.d/zed.rc
@@ -0,0 +1,28 @@
+# zed.rc
+
+# Absolute path to the debug output file.
+#ZED_DEBUG_LOG="/tmp/zed.debug.log"
+
+# Email address of the zpool administrator.
+# Email will only be sent if ZED_EMAIL is defined.
+#ZED_EMAIL="root"
+
+# Email verbosity.
+# If set to 0, suppress email if the pool is healthy.
+# If set to 1, send email regardless of pool health.
+#ZED_EMAIL_VERBOSE=0
+
+# Minimum number of seconds between emails sent for a similar event.
+#ZED_EMAIL_INTERVAL_SECS="3600"
+
+# Default directory for zed lock files.
+#ZED_LOCKDIR="/var/lock"
+
+# Default directory for zed state files.
+#ZED_RUNDIR="/var/run"
+
+# The syslog priority (eg, specified as a "facility.level" pair).
+#ZED_SYSLOG_PRIORITY="daemon.notice"
+
+# The syslog tag for marking zed events.
+#ZED_SYSLOG_TAG="zed"
diff --git a/cmd/zed/zed.h b/cmd/zed/zed.h
new file mode 100644
index 000000000..179006cd3
--- /dev/null
+++ b/cmd/zed/zed.h
@@ -0,0 +1,70 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License (the "License").
+ * You may not use this file except in compliance with the License.
+ *
+ * You can obtain a copy of the license from the top-level
+ * OPENSOLARIS.LICENSE or <http://opensource.org/licenses/CDDL-1.0>.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each file
+ * and include the License file from the top-level OPENSOLARIS.LICENSE.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information: Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ */
+
+/*
+ * Developed at Lawrence Livermore National Laboratory (LLNL-CODE-403049).
+ * Copyright (C) 2013-2014 Lawrence Livermore National Security, LLC.
+ */
+
+#ifndef ZED_H
+#define ZED_H
+
+/*
+ * Absolute path for the default zed configuration file.
+ */
+#define ZED_CONF_FILE SYSCONFDIR "/zfs/zed.conf"
+
+/*
+ * Absolute path for the default zed pid file.
+ */
+#define ZED_PID_FILE RUNSTATEDIR "/zed.pid"
+
+/*
+ * Absolute path for the default zed state file.
+ */
+#define ZED_STATE_FILE RUNSTATEDIR "/zed.state"
+
+/*
+ * Absolute path for the default zed script directory.
+ */
+#define ZED_SCRIPT_DIR SYSCONFDIR "/zfs/zed.d"
+
+/*
+ * Reserved for future use.
+ */
+#define ZED_MAX_EVENTS 0
+
+/*
+ * Reserved for future use.
+ */
+#define ZED_MIN_EVENTS 0
+
+/*
+ * String prefix for ZED variables passed via environment variables.
+ */
+#define ZED_VAR_PREFIX "ZED_"
+
+/*
+ * String prefix for ZFS event names passed via environment variables.
+ */
+#define ZEVENT_VAR_PREFIX "ZEVENT_"
+
+#endif /* !ZED_H */
diff --git a/cmd/zed/zed_conf.c b/cmd/zed/zed_conf.c
new file mode 100644
index 000000000..fb3e552e9
--- /dev/null
+++ b/cmd/zed/zed_conf.c
@@ -0,0 +1,673 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License (the "License").
+ * You may not use this file except in compliance with the License.
+ *
+ * You can obtain a copy of the license from the top-level
+ * OPENSOLARIS.LICENSE or <http://opensource.org/licenses/CDDL-1.0>.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each file
+ * and include the License file from the top-level OPENSOLARIS.LICENSE.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information: Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ */
+
+/*
+ * Developed at Lawrence Livermore National Laboratory (LLNL-CODE-403049).
+ * Copyright (C) 2013-2014 Lawrence Livermore National Security, LLC.
+ */
+
+#include <assert.h>
+#include <ctype.h>
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/uio.h>
+#include <unistd.h>
+#include "zed.h"
+#include "zed_conf.h"
+#include "zed_file.h"
+#include "zed_log.h"
+#include "zed_strings.h"
+
+/*
+ * Return a new configuration with default values.
+ */
+struct zed_conf *
+zed_conf_create(void)
+{
+ struct zed_conf *zcp;
+
+ zcp = malloc(sizeof (*zcp));
+ if (!zcp)
+ goto nomem;
+
+ memset(zcp, 0, sizeof (*zcp));
+
+ zcp->syslog_facility = LOG_DAEMON;
+ zcp->min_events = ZED_MIN_EVENTS;
+ zcp->max_events = ZED_MAX_EVENTS;
+ zcp->scripts = 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->script_dir = strdup(ZED_SCRIPT_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);
+}
+
+/*
+ * Destroy the configuration [zcp].
+ * Note: zfs_hdl & zevent_fd are destroyed via zed_event_fini().
+ */
+void
+zed_conf_destroy(struct zed_conf *zcp)
+{
+ if (!zcp)
+ return;
+
+ if (zcp->state_fd >= 0) {
+ if (close(zcp->state_fd) < 0)
+ zed_log_msg(LOG_WARNING,
+ "Failed to close state file \"%s\": %s",
+ zcp->state_file, strerror(errno));
+ }
+ if (zcp->pid_file) {
+ if ((unlink(zcp->pid_file) < 0) && (errno != ENOENT))
+ zed_log_msg(LOG_WARNING,
+ "Failed to remove pid file \"%s\": %s",
+ zcp->pid_file, strerror(errno));
+ }
+ if (zcp->conf_file)
+ free(zcp->conf_file);
+
+ if (zcp->pid_file)
+ free(zcp->pid_file);
+
+ if (zcp->script_dir)
+ free(zcp->script_dir);
+
+ if (zcp->state_file)
+ free(zcp->state_file);
+
+ if (zcp->scripts)
+ zed_strings_destroy(zcp->scripts);
+
+ free(zcp);
+}
+
+/*
+ * Display command-line help and exit.
+ * If [got_err] is 0, output to stdout and exit normally;
+ * otherwise, output to stderr and exit with a failure status.
+ */
+static void
+_zed_conf_display_help(const char *prog, int got_err)
+{
+ FILE *fp = got_err ? stderr : stdout;
+ int w1 = 4; /* width of leading whitespace */
+ int w2 = 8; /* width of L-justified option field */
+
+ fprintf(fp, "Usage: %s [OPTION]...\n", (prog ? prog : "zed"));
+ fprintf(fp, "\n");
+ fprintf(fp, "%*c%*s %s\n", w1, 0x20, -w2, "-h",
+ "Display help.");
+ fprintf(fp, "%*c%*s %s\n", w1, 0x20, -w2, "-L",
+ "Display license information.");
+ fprintf(fp, "%*c%*s %s\n", w1, 0x20, -w2, "-V",
+ "Display version information.");
+ fprintf(fp, "\n");
+ fprintf(fp, "%*c%*s %s\n", w1, 0x20, -w2, "-v",
+ "Be verbose.");
+ fprintf(fp, "%*c%*s %s\n", w1, 0x20, -w2, "-f",
+ "Force daemon to run.");
+ fprintf(fp, "%*c%*s %s\n", w1, 0x20, -w2, "-F",
+ "Run daemon in the foreground.");
+ fprintf(fp, "%*c%*s %s\n", w1, 0x20, -w2, "-M",
+ "Lock all pages in memory.");
+ fprintf(fp, "%*c%*s %s\n", w1, 0x20, -w2, "-Z",
+ "Zero state file.");
+ fprintf(fp, "\n");
+#if 0
+ fprintf(fp, "%*c%*s %s [%s]\n", w1, 0x20, -w2, "-c FILE",
+ "Read configuration from FILE.", ZED_CONF_FILE);
+#endif
+ fprintf(fp, "%*c%*s %s [%s]\n", w1, 0x20, -w2, "-d DIR",
+ "Read enabled scripts from DIR.", ZED_SCRIPT_DIR);
+ fprintf(fp, "%*c%*s %s [%s]\n", w1, 0x20, -w2, "-p FILE",
+ "Write daemon's PID to FILE.", ZED_PID_FILE);
+ fprintf(fp, "%*c%*s %s [%s]\n", w1, 0x20, -w2, "-s FILE",
+ "Write daemon's state to FILE.", ZED_STATE_FILE);
+ fprintf(fp, "\n");
+
+ exit(got_err ? EXIT_FAILURE : EXIT_SUCCESS);
+}
+
+/*
+ * Display license information to stdout and exit.
+ */
+static void
+_zed_conf_display_license(void)
+{
+ const char **pp;
+ const char *text[] = {
+ "The ZFS Event Daemon (ZED) is distributed under the terms of the",
+ " Common Development and Distribution License (CDDL-1.0)",
+ " <http://opensource.org/licenses/CDDL-1.0>.",
+ "Developed at Lawrence Livermore National Laboratory"
+ " (LLNL-CODE-403049).",
+ "Copyright (C) 2013-2014"
+ " Lawrence Livermore National Security, LLC.",
+ "",
+ NULL
+ };
+
+ for (pp = text; *pp; pp++)
+ printf("%s\n", *pp);
+
+ exit(EXIT_SUCCESS);
+}
+
+/*
+ * Display version information to stdout and exit.
+ */
+static void
+_zed_conf_display_version(void)
+{
+ printf("%s-%s-%s\n",
+ ZFS_META_NAME, ZFS_META_VERSION, ZFS_META_RELEASE);
+
+ exit(EXIT_SUCCESS);
+}
+
+/*
+ * 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));
+}
+
+/*
+ * 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->script_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;
+ }
+ }
+}
+
+/*
+ * 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));
+}
+
+/*
+ * Scan the [zcp] script_dir for files to exec based on the event class.
+ * Files must be executable by user, but not writable by group or other.
+ * Dotfiles are ignored.
+ * Return 0 on success with an updated set of scripts,
+ * or -1 on error with errno set.
+ * FIXME: Check if script_dir and all parent dirs are secure.
+ */
+int
+zed_conf_scan_dir(struct zed_conf *zcp)
+{
+ zed_strings_t *scripts;
+ DIR *dirp;
+ struct dirent *direntp;
+ char pathname[PATH_MAX];
+ struct stat st;
+ int n;
+
+ if (!zcp) {
+ errno = EINVAL;
+ zed_log_msg(LOG_ERR, "Failed to scan script dir: %s",
+ strerror(errno));
+ return (-1);
+ }
+ scripts = zed_strings_create();
+ if (!scripts) {
+ errno = ENOMEM;
+ zed_log_msg(LOG_WARNING, "Failed to scan dir \"%s\": %s",
+ zcp->script_dir, strerror(errno));
+ return (-1);
+ }
+ dirp = opendir(zcp->script_dir);
+ if (!dirp) {
+ int errno_bak = errno;
+ zed_log_msg(LOG_WARNING, "Failed to open dir \"%s\": %s",
+ zcp->script_dir, strerror(errno));
+ zed_strings_destroy(scripts);
+ errno = errno_bak;
+ return (-1);
+ }
+ while ((direntp = readdir(dirp))) {
+ if (direntp->d_name[0] == '.')
+ continue;
+
+ n = snprintf(pathname, sizeof (pathname),
+ "%s/%s", zcp->script_dir, direntp->d_name);
+ if ((n < 0) || (n >= sizeof (pathname))) {
+ zed_log_msg(LOG_WARNING, "Failed to stat \"%s\": %s",
+ direntp->d_name, strerror(ENAMETOOLONG));
+ continue;
+ }
+ if (stat(pathname, &st) < 0) {
+ zed_log_msg(LOG_WARNING, "Failed to stat \"%s\": %s",
+ pathname, strerror(errno));
+ continue;
+ }
+ if (!S_ISREG(st.st_mode)) {
+ zed_log_msg(LOG_INFO,
+ "Ignoring \"%s\": not a regular file",
+ direntp->d_name);
+ continue;
+ }
+ if ((st.st_uid != 0) && !zcp->do_force) {
+ zed_log_msg(LOG_NOTICE,
+ "Ignoring \"%s\": not owned by root",
+ direntp->d_name);
+ continue;
+ }
+ if (!(st.st_mode & S_IXUSR)) {
+ zed_log_msg(LOG_INFO,
+ "Ignoring \"%s\": not executable by user",
+ direntp->d_name);
+ continue;
+ }
+ if ((st.st_mode & S_IWGRP) & !zcp->do_force) {
+ zed_log_msg(LOG_NOTICE,
+ "Ignoring \"%s\": writable by group",
+ direntp->d_name);
+ continue;
+ }
+ if ((st.st_mode & S_IWOTH) & !zcp->do_force) {
+ zed_log_msg(LOG_NOTICE,
+ "Ignoring \"%s\": writable by other",
+ direntp->d_name);
+ continue;
+ }
+ if (zed_strings_add(scripts, direntp->d_name) < 0) {
+ zed_log_msg(LOG_WARNING,
+ "Failed to register \"%s\": %s",
+ direntp->d_name, strerror(errno));
+ continue;
+ }
+ if (zcp->do_verbose)
+ zed_log_msg(LOG_INFO,
+ "Registered script \"%s\"", direntp->d_name);
+ }
+ if (closedir(dirp) < 0) {
+ int errno_bak = errno;
+ zed_log_msg(LOG_WARNING, "Failed to close dir \"%s\": %s",
+ zcp->script_dir, strerror(errno));
+ zed_strings_destroy(scripts);
+ errno = errno_bak;
+ return (-1);
+ }
+ if (zcp->scripts)
+ zed_strings_destroy(zcp->scripts);
+
+ zcp->scripts = scripts;
+ return (0);
+}
+
+/*
+ * 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.
+ */
+int
+zed_conf_write_pid(struct zed_conf *zcp)
+{
+ char dirbuf[PATH_MAX];
+ int n;
+ char *p;
+ mode_t mask;
+ FILE *fp;
+
+ if (!zcp || !zcp->pid_file) {
+ errno = EINVAL;
+ zed_log_msg(LOG_ERR, "Failed to write pid file: %s",
+ strerror(errno));
+ return (-1);
+ }
+ n = strlcpy(dirbuf, zcp->pid_file, sizeof (dirbuf));
+ if (n >= sizeof (dirbuf)) {
+ errno = ENAMETOOLONG;
+ zed_log_msg(LOG_WARNING, "Failed to write pid file: %s",
+ strerror(errno));
+ return (-1);
+ }
+ p = strrchr(dirbuf, '/');
+ if (p)
+ *p = '\0';
+
+ /* FIXME: Replace with mkdirp()? (lib/libspl/mkdirp.c) */
+ if (zed_file_create_dirs(dirbuf) < 0)
+ return (-1);
+
+ (void) unlink(zcp->pid_file);
+
+ mask = umask(0);
+ umask(mask | 022);
+ fp = fopen(zcp->pid_file, "w");
+ umask(mask);
+
+ if (!fp) {
+ zed_log_msg(LOG_WARNING, "Failed to open pid file \"%s\": %s",
+ zcp->pid_file, strerror(errno));
+ } else if (fprintf(fp, "%d\n", (int) getpid()) == EOF) {
+ zed_log_msg(LOG_WARNING, "Failed to write pid file \"%s\": %s",
+ zcp->pid_file, strerror(errno));
+ } else if (fclose(fp) == EOF) {
+ zed_log_msg(LOG_WARNING, "Failed to close pid file \"%s\": %s",
+ zcp->pid_file, strerror(errno));
+ } else {
+ return (0);
+ }
+ (void) unlink(zcp->pid_file);
+ return (-1);
+}
+
+/*
+ * Open and lock the [zcp] state_file.
+ * Return 0 on success, -1 on error.
+ * FIXME: If state_file exists, verify ownership & permissions.
+ * FIXME: Move lock to pid_file instead.
+ */
+int
+zed_conf_open_state(struct zed_conf *zcp)
+{
+ char dirbuf[PATH_MAX];
+ int n;
+ char *p;
+ int rv;
+
+ if (!zcp || !zcp->state_file) {
+ errno = EINVAL;
+ zed_log_msg(LOG_ERR, "Failed to open state file: %s",
+ strerror(errno));
+ return (-1);
+ }
+ n = strlcpy(dirbuf, zcp->state_file, sizeof (dirbuf));
+ if (n >= sizeof (dirbuf)) {
+ errno = ENAMETOOLONG;
+ zed_log_msg(LOG_WARNING, "Failed to open state file: %s",
+ strerror(errno));
+ return (-1);
+ }
+ p = strrchr(dirbuf, '/');
+ if (p)
+ *p = '\0';
+
+ /* FIXME: Replace with mkdirp()? (lib/libspl/mkdirp.c) */
+ if (zed_file_create_dirs(dirbuf) < 0)
+ return (-1);
+
+ if (zcp->state_fd >= 0) {
+ if (close(zcp->state_fd) < 0) {
+ zed_log_msg(LOG_WARNING,
+ "Failed to close state file \"%s\": %s",
+ zcp->state_file, strerror(errno));
+ return (-1);
+ }
+ }
+ if (zcp->do_zero)
+ (void) unlink(zcp->state_file);
+
+ zcp->state_fd = open(zcp->state_file,
+ (O_RDWR | O_CREAT), (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH));
+ if (zcp->state_fd < 0) {
+ zed_log_msg(LOG_WARNING, "Failed to open state file \"%s\": %s",
+ zcp->state_file, strerror(errno));
+ return (-1);
+ }
+ rv = zed_file_lock(zcp->state_fd);
+ if (rv < 0) {
+ zed_log_msg(LOG_WARNING, "Failed to lock state file \"%s\": %s",
+ zcp->state_file, strerror(errno));
+ return (-1);
+ }
+ if (rv > 0) {
+ pid_t pid = zed_file_is_locked(zcp->state_fd);
+ if (pid < 0) {
+ zed_log_msg(LOG_WARNING,
+ "Failed to test lock on state file \"%s\"",
+ zcp->state_file);
+ } else if (pid > 0) {
+ zed_log_msg(LOG_WARNING,
+ "Found pid %d bound to state file \"%s\"",
+ pid, zcp->state_file);
+ } else {
+ zed_log_msg(LOG_WARNING,
+ "Inconsistent lock state on state file \"%s\"",
+ zcp->state_file);
+ }
+ return (-1);
+ }
+ return (0);
+}
+
+/*
+ * Read the opened [zcp] state_file to obtain the eid & etime
+ * of the last event processed.
+ * Write the state from the last event to the [eidp] & [etime] args
+ * passed by reference.
+ * Note that etime[] is an array of size 2.
+ * Return 0 on success, -1 on error.
+ */
+int
+zed_conf_read_state(struct zed_conf *zcp, uint64_t *eidp, int64_t etime[])
+{
+ ssize_t len;
+ struct iovec iov[3];
+ ssize_t n;
+
+ if (!zcp || !eidp || !etime) {
+ errno = EINVAL;
+ zed_log_msg(LOG_ERR,
+ "Failed to read state file: %s", strerror(errno));
+ return (-1);
+ }
+ if (lseek(zcp->state_fd, 0, SEEK_SET) == (off_t) -1) {
+ zed_log_msg(LOG_WARNING,
+ "Failed to reposition state file offset: %s",
+ strerror(errno));
+ return (-1);
+ }
+ len = 0;
+ iov[0].iov_base = eidp;
+ len += iov[0].iov_len = sizeof (*eidp);
+ iov[1].iov_base = &etime[0];
+ len += iov[1].iov_len = sizeof (etime[0]);
+ iov[2].iov_base = &etime[1];
+ len += iov[2].iov_len = sizeof (etime[1]);
+
+ n = readv(zcp->state_fd, iov, 3);
+ if (n == 0) {
+ *eidp = 0;
+ } else if (n < 0) {
+ zed_log_msg(LOG_WARNING,
+ "Failed to read state file \"%s\": %s",
+ zcp->state_file, strerror(errno));
+ return (-1);
+ } else if (n != len) {
+ errno = EIO;
+ zed_log_msg(LOG_WARNING,
+ "Failed to read state file \"%s\": Read %d of %d bytes",
+ zcp->state_file, n, len);
+ return (-1);
+ }
+ return (0);
+}
+
+/*
+ * Write the [eid] & [etime] of the last processed event to the opened
+ * [zcp] state_file.
+ * Note that etime[] is an array of size 2.
+ * Return 0 on success, -1 on error.
+ */
+int
+zed_conf_write_state(struct zed_conf *zcp, uint64_t eid, int64_t etime[])
+{
+ ssize_t len;
+ struct iovec iov[3];
+ ssize_t n;
+
+ if (!zcp) {
+ errno = EINVAL;
+ zed_log_msg(LOG_ERR,
+ "Failed to write state file: %s", strerror(errno));
+ return (-1);
+ }
+ if (lseek(zcp->state_fd, 0, SEEK_SET) == (off_t) -1) {
+ zed_log_msg(LOG_WARNING,
+ "Failed to reposition state file offset: %s",
+ strerror(errno));
+ return (-1);
+ }
+ len = 0;
+ iov[0].iov_base = &eid;
+ len += iov[0].iov_len = sizeof (eid);
+ iov[1].iov_base = &etime[0];
+ len += iov[1].iov_len = sizeof (etime[0]);
+ iov[2].iov_base = &etime[1];
+ len += iov[2].iov_len = sizeof (etime[1]);
+
+ n = writev(zcp->state_fd, iov, 3);
+ if (n < 0) {
+ zed_log_msg(LOG_WARNING,
+ "Failed to write state file \"%s\": %s",
+ zcp->state_file, strerror(errno));
+ return (-1);
+ }
+ if (n != len) {
+ errno = EIO;
+ zed_log_msg(LOG_WARNING,
+ "Failed to write state file \"%s\": Wrote %d of %d bytes",
+ zcp->state_file, n, len);
+ return (-1);
+ }
+ if (fdatasync(zcp->state_fd) < 0) {
+ zed_log_msg(LOG_WARNING,
+ "Failed to sync state file \"%s\": %s",
+ zcp->state_file, strerror(errno));
+ return (-1);
+ }
+ return (0);
+}
diff --git a/cmd/zed/zed_conf.h b/cmd/zed/zed_conf.h
new file mode 100644
index 000000000..51b98ea76
--- /dev/null
+++ b/cmd/zed/zed_conf.h
@@ -0,0 +1,71 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License (the "License").
+ * You may not use this file except in compliance with the License.
+ *
+ * You can obtain a copy of the license from the top-level
+ * OPENSOLARIS.LICENSE or <http://opensource.org/licenses/CDDL-1.0>.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each file
+ * and include the License file from the top-level OPENSOLARIS.LICENSE.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information: Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ */
+
+/*
+ * Developed at Lawrence Livermore National Laboratory (LLNL-CODE-403049).
+ * Copyright (C) 2013-2014 Lawrence Livermore National Security, LLC.
+ */
+
+#ifndef ZED_CONF_H
+#define ZED_CONF_H
+
+#include <libzfs.h>
+#include <stdint.h>
+#include "zed_strings.h"
+
+struct zed_conf {
+ unsigned do_force:1; /* true if force enabled */
+ unsigned do_foreground:1; /* true if run in foreground */
+ unsigned do_memlock:1; /* true if locking memory */
+ unsigned do_verbose:1; /* true if verbosity enabled */
+ unsigned do_zero:1; /* true if zeroing state */
+ int syslog_facility; /* syslog facility value */
+ int min_events; /* RESERVED FOR FUTURE USE */
+ int max_events; /* RESERVED FOR FUTURE USE */
+ char *conf_file; /* abs path to config file */
+ char *pid_file; /* abs path to pid file */
+ char *script_dir; /* abs path to script dir */
+ zed_strings_t *scripts; /* names of enabled scripts */
+ char *state_file; /* abs path to state file */
+ int state_fd; /* fd to state file */
+ libzfs_handle_t *zfs_hdl; /* handle to libzfs */
+ int zevent_fd; /* fd for access to zevents */
+};
+
+struct zed_conf *zed_conf_create(void);
+
+void zed_conf_destroy(struct zed_conf *zcp);
+
+void zed_conf_parse_opts(struct zed_conf *zcp, int argc, char **argv);
+
+void zed_conf_parse_file(struct zed_conf *zcp);
+
+int zed_conf_scan_dir(struct zed_conf *zcp);
+
+int zed_conf_write_pid(struct zed_conf *zcp);
+
+int zed_conf_open_state(struct zed_conf *zcp);
+
+int zed_conf_read_state(struct zed_conf *zcp, uint64_t *eidp, int64_t etime[]);
+
+int zed_conf_write_state(struct zed_conf *zcp, uint64_t eid, int64_t etime[]);
+
+#endif /* !ZED_CONF_H */
diff --git a/cmd/zed/zed_event.c b/cmd/zed/zed_event.c
new file mode 100644
index 000000000..e504aefb9
--- /dev/null
+++ b/cmd/zed/zed_event.c
@@ -0,0 +1,829 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License (the "License").
+ * You may not use this file except in compliance with the License.
+ *
+ * You can obtain a copy of the license from the top-level
+ * OPENSOLARIS.LICENSE or <http://opensource.org/licenses/CDDL-1.0>.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each file
+ * and include the License file from the top-level OPENSOLARIS.LICENSE.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information: Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ */
+
+/*
+ * Developed at Lawrence Livermore National Laboratory (LLNL-CODE-403049).
+ * Copyright (C) 2013-2014 Lawrence Livermore National Security, LLC.
+ */
+
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <libzfs.h> /* FIXME: Replace with libzfs_core. */
+#include <paths.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/zfs_ioctl.h>
+#include <time.h>
+#include <unistd.h>
+#include "zed.h"
+#include "zed_conf.h"
+#include "zed_exec.h"
+#include "zed_file.h"
+#include "zed_log.h"
+#include "zed_strings.h"
+
+/*
+ * 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));
+}
+
+/*
+ * 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;
+ }
+}
+
+/*
+ * Seek to the event specified by [saved_eid] and [saved_etime].
+ * This protects against processing a given event more than once.
+ * Return 0 upon a successful seek to the specified event, or -1 otherwise.
+ * A zevent is considered to be uniquely specified by its (eid,time) tuple.
+ * The unsigned 64b eid is set to 1 when the kernel module is loaded, and
+ * incremented by 1 for each new event. Since the state file can persist
+ * across a kernel module reload, the time must be checked to ensure a match.
+ */
+int
+zed_event_seek(struct zed_conf *zcp, uint64_t saved_eid, int64_t saved_etime[])
+{
+ uint64_t eid;
+ int found;
+ nvlist_t *nvl;
+ int n_dropped;
+ int64_t *etime;
+ uint_t nelem;
+ int rv;
+
+ if (!zcp) {
+ errno = EINVAL;
+ zed_log_msg(LOG_ERR, "Failed to seek zevent: %s",
+ strerror(errno));
+ return (-1);
+ }
+ eid = 0;
+ found = 0;
+ while ((eid < saved_eid) && !found) {
+ rv = zpool_events_next(zcp->zfs_hdl, &nvl, &n_dropped,
+ ZEVENT_NONBLOCK, zcp->zevent_fd);
+
+ if ((rv != 0) || !nvl)
+ break;
+
+ if (n_dropped > 0) {
+ zed_log_msg(LOG_WARNING, "Missed %d events", n_dropped);
+ /*
+ * FIXME: Increase max size of event nvlist in
+ * /sys/module/zfs/parameters/zfs_zevent_len_max ?
+ */
+ }
+ if (nvlist_lookup_uint64(nvl, "eid", &eid) != 0) {
+ zed_log_msg(LOG_WARNING, "Failed to lookup zevent eid");
+ } else if (nvlist_lookup_int64_array(nvl, "time",
+ &etime, &nelem) != 0) {
+ zed_log_msg(LOG_WARNING,
+ "Failed to lookup zevent time (eid=%llu)", eid);
+ } else if (nelem != 2) {
+ zed_log_msg(LOG_WARNING,
+ "Failed to lookup zevent time (eid=%llu, nelem=%u)",
+ eid, nelem);
+ } else if ((eid != saved_eid) ||
+ (etime[0] != saved_etime[0]) ||
+ (etime[1] != saved_etime[1])) {
+ /* no-op */
+ } else {
+ found = 1;
+ }
+ free(nvl);
+ }
+ if (!found && (saved_eid > 0)) {
+ if (zpool_events_seek(zcp->zfs_hdl, ZEVENT_SEEK_START,
+ zcp->zevent_fd) < 0)
+ zed_log_msg(LOG_WARNING, "Failed to seek to eid=0");
+ else
+ eid = 0;
+ }
+ zed_log_msg(LOG_NOTICE, "Processing events since eid=%llu", eid);
+ return (found ? 0 : -1);
+}
+
+static int
+_zed_event_convert_int8_array(char *buf, int buflen, nvpair_t *nvp)
+{
+ int8_t *i8p;
+ uint_t nelem;
+ uint_t i;
+ char *p;
+ int n;
+
+ assert(buf != NULL);
+
+ (void) nvpair_value_int8_array(nvp, &i8p, &nelem);
+ for (i = 0, p = buf; (i < nelem) && (buflen > 0); i++) {
+ n = snprintf(p, buflen, "%d ", i8p[i]);
+ if ((n < 0) || (n >= buflen)) {
+ *buf = '\0';
+ return (-1);
+ }
+ p += n;
+ buflen -= n;
+ }
+ if (nelem > 0)
+ *--p = '\0';
+
+ return (p - buf);
+}
+
+static int
+_zed_event_convert_uint8_array(char *buf, int buflen, nvpair_t *nvp)
+{
+ uint8_t *u8p;
+ uint_t nelem;
+ uint_t i;
+ char *p;
+ int n;
+
+ assert(buf != NULL);
+
+ (void) nvpair_value_uint8_array(nvp, &u8p, &nelem);
+ for (i = 0, p = buf; (i < nelem) && (buflen > 0); i++) {
+ n = snprintf(p, buflen, "%u ", u8p[i]);
+ if ((n < 0) || (n >= buflen)) {
+ *buf = '\0';
+ return (-1);
+ }
+ p += n;
+ buflen -= n;
+ }
+ if (nelem > 0)
+ *--p = '\0';
+
+ return (p - buf);
+}
+
+static int
+_zed_event_convert_int16_array(char *buf, int buflen, nvpair_t *nvp)
+{
+ int16_t *i16p;
+ uint_t nelem;
+ uint_t i;
+ char *p;
+ int n;
+
+ assert(buf != NULL);
+
+ (void) nvpair_value_int16_array(nvp, &i16p, &nelem);
+ for (i = 0, p = buf; (i < nelem) && (buflen > 0); i++) {
+ n = snprintf(p, buflen, "%d ", i16p[i]);
+ if ((n < 0) || (n >= buflen)) {
+ *buf = '\0';
+ return (-1);
+ }
+ p += n;
+ buflen -= n;
+ }
+ if (nelem > 0)
+ *--p = '\0';
+
+ return (p - buf);
+}
+
+static int
+_zed_event_convert_uint16_array(char *buf, int buflen, nvpair_t *nvp)
+{
+ uint16_t *u16p;
+ uint_t nelem;
+ uint_t i;
+ char *p;
+ int n;
+
+ assert(buf != NULL);
+
+ (void) nvpair_value_uint16_array(nvp, &u16p, &nelem);
+ for (i = 0, p = buf; (i < nelem) && (buflen > 0); i++) {
+ n = snprintf(p, buflen, "%u ", u16p[i]);
+ if ((n < 0) || (n >= buflen)) {
+ *buf = '\0';
+ return (-1);
+ }
+ p += n;
+ buflen -= n;
+ }
+ if (nelem > 0)
+ *--p = '\0';
+
+ return (p - buf);
+}
+
+static int
+_zed_event_convert_int32_array(char *buf, int buflen, nvpair_t *nvp)
+{
+ int32_t *i32p;
+ uint_t nelem;
+ uint_t i;
+ char *p;
+ int n;
+
+ assert(buf != NULL);
+
+ (void) nvpair_value_int32_array(nvp, &i32p, &nelem);
+ for (i = 0, p = buf; (i < nelem) && (buflen > 0); i++) {
+ n = snprintf(p, buflen, "%d ", i32p[i]);
+ if ((n < 0) || (n >= buflen)) {
+ *buf = '\0';
+ return (-1);
+ }
+ p += n;
+ buflen -= n;
+ }
+ if (nelem > 0)
+ *--p = '\0';
+
+ return (p - buf);
+}
+
+static int
+_zed_event_convert_uint32_array(char *buf, int buflen, nvpair_t *nvp)
+{
+ uint32_t *u32p;
+ uint_t nelem;
+ uint_t i;
+ char *p;
+ int n;
+
+ assert(buf != NULL);
+
+ (void) nvpair_value_uint32_array(nvp, &u32p, &nelem);
+ for (i = 0, p = buf; (i < nelem) && (buflen > 0); i++) {
+ n = snprintf(p, buflen, "%u ", u32p[i]);
+ if ((n < 0) || (n >= buflen)) {
+ *buf = '\0';
+ return (-1);
+ }
+ p += n;
+ buflen -= n;
+ }
+ if (nelem > 0)
+ *--p = '\0';
+
+ return (p - buf);
+}
+
+static int
+_zed_event_convert_int64_array(char *buf, int buflen, nvpair_t *nvp)
+{
+ int64_t *i64p;
+ uint_t nelem;
+ uint_t i;
+ char *p;
+ int n;
+
+ assert(buf != NULL);
+
+ (void) nvpair_value_int64_array(nvp, &i64p, &nelem);
+ for (i = 0, p = buf; (i < nelem) && (buflen > 0); i++) {
+ n = snprintf(p, buflen, "%lld ", (u_longlong_t) i64p[i]);
+ if ((n < 0) || (n >= buflen)) {
+ *buf = '\0';
+ return (-1);
+ }
+ p += n;
+ buflen -= n;
+ }
+ if (nelem > 0)
+ *--p = '\0';
+
+ return (p - buf);
+}
+
+static int
+_zed_event_convert_uint64_array(char *buf, int buflen, nvpair_t *nvp,
+ const char *fmt)
+{
+ uint64_t *u64p;
+ uint_t nelem;
+ uint_t i;
+ char *p;
+ int n;
+
+ assert(buf != NULL);
+
+ (void) nvpair_value_uint64_array(nvp, &u64p, &nelem);
+ for (i = 0, p = buf; (i < nelem) && (buflen > 0); i++) {
+ n = snprintf(p, buflen, fmt, (u_longlong_t) u64p[i]);
+ if ((n < 0) || (n >= buflen)) {
+ *buf = '\0';
+ return (-1);
+ }
+ p += n;
+ buflen -= n;
+ }
+ if (nelem > 0)
+ *--p = '\0';
+
+ return (p - buf);
+}
+
+static int
+_zed_event_convert_string_array(char *buf, int buflen, nvpair_t *nvp)
+{
+ char **strp;
+ uint_t nelem;
+ uint_t i;
+ char *p;
+ int n;
+
+ assert(buf != NULL);
+
+ (void) nvpair_value_string_array(nvp, &strp, &nelem);
+ for (i = 0, p = buf; (i < nelem) && (buflen > 0); i++) {
+ n = snprintf(p, buflen, "%s ", strp[i] ? strp[i] : "<NULL>");
+ if ((n < 0) || (n >= buflen)) {
+ *buf = '\0';
+ return (-1);
+ }
+ p += n;
+ buflen -= n;
+ }
+ if (nelem > 0)
+ *--p = '\0';
+
+ return (p - buf);
+}
+
+/*
+ * Return non-zero if nvpair [name] should be formatted in hex; o/w, return 0.
+ */
+static int
+_zed_event_value_is_hex(const char *name)
+{
+ const char *hex_suffix[] = {
+ "_guid",
+ "_guids",
+ NULL
+ };
+ const char **pp;
+ char *p;
+
+ if (!name)
+ return (0);
+
+ for (pp = hex_suffix; *pp; pp++) {
+ p = strstr(name, *pp);
+ if (p && strlen(p) == strlen(*pp))
+ return (1);
+ }
+ return (0);
+}
+
+/*
+ * Convert the nvpair [nvp] to a string which is added to the environment
+ * of the child process.
+ * Return 0 on success, -1 on error.
+ * FIXME: Refactor with cmd/zpool/zpool_main.c:zpool_do_events_nvprint()?
+ */
+static void
+_zed_event_add_nvpair(uint64_t eid, zed_strings_t *zsp, nvpair_t *nvp)
+{
+ const char *name;
+ data_type_t type;
+ char buf[4096];
+ int buflen;
+ int n;
+ char *p;
+ const char *q;
+ const char *fmt;
+
+ boolean_t b;
+ double d;
+ uint8_t i8;
+ uint16_t i16;
+ uint32_t i32;
+ uint64_t i64;
+ char *str;
+
+ assert(zsp != NULL);
+ assert(nvp != NULL);
+
+ name = nvpair_name(nvp);
+ type = nvpair_type(nvp);
+ buflen = sizeof (buf);
+
+ /* Copy NAME prefix for ZED zevent namespace. */
+ n = strlcpy(buf, ZEVENT_VAR_PREFIX, sizeof (buf));
+ if (n >= sizeof (buf)) {
+ zed_log_msg(LOG_WARNING,
+ "Failed to convert nvpair \"%s\" for eid=%llu: %s",
+ name, eid, "Exceeded buffer size");
+ return;
+ }
+ buflen -= n;
+ p = buf + n;
+
+ /* Convert NAME to alphanumeric uppercase. */
+ for (q = name; *q && (buflen > 0); q++) {
+ *p++ = isalnum(*q) ? toupper(*q) : '_';
+ buflen--;
+ }
+
+ /* Separate NAME from VALUE. */
+ if (buflen > 0) {
+ *p++ = '=';
+ buflen--;
+ }
+ *p = '\0';
+
+ /* Convert VALUE. */
+ switch (type) {
+ case DATA_TYPE_BOOLEAN:
+ n = snprintf(p, buflen, "%s", "1");
+ break;
+ case DATA_TYPE_BOOLEAN_VALUE:
+ (void) nvpair_value_boolean_value(nvp, &b);
+ n = snprintf(p, buflen, "%s", b ? "1" : "0");
+ break;
+ case DATA_TYPE_BYTE:
+ (void) nvpair_value_byte(nvp, &i8);
+ n = snprintf(p, buflen, "%d", i8);
+ break;
+ case DATA_TYPE_INT8:
+ (void) nvpair_value_int8(nvp, (int8_t *) &i8);
+ n = snprintf(p, buflen, "%d", i8);
+ break;
+ case DATA_TYPE_UINT8:
+ (void) nvpair_value_uint8(nvp, &i8);
+ n = snprintf(p, buflen, "%u", i8);
+ break;
+ case DATA_TYPE_INT16:
+ (void) nvpair_value_int16(nvp, (int16_t *) &i16);
+ n = snprintf(p, buflen, "%d", i16);
+ break;
+ case DATA_TYPE_UINT16:
+ (void) nvpair_value_uint16(nvp, &i16);
+ n = snprintf(p, buflen, "%u", i16);
+ break;
+ case DATA_TYPE_INT32:
+ (void) nvpair_value_int32(nvp, (int32_t *) &i32);
+ n = snprintf(p, buflen, "%d", i32);
+ break;
+ case DATA_TYPE_UINT32:
+ (void) nvpair_value_uint32(nvp, &i32);
+ n = snprintf(p, buflen, "%u", i32);
+ break;
+ case DATA_TYPE_INT64:
+ (void) nvpair_value_int64(nvp, (int64_t *) &i64);
+ n = snprintf(p, buflen, "%lld", (longlong_t) i64);
+ break;
+ case DATA_TYPE_UINT64:
+ (void) nvpair_value_uint64(nvp, &i64);
+ fmt = _zed_event_value_is_hex(name) ? "0x%.16llX" : "%llu";
+ n = snprintf(p, buflen, fmt, (u_longlong_t) i64);
+ break;
+ case DATA_TYPE_DOUBLE:
+ (void) nvpair_value_double(nvp, &d);
+ n = snprintf(p, buflen, "%g", d);
+ break;
+ case DATA_TYPE_HRTIME:
+ (void) nvpair_value_hrtime(nvp, (hrtime_t *) &i64);
+ n = snprintf(p, buflen, "%llu", (u_longlong_t) i64);
+ break;
+ case DATA_TYPE_NVLIST:
+ /* FIXME */
+ n = snprintf(p, buflen, "%s", "_NOT_IMPLEMENTED_");
+ break;
+ case DATA_TYPE_STRING:
+ (void) nvpair_value_string(nvp, &str);
+ n = snprintf(p, buflen, "%s", (str ? str : "<NULL>"));
+ break;
+ case DATA_TYPE_BOOLEAN_ARRAY:
+ /* FIXME */
+ n = snprintf(p, buflen, "%s", "_NOT_IMPLEMENTED_");
+ break;
+ case DATA_TYPE_BYTE_ARRAY:
+ /* FIXME */
+ n = snprintf(p, buflen, "%s", "_NOT_IMPLEMENTED_");
+ break;
+ case DATA_TYPE_INT8_ARRAY:
+ n = _zed_event_convert_int8_array(p, buflen, nvp);
+ break;
+ case DATA_TYPE_UINT8_ARRAY:
+ n = _zed_event_convert_uint8_array(p, buflen, nvp);
+ break;
+ case DATA_TYPE_INT16_ARRAY:
+ n = _zed_event_convert_int16_array(p, buflen, nvp);
+ break;
+ case DATA_TYPE_UINT16_ARRAY:
+ n = _zed_event_convert_uint16_array(p, buflen, nvp);
+ break;
+ case DATA_TYPE_INT32_ARRAY:
+ n = _zed_event_convert_int32_array(p, buflen, nvp);
+ break;
+ case DATA_TYPE_UINT32_ARRAY:
+ n = _zed_event_convert_uint32_array(p, buflen, nvp);
+ break;
+ case DATA_TYPE_INT64_ARRAY:
+ n = _zed_event_convert_int64_array(p, buflen, nvp);
+ break;
+ case DATA_TYPE_UINT64_ARRAY:
+ fmt = _zed_event_value_is_hex(name) ? "0x%.16llX " : "%llu ";
+ n = _zed_event_convert_uint64_array(p, buflen, nvp, fmt);
+ break;
+ case DATA_TYPE_STRING_ARRAY:
+ n = _zed_event_convert_string_array(p, buflen, nvp);
+ break;
+ case DATA_TYPE_NVLIST_ARRAY:
+ /* FIXME */
+ n = snprintf(p, buflen, "%s", "_NOT_IMPLEMENTED_");
+ break;
+ default:
+ zed_log_msg(LOG_WARNING,
+ "Failed to convert nvpair \"%s\" for eid=%llu: "
+ "Unrecognized type=%u", name, eid, (unsigned int) type);
+ return;
+ }
+ if ((n < 0) || (n >= sizeof (buf))) {
+ zed_log_msg(LOG_WARNING,
+ "Failed to convert nvpair \"%s\" for eid=%llu: %s",
+ name, eid, "Exceeded buffer size");
+ return;
+ }
+ if (zed_strings_add(zsp, buf) < 0) {
+ zed_log_msg(LOG_WARNING,
+ "Failed to convert nvpair \"%s\" for eid=%llu: %s",
+ name, eid, strerror(ENOMEM));
+ return;
+ }
+}
+
+/*
+ * Add the environment variable specified by the format string [fmt].
+ */
+static void
+_zed_event_add_var(uint64_t eid, zed_strings_t *zsp, const char *fmt, ...)
+{
+ char buf[4096];
+ va_list vargs;
+ int n;
+ const char *p;
+ size_t namelen;
+
+ assert(zsp != NULL);
+ assert(fmt != NULL);
+
+ va_start(vargs, fmt);
+ n = vsnprintf(buf, sizeof (buf), fmt, vargs);
+ va_end(vargs);
+ p = strchr(buf, '=');
+ namelen = (p) ? p - buf : strlen(buf);
+
+ if ((n < 0) || (n >= sizeof (buf))) {
+ zed_log_msg(LOG_WARNING, "Failed to add %.*s for eid=%llu: %s",
+ namelen, buf, eid, "Exceeded buffer size");
+ } else if (!p) {
+ zed_log_msg(LOG_WARNING, "Failed to add %.*s for eid=%llu: %s",
+ namelen, buf, eid, "Missing assignment");
+ } else if (zed_strings_add(zsp, buf) < 0) {
+ zed_log_msg(LOG_WARNING, "Failed to add %.*s for eid=%llu: %s",
+ namelen, buf, eid, strerror(ENOMEM));
+ }
+}
+
+/*
+ * Restrict various environment variables to safe and sane values
+ * when constructing the environment for the child process.
+ * Reference: Secure Programming Cookbook by Viega & Messier, Section 1.1.
+ */
+static void
+_zed_event_add_env_restrict(uint64_t eid, zed_strings_t *zsp)
+{
+ const char *env_restrict[] = {
+ "IFS= \t\n",
+ "PATH=" _PATH_STDPATH,
+ "ZDB=" SBINDIR "/zdb",
+ "ZED=" SBINDIR "/zed",
+ "ZFS=" SBINDIR "/zfs",
+ "ZINJECT=" SBINDIR "/zinject",
+ "ZPOOL=" SBINDIR "/zpool",
+ "ZFS_ALIAS=" ZFS_META_ALIAS,
+ "ZFS_VERSION=" ZFS_META_VERSION,
+ "ZFS_RELEASE=" ZFS_META_RELEASE,
+ NULL
+ };
+ const char **pp;
+
+ assert(zsp != NULL);
+
+ for (pp = env_restrict; *pp; pp++) {
+ _zed_event_add_var(eid, zsp, "%s", *pp);
+ }
+}
+
+/*
+ * Preserve specified variables from the parent environment
+ * when constructing the environment for the child process.
+ * Reference: Secure Programming Cookbook by Viega & Messier, Section 1.1.
+ */
+static void
+_zed_event_add_env_preserve(uint64_t eid, zed_strings_t *zsp)
+{
+ const char *env_preserve[] = {
+ "TZ",
+ NULL
+ };
+ const char **pp;
+ const char *p;
+
+ assert(zsp != NULL);
+
+ for (pp = env_preserve; *pp; pp++) {
+ if ((p = getenv(*pp)))
+ _zed_event_add_var(eid, zsp, "%s=%s", *pp, p);
+ }
+}
+
+/*
+ * Compute the "subclass" by removing the first 3 components of [class]
+ * (which seem to always be either "ereport.fs.zfs" or "resource.fs.zfs").
+ * Return a pointer inside the string [class], or NULL if insufficient
+ * components exist.
+ */
+static const char *
+_zed_event_get_subclass(const char *class)
+{
+ const char *p;
+ int i;
+
+ if (!class)
+ return (NULL);
+
+ p = class;
+ for (i = 0; i < 3; i++) {
+ p = strchr(p, '.');
+ if (!p)
+ break;
+ p++;
+ }
+ return (p);
+}
+
+/*
+ * Convert the zevent time from a 2-element array of 64b integers
+ * into a more convenient form:
+ * TIME_SECS is the second component of the time.
+ * TIME_NSECS is the nanosecond component of the time.
+ * TIME_STRING is an almost-RFC3339-compliant string representation.
+ */
+static void
+_zed_event_add_time_strings(uint64_t eid, zed_strings_t *zsp, int64_t etime[])
+{
+ struct tm *stp;
+ char buf[32];
+
+ assert(zsp != NULL);
+ assert(etime != NULL);
+
+ _zed_event_add_var(eid, zsp, "%s%s=%lld",
+ ZEVENT_VAR_PREFIX, "TIME_SECS", (long long int) etime[0]);
+ _zed_event_add_var(eid, zsp, "%s%s=%lld",
+ ZEVENT_VAR_PREFIX, "TIME_NSECS", (long long int) etime[1]);
+
+ if (!(stp = localtime((const time_t *) &etime[0]))) {
+ zed_log_msg(LOG_WARNING, "Failed to add %s%s for eid=%llu: %s",
+ ZEVENT_VAR_PREFIX, "TIME_STRING", eid, "localtime error");
+ } else if (!strftime(buf, sizeof (buf), "%Y-%m-%d %H:%M:%S%z", stp)) {
+ zed_log_msg(LOG_WARNING, "Failed to add %s%s for eid=%llu: %s",
+ ZEVENT_VAR_PREFIX, "TIME_STRING", eid, "strftime error");
+ } else {
+ _zed_event_add_var(eid, zsp, "%s%s=%s",
+ ZEVENT_VAR_PREFIX, "TIME_STRING", buf);
+ }
+}
+
+/*
+ * Service the next zevent, blocking until one is available.
+ */
+void
+zed_event_service(struct zed_conf *zcp)
+{
+ nvlist_t *nvl;
+ nvpair_t *nvp;
+ int n_dropped;
+ zed_strings_t *zsp;
+ uint64_t eid;
+ int64_t *etime;
+ uint_t nelem;
+ char *class;
+ const char *subclass;
+ int rv;
+
+ if (!zcp) {
+ errno = EINVAL;
+ zed_log_msg(LOG_ERR, "Failed to service zevent: %s",
+ strerror(errno));
+ return;
+ }
+ rv = zpool_events_next(zcp->zfs_hdl, &nvl, &n_dropped, ZEVENT_NONE,
+ zcp->zevent_fd);
+
+ if ((rv != 0) || !nvl)
+ return;
+
+ if (n_dropped > 0) {
+ zed_log_msg(LOG_WARNING, "Missed %d events", n_dropped);
+ /*
+ * FIXME: Increase max size of event nvlist in
+ * /sys/module/zfs/parameters/zfs_zevent_len_max ?
+ */
+ }
+ if (nvlist_lookup_uint64(nvl, "eid", &eid) != 0) {
+ zed_log_msg(LOG_WARNING, "Failed to lookup zevent eid");
+ } else if (nvlist_lookup_int64_array(
+ nvl, "time", &etime, &nelem) != 0) {
+ zed_log_msg(LOG_WARNING,
+ "Failed to lookup zevent time (eid=%llu)", eid);
+ } else if (nelem != 2) {
+ zed_log_msg(LOG_WARNING,
+ "Failed to lookup zevent time (eid=%llu, nelem=%u)",
+ eid, nelem);
+ } else if (nvlist_lookup_string(nvl, "class", &class) != 0) {
+ zed_log_msg(LOG_WARNING,
+ "Failed to lookup zevent class (eid=%llu)", eid);
+ } else {
+ zsp = zed_strings_create();
+
+ nvp = NULL;
+ while ((nvp = nvlist_next_nvpair(nvl, nvp)))
+ _zed_event_add_nvpair(eid, zsp, nvp);
+
+ _zed_event_add_env_restrict(eid, zsp);
+ _zed_event_add_env_preserve(eid, zsp);
+
+ _zed_event_add_var(eid, zsp, "%s%s=%d",
+ ZED_VAR_PREFIX, "PID", (int) getpid());
+ _zed_event_add_var(eid, zsp, "%s%s=%s",
+ ZED_VAR_PREFIX, "SCRIPT_DIR", zcp->script_dir);
+
+ subclass = _zed_event_get_subclass(class);
+ _zed_event_add_var(eid, zsp, "%s%s=%s",
+ ZEVENT_VAR_PREFIX, "SUBCLASS",
+ (subclass ? subclass : class));
+ _zed_event_add_time_strings(eid, zsp, etime);
+
+ zed_exec_process(eid, class, subclass,
+ zcp->script_dir, zcp->scripts, zsp, zcp->zevent_fd);
+
+ zed_conf_write_state(zcp, eid, etime);
+
+ zed_strings_destroy(zsp);
+ }
+ nvlist_free(nvl);
+}
diff --git a/cmd/zed/zed_event.h b/cmd/zed/zed_event.h
new file mode 100644
index 000000000..71b3a2bab
--- /dev/null
+++ b/cmd/zed/zed_event.h
@@ -0,0 +1,41 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License (the "License").
+ * You may not use this file except in compliance with the License.
+ *
+ * You can obtain a copy of the license from the top-level
+ * OPENSOLARIS.LICENSE or <http://opensource.org/licenses/CDDL-1.0>.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each file
+ * and include the License file from the top-level OPENSOLARIS.LICENSE.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information: Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ */
+
+/*
+ * Developed at Lawrence Livermore National Laboratory (LLNL-CODE-403049).
+ * Copyright (C) 2013-2014 Lawrence Livermore National Security, LLC.
+ */
+
+#ifndef ZED_EVENT_H
+#define ZED_EVENT_H
+
+#include <stdint.h>
+
+void zed_event_init(struct zed_conf *zcp);
+
+void zed_event_fini(struct zed_conf *zcp);
+
+int zed_event_seek(struct zed_conf *zcp, uint64_t saved_eid,
+ int64_t saved_etime[]);
+
+void zed_event_service(struct zed_conf *zcp);
+
+#endif /* !ZED_EVENT_H */
diff --git a/cmd/zed/zed_exec.c b/cmd/zed/zed_exec.c
new file mode 100644
index 000000000..f461b7840
--- /dev/null
+++ b/cmd/zed/zed_exec.c
@@ -0,0 +1,207 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License (the "License").
+ * You may not use this file except in compliance with the License.
+ *
+ * You can obtain a copy of the license from the top-level
+ * OPENSOLARIS.LICENSE or <http://opensource.org/licenses/CDDL-1.0>.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each file
+ * and include the License file from the top-level OPENSOLARIS.LICENSE.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information: Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ */
+
+/*
+ * Developed at Lawrence Livermore National Laboratory (LLNL-CODE-403049).
+ * Copyright (C) 2013-2014 Lawrence Livermore National Security, LLC.
+ */
+
+#include <assert.h>
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <unistd.h>
+#include "zed_file.h"
+#include "zed_log.h"
+#include "zed_strings.h"
+
+#define ZEVENT_FILENO 3
+
+/*
+ * Create an environment string array for passing to execve() using the
+ * NAME=VALUE strings in container [zsp].
+ * Return a newly-allocated environment, or NULL on error.
+ */
+static char **
+_zed_exec_create_env(zed_strings_t *zsp)
+{
+ int num_ptrs;
+ int buflen;
+ char *buf;
+ char **pp;
+ char *p;
+ const char *q;
+ int i;
+ int len;
+
+ num_ptrs = zed_strings_count(zsp) + 1;
+ buflen = num_ptrs * sizeof (char *);
+ for (q = zed_strings_first(zsp); q; q = zed_strings_next(zsp))
+ buflen += strlen(q) + 1;
+
+ buf = malloc(buflen);
+ if (!buf)
+ return (NULL);
+
+ pp = (char **) buf;
+ p = buf + (num_ptrs * sizeof (char *));
+ i = 0;
+ for (q = zed_strings_first(zsp); q; q = zed_strings_next(zsp)) {
+ pp[i] = p;
+ len = strlen(q) + 1;
+ memcpy(p, q, len);
+ p += len;
+ i++;
+ }
+ pp[i] = NULL;
+ assert(buf + buflen == p);
+ return ((char **) buf);
+}
+
+/*
+ * Fork a child process to handle event [eid]. The program [prog]
+ * in directory [dir] is executed with the envionment [env].
+ * The file descriptor [zfd] is the zevent_fd used to track the
+ * current cursor location within the zevent nvlist.
+ */
+static void
+_zed_exec_fork_child(uint64_t eid, const char *dir, const char *prog,
+ char *env[], int zfd)
+{
+ char path[PATH_MAX];
+ int n;
+ pid_t pid;
+ int fd;
+ pid_t wpid;
+ int status;
+
+ assert(dir != NULL);
+ assert(prog != NULL);
+ assert(env != NULL);
+ assert(zfd >= 0);
+
+ n = snprintf(path, sizeof (path), "%s/%s", dir, prog);
+ if ((n < 0) || (n >= sizeof (path))) {
+ zed_log_msg(LOG_WARNING,
+ "Failed to fork \"%s\" for eid=%llu: %s",
+ prog, eid, strerror(ENAMETOOLONG));
+ return;
+ }
+ pid = fork();
+ if (pid < 0) {
+ zed_log_msg(LOG_WARNING,
+ "Failed to fork \"%s\" for eid=%llu: %s",
+ prog, eid, strerror(errno));
+ return;
+ } else if (pid == 0) {
+ (void) umask(022);
+ fd = open("/dev/null", O_RDWR);
+ (void) dup2(fd, STDIN_FILENO);
+ (void) dup2(fd, STDOUT_FILENO);
+ (void) dup2(fd, STDERR_FILENO);
+ (void) dup2(zfd, ZEVENT_FILENO);
+ zed_file_close_from(ZEVENT_FILENO + 1);
+ execle(path, prog, NULL, env);
+ _exit(127);
+ } else {
+ zed_log_msg(LOG_INFO, "Invoking \"%s\" eid=%llu pid=%d",
+ prog, eid, pid);
+ /* FIXME: Timeout rogue child processes with sigalarm? */
+restart:
+ wpid = waitpid(pid, &status, 0);
+ if (wpid == (pid_t) -1) {
+ if (errno == EINTR)
+ goto restart;
+ zed_log_msg(LOG_WARNING,
+ "Failed to wait for \"%s\" eid=%llu pid=%d",
+ prog, eid, pid);
+ } else if (WIFEXITED(status)) {
+ zed_log_msg(LOG_INFO,
+ "Finished \"%s\" eid=%llu pid=%d exit=%d",
+ prog, eid, pid, WEXITSTATUS(status));
+ } else if (WIFSIGNALED(status)) {
+ zed_log_msg(LOG_INFO,
+ "Finished \"%s\" eid=%llu pid=%d sig=%d/%s",
+ prog, eid, pid, WTERMSIG(status),
+ strsignal(WTERMSIG(status)));
+ } else {
+ zed_log_msg(LOG_INFO,
+ "Finished \"%s\" eid=%llu pid=%d status=0x%X",
+ prog, eid, (unsigned int) status);
+ }
+ }
+}
+
+/*
+ * Process the event [eid] by synchronously invoking all scripts with a
+ * matching class prefix.
+ * Each executable in [scripts] from the directory [dir] is matched against
+ * the event's [class], [subclass], and the "all" class (which matches
+ * all events). Every script with a matching class prefix is invoked.
+ * The NAME=VALUE strings in [envs] will be passed to the script as
+ * environment variables.
+ * The file descriptor [zfd] is the zevent_fd used to track the
+ * current cursor location within the zevent nvlist.
+ * Return 0 on success, -1 on error.
+ */
+int
+zed_exec_process(uint64_t eid, const char *class, const char *subclass,
+ const char *dir, zed_strings_t *scripts, zed_strings_t *envs, int zfd)
+{
+ const char *class_strings[4];
+ const char *allclass = "all";
+ const char **csp;
+ const char *s;
+ char **e;
+ int n;
+
+ if (!dir || !scripts || !envs || zfd < 0)
+ return (-1);
+
+ csp = class_strings;
+
+ if (class)
+ *csp++ = class;
+
+ if (subclass)
+ *csp++ = subclass;
+
+ if (allclass)
+ *csp++ = allclass;
+
+ *csp = NULL;
+
+ e = _zed_exec_create_env(envs);
+
+ for (s = zed_strings_first(scripts); s; s = zed_strings_next(scripts)) {
+ for (csp = class_strings; *csp; csp++) {
+ n = strlen(*csp);
+ if ((strncmp(s, *csp, n) == 0) && !isalpha(s[n]))
+ _zed_exec_fork_child(eid, dir, s, e, zfd);
+ }
+ }
+ free(e);
+ return (0);
+}
diff --git a/cmd/zed/zed_exec.h b/cmd/zed/zed_exec.h
new file mode 100644
index 000000000..52bdc12a8
--- /dev/null
+++ b/cmd/zed/zed_exec.h
@@ -0,0 +1,36 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License (the "License").
+ * You may not use this file except in compliance with the License.
+ *
+ * You can obtain a copy of the license from the top-level
+ * OPENSOLARIS.LICENSE or <http://opensource.org/licenses/CDDL-1.0>.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each file
+ * and include the License file from the top-level OPENSOLARIS.LICENSE.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information: Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ */
+
+/*
+ * Developed at Lawrence Livermore National Laboratory (LLNL-CODE-403049).
+ * Copyright (C) 2013-2014 Lawrence Livermore National Security, LLC.
+ */
+
+#ifndef ZED_EXEC_H
+#define ZED_EXEC_H
+
+#include <stdint.h>
+
+int zed_exec_process(uint64_t eid, const char *class, const char *subclass,
+ const char *dir, zed_strings_t *scripts, zed_strings_t *envs,
+ int zevent_fd);
+
+#endif /* !ZED_EXEC_H */
diff --git a/cmd/zed/zed_file.c b/cmd/zed/zed_file.c
new file mode 100644
index 000000000..d73e64976
--- /dev/null
+++ b/cmd/zed/zed_file.c
@@ -0,0 +1,316 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License (the "License").
+ * You may not use this file except in compliance with the License.
+ *
+ * You can obtain a copy of the license from the top-level
+ * OPENSOLARIS.LICENSE or <http://opensource.org/licenses/CDDL-1.0>.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each file
+ * and include the License file from the top-level OPENSOLARIS.LICENSE.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information: Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ */
+
+/*
+ * Developed at Lawrence Livermore National Laboratory (LLNL-CODE-403049).
+ * Copyright (C) 2013-2014 Lawrence Livermore National Security, LLC.
+ */
+
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <string.h>
+#include <sys/resource.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include "zed_log.h"
+
+/*
+ * Read up to [n] bytes from [fd] into [buf].
+ * Return the number of bytes read, 0 on EOF, or -1 on error.
+ */
+ssize_t
+zed_file_read_n(int fd, void *buf, size_t n)
+{
+ unsigned char *p;
+ size_t n_left;
+ ssize_t n_read;
+
+ p = buf;
+ n_left = n;
+ while (n_left > 0) {
+ if ((n_read = read(fd, p, n_left)) < 0) {
+ if (errno == EINTR)
+ continue;
+ else
+ return (-1);
+
+ } else if (n_read == 0) {
+ break;
+ }
+ n_left -= n_read;
+ p += n_read;
+ }
+ return (n - n_left);
+}
+
+/*
+ * Write [n] bytes from [buf] out to [fd].
+ * Return the number of bytes written, or -1 on error.
+ */
+ssize_t
+zed_file_write_n(int fd, void *buf, size_t n)
+{
+ const unsigned char *p;
+ size_t n_left;
+ ssize_t n_written;
+
+ p = buf;
+ n_left = n;
+ while (n_left > 0) {
+ if ((n_written = write(fd, p, n_left)) < 0) {
+ if (errno == EINTR)
+ continue;
+ else
+ return (-1);
+
+ }
+ n_left -= n_written;
+ p += n_written;
+ }
+ return (n);
+}
+
+/*
+ * Set an exclusive advisory lock on the open file descriptor [fd].
+ * Return 0 on success, 1 if a conflicting lock is held by another process,
+ * or -1 on error (with errno set).
+ */
+int
+zed_file_lock(int fd)
+{
+ struct flock lock;
+
+ if (fd < 0) {
+ errno = EBADF;
+ return (-1);
+ }
+ lock.l_type = F_WRLCK;
+ lock.l_whence = SEEK_SET;
+ lock.l_start = 0;
+ lock.l_len = 0;
+
+ if (fcntl(fd, F_SETLK, &lock) < 0) {
+ if ((errno == EACCES) || (errno == EAGAIN))
+ return (1);
+
+ return (-1);
+ }
+ return (0);
+}
+
+/*
+ * Release an advisory lock held on the open file descriptor [fd].
+ * Return 0 on success, or -1 on error (with errno set).
+ */
+int
+zed_file_unlock(int fd)
+{
+ struct flock lock;
+
+ if (fd < 0) {
+ errno = EBADF;
+ return (-1);
+ }
+ lock.l_type = F_UNLCK;
+ lock.l_whence = SEEK_SET;
+ lock.l_start = 0;
+ lock.l_len = 0;
+
+ if (fcntl(fd, F_SETLK, &lock) < 0)
+ return (-1);
+
+ return (0);
+}
+
+/*
+ * Test whether an exclusive advisory lock could be obtained for the open
+ * file descriptor [fd].
+ * Return 0 if the file is not locked, >0 for the pid of another process
+ * holding a conflicting lock, or -1 on error (with errno set).
+ */
+pid_t
+zed_file_is_locked(int fd)
+{
+ struct flock lock;
+
+ if (fd < 0) {
+ errno = EBADF;
+ return (-1);
+ }
+ lock.l_type = F_WRLCK;
+ lock.l_whence = SEEK_SET;
+ lock.l_start = 0;
+ lock.l_len = 0;
+
+ if (fcntl(fd, F_GETLK, &lock) < 0)
+ return (-1);
+
+ if (lock.l_type == F_UNLCK)
+ return (0);
+
+ return (lock.l_pid);
+}
+
+/*
+ * Close all open file descriptors greater than or equal to [lowfd].
+ * Any errors encountered while closing file descriptors are ignored.
+ */
+void
+zed_file_close_from(int lowfd)
+{
+ const int maxfd_def = 256;
+ int errno_bak;
+ struct rlimit rl;
+ int maxfd;
+ int fd;
+
+ errno_bak = errno;
+
+ if (getrlimit(RLIMIT_NOFILE, &rl) < 0) {
+ maxfd = maxfd_def;
+ } else if (rl.rlim_max == RLIM_INFINITY) {
+ maxfd = maxfd_def;
+ } else {
+ maxfd = rl.rlim_max;
+ }
+ for (fd = lowfd; fd < maxfd; fd++)
+ (void) close(fd);
+
+ errno = errno_bak;
+}
+
+/*
+ * Set the CLOEXEC flag on file descriptor [fd] so it will be automatically
+ * closed upon successful execution of one of the exec functions.
+ * Return 0 on success, or -1 on error.
+ * FIXME: No longer needed?
+ */
+int
+zed_file_close_on_exec(int fd)
+{
+ int flags;
+
+ if (fd < 0) {
+ errno = EBADF;
+ return (-1);
+ }
+ flags = fcntl(fd, F_GETFD);
+ if (flags == -1)
+ return (-1);
+
+ flags |= FD_CLOEXEC;
+
+ if (fcntl(fd, F_SETFD, flags) == -1)
+ return (-1);
+
+ return (0);
+}
+
+/*
+ * Create the directory [dir_name] and any missing parent directories.
+ * Directories will be created with permissions 0755 modified by the umask.
+ * Return 0 on success, or -1 on error.
+ * FIXME: Deprecate in favor of mkdirp(). (lib/libspl/mkdirp.c)
+ */
+int
+zed_file_create_dirs(const char *dir_name)
+{
+ struct stat st;
+ char dir_buf[PATH_MAX];
+ mode_t dir_mode = S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH;
+ char *p;
+
+ if ((dir_name == NULL) || (dir_name[0] == '\0')) {
+ zed_log_msg(LOG_WARNING,
+ "Failed to create directory: no directory specified");
+ errno = EINVAL;
+ return (-1);
+ }
+ if (dir_name[0] != '/') {
+ zed_log_msg(LOG_WARNING,
+ "Failed to create directory \"%s\": not absolute path",
+ dir_name);
+ errno = EINVAL;
+ return (-1);
+ }
+ /* Check if directory already exists. */
+ if (stat(dir_name, &st) == 0) {
+ if (S_ISDIR(st.st_mode))
+ return (0);
+
+ errno = EEXIST;
+ zed_log_msg(LOG_WARNING,
+ "Failed to create directory \"%s\": %s",
+ dir_name, strerror(errno));
+ return (-1);
+ }
+ /* Create copy for modification. */
+ if (strlen(dir_name) >= sizeof (dir_buf)) {
+ errno = ENAMETOOLONG;
+ zed_log_msg(LOG_WARNING,
+ "Failed to create directory \"%s\": %s",
+ dir_name, strerror(errno));
+ return (-1);
+ }
+ strncpy(dir_buf, dir_name, sizeof (dir_buf));
+
+ /* Remove trailing slashes. */
+ p = dir_buf + strlen(dir_buf) - 1;
+ while ((p > dir_buf) && (*p == '/'))
+ *p-- = '\0';
+
+ /* Process directory components starting from the root dir. */
+ p = dir_buf;
+
+ while (1) {
+
+ /* Skip over adjacent slashes. */
+ while (*p == '/')
+ p++;
+
+ /* Advance to the next path component. */
+ p = strchr(p, '/');
+ if (p != NULL)
+ *p = '\0';
+
+ /* Create directory. */
+ if (mkdir(dir_buf, dir_mode) < 0) {
+
+ int mkdir_errno = errno;
+
+ if ((mkdir_errno == EEXIST) ||
+ (stat(dir_buf, &st) < 0) ||
+ (!S_ISDIR(st.st_mode))) {
+ zed_log_msg(LOG_WARNING,
+ "Failed to create directory \"%s\": %s",
+ dir_buf, strerror(mkdir_errno));
+ return (-1);
+ }
+ }
+ if (p == NULL)
+ break;
+
+ *p++ = '/';
+ }
+ return (0);
+}
diff --git a/cmd/zed/zed_file.h b/cmd/zed/zed_file.h
new file mode 100644
index 000000000..6a31dc3d6
--- /dev/null
+++ b/cmd/zed/zed_file.h
@@ -0,0 +1,49 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License (the "License").
+ * You may not use this file except in compliance with the License.
+ *
+ * You can obtain a copy of the license from the top-level
+ * OPENSOLARIS.LICENSE or <http://opensource.org/licenses/CDDL-1.0>.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each file
+ * and include the License file from the top-level OPENSOLARIS.LICENSE.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information: Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ */
+
+/*
+ * Developed at Lawrence Livermore National Laboratory (LLNL-CODE-403049).
+ * Copyright (C) 2013-2014 Lawrence Livermore National Security, LLC.
+ */
+
+#ifndef ZED_FILE_H
+#define ZED_FILE_H
+
+#include <sys/types.h>
+#include <unistd.h>
+
+ssize_t zed_file_read_n(int fd, void *buf, size_t n);
+
+ssize_t zed_file_write_n(int fd, void *buf, size_t n);
+
+int zed_file_lock(int fd);
+
+int zed_file_unlock(int fd);
+
+pid_t zed_file_is_locked(int fd);
+
+void zed_file_close_from(int fd);
+
+int zed_file_close_on_exec(int fd);
+
+int zed_file_create_dirs(const char *dir_name);
+
+#endif /* !ZED_FILE_H */
diff --git a/cmd/zed/zed_log.c b/cmd/zed/zed_log.c
new file mode 100644
index 000000000..bc432bc21
--- /dev/null
+++ b/cmd/zed/zed_log.c
@@ -0,0 +1,171 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License (the "License").
+ * You may not use this file except in compliance with the License.
+ *
+ * You can obtain a copy of the license from the top-level
+ * OPENSOLARIS.LICENSE or <http://opensource.org/licenses/CDDL-1.0>.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each file
+ * and include the License file from the top-level OPENSOLARIS.LICENSE.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information: Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ */
+
+/*
+ * Developed at Lawrence Livermore National Laboratory (LLNL-CODE-403049).
+ * Copyright (C) 2013-2014 Lawrence Livermore National Security, LLC.
+ */
+
+#include <assert.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+#include "zed_log.h"
+
+#define ZED_LOG_MAX_ID_LEN 64
+#define ZED_LOG_MAX_LOG_LEN 1024
+
+static struct {
+ unsigned do_stderr:1;
+ unsigned do_syslog:1;
+ int level;
+ char id[ZED_LOG_MAX_ID_LEN];
+} _ctx;
+
+void
+zed_log_init(const char *identity)
+{
+ const char *p;
+
+ if (identity) {
+ p = (p = strrchr(identity, '/')) ? p + 1 : identity;
+ strlcpy(_ctx.id, p, sizeof (_ctx.id));
+ } else {
+ _ctx.id[0] = '\0';
+ }
+}
+
+void
+zed_log_fini()
+{
+ if (_ctx.do_syslog) {
+ closelog();
+ }
+}
+
+void
+zed_log_stderr_open(int level)
+{
+ _ctx.do_stderr = 1;
+ _ctx.level = level;
+}
+
+void
+zed_log_stderr_close(void)
+{
+ _ctx.do_stderr = 0;
+}
+
+void
+zed_log_syslog_open(int facility)
+{
+ const char *identity;
+
+ _ctx.do_syslog = 1;
+ identity = (_ctx.id[0] == '\0') ? NULL : _ctx.id;
+ openlog(identity, LOG_NDELAY, facility);
+}
+
+void
+zed_log_syslog_close(void)
+{
+ _ctx.do_syslog = 0;
+ closelog();
+}
+
+static void
+_zed_log_aux(int priority, const char *fmt, va_list vargs)
+{
+ char buf[ZED_LOG_MAX_LOG_LEN];
+ char *syslogp;
+ char *p;
+ int len;
+ int n;
+
+ assert(fmt != NULL);
+
+ syslogp = NULL;
+ p = buf;
+ len = sizeof (buf);
+
+ if (_ctx.id[0] != '\0') {
+ n = snprintf(p, len, "%s: ", _ctx.id);
+ if ((n < 0) || (n >= len)) {
+ p += len - 1;
+ len = 0;
+ } else {
+ p += n;
+ len -= n;
+ }
+ }
+ if ((len > 0) && fmt) {
+ syslogp = p;
+ n = vsnprintf(p, len, fmt, vargs);
+ if ((n < 0) || (n >= len)) {
+ p += len - 1;
+ len = 0;
+ } else {
+ p += n;
+ len -= n;
+ }
+ }
+ *p = '\0';
+
+ if (_ctx.do_syslog && syslogp)
+ syslog(priority, "%s", syslogp);
+
+ if (_ctx.do_stderr && priority <= _ctx.level)
+ fprintf(stderr, "%s\n", buf);
+}
+
+/*
+ * Log a message at the given [priority] level specified by the printf-style
+ * format string [fmt].
+ */
+void
+zed_log_msg(int priority, const char *fmt, ...)
+{
+ va_list vargs;
+
+ if (fmt) {
+ va_start(vargs, fmt);
+ _zed_log_aux(priority, fmt, vargs);
+ va_end(vargs);
+ }
+}
+
+/*
+ * Log a fatal error message specified by the printf-style format string [fmt].
+ */
+void
+zed_log_die(const char *fmt, ...)
+{
+ va_list vargs;
+
+ if (fmt) {
+ va_start(vargs, fmt);
+ _zed_log_aux(LOG_ERR, fmt, vargs);
+ va_end(vargs);
+ }
+ exit(EXIT_FAILURE);
+}
diff --git a/cmd/zed/zed_log.h b/cmd/zed/zed_log.h
new file mode 100644
index 000000000..7ae4549fe
--- /dev/null
+++ b/cmd/zed/zed_log.h
@@ -0,0 +1,48 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License (the "License").
+ * You may not use this file except in compliance with the License.
+ *
+ * You can obtain a copy of the license from the top-level
+ * OPENSOLARIS.LICENSE or <http://opensource.org/licenses/CDDL-1.0>.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each file
+ * and include the License file from the top-level OPENSOLARIS.LICENSE.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information: Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ */
+
+/*
+ * Developed at Lawrence Livermore National Laboratory (LLNL-CODE-403049).
+ * Copyright (C) 2013-2014 Lawrence Livermore National Security, LLC.
+ */
+
+#ifndef ZED_LOG_H
+#define ZED_LOG_H
+
+#include <syslog.h>
+
+void zed_log_init(const char *identity);
+
+void zed_log_fini(void);
+
+void zed_log_stderr_open(int level);
+
+void zed_log_stderr_close(void);
+
+void zed_log_syslog_open(int facility);
+
+void zed_log_syslog_close(void);
+
+void zed_log_msg(int priority, const char *fmt, ...);
+
+void zed_log_die(const char *fmt, ...);
+
+#endif /* !ZED_LOG_H */
diff --git a/cmd/zed/zed_strings.c b/cmd/zed/zed_strings.c
new file mode 100644
index 000000000..05a374055
--- /dev/null
+++ b/cmd/zed/zed_strings.c
@@ -0,0 +1,200 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License (the "License").
+ * You may not use this file except in compliance with the License.
+ *
+ * You can obtain a copy of the license from the top-level
+ * OPENSOLARIS.LICENSE or <http://opensource.org/licenses/CDDL-1.0>.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each file
+ * and include the License file from the top-level OPENSOLARIS.LICENSE.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information: Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ */
+
+/*
+ * Developed at Lawrence Livermore National Laboratory (LLNL-CODE-403049).
+ * Copyright (C) 2013-2014 Lawrence Livermore National Security, LLC.
+ */
+
+#include <assert.h>
+#include <errno.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/avl.h>
+#include <sys/sysmacros.h>
+#include "zed_strings.h"
+
+struct zed_strings {
+ avl_tree_t tree;
+ avl_node_t *iteratorp;
+};
+
+struct zed_strings_node {
+ avl_node_t node;
+ char string[];
+};
+
+typedef struct zed_strings_node zed_strings_node_t;
+
+/*
+ * Compare zed_strings_node_t nodes [x1] and [x2].
+ * As required for the AVL tree, return exactly
+ * -1 for <, 0 for ==, and +1 for >.
+ */
+static int
+_zed_strings_node_compare(const void *x1, const void *x2)
+{
+ const char *s1;
+ const char *s2;
+ int rv;
+
+ assert(x1 != NULL);
+ assert(x2 != NULL);
+
+ s1 = ((const zed_strings_node_t *) x1)->string;
+ assert(s1 != NULL);
+ s2 = ((const zed_strings_node_t *) x2)->string;
+ assert(s2 != NULL);
+ rv = strcmp(s1, s2);
+
+ if (rv < 0)
+ return (-1);
+
+ if (rv > 0)
+ return (1);
+
+ return (0);
+}
+
+/*
+ * Return a new string container, or NULL on error.
+ */
+zed_strings_t *
+zed_strings_create(void)
+{
+ zed_strings_t *zsp;
+
+ zsp = malloc(sizeof (*zsp));
+ if (!zsp)
+ return (NULL);
+
+ memset(zsp, 0, sizeof (*zsp));
+ avl_create(&zsp->tree, _zed_strings_node_compare,
+ sizeof (zed_strings_node_t), offsetof(zed_strings_node_t, node));
+
+ zsp->iteratorp = NULL;
+ return (zsp);
+}
+
+/*
+ * Destroy the string container [zsp] and all strings within.
+ */
+void
+zed_strings_destroy(zed_strings_t *zsp)
+{
+ void *cookie;
+ zed_strings_node_t *np;
+
+ if (!zsp)
+ return;
+
+ cookie = NULL;
+ while ((np = avl_destroy_nodes(&zsp->tree, &cookie)))
+ free(np);
+
+ avl_destroy(&zsp->tree);
+ free(zsp);
+}
+
+/*
+ * Add a copy of the string [s] to the container [zsp].
+ * Return 0 on success, or -1 on error.
+ * FIXME: Handle dup strings.
+ */
+int
+zed_strings_add(zed_strings_t *zsp, const char *s)
+{
+ size_t len;
+ zed_strings_node_t *np;
+
+ if (!zsp || !s) {
+ errno = EINVAL;
+ return (-1);
+ }
+ len = sizeof (zed_strings_node_t) + strlen(s) + 1;
+ np = malloc(len);
+ if (!np)
+ return (-1);
+
+ memset(np, 0, len);
+ assert((char *) np->string + strlen(s) < (char *) np + len);
+ (void) strcpy(np->string, s);
+ avl_add(&zsp->tree, np);
+ return (0);
+}
+
+/*
+ * Return the first string in container [zsp].
+ * Return NULL if there are no strings, or on error.
+ * This can be called multiple times to re-traverse [zsp].
+ * XXX: Not thread-safe.
+ */
+const char *
+zed_strings_first(zed_strings_t *zsp)
+{
+ if (!zsp) {
+ errno = EINVAL;
+ return (NULL);
+ }
+ zsp->iteratorp = avl_first(&zsp->tree);
+ if (!zsp->iteratorp)
+ return (NULL);
+
+ return (((zed_strings_node_t *) zsp->iteratorp)->string);
+
+}
+
+/*
+ * Return the next string in container [zsp].
+ * Return NULL after the last string, or on error.
+ * This must be called after zed_strings_first().
+ * XXX: Not thread-safe.
+ */
+const char *
+zed_strings_next(zed_strings_t *zsp)
+{
+ if (!zsp) {
+ errno = EINVAL;
+ return (NULL);
+ }
+ if (!zsp->iteratorp)
+ return (NULL);
+
+ zsp->iteratorp = AVL_NEXT(&zsp->tree, zsp->iteratorp);
+ if (!zsp->iteratorp)
+ return (NULL);
+
+ return (((zed_strings_node_t *)zsp->iteratorp)->string);
+}
+
+/*
+ * Return the number of strings in container [zsp], or -1 on error.
+ */
+int
+zed_strings_count(zed_strings_t *zsp)
+{
+ if (!zsp) {
+ errno = EINVAL;
+ return (-1);
+ }
+ return (avl_numnodes(&zsp->tree));
+}
diff --git a/cmd/zed/zed_strings.h b/cmd/zed/zed_strings.h
new file mode 100644
index 000000000..c1ea804bb
--- /dev/null
+++ b/cmd/zed/zed_strings.h
@@ -0,0 +1,44 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License (the "License").
+ * You may not use this file except in compliance with the License.
+ *
+ * You can obtain a copy of the license from the top-level
+ * OPENSOLARIS.LICENSE or <http://opensource.org/licenses/CDDL-1.0>.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each file
+ * and include the License file from the top-level OPENSOLARIS.LICENSE.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information: Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ */
+
+/*
+ * Developed at Lawrence Livermore National Laboratory (LLNL-CODE-403049).
+ * Copyright (C) 2013-2014 Lawrence Livermore National Security, LLC.
+ */
+
+#ifndef ZED_STRINGS_H
+#define ZED_STRINGS_H
+
+typedef struct zed_strings zed_strings_t;
+
+zed_strings_t * zed_strings_create(void);
+
+void zed_strings_destroy(zed_strings_t *zsp);
+
+int zed_strings_add(zed_strings_t *zsp, const char *s);
+
+const char * zed_strings_first(zed_strings_t *zsp);
+
+const char * zed_strings_next(zed_strings_t *zsp);
+
+int zed_strings_count(zed_strings_t *zsp);
+
+#endif /* !ZED_STRINGS_H */