Index: cddl/usr.sbin/Makefile
===================================================================
--- cddl/usr.sbin/Makefile
+++ cddl/usr.sbin/Makefile
@@ -7,6 +7,7 @@
 	${_plockstat} \
 	${_tests} \
 	${_zdb} \
+	${_zfsd} \
 	${_zhack}
 
 .if ${MK_TESTS} != "no"
@@ -18,6 +19,9 @@
 _zdb=	zdb
 _zhack=	zhack
 .endif
+. if ${MK_CXX} != "no"
+_zfsd=	zfsd
+. endif
 .endif
 
 .if ${MACHINE_ARCH} == "amd64" || ${MACHINE_ARCH} == "i386"
Index: cddl/usr.sbin/zfsd/Makefile
===================================================================
--- /dev/null
+++ cddl/usr.sbin/zfsd/Makefile
@@ -0,0 +1,16 @@
+# $FreeBSD$
+
+SRCDIR=${.CURDIR}/../../..
+.include "Makefile.common"
+
+PROG_CXX=	zfsd
+MAN=		zfsd.8
+
+.include <bsd.prog.mk>
+
+# Check for the existence of the googletest and googlemock header files, which
+# come from ports.  Don't compile the tests without them.
+.if exists(${LOCALBASE}/include/gtest/gtest.h) && exists(${LOCALBASE}/include/gmock/gmock.h)
+.else
+SUBDIR=
+.endif
Index: cddl/usr.sbin/zfsd/Makefile.common
===================================================================
--- /dev/null
+++ cddl/usr.sbin/zfsd/Makefile.common
@@ -0,0 +1,42 @@
+# $FreeBSD$
+
+SRCS=		callout.cc		\
+		case_file.cc		\
+		zfsd_event.cc		\
+		vdev.cc			\
+		vdev_iterator.cc	\
+		zfsd.cc			\
+		zfsd_exception.cc	\
+		zpool_list.cc		\
+		zfsd_main.cc
+
+WARNS?=		3
+
+# Ignore warnings about Solaris specific pragmas.
+IGNORE_PRAGMA=  YES
+
+INCFLAGS+= -I${SRCDIR}/cddl/contrib/opensolaris/lib/libzpool/common
+INCFLAGS+= -I${SRCDIR}/cddl/compat/opensolaris/include
+INCFLAGS+= -I${SRCDIR}/cddl/compat/opensolaris/lib/libumem
+INCFLAGS+= -I${SRCDIR}/sys/cddl/compat/opensolaris
+INCFLAGS+= -I${SRCDIR}/cddl/contrib/opensolaris/head
+INCFLAGS+= -I${SRCDIR}/cddl/contrib/opensolaris/lib/libuutil/common
+INCFLAGS+= -I${SRCDIR}/cddl/contrib/opensolaris/lib/libumem/common
+INCFLAGS+= -I${SRCDIR}/cddl/contrib/opensolaris/lib/libzfs_core/common
+INCFLAGS+= -I${SRCDIR}/cddl/contrib/opensolaris/lib/libzfs/common
+INCFLAGS+= -I${SRCDIR}/cddl/contrib/opensolaris/lib/libnvpair
+INCFLAGS+= -I${SRCDIR}/sys/cddl/contrib/opensolaris/common/zfs
+INCFLAGS+= -I${SRCDIR}/sys/cddl/contrib/opensolaris/uts/common
+INCFLAGS+= -I${SRCDIR}/sys/cddl/contrib/opensolaris/uts/common/fs/zfs
+INCFLAGS+= -I${SRCDIR}/sys/cddl/contrib/opensolaris/uts/common/sys
+
+CFLAGS= -g -DNEED_SOLARIS_BOOLEAN ${INCFLAGS}
+
+DPADD=  ${LIBDEVDCTL} ${LIBZFS} ${LIBZFS_CORE} ${LIBUTIL} ${LIBGEOM} \
+	${LIBBSDXML} ${LIBSBUF} ${LIBNVPAIR} ${LIBUUTIL}
+LIBADD=  devdctl zfs zfs_core util geom bsdxml sbuf nvpair uutil
+
+cscope:
+	find ${.CURDIR} -type f -a \( -name "*.[ch]" -o -name "*.cc" \) \
+	     > ${.CURDIR}/cscope.files
+	cd ${.CURDIR} && cscope -buq ${INCFLAGS}
Index: cddl/usr.sbin/zfsd/callout.h
===================================================================
--- /dev/null
+++ cddl/usr.sbin/zfsd/callout.h
@@ -0,0 +1,183 @@
+/*-
+ * Copyright (c) 2011 Spectra Logic Corporation
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions, and the following disclaimer,
+ *    without modification.
+ * 2. Redistributions in binary form must reproduce at minimum a disclaimer
+ *    substantially similar to the "NO WARRANTY" disclaimer below
+ *    ("Disclaimer") and any redistribution must be conditioned upon
+ *    including a substantially similar Disclaimer requirement for further
+ *    binary redistribution.
+ *
+ * NO WARRANTY
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDERS OR CONTRIBUTORS BE LIABLE FOR SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
+ * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGES.
+ *
+ * Authors: Justin T. Gibbs     (Spectra Logic Corporation)
+ */
+
+/**
+ * \file callout.h
+ *
+ * \brief Interface for timer based callback services.
+ *
+ * Header requirements:
+ *
+ *     #include <sys/time.h>
+ *
+ *     #include <list>
+ */
+
+#ifndef _CALLOUT_H_
+#define _CALLOUT_H_
+
+/**
+ * \brief Type of the function callback from a Callout.
+ */
+typedef void CalloutFunc_t(void *);
+
+/**
+ * \brief Interface to a schedulable one-shot timer with the granularity
+ *        of the system clock (see setitimer(2)).
+ *
+ * Determination of callback expiration is triggered by the SIGALRM
+ * signal.  Callout callbacks are always delivered from Zfsd's event
+ * processing loop.
+ *
+ * Periodic actions can be triggered via the Callout mechanisms by
+ * resetting the Callout from within its callback.
+ */
+class Callout
+{
+public:
+
+	/**
+	 * Initialize the Callout subsystem.
+	 */
+	static void Init();
+
+	/**
+	 * Function called (via SIGALRM) when our interval
+	 * timer expires.
+	 */
+	static void AlarmSignalHandler(int);
+
+	/**
+	 * Execute callbacks for all callouts that have the same
+	 * expiration time as the first callout in the list.
+	 */
+	static void ExpireCallouts();
+
+	/** Constructor. */
+	Callout();
+
+	/**
+	 * Returns true if callout has not been stopped,
+	 * or deactivated since the last time the callout was
+	 * reset.
+	 */
+	bool IsActive() const;
+
+	/**
+	 * Returns true if callout is still waiting to expire.
+	 */
+	bool IsPending() const;
+
+	/**
+	 * Disestablish a callout.
+	 */
+	bool Stop();
+
+	/**
+	 * \brief Establish or change a timeout.
+	 *
+	 * \param interval  Timeval indicating the time which must elapse
+	 *                  before this callout fires.
+	 * \param func      Pointer to the callback funtion
+	 * \param arg       Argument pointer to pass to callback function
+	 *
+	 * \return  Cancellation status.
+	 *             true:  The previous callback was pending and therefore
+	 *                    was cancelled.
+	 *             false: The callout was not pending at the time of this
+	 *                    reset request.
+	 *          In all cases, a new callout is established.
+	 */
+	bool  Reset(const timeval &interval, CalloutFunc_t *func, void *arg);
+
+	/**
+	 * \brief Calculate the remaining time until this Callout's timer
+	 *        expires.
+	 *
+	 * The return value will be slightly greater than the actual time to
+	 * expiry.
+	 *
+	 * If the callout is not pending, returns INT_MAX.
+	 */
+	timeval TimeRemaining() const;
+
+private:
+	/**
+	 * All active callouts sorted by expiration time.  The callout
+	 * with the nearest expiration time is at the head of the list.
+	 */
+	static std::list<Callout *> s_activeCallouts;
+
+	/**
+	 * The interval timer has expired.  This variable is set from
+	 * signal handler context and tested from Zfsd::EventLoop()
+	 * context via ExpireCallouts().
+	 */
+	static bool                 s_alarmFired;
+
+	/**
+	 * Time, relative to others in the active list, until
+	 * this callout is fired.
+	 */
+	timeval                     m_interval;
+
+	/** Callback function argument. */
+	void                       *m_arg;
+
+	/**
+	 * The callback function associated with this timer
+	 * entry.
+	 */
+	CalloutFunc_t              *m_func;
+
+	/** State of this callout. */
+	bool                        m_pending;
+};
+
+//- Callout public const methods ----------------------------------------------
+inline bool
+Callout::IsPending() const
+{
+	return (m_pending);
+}
+
+//- Callout public methods ----------------------------------------------------
+inline
+Callout::Callout()
+ : m_arg(0),
+   m_func(NULL),
+   m_pending(false)
+{
+	timerclear(&m_interval);
+}
+
+#endif /* CALLOUT_H_ */
Index: cddl/usr.sbin/zfsd/callout.cc
===================================================================
--- /dev/null
+++ cddl/usr.sbin/zfsd/callout.cc
@@ -0,0 +1,217 @@
+/*-
+ * Copyright (c) 2011 Spectra Logic Corporation
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions, and the following disclaimer,
+ *    without modification.
+ * 2. Redistributions in binary form must reproduce at minimum a disclaimer
+ *    substantially similar to the "NO WARRANTY" disclaimer below
+ *    ("Disclaimer") and any redistribution must be conditioned upon
+ *    including a substantially similar Disclaimer requirement for further
+ *    binary redistribution.
+ *
+ * NO WARRANTY
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDERS OR CONTRIBUTORS BE LIABLE FOR SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
+ * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGES.
+ *
+ * Authors: Justin T. Gibbs     (Spectra Logic Corporation)
+ */
+
+/**
+ * \file callout.cc
+ *
+ * \brief Implementation of the Callout class - multi-client
+ *        timer services built on top of the POSIX interval timer.
+ */
+
+#include <sys/time.h>
+
+#include <signal.h>
+#include <syslog.h>
+
+#include <climits>
+#include <list>
+#include <map>
+#include <string>
+
+#include <devdctl/guid.h>
+#include <devdctl/event.h>
+#include <devdctl/event_factory.h>
+#include <devdctl/consumer.h>
+#include <devdctl/exception.h>
+
+#include "callout.h"
+#include "vdev_iterator.h"
+#include "zfsd.h"
+#include "zfsd_exception.h"
+
+std::list<Callout *> Callout::s_activeCallouts;
+bool		     Callout::s_alarmFired(false);
+
+void
+Callout::Init()
+{
+	signal(SIGALRM,  Callout::AlarmSignalHandler);
+}
+
+bool
+Callout::Stop()
+{
+	if (!IsPending())
+		return (false);
+
+	for (std::list<Callout *>::iterator it(s_activeCallouts.begin());
+	     it != s_activeCallouts.end(); it++) {
+		if (*it != this)
+			continue;
+
+		it = s_activeCallouts.erase(it);
+		if (it != s_activeCallouts.end()) {
+
+			/*
+			 * Maintain correct interval for the
+			 * callouts that follow the just removed
+			 * entry.
+			 */
+			timeradd(&(*it)->m_interval, &m_interval,
+				 &(*it)->m_interval);
+		}
+		break;
+	}
+	m_pending = false;
+	return (true);
+}
+
+bool
+Callout::Reset(const timeval &interval, CalloutFunc_t *func, void *arg)
+{
+	bool cancelled(false);
+
+	if (!timerisset(&interval))
+		throw ZfsdException("Callout::Reset: interval of 0");
+
+	cancelled = Stop();
+
+	m_interval = interval;
+	m_func     = func;
+	m_arg      = arg;
+	m_pending  = true;
+
+	std::list<Callout *>::iterator it(s_activeCallouts.begin());
+	for (; it != s_activeCallouts.end(); it++) {
+
+		if (timercmp(&(*it)->m_interval, &m_interval, <=)) {
+			/*
+			 * Decrease our interval by those that come
+			 * before us.
+			 */
+			timersub(&m_interval, &(*it)->m_interval, &m_interval);
+		} else {
+			/*
+			 * Account for the time between the newly
+			 * inserted event and those that follow.
+			 */
+			timersub(&(*it)->m_interval, &m_interval,
+				 &(*it)->m_interval);
+			break;
+		}
+	}
+	s_activeCallouts.insert(it, this);
+
+
+	if (s_activeCallouts.front() == this) {
+		itimerval timerval = { {0, 0}, m_interval };
+
+		setitimer(ITIMER_REAL, &timerval, NULL);
+	}
+
+	return (cancelled);
+}
+
+void
+Callout::AlarmSignalHandler(int)
+{
+	s_alarmFired = true;
+	ZfsDaemon::WakeEventLoop();
+}
+
+void
+Callout::ExpireCallouts()
+{
+	if (!s_alarmFired)
+		return;
+
+	s_alarmFired = false;
+	if (s_activeCallouts.empty()) {
+		/* Callout removal/SIGALRM race was lost. */
+		return;
+	}
+
+	/*
+	 * Expire the first callout (the one we used to set the
+	 * interval timer) as well as any callouts following that
+	 * expire at the same time (have a zero interval from
+	 * the callout before it).
+	 */
+	do {
+		Callout *cur(s_activeCallouts.front());
+		s_activeCallouts.pop_front();
+		cur->m_pending = false;
+		cur->m_func(cur->m_arg);
+	} while (!s_activeCallouts.empty()
+	      && timerisset(&s_activeCallouts.front()->m_interval) == 0);
+
+	if (!s_activeCallouts.empty()) {
+		Callout *next(s_activeCallouts.front());
+		itimerval timerval = { { 0, 0 }, next->m_interval };
+
+		setitimer(ITIMER_REAL, &timerval, NULL);
+	}
+}
+
+timeval
+Callout::TimeRemaining() const
+{
+	/*
+	 * Outline: Add the m_interval for each callout in s_activeCallouts
+	 * ahead of this, except for the first callout.  Add to that the result
+	 * of getitimer (That's because the first callout stores its original
+	 * interval setting while the timer is ticking).
+	 */
+	itimerval timervalToAlarm;
+	timeval timeToExpiry;
+	std::list<Callout *>::iterator it;
+
+	if (!IsPending()) {
+		timeToExpiry.tv_sec = INT_MAX;
+		timeToExpiry.tv_usec = 999999;	/*maximum normalized value*/
+		return (timeToExpiry);
+	}
+
+	timerclear(&timeToExpiry);
+	getitimer(ITIMER_REAL, &timervalToAlarm);
+	timeval& timeToAlarm = timervalToAlarm.it_value;
+	timeradd(&timeToExpiry, &timeToAlarm, &timeToExpiry);
+
+	it =s_activeCallouts.begin();
+	it++;	/*skip the first callout in the list*/
+	for (; it != s_activeCallouts.end(); it++) {
+		timeradd(&timeToExpiry, &(*it)->m_interval, &timeToExpiry);
+		if ((*it) == this)
+			break;
+	}
+	return (timeToExpiry);
+}
Index: cddl/usr.sbin/zfsd/case_file.h
===================================================================
--- /dev/null
+++ cddl/usr.sbin/zfsd/case_file.h
@@ -0,0 +1,426 @@
+/*-
+ * Copyright (c) 2011 Spectra Logic Corporation
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions, and the following disclaimer,
+ *    without modification.
+ * 2. Redistributions in binary form must reproduce at minimum a disclaimer
+ *    substantially similar to the "NO WARRANTY" disclaimer below
+ *    ("Disclaimer") and any redistribution must be conditioned upon
+ *    including a substantially similar Disclaimer requirement for further
+ *    binary redistribution.
+ *
+ * NO WARRANTY
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDERS OR CONTRIBUTORS BE LIABLE FOR SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
+ * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGES.
+ *
+ * Authors: Justin T. Gibbs     (Spectra Logic Corporation)
+ *
+ * $FreeBSD$
+ */
+
+/**
+ * \file case_file.h
+ *
+ * CaseFile objects aggregate vdev faults that may require ZFSD action
+ * in order to maintain the health of a ZFS pool.
+ *
+ * Header requirements:
+ *
+ *    #include <list>
+ *
+ *    #include "callout.h"
+ *    #include "zfsd_event.h"
+ */
+#ifndef _CASE_FILE_H_
+#define	_CASE_FILE_H_
+
+/*=========================== Forward Declarations ===========================*/
+class CaseFile;
+class Vdev;
+
+/*============================= Class Definitions ============================*/
+/*------------------------------- CaseFileList -------------------------------*/
+/**
+ * CaseFileList is a specialization of the standard list STL container.
+ */
+typedef std::list< CaseFile *> CaseFileList;
+
+/*--------------------------------- CaseFile ---------------------------------*/
+/**
+ * A CaseFile object is instantiated anytime a vdev for an active pool
+ * experiences an I/O error, is faulted by ZFS, or is determined to be
+ * missing/removed.
+ *
+ * A vdev may have at most one CaseFile.
+ *
+ * CaseFiles are retired when a vdev leaves an active pool configuration
+ * or an action is taken to resolve the issues recorded in the CaseFile.
+ *
+ * Logging a case against a vdev does not imply that an immediate action
+ * to resolve a fault is required or even desired.  For example, a CaseFile
+ * must accumulate a number of I/O errors in order to flag a device as
+ * degraded.
+ *
+ * Vdev I/O errors are not recorded in ZFS label inforamation.  For this
+ * reasons, CaseFile%%s with accumulated I/O error events are serialized
+ * to the file system so that they survive across boots.  Currently all
+ * other fault types can be reconstructed from ZFS label information, so
+ * CaseFile%%s for missing, faulted, or degradded members are just recreated
+ * at ZFSD startup instead of being deserialized from the file system.
+ */
+class CaseFile
+{
+public:
+	/**
+	 * \brief Find a CaseFile object by a vdev's pool/vdev GUID tuple.
+	 *
+	 * \param poolGUID  Pool GUID for the vdev of the CaseFile to find.
+	 * \param vdevGUID  Vdev GUID for the vdev of the CaseFile to find.
+	 *
+	 * \return  If found, a pointer to a valid CaseFile object.
+	 *          Otherwise NULL.
+	 */
+	static CaseFile *Find(DevdCtl::Guid poolGUID, DevdCtl::Guid vdevGUID);
+
+	/**
+	 * \brief Find a CaseFile object by a vdev's current/last known
+	 *        physical path.
+	 *
+	 * \param physPath  Physical path of the vdev of the CaseFile to find.
+	 *
+	 * \return  If found, a pointer to a valid CaseFile object.
+	 *          Otherwise NULL.
+	 */
+	static CaseFile *Find(const string &physPath);
+
+	/**
+	 * \brief ReEvaluate all open cases whose pool guid matches the argument
+	 *
+	 * \param poolGUID	Only reevaluate cases for this pool
+	 * \param event		Try to consume this event with the casefile
+	 */
+	static void ReEvaluateByGuid(DevdCtl::Guid poolGUID,
+				     const ZfsEvent &event);
+
+	/**
+	 * \brief Create or return an existing active CaseFile for the
+	 *        specified vdev.
+	 *
+	 * \param vdev  The vdev object for which to find/create a CaseFile.
+	 *
+	 * \return  A reference to a valid CaseFile object.
+	 */
+	static CaseFile &Create(Vdev &vdev);
+
+	/**
+	 * \brief Deserialize all serialized CaseFile objects found in
+	 *        the file system.
+	 */
+	static void      DeSerialize();
+
+	/**
+	 * \brief Emit syslog data on all active CaseFile%%s in the system.
+	 */
+	static void      LogAll();
+
+	/**
+	 * \brief Destroy the in-core cache of CaseFile data.
+	 *
+	 * This routine does not disturb the on disk, serialized, CaseFile
+	 * data.
+	 */
+	static void      PurgeAll();
+
+	DevdCtl::Guid PoolGUID()       const;
+	DevdCtl::Guid VdevGUID()       const;
+	vdev_state    VdevState()      const;
+	const string &PoolGUIDString() const;
+	const string &VdevGUIDString() const;
+	const string &PhysicalPath()   const;
+
+	/**
+	 * \brief Attempt to resolve this CaseFile using the disk
+	 *        resource at the given device/physical path/vdev object
+	 *        tuple.
+	 *
+	 * \param devPath   The devfs path for the disk resource.
+	 * \param physPath  The physical path information reported by
+	 *                  the disk resource.
+	 * \param vdev      If the disk contains ZFS label information,
+	 *                  a pointer to the disk label's vdev object
+	 *                  data.  Otherwise NULL.
+	 *
+	 * \return  True if this event was consumed by this CaseFile.
+	 */
+	bool ReEvaluate(const string &devPath, const string &physPath,
+			Vdev *vdev);
+
+	/**
+	 * \brief Update this CaseFile in light of the provided ZfsEvent.
+	 *
+	 * Must be virtual so it can be overridden in the unit tests
+	 *
+	 * \param event  The ZfsEvent to evaluate.
+	 *
+	 * \return  True if this event was consumed by this CaseFile.
+	 */
+	virtual bool ReEvaluate(const ZfsEvent &event);
+
+	/**
+	 * \brief Register an itimer callout for the given event, if necessary
+	 */
+	virtual void RegisterCallout(const DevdCtl::Event &event);
+
+	/**
+	 * \brief Close a case if it is no longer relevant.
+	 *
+	 * This method deals with cases tracking soft errors.  Soft errors
+	 * will be discarded should a remove event occur within a short period
+	 * of the soft errors being reported.  We also discard the events
+	 * if the vdev is marked degraded or failed.
+	 *
+	 * \return  True if the case is closed.  False otherwise.
+	 */
+	bool CloseIfSolved();
+
+	/**
+	 * \brief Emit data about this CaseFile via syslog(3).
+	 */
+	void Log();
+
+	/**
+	 * \brief Whether we should degrade this vdev
+	 */
+	bool ShouldDegrade() const;
+
+	/**
+	 * \brief Whether we should fault this vdev
+	 */
+	bool ShouldFault() const;
+
+protected:
+	enum {
+		/**
+		 * The number of soft errors on a vdev required
+		 * to transition a vdev from healthy to degraded
+		 * status.
+		 */
+		ZFS_DEGRADE_IO_COUNT = 50
+	};
+
+	static CalloutFunc_t OnGracePeriodEnded;
+
+	/**
+	 * \brief scandir(3) filter function used to find files containing
+	 *        serialized CaseFile data.
+	 *
+	 * \param dirEntry  Directory entry for the file to filter.
+	 *
+	 * \return  Non-zero for a file to include in the selection,
+	 *          otherwise 0.
+	 */
+	static int  DeSerializeSelector(const struct dirent *dirEntry);
+
+	/**
+	 * \brief Given the name of a file containing serialized events from a
+	 *        CaseFile object, create/update an in-core CaseFile object
+	 *        representing the serialized data.
+	 *
+	 * \param fileName  The name of a file containing serialized events
+	 *                  from a CaseFile object.
+	 */
+	static void DeSerializeFile(const char *fileName);
+
+	/** Constructor. */
+	CaseFile(const Vdev &vdev);
+
+	/**
+	 * Destructor.
+	 * Must be virtual so it can be subclassed in the unit tests
+	 */
+	virtual ~CaseFile();
+
+	/**
+	 * \brief Reload state for the vdev associated with this CaseFile.
+	 *
+	 * \return  True if the refresh was successful.  False if the system
+	 *          has no record of the pool or vdev for this CaseFile.
+	 */
+	virtual bool RefreshVdevState();
+
+	/**
+	 * \brief Free all events in the m_events list.
+	 */
+	void PurgeEvents();
+
+	/**
+	 * \brief Free all events in the m_tentativeEvents list.
+	 */
+	void PurgeTentativeEvents();
+
+	/**
+	 * \brief Commit to file system storage.
+	 */
+	void Serialize();
+
+	/**
+	 * \brief Retrieve event data from a serialization stream.
+	 *
+	 * \param caseStream  The serializtion stream to parse.
+	 */
+	void DeSerialize(std::ifstream &caseStream);
+
+	/**
+	 * \brief Serializes the supplied event list and writes it to fd
+	 *
+	 * \param prefix  If not NULL, this prefix will be prepended to
+	 *                every event in the file.
+	 */
+	void SerializeEvList(const DevdCtl::EventList events, int fd,
+			     const char* prefix=NULL) const;
+
+	/**
+	 * \brief Unconditionally close a CaseFile.
+	 */
+	virtual void Close();
+
+	/**
+	 * \brief Callout callback invoked when the remove timer grace
+	 *        period expires.
+	 *
+	 * If no remove events are received prior to the grace period
+	 * firing, then any tentative events are promoted and counted
+	 * against the health of the vdev.
+	 */
+	void OnGracePeriodEnded();
+
+	/**
+	 * \brief Attempt to activate a spare on this case's pool.
+	 *
+	 * Call this whenever a pool becomes degraded.  It will look for any
+	 * spare devices and activate one to replace the casefile's vdev.  It
+	 * will _not_ close the casefile; that should only happen when the
+	 * missing drive is replaced or the user promotes the spare.
+	 *
+	 * \return True if a spare was activated
+	 */
+	bool ActivateSpare();
+
+	/**
+	 * \brief replace a pool's vdev with another
+	 *
+	 * \param vdev_type   The type of the new vdev.  Usually either
+	 *                    VDEV_TYPE_DISK or VDEV_TYPE_FILE
+	 * \param path        The file system path to the new vdev
+	 * \param isspare     Whether the new vdev is a spare
+	 *
+	 * \return            true iff the replacement was successful
+	 */
+	bool Replace(const char* vdev_type, const char* path, bool isspare);
+
+	/**
+	 * \brief Which vdev, if any, is replacing ours.
+	 *
+	 * \param zhp		Pool handle state from the caller context
+	 *
+	 * \return		the vdev that is currently replacing ours,
+	 *			or NonexistentVdev if there isn't one.
+	 */
+	Vdev BeingReplacedBy(zpool_handle_t *zhp);
+
+	/**
+	 * \brief All CaseFiles being tracked by ZFSD.
+	 */
+	static CaseFileList  s_activeCases;
+
+	/**
+	 * \brief The file system path to serialized CaseFile data.
+	 */
+	static const string  s_caseFilePath;
+
+	/**
+	 * \brief The time ZFSD waits before promoting a tentative event
+	 *        into a permanent event.
+	 */
+	static const timeval s_removeGracePeriod;
+
+	/**
+	 * \brief A list of soft error events counted against the health of
+	 *        a vdev.
+	 */
+	DevdCtl::EventList m_events;
+
+	/**
+	 * \brief A list of soft error events waiting for a grace period
+	 *        expiration before being counted against the health of
+	 *        a vdev.
+	 */
+	DevdCtl::EventList m_tentativeEvents;
+
+	DevdCtl::Guid	   m_poolGUID;
+	DevdCtl::Guid	   m_vdevGUID;
+	vdev_state	   m_vdevState;
+	string		   m_poolGUIDString;
+	string		   m_vdevGUIDString;
+	string		   m_vdevPhysPath;
+
+	/**
+	 * \brief Callout activated when a grace period
+	 */
+	Callout		  m_tentativeTimer;
+
+private:
+	nvlist_t	*CaseVdev(zpool_handle_t *zhp)	const;
+};
+
+inline DevdCtl::Guid
+CaseFile::PoolGUID() const
+{
+	return (m_poolGUID);
+}
+
+inline DevdCtl::Guid
+CaseFile::VdevGUID() const
+{
+	return (m_vdevGUID);
+}
+
+inline vdev_state
+CaseFile::VdevState() const
+{
+	return (m_vdevState);
+}
+
+inline const string &
+CaseFile::PoolGUIDString() const
+{
+	return (m_poolGUIDString);
+}
+
+inline const string &
+CaseFile::VdevGUIDString() const
+{
+	return (m_vdevGUIDString);
+}
+
+inline const string &
+CaseFile::PhysicalPath() const
+{
+	return (m_vdevPhysPath);
+}
+
+#endif /* _CASE_FILE_H_ */
Index: cddl/usr.sbin/zfsd/case_file.cc
===================================================================
--- /dev/null
+++ cddl/usr.sbin/zfsd/case_file.cc
@@ -0,0 +1,1104 @@
+/*-
+ * Copyright (c) 2011, 2012, 2013 Spectra Logic Corporation
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions, and the following disclaimer,
+ *    without modification.
+ * 2. Redistributions in binary form must reproduce at minimum a disclaimer
+ *    substantially similar to the "NO WARRANTY" disclaimer below
+ *    ("Disclaimer") and any redistribution must be conditioned upon
+ *    including a substantially similar Disclaimer requirement for further
+ *    binary redistribution.
+ *
+ * NO WARRANTY
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDERS OR CONTRIBUTORS BE LIABLE FOR SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
+ * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGES.
+ *
+ * Authors: Justin T. Gibbs     (Spectra Logic Corporation)
+ */
+
+/**
+ * \file case_file.cc
+ *
+ * We keep case files for any leaf vdev that is not in the optimal state.
+ * However, we only serialize to disk those events that need to be preserved
+ * across reboots.  For now, this is just a log of soft errors which we
+ * accumulate in order to mark a device as degraded.
+ */
+#include <sys/cdefs.h>
+#include <sys/time.h>
+
+#include <sys/fs/zfs.h>
+
+#include <dirent.h>
+#include <iomanip>
+#include <fstream>
+#include <functional>
+#include <sstream>
+#include <syslog.h>
+#include <unistd.h>
+
+#include <libzfs.h>
+
+#include <list>
+#include <map>
+#include <string>
+
+#include <devdctl/guid.h>
+#include <devdctl/event.h>
+#include <devdctl/event_factory.h>
+#include <devdctl/exception.h>
+#include <devdctl/consumer.h>
+
+#include "callout.h"
+#include "vdev_iterator.h"
+#include "zfsd_event.h"
+#include "case_file.h"
+#include "vdev.h"
+#include "zfsd.h"
+#include "zfsd_exception.h"
+#include "zpool_list.h"
+
+__FBSDID("$FreeBSD$");
+
+/*============================ Namespace Control =============================*/
+using std::auto_ptr;
+using std::hex;
+using std::ifstream;
+using std::stringstream;
+using std::setfill;
+using std::setw;
+
+using DevdCtl::Event;
+using DevdCtl::EventBuffer;
+using DevdCtl::EventFactory;
+using DevdCtl::EventList;
+using DevdCtl::Guid;
+using DevdCtl::ParseException;
+
+/*--------------------------------- CaseFile ---------------------------------*/
+//- CaseFile Static Data -------------------------------------------------------
+
+CaseFileList  CaseFile::s_activeCases;
+const string  CaseFile::s_caseFilePath = "/var/db/zfsd/cases";
+const timeval CaseFile::s_removeGracePeriod = { 60 /*sec*/, 0 /*usec*/};
+
+//- CaseFile Static Public Methods ---------------------------------------------
+CaseFile *
+CaseFile::Find(Guid poolGUID, Guid vdevGUID)
+{
+	for (CaseFileList::iterator curCase = s_activeCases.begin();
+	     curCase != s_activeCases.end(); curCase++) {
+
+		if ((*curCase)->PoolGUID() != poolGUID
+		 || (*curCase)->VdevGUID() != vdevGUID)
+			continue;
+
+		/*
+		 * We only carry one active case per-vdev.
+		 */
+		return (*curCase);
+	}
+	return (NULL);
+}
+
+CaseFile *
+CaseFile::Find(const string &physPath)
+{
+	CaseFile *result = NULL;
+
+	for (CaseFileList::iterator curCase = s_activeCases.begin();
+	     curCase != s_activeCases.end(); curCase++) {
+
+		if ((*curCase)->PhysicalPath() != physPath)
+			continue;
+
+		if (result != NULL) {
+			syslog(LOG_WARNING, "Multiple casefiles found for "
+			    "physical path %s.  "
+			    "This is most likely a bug in zfsd",
+			    physPath.c_str());
+		}
+		result = *curCase;
+	}
+	return (result);
+}
+
+
+void
+CaseFile::ReEvaluateByGuid(Guid poolGUID, const ZfsEvent &event)
+{
+	CaseFileList::iterator casefile;
+	for (casefile = s_activeCases.begin(); casefile != s_activeCases.end();){
+		CaseFileList::iterator next = casefile;
+		next++;
+		if (poolGUID == (*casefile)->PoolGUID())
+			(*casefile)->ReEvaluate(event);
+		casefile = next;
+	}
+}
+
+CaseFile &
+CaseFile::Create(Vdev &vdev)
+{
+	CaseFile *activeCase;
+
+	activeCase = Find(vdev.PoolGUID(), vdev.GUID());
+	if (activeCase == NULL)
+		activeCase = new CaseFile(vdev);
+
+	return (*activeCase);
+}
+
+void
+CaseFile::DeSerialize()
+{
+	struct dirent **caseFiles;
+
+	int numCaseFiles(scandir(s_caseFilePath.c_str(), &caseFiles,
+			 DeSerializeSelector, /*compar*/NULL));
+
+	if (numCaseFiles == -1)
+		return;
+	if (numCaseFiles == 0) {
+		free(caseFiles);
+		return;
+	}
+
+	for (int i = 0; i < numCaseFiles; i++) {
+
+		DeSerializeFile(caseFiles[i]->d_name);
+		free(caseFiles[i]);
+	}
+	free(caseFiles);
+}
+
+void
+CaseFile::LogAll()
+{
+	for (CaseFileList::iterator curCase = s_activeCases.begin();
+	     curCase != s_activeCases.end(); curCase++)
+		(*curCase)->Log();
+}
+
+void
+CaseFile::PurgeAll()
+{
+	/*
+	 * Serialize casefiles before deleting them so that they can be reread
+	 * and revalidated during BuildCaseFiles.
+	 * CaseFiles remove themselves from this list on destruction.
+	 */
+	while (s_activeCases.size() != 0) {
+		CaseFile *casefile = s_activeCases.front();
+		casefile->Serialize();
+		delete casefile;
+	}
+
+}
+
+//- CaseFile Public Methods ----------------------------------------------------
+bool
+CaseFile::RefreshVdevState()
+{
+	ZpoolList zpl(ZpoolList::ZpoolByGUID, &m_poolGUID);
+	zpool_handle_t *casePool(zpl.empty() ? NULL : zpl.front());
+	if (casePool == NULL)
+		return (false);
+
+	Vdev vd(casePool, CaseVdev(casePool));
+	if (vd.DoesNotExist())
+		return (false);
+
+	m_vdevState    = vd.State();
+	m_vdevPhysPath = vd.PhysicalPath();
+	return (true);
+}
+
+bool
+CaseFile::ReEvaluate(const string &devPath, const string &physPath, Vdev *vdev)
+{
+	ZpoolList zpl(ZpoolList::ZpoolByGUID, &m_poolGUID);
+	zpool_handle_t *pool(zpl.empty() ? NULL : zpl.front());
+
+	if (pool == NULL || !RefreshVdevState()) {
+		/*
+		 * The pool or vdev for this case file is no longer
+		 * part of the configuration.  This can happen
+		 * if we process a device arrival notification
+		 * before seeing the ZFS configuration change
+		 * event.
+		 */
+		syslog(LOG_INFO,
+		       "CaseFile::ReEvaluate(%s,%s) Pool/Vdev unconfigured.  "
+		       "Closing\n",
+		       PoolGUIDString().c_str(),
+		       VdevGUIDString().c_str());
+		Close();
+
+		/*
+		 * Since this event was not used to close this
+		 * case, do not report it as consumed.
+		 */
+		return (/*consumed*/false);
+	}
+
+	if (VdevState() > VDEV_STATE_CANT_OPEN) {
+		/*
+		 * For now, newly discovered devices only help for
+		 * devices that are missing.  In the future, we might
+		 * use a newly inserted spare to replace a degraded
+		 * or faulted device.
+		 */
+		syslog(LOG_INFO, "CaseFile::ReEvaluate(%s,%s): Pool/Vdev ignored",
+		    PoolGUIDString().c_str(), VdevGUIDString().c_str());
+		return (/*consumed*/false);
+	}
+
+	if (vdev != NULL
+	 && vdev->PoolGUID() == m_poolGUID
+	 && vdev->GUID() == m_vdevGUID) {
+
+		zpool_vdev_online(pool, vdev->GUIDString().c_str(),
+				  ZFS_ONLINE_CHECKREMOVE | ZFS_ONLINE_UNSPARE,
+				  &m_vdevState);
+		syslog(LOG_INFO, "Onlined vdev(%s/%s:%s).  State now %s.\n",
+		       zpool_get_name(pool), vdev->GUIDString().c_str(),
+		       devPath.c_str(),
+		       zpool_state_to_name(VdevState(), VDEV_AUX_NONE));
+
+		/*
+		 * Check the vdev state post the online action to see
+		 * if we can retire this case.
+		 */
+		CloseIfSolved();
+
+		return (/*consumed*/true);
+	}
+
+	/*
+	 * If the auto-replace policy is enabled, and we have physical
+	 * path information, try a physical path replacement.
+	 */
+	if (zpool_get_prop_int(pool, ZPOOL_PROP_AUTOREPLACE, NULL) == 0) {
+		syslog(LOG_INFO,
+		       "CaseFile(%s:%s:%s): AutoReplace not set.  "
+		       "Ignoring device insertion.\n",
+		       PoolGUIDString().c_str(),
+		       VdevGUIDString().c_str(),
+		       zpool_state_to_name(VdevState(), VDEV_AUX_NONE));
+		return (/*consumed*/false);
+	}
+
+	if (PhysicalPath().empty()) {
+		syslog(LOG_INFO,
+		       "CaseFile(%s:%s:%s): No physical path information.  "
+		       "Ignoring device insertion.\n",
+		       PoolGUIDString().c_str(),
+		       VdevGUIDString().c_str(),
+		       zpool_state_to_name(VdevState(), VDEV_AUX_NONE));
+		return (/*consumed*/false);
+	}
+
+	if (physPath != PhysicalPath()) {
+		syslog(LOG_INFO,
+		       "CaseFile(%s:%s:%s): Physical path mismatch.  "
+		       "Ignoring device insertion.\n",
+		       PoolGUIDString().c_str(),
+		       VdevGUIDString().c_str(),
+		       zpool_state_to_name(VdevState(), VDEV_AUX_NONE));
+		return (/*consumed*/false);
+	}
+
+	/* Write a label on the newly inserted disk. */
+	if (zpool_label_disk(g_zfsHandle, pool, devPath.c_str()) != 0) {
+		syslog(LOG_ERR,
+		       "Replace vdev(%s/%s) by physical path (label): %s: %s\n",
+		       zpool_get_name(pool), VdevGUIDString().c_str(),
+		       libzfs_error_action(g_zfsHandle),
+		       libzfs_error_description(g_zfsHandle));
+		return (/*consumed*/false);
+	}
+
+	syslog(LOG_INFO, "CaseFile::ReEvaluate(%s/%s): Replacing with %s",
+	    PoolGUIDString().c_str(), VdevGUIDString().c_str(),
+	    devPath.c_str());
+	return (Replace(VDEV_TYPE_DISK, devPath.c_str(), /*isspare*/false));
+}
+
+bool
+CaseFile::ReEvaluate(const ZfsEvent &event)
+{
+	bool consumed(false);
+
+	if (event.Value("type") == "misc.fs.zfs.vdev_remove") {
+		/*
+		 * The Vdev we represent has been removed from the
+		 * configuration.  This case is no longer of value.
+		 */
+		Close();
+
+		return (/*consumed*/true);
+	} else if (event.Value("type") == "misc.fs.zfs.pool_destroy") {
+		/* This Pool has been destroyed.  Discard the case */
+		Close();
+
+		return (/*consumed*/true);
+	} else if (event.Value("type") == "misc.fs.zfs.config_sync") {
+		RefreshVdevState();
+		if (VdevState() < VDEV_STATE_HEALTHY)
+			consumed = ActivateSpare();
+	}
+
+
+	if (event.Value("class") == "resource.fs.zfs.removed") {
+		bool spare_activated;
+
+		if (!RefreshVdevState()) {
+			/*
+			 * The pool or vdev for this case file is no longer
+			 * part of the configuration.  This can happen
+			 * if we process a device arrival notification
+			 * before seeing the ZFS configuration change
+			 * event.
+			 */
+			syslog(LOG_INFO,
+			       "CaseFile::ReEvaluate(%s,%s) Pool/Vdev "
+			       "unconfigured.  Closing\n",
+			       PoolGUIDString().c_str(),
+			       VdevGUIDString().c_str());
+			/*
+			 * Close the case now so we won't waste cycles in the
+			 * system rescan
+			 */
+			Close();
+
+			/*
+			 * Since this event was not used to close this
+			 * case, do not report it as consumed.
+			 */
+			return (/*consumed*/false);
+		}
+
+		/*
+		 * Discard any tentative I/O error events for
+		 * this case.  They were most likely caused by the
+		 * hot-unplug of this device.
+		 */
+		PurgeTentativeEvents();
+
+		/* Try to activate spares if they are available */
+		spare_activated = ActivateSpare();
+
+		/*
+		 * Rescan the drives in the system to see if a recent
+		 * drive arrival can be used to solve this case.
+		 */
+		ZfsDaemon::RequestSystemRescan();
+
+		/*
+		 * Consume the event if we successfully activated a spare.
+		 * Otherwise, leave it in the unconsumed events list so that the
+		 * future addition of a spare to this pool might be able to
+		 * close the case
+		 */
+		consumed = spare_activated;
+	} else if (event.Value("class") == "resource.fs.zfs.statechange") {
+		RefreshVdevState();
+		/*
+		 * If this vdev is DEGRADED, FAULTED, or UNAVAIL, try to
+		 * activate a hotspare.  Otherwise, ignore the event
+		 */
+		if (VdevState() == VDEV_STATE_FAULTED ||
+		    VdevState() == VDEV_STATE_DEGRADED ||
+		    VdevState() == VDEV_STATE_CANT_OPEN)
+			(void) ActivateSpare();
+		consumed = true;
+	}
+	else if (event.Value("class") == "ereport.fs.zfs.io" ||
+	         event.Value("class") == "ereport.fs.zfs.checksum") {
+
+		m_tentativeEvents.push_front(event.DeepCopy());
+		RegisterCallout(event);
+		consumed = true;
+	}
+
+	bool closed(CloseIfSolved());
+
+	return (consumed || closed);
+}
+
+
+bool
+CaseFile::ActivateSpare() {
+	nvlist_t	*config, *nvroot;
+	nvlist_t       **spares;
+	char		*devPath, *vdev_type;
+	const char	*poolname;
+	u_int		 nspares, i;
+	int		 error;
+
+	ZpoolList zpl(ZpoolList::ZpoolByGUID, &m_poolGUID);
+	zpool_handle_t	*zhp(zpl.empty() ? NULL : zpl.front());
+	if (zhp == NULL) {
+		syslog(LOG_ERR, "CaseFile::ActivateSpare: Could not find pool "
+		       "for pool_guid %"PRIu64".", (uint64_t)m_poolGUID);
+		return (false);
+	}
+	poolname = zpool_get_name(zhp);
+	config = zpool_get_config(zhp, NULL);
+	if (config == NULL) {
+		syslog(LOG_ERR, "CaseFile::ActivateSpare: Could not find pool "
+		       "config for pool %s", poolname);
+		return (false);
+	}
+	error = nvlist_lookup_nvlist(config, ZPOOL_CONFIG_VDEV_TREE, &nvroot);
+	if (error != 0){
+		syslog(LOG_ERR, "CaseFile::ActivateSpare: Could not find vdev "
+		       "tree for pool %s", poolname);
+		return (false);
+	}
+	nspares = 0;
+	nvlist_lookup_nvlist_array(nvroot, ZPOOL_CONFIG_SPARES, &spares,
+				   &nspares);
+	if (nspares == 0) {
+		/* The pool has no spares configured */
+		syslog(LOG_INFO, "CaseFile::ActivateSpare: "
+		       "No spares available for pool %s", poolname);
+		return (false);
+	}
+	for (i = 0; i < nspares; i++) {
+		uint64_t    *nvlist_array;
+		vdev_stat_t *vs;
+		uint_t	     nstats;
+
+		if (nvlist_lookup_uint64_array(spares[i],
+		    ZPOOL_CONFIG_VDEV_STATS, &nvlist_array, &nstats) != 0) {
+			syslog(LOG_ERR, "CaseFile::ActivateSpare: Could not "
+			       "find vdev stats for pool %s, spare %d",
+			       poolname, i);
+			return (false);
+		}
+		vs = reinterpret_cast<vdev_stat_t *>(nvlist_array);
+
+		if ((vs->vs_aux != VDEV_AUX_SPARED)
+		 && (vs->vs_state == VDEV_STATE_HEALTHY)) {
+			/* We found a usable spare */
+			break;
+		}
+	}
+
+	if (i == nspares) {
+		/* No available spares were found */
+		return (false);
+	}
+
+	error = nvlist_lookup_string(spares[i], ZPOOL_CONFIG_PATH, &devPath);
+	if (error != 0) {
+		syslog(LOG_ERR, "CaseFile::ActivateSpare: Cannot determine "
+		       "the path of pool %s, spare %d. Error %d",
+		       poolname, i, error);
+		return (false);
+	}
+
+	error = nvlist_lookup_string(spares[i], ZPOOL_CONFIG_TYPE, &vdev_type);
+	if (error != 0) {
+		syslog(LOG_ERR, "CaseFile::ActivateSpare: Cannot determine "
+		       "the vdev type of pool %s, spare %d. Error %d",
+		       poolname, i, error);
+		return (false);
+	}
+
+	return (Replace(vdev_type, devPath, /*isspare*/true));
+}
+
+void
+CaseFile::RegisterCallout(const Event &event)
+{
+	timeval now, countdown, elapsed, timestamp, zero, remaining;
+
+	gettimeofday(&now, 0);
+	timestamp = event.GetTimestamp();
+	timersub(&now, &timestamp, &elapsed);
+	timersub(&s_removeGracePeriod, &elapsed, &countdown);
+	/*
+	 * If countdown is <= zero, Reset the timer to the
+	 * smallest positive time value instead
+	 */
+	timerclear(&zero);
+	if (timercmp(&countdown, &zero, <=)) {
+		timerclear(&countdown);
+		countdown.tv_usec = 1;
+	}
+
+	remaining = m_tentativeTimer.TimeRemaining();
+
+	if (!m_tentativeTimer.IsPending()
+	 || timercmp(&countdown, &remaining, <))
+		m_tentativeTimer.Reset(countdown, OnGracePeriodEnded, this);
+}
+
+
+bool
+CaseFile::CloseIfSolved()
+{
+	if (m_events.empty()
+	 && m_tentativeEvents.empty()) {
+
+		/*
+		 * We currently do not track or take actions on
+		 * devices in the degraded or faulted state.
+		 * Once we have support for spare pools, we'll
+		 * retain these cases so that any spares added in
+		 * the future can be applied to them.
+		 */
+		switch (VdevState()) {
+		case VDEV_STATE_HEALTHY:
+			/* No need to keep cases for healthy vdevs */
+			Close();
+			return (true);
+		case VDEV_STATE_REMOVED:
+		case VDEV_STATE_CANT_OPEN:
+			/*
+			 * Keep open.  We may solve it with a newly inserted
+			 * device.
+			 */
+		case VDEV_STATE_FAULTED:
+		case VDEV_STATE_DEGRADED:
+			/*
+			 * Keep open.  We may solve it with the future
+			 * addition of a spare to the pool
+			 */
+		case VDEV_STATE_UNKNOWN:
+		case VDEV_STATE_CLOSED:
+		case VDEV_STATE_OFFLINE:
+			/*
+			 * Keep open?  This may not be the correct behavior,
+			 * but it's what we've always done
+			 */
+			;
+		}
+
+		/*
+		 * Re-serialize the case in order to remove any
+		 * previous event data.
+		 */
+		Serialize();
+	}
+
+	return (false);
+}
+
+void
+CaseFile::Log()
+{
+	syslog(LOG_INFO, "CaseFile(%s,%s,%s)\n", PoolGUIDString().c_str(),
+	       VdevGUIDString().c_str(), PhysicalPath().c_str());
+	syslog(LOG_INFO, "\tVdev State = %s\n",
+	       zpool_state_to_name(VdevState(), VDEV_AUX_NONE));
+	if (m_tentativeEvents.size() != 0) {
+		syslog(LOG_INFO, "\t=== Tentative Events ===\n");
+		for (EventList::iterator event(m_tentativeEvents.begin());
+		     event != m_tentativeEvents.end(); event++)
+			(*event)->Log(LOG_INFO);
+	}
+	if (m_events.size() != 0) {
+		syslog(LOG_INFO, "\t=== Events ===\n");
+		for (EventList::iterator event(m_events.begin());
+		     event != m_events.end(); event++)
+			(*event)->Log(LOG_INFO);
+	}
+}
+
+//- CaseFile Static Protected Methods ------------------------------------------
+void
+CaseFile::OnGracePeriodEnded(void *arg)
+{
+	CaseFile &casefile(*static_cast<CaseFile *>(arg));
+
+	casefile.OnGracePeriodEnded();
+}
+
+int
+CaseFile::DeSerializeSelector(const struct dirent *dirEntry)
+{
+	uint64_t poolGUID;
+	uint64_t vdevGUID;
+
+	if (dirEntry->d_type == DT_REG
+	 && sscanf(dirEntry->d_name, "pool_%"PRIu64"_vdev_%"PRIu64".case",
+		   &poolGUID, &vdevGUID) == 2)
+		return (1);
+	return (0);
+}
+
+void
+CaseFile::DeSerializeFile(const char *fileName)
+{
+	string	  fullName(s_caseFilePath + '/' + fileName);
+	CaseFile *existingCaseFile(NULL);
+	CaseFile *caseFile(NULL);
+
+	try {
+		uint64_t poolGUID;
+		uint64_t vdevGUID;
+		nvlist_t *vdevConf;
+
+		sscanf(fileName, "pool_%"PRIu64"_vdev_%"PRIu64".case",
+		       &poolGUID, &vdevGUID);
+		existingCaseFile = Find(Guid(poolGUID), Guid(vdevGUID));
+		if (existingCaseFile != NULL) {
+			/*
+			 * If the vdev is already degraded or faulted,
+			 * there's no point in keeping the state around
+			 * that we use to put a drive into the degraded
+			 * state.  However, if the vdev is simply missing,
+			 * preserve the case data in the hopes that it will
+			 * return.
+			 */
+			caseFile = existingCaseFile;
+			vdev_state curState(caseFile->VdevState());
+			if (curState > VDEV_STATE_CANT_OPEN
+			 && curState < VDEV_STATE_HEALTHY) {
+				unlink(fileName);
+				return;
+			}
+		} else {
+			ZpoolList zpl(ZpoolList::ZpoolByGUID, &poolGUID);
+			if (zpl.empty()
+			 || (vdevConf = VdevIterator(zpl.front())
+						    .Find(vdevGUID)) == NULL) {
+				/*
+				 * Either the pool no longer exists
+				 * or this vdev is no longer a member of
+				 * the pool.
+				 */
+				unlink(fullName.c_str());
+				return;
+			}
+
+			/*
+			 * Any vdev we find that does not have a case file
+			 * must be in the healthy state and thus worthy of
+			 * continued SERD data tracking.
+			 */
+			caseFile = new CaseFile(Vdev(zpl.front(), vdevConf));
+		}
+
+		ifstream caseStream(fullName.c_str());
+		if (!caseStream)
+			throw ZfsdException("CaseFile::DeSerialize: Unable to "
+					    "read %s.\n", fileName);
+
+		caseFile->DeSerialize(caseStream);
+	} catch (const ParseException &exp) {
+
+		exp.Log();
+		if (caseFile != existingCaseFile)
+			delete caseFile;
+
+		/*
+		 * Since we can't parse the file, unlink it so we don't
+		 * trip over it again.
+		 */
+		unlink(fileName);
+	} catch (const ZfsdException &zfsException) {
+
+		zfsException.Log();
+		if (caseFile != existingCaseFile)
+			delete caseFile;
+	}
+}
+
+//- CaseFile Protected Methods -------------------------------------------------
+CaseFile::CaseFile(const Vdev &vdev)
+ : m_poolGUID(vdev.PoolGUID()),
+   m_vdevGUID(vdev.GUID()),
+   m_vdevState(vdev.State()),
+   m_vdevPhysPath(vdev.PhysicalPath())
+{
+	stringstream guidString;
+
+	guidString << m_vdevGUID;
+	m_vdevGUIDString = guidString.str();
+	guidString.str("");
+	guidString << m_poolGUID;
+	m_poolGUIDString = guidString.str();
+
+	s_activeCases.push_back(this);
+
+	syslog(LOG_INFO, "Creating new CaseFile:\n");
+	Log();
+}
+
+CaseFile::~CaseFile()
+{
+	PurgeEvents();
+	PurgeTentativeEvents();
+	m_tentativeTimer.Stop();
+	s_activeCases.remove(this);
+}
+
+void
+CaseFile::PurgeEvents()
+{
+	for (EventList::iterator event(m_events.begin());
+	     event != m_events.end(); event++)
+		delete *event;
+
+	m_events.clear();
+}
+
+void
+CaseFile::PurgeTentativeEvents()
+{
+	for (EventList::iterator event(m_tentativeEvents.begin());
+	     event != m_tentativeEvents.end(); event++)
+		delete *event;
+
+	m_tentativeEvents.clear();
+}
+
+void
+CaseFile::SerializeEvList(const EventList events, int fd,
+		const char* prefix) const
+{
+	if (events.empty())
+		return;
+	for (EventList::const_iterator curEvent = events.begin();
+	     curEvent != events.end(); curEvent++) {
+		const string &eventString((*curEvent)->GetEventString());
+
+		// TODO: replace many write(2) calls with a single writev(2)
+		if (prefix)
+			write(fd, prefix, strlen(prefix));
+		write(fd, eventString.c_str(), eventString.length());
+	}
+}
+
+void
+CaseFile::Serialize()
+{
+	stringstream saveFile;
+
+	saveFile << setfill('0')
+		 << s_caseFilePath << "/"
+		 << "pool_" << PoolGUIDString()
+		 << "_vdev_" << VdevGUIDString()
+		 << ".case";
+
+	if (m_events.empty() && m_tentativeEvents.empty()) {
+		unlink(saveFile.str().c_str());
+		return;
+	}
+
+	int fd(open(saveFile.str().c_str(), O_CREAT|O_TRUNC|O_WRONLY, 0644));
+	if (fd == -1) {
+		syslog(LOG_ERR, "CaseFile::Serialize: Unable to open %s.\n",
+		       saveFile.str().c_str());
+		return;
+	}
+	SerializeEvList(m_events, fd);
+	SerializeEvList(m_tentativeEvents, fd, "tentative ");
+	close(fd);
+}
+
+/*
+ * XXX: This method assumes that events may not contain embedded newlines.  If
+ * ever events can contain embedded newlines, then CaseFile must switch
+ * serialization formats
+ */
+void
+CaseFile::DeSerialize(ifstream &caseStream)
+{
+	string	      evString;
+	const EventFactory &factory(ZfsDaemon::Get().GetFactory());
+
+	caseStream >> std::noskipws >> std::ws;
+	while (caseStream.good()) {
+		/*
+		 * Outline:
+		 * read the beginning of a line and check it for
+		 * "tentative".  If found, discard "tentative".
+		 * Create a new event
+		 * continue
+		 */
+		EventList* destEvents;
+		const string tentFlag("tentative ");
+		string line;
+		std::stringbuf lineBuf;
+
+		caseStream.get(lineBuf);
+		caseStream.ignore();  /*discard the newline character*/
+		line = lineBuf.str();
+		if (line.compare(0, tentFlag.size(), tentFlag) == 0) {
+			/* Discard "tentative" */
+			line.erase(0, tentFlag.size());
+			destEvents = &m_tentativeEvents;
+		} else {
+			destEvents = &m_events;
+		}
+		Event *event(Event::CreateEvent(factory, line));
+		if (event != NULL) {
+			destEvents->push_back(event);
+			RegisterCallout(*event);
+		}
+	}
+}
+
+void
+CaseFile::Close()
+{
+	/*
+	 * This case is no longer relevant.  Clean up our
+	 * serialization file, and delete the case.
+	 */
+	syslog(LOG_INFO, "CaseFile(%s,%s) closed - State %s\n",
+	       PoolGUIDString().c_str(), VdevGUIDString().c_str(),
+	       zpool_state_to_name(VdevState(), VDEV_AUX_NONE));
+
+	/*
+	 * Serialization of a Case with no event data, clears the
+	 * Serialization data for that event.
+	 */
+	PurgeEvents();
+	Serialize();
+
+	delete this;
+}
+
+void
+CaseFile::OnGracePeriodEnded()
+{
+	bool should_fault, should_degrade;
+	ZpoolList zpl(ZpoolList::ZpoolByGUID, &m_poolGUID);
+	zpool_handle_t *zhp(zpl.empty() ? NULL : zpl.front());
+
+	m_events.splice(m_events.begin(), m_tentativeEvents);
+	should_fault = ShouldFault();
+	should_degrade = ShouldDegrade();
+
+	if (should_fault || should_degrade) {
+		if (zhp == NULL
+		 || (VdevIterator(zhp).Find(m_vdevGUID)) == NULL) {
+			/*
+			 * Either the pool no longer exists
+			 * or this vdev is no longer a member of
+			 * the pool.
+			 */
+			Close();
+			return;
+		}
+
+	}
+
+	/* A fault condition has priority over a degrade condition */
+	if (ShouldFault()) {
+		/* Fault the vdev and close the case. */
+		if (zpool_vdev_fault(zhp, (uint64_t)m_vdevGUID,
+				       VDEV_AUX_ERR_EXCEEDED) == 0) {
+			syslog(LOG_INFO, "Faulting vdev(%s/%s)",
+			       PoolGUIDString().c_str(),
+			       VdevGUIDString().c_str());
+			Close();
+			return;
+		}
+		else {
+			syslog(LOG_ERR, "Fault vdev(%s/%s): %s: %s\n",
+			       PoolGUIDString().c_str(),
+			       VdevGUIDString().c_str(),
+			       libzfs_error_action(g_zfsHandle),
+			       libzfs_error_description(g_zfsHandle));
+		}
+	}
+	else if (ShouldDegrade()) {
+		/* Degrade the vdev and close the case. */
+		if (zpool_vdev_degrade(zhp, (uint64_t)m_vdevGUID,
+				       VDEV_AUX_ERR_EXCEEDED) == 0) {
+			syslog(LOG_INFO, "Degrading vdev(%s/%s)",
+			       PoolGUIDString().c_str(),
+			       VdevGUIDString().c_str());
+			Close();
+			return;
+		}
+		else {
+			syslog(LOG_ERR, "Degrade vdev(%s/%s): %s: %s\n",
+			       PoolGUIDString().c_str(),
+			       VdevGUIDString().c_str(),
+			       libzfs_error_action(g_zfsHandle),
+			       libzfs_error_description(g_zfsHandle));
+		}
+	}
+	Serialize();
+}
+
+Vdev
+CaseFile::BeingReplacedBy(zpool_handle_t *zhp) {
+	Vdev vd(zhp, CaseVdev(zhp));
+	std::list<Vdev> children;
+	std::list<Vdev>::iterator children_it;
+
+	Vdev parent(vd.Parent());
+	Vdev replacing(NonexistentVdev);
+
+	/*
+	 * To determine whether we are being replaced by another spare that
+	 * is still working, then make sure that it is currently spared and
+	 * that the spare is either resilvering or healthy.  If any of these
+	 * conditions fail, then we are not being replaced by a spare.
+	 *
+	 * If the spare is healthy, then the case file should be closed very
+	 * soon after this check.
+	 */
+	if (parent.DoesNotExist()
+	 || parent.Name(zhp, /*verbose*/false) != "spare")
+		return (NonexistentVdev);
+
+	children = parent.Children();
+	children_it = children.begin();
+	for (;children_it != children.end(); children_it++) {
+		Vdev child = *children_it;
+
+		/* Skip our vdev. */
+		if (child.GUID() == VdevGUID())
+			continue;
+		/*
+		 * Accept the first child that doesn't match our GUID, or
+		 * any resilvering/healthy device if one exists.
+		 */
+		if (replacing.DoesNotExist() || child.IsResilvering()
+		 || child.State() == VDEV_STATE_HEALTHY)
+			replacing = child;
+	}
+
+	return (replacing);
+}
+
+bool
+CaseFile::Replace(const char* vdev_type, const char* path, bool isspare) {
+	nvlist_t *nvroot, *newvd;
+	const char *poolname;
+	string oldstr(VdevGUIDString());
+	bool retval = true;
+
+	/* Figure out what pool we're working on */
+	ZpoolList zpl(ZpoolList::ZpoolByGUID, &m_poolGUID);
+	zpool_handle_t *zhp(zpl.empty() ? NULL : zpl.front());
+	if (zhp == NULL) {
+		syslog(LOG_ERR, "CaseFile::Replace: could not find pool for "
+		       "pool_guid %"PRIu64".", (uint64_t)m_poolGUID);
+		return (false);
+	}
+	poolname = zpool_get_name(zhp);
+	Vdev vd(zhp, CaseVdev(zhp));
+	Vdev replaced(BeingReplacedBy(zhp));
+
+	if (isspare && !vd.IsSpare() && !replaced.DoesNotExist()) {
+		/* If we are already being replaced by a working spare, pass. */
+		if (replaced.IsResilvering()
+		 || replaced.State() == VDEV_STATE_HEALTHY) {
+			syslog(LOG_INFO, "CaseFile::Replace(%s->%s): already "
+			    "replaced", VdevGUIDString().c_str(), path);
+			return (/*consumed*/false);
+		}
+		/*
+		 * If we have already been replaced by a spare, but that spare
+		 * is broken, we must spare the spare, not the original device.
+		 */
+		oldstr = replaced.GUIDString();
+		syslog(LOG_INFO, "CaseFile::Replace(%s->%s): sparing "
+		    "broken spare %s instead", VdevGUIDString().c_str(),
+		    path, oldstr.c_str());
+	}
+
+	/*
+	 * Build a root vdev/leaf vdev configuration suitable for
+	 * zpool_vdev_attach. Only enough data for the kernel to find
+	 * the device (i.e. type and disk device node path) are needed.
+	 */
+	nvroot = NULL;
+	newvd = NULL;
+
+	if (nvlist_alloc(&nvroot, NV_UNIQUE_NAME, 0) != 0
+	 || nvlist_alloc(&newvd, NV_UNIQUE_NAME, 0) != 0) {
+		syslog(LOG_ERR, "Replace vdev(%s/%s): Unable to allocate "
+		    "configuration data.", poolname, oldstr.c_str());
+		if (nvroot != NULL)
+			nvlist_free(nvroot);
+		return (false);
+	}
+	if (nvlist_add_string(newvd, ZPOOL_CONFIG_TYPE, vdev_type) != 0
+	 || nvlist_add_string(newvd, ZPOOL_CONFIG_PATH, path) != 0
+	 || nvlist_add_string(nvroot, ZPOOL_CONFIG_TYPE, VDEV_TYPE_ROOT) != 0
+	 || nvlist_add_nvlist_array(nvroot, ZPOOL_CONFIG_CHILDREN,
+				    &newvd, 1) != 0) {
+		syslog(LOG_ERR, "Replace vdev(%s/%s): Unable to initialize "
+		    "configuration data.", poolname, oldstr.c_str());
+		nvlist_free(newvd);
+		nvlist_free(nvroot);
+		return (true);
+	}
+
+	/* Data was copied when added to the root vdev. */
+	nvlist_free(newvd);
+
+	retval = (zpool_vdev_attach(zhp, oldstr.c_str(), path, nvroot,
+	    /*replace*/B_TRUE) == 0);
+	if (retval)
+		syslog(LOG_INFO, "Replacing vdev(%s/%s) with %s\n",
+		    poolname, oldstr.c_str(), path);
+	else
+		syslog(LOG_ERR, "Replace vdev(%s/%s): %s: %s\n",
+		    poolname, oldstr.c_str(), libzfs_error_action(g_zfsHandle),
+		    libzfs_error_description(g_zfsHandle));
+	nvlist_free(nvroot);
+
+	return (retval);
+}
+
+/* Does the argument event refer to a checksum error? */
+static bool
+IsChecksumEvent(const Event* const event)
+{
+	return ("ereport.fs.zfs.checksum" == event->Value("type"));
+}
+
+/* Does the argument event refer to an IO error? */
+static bool
+IsIOEvent(const Event* const event)
+{
+	return ("ereport.fs.zfs.io" == event->Value("type"));
+}
+
+bool
+CaseFile::ShouldDegrade() const
+{
+	return (std::count_if(m_events.begin(), m_events.end(),
+			      IsChecksumEvent) > ZFS_DEGRADE_IO_COUNT);
+}
+
+bool
+CaseFile::ShouldFault() const
+{
+	return (std::count_if(m_events.begin(), m_events.end(),
+			      IsIOEvent) > ZFS_DEGRADE_IO_COUNT);
+}
+
+nvlist_t *
+CaseFile::CaseVdev(zpool_handle_t *zhp) const
+{
+	return (VdevIterator(zhp).Find(VdevGUID()));
+}
Index: cddl/usr.sbin/zfsd/tests/Makefile
===================================================================
--- /dev/null
+++ cddl/usr.sbin/zfsd/tests/Makefile
@@ -0,0 +1,45 @@
+# $FreeBSD$
+
+SRCDIR=${.CURDIR}/../../../..
+.include "${.CURDIR}/../Makefile.common"
+.PATH:	${.CURDIR}/..
+
+TESTSDIR?=	${TESTSBASE}/cddl/sbin/zfsd
+
+PLAIN_TESTS_CXX=	zfsd_unittest
+SRCS.zfsd_unittest:=		${SRCS:Nzfsd_main.cc}
+SRCS.zfsd_unittest+=		libmocks.c zfsd_unittest.cc
+SRCS=
+
+# Use #include <zfsd/xxx.h> in test programs.
+INCFLAGS+=	-I${.CURDIR}/../..
+
+.if defined(DESTDIR)
+INCFLAGS+=	-I${DESTDIR}/usr/include
+LIBRARY_PATH=	${DESTDIR}/lib:${DESTDIR}/usr/lib
+LDFLAGS.zfsd_unittest+=	-L${DESTDIR}/lib -L${DESTDIR}/usr/lib
+.elif defined(WORLDTMP)
+INCFLAGS+=	-I${WORLDTMP}/usr/include
+LIBRARY_PATH=	${WORLDTMP}/lib:${WORLDTMP}/usr/lib
+LDFLAGS.zfsd_unittest+=	-L${WORLDTMP}/lib -L${WORLDTMP}/usr/lib
+.else
+LIBRARY_PATH=
+.endif
+
+# Googletest options
+LOCALBASE?=	/usr/local
+INCFLAGS+=	-I${LOCALBASE}/include -D_THREAD_SAFE -pthread
+LDFLAGS.zfsd_unittest+=	-L${LOCALBASE}/lib -D_THREAD_SAFE -pthread
+LDADD.zfsd_unittest+=		${LOCALBASE}/lib/libgtest.a
+
+# GoogleMock options
+LDADD.zfsd_unittest+= ${LOCALBASE}/lib/libgmock.a ${LOCALBASE}/lib/libgmock_main.a
+
+# Googlemock fails if we don't have this line
+# https://groups.google.com/forum/#!msg/googletestframework/h8ixEPCFm0o/amwfu4xGJb0J
+CFLAGS.zfsd_unittest+= -DGTEST_HAS_PTHREAD
+
+# Install the tests
+TESTSBASE?=	/usr/tests
+
+.include <bsd.test.mk>
Index: cddl/usr.sbin/zfsd/tests/libmocks.h
===================================================================
--- /dev/null
+++ cddl/usr.sbin/zfsd/tests/libmocks.h
@@ -0,0 +1,24 @@
+#ifndef _LIBMOCKS_H_
+#define _LIBMOCKS_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct libzfs_handle;
+typedef struct libzfs_handle libzfs_handle_t;
+struct zpool_handle;
+typedef struct zpool_handle zpool_handle_t;
+typedef int (*zpool_iter_f)(zpool_handle_t *, void *);
+
+void syslog(int priority, const char* message, ...);
+int zpool_iter(libzfs_handle_t*, zpool_iter_f, void*);
+
+extern int syslog_last_priority;
+extern char syslog_last_message[4096];
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
Index: cddl/usr.sbin/zfsd/tests/libmocks.c
===================================================================
--- /dev/null
+++ cddl/usr.sbin/zfsd/tests/libmocks.c
@@ -0,0 +1,24 @@
+#include <stdio.h>
+#include <stdarg.h>
+#include "libmocks.h"
+
+/*
+ * This file mocks shared library functions that are used by zfsd.  Every
+ * function present will be used for all tests in all test suites instead of the
+ * normal function.
+ */
+
+int syslog_last_priority;
+char syslog_last_message[4096];
+void syslog(int priority, const char* message, ...) {
+	va_list ap;
+
+	syslog_last_priority = priority;
+	va_start(ap, message);
+	vsnprintf(syslog_last_message, 4096, message, ap);
+	va_end(ap);
+}
+
+int zpool_iter(libzfs_handle_t* handle, zpool_iter_f iter, void* arg) {
+	return (0);
+}
Index: cddl/usr.sbin/zfsd/tests/zfsd_unittest.cc
===================================================================
--- /dev/null
+++ cddl/usr.sbin/zfsd/tests/zfsd_unittest.cc
@@ -0,0 +1,771 @@
+/*-
+ * Copyright (c) 2012, 2013 Spectra Logic Corporation
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions, and the following disclaimer,
+ *    without modification.
+ * 2. Redistributions in binary form must reproduce at minimum a disclaimer
+ *    substantially similar to the "NO WARRANTY" disclaimer below
+ *    ("Disclaimer") and any redistribution must be conditioned upon
+ *    including a substantially similar Disclaimer requirement for further
+ *    binary redistribution.
+ *
+ * NO WARRANTY
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDERS OR CONTRIBUTORS BE LIABLE FOR SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
+ * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGES.
+ *
+ * Authors: Alan Somers         (Spectra Logic Corporation)
+ */
+#include <sys/cdefs.h>
+
+#include <stdarg.h>
+#include <syslog.h>
+
+#include <libnvpair.h>
+#include <libzfs.h>
+
+#include <list>
+#include <map>
+#include <sstream>
+#include <string>
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include <devdctl/guid.h>
+#include <devdctl/event.h>
+#include <devdctl/event_factory.h>
+#include <devdctl/exception.h>
+#include <devdctl/consumer.h>
+
+#include <zfsd/callout.h>
+#include <zfsd/vdev_iterator.h>
+#include <zfsd/zfsd_event.h>
+#include <zfsd/case_file.h>
+#include <zfsd/vdev.h>
+#include <zfsd/zfsd.h>
+#include <zfsd/zfsd_exception.h>
+#include <zfsd/zpool_list.h>
+
+#include "libmocks.h"
+
+__FBSDID("$FreeBSD$");
+
+/*================================== Macros ==================================*/
+#define	NUM_ELEMENTS(x) (sizeof(x) / sizeof(*x))
+
+/*============================ Namespace Control =============================*/
+using std::string;
+using std::stringstream;
+
+using DevdCtl::Event;
+using DevdCtl::EventBuffer;
+using DevdCtl::EventFactory;
+using DevdCtl::EventList;
+using DevdCtl::Guid;
+using DevdCtl::NVPairMap;
+
+/* redefine zpool_handle here because libzfs_impl.h is not includable */
+struct zpool_handle
+{
+        libzfs_handle_t *zpool_hdl;
+        zpool_handle_t *zpool_next;
+        char zpool_name[ZPOOL_MAXNAMELEN];
+        int zpool_state;
+        size_t zpool_config_size;
+        nvlist_t *zpool_config;
+        nvlist_t *zpool_old_config;
+        nvlist_t *zpool_props;
+        diskaddr_t zpool_start_block;
+};
+
+class MockZfsEvent : public ZfsEvent
+{
+public:
+	MockZfsEvent(Event::Type, NVPairMap&, const string&);
+	virtual ~MockZfsEvent() {}
+
+	static BuildMethod MockZfsEventBuilder;
+
+	MOCK_CONST_METHOD0(ProcessPoolEvent, void());
+
+	static EventFactory::Record s_buildRecords[];
+};
+
+EventFactory::Record MockZfsEvent::s_buildRecords[] =
+{
+        { Event::NOTIFY, "ZFS", &MockZfsEvent::MockZfsEventBuilder }
+};
+
+MockZfsEvent::MockZfsEvent(Event::Type type, NVPairMap& map,
+			   const string& str)
+ : ZfsEvent(type, map, str)
+{
+}
+
+Event *
+MockZfsEvent::MockZfsEventBuilder(Event::Type type,
+				  NVPairMap &nvpairs,
+			  	  const string &eventString)
+{
+	return (new MockZfsEvent(type, nvpairs, eventString));
+}
+
+/*
+ * A dummy Vdev class used for testing other classes
+ */
+class MockVdev : public Vdev
+{
+public:
+	MockVdev(nvlist_t *vdevConfig);
+	virtual ~MockVdev() {}
+
+	MOCK_CONST_METHOD0(GUID, Guid());
+	MOCK_CONST_METHOD0(PoolGUID, Guid());
+	MOCK_CONST_METHOD0(State, vdev_state());
+	MOCK_CONST_METHOD0(PhysicalPath, string());
+};
+
+MockVdev::MockVdev(nvlist_t *vdevConfig)
+ : Vdev(vdevConfig)
+{
+}
+
+/*
+ * A CaseFile class with side effects removed, for testing
+ */
+class TestableCaseFile : public CaseFile
+{
+public:
+	static TestableCaseFile &Create(Vdev &vdev);
+	TestableCaseFile(Vdev &vdev);
+	virtual ~TestableCaseFile() {}
+
+	MOCK_METHOD0(Close, void());
+	MOCK_METHOD1(RegisterCallout, void(const Event &event));
+	MOCK_METHOD0(RefreshVdevState, bool());
+	MOCK_METHOD1(ReEvaluate, bool(const ZfsEvent &event));
+
+	bool RealReEvaluate(const ZfsEvent &event)
+	{
+		return (CaseFile::ReEvaluate(event));
+	}
+
+	/*
+	 * This splices the event lists, a procedure that would normally be done
+	 * by OnGracePeriodEnded, but we don't necessarily call that in the
+	 * unit tests
+	 */
+	void SpliceEvents();
+
+	/*
+	 * Used by some of our expectations.  CaseFile does not publicize this
+	 */
+	static int getActiveCases()
+	{
+		return (s_activeCases.size());
+	}
+};
+
+TestableCaseFile::TestableCaseFile(Vdev &vdev)
+ : CaseFile(vdev)
+{
+}
+
+TestableCaseFile &
+TestableCaseFile::Create(Vdev &vdev)
+{
+	TestableCaseFile *newCase;
+	newCase = new TestableCaseFile(vdev);
+	return (*newCase);
+}
+
+void
+TestableCaseFile::SpliceEvents()
+{
+	m_events.splice(m_events.begin(), m_tentativeEvents);
+}
+
+
+/*
+ * Test class ZfsdException
+ */
+class ZfsdExceptionTest : public ::testing::Test
+{
+protected:
+	virtual void SetUp()
+	{
+		ASSERT_EQ(0, nvlist_alloc(&poolConfig, NV_UNIQUE_NAME, 0));
+		ASSERT_EQ(0, nvlist_add_string(poolConfig,
+				ZPOOL_CONFIG_POOL_NAME, "unit_test_pool"));
+		ASSERT_EQ(0, nvlist_add_uint64(poolConfig,
+				ZPOOL_CONFIG_POOL_GUID, 0x1234));
+
+		ASSERT_EQ(0, nvlist_alloc(&vdevConfig, NV_UNIQUE_NAME, 0));
+		ASSERT_EQ(0, nvlist_add_uint64(vdevConfig,
+				ZPOOL_CONFIG_GUID, 0x5678));
+		bzero(&poolHandle, sizeof(poolHandle));
+		poolHandle.zpool_config = poolConfig;
+	}
+
+	virtual void TearDown()
+	{
+		nvlist_free(poolConfig);
+		nvlist_free(vdevConfig);
+	}
+
+	nvlist_t	*poolConfig;
+	nvlist_t	*vdevConfig;
+	zpool_handle_t   poolHandle;
+};
+
+TEST_F(ZfsdExceptionTest, StringConstructorNull)
+{
+	ZfsdException ze("");
+	EXPECT_STREQ("", ze.GetString().c_str());
+}
+
+TEST_F(ZfsdExceptionTest, StringConstructorFormatted)
+{
+	ZfsdException ze(" %d %s", 55, "hello world");
+	EXPECT_STREQ(" 55 hello world", ze.GetString().c_str());
+}
+
+TEST_F(ZfsdExceptionTest, LogSimple)
+{
+	ZfsdException ze("unit test w/o vdev or pool");
+	ze.Log();
+	EXPECT_EQ(LOG_ERR, syslog_last_priority);
+	EXPECT_STREQ("unit test w/o vdev or pool\n", syslog_last_message);
+}
+
+TEST_F(ZfsdExceptionTest, Pool)
+{
+	const char msg[] = "Exception with pool name";
+	char expected[4096];
+	sprintf(expected, "Pool unit_test_pool: %s\n", msg);
+	ZfsdException ze(poolConfig, msg);
+	ze.Log();
+	EXPECT_STREQ(expected, syslog_last_message);
+}
+
+TEST_F(ZfsdExceptionTest, PoolHandle)
+{
+	const char msg[] = "Exception with pool handle";
+	char expected[4096];
+	sprintf(expected, "Pool unit_test_pool: %s\n", msg);
+	ZfsdException ze(&poolHandle, msg);
+	ze.Log();
+	EXPECT_STREQ(expected, syslog_last_message);
+}
+
+/*
+ * Test class Vdev
+ */
+class VdevTest : public ::testing::Test
+{
+protected:
+	virtual void SetUp()
+	{
+		ASSERT_EQ(0, nvlist_alloc(&m_poolConfig, NV_UNIQUE_NAME, 0));
+		ASSERT_EQ(0, nvlist_add_uint64(m_poolConfig,
+					       ZPOOL_CONFIG_POOL_GUID,
+					       0x1234));
+
+		ASSERT_EQ(0, nvlist_alloc(&m_vdevConfig, NV_UNIQUE_NAME, 0));
+		ASSERT_EQ(0, nvlist_add_uint64(m_vdevConfig, ZPOOL_CONFIG_GUID,
+					       0x5678));
+	}
+
+	virtual void TearDown()
+	{
+		nvlist_free(m_poolConfig);
+		nvlist_free(m_vdevConfig);
+	}
+
+	nvlist_t	*m_poolConfig;
+	nvlist_t	*m_vdevConfig;
+};
+
+
+TEST_F(VdevTest, StateFromConfig)
+{
+	vdev_stat_t vs;
+
+	vs.vs_state = VDEV_STATE_OFFLINE;
+
+	ASSERT_EQ(0, nvlist_add_uint64_array(m_vdevConfig,
+					     ZPOOL_CONFIG_VDEV_STATS,
+					     (uint64_t*)&vs,
+					     sizeof(vs) / sizeof(uint64_t)));
+
+	Vdev vdev(m_poolConfig, m_vdevConfig);
+
+	EXPECT_EQ(VDEV_STATE_OFFLINE, vdev.State());
+}
+
+TEST_F(VdevTest, StateFaulted)
+{
+	ASSERT_EQ(0, nvlist_add_uint64(m_vdevConfig, ZPOOL_CONFIG_FAULTED, 1));
+
+	Vdev vdev(m_poolConfig, m_vdevConfig);
+
+	EXPECT_EQ(VDEV_STATE_FAULTED, vdev.State());
+}
+
+/*
+ * Test that we can construct a Vdev from the label information that is stored
+ * on an available spare drive
+ */
+TEST_F(VdevTest, ConstructAvailSpare)
+{
+	nvlist_t	*labelConfig;
+
+	ASSERT_EQ(0, nvlist_alloc(&labelConfig, NV_UNIQUE_NAME, 0));
+	ASSERT_EQ(0, nvlist_add_uint64(labelConfig, ZPOOL_CONFIG_GUID,
+				       1948339428197961030));
+	ASSERT_EQ(0, nvlist_add_uint64(labelConfig, ZPOOL_CONFIG_POOL_STATE,
+				       POOL_STATE_SPARE));
+
+	EXPECT_NO_THROW(Vdev vdev(labelConfig));
+
+	nvlist_free(labelConfig);
+}
+
+/* Available spares will always show the HEALTHY state */
+TEST_F(VdevTest, AvailSpareState) {
+	nvlist_t	*labelConfig;
+
+	ASSERT_EQ(0, nvlist_alloc(&labelConfig, NV_UNIQUE_NAME, 0));
+	ASSERT_EQ(0, nvlist_add_uint64(labelConfig, ZPOOL_CONFIG_GUID,
+				       1948339428197961030));
+	ASSERT_EQ(0, nvlist_add_uint64(labelConfig, ZPOOL_CONFIG_POOL_STATE,
+				       POOL_STATE_SPARE));
+
+	Vdev vdev(labelConfig);
+	EXPECT_EQ(VDEV_STATE_HEALTHY, vdev.State());
+
+	nvlist_free(labelConfig);
+}
+
+/* Test the Vdev::IsSpare method */
+TEST_F(VdevTest, IsSpare) {
+	Vdev notSpare(m_poolConfig, m_vdevConfig);
+	EXPECT_EQ(false, notSpare.IsSpare());
+
+	ASSERT_EQ(0, nvlist_add_uint64(m_vdevConfig, ZPOOL_CONFIG_IS_SPARE, 1));
+	Vdev isSpare(m_poolConfig, m_vdevConfig);
+	EXPECT_EQ(true, isSpare.IsSpare());
+}
+
+/*
+ * Test class ZFSEvent
+ */
+class ZfsEventTest : public ::testing::Test
+{
+protected:
+	virtual void SetUp()
+	{
+		m_eventFactory = new EventFactory();
+		m_eventFactory->UpdateRegistry(MockZfsEvent::s_buildRecords,
+		    NUM_ELEMENTS(MockZfsEvent::s_buildRecords));
+
+		m_event = NULL;
+	}
+
+	virtual void TearDown()
+	{
+		delete m_eventFactory;
+		delete m_event;
+	}
+
+	EventFactory	*m_eventFactory;
+	Event		*m_event;
+};
+
+TEST_F(ZfsEventTest, ProcessPoolEventGetsCalled)
+{
+	string evString("!system=ZFS "
+			"subsystem=ZFS "
+			"type=misc.fs.zfs.vdev_remove "
+			"pool_name=foo "
+			"pool_guid=9756779504028057996 "
+			"vdev_guid=1631193447431603339 "
+			"vdev_path=/dev/da1 "
+			"timestamp=1348871594");
+	m_event = Event::CreateEvent(*m_eventFactory, evString);
+	MockZfsEvent *mock_event = static_cast<MockZfsEvent*>(m_event);
+
+	EXPECT_CALL(*mock_event, ProcessPoolEvent()).Times(1);
+	mock_event->Process();
+}
+
+/*
+ * Test class CaseFile
+ */
+
+class CaseFileTest : public ::testing::Test
+{
+protected:
+	virtual void SetUp()
+	{
+		m_eventFactory = new EventFactory();
+		m_eventFactory->UpdateRegistry(MockZfsEvent::s_buildRecords,
+		    NUM_ELEMENTS(MockZfsEvent::s_buildRecords));
+
+		m_event = NULL;
+
+		nvlist_alloc(&m_vdevConfig, NV_UNIQUE_NAME, 0);
+		ASSERT_EQ(0, nvlist_add_uint64(m_vdevConfig,
+					       ZPOOL_CONFIG_GUID, 0xbeef));
+		m_vdev = new MockVdev(m_vdevConfig);
+		ON_CALL(*m_vdev, GUID())
+		    .WillByDefault(::testing::Return(Guid(123)));
+		ON_CALL(*m_vdev, PoolGUID())
+		    .WillByDefault(::testing::Return(Guid(456)));
+		ON_CALL(*m_vdev, State())
+		    .WillByDefault(::testing::Return(VDEV_STATE_HEALTHY));
+		m_caseFile = &TestableCaseFile::Create(*m_vdev);
+		ON_CALL(*m_caseFile, ReEvaluate(::testing::_))
+		    .WillByDefault(::testing::Invoke(m_caseFile, &TestableCaseFile::RealReEvaluate));
+		return;
+	}
+
+	virtual void TearDown()
+	{
+		delete m_caseFile;
+		nvlist_free(m_vdevConfig);
+		delete m_vdev;
+		delete m_event;
+		delete m_eventFactory;
+	}
+
+	nvlist_t		*m_vdevConfig;
+	MockVdev		*m_vdev;
+	TestableCaseFile 	*m_caseFile;
+	Event			*m_event;
+	EventFactory		*m_eventFactory;
+};
+
+/*
+ * A Vdev with no events should not be degraded or faulted
+ */
+TEST_F(CaseFileTest, HealthyVdev)
+{
+	EXPECT_FALSE(m_caseFile->ShouldDegrade());
+	EXPECT_FALSE(m_caseFile->ShouldFault());
+}
+
+/*
+ * A Vdev with only one event should not be degraded or faulted
+ * For performance reasons, RefreshVdevState should not be called.
+ */
+TEST_F(CaseFileTest, HealthyishVdev)
+{
+	string evString("!system=ZFS "
+			"class=ereport.fs.zfs.io "
+			"ena=12091638756982918145 "
+			"parent_guid=13237004955564865395 "
+			"parent_type=raidz "
+			"pool=testpool.4415 "
+			"pool_context=0 "
+			"pool_failmode=wait "
+			"pool_guid=456 "
+			"subsystem=ZFS "
+			"timestamp=1348867914 "
+			"type=ereport.fs.zfs.io "
+			"vdev_guid=123 "
+			"vdev_path=/dev/da400 "
+			"vdev_type=disk "
+			"zio_blkid=622 "
+			"zio_err=1 "
+			"zio_level=-2 "
+			"zio_object=0 "
+			"zio_objset=37 "
+			"zio_offset=25598976 "
+			"zio_size=1024");
+	m_event = Event::CreateEvent(*m_eventFactory, evString);
+	ZfsEvent *zfs_event = static_cast<ZfsEvent*>(m_event);
+
+	EXPECT_CALL(*m_caseFile, RefreshVdevState())
+	    .Times(::testing::Exactly(0));
+	EXPECT_TRUE(m_caseFile->ReEvaluate(*zfs_event));
+	EXPECT_FALSE(m_caseFile->ShouldDegrade());
+	EXPECT_FALSE(m_caseFile->ShouldFault());
+}
+
+/* The case file should be closed when its pool is destroyed */
+TEST_F(CaseFileTest, PoolDestroy)
+{
+	string evString("!system=ZFS "
+			"pool_name=testpool.4415 "
+			"pool_guid=456 "
+			"subsystem=ZFS "
+			"timestamp=1348867914 "
+			"type=misc.fs.zfs.pool_destroy ");
+	m_event = Event::CreateEvent(*m_eventFactory, evString);
+	ZfsEvent *zfs_event = static_cast<ZfsEvent*>(m_event);
+	EXPECT_CALL(*m_caseFile, Close());
+	EXPECT_TRUE(m_caseFile->ReEvaluate(*zfs_event));
+}
+
+/*
+ * A Vdev with a very large number of IO errors should fault
+ * For performance reasons, RefreshVdevState should be called at most once
+ */
+TEST_F(CaseFileTest, VeryManyIOErrors)
+{
+	EXPECT_CALL(*m_caseFile, RefreshVdevState())
+	    .Times(::testing::AtMost(1))
+	    .WillRepeatedly(::testing::Return(true));
+
+	for(int i=0; i<100; i++) {
+		stringstream evStringStream;
+		evStringStream <<
+			"!system=ZFS "
+			"class=ereport.fs.zfs.io "
+			"ena=12091638756982918145 "
+			"parent_guid=13237004955564865395 "
+			"parent_type=raidz "
+			"pool=testpool.4415 "
+			"pool_context=0 "
+			"pool_failmode=wait "
+			"pool_guid=456 "
+			"subsystem=ZFS "
+			"timestamp=";
+		evStringStream << i << " ";
+		evStringStream <<
+			"type=ereport.fs.zfs.io "
+			"vdev_guid=123 "
+			"vdev_path=/dev/da400 "
+			"vdev_type=disk "
+			"zio_blkid=622 "
+			"zio_err=1 "
+			"zio_level=-2 "
+			"zio_object=0 "
+			"zio_objset=37 "
+			"zio_offset=25598976 "
+			"zio_size=1024";
+		Event *event(Event::CreateEvent(*m_eventFactory,
+						evStringStream.str()));
+		ZfsEvent *zfs_event = static_cast<ZfsEvent*>(event);
+		EXPECT_TRUE(m_caseFile->ReEvaluate(*zfs_event));
+		delete event;
+	}
+
+	m_caseFile->SpliceEvents();
+	EXPECT_FALSE(m_caseFile->ShouldDegrade());
+	EXPECT_TRUE(m_caseFile->ShouldFault());
+}
+
+/*
+ * A Vdev with a very large number of checksum errors should degrade
+ * For performance reasons, RefreshVdevState should be called at most once
+ */
+TEST_F(CaseFileTest, VeryManyChecksumErrors)
+{
+	EXPECT_CALL(*m_caseFile, RefreshVdevState())
+	    .Times(::testing::AtMost(1))
+	    .WillRepeatedly(::testing::Return(true));
+
+	for(int i=0; i<100; i++) {
+		stringstream evStringStream;
+		evStringStream <<
+			"!system=ZFS "
+			"bad_cleared_bits=03000000000000803f50b00000000000 "
+			"bad_range_clears=0000000e "
+			"bad_range_sets=00000000 "
+			"bad_ranges=0000000000000010 "
+			"bad_ranges_min_gap=8 "
+			"bad_set_bits=00000000000000000000000000000000 "
+			"class=ereport.fs.zfs.checksum "
+			"ena=12272856582652437505 "
+			"parent_guid=5838204195352909894 "
+			"parent_type=raidz pool=testpool.7640 "
+			"pool_context=0 "
+			"pool_failmode=wait "
+			"pool_guid=456 "
+			"subsystem=ZFS timestamp=";
+		evStringStream << i << " ";
+		evStringStream <<
+			"type=ereport.fs.zfs.checksum "
+			"vdev_guid=123 "
+			"vdev_path=/mnt/tmp/file1.7702 "
+			"vdev_type=file "
+			"zio_blkid=0 "
+			"zio_err=0 "
+			"zio_level=0 "
+			"zio_object=3 "
+			"zio_objset=0 "
+			"zio_offset=16896 "
+			"zio_size=512";
+		Event *event(Event::CreateEvent(*m_eventFactory,
+						evStringStream.str()));
+		ZfsEvent *zfs_event = static_cast<ZfsEvent*>(event);
+		EXPECT_TRUE(m_caseFile->ReEvaluate(*zfs_event));
+		delete event;
+	}
+
+	m_caseFile->SpliceEvents();
+	EXPECT_TRUE(m_caseFile->ShouldDegrade());
+	EXPECT_FALSE(m_caseFile->ShouldFault());
+}
+
+/*
+ * Test CaseFile::ReEvaluateByGuid
+ */
+class ReEvaluateByGuidTest : public ::testing::Test
+{
+protected:
+	virtual void SetUp()
+	{
+		m_eventFactory = new EventFactory();
+		m_eventFactory->UpdateRegistry(MockZfsEvent::s_buildRecords,
+		    NUM_ELEMENTS(MockZfsEvent::s_buildRecords));
+		m_event = Event::CreateEvent(*m_eventFactory, s_evString);
+		nvlist_alloc(&m_vdevConfig, NV_UNIQUE_NAME, 0);
+		ASSERT_EQ(0, nvlist_add_uint64(m_vdevConfig,
+					       ZPOOL_CONFIG_GUID, 0xbeef));
+		m_vdev456 = new ::testing::NiceMock<MockVdev>(m_vdevConfig);
+		m_vdev789 = new ::testing::NiceMock<MockVdev>(m_vdevConfig);
+		ON_CALL(*m_vdev456, GUID())
+		    .WillByDefault(::testing::Return(Guid(123)));
+		ON_CALL(*m_vdev456, PoolGUID())
+		    .WillByDefault(::testing::Return(Guid(456)));
+		ON_CALL(*m_vdev456, State())
+		    .WillByDefault(::testing::Return(VDEV_STATE_HEALTHY));
+		ON_CALL(*m_vdev789, GUID())
+		    .WillByDefault(::testing::Return(Guid(123)));
+		ON_CALL(*m_vdev789, PoolGUID())
+		    .WillByDefault(::testing::Return(Guid(789)));
+		ON_CALL(*m_vdev789, State())
+		    .WillByDefault(::testing::Return(VDEV_STATE_HEALTHY));
+		m_caseFile456 = NULL;
+		m_caseFile789 = NULL;
+		return;
+	}
+
+	virtual void TearDown()
+	{
+		delete m_caseFile456;
+		delete m_caseFile789;
+		nvlist_free(m_vdevConfig);
+		delete m_vdev456;
+		delete m_vdev789;
+		delete m_event;
+		delete m_eventFactory;
+	}
+
+	static string			 s_evString;
+	nvlist_t			*m_vdevConfig;
+	::testing::NiceMock<MockVdev>	*m_vdev456;
+	::testing::NiceMock<MockVdev>	*m_vdev789;
+	TestableCaseFile 		*m_caseFile456;
+	TestableCaseFile 		*m_caseFile789;
+	Event				*m_event;
+	EventFactory			*m_eventFactory;
+};
+
+string ReEvaluateByGuidTest::s_evString(
+	"!system=ZFS "
+	"pool_guid=16271873792808333580 "
+	"pool_name=foo "
+	"subsystem=ZFS "
+	"timestamp=1360620391 "
+	"type=misc.fs.zfs.config_sync");
+
+
+/*
+ * Test the ReEvaluateByGuid method on an empty list of casefiles.
+ * We must create one event, even though it never gets used, because it will
+ * be passed by reference to ReEvaluateByGuid
+ */
+TEST_F(ReEvaluateByGuidTest, ReEvaluateByGuid_empty)
+{
+	ZfsEvent *zfs_event = static_cast<ZfsEvent*>(m_event);
+
+	EXPECT_EQ(0, TestableCaseFile::getActiveCases());
+	CaseFile::ReEvaluateByGuid(Guid(456), *zfs_event);
+	EXPECT_EQ(0, TestableCaseFile::getActiveCases());
+}
+
+/*
+ * Test the ReEvaluateByGuid method on a list of CaseFiles that contains only
+ * one CaseFile, which doesn't match the criteria
+ */
+TEST_F(ReEvaluateByGuidTest, ReEvaluateByGuid_oneFalse)
+{
+	m_caseFile456 = &TestableCaseFile::Create(*m_vdev456);
+	ZfsEvent *zfs_event = static_cast<ZfsEvent*>(m_event);
+
+	EXPECT_EQ(1, TestableCaseFile::getActiveCases());
+	EXPECT_CALL(*m_caseFile456, ReEvaluate(::testing::_))
+	    .Times(::testing::Exactly(0));
+	CaseFile::ReEvaluateByGuid(Guid(789), *zfs_event);
+	EXPECT_EQ(1, TestableCaseFile::getActiveCases());
+}
+
+/*
+ * Test the ReEvaluateByGuid method on a list of CaseFiles that contains only
+ * one CaseFile, which does match the criteria
+ */
+TEST_F(ReEvaluateByGuidTest, ReEvaluateByGuid_oneTrue)
+{
+	m_caseFile456 = &TestableCaseFile::Create(*m_vdev456);
+	ZfsEvent *zfs_event = static_cast<ZfsEvent*>(m_event);
+
+	EXPECT_EQ(1, TestableCaseFile::getActiveCases());
+	EXPECT_CALL(*m_caseFile456, ReEvaluate(::testing::_))
+	    .Times(::testing::Exactly(1))
+	    .WillRepeatedly(::testing::Return(false));
+	CaseFile::ReEvaluateByGuid(Guid(456), *zfs_event);
+	EXPECT_EQ(1, TestableCaseFile::getActiveCases());
+}
+
+/*
+ * Test the ReEvaluateByGuid method on a long list of CaseFiles that contains a
+ * few cases which meet the criteria
+ */
+TEST_F(ReEvaluateByGuidTest, ReEvaluateByGuid_five)
+{
+	TestableCaseFile *CaseFile1 = &TestableCaseFile::Create(*m_vdev456);
+	TestableCaseFile *CaseFile2 = &TestableCaseFile::Create(*m_vdev789);
+	TestableCaseFile *CaseFile3 = &TestableCaseFile::Create(*m_vdev456);
+	TestableCaseFile *CaseFile4 = &TestableCaseFile::Create(*m_vdev789);
+	TestableCaseFile *CaseFile5 = &TestableCaseFile::Create(*m_vdev789);
+	ZfsEvent *zfs_event = static_cast<ZfsEvent*>(m_event);
+
+	EXPECT_EQ(5, TestableCaseFile::getActiveCases());
+	EXPECT_CALL(*CaseFile1, ReEvaluate(::testing::_))
+	    .Times(::testing::Exactly(1))
+	    .WillRepeatedly(::testing::Return(false));
+	EXPECT_CALL(*CaseFile3, ReEvaluate(::testing::_))
+	    .Times(::testing::Exactly(1))
+	    .WillRepeatedly(::testing::Return(false));
+	EXPECT_CALL(*CaseFile2, ReEvaluate(::testing::_))
+	    .Times(::testing::Exactly(0));
+	EXPECT_CALL(*CaseFile4, ReEvaluate(::testing::_))
+	    .Times(::testing::Exactly(0));
+	EXPECT_CALL(*CaseFile5, ReEvaluate(::testing::_))
+	    .Times(::testing::Exactly(0));
+	CaseFile::ReEvaluateByGuid(Guid(456), *zfs_event);
+	EXPECT_EQ(5, TestableCaseFile::getActiveCases());
+	delete CaseFile1;
+	delete CaseFile2;
+	delete CaseFile3;
+	delete CaseFile4;
+	delete CaseFile5;
+}
Index: cddl/usr.sbin/zfsd/tests/zfsd_unittest.supp
===================================================================
--- /dev/null
+++ cddl/usr.sbin/zfsd/tests/zfsd_unittest.supp
@@ -0,0 +1,104 @@
+# This is a valgrind suppression file used for running zfsd_unittest with
+# valgrind.  It suppress spurious errors generated by the googletest and
+# googlemock libraries.
+#
+# To use, do:
+# valgrind --suppressions=$PWD/zfsd_unittest.supp ./zfsd_unittest 
+
+{
+   <insert_a_suppression_name_here>
+   Memcheck:Free
+   fun:free
+   ...
+   fun:__cxa_finalize
+   fun:exit
+   fun:(below main)
+}
+
+{
+   <insert_a_suppression_name_here>
+   Memcheck:Free
+   fun:free
+    ...
+   fun:_ZN7testing8internal27PrettyUnitTestResultPrinter*
+   ...
+   ...
+   fun:main
+}
+
+{
+   <insert_a_suppression_name_here>
+   Memcheck:Free
+   fun:free
+   fun:_ZN7testing*
+   ...
+   fun:main
+}
+
+{
+   <insert_a_suppression_name_here>
+   Memcheck:Free
+   fun:free
+   ...
+   fun:_Z41__static_initialization_and_destruction_0ii
+   ...
+}
+
+{
+   <insert_a_suppression_name_here>
+   Memcheck:Free
+   fun:free
+   ...
+   fun:_ZN7testing8internal8MockSpec*
+   ...
+   fun:_ZN7testing4Test3RunEv
+   fun:_ZN7testing8internal12TestInfoImpl3RunEv
+   fun:_ZN7testing8TestCase3RunEv
+   fun:_ZN7testing8internal12UnitTestImpl11RunAllTestsEv
+}
+
+{
+   <insert_a_suppression_name_here>
+   Memcheck:Free
+   fun:free
+   ...
+   fun:_ZN7testing8internal14FunctionMocker*
+   ...
+}
+
+{
+   <insert_a_suppression_name_here>
+   Memcheck:Cond
+   obj:/lib/libc.so.7
+   obj:/lib/libc.so.7
+   fun:snprintf
+   fun:_ZN7testing45_GLOBAL__N_src_gmock_all.cc_00000000_917CAD5926PrintByteSegmentInObjectToEPKhmmPSo
+   fun:_ZN7testing9internal220PrintBytesInObjectToEPKhmPSo
+   fun:_ZN7testing9internal220TypeWithoutFormatterI8ZfsEventLb0EE10PrintValueERKS2_PSo
+   fun:_ZN7testing9internal2lsIcSt11char_traitsIcE8ZfsEventEERSt13basic_ostreamIT_T0_ES9_RKT1_
+   fun:_ZN16testing_internal26DefaultPrintNonContainerToI8ZfsEventEEvRKT_PSo
+   fun:_ZN7testing8internal14DefaultPrintToI8ZfsEventEEvcNS0_13bool_constantILb0EEERKT_PSo
+   fun:_ZN7testing8internal7PrintToI8ZfsEventEEvRKT_PSo
+   fun:_ZN7testing8internal16UniversalPrinterIK8ZfsEventE5PrintERS3_PSo
+   fun:_ZN7testing8internal16UniversalPrinterIRK8ZfsEventE5PrintES4_PSo
+}
+
+{
+   <insert_a_suppression_name_here>
+   Memcheck:Cond
+   ...
+   fun:snprintf
+   ...
+   fun:_ZN7testing9internal220PrintBytesInObjectToEPKhmPSo
+   ...
+}
+{
+   <insert_a_suppression_name_here>
+   Memcheck:Value8
+   ...
+   fun:snprintf
+   ...
+   fun:_ZN7testing9internal220PrintBytesInObjectToEPKhmPSo
+   ...
+}
+
Index: cddl/usr.sbin/zfsd/vdev.h
===================================================================
--- /dev/null
+++ cddl/usr.sbin/zfsd/vdev.h
@@ -0,0 +1,178 @@
+/*-
+ * Copyright (c) 2011 Spectra Logic Corporation
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions, and the following disclaimer,
+ *    without modification.
+ * 2. Redistributions in binary form must reproduce at minimum a disclaimer
+ *    substantially similar to the "NO WARRANTY" disclaimer below
+ *    ("Disclaimer") and any redistribution must be conditioned upon
+ *    including a substantially similar Disclaimer requirement for further
+ *    binary redistribution.
+ *
+ * NO WARRANTY
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDERS OR CONTRIBUTORS BE LIABLE FOR SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
+ * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGES.
+ *
+ * Authors: Justin T. Gibbs     (Spectra Logic Corporation)
+ *
+ * $FreeBSD$
+ */
+
+/**
+ * \file vdev.h
+ *
+ * Definition of the Vdev class.
+ *
+ * Header requirements:
+ *
+ *    #include <string>
+ *    #include <list>
+ *
+ *    #include <devdctl/guid.h>
+ */
+#ifndef	_VDEV_H_
+#define	_VDEV_H_
+
+/*=========================== Forward Declarations ===========================*/
+struct zpool_handle;
+typedef struct zpool_handle zpool_handle_t;
+
+struct nvlist;
+typedef struct nvlist nvlist_t;
+
+/*============================= Class Definitions ============================*/
+/*----------------------------------- Vdev -----------------------------------*/
+/**
+ * \brief Wrapper class for a vdev's name/value configuration list
+ *        simplifying access to commonly used vdev attributes.
+ */
+class Vdev
+{
+public:
+	/**
+	 * \brief Instantiate a vdev object for a vdev that is a member
+	 *        of an imported pool.
+	 *
+	 * \param pool        The pool object containing the vdev with
+	 *                    configuration data provided in vdevConfig.
+	 * \param vdevConfig  Vdev configuration data.
+	 *
+	 * This method should be used whenever dealing with vdev's
+	 * enumerated via the ZpoolList class.  The in-core configuration
+	 * data for a vdev does not contain all of the items found in
+	 * the on-disk label.  This requires the vdev class to augment
+	 * the data in vdevConfig with data found in the pool object.
+	 */
+	Vdev(zpool_handle_t *pool, nvlist_t *vdevConfig);
+
+	/**
+	 * \brief Instantiate a vdev object for a vdev that is a member
+	 *        of a pool configuration.
+	 *
+	 * \param poolConfig  The pool configuration containing the vdev
+	 *                    configuration data provided in vdevConfig.
+	 * \param vdevConfig  Vdev configuration data.
+	 *
+	 * This method should be used whenever dealing with vdev's
+	 * enumerated via the ZpoolList class.  The in-core configuration
+	 * data for a vdev does not contain all of the items found in
+	 * the on-disk label.  This requires the vdev class to augment
+	 * the data in vdevConfig with data found in the pool object.
+	 */
+	Vdev(nvlist_t *poolConfig, nvlist_t *vdevConfig);
+
+	/**
+	 * \brief Instantiate a vdev object from a ZFS label stored on
+	 *        the device.
+	 *
+	 * \param vdevConfig  The name/value list retrieved by reading
+	 *                    the label information on a leaf vdev.
+	 */
+	Vdev(nvlist_t *vdevConfig);
+
+	/**
+	 * \brief No-op copy constructor for nonexistent vdevs.
+	 */
+	Vdev();
+	bool			DoesNotExist()	const;
+
+	/**
+	 * \brief Return a list of the vdev's children.
+	 */
+	std::list<Vdev>		 Children();
+
+	virtual DevdCtl::Guid	 GUID()		const;
+	bool			 IsSpare()	const;
+	virtual DevdCtl::Guid	 PoolGUID()	const;
+	virtual vdev_state	 State()	const;
+	std::string		 Path()		const;
+	virtual std::string	 PhysicalPath()	const;
+	std::string		 GUIDString()	const;
+	nvlist_t		*PoolConfig()	const;
+	nvlist_t		*Config()	const;
+	Vdev			 Parent();
+	Vdev			 RootVdev();
+	std::string		 Name(zpool_handle_t *, bool verbose)	const;
+	bool			 IsSpare();
+	bool			 IsAvailableSpare()	const;
+	bool			 IsActiveSpare()	const;
+	bool			 IsResilvering()	const;
+
+private:
+	void			 VdevLookupGuid();
+	bool			 VdevLookupPoolGuid();
+	DevdCtl::Guid		 m_poolGUID;
+	DevdCtl::Guid		 m_vdevGUID;
+	nvlist_t		*m_poolConfig;
+	nvlist_t		*m_config;
+};
+
+//- Special objects -----------------------------------------------------------
+extern Vdev NonexistentVdev;
+
+//- Vdev Inline Public Methods ------------------------------------------------
+inline DevdCtl::Guid
+Vdev::PoolGUID() const
+{
+	return (m_poolGUID);
+}
+
+inline DevdCtl::Guid
+Vdev::GUID() const
+{
+	return (m_vdevGUID);
+}
+
+inline nvlist_t *
+Vdev::PoolConfig() const
+{
+	return (m_poolConfig);
+}
+
+inline nvlist_t *
+Vdev::Config() const
+{
+	return (m_config);
+}
+
+inline bool
+Vdev::DoesNotExist() const
+{
+	return (m_config == NULL);
+}
+
+#endif /* _VDEV_H_ */
Index: cddl/usr.sbin/zfsd/vdev.cc
===================================================================
--- /dev/null
+++ cddl/usr.sbin/zfsd/vdev.cc
@@ -0,0 +1,357 @@
+/*-
+ * Copyright (c) 2011 Spectra Logic Corporation
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions, and the following disclaimer,
+ *    without modification.
+ * 2. Redistributions in binary form must reproduce at minimum a disclaimer
+ *    substantially similar to the "NO WARRANTY" disclaimer below
+ *    ("Disclaimer") and any redistribution must be conditioned upon
+ *    including a substantially similar Disclaimer requirement for further
+ *    binary redistribution.
+ *
+ * NO WARRANTY
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDERS OR CONTRIBUTORS BE LIABLE FOR SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
+ * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGES.
+ *
+ * Authors: Justin T. Gibbs     (Spectra Logic Corporation)
+ *
+ * $FreeBSD$
+ */
+
+/**
+ * \file vdev.cc
+ *
+ * Implementation of the Vdev class.
+ */
+#include <syslog.h>
+#include <sys/cdefs.h>
+#include <sys/fs/zfs.h>
+
+#include <libzfs.h>
+/* 
+ * Undefine flush, defined by cpufunc.h on sparc64, because it conflicts with
+ * C++ flush methods
+ */
+#undef   flush
+
+#include <list>
+#include <map>
+#include <string>
+#include <sstream>
+
+#include <devdctl/guid.h>
+#include <devdctl/event.h>
+#include <devdctl/event_factory.h>
+#include <devdctl/exception.h>
+#include <devdctl/consumer.h>
+
+#include "vdev.h"
+#include "vdev_iterator.h"
+#include "zfsd.h"
+#include "zfsd_exception.h"
+#include "zpool_list.h"
+
+__FBSDID("$FreeBSD$");
+/*============================ Namespace Control =============================*/
+using std::string;
+using std::stringstream;
+
+//- Special objects -----------------------------------------------------------
+Vdev NonexistentVdev;
+
+//- Vdev Inline Public Methods ------------------------------------------------
+/*=========================== Class Implementations ==========================*/
+/*----------------------------------- Vdev -----------------------------------*/
+
+/* Special constructor for NonexistentVdev. */
+Vdev::Vdev()
+ : m_poolConfig(NULL),
+   m_config(NULL)
+{}
+
+bool
+Vdev::VdevLookupPoolGuid()
+{
+	uint64_t guid;
+	if (nvlist_lookup_uint64(m_poolConfig, ZPOOL_CONFIG_POOL_GUID, &guid))
+		return (false);
+	m_poolGUID = guid;
+	return (true);
+}
+
+void
+Vdev::VdevLookupGuid()
+{
+	uint64_t guid;
+	if (nvlist_lookup_uint64(m_config, ZPOOL_CONFIG_GUID, &guid) != 0)
+		throw ZfsdException("Unable to extract vdev GUID "
+				    "from vdev config data.");
+	m_vdevGUID = guid;
+}
+
+Vdev::Vdev(zpool_handle_t *pool, nvlist_t *config)
+ : m_poolConfig(zpool_get_config(pool, NULL)),
+   m_config(config)
+{
+	if (!VdevLookupPoolGuid())
+		throw ZfsdException("Can't extract pool GUID from handle.");
+	VdevLookupGuid();
+}
+
+Vdev::Vdev(nvlist_t *poolConfig, nvlist_t *config)
+ : m_poolConfig(poolConfig),
+   m_config(config)
+{
+	if (!VdevLookupPoolGuid())
+		throw ZfsdException("Can't extract pool GUID from config.");
+	VdevLookupGuid();
+}
+
+Vdev::Vdev(nvlist_t *labelConfig)
+ : m_poolConfig(labelConfig),
+   m_config(labelConfig)
+{
+	/*
+	 * Spares do not have a Pool GUID.  Tolerate its absence.
+	 * Code accessing this Vdev in a context where the Pool GUID is
+	 * required will find it invalid (as it is upon Vdev construction)
+	 * and act accordingly.
+	 */
+	(void) VdevLookupPoolGuid();
+	VdevLookupGuid();
+
+	try {
+		m_config = VdevIterator(labelConfig).Find(m_vdevGUID);
+	} catch (const ZfsdException &exp) {
+		/*
+		 * When reading a spare's label, it is normal not to find
+		 * a list of vdevs
+		 */
+		m_config = NULL;
+	}
+}
+
+bool
+Vdev::IsSpare() const
+{
+	uint64_t is_spare(0);
+
+	if (m_config == NULL)
+		return (false);
+
+	(void)nvlist_lookup_uint64(m_config, ZPOOL_CONFIG_IS_SPARE, &is_spare);
+	return (bool(is_spare));
+}
+
+vdev_state
+Vdev::State() const
+{
+	uint64_t    *nvlist_array;
+	vdev_stat_t *vs;
+	uint_t       vsc;
+
+	if (m_config == NULL) {
+		/*
+		 * If we couldn't find the list of vdevs, that normally means
+		 * that this is an available hotspare.  In that case, we will
+		 * presume it to be healthy.  Even if this spare had formerly
+		 * been in use, been degraded, and been replaced, the act of
+		 * replacement wipes the degraded bit from the label.  So we
+		 * have no choice but to presume that it is healthy.
+		 */
+		return (VDEV_STATE_HEALTHY);
+	}
+
+	if (nvlist_lookup_uint64_array(m_config, ZPOOL_CONFIG_VDEV_STATS,
+				       &nvlist_array, &vsc) == 0) {
+		vs = reinterpret_cast<vdev_stat_t *>(nvlist_array);
+		return (static_cast<vdev_state>(vs->vs_state));
+	}
+
+	/*
+	 * Stats are not available.  This vdev was created from a label.
+	 * Synthesize a state based on available data.
+	 */
+	uint64_t faulted(0);
+	uint64_t degraded(0);
+	(void)nvlist_lookup_uint64(m_config, ZPOOL_CONFIG_FAULTED, &faulted);
+	(void)nvlist_lookup_uint64(m_config, ZPOOL_CONFIG_DEGRADED, &degraded);
+	if (faulted)
+		return (VDEV_STATE_FAULTED);
+	if (degraded)
+		return (VDEV_STATE_DEGRADED);
+	return (VDEV_STATE_HEALTHY);
+}
+
+std::list<Vdev>
+Vdev::Children()
+{
+	nvlist_t **vdevChildren;
+	int result;
+	u_int numChildren;
+	std::list<Vdev> children;
+
+	if (m_poolConfig == NULL || m_config == NULL)
+		return (children);
+
+	result = nvlist_lookup_nvlist_array(m_config,
+	    ZPOOL_CONFIG_CHILDREN, &vdevChildren, &numChildren);
+	if (result != 0)
+		return (children);
+
+	for (u_int c = 0;c < numChildren; c++)
+		children.push_back(Vdev(m_poolConfig, vdevChildren[c]));
+
+	return (children);
+}
+
+Vdev
+Vdev::RootVdev()
+{
+	nvlist_t *rootVdev;
+
+	if (m_poolConfig == NULL)
+		return (NonexistentVdev);
+
+	if (nvlist_lookup_nvlist(m_poolConfig, ZPOOL_CONFIG_VDEV_TREE,
+	    &rootVdev) != 0)
+		return (NonexistentVdev);
+	return (Vdev(m_poolConfig, rootVdev));
+}
+
+/*
+ * Find our parent.  This requires doing a traversal of the config; we can't
+ * cache it as leaf vdevs may change their pool config location (spare,
+ * replacing, mirror, etc).
+ */
+Vdev
+Vdev::Parent()
+{
+	std::list<Vdev> to_examine;
+	std::list<Vdev> children;
+	std::list<Vdev>::iterator children_it;
+
+	to_examine.push_back(RootVdev());
+	for (;;) {
+		if (to_examine.empty())
+			return (NonexistentVdev);
+		Vdev vd = to_examine.front();
+		if (vd.DoesNotExist())
+			return (NonexistentVdev);
+		to_examine.pop_front();
+		children = vd.Children();
+		children_it = children.begin();
+		for (;children_it != children.end(); children_it++) {
+			Vdev child = *children_it;
+
+			if (child.GUID() == GUID())
+				return (vd);
+			to_examine.push_front(child);
+		}
+	}
+}
+
+bool
+Vdev::IsAvailableSpare() const
+{
+	/* If we have a pool guid, we cannot be an available spare. */
+	if (PoolGUID())
+		return (false);
+
+	return (true);
+}
+
+bool
+Vdev::IsSpare()
+{
+	uint64_t spare;
+	if (nvlist_lookup_uint64(m_config, ZPOOL_CONFIG_IS_SPARE, &spare) != 0)
+		return (false);
+	return (spare != 0);
+}
+
+bool
+Vdev::IsActiveSpare() const
+{
+	vdev_stat_t *vs;
+	uint_t c;
+
+	if (m_poolConfig == NULL)
+		return (false);
+
+	(void) nvlist_lookup_uint64_array(m_config, ZPOOL_CONFIG_VDEV_STATS,
+	    reinterpret_cast<uint64_t **>(&vs), &c);
+	if (vs == NULL || vs->vs_aux != VDEV_AUX_SPARED)
+		return (false);
+	return (true);
+}
+
+bool
+Vdev::IsResilvering() const
+{
+	pool_scan_stat_t *ps = NULL;
+	uint_t c;
+
+	if (State() != VDEV_STATE_HEALTHY)
+		return (false);
+
+	(void) nvlist_lookup_uint64_array(m_config, ZPOOL_CONFIG_SCAN_STATS,
+	    reinterpret_cast<uint64_t **>(&ps), &c);
+	if (ps == NULL || ps->pss_func != POOL_SCAN_RESILVER)
+		return (false);
+	return (true);
+}
+
+string
+Vdev::GUIDString() const
+{
+	stringstream vdevGUIDString;
+
+	vdevGUIDString << GUID();
+	return (vdevGUIDString.str());
+}
+
+string
+Vdev::Name(zpool_handle_t *zhp, bool verbose) const
+{
+	return (zpool_vdev_name(g_zfsHandle, zhp, m_config,
+	    verbose ? B_TRUE : B_FALSE));
+}
+
+string
+Vdev::Path() const
+{
+	char *path(NULL);
+
+	if ((m_config != NULL)
+	    && (nvlist_lookup_string(m_config, ZPOOL_CONFIG_PATH, &path) == 0))
+		return (path);
+
+	return ("");
+}
+
+string
+Vdev::PhysicalPath() const
+{
+	char *path(NULL);
+
+	if ((m_config != NULL) && (nvlist_lookup_string(m_config,
+				    ZPOOL_CONFIG_PHYS_PATH, &path) == 0))
+		return (path);
+
+	return ("");
+}
Index: cddl/usr.sbin/zfsd/vdev_iterator.h
===================================================================
--- /dev/null
+++ cddl/usr.sbin/zfsd/vdev_iterator.h
@@ -0,0 +1,121 @@
+/*-
+ * Copyright (c) 2011 Spectra Logic Corporation
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions, and the following disclaimer,
+ *    without modification.
+ * 2. Redistributions in binary form must reproduce at minimum a disclaimer
+ *    substantially similar to the "NO WARRANTY" disclaimer below
+ *    ("Disclaimer") and any redistribution must be conditioned upon
+ *    including a substantially similar Disclaimer requirement for further
+ *    binary redistribution.
+ *
+ * NO WARRANTY
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDERS OR CONTRIBUTORS BE LIABLE FOR SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
+ * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGES.
+ *
+ * Authors: Justin T. Gibbs     (Spectra Logic Corporation)
+ */
+
+/**
+ * \file vdev_iterator.h
+ *
+ * VdevIterator class definition.
+ *
+ * Header requirements:
+ *
+ *    #include <list>
+ */
+#ifndef	_VDEV_ITERATOR_H_
+#define	_VDEV_ITERATOR_H_
+
+/*=========================== Forward Declarations ===========================*/
+struct zpool_handle;
+typedef struct zpool_handle zpool_handle_t;
+
+struct nvlist;
+typedef struct nvlist nvlist_t;
+
+class Vdev;
+
+/*============================= Class Definitions ============================*/
+/*------------------------------- VdevIterator -------------------------------*/
+typedef bool VdevCallback_t(Vdev &vdev, void *cbArg);
+
+/**
+ * \brief VdevIterator provides mechanisms for traversing and searching
+ *        the leaf vdevs contained in a ZFS pool configuration.
+ */
+class VdevIterator
+{
+public:
+	/**
+	 * \brief Instantiate a VdevIterator for the given ZFS pool.
+	 *
+	 * \param pool  The ZFS pool to traverse/search.
+	 */
+	VdevIterator(zpool_handle_t *pool);
+
+	/**
+	 * \brief Instantiate a VdevIterator for the given ZFS pool.
+	 *
+	 * \param poolConfig  The configuration data for the ZFS pool
+	 *                    to traverse/search.
+	 */
+	VdevIterator(nvlist_t *poolConfig);
+
+	/**
+	 * \brief Reset this iterator's cursor so that Next() will
+	 *        report the first member of the pool.
+	 */
+	void      Reset();
+
+	/**
+	 * \brief Report the leaf vdev at this iterator's cursor and increment
+	 *        the cursor to the next leaf pool member.
+	 */
+	nvlist_t *Next();
+
+	/**
+	 * \brief Traverse the entire pool configuration starting its
+	 *        first member, returning a vdev object with the given
+	 *        vdev GUID if found.
+	 *
+	 * \param vdevGUID  The vdev GUID of the vdev object to find.
+	 *
+	 * \return  A Vdev object for the matching vdev if found.  Otherwise
+	 *          NULL.
+	 *
+	 * Upon return, the VdevIterator's cursor points to the vdev just
+	 * past the returned vdev or end() if no matching vdev is found.
+	 */
+	nvlist_t *Find(DevdCtl::Guid vdevGUID);
+
+	/**
+	 * \brief Perform the specified operation on each leaf member of
+	 *        a pool's vdev membership.
+	 *
+	 * \param cb     Callback function to execute for each member.
+	 * \param cbArg  Argument to pass to cb.
+	 */
+	void	  Each(VdevCallback_t *cb, void *cbArg);
+
+private:
+	nvlist_t                *m_poolConfig;
+	std::list<nvlist_t *>	 m_vdevQueue;
+};
+
+#endif	/* _VDEV_ITERATOR_H_ */
Index: cddl/usr.sbin/zfsd/vdev_iterator.cc
===================================================================
--- /dev/null
+++ cddl/usr.sbin/zfsd/vdev_iterator.cc
@@ -0,0 +1,151 @@
+/*-
+ * Copyright (c) 2011 Spectra Logic Corporation
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions, and the following disclaimer,
+ *    without modification.
+ * 2. Redistributions in binary form must reproduce at minimum a disclaimer
+ *    substantially similar to the "NO WARRANTY" disclaimer below
+ *    ("Disclaimer") and any redistribution must be conditioned upon
+ *    including a substantially similar Disclaimer requirement for further
+ *    binary redistribution.
+ *
+ * NO WARRANTY
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDERS OR CONTRIBUTORS BE LIABLE FOR SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
+ * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGES.
+ *
+ * Authors: Justin T. Gibbs     (Spectra Logic Corporation)
+ */
+
+/**
+ * \file vdev_iterator.cc
+ *
+ * Implementation of the VdevIterator class.
+ */
+#include <sys/cdefs.h>
+#include <sys/fs/zfs.h>
+
+#include <stdint.h>
+#include <syslog.h>
+
+#include <libzfs.h>
+
+#include <list>
+#include <string>
+
+#include <devdctl/exception.h>
+#include <devdctl/guid.h>
+
+#include "vdev.h"
+#include "vdev_iterator.h"
+#include "zfsd_exception.h"
+
+/*============================ Namespace Control =============================*/
+using DevdCtl::Guid;
+
+/*=========================== Class Implementations ==========================*/
+/*------------------------------- VdevIterator -------------------------------*/
+VdevIterator::VdevIterator(zpool_handle_t *pool)
+ : m_poolConfig(zpool_get_config(pool, NULL))
+{
+	Reset();
+}
+
+VdevIterator::VdevIterator(nvlist_t *poolConfig)
+ : m_poolConfig(poolConfig)
+{
+	Reset();
+}
+
+void
+VdevIterator::Reset()
+{
+	nvlist_t  *rootVdev;
+	int	   result;
+
+	result = nvlist_lookup_nvlist(m_poolConfig,
+				      ZPOOL_CONFIG_VDEV_TREE,
+				      &rootVdev);
+	if (result != 0)
+		throw ZfsdException(m_poolConfig, "Unable to extract "
+				    "ZPOOL_CONFIG_VDEV_TREE from pool.");
+	m_vdevQueue.assign(1, rootVdev);
+}
+
+nvlist_t *
+VdevIterator::Next()
+{
+	nvlist_t *vdevConfig;
+
+	if (m_vdevQueue.empty())
+		return (NULL);
+
+	while (1) {
+		nvlist_t **vdevChildren;
+		int        result;
+		u_int      numChildren;
+
+		vdevConfig = m_vdevQueue.front();
+		m_vdevQueue.pop_front();
+
+		/* Expand non-leaf vdevs. */
+		result = nvlist_lookup_nvlist_array(vdevConfig,
+						    ZPOOL_CONFIG_CHILDREN,
+						   &vdevChildren, &numChildren);
+		if (result != 0) {
+			/* leaf vdev */
+			break;
+		}
+
+		/*
+		 * Insert children at the head of the queue to effect a
+		 * depth first traversal of the tree.
+		 */
+		m_vdevQueue.insert(m_vdevQueue.begin(), vdevChildren,
+				   vdevChildren + numChildren);
+	};
+
+	return (vdevConfig);
+}
+
+void
+VdevIterator::Each(VdevCallback_t *callBack, void *callBackArg)
+{
+	nvlist_t *vdevConfig;
+
+	Reset();
+	while ((vdevConfig = Next()) != NULL) {
+		Vdev vdev(m_poolConfig, vdevConfig);
+
+		if (callBack(vdev, callBackArg))
+			break;
+	}
+}
+
+nvlist_t *
+VdevIterator::Find(Guid vdevGUID)
+{
+	nvlist_t *vdevConfig;
+
+	Reset();
+	while ((vdevConfig = Next()) != NULL) {
+		Vdev vdev(m_poolConfig, vdevConfig);
+
+		if (vdev.GUID() == vdevGUID)
+			return (vdevConfig);
+	}
+	return (NULL);
+}
Index: cddl/usr.sbin/zfsd/zfsd.h
===================================================================
--- /dev/null
+++ cddl/usr.sbin/zfsd/zfsd.h
@@ -0,0 +1,228 @@
+/*-
+ * Copyright (c) 2011 Spectra Logic Corporation
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions, and the following disclaimer,
+ *    without modification.
+ * 2. Redistributions in binary form must reproduce at minimum a disclaimer
+ *    substantially similar to the "NO WARRANTY" disclaimer below
+ *    ("Disclaimer") and any redistribution must be conditioned upon
+ *    including a substantially similar Disclaimer requirement for further
+ *    binary redistribution.
+ *
+ * NO WARRANTY
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDERS OR CONTRIBUTORS BE LIABLE FOR SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
+ * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGES.
+ *
+ * Authors: Justin T. Gibbs     (Spectra Logic Corporation)
+ *
+ * $FreeBSD$
+ */
+
+/**
+ * \file zfsd.h
+ *
+ * Class definitions and supporting data strutures for the ZFS fault
+ * management daemon.
+ *
+ * Header requirements:
+ *
+ *    #include <sys/fs/zfs.h>
+ *
+ *    #include <libzfs.h>
+ *
+ *    #include <list>
+ *    #include <map>
+ *    #include <string>
+ *
+ *    #include <devdctl/guid.h>
+ *    #include <devdctl/event.h>
+ *    #include <devdctl/event_factory.h>
+ *    #include <devdctl/consumer.h>
+ *
+ *    #include "vdev_iterator.h"
+ */
+#ifndef	_ZFSD_H_
+#define	_ZFSD_H_
+
+/*=========================== Forward Declarations ===========================*/
+struct pidfh;
+
+struct zpool_handle;
+typedef struct zpool_handle zpool_handle_t;
+
+struct zfs_handle;
+typedef struct libzfs_handle libzfs_handle_t;
+
+struct nvlist;
+typedef struct nvlist nvlist_t;
+
+typedef int LeafIterFunc(zpool_handle_t *, nvlist_t *, void *);
+
+/*================================ Global Data ===============================*/
+extern int              g_debug;
+extern libzfs_handle_t *g_zfsHandle;
+
+/*============================= Class Definitions ============================*/
+/*--------------------------------- ZfsDaemon --------------------------------*/
+/**
+ * Static singleton orchestrating the operations of the ZFS daemon program.
+ */
+class ZfsDaemon : public DevdCtl::Consumer
+{
+public:
+	/** Return the ZfsDaemon singleton. */
+	static ZfsDaemon &Get();
+
+	/**
+	 * Used by signal handlers to ensure, in a race free way, that
+	 * the event loop will perform at least one more full loop
+	 * before sleeping again.
+	 */
+	static void WakeEventLoop();
+
+	/**
+	 * Schedules a rescan of devices in the system for potential
+	 * candidates to replace a missing vdev.  The scan is performed
+	 * during the next run of the event loop.
+	 */
+	static void RequestSystemRescan();
+
+	/** Daemonize and perform all functions of the ZFS daemon. */
+	static void Run();
+
+private:
+	ZfsDaemon();
+	~ZfsDaemon();
+
+	static VdevCallback_t VdevAddCaseFile;
+
+	/** Purge our cache of outstanding ZFS issues in the system. */
+	void PurgeCaseFiles();
+
+	/** Build a cache of outstanding ZFS issues in the system. */
+	void BuildCaseFiles();
+
+	/**
+	 * Iterate over all known issues and attempt to solve them
+	 * given resources currently available in the system.
+	 */
+	void RescanSystem();
+
+	/**
+	 * Interrogate the system looking for previously unknown
+	 * faults that occurred either before ZFSD was started,
+	 * or during a period of lost communication with Devd.
+	 */
+	void DetectMissedEvents();
+
+	/**
+	 * Wait for and process event source activity.
+	 */
+	void EventLoop();
+
+	/**
+	 * Signal handler for which our response is to
+	 * log the current state of the daemon.
+	 *
+	 * \param sigNum  The signal caught.
+	 */
+	static void InfoSignalHandler(int sigNum);
+
+	/**
+	 * Signal handler for which our response is to
+	 * request a case rescan.
+	 *
+	 * \param sigNum  The signal caught.
+	 */
+	static void RescanSignalHandler(int sigNum);
+
+	/**
+	 * Signal handler for which our response is to
+	 * gracefully terminate.
+	 *
+	 * \param sigNum  The signal caught.
+	 */
+	static void QuitSignalHandler(int sigNum);
+
+	/**
+	 * Open and lock our PID file.
+	 */
+	static void OpenPIDFile();
+
+	/**
+	 * Update our PID file with our PID.
+	 */
+	static void UpdatePIDFile();
+
+	/**
+	 * Close and release the lock on our PID file.
+	 */
+	static void ClosePIDFile();
+
+	/**
+	 * Perform syslog configuration.
+	 */
+	static void InitializeSyslog();
+
+	static ZfsDaemon		       *s_theZfsDaemon;
+
+	/**
+	 * Set to true when our program is signaled to
+	 * gracefully exit.
+	 */
+	static bool				s_logCaseFiles;
+
+	/**
+	 * Set to true when our program is signaled to
+	 * gracefully exit.
+	 */
+	static bool				s_terminateEventLoop;
+
+	/**
+	 * The canonical path and file name of zfsd's PID file.
+	 */
+	static char				s_pidFilePath[];
+
+	/**
+	 * Control structure for PIDFILE(3) API.
+	 */
+	static pidfh			       *s_pidFH;
+
+	/**
+	 * Pipe file descriptors used to close races with our
+	 * signal handlers.
+	 */
+	static int				s_signalPipeFD[2];
+
+	/**
+	 * Flag controlling a rescan from ZFSD's event loop of all
+	 * GEOM providers in the system to find candidates for solving
+	 * cases.
+	 */
+	static bool				s_systemRescanRequested;
+
+	/**
+	 * Flag controlling whether events can be queued.  This boolean
+	 * is set during event replay to ensure that events for pools or
+	 * devices no longer in the system are not retained forever.
+	 */
+	static bool				s_consumingEvents;
+
+	static DevdCtl::EventFactory::Record	s_registryEntries[];
+};
+
+#endif	/* _ZFSD_H_ */
Index: cddl/usr.sbin/zfsd/zfsd.8
===================================================================
--- /dev/null
+++ cddl/usr.sbin/zfsd/zfsd.8
@@ -0,0 +1,157 @@
+.\"-
+.\" Copyright (c) 2016 Allan Jude
+.\" All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\"    notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\"    notice, this list of conditions and the following disclaimer in the
+.\"    documentation and/or other materials provided with the distribution.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" $FreeBSD$
+.\"
+.Dd May 26, 2016
+.Dt ZFSD 8
+.Os
+.Sh NAME
+.Nm zfsd
+.Nd ZFS fault management daemon
+.Sh SYNOPSIS
+.Nm
+.Op Fl d
+.Sh DESCRIPTION
+.Nm
+attempts to resolve ZFS faults that the kernel can't resolve by itself.
+It listens to
+.Xr devctl 4
+events, which are how the kernel notifies userland of events such as I/O
+errors and disk removals.
+.Nm
+attempts to resolve these faults by activating or deactivating hot spares
+and onlining offline vdevs.
+.Pp
+The following options are available:
+.Bl -tag -width indent
+.It Fl d
+Run in the foreground instead of daemonizing.
+.El
+.Pp
+System administrators never interact with
+.Nm
+directly.
+Instead, they control its behavior indirectly through zpool configuration.
+There are two ways to influence
+.Nm :
+assigning hotspares and setting pool properties.
+Currently, only the
+.Em autoreplace
+property has any effect.
+See
+.Xr zpool 8
+for details.
+.Pp
+.Nm
+will attempt to resolve the following types of fault:
+.Bl -tag -width a
+.It device removal
+When a leaf vdev disappears,
+.Nm
+will activate any available hotspare.
+.It device arrival
+When a new GEOM device appears,
+.Nm
+will attempt to read its ZFS label, if any.
+If it matches a previously removed vdev on an active pool,
+.Nm
+will online it.
+Once resilvering completes, any active hotspare will detach automatically.
+.Pp
+If the new device has no ZFS label but its physical path matches the
+physical path of a previously removed vdev on an active pool, and that
+pool has the autoreplace property set, then
+.Nm
+will replace the missing vdev with the newly arrived device.
+Once resilvering completes, any active hotspare will detach automatically.
+.It vdev degrade or fault events
+If a vdev becomes degraded or faulted,
+.Nm
+will activate any available hotspare.
+.It I/O errors
+If a leaf vdev generates more than 50 I/O errors in a 60 second period, then
+.Nm
+will mark that vdev as
+.Em FAULTED .
+.Xr zfs 4
+will no longer issue any I/Os to it.
+.Nm
+will activate a hotspare if one is available.
+.It Checksum errors
+If a leaf vdev generates more than 50 checksum errors in a 60 second
+period, then
+.Nm
+will mark that vdev as
+.Em DEGRADED .
+.Xr zfs 4
+will still use it, but zfsd will activate a spare anyway.
+.It Spare addition
+If the system administrator adds a hotspare to a pool that is already degraded,
+.Nm
+will activate the spare.
+.It Resilver complete
+.Nm
+will detach any hotspare once a permanent replacement finishes resilvering.
+.It Physical path change
+If the physical path of an existing disk changes,
+.Nm
+will attempt to replace any missing disk with the same physical path,
+if its pool's autoreplace property is set.
+.El
+.Pp
+.Nm
+will log interesting events and its actions to syslog with facility
+.Em daemon
+and identity
+.Op zfsd .
+.El
+.Sh FILES
+.Bl -tag -width a -compact
+.It Pa /var/db/zfsd/cases
+When
+.Nm
+exits, it serializes any unresolved casefiles here,
+then reads them back in when next it starts up.
+.El
+.Sh SEE ALSO
+.Xr devctl 4 ,
+.Xr zfs 4 ,
+.Xr zpool 8
+.Sh HISTORY
+.Nm
+first appeared in
+.Fx 11.0 .
+.Sh AUTHORS
+.Nm
+was originally written by
+.An Justin Gibbs Aq Mt gibbs@FreeBSD.org
+and
+.An Alan Somers Aq Mt asomers@FreeBSD.org
+.Sh TODO
+In the future,
+.Nm
+should be able to resume a pool that became suspended due to device
+removals, if enough missing devices have returned.
Index: cddl/usr.sbin/zfsd/zfsd.cc
===================================================================
--- /dev/null
+++ cddl/usr.sbin/zfsd/zfsd.cc
@@ -0,0 +1,448 @@
+/*-
+ * Copyright (c) 2011, 2012, 2013 Spectra Logic Corporation
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions, and the following disclaimer,
+ *    without modification.
+ * 2. Redistributions in binary form must reproduce at minimum a disclaimer
+ *    substantially similar to the "NO WARRANTY" disclaimer below
+ *    ("Disclaimer") and any redistribution must be conditioned upon
+ *    including a substantially similar Disclaimer requirement for further
+ *    binary redistribution.
+ *
+ * NO WARRANTY
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDERS OR CONTRIBUTORS BE LIABLE FOR SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
+ * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGES.
+ *
+ * Authors: Justin T. Gibbs     (Spectra Logic Corporation)
+ */
+
+/**
+ * \file zfsd.cc
+ *
+ * The ZFS daemon consumes kernel devdctl(4) event data via devd(8)'s
+ * unix domain socket in order to react to system changes that impact
+ * the function of ZFS storage pools.  The goal of this daemon is to
+ * provide similar functionality to the Solaris ZFS Diagnostic Engine
+ * (zfs-diagnosis), the Solaris ZFS fault handler (zfs-retire), and
+ * the Solaris ZFS vdev insertion agent (zfs-mod sysevent handler).
+ */
+
+#include <sys/cdefs.h>
+#include <sys/param.h>
+#include <sys/fs/zfs.h>
+
+#include <err.h>
+#include <libgeom.h>
+#include <libutil.h>
+#include <poll.h>
+#include <syslog.h>
+
+#include <libzfs.h>
+
+#include <list>
+#include <map>
+#include <string>
+
+#include <devdctl/guid.h>
+#include <devdctl/event.h>
+#include <devdctl/event_factory.h>
+#include <devdctl/exception.h>
+#include <devdctl/consumer.h>
+
+#include "callout.h"
+#include "vdev_iterator.h"
+#include "zfsd_event.h"
+#include "case_file.h"
+#include "vdev.h"
+#include "vdev_iterator.h"
+#include "zfsd.h"
+#include "zfsd_exception.h"
+#include "zpool_list.h"
+
+__FBSDID("$FreeBSD$");
+
+/*================================== Macros ==================================*/
+#define NUM_ELEMENTS(x) (sizeof(x) / sizeof(*x))
+
+/*============================ Namespace Control =============================*/
+using DevdCtl::Event;
+using DevdCtl::EventFactory;
+using DevdCtl::EventList;
+
+/*================================ Global Data ===============================*/
+int              g_debug = 0;
+libzfs_handle_t *g_zfsHandle;
+
+/*--------------------------------- ZfsDaemon --------------------------------*/
+//- ZfsDaemon Static Private Data ----------------------------------------------
+ZfsDaemon	    *ZfsDaemon::s_theZfsDaemon;
+bool		     ZfsDaemon::s_logCaseFiles;
+bool		     ZfsDaemon::s_terminateEventLoop;
+char		     ZfsDaemon::s_pidFilePath[] = "/var/run/zfsd.pid";
+pidfh		    *ZfsDaemon::s_pidFH;
+int		     ZfsDaemon::s_signalPipeFD[2];
+bool		     ZfsDaemon::s_systemRescanRequested(false);
+EventFactory::Record ZfsDaemon::s_registryEntries[] =
+{
+	{ Event::NOTIFY, "DEVFS", &DevfsEvent::Builder },
+	{ Event::NOTIFY, "GEOM",  &GeomEvent::Builder },
+	{ Event::NOTIFY, "ZFS",   &ZfsEvent::Builder }
+};
+
+//- ZfsDaemon Static Public Methods --------------------------------------------
+ZfsDaemon &
+ZfsDaemon::Get()
+{
+	return (*s_theZfsDaemon);
+}
+
+void
+ZfsDaemon::WakeEventLoop()
+{
+	write(s_signalPipeFD[1], "+", 1);
+}
+
+void
+ZfsDaemon::RequestSystemRescan()
+{
+	s_systemRescanRequested = true;
+	ZfsDaemon::WakeEventLoop();
+}
+
+void
+ZfsDaemon::Run()
+{
+	ZfsDaemon daemon;
+
+	while (s_terminateEventLoop == false) {
+
+		try {
+			daemon.DisconnectFromDevd();
+
+			if (daemon.ConnectToDevd() == false) {
+				sleep(30);
+				continue;
+			}
+
+			daemon.DetectMissedEvents();
+
+			daemon.EventLoop();
+
+		} catch (const DevdCtl::Exception &exp) {
+			exp.Log();
+		}
+	}
+
+	daemon.DisconnectFromDevd();
+}
+
+//- ZfsDaemon Private Methods --------------------------------------------------
+ZfsDaemon::ZfsDaemon()
+ : Consumer(/*defBuilder*/NULL, s_registryEntries,
+	    NUM_ELEMENTS(s_registryEntries))
+{
+	if (s_theZfsDaemon != NULL)
+		errx(1, "Multiple ZfsDaemon instances created. Exiting");
+
+	s_theZfsDaemon = this;
+
+	if (pipe(s_signalPipeFD) != 0)
+		errx(1, "Unable to allocate signal pipe. Exiting");
+
+	if (fcntl(s_signalPipeFD[0], F_SETFL, O_NONBLOCK) == -1)
+		errx(1, "Unable to set pipe as non-blocking. Exiting");
+
+	if (fcntl(s_signalPipeFD[1], F_SETFL, O_NONBLOCK) == -1)
+		errx(1, "Unable to set pipe as non-blocking. Exiting");
+
+	signal(SIGHUP,  ZfsDaemon::RescanSignalHandler);
+	signal(SIGINFO, ZfsDaemon::InfoSignalHandler);
+	signal(SIGINT,  ZfsDaemon::QuitSignalHandler);
+	signal(SIGTERM, ZfsDaemon::QuitSignalHandler);
+	signal(SIGUSR1, ZfsDaemon::RescanSignalHandler);
+
+	g_zfsHandle = libzfs_init();
+	if (g_zfsHandle == NULL)
+		errx(1, "Unable to initialize ZFS library. Exiting");
+
+	Callout::Init();
+	InitializeSyslog();
+	OpenPIDFile();
+
+	if (g_debug == 0)
+		daemon(0, 0);
+
+	UpdatePIDFile();
+}
+
+ZfsDaemon::~ZfsDaemon()
+{
+	PurgeCaseFiles();
+	ClosePIDFile();
+}
+
+void
+ZfsDaemon::PurgeCaseFiles()
+{
+	CaseFile::PurgeAll();
+}
+
+bool
+ZfsDaemon::VdevAddCaseFile(Vdev &vdev, void *cbArg)
+{
+	if (vdev.State() != VDEV_STATE_HEALTHY)
+		CaseFile::Create(vdev);
+
+	return (/*break early*/false);
+}
+
+void
+ZfsDaemon::BuildCaseFiles()
+{
+	ZpoolList zpl;
+	ZpoolList::iterator pool;
+
+	/* Add CaseFiles for vdevs with issues. */
+	for (pool = zpl.begin(); pool != zpl.end(); pool++)
+		VdevIterator(*pool).Each(VdevAddCaseFile, NULL);
+
+	/* De-serialize any saved cases. */
+	CaseFile::DeSerialize();
+
+	/* Simulate config_sync events to force CaseFile reevaluation */
+	for (pool = zpl.begin(); pool != zpl.end(); pool++) {
+		char evString[160];
+		Event *event;
+		nvlist_t *config;
+		uint64_t poolGUID;
+		const char *poolname;
+
+		poolname = zpool_get_name(*pool);
+		config = zpool_get_config(*pool, NULL);
+		if (config == NULL) {
+			syslog(LOG_ERR, "ZFSDaemon::BuildCaseFiles: Could not "
+			    "find pool config for pool %s", poolname);
+			continue;
+		}
+		if (nvlist_lookup_uint64(config, ZPOOL_CONFIG_POOL_GUID,
+				     &poolGUID) != 0) {
+			syslog(LOG_ERR, "ZFSDaemon::BuildCaseFiles: Could not "
+			    "find pool guid for pool %s", poolname);
+			continue;
+		}
+
+		
+		snprintf(evString, 160, "!system=ZFS subsystem=ZFS "
+		    "type=misc.fs.zfs.config_sync sub_type=synthesized "
+		    "pool_name=%s pool_guid=%" PRIu64 "\n", poolname, poolGUID);
+		event = Event::CreateEvent(GetFactory(), string(evString));
+		if (event != NULL) {
+			event->Process();
+			delete event;
+		}
+	}
+}
+
+void
+ZfsDaemon::RescanSystem()
+{
+        struct gmesh	  mesh;
+        struct gclass	 *mp;
+        struct ggeom	 *gp;
+        struct gprovider *pp;
+	int		  result;
+
+        /*
+	 * The devdctl system doesn't replay events for new consumers
+	 * of the interface.  Emit manufactured DEVFS arrival events
+	 * for any devices that already before we started or during
+	 * periods where we've lost our connection to devd.
+         */
+	result = geom_gettree(&mesh);
+	if (result != 0) {
+		syslog(LOG_ERR, "ZfsDaemon::RescanSystem: "
+		       "geom_gettree faild with error %d\n", result);
+		return;
+	}
+
+	const string evStart("!system=DEVFS subsystem=CDEV type=CREATE "
+			     "sub_type=synthesized cdev=");
+        LIST_FOREACH(mp, &mesh.lg_class, lg_class) {
+                LIST_FOREACH(gp, &mp->lg_geom, lg_geom) {
+                        LIST_FOREACH(pp, &gp->lg_provider, lg_provider) {
+				Event *event;
+
+				string evString(evStart + pp->lg_name + "\n");
+				event = Event::CreateEvent(GetFactory(),
+							   evString);
+				if (event != NULL) {
+					if (event->Process())
+						SaveEvent(*event);
+					delete event;
+				}
+                        }
+                }
+	}
+	geom_deletetree(&mesh);
+}
+
+void
+ZfsDaemon::DetectMissedEvents()
+{
+	do {
+		PurgeCaseFiles();
+
+		/*
+		 * Discard any events waiting for us.  We don't know
+		 * if they still apply to the current state of the
+		 * system.
+		 */
+		FlushEvents();
+
+		BuildCaseFiles();
+
+		/*
+		 * If the system state has changed during our
+		 * interrogation, start over.
+		 */
+	} while (s_terminateEventLoop == false && EventsPending());
+
+	RescanSystem();
+}
+
+void
+ZfsDaemon::EventLoop()
+{
+	while (s_terminateEventLoop == false) {
+		struct pollfd fds[2];
+		int	      result;
+
+		if (s_logCaseFiles == true) {
+			EventList::iterator event(m_unconsumedEvents.begin());
+			s_logCaseFiles = false;
+			CaseFile::LogAll();
+			while (event != m_unconsumedEvents.end())
+				(*event++)->Log(LOG_INFO);
+		}
+
+		Callout::ExpireCallouts();
+
+		/* Wait for data. */
+		fds[0].fd      = m_devdSockFD;
+		fds[0].events  = POLLIN;
+		fds[0].revents = 0;
+		fds[1].fd      = s_signalPipeFD[0];
+		fds[1].events  = POLLIN;
+		fds[1].revents = 0;
+		result = poll(fds, NUM_ELEMENTS(fds), /*timeout*/INFTIM);
+		if (result == -1) {
+			if (errno == EINTR)
+				continue;
+			else
+				err(1, "Polling for devd events failed");
+		} else if (result == 0) {
+			errx(1, "Unexpected result of 0 from poll. Exiting");
+		}
+
+		if ((fds[0].revents & POLLIN) != 0)
+			ProcessEvents();
+
+		if ((fds[1].revents & POLLIN) != 0) {
+			static char discardBuf[128];
+
+			/*
+			 * This pipe exists just to close the signal
+			 * race.  Its contents are of no interest to
+			 * us, but we must ensure that future signals
+			 * have space in the pipe to write.
+			 */
+			while (read(s_signalPipeFD[0], discardBuf,
+				    sizeof(discardBuf)) > 0)
+				;
+		}
+
+		if (s_systemRescanRequested == true) {
+			s_systemRescanRequested = false;
+			syslog(LOG_INFO, "System Rescan request processed.");
+			RescanSystem();
+		}
+
+		if ((fds[0].revents & POLLERR) != 0) {
+			syslog(LOG_INFO, "POLLERROR detected on devd socket.");
+			break;
+		}
+
+		if ((fds[0].revents & POLLHUP) != 0) {
+			syslog(LOG_INFO, "POLLHUP detected on devd socket.");
+			break;
+		}
+	}
+}
+//- ZfsDaemon staic Private Methods --------------------------------------------
+void
+ZfsDaemon::InfoSignalHandler(int)
+{
+	s_logCaseFiles = true;
+	ZfsDaemon::WakeEventLoop();
+}
+
+void
+ZfsDaemon::RescanSignalHandler(int)
+{
+	RequestSystemRescan();
+}
+
+void
+ZfsDaemon::QuitSignalHandler(int)
+{
+	s_terminateEventLoop = true;
+	ZfsDaemon::WakeEventLoop();
+}
+
+void
+ZfsDaemon::OpenPIDFile()
+{
+	pid_t otherPID;
+
+	s_pidFH = pidfile_open(s_pidFilePath, 0600, &otherPID);
+	if (s_pidFH == NULL) {
+		if (errno == EEXIST)
+			errx(1, "already running as PID %d. Exiting", otherPID);
+		warn("cannot open PID file");
+	}
+}
+
+void
+ZfsDaemon::UpdatePIDFile()
+{
+	if (s_pidFH != NULL)
+		pidfile_write(s_pidFH);
+}
+
+void
+ZfsDaemon::ClosePIDFile()
+{
+	if (s_pidFH != NULL)
+		pidfile_close(s_pidFH);
+}
+
+void
+ZfsDaemon::InitializeSyslog()
+{
+	openlog("zfsd", LOG_NDELAY, LOG_DAEMON);
+}
+
Index: cddl/usr.sbin/zfsd/zfsd_event.h
===================================================================
--- /dev/null
+++ cddl/usr.sbin/zfsd/zfsd_event.h
@@ -0,0 +1,168 @@
+/*-
+ * Copyright (c) 2011 Spectra Logic Corporation
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions, and the following disclaimer,
+ *    without modification.
+ * 2. Redistributions in binary form must reproduce at minimum a disclaimer
+ *    substantially similar to the "NO WARRANTY" disclaimer below
+ *    ("Disclaimer") and any redistribution must be conditioned upon
+ *    including a substantially similar Disclaimer requirement for further
+ *    binary redistribution.
+ *
+ * NO WARRANTY
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDERS OR CONTRIBUTORS BE LIABLE FOR SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
+ * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGES.
+ *
+ * Authors: Justin T. Gibbs     (Spectra Logic Corporation)
+ *
+ * $FreeBSD$
+ */
+
+/**
+ * \file dev_ctl_event.h
+ *
+ * \brief Class hierarchy used to express events received via
+ *        the devdctl API.
+ *
+ * Header requirements:
+ *    #include <string>
+ *    #include <list>
+ *    #include <map>
+ *
+ *    #include <devdctl/guid.h>
+ *    #include <devdctl/event.h>
+ */
+
+#ifndef _ZFSD_EVENT_H_
+#define	_ZFSD_EVENT_H_
+
+/*============================ Namespace Control =============================*/
+using std::string;
+
+/*=========================== Forward Declarations ===========================*/
+struct zpool_handle;
+typedef struct zpool_handle zpool_handle_t;
+
+struct nvlist;
+typedef struct nvlist nvlist_t;
+
+/*============================= Class Definitions ============================*/
+/*-------------------------------- DevfsEvent --------------------------------*/
+class DevfsEvent : public DevdCtl::DevfsEvent
+{
+public:
+	/** Specialized DevdCtlEvent object factory for Devfs events. */
+	static BuildMethod Builder;
+
+	virtual DevdCtl::Event *DeepCopy() const;
+
+	/**
+	 * Interpret and perform any actions necessary to
+	 * consume the event.
+	 * \return True if this event should be queued for later reevaluation
+	 */
+	virtual bool Process()		  const;
+
+protected:
+	/**
+	 * \brief Read and return label information for a device.
+	 *
+	 * \param devFd     The device from which to read ZFS label information.
+	 * \param inUse     The device is part of an active or potentially
+	 *                  active configuration.
+	 * \param degraded  The device label indicates the vdev is not healthy.
+	 *
+	 * \return  If label information is available, an nvlist describing
+	 *          the vdev configuraiton found on the device specified by
+	 *          devFd.  Otherwise NULL.
+	 */
+	static nvlist_t    *ReadLabel(int devFd, bool &inUse, bool &degraded);
+
+	/**
+	 * Attempt to match the ZFS labeled device at devPath with an active
+	 * CaseFile for a missing vdev.  If a CaseFile is found, attempt
+	 * to re-integrate the device with its pool.
+	 *
+	 * \param devPath    The devfs path to the potential leaf vdev.
+	 * \param physPath   The physical path string reported by the device
+	 *                   at devPath.
+	 * \param devConfig  The ZFS label information found on the device
+	 *                   at devPath.
+	 *
+	 * \return  true if the event that caused the online action can
+	 *          be considered consumed.
+	 */
+	static bool	    OnlineByLabel(const string &devPath,
+					  const string& physPath,
+					  nvlist_t *devConfig);
+
+	/** DeepCopy Constructor. */
+	DevfsEvent(const DevfsEvent &src);
+
+	/** Constructor */
+	DevfsEvent(Type, DevdCtl::NVPairMap &, const string &);
+};
+
+/*--------------------------------- ZfsEvent ---------------------------------*/
+class ZfsEvent : public DevdCtl::ZfsEvent
+{
+public:
+	/** Specialized DevdCtlEvent object factory for ZFS events. */
+	static BuildMethod Builder;
+
+	virtual DevdCtl::Event *DeepCopy() const;
+
+	/**
+	 * Interpret and perform any actions necessary to
+	 * consume the event.
+	 * \return True if this event should be queued for later reevaluation
+	 */
+	virtual bool Process()		  const;
+
+protected:
+	/** DeepCopy Constructor. */
+	ZfsEvent(const ZfsEvent &src);
+
+	/** Constructor */
+	ZfsEvent(Type, DevdCtl::NVPairMap &, const string &);
+
+	/**
+	 * Detach any spares that are no longer needed, but were not
+	 * automatically detached by the kernel
+	 */
+	virtual void CleanupSpares()	  const;
+	virtual void ProcessPoolEvent()	  const;
+	static VdevCallback_t TryDetach;
+};
+
+class GeomEvent : public DevdCtl::GeomEvent
+{
+public:
+	static BuildMethod Builder;
+
+	virtual DevdCtl::Event *DeepCopy() const; 
+
+	virtual bool Process()		  const;
+
+protected:
+	/** DeepCopy Constructor. */
+	GeomEvent(const GeomEvent &src);
+
+	/** Constructor */
+	GeomEvent(Type, DevdCtl::NVPairMap &, const string &);
+};
+#endif /*_ZFSD_EVENT_H_ */
Index: cddl/usr.sbin/zfsd/zfsd_event.cc
===================================================================
--- /dev/null
+++ cddl/usr.sbin/zfsd/zfsd_event.cc
@@ -0,0 +1,535 @@
+/*-
+ * Copyright (c) 2011 Spectra Logic Corporation
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions, and the following disclaimer,
+ *    without modification.
+ * 2. Redistributions in binary form must reproduce at minimum a disclaimer
+ *    substantially similar to the "NO WARRANTY" disclaimer below
+ *    ("Disclaimer") and any redistribution must be conditioned upon
+ *    including a substantially similar Disclaimer requirement for further
+ *    binary redistribution.
+ *
+ * NO WARRANTY
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDERS OR CONTRIBUTORS BE LIABLE FOR SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
+ * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGES.
+ *
+ * Authors: Justin T. Gibbs     (Spectra Logic Corporation)
+ */
+
+/**
+ * \file zfsd_event.cc
+ */
+#include <sys/cdefs.h>
+#include <sys/time.h>
+#include <sys/fs/zfs.h>
+
+#include <syslog.h>
+
+#include <libzfs.h>
+/* 
+ * Undefine flush, defined by cpufunc.h on sparc64, because it conflicts with
+ * C++ flush methods
+ */
+#undef   flush
+
+#include <list>
+#include <map>
+#include <sstream>
+#include <string>
+
+#include <devdctl/guid.h>
+#include <devdctl/event.h>
+#include <devdctl/event_factory.h>
+#include <devdctl/exception.h>
+#include <devdctl/consumer.h>
+
+#include "callout.h"
+#include "vdev_iterator.h"
+#include "zfsd_event.h"
+#include "case_file.h"
+#include "vdev.h"
+#include "zfsd.h"
+#include "zfsd_exception.h"
+#include "zpool_list.h"
+
+__FBSDID("$FreeBSD$");
+/*============================ Namespace Control =============================*/
+using DevdCtl::Event;
+using DevdCtl::Guid;
+using DevdCtl::NVPairMap;
+using std::stringstream;
+
+/*=========================== Class Implementations ==========================*/
+
+/*-------------------------------- DevfsEvent --------------------------------*/
+
+//- DevfsEvent Static Public Methods -------------------------------------------
+Event *
+DevfsEvent::Builder(Event::Type type,
+		    NVPairMap &nvPairs,
+		    const string &eventString)
+{
+	return (new DevfsEvent(type, nvPairs, eventString));
+}
+
+//- DevfsEvent Static Protected Methods ----------------------------------------
+nvlist_t *
+DevfsEvent::ReadLabel(int devFd, bool &inUse, bool &degraded)
+{
+	pool_state_t poolState;
+	char        *poolName;
+	boolean_t    b_inuse;
+
+	inUse    = false;
+	degraded = false;
+	poolName = NULL;
+	if (zpool_in_use(g_zfsHandle, devFd, &poolState,
+			 &poolName, &b_inuse) == 0) {
+		nvlist_t *devLabel;
+
+		inUse = b_inuse == B_TRUE;
+		if (poolName != NULL)
+			free(poolName);
+
+		if (zpool_read_label(devFd, &devLabel) != 0
+		 || devLabel == NULL)
+			return (NULL);
+
+		try {
+			Vdev vdev(devLabel);
+			degraded = vdev.State() != VDEV_STATE_HEALTHY;
+			return (devLabel);
+		} catch (ZfsdException &exp) {
+			string devName = fdevname(devFd);
+			string devPath = _PATH_DEV + devName;
+			string context("DevfsEvent::ReadLabel: "
+				     + devPath + ": ");
+
+			exp.GetString().insert(0, context);
+			exp.Log();
+		}
+	}
+	return (NULL);
+}
+
+bool
+DevfsEvent::OnlineByLabel(const string &devPath, const string& physPath,
+			      nvlist_t *devConfig)
+{
+	try {
+		/*
+		 * A device with ZFS label information has been
+		 * inserted.  If it matches a device for which we
+		 * have a case, see if we can solve that case.
+		 */
+		syslog(LOG_INFO, "Interrogating VDEV label for %s\n",
+		       devPath.c_str());
+		Vdev vdev(devConfig);
+		CaseFile *caseFile(CaseFile::Find(vdev.PoolGUID(),
+						  vdev.GUID()));
+		if (caseFile != NULL)
+			return (caseFile->ReEvaluate(devPath, physPath, &vdev));
+
+	} catch (ZfsdException &exp) {
+		string context("DevfsEvent::OnlineByLabel: " + devPath + ": ");
+
+		exp.GetString().insert(0, context);
+		exp.Log();
+	}
+	return (false);
+}
+
+//- DevfsEvent Virtual Public Methods ------------------------------------------
+Event *
+DevfsEvent::DeepCopy() const
+{
+	return (new DevfsEvent(*this));
+}
+
+bool
+DevfsEvent::Process() const
+{
+	/*
+	 * We are only concerned with newly discovered
+	 * devices that can be ZFS vdevs.
+	 */
+	if (Value("type") != "CREATE" || !IsDiskDev())
+		return (false);
+
+	/* Log the event since it is of interest. */
+	Log(LOG_INFO);
+
+	string devPath;
+	if (!DevPath(devPath))
+		return (false);
+
+	int devFd(open(devPath.c_str(), O_RDONLY));
+	if (devFd == -1)
+		return (false);
+
+	bool inUse;
+	bool degraded;
+	nvlist_t *devLabel(ReadLabel(devFd, inUse, degraded));
+
+	string physPath;
+	bool havePhysPath(PhysicalPath(physPath));
+
+	string devName;
+	DevName(devName);
+	close(devFd);
+
+	if (inUse && devLabel != NULL) {
+		OnlineByLabel(devPath, physPath, devLabel);
+	} else if (degraded) {
+		syslog(LOG_INFO, "%s is marked degraded.  Ignoring "
+		       "as a replace by physical path candidate.\n",
+		       devName.c_str());
+	} else if (havePhysPath && IsWholeDev()) {
+		/*
+		 * TODO: attempt to resolve events using every casefile
+		 * that matches this physpath
+		 */
+		CaseFile *caseFile(CaseFile::Find(physPath));
+		if (caseFile != NULL) {
+			syslog(LOG_INFO,
+			       "Found CaseFile(%s:%s:%s) - ReEvaluating\n",
+			       caseFile->PoolGUIDString().c_str(),
+			       caseFile->VdevGUIDString().c_str(),
+			       zpool_state_to_name(caseFile->VdevState(),
+						   VDEV_AUX_NONE));
+			caseFile->ReEvaluate(devPath, physPath, /*vdev*/NULL);
+		}
+	}
+	if (devLabel != NULL)
+		nvlist_free(devLabel);
+	return (false);
+}
+
+//- DevfsEvent Protected Methods -----------------------------------------------
+DevfsEvent::DevfsEvent(Event::Type type, NVPairMap &nvpairs,
+			       const string &eventString)
+ : DevdCtl::DevfsEvent(type, nvpairs, eventString)
+{
+}
+
+DevfsEvent::DevfsEvent(const DevfsEvent &src)
+ : DevdCtl::DevfsEvent::DevfsEvent(src)
+{
+}
+
+/*-------------------------------- GeomEvent --------------------------------*/
+
+//- GeomEvent Static Public Methods -------------------------------------------
+Event *
+GeomEvent::Builder(Event::Type type,
+		   NVPairMap &nvPairs,
+		   const string &eventString)
+{
+	return (new GeomEvent(type, nvPairs, eventString));
+}
+
+//- GeomEvent Virtual Public Methods ------------------------------------------
+Event *
+GeomEvent::DeepCopy() const
+{
+	return (new GeomEvent(*this));
+}
+ 
+bool
+GeomEvent::Process() const
+{
+	/*
+	 * We are only concerned with physical path changes, because those can
+	 * be used to satisfy autoreplace operations
+	 */
+	if (Value("type") != "GEOM::physpath" || !IsDiskDev())
+		return (false);
+
+	/* Log the event since it is of interest. */
+	Log(LOG_INFO);
+
+	string devPath;
+	if (!DevPath(devPath))
+		return (false);
+
+	string physPath;
+        bool havePhysPath(PhysicalPath(physPath));
+
+	string devName;
+	DevName(devName);
+
+	if (havePhysPath) {
+		/* 
+		 * TODO: attempt to resolve events using every casefile
+		 * that matches this physpath
+		 */
+		CaseFile *caseFile(CaseFile::Find(physPath));
+		if (caseFile != NULL) {
+			syslog(LOG_INFO,
+			       "Found CaseFile(%s:%s:%s) - ReEvaluating\n",
+			       caseFile->PoolGUIDString().c_str(),
+			       caseFile->VdevGUIDString().c_str(),
+			       zpool_state_to_name(caseFile->VdevState(),
+						   VDEV_AUX_NONE));
+			caseFile->ReEvaluate(devPath, physPath, /*vdev*/NULL);
+		}
+	}
+	return (false);
+}
+
+//- GeomEvent Protected Methods -----------------------------------------------
+GeomEvent::GeomEvent(Event::Type type, NVPairMap &nvpairs,
+			       const string &eventString)
+ : DevdCtl::GeomEvent(type, nvpairs, eventString)
+{
+}
+
+GeomEvent::GeomEvent(const GeomEvent &src)
+ : DevdCtl::GeomEvent::GeomEvent(src)
+{
+}
+
+
+/*--------------------------------- ZfsEvent ---------------------------------*/
+//- ZfsEvent Static Public Methods ---------------------------------------------
+DevdCtl::Event *
+ZfsEvent::Builder(Event::Type type, NVPairMap &nvpairs,
+		  const string &eventString)
+{
+	return (new ZfsEvent(type, nvpairs, eventString));
+}
+
+//- ZfsEvent Virtual Public Methods --------------------------------------------
+Event *
+ZfsEvent::DeepCopy() const
+{
+	return (new ZfsEvent(*this));
+}
+
+bool
+ZfsEvent::Process() const
+{
+	string logstr("");
+
+	if (!Contains("class") && !Contains("type")) {
+		syslog(LOG_ERR,
+		       "ZfsEvent::Process: Missing class or type data.");
+		return (false);
+	}
+
+	/* On config syncs, replay any queued events first. */
+	if (Value("type").find("misc.fs.zfs.config_sync") == 0) {
+		/*
+		 * Even if saved events are unconsumed the second time
+		 * around, drop them.  Any events that still can't be
+		 * consumed are probably referring to vdevs or pools that
+		 * no longer exist.
+		 */
+		ZfsDaemon::Get().ReplayUnconsumedEvents(/*discard*/true);
+		CaseFile::ReEvaluateByGuid(PoolGUID(), *this);
+	}
+
+	if (Value("type").find("misc.fs.zfs.") == 0) {
+		/* Configuration changes, resilver events, etc. */
+		ProcessPoolEvent();
+		return (false);
+	}
+
+	if (!Contains("pool_guid") || !Contains("vdev_guid")) {
+		/* Only currently interested in Vdev related events. */
+		return (false);
+	}
+
+	CaseFile *caseFile(CaseFile::Find(PoolGUID(), VdevGUID()));
+	if (caseFile != NULL) {
+		Log(LOG_INFO);
+		syslog(LOG_INFO, "Evaluating existing case file\n");
+		caseFile->ReEvaluate(*this);
+		return (false);
+	}
+
+	/* Skip events that can't be handled. */
+	Guid poolGUID(PoolGUID());
+	/* If there are no replicas for a pool, then it's not manageable. */
+	if (Value("class").find("fs.zfs.vdev.no_replicas") == 0) {
+		stringstream msg;
+		msg << "No replicas available for pool "  << poolGUID;
+		msg << ", ignoring";
+		Log(LOG_INFO);
+		syslog(LOG_INFO, "%s", msg.str().c_str());
+		return (false);
+	}
+
+	/*
+	 * Create a case file for this vdev, and have it
+	 * evaluate the event.
+	 */
+	ZpoolList zpl(ZpoolList::ZpoolByGUID, &poolGUID);
+	if (zpl.empty()) {
+		stringstream msg;
+		int priority = LOG_INFO;
+		msg << "ZfsEvent::Process: Event for unknown pool ";
+		msg << poolGUID << " ";
+		msg << "queued";
+		Log(LOG_INFO);
+		syslog(priority, "%s", msg.str().c_str());
+		return (true);
+	}
+
+	nvlist_t *vdevConfig = VdevIterator(zpl.front()).Find(VdevGUID());
+	if (vdevConfig == NULL) {
+		stringstream msg;
+		int priority = LOG_INFO;
+		msg << "ZfsEvent::Process: Event for unknown vdev ";
+		msg << VdevGUID() << " ";
+		msg << "queued";
+		Log(LOG_INFO);
+		syslog(priority, "%s", msg.str().c_str());
+		return (true);
+	}
+
+	Vdev vdev(zpl.front(), vdevConfig);
+	caseFile = &CaseFile::Create(vdev);
+	if (caseFile->ReEvaluate(*this) == false) {
+		stringstream msg;
+		int priority = LOG_INFO;
+		msg << "ZfsEvent::Process: Unconsumed event for vdev(";
+		msg << zpool_get_name(zpl.front()) << ",";
+		msg << vdev.GUID() << ") ";
+		msg << "queued";
+		Log(LOG_INFO);
+		syslog(priority, "%s", msg.str().c_str());
+		return (true);
+	}
+	return (false);
+}
+
+//- ZfsEvent Protected Methods -------------------------------------------------
+ZfsEvent::ZfsEvent(Event::Type type, NVPairMap &nvpairs,
+			   const string &eventString)
+ : DevdCtl::ZfsEvent(type, nvpairs, eventString)
+{
+}
+
+ZfsEvent::ZfsEvent(const ZfsEvent &src)
+ : DevdCtl::ZfsEvent(src)
+{
+}
+
+/*
+ * Sometimes the kernel won't detach a spare when it is no longer needed.  This
+ * can happen for example if a drive is removed, then either the pool is
+ * exported or the machine is powered off, then the drive is reinserted, then
+ * the machine is powered on or the pool is imported.  ZFSD must detach these
+ * spares itself.
+ */
+void
+ZfsEvent::CleanupSpares() const
+{
+	Guid poolGUID(PoolGUID());
+	ZpoolList zpl(ZpoolList::ZpoolByGUID, &poolGUID);
+	if (!zpl.empty()) {
+		zpool_handle_t* hdl;
+
+		hdl = zpl.front();
+		VdevIterator(hdl).Each(TryDetach, (void*)hdl);
+	}
+}
+
+void
+ZfsEvent::ProcessPoolEvent() const
+{
+	bool degradedDevice(false);
+
+	/* The pool is destroyed.  Discard any open cases */
+	if (Value("type") == "misc.fs.zfs.pool_destroy") {
+		Log(LOG_INFO);
+		CaseFile::ReEvaluateByGuid(PoolGUID(), *this);
+		return;
+	}
+
+	CaseFile *caseFile(CaseFile::Find(PoolGUID(), VdevGUID()));
+	if (caseFile != NULL) {
+		if (caseFile->VdevState() != VDEV_STATE_UNKNOWN
+		 && caseFile->VdevState() < VDEV_STATE_HEALTHY)
+			degradedDevice = true;
+
+		Log(LOG_INFO);
+		caseFile->ReEvaluate(*this);
+	}
+	else if (Value("type") == "misc.fs.zfs.resilver_finish")
+	{
+		/*
+		 * It's possible to get a resilver_finish event with no
+		 * corresponding casefile.  For example, if a damaged pool were
+		 * exported, repaired, then reimported.
+		 */
+		Log(LOG_INFO);
+		CleanupSpares();
+	}
+
+	if (Value("type") == "misc.fs.zfs.vdev_remove"
+	 && degradedDevice == false) {
+
+		/* See if any other cases can make use of this device. */
+		Log(LOG_INFO);
+		ZfsDaemon::RequestSystemRescan();
+	}
+}
+
+bool
+ZfsEvent::TryDetach(Vdev &vdev, void *cbArg)
+{
+	/*
+	 * Outline:
+	 * if this device is a spare, and its parent includes one healthy,
+	 * non-spare child, then detach this device.
+	 */
+	zpool_handle_t *hdl(static_cast<zpool_handle_t*>(cbArg));
+
+	if (vdev.IsSpare()) {
+		std::list<Vdev> siblings;
+		std::list<Vdev>::iterator siblings_it;
+		boolean_t cleanup = B_FALSE;
+
+		Vdev parent = vdev.Parent();
+		siblings = parent.Children();
+
+		/* Determine whether the parent should be cleaned up */
+		for (siblings_it = siblings.begin();
+		     siblings_it != siblings.end();
+		     siblings_it++) {
+			Vdev sibling = *siblings_it;
+
+			if (!sibling.IsSpare() &&
+			     sibling.State() == VDEV_STATE_HEALTHY) {
+				cleanup = B_TRUE;
+				break;
+			}
+		}
+
+		if (cleanup) {
+			syslog(LOG_INFO, "Detaching spare vdev %s from pool %s",
+			       vdev.Path().c_str(), zpool_get_name(hdl));
+			zpool_vdev_detach(hdl, vdev.Path().c_str());
+		}
+
+	}
+
+	/* Always return false, because there may be other spares to detach */
+	return (false);
+}
Index: cddl/usr.sbin/zfsd/zfsd_exception.h
===================================================================
--- /dev/null
+++ cddl/usr.sbin/zfsd/zfsd_exception.h
@@ -0,0 +1,107 @@
+/*-
+ * Copyright (c) 2011 Spectra Logic Corporation
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions, and the following disclaimer,
+ *    without modification.
+ * 2. Redistributions in binary form must reproduce at minimum a disclaimer
+ *    substantially similar to the "NO WARRANTY" disclaimer below
+ *    ("Disclaimer") and any redistribution must be conditioned upon
+ *    including a substantially similar Disclaimer requirement for further
+ *    binary redistribution.
+ *
+ * NO WARRANTY
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDERS OR CONTRIBUTORS BE LIABLE FOR SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
+ * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGES.
+ *
+ * Authors: Justin T. Gibbs     (Spectra Logic Corporation)
+ */
+
+/**
+ * \file zfsd_exception.h
+ *
+ * Definition of the ZfsdException class hierarchy.  All exceptions
+ * explicitly thrown by Zfsd are defined here.
+ *
+ * Header requirements:
+ *     #include <string>
+ *
+ *     #include <devdctl/exception.h>
+ */
+#ifndef	_ZFSD_EXCEPTION_H_
+#define	_ZFSD_EXCEPTION_H_
+
+/*=========================== Forward Declarations ===========================*/
+struct zpool_handle;
+typedef struct zpool_handle zpool_handle_t;
+
+struct nvlist;
+typedef struct nvlist nvlist_t;
+
+/*============================= Class Definitions ============================*/
+/*------------------------------- ZfsdException ------------------------------*/
+/**
+ * \brief Class allowing unified reporting/logging of exceptional events.
+ */
+class ZfsdException : public DevdCtl::Exception
+{
+public:
+	/**
+	 * \brief ZfsdException constructor allowing arbitrary string
+	 *        data to be reported.
+	 *
+	 * \param fmt  Printf-like string format specifier.
+	 */
+	ZfsdException(const char *fmt, ...);
+
+	/**
+	 * \brief ZfsdException constructor allowing arbitrary string
+	 *        data to be reported and associated with the configuration
+	 *        data for a ZFS pool.
+	 *
+	 * \param pool  Pool handle describing the pool to which this
+	 *              exception is associated.
+	 * \param fmt   Printf-like string format specifier.
+	 *
+	 * Instantiation with this method is used to report global
+	 * pool errors.
+	 */
+	ZfsdException(zpool_handle_t *pool, const char *, ...);
+
+	/**
+	 * \brief ZfsdException constructor allowing arbitrary string
+	 *        data to be reported and associated with the configuration
+	 *        data for a ZFS pool.
+	 *
+	 * \param poolConfig  Pool configuration describing the pool to
+	 *                    which this exception is associated.
+	 * \param fmt         Printf-like string format specifier.
+	 *
+	 * Instantiation with this method is used to report global
+	 * pool errors.
+	 */
+	ZfsdException(nvlist_t *poolConfig, const char *, ...);
+
+	/**
+	 * \brief Emit exception data to syslog(3).
+	 */
+	virtual void Log() const;
+private:
+	nvlist_t     *m_poolConfig;
+	nvlist_t     *m_vdevConfig;
+};
+
+#endif /* _ZFSD_EXCEPTION_H_ */
Index: cddl/usr.sbin/zfsd/zfsd_exception.cc
===================================================================
--- /dev/null
+++ cddl/usr.sbin/zfsd/zfsd_exception.cc
@@ -0,0 +1,134 @@
+/*-
+ * Copyright (c) 2011, 2012, 2013 Spectra Logic Corporation
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions, and the following disclaimer,
+ *    without modification.
+ * 2. Redistributions in binary form must reproduce at minimum a disclaimer
+ *    substantially similar to the "NO WARRANTY" disclaimer below
+ *    ("Disclaimer") and any redistribution must be conditioned upon
+ *    including a substantially similar Disclaimer requirement for further
+ *    binary redistribution.
+ *
+ * NO WARRANTY
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDERS OR CONTRIBUTORS BE LIABLE FOR SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
+ * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGES.
+ *
+ * Authors: Justin T. Gibbs     (Spectra Logic Corporation)
+ */
+
+/**
+ * \file zfsd_exception
+ *
+ * Implementation of the ZfsdException class.
+ */
+#include <sys/cdefs.h>
+#include <sys/fs/zfs.h>
+
+#include <syslog.h>
+
+#include <string>
+#include <list>
+#include <sstream>
+
+#include <devdctl/exception.h>
+#include <devdctl/guid.h>
+
+#include <libzfs.h>
+
+#include "vdev.h"
+#include "zfsd_exception.h"
+
+__FBSDID("$FreeBSD$");
+/*============================ Namespace Control =============================*/
+using std::endl;
+using std::string;
+using std::stringstream;
+
+/*=========================== Class Implementations ==========================*/
+/*------------------------------- ZfsdException ------------------------------*/
+ZfsdException::ZfsdException(const char *fmt, ...)
+ : DevdCtl::Exception(),
+   m_poolConfig(NULL),
+   m_vdevConfig(NULL)
+{
+	va_list ap;
+
+	va_start(ap, fmt);
+	FormatLog(fmt, ap);
+	va_end(ap);
+}
+
+ZfsdException::ZfsdException(zpool_handle_t *pool, const char *fmt, ...)
+ : DevdCtl::Exception(),
+   m_poolConfig(zpool_get_config(pool, NULL)),
+   m_vdevConfig(NULL)
+{
+	va_list ap;
+
+	va_start(ap, fmt);
+	FormatLog(fmt, ap);
+	va_end(ap);
+}
+
+ZfsdException::ZfsdException(nvlist_t *poolConfig, const char *fmt, ...)
+ : DevdCtl::Exception(),
+   m_poolConfig(poolConfig),
+   m_vdevConfig(NULL)
+{
+	va_list ap;
+
+	va_start(ap, fmt);
+	FormatLog(fmt, ap);
+	va_end(ap);
+}
+
+void
+ZfsdException::Log() const
+{
+	stringstream output;
+
+	if (m_poolConfig != NULL) {
+
+		output << "Pool ";
+
+		char *poolName;
+		if (nvlist_lookup_string(m_poolConfig, ZPOOL_CONFIG_POOL_NAME,
+				     &poolName) == 0)
+			output << poolName;
+		else
+			output << "Unknown";
+		output << ": ";
+	}
+
+	if (m_vdevConfig != NULL) {
+
+		if (m_poolConfig != NULL) {
+			Vdev vdev(m_poolConfig, m_vdevConfig);
+
+			output << "Vdev " <<  vdev.GUID() << ": ";
+		} else {
+			Vdev vdev(m_vdevConfig);
+
+			output << "Pool " <<  vdev.PoolGUID() << ": ";
+			output << "Vdev " <<  vdev.GUID() << ": ";
+		}
+	}
+
+	output << m_log << endl;
+	syslog(LOG_ERR, "%s", output.str().c_str());
+}
+
Index: cddl/usr.sbin/zfsd/zfsd_main.cc
===================================================================
--- /dev/null
+++ cddl/usr.sbin/zfsd/zfsd_main.cc
@@ -0,0 +1,90 @@
+/*-
+ * Copyright (c) 2011 Spectra Logic Corporation
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions, and the following disclaimer,
+ *    without modification.
+ * 2. Redistributions in binary form must reproduce at minimum a disclaimer
+ *    substantially similar to the "NO WARRANTY" disclaimer below
+ *    ("Disclaimer") and any redistribution must be conditioned upon
+ *    including a substantially similar Disclaimer requirement for further
+ *    binary redistribution.
+ *
+ * NO WARRANTY
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDERS OR CONTRIBUTORS BE LIABLE FOR SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
+ * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGES.
+ *
+ * Authors: Alan Somers     (Spectra Logic Corporation)
+ */
+
+/**
+ * \file zfsd_main.cc
+ *
+ * main function for the ZFS Daemon.  Separated to facilitate testing.
+ *
+ */
+
+#include <sys/cdefs.h>
+
+#include <cstdlib>
+#include <cstdio>
+#include <unistd.h>
+
+#include <list>
+#include <map>
+#include <string>
+
+#include <devdctl/guid.h>
+#include <devdctl/event.h>
+#include <devdctl/event_factory.h>
+#include <devdctl/exception.h>
+#include <devdctl/consumer.h>
+
+#include "vdev_iterator.h"
+#include "zfsd.h"
+
+__FBSDID("$FreeBSD$");
+
+/*=============================== Program Main ===============================*/
+static void
+usage()
+{
+	fprintf(stderr, "usage: %s [-d]\n", getprogname());
+	exit(1);
+}
+
+/**
+ * Program entry point.
+ */
+int
+main(int argc, char **argv)
+{
+	int ch;
+
+	while ((ch = getopt(argc, argv, "d")) != -1) {
+		switch (ch) {
+		case 'd':
+			g_debug++;
+			break;
+		default:
+			usage();
+		}
+	}
+
+	ZfsDaemon::Run();
+
+	return (0);
+}
Index: cddl/usr.sbin/zfsd/zpool_list.h
===================================================================
--- /dev/null
+++ cddl/usr.sbin/zfsd/zpool_list.h
@@ -0,0 +1,131 @@
+/*-
+ * Copyright (c) 2011 Spectra Logic Corporation
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions, and the following disclaimer,
+ *    without modification.
+ * 2. Redistributions in binary form must reproduce at minimum a disclaimer
+ *    substantially similar to the "NO WARRANTY" disclaimer below
+ *    ("Disclaimer") and any redistribution must be conditioned upon
+ *    including a substantially similar Disclaimer requirement for further
+ *    binary redistribution.
+ *
+ * NO WARRANTY
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDERS OR CONTRIBUTORS BE LIABLE FOR SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
+ * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGES.
+ *
+ * Authors: Justin T. Gibbs     (Spectra Logic Corporation)
+ */
+
+/**
+ * \file zpool_list.h
+ *
+ * ZpoolList class definition.  ZpoolList is a standard container
+ * allowing filtering and iteration of imported ZFS pool information.
+ *
+ * Header requirements:
+ *
+ *    #include <list>
+ *    #include <string>
+ */
+#ifndef	_ZPOOL_LIST_H_
+#define	_ZPOOL_LIST_H_
+
+/*============================ Namespace Control =============================*/
+using std::string;
+
+/*=========================== Forward Declarations ===========================*/
+struct zpool_handle;
+typedef struct zpool_handle zpool_handle_t;
+
+struct nvlist;
+typedef struct nvlist nvlist_t;
+
+class Vdev;
+
+/*============================= Class Definitions ============================*/
+/*--------------------------------- ZpoolList --------------------------------*/
+class ZpoolList;
+typedef bool PoolFilter_t(zpool_handle_t *pool, nvlist_t *poolConfig,
+			  void *filterArg);
+
+/**
+ * \brief Container of imported ZFS pool data.
+ *
+ * ZpoolList is a convenience class that converts libzfs's ZFS
+ * pool methods into a standard list container.
+ */
+class ZpoolList : public std::list<zpool_handle_t *>
+{
+public:
+	/**
+	 * \brief Utility ZpoolList construction filter that causes all
+	 *        pools known to the system to be included in the
+	 *        instantiated ZpoolList.
+	 */
+	static PoolFilter_t ZpoolAll;
+
+	/**
+	 * \brief Utility ZpoolList construction filter that causes only
+	 *        a pool known to the system and having the specified GUID
+	 *        to be included in the instantiated ZpoolList.
+	 */
+	static PoolFilter_t ZpoolByGUID;
+
+	/**
+	 * \brief Utility ZpoolList construction filter that causes only
+	 *        pools known to the system and having the specified name
+	 *        to be included in the instantiated ZpoolList.
+	 */
+	static PoolFilter_t ZpoolByName;
+
+	/**
+	 * \brief ZpoolList contructor
+	 *
+	 * \param filter     The filter function to use when constructing
+	 *                   the ZpoolList.  This may be one of the static
+	 *                   utility filters defined for ZpoolList or a
+	 *                   user defined function.
+	 * \param filterArg  A single argument to pass into the filter function
+	 *                   when it is invoked on each candidate pool.
+	 */
+	ZpoolList(PoolFilter_t *filter = ZpoolAll, void *filterArg = NULL);
+	~ZpoolList();
+
+private:
+	/**
+	 * \brief Helper routine used to populate the internal
+	 *        data store of ZFS pool objects using libzfs's
+	 *        zpool_iter() function.
+	 *
+	 * \param pool  The ZFS pool object to filter.
+	 * \param data  User argument passed through zpool_iter().
+	 */
+	static int LoadIterator(zpool_handle_t *pool, void *data);
+
+	/**
+	 * \brief The filter with which this ZpoolList was constructed.
+	 */
+	PoolFilter_t *m_filter;
+
+	/**
+	 * \brief The filter argument with which this ZpoolList was
+	 *        constructed.
+	 */
+	void	     *m_filterArg;
+};
+
+#endif	/* _ZPOOL_ITERATOR_H_ */
Index: cddl/usr.sbin/zfsd/zpool_list.cc
===================================================================
--- /dev/null
+++ cddl/usr.sbin/zfsd/zpool_list.cc
@@ -0,0 +1,119 @@
+/*-
+ * Copyright (c) 2011 Spectra Logic Corporation
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions, and the following disclaimer,
+ *    without modification.
+ * 2. Redistributions in binary form must reproduce at minimum a disclaimer
+ *    substantially similar to the "NO WARRANTY" disclaimer below
+ *    ("Disclaimer") and any redistribution must be conditioned upon
+ *    including a substantially similar Disclaimer requirement for further
+ *    binary redistribution.
+ *
+ * NO WARRANTY
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDERS OR CONTRIBUTORS BE LIABLE FOR SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
+ * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGES.
+ *
+ * Authors: Justin T. Gibbs     (Spectra Logic Corporation)
+ */
+
+/**
+ * \file zpool_list.cc
+ *
+ * Implementation of the ZpoolList class.
+ */
+#include <sys/cdefs.h>
+#include <sys/fs/zfs.h>
+
+#include <stdint.h>
+
+#include <libzfs.h>
+
+#include <list>
+#include <map>
+#include <string>
+
+#include <devdctl/guid.h>
+#include <devdctl/event.h>
+#include <devdctl/event_factory.h>
+#include <devdctl/exception.h>
+#include <devdctl/consumer.h>
+
+#include "vdev.h"
+#include "vdev_iterator.h"
+#include "zpool_list.h"
+#include "zfsd.h"
+
+/*============================ Namespace Control =============================*/
+using DevdCtl::Guid;
+
+/*=========================== Class Implementations ==========================*/
+/*--------------------------------- ZpoolList --------------------------------*/
+bool
+ZpoolList::ZpoolAll(zpool_handle_t *pool, nvlist_t *poolConfig, void *cbArg)
+{
+	return (true);
+}
+
+bool
+ZpoolList::ZpoolByGUID(zpool_handle_t *pool, nvlist_t *poolConfig,
+			   void *cbArg)
+{
+	Guid *desiredPoolGUID(static_cast<Guid *>(cbArg));
+	uint64_t poolGUID;
+
+	/* We are only intested in the pool that matches our pool GUID. */
+	return (nvlist_lookup_uint64(poolConfig, ZPOOL_CONFIG_POOL_GUID,
+				     &poolGUID) == 0
+	     && poolGUID == (uint64_t)*desiredPoolGUID);
+}
+
+bool
+ZpoolList::ZpoolByName(zpool_handle_t *pool, nvlist_t *poolConfig, void *cbArg)
+{
+	const string &desiredPoolName(*static_cast<const string *>(cbArg));
+
+	/* We are only intested in the pool that matches our pool GUID. */
+	return (desiredPoolName == zpool_get_name(pool));
+}
+
+int
+ZpoolList::LoadIterator(zpool_handle_t *pool, void *data)
+{
+	ZpoolList *zpl(reinterpret_cast<ZpoolList *>(data));
+	nvlist_t  *poolConfig(zpool_get_config(pool, NULL));
+
+	if (zpl->m_filter(pool, poolConfig, zpl->m_filterArg))
+		zpl->push_back(pool);
+	else
+		zpool_close(pool);
+	return (0);
+}
+
+ZpoolList::ZpoolList(PoolFilter_t *filter, void * filterArg)
+ : m_filter(filter),
+   m_filterArg(filterArg)
+{
+	zpool_iter(g_zfsHandle, LoadIterator, this);
+}
+
+ZpoolList::~ZpoolList()
+{
+	for (iterator it(begin()); it != end(); it++)
+		zpool_close(*it);
+
+	clear();
+}
Index: etc/defaults/rc.conf
===================================================================
--- etc/defaults/rc.conf
+++ etc/defaults/rc.conf
@@ -60,6 +60,10 @@
 # ZFS support
 zfs_enable="NO"		# Set to YES to automatically mount ZFS file systems
 
+# ZFSD support
+zfsd_enable="NO"	# Set to YES to automatically start the ZFS fault
+			# management daemon.
+
 gptboot_enable="YES"	# GPT boot success/failure reporting.
 
 # Experimental - test before enabling
Index: etc/mtree/BSD.include.dist
===================================================================
--- etc/mtree/BSD.include.dist
+++ etc/mtree/BSD.include.dist
@@ -155,6 +155,8 @@
         wi
         ..
     ..
+    devdctl
+    ..
     edit
         readline
         ..
Index: etc/mtree/BSD.tests.dist
===================================================================
--- etc/mtree/BSD.tests.dist
+++ etc/mtree/BSD.tests.dist
@@ -48,8 +48,6 @@
     cddl
         lib
         ..
-        sbin
-        ..
         usr.bin
         ..
         usr.sbin
@@ -215,6 +213,8 @@
                     ..
                 ..
             ..
+            zfsd
+            ..
         ..
     ..
     etc
@@ -308,6 +308,8 @@
         ..
         libcrypt
         ..
+        libdevdctl
+        ..
         libmp
         ..
         libnv
@@ -376,6 +378,278 @@
         ..
         aio
         ..
+        cddl
+            zfs
+                bin
+                ..
+                include
+                ..
+                tests
+                    acl
+                        cifs
+                        ..
+                        nontrivial
+                        ..
+                        trivial
+                        ..
+                    ..
+                    atime
+                    ..
+                    bootfs
+                    ..
+                    cache
+                    ..
+                    cachefile
+                    ..
+                    clean_mirror
+                        assertion_001
+                        ..
+                        assertion_002
+                        ..
+                        assertion_003
+                        ..
+                        assertion_004
+                        ..
+                    ..
+                    cli_root
+                        zfs_upgrade
+                        ..
+                        zfs_promote
+                        ..
+                        zfs_clone
+                        ..
+                        zfs_property
+                        ..
+                        zfs_destroy
+                        ..
+                        zpool_create
+                        ..
+                        zpool_history
+                        ..
+                        zpool_expand
+                        ..
+                        zpool_remove
+                        ..
+                        zfs_mount
+                        ..
+                        zfs_unshare
+                        ..
+                        zdb
+                        ..
+                        zpool_online
+                        ..
+                        zpool_get
+                        ..
+                        zpool_export
+                        ..
+                        zfs_copies
+                        ..
+                        zfs_get
+                        ..
+                        zfs
+                        ..
+                        zpool_clear
+                        ..
+                        zpool_import
+                        ..
+                        zpool
+                        ..
+                        zpool_offline
+                        ..
+                        zpool_replace
+                        ..
+                        zfs_rollback
+                        ..
+                        zpool_set
+                        ..
+                        zfs_send
+                        ..
+                        zfs_set
+                        ..
+                        zpool_detach
+                        ..
+                        zpool_scrub
+                        ..
+                        zfs_inherit
+                        ..
+                        zfs_snapshot
+                        ..
+                        zfs_share
+                        ..
+                        zpool_destroy
+                        ..
+                        zpool_status
+                        ..
+                        zfs_unmount
+                        ..
+                        zfs_receive
+                        ..
+                        zfs_create
+                        ..
+                        zpool_upgrade
+                            blockfiles
+                            ..
+                        ..
+                        zpool_add
+                        ..
+                        zfs_rename
+                        ..
+                        zpool_attach
+                        ..
+                        zfs_reservation
+                        ..
+                    ..
+                    cli_user
+                        misc
+                        ..
+                        zfs_list
+                        ..
+                        zpool_iostat
+                        ..
+                        zpool_list
+                        ..
+                    ..
+                    compression
+                    ..
+                    ctime
+                    ..
+                    delegate
+                        zfs_allow
+                        ..
+                        zfs_unallow
+                        ..
+                    ..
+                    devices
+                    ..
+                    exec
+                    ..
+                    grow_pool
+                    ..
+                    grow_replicas
+                    ..
+                    history
+                    ..
+                    hotplug
+                    ..
+                    hotspare
+                    ..
+                    inheritance
+                    ..
+                    interop
+                    ..
+                    inuse
+                    ..
+                    iscsi
+                    ..
+                    large_files
+                    ..
+                    largest_pool
+                    ..
+                    link_count
+                    ..
+                    mdb
+                    ..
+                    migration
+                    ..
+                    mmap
+                        mmap_read
+                        ..
+                        mmap_write
+                        ..
+                    ..
+                    mount
+                    ..
+                    mv_files
+                    ..
+                    nestedfs
+                    ..
+                    no_space
+                    ..
+                    online_offline
+                    ..
+                    pool_names
+                    ..
+                    poolversion
+                    ..
+                    privilege
+                    ..
+                    quota
+                    ..
+                    redundancy
+                    ..
+                    refquota
+                    ..
+                    refreserv
+                    ..
+                    remote
+                        cross_endian
+                        ..
+                        rebooting
+                        ..
+                        sharing
+                        ..
+                    ..
+                    rename_dirs
+                    ..
+                    replacement
+                    ..
+                    reservation
+                    ..
+                    rootpool
+                    ..
+                    rsend
+                    ..
+                    sas_phy_thrash
+                    ..
+                    scrub_mirror
+                    ..
+                    slog
+                    ..
+                    snapshot
+                    ..
+                    snapused
+                    ..
+                    sparse
+                    ..
+                    threadsappend
+                    ..
+                    truncate
+                    ..
+                    txg_integrity
+                    ..
+                    userquota
+                    ..
+                    utils_test
+                    ..
+                    write_dirs
+                    ..
+                    xattr
+                    ..
+                    zfsd
+                    ..
+                    zil
+                    ..
+                    zinject
+                    ..
+                    zones
+                    ..
+                    zvol
+                        zvol_ENOSPC
+                        ..
+                        zvol_cli
+                        ..
+                        zvol_misc
+                        ..
+                        zvol_swap
+                        ..
+                    ..
+                    zvol_thrash
+                    ..
+                ..
+            ..
+        ..
+        dtrace
+        ..
         fifo
         ..
         file
@@ -394,6 +668,8 @@
                 ..
                 nop
                 ..
+                part
+                ..
                 raid3
                 ..
                 shsec
@@ -424,6 +700,8 @@
         ..
         mqueue
         ..
+        net
+        ..
         netinet
         ..
         opencrypto
Index: etc/mtree/BSD.var.dist
===================================================================
--- etc/mtree/BSD.var.dist
+++ etc/mtree/BSD.var.dist
@@ -54,6 +54,10 @@
         ..
         portsnap
         ..
+        zfsd
+            cases
+            ..
+        ..
     ..
     empty           mode=0555 flags=schg
     ..
Index: etc/rc.d/Makefile
===================================================================
--- etc/rc.d/Makefile
+++ etc/rc.d/Makefile
@@ -123,7 +123,7 @@
 	ypserv \
 	ypset \
 	ypupdated \
-	ypxfrd \
+	ypxfrd
 
 .if ${MK_ACCT} != "no"
 FILESGROUPS+=	ACCT
@@ -308,6 +308,7 @@
 .if ${MK_ZFS} != "no"
 FILESGROUPS+=	ZFS
 ZFS+=		zfs
+ZFS+=		zfsd
 ZFS+=		zvol
 ZFSPACKAGE=	zfs
 .endif
Index: etc/rc.d/zfsd
===================================================================
--- /dev/null
+++ etc/rc.d/zfsd
@@ -0,0 +1,17 @@
+#!/bin/sh
+#
+# $FreeBSD$
+#
+
+# PROVIDE: zfsd
+# REQUIRE: devd zfs
+# KEYWORD: nojail shutdown
+
+. /etc/rc.subr
+
+name="zfsd"
+rcvar="zfsd_enable"
+command="/usr/sbin/${name}"
+
+load_rc_config $name
+run_rc_command "$1"
Index: lib/Makefile
===================================================================
--- lib/Makefile
+++ lib/Makefile
@@ -41,6 +41,7 @@
 	libcompat \
 	libcrypt \
 	libdevctl \
+	libdevdctl \
 	libdevinfo \
 	libdevstat \
 	libdpv \
Index: lib/libdevdctl/Makefile
===================================================================
--- /dev/null
+++ lib/libdevdctl/Makefile
@@ -0,0 +1,21 @@
+# $FreeBSD: stable/9/lib/libdevinfo/Makefile 201381 2010-01-02 09:58:07Z ed $
+
+LIB_CXX=	devdctl
+INCS=	consumer.h		\
+	event.h			\
+	event_factory.h		\
+	exception.h		\
+	guid.h
+SRCS=	consumer.cc		\
+	event.cc		\
+	event_factory.cc	\
+	exception.cc		\
+	guid.cc
+
+INCSDIR= ${INCLUDEDIR}/devdctl
+
+WARNS?=	3
+PRIVATELIB=	true
+SHLIB_MAJOR=	0
+
+.include <bsd.lib.mk>
Index: lib/libdevdctl/consumer.h
===================================================================
--- /dev/null
+++ lib/libdevdctl/consumer.h
@@ -0,0 +1,186 @@
+/*-
+ * Copyright (c) 2011, 2012, 2013 Spectra Logic Corporation
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions, and the following disclaimer,
+ *    without modification.
+ * 2. Redistributions in binary form must reproduce at minimum a disclaimer
+ *    substantially similar to the "NO WARRANTY" disclaimer below
+ *    ("Disclaimer") and any redistribution must be conditioned upon
+ *    including a substantially similar Disclaimer requirement for further
+ *    binary redistribution.
+ *
+ * NO WARRANTY
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDERS OR CONTRIBUTORS BE LIABLE FOR SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
+ * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGES.
+ *
+ * Authors: Justin T. Gibbs     (Spectra Logic Corporation)
+ *
+ * $FreeBSD$
+ */
+
+/**
+ * \file devdctl_consumer.h
+ */
+#ifndef	_DEVDCTL_CONSUMER_H_
+#define	_DEVDCTL_CONSUMER_H_
+
+/*============================ Namespace Control =============================*/
+namespace DevdCtl
+{
+
+/*=========================== Forward Declarations ===========================*/
+class Event;
+class EventBuffer;
+class FDReader;
+
+/*============================ Class Declarations ============================*/
+/*----------------------------- DevdCtl::Consumer ----------------------------*/
+
+/**
+ */
+class Consumer
+{
+public:
+	Consumer(Event::BuildMethod *defBuilder = NULL,
+		 EventFactory::Record *regEntries = NULL,
+		 size_t numEntries = 0);
+	virtual ~Consumer();
+
+	bool Connected() const;
+
+	/**
+	 * Return file descriptor useful for client's wishing to poll(2)
+	 * for new events.
+	 */
+	int GetPollFd();
+
+	/**                                                          
+         * Queue an event for deferred processing or replay.
+         */ 
+	bool SaveEvent(const Event &event);
+
+	/**                                  
+	 * Reprocess any events saved via the SaveEvent() facility.   
+	 *
+	 * \param discardUnconsumed  If true, events that are not consumed
+	 *                           during replay are discarded.
+	 */                                                              
+	void ReplayUnconsumedEvents(bool discardUnconsumed);
+
+	/** Return an event, if one is available.  */
+	Event *NextEvent();
+
+	/**
+	 * Extract events and invoke each event's Process method.
+	 */
+	void ProcessEvents();
+
+	/** Discard all data pending in m_devdSockFD. */
+	void FlushEvents();
+
+	/**
+	 * Test for data pending in m_devdSockFD
+	 *
+	 * \return  True if data is pending.  Otherwise false.
+	 */
+	bool EventsPending();
+
+	/**
+	 * Open a connection to devd's unix domain socket.
+	 *
+	 * \return  True if the connection attempt is successsful.  Otherwise
+	 *          false.
+	 */
+	bool ConnectToDevd();
+
+	/**
+	 * Close a connection (if any) to devd's unix domain socket.
+	 */
+	void DisconnectFromDevd();
+
+	EventFactory GetFactory();
+
+protected:
+	/**
+	 * \brief Reads the most recent record
+	 *
+	 * On error, "" is returned, and errno will be set by the OS
+	 *
+	 * \returns      A string containing the record
+	 */
+	std::string ReadEvent();
+
+	enum {
+		/*
+		 * The maximum event size supported by libdevdctl.
+		 */
+		MAX_EVENT_SIZE = 8192,
+	};
+
+	static const char  s_devdSockPath[];
+
+	/**
+	 * File descriptor representing the unix domain socket
+	 * connection with devd.
+	 */
+	int                m_devdSockFD;
+
+	/**
+	 * Reader tied to the devd socket.
+	 */
+	FDReader	  *m_reader;
+
+	/**
+	 * Default EventBuffer connected to m_reader.
+	 */
+	EventBuffer	  *m_eventBuffer;
+
+	EventFactory	   m_eventFactory;
+
+	/** Queued events for replay. */
+	EventList	   m_unconsumedEvents;
+
+	/**                                                             
+	 * Flag controlling whether events can be queued.  This boolean
+	 * is set during event replay to ensure that previosuly deferred
+	 * events are not requeued and thus retained forever.
+	 */
+	bool		   m_replayingEvents;
+};
+
+//- Consumer Const Public Inline Methods ---------------------------------------
+inline bool
+Consumer::Connected() const
+{
+	return (m_devdSockFD != -1);
+}
+
+//- Consumer Public Inline Methods ---------------------------------------------
+inline int
+Consumer::GetPollFd()
+{
+	return (m_devdSockFD);
+}
+
+inline EventFactory
+Consumer::GetFactory()
+{
+	return (m_eventFactory);
+}
+
+} // namespace DevdCtl
+#endif	/* _DEVDCTL_CONSUMER_H_ */
Index: lib/libdevdctl/consumer.cc
===================================================================
--- /dev/null
+++ lib/libdevdctl/consumer.cc
@@ -0,0 +1,258 @@
+/*-
+ * Copyright (c) 2011, 2012, 2013 Spectra Logic Corporation
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions, and the following disclaimer,
+ *    without modification.
+ * 2. Redistributions in binary form must reproduce at minimum a disclaimer
+ *    substantially similar to the "NO WARRANTY" disclaimer below
+ *    ("Disclaimer") and any redistribution must be conditioned upon
+ *    including a substantially similar Disclaimer requirement for further
+ *    binary redistribution.
+ *
+ * NO WARRANTY
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDERS OR CONTRIBUTORS BE LIABLE FOR SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
+ * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGES.
+ *
+ * Authors: Justin T. Gibbs     (Spectra Logic Corporation)
+ */
+
+/**
+ * \file consumer.cc
+ */
+
+#include <sys/cdefs.h>
+#include <sys/poll.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <syslog.h>
+#include <unistd.h>
+
+#include <cstdarg>
+#include <cstring>
+#include <list>
+#include <map>
+#include <string>
+
+#include "guid.h"
+#include "event.h"
+#include "event_factory.h"
+#include "exception.h"
+
+#include "consumer.h"
+
+__FBSDID("$FreeBSD$");
+
+/*================================== Macros ==================================*/
+#define NUM_ELEMENTS(x) (sizeof(x) / sizeof(*x))
+
+/*============================ Namespace Control =============================*/
+using std::string;
+namespace DevdCtl
+{
+
+/*============================= Class Definitions ============================*/
+/*----------------------------- DevdCtl::Consumer ----------------------------*/
+//- Consumer Static Private Data -----------------------------------------------
+const char Consumer::s_devdSockPath[] = "/var/run/devd.seqpacket.pipe";
+
+//- Consumer Public Methods ----------------------------------------------------
+Consumer::Consumer(Event::BuildMethod *defBuilder,
+		   EventFactory::Record *regEntries,
+		   size_t numEntries)
+ : m_devdSockFD(-1),
+   m_eventFactory(defBuilder),
+   m_replayingEvents(false)
+{
+	m_eventFactory.UpdateRegistry(regEntries, numEntries);
+}
+
+Consumer::~Consumer()
+{
+	DisconnectFromDevd();
+}
+
+bool
+Consumer::ConnectToDevd()
+{
+	struct sockaddr_un devdAddr;
+	int		   sLen;
+	int		   result;
+
+	if (m_devdSockFD != -1) {
+		/* Already connected. */
+		syslog(LOG_DEBUG, "%s: Already connected.", __func__);
+		return (true);
+	}
+	syslog(LOG_INFO, "%s: Connecting to devd.", __func__);
+
+	memset(&devdAddr, 0, sizeof(devdAddr));
+	devdAddr.sun_family= AF_UNIX;
+	strlcpy(devdAddr.sun_path, s_devdSockPath, sizeof(devdAddr.sun_path));
+	sLen = SUN_LEN(&devdAddr);
+
+	m_devdSockFD = socket(AF_UNIX, SOCK_SEQPACKET, 0);
+	if (m_devdSockFD == -1)
+		err(1, "Unable to create socket");
+        if (fcntl(m_devdSockFD, F_SETFL, O_NONBLOCK) < 0)
+                err(1, "fcntl");
+	result = connect(m_devdSockFD,
+			 reinterpret_cast<sockaddr *>(&devdAddr),
+			 sLen);
+	if (result == -1) {
+		syslog(LOG_INFO, "Unable to connect to devd");
+		DisconnectFromDevd();
+		return (false);
+	}
+
+	syslog(LOG_INFO, "Connection to devd successful");
+	return (true);
+}
+
+void
+Consumer::DisconnectFromDevd()
+{
+	if (m_devdSockFD != -1)
+		syslog(LOG_INFO, "Disconnecting from devd.");
+
+	close(m_devdSockFD);
+	m_devdSockFD = -1;
+}
+
+std::string
+Consumer::ReadEvent()
+{
+	char buf[MAX_EVENT_SIZE + 1];
+	ssize_t len;
+
+	len = ::recv(m_devdSockFD, buf, MAX_EVENT_SIZE, MSG_WAITALL);
+	if (len == -1)
+		return (std::string(""));
+	else {
+		/* NULL-terminate the result */
+		buf[len] = '\0';
+		return (std::string(buf));
+	}
+}
+
+void
+Consumer::ReplayUnconsumedEvents(bool discardUnconsumed)
+{
+	EventList::iterator event(m_unconsumedEvents.begin());
+	bool replayed_any = (event != m_unconsumedEvents.end());
+
+	m_replayingEvents = true;
+	if (replayed_any)
+		syslog(LOG_INFO, "Started replaying unconsumed events");
+	while (event != m_unconsumedEvents.end()) {
+		bool consumed((*event)->Process());
+		if (consumed || discardUnconsumed) {
+			delete *event;
+			event = m_unconsumedEvents.erase(event);
+		} else {
+			event++;
+		}
+	}
+	if (replayed_any)
+		syslog(LOG_INFO, "Finished replaying unconsumed events");
+	m_replayingEvents = false;
+}
+
+bool
+Consumer::SaveEvent(const Event &event)
+{
+        if (m_replayingEvents)
+                return (false);
+        m_unconsumedEvents.push_back(event.DeepCopy());
+        return (true);
+}
+
+Event *
+Consumer::NextEvent()
+{
+	if (!Connected())
+		return(NULL);
+
+	Event *event(NULL);
+	try {
+		string evString;
+
+		evString = ReadEvent();
+		if (! evString.empty()) {
+			Event::TimestampEventString(evString);
+			event = Event::CreateEvent(m_eventFactory, evString);
+		}
+	} catch (const Exception &exp) {
+		exp.Log();
+		DisconnectFromDevd();
+	}
+	return (event);
+}
+
+/* Capture and process buffered events. */
+void
+Consumer::ProcessEvents()
+{
+	Event *event;
+	while ((event = NextEvent()) != NULL) {
+		if (event->Process())
+			SaveEvent(*event);
+		delete event;
+	}
+}
+
+void
+Consumer::FlushEvents()
+{
+	std::string s;
+
+	do
+		s = ReadEvent();
+	while (! s.empty()) ;
+}
+
+bool
+Consumer::EventsPending()
+{
+	struct pollfd fds[1];
+	int	      result;
+
+	do {
+		fds->fd      = m_devdSockFD;
+		fds->events  = POLLIN;
+		fds->revents = 0;
+		result = poll(fds, NUM_ELEMENTS(fds), /*timeout*/0);
+	} while (result == -1 && errno == EINTR);
+
+	if (result == -1)
+		err(1, "Polling for devd events failed");
+
+	if ((fds->revents & POLLERR) != 0)
+		throw Exception("Consumer::EventsPending(): "
+				"POLLERR detected on devd socket.");
+
+	if ((fds->revents & POLLHUP) != 0)
+		throw Exception("Consumer::EventsPending(): "
+				"POLLHUP detected on devd socket.");
+
+	return ((fds->revents & POLLIN) != 0);
+}
+
+} // namespace DevdCtl
Index: lib/libdevdctl/event.h
===================================================================
--- /dev/null
+++ lib/libdevdctl/event.h
@@ -0,0 +1,423 @@
+/*-
+ * Copyright (c) 2011, 2012, 2013, 2016 Spectra Logic Corporation
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions, and the following disclaimer,
+ *    without modification.
+ * 2. Redistributions in binary form must reproduce at minimum a disclaimer
+ *    substantially similar to the "NO WARRANTY" disclaimer below
+ *    ("Disclaimer") and any redistribution must be conditioned upon
+ *    including a substantially similar Disclaimer requirement for further
+ *    binary redistribution.
+ *
+ * NO WARRANTY
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDERS OR CONTRIBUTORS BE LIABLE FOR SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
+ * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGES.
+ *
+ * Authors: Justin T. Gibbs     (Spectra Logic Corporation)
+ *
+ * $FreeBSD$
+ */
+
+/**
+ * \file devdctl_event.h
+ *
+ * \brief Class hierarchy used to express events received via
+ *        the devdctl API.
+ */
+
+#ifndef _DEVDCTL_EVENT_H_
+#define	_DEVDCTL_EVENT_H_
+
+/*============================ Namespace Control =============================*/
+namespace DevdCtl
+{
+
+/*=========================== Forward Declarations ===========================*/
+class EventFactory;
+
+/*============================= Class Definitions ============================*/
+/*-------------------------------- NVPairMap ---------------------------------*/
+/**
+ * NVPairMap is a specialization of the standard map STL container.
+ */
+typedef std::map<std::string, std::string> NVPairMap;
+
+/*----------------------------------- Event ----------------------------------*/
+/**
+ * \brief Container for the name => value pairs that comprise the content of
+ *        a device control event.
+ *
+ * All name => value data for events can be accessed via the Contains()
+ * and Value() methods.  name => value pairs for data not explicitly
+ * received as a name => value pair are synthesized during parsing.  For
+ * example, ATTACH and DETACH events have "device-name" and "parent"
+ * name => value pairs added.
+ */
+class Event
+{
+	friend class EventFactory;
+
+public:
+	/** Event type */
+	enum Type {
+		/** Generic event notification. */
+		NOTIFY  = '!',
+
+		/** A driver was not found for this device. */
+		NOMATCH = '?',
+
+		/** A bus device instance has been added. */
+		ATTACH  = '+',
+
+		/** A bus device instance has been removed. */
+		DETACH  = '-'
+	};
+
+	/**
+	 * Factory method type to construct an Event given
+	 * the type of event and an NVPairMap populated from
+	 * the event string received from devd.
+	 */
+	typedef Event* (BuildMethod)(Type, NVPairMap &, const std::string &);
+
+	/** Generic Event object factory. */
+	static BuildMethod Builder;
+
+	static Event *CreateEvent(const EventFactory &factory,
+				  const std::string &eventString);
+
+	/**
+	 * Returns the devname, if any, associated with the event
+	 *
+	 * \param name	Devname, returned by reference
+	 * \return	True iff the event contained a devname
+	 */
+	virtual bool DevName(std::string &name)	const;
+
+	/**
+	 * Returns the absolute pathname of the device associated with this
+	 * event.
+	 *
+	 * \param name	Devname, returned by reference
+	 * \return	True iff the event contained a devname
+	 */
+	bool DevPath(std::string &path)		const;
+
+	/**
+	 * Returns true iff this event refers to a disk device
+	 */
+	bool IsDiskDev()			const;
+
+	/** Returns the physical path of the device, if any
+	 *
+	 * \param path	Physical path, returned by reference
+	 * \return	True iff the event contains a device with a physical
+	 * 		path
+	 */
+	bool PhysicalPath(std::string &path)	const;
+
+	/**
+	 * Provide a user friendly string representation of an
+	 * event type.
+	 *
+	 * \param type  The type of event to map to a string.
+	 *
+	 * \return  A user friendly string representing the input type.
+	 */
+	static const char  *TypeToString(Type type);
+
+	/**
+	 * Determine the availability of a name => value pair by name.
+	 *
+	 * \param name  The key name to search for in this event instance.
+	 *
+	 * \return  true if the specified key is available in this
+	 *          event, otherwise false.
+	 */
+	bool Contains(const std::string &name)		 const;
+
+	/**
+	 * \param key  The name of the key for which to retrieve its
+	 *             associated value.
+	 *
+	 * \return  A const reference to the string representing the
+	 *          value associated with key.
+	 *
+	 * \note  For key's with no registered value, the empty string
+	 *        is returned.
+	 */
+	const std::string &Value(const std::string &key) const;
+
+	/**
+	 * Get the type of this event instance.
+	 *
+	 * \return  The type of this event instance.
+	 */
+	Type GetType()					 const;
+
+	/**
+	 * Get the orginal DevdCtl event string for this event.
+	 *
+	 * \return  The DevdCtl event string.
+	 */
+	const std::string &GetEventString()		 const;
+
+	/**
+	 * Convert the event instance into a string suitable for
+	 * printing to the console or emitting to syslog.
+	 *
+	 * \return  A string of formatted event data.
+	 */
+	std::string ToString()				 const;
+
+	/**
+	 * Pretty-print this event instance to cout.
+	 */
+	void Print()					 const;
+
+	/**
+	 * Pretty-print this event instance to syslog.
+	 *
+	 * \param priority  The logging priority/facility.
+	 *                  See syslog(3).
+	 */
+	void Log(int priority)				 const;
+
+	/**
+	 * Create and return a fully independent clone
+	 * of this event.
+	 */
+	virtual Event *DeepCopy()			 const;
+
+	/** Destructor */
+	virtual ~Event();
+
+	/**
+	 * Interpret and perform any actions necessary to
+	 * consume the event.
+	 *
+	 * \return True if this event should be queued for later reevaluation
+	 */
+	virtual bool Process()				 const;
+
+	/**
+	 * Get the time that the event was created
+	 */
+	timeval GetTimestamp()				 const;
+
+	/**
+	 * Add a timestamp to the event string, if one does not already exist
+	 * TODO: make this an instance method that operates on the std::map
+	 * instead of the string.  We must fix zfsd's CaseFile serialization
+	 * routines first, so that they don't need the raw event string.
+	 *
+	 * \param[in,out] eventString The devd event string to modify
+	 */
+	static void TimestampEventString(std::string &eventString);
+
+	/**
+	 * Access all parsed key => value pairs.
+	 */
+	const NVPairMap &GetMap()			 const;
+
+protected:
+	/** Table entries used to map a type to a user friendly string. */
+	struct EventTypeRecord
+	{
+		Type         m_type;
+		const char  *m_typeName;
+	};
+
+	/**
+	 * Constructor
+	 *
+	 * \param type  The type of event to create.
+	 */
+	Event(Type type, NVPairMap &map, const std::string &eventString);
+
+	/** Deep copy constructor. */
+	Event(const Event &src);
+
+	/** Always empty string returned when NVPairMap lookups fail. */
+	static const std::string    s_theEmptyString;
+
+	/** Unsorted table of event types. */
+	static EventTypeRecord      s_typeTable[];
+
+	/** The type of this event. */
+	const Type                  m_type;
+
+	/**
+	 * Event attribute storage.
+	 *
+	 * \note Although stored by reference (since m_nvPairs can
+	 *       never be NULL), the NVPairMap referenced by this field
+	 *       is dynamically allocated and owned by this event object.
+	 *       m_nvPairs must be deleted at event desctruction.
+	 */
+	NVPairMap                  &m_nvPairs;
+
+	/**
+	 * The unaltered event string, as received from devd, used to
+	 * create this event object.
+	 */
+	std::string                 m_eventString;
+
+private:
+	/**
+	 * Ingest event data from the supplied string.
+	 *
+	 * \param[in] eventString  The string of devd event data to parse.
+	 * \param[out] nvpairs     Returns the parsed data
+	 */
+	static void ParseEventString(Type type, const std::string &eventString,
+				     NVPairMap &nvpairs);
+};
+
+inline Event::Type
+Event::GetType() const
+{
+	return (m_type);
+}
+
+inline const std::string &
+Event::GetEventString() const
+{
+	return (m_eventString);
+}
+
+inline const NVPairMap &
+Event::GetMap()	const
+{
+	return (m_nvPairs);
+}
+
+/*--------------------------------- EventList --------------------------------*/
+/**
+ * EventList is a specialization of the standard list STL container.
+ */
+typedef std::list<Event *> EventList;
+
+/*-------------------------------- DevfsEvent --------------------------------*/
+class DevfsEvent : public Event
+{
+public:
+	/** Specialized Event object factory for Devfs events. */
+	static BuildMethod Builder;
+
+	virtual Event *DeepCopy()		const;
+
+	/**
+	 * Interpret and perform any actions necessary to
+	 * consume the event.
+	 * \return True if this event should be queued for later reevaluation
+	 */
+	virtual bool Process()			const;
+
+	bool IsWholeDev()			const;
+	virtual bool DevName(std::string &name)	const;
+
+protected:
+	/**
+	 * Given the device name of a disk, determine if the device
+	 * represents the whole device, not just a partition.
+	 *
+	 * \param devName  Device name of disk device to test.
+	 *
+	 * \return  True if the device name represents the whole device.
+	 *          Otherwise false.
+	 */
+	static bool IsWholeDev(const std::string &devName);
+
+	/** DeepCopy Constructor. */
+	DevfsEvent(const DevfsEvent &src);
+
+	/** Constructor */
+	DevfsEvent(Type, NVPairMap &, const std::string &);
+};
+
+/*--------------------------------- GeomEvent --------------------------------*/
+class GeomEvent : public Event
+{
+public:
+	/** Specialized Event object factory for GEOM events. */
+	static BuildMethod Builder;
+
+	virtual Event *DeepCopy()	const;
+
+	virtual bool DevName(std::string &name)	const;
+
+	const std::string &DeviceName()	const;
+
+protected:
+	/** Constructor */
+	GeomEvent(Type, NVPairMap &, const std::string &);
+
+	/** Deep copy constructor. */
+	GeomEvent(const GeomEvent &src);
+
+	std::string m_devname;
+};
+
+/*--------------------------------- ZfsEvent ---------------------------------*/
+class ZfsEvent : public Event
+{
+public:
+	/** Specialized Event object factory for ZFS events. */
+	static BuildMethod Builder;
+
+	virtual Event *DeepCopy()	const;
+
+	virtual bool DevName(std::string &name)	const;
+
+	const std::string &PoolName()	const;
+	Guid		   PoolGUID()	const;
+	Guid		   VdevGUID()	const;
+
+protected:
+	/** Constructor */
+	ZfsEvent(Type, NVPairMap &, const std::string &);
+
+	/** Deep copy constructor. */
+	ZfsEvent(const ZfsEvent &src);
+
+	Guid	m_poolGUID;
+	Guid	m_vdevGUID;
+};
+
+//- ZfsEvent Inline Public Methods --------------------------------------------
+inline const std::string&
+ZfsEvent::PoolName() const
+{
+	/* The pool name is reported as the subsystem of ZFS events. */
+	return (Value("subsystem"));
+}
+
+inline Guid
+ZfsEvent::PoolGUID() const
+{
+	return (m_poolGUID);
+}
+
+inline Guid
+ZfsEvent::VdevGUID() const
+{
+	return (m_vdevGUID);
+}
+
+} // namespace DevdCtl
+#endif /*_DEVDCTL_EVENT_H_ */
Index: lib/libdevdctl/event.cc
===================================================================
--- /dev/null
+++ lib/libdevdctl/event.cc
@@ -0,0 +1,602 @@
+/*-
+ * Copyright (c) 2011, 2012, 2013, 2016 Spectra Logic Corporation
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions, and the following disclaimer,
+ *    without modification.
+ * 2. Redistributions in binary form must reproduce at minimum a disclaimer
+ *    substantially similar to the "NO WARRANTY" disclaimer below
+ *    ("Disclaimer") and any redistribution must be conditioned upon
+ *    including a substantially similar Disclaimer requirement for further
+ *    binary redistribution.
+ *
+ * NO WARRANTY
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDERS OR CONTRIBUTORS BE LIABLE FOR SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
+ * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGES.
+ *
+ * Authors: Justin T. Gibbs     (Spectra Logic Corporation)
+ */
+
+/**
+ * \file event.cc
+ *
+ * Implementation of the class hierarchy used to express events
+ * received via the devdctl API.
+ */
+#include <sys/cdefs.h>
+#include <sys/disk.h>
+#include <sys/filio.h>
+#include <sys/param.h>
+#include <sys/stat.h>
+
+#include <err.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <paths.h>
+#include <stdlib.h>
+#include <syslog.h>
+#include <unistd.h>
+
+#include <cstdarg>
+#include <cstring>
+#include <iostream>
+#include <list>
+#include <map>
+#include <sstream>
+#include <string>
+
+#include "guid.h"
+#include "event.h"
+#include "event_factory.h"
+#include "exception.h"
+
+__FBSDID("$FreeBSD$");
+
+/*================================== Macros ==================================*/
+#define NUM_ELEMENTS(x) (sizeof(x) / sizeof(*x))
+
+/*============================ Namespace Control =============================*/
+using std::cout;
+using std::endl;
+using std::string;
+using std::stringstream;
+
+namespace DevdCtl
+{
+
+/*=========================== Class Implementations ==========================*/
+/*----------------------------------- Event ----------------------------------*/
+//- Event Static Protected Data ------------------------------------------------
+const string Event::s_theEmptyString;
+
+Event::EventTypeRecord Event::s_typeTable[] =
+{
+	{ Event::NOTIFY,  "Notify" },
+	{ Event::NOMATCH, "No Driver Match" },
+	{ Event::ATTACH,  "Attach" },
+	{ Event::DETACH,  "Detach" }
+};
+
+//- Event Static Public Methods ------------------------------------------------
+Event *
+Event::Builder(Event::Type type, NVPairMap &nvPairs,
+	       const string &eventString)
+{
+	return (new Event(type, nvPairs, eventString));
+}
+
+Event *
+Event::CreateEvent(const EventFactory &factory, const string &eventString)
+{
+	NVPairMap &nvpairs(*new NVPairMap);
+	Type       type(static_cast<Event::Type>(eventString[0]));
+
+	try {
+		ParseEventString(type, eventString, nvpairs);
+	} catch (const ParseException &exp) {
+		if (exp.GetType() == ParseException::INVALID_FORMAT)
+			exp.Log();
+		return (NULL);
+	}
+
+	/*
+	 * Allow entries in our table for events with no system specified.
+	 * These entries should specify the string "none".
+	 */
+	NVPairMap::iterator system_item(nvpairs.find("system"));
+	if (system_item == nvpairs.end())
+		nvpairs["system"] = "none";
+
+	return (factory.Build(type, nvpairs, eventString));
+}
+
+bool
+Event::DevName(std::string &name) const
+{
+	return (false);
+}
+
+/* TODO: simplify this function with C++-11 <regex> methods */
+bool
+Event::IsDiskDev() const
+{
+	const int numDrivers = 2;
+	static const char *diskDevNames[numDrivers] =
+	{
+		"da",
+		"ada"
+	};
+	const char **dName;
+	string devName;
+
+	if (! DevName(devName))
+		return false;
+
+	size_t find_start = devName.rfind('/');
+	if (find_start == string::npos) {
+		find_start = 0;
+	} else {
+		/* Just after the last '/'. */
+		find_start++;
+	}
+
+	for (dName = &diskDevNames[0];
+	     dName <= &diskDevNames[numDrivers - 1]; dName++) {
+
+		size_t loc(devName.find(*dName, find_start));
+		if (loc == find_start) {
+			size_t prefixLen(strlen(*dName));
+
+			if (devName.length() - find_start >= prefixLen
+			 && isdigit(devName[find_start + prefixLen]))
+				return (true);
+		}
+	}
+
+	return (false);
+}
+
+const char *
+Event::TypeToString(Event::Type type)
+{
+	EventTypeRecord *rec(s_typeTable);
+	EventTypeRecord *lastRec(s_typeTable + NUM_ELEMENTS(s_typeTable) - 1);
+
+	for (; rec <= lastRec; rec++) {
+		if (rec->m_type == type)
+			return (rec->m_typeName);
+	}
+	return ("Unknown");
+}
+
+//- Event Public Methods -------------------------------------------------------
+const string &
+Event::Value(const string &varName) const
+{
+	NVPairMap::const_iterator item(m_nvPairs.find(varName));
+	if (item == m_nvPairs.end())
+		return (s_theEmptyString);
+
+	return (item->second);
+}
+
+bool
+Event::Contains(const string &varName) const
+{
+	return (m_nvPairs.find(varName) != m_nvPairs.end());
+}
+
+string
+Event::ToString() const
+{
+	stringstream result;
+
+	NVPairMap::const_iterator devName(m_nvPairs.find("device-name"));
+	if (devName != m_nvPairs.end())
+		result << devName->second << ": ";
+
+	NVPairMap::const_iterator systemName(m_nvPairs.find("system"));
+	if (systemName != m_nvPairs.end()
+	 && systemName->second != "none")
+		result << systemName->second << ": ";
+
+	result << TypeToString(GetType()) << ' ';
+
+	for (NVPairMap::const_iterator curVar = m_nvPairs.begin();
+	     curVar != m_nvPairs.end(); curVar++) {
+		if (curVar == devName || curVar == systemName)
+			continue;
+
+		result << ' '
+		     << curVar->first << "=" << curVar->second;
+	}
+	result << endl;
+
+	return (result.str());
+}
+
+void
+Event::Print() const
+{
+	cout << ToString() << std::flush;
+}
+
+void
+Event::Log(int priority) const
+{
+	syslog(priority, "%s", ToString().c_str());
+}
+
+//- Event Virtual Public Methods -----------------------------------------------
+Event::~Event()
+{
+	delete &m_nvPairs;
+}
+
+Event *
+Event::DeepCopy() const
+{
+	return (new Event(*this));
+}
+
+bool
+Event::Process() const
+{
+	return (false);
+}
+
+timeval
+Event::GetTimestamp() const
+{
+	timeval tv_timestamp;
+	struct tm tm_timestamp;
+
+	if (!Contains("timestamp")) {
+		throw Exception("Event contains no timestamp: %s",
+				m_eventString.c_str());
+	}
+	strptime(Value(string("timestamp")).c_str(), "%s", &tm_timestamp);
+	tv_timestamp.tv_sec = mktime(&tm_timestamp);
+	tv_timestamp.tv_usec = 0;
+	return (tv_timestamp);
+}
+
+bool
+Event::DevPath(std::string &path) const
+{
+	string devName;
+
+	if (!DevName(devName))
+		return (false);
+
+	string devPath(_PATH_DEV + devName);
+	int devFd(open(devPath.c_str(), O_RDONLY));
+	if (devFd == -1)
+		return (false);
+
+	/* Normalize the device name in case the DEVFS event is for a link. */
+	devName = fdevname(devFd);
+	path = _PATH_DEV + devName;
+
+	close(devFd);
+
+	return (true);
+}
+
+bool
+Event::PhysicalPath(std::string &path) const
+{
+	string devPath;
+
+	if (!DevPath(devPath))
+		return (false);
+
+	int devFd(open(devPath.c_str(), O_RDONLY));
+	if (devFd == -1)
+		return (false);
+	
+	char physPath[MAXPATHLEN];
+	physPath[0] = '\0';
+	bool result(ioctl(devFd, DIOCGPHYSPATH, physPath) == 0);
+	close(devFd);
+	if (result)
+		path = physPath;
+	return (result);
+}
+
+//- Event Protected Methods ----------------------------------------------------
+Event::Event(Type type, NVPairMap &map, const string &eventString)
+ : m_type(type),
+   m_nvPairs(map),
+   m_eventString(eventString)
+{
+}
+
+Event::Event(const Event &src)
+ : m_type(src.m_type),
+   m_nvPairs(*new NVPairMap(src.m_nvPairs)),
+   m_eventString(src.m_eventString)
+{
+}
+
+void
+Event::ParseEventString(Event::Type type,
+			      const string &eventString,
+			      NVPairMap& nvpairs)
+{
+	size_t start;
+	size_t end;
+
+	switch (type) {
+	case ATTACH:
+	case DETACH:
+
+		/*
+		 * <type><device-name><unit> <pnpvars> \
+		 *                        at <location vars> <pnpvars> \
+		 *                        on <parent>
+		 *
+		 * Handle all data that doesn't conform to the
+		 * "name=value" format, and let the generic parser
+		 * below handle the rest.
+		 *
+		 * Type is a single char.  Skip it.
+		 */
+		start = 1;
+		end = eventString.find_first_of(" \t\n", start);
+		if (end == string::npos)
+			throw ParseException(ParseException::INVALID_FORMAT,
+					     eventString, start);
+
+		nvpairs["device-name"] = eventString.substr(start, end - start);
+
+		start = eventString.find(" on ", end);
+		if (end == string::npos)
+			throw ParseException(ParseException::INVALID_FORMAT,
+					     eventString, start);
+		start += 4;
+		end = eventString.find_first_of(" \t\n", start);
+		nvpairs["parent"] = eventString.substr(start, end);
+		break;
+	case NOTIFY:
+		break;
+	case NOMATCH:
+		throw ParseException(ParseException::DISCARDED_EVENT_TYPE,
+				     eventString);
+	default:
+		throw ParseException(ParseException::UNKNOWN_EVENT_TYPE,
+				     eventString);
+	}
+
+	/* Process common "key=value" format. */
+	for (start = 1; start < eventString.length(); start = end + 1) {
+
+		/* Find the '=' in the middle of the key/value pair. */
+		end = eventString.find('=', start);
+		if (end == string::npos)
+			break;
+
+		/*
+		 * Find the start of the key by backing up until
+		 * we hit whitespace or '!' (event type "notice").
+		 * Due to the devdctl format, all key/value pair must
+		 * start with one of these two characters.
+		 */
+		start = eventString.find_last_of("! \t\n", end);
+		if (start == string::npos)
+			throw ParseException(ParseException::INVALID_FORMAT,
+					     eventString, end);
+		start++;
+		string key(eventString.substr(start, end - start));
+
+		/*
+		 * Walk forward from the '=' until either we exhaust
+		 * the buffer or we hit whitespace.
+		 */
+		start = end + 1;
+		if (start >= eventString.length())
+			throw ParseException(ParseException::INVALID_FORMAT,
+					     eventString, end);
+		end = eventString.find_first_of(" \t\n", start);
+		if (end == string::npos)
+			end = eventString.length() - 1;
+		string value(eventString.substr(start, end - start));
+
+		nvpairs[key] = value;
+	}
+}
+
+void
+Event::TimestampEventString(std::string &eventString)
+{
+	if (eventString.size() > 0) {
+		/*
+		 * Add a timestamp as the final field of the event if it is
+		 * not already present.
+		 */
+		if (eventString.find("timestamp=") == string::npos) {
+			const size_t bufsize = 32;	// Long enough for a 64-bit int
+			timeval now;
+			char timebuf[bufsize];
+
+			size_t eventEnd(eventString.find_last_not_of('\n') + 1);
+			if (gettimeofday(&now, NULL) != 0)
+				err(1, "gettimeofday");
+			snprintf(timebuf, bufsize, " timestamp=%"PRId64,
+				(int64_t) now.tv_sec);
+			eventString.insert(eventEnd, timebuf);
+		}
+	}
+}
+
+/*-------------------------------- DevfsEvent --------------------------------*/
+//- DevfsEvent Static Public Methods -------------------------------------------
+Event *
+DevfsEvent::Builder(Event::Type type, NVPairMap &nvPairs,
+		    const string &eventString)
+{
+	return (new DevfsEvent(type, nvPairs, eventString));
+}
+
+//- DevfsEvent Static Protected Methods ----------------------------------------
+bool
+DevfsEvent::IsWholeDev(const string &devName)
+{
+	string::const_iterator i(devName.begin());
+
+	size_t start = devName.rfind('/');
+	if (start == string::npos) {
+		start = 0;
+	} else {
+		/* Just after the last '/'. */
+		start++;
+	}
+	i += start;
+
+	/* alpha prefix followed only by digits. */
+	for (; i < devName.end() && !isdigit(*i); i++)
+		;
+
+	if (i == devName.end())
+		return (false);
+
+	for (; i < devName.end() && isdigit(*i); i++)
+		;
+
+	return (i == devName.end());
+}
+
+//- DevfsEvent Virtual Public Methods ------------------------------------------
+Event *
+DevfsEvent::DeepCopy() const
+{
+	return (new DevfsEvent(*this));
+}
+
+bool
+DevfsEvent::Process() const
+{
+	return (true);
+}
+
+//- DevfsEvent Public Methods --------------------------------------------------
+bool
+DevfsEvent::IsWholeDev() const
+{
+	string devName;
+
+	return (DevName(devName) && IsDiskDev() && IsWholeDev(devName));
+}
+
+bool
+DevfsEvent::DevName(std::string &name) const
+{
+	if (Value("subsystem") != "CDEV")
+		return (false);
+
+	name = Value("cdev");
+	return (!name.empty());
+}
+
+//- DevfsEvent Protected Methods -----------------------------------------------
+DevfsEvent::DevfsEvent(Event::Type type, NVPairMap &nvpairs,
+		       const string &eventString)
+ : Event(type, nvpairs, eventString)
+{
+}
+
+DevfsEvent::DevfsEvent(const DevfsEvent &src)
+ : Event(src)
+{
+}
+
+/*--------------------------------- GeomEvent --------------------------------*/
+//- GeomEvent Static Public Methods --------------------------------------------
+Event *
+GeomEvent::Builder(Event::Type type, NVPairMap &nvpairs,
+		   const string &eventString)
+{
+	return (new GeomEvent(type, nvpairs, eventString));
+}
+
+//- GeomEvent Virtual Public Methods -------------------------------------------
+Event *
+GeomEvent::DeepCopy() const
+{
+	return (new GeomEvent(*this));
+}
+
+bool
+GeomEvent::DevName(std::string &name) const
+{
+	name = Value("devname");
+	return (!name.empty());
+}
+
+
+//- GeomEvent Protected Methods ------------------------------------------------
+GeomEvent::GeomEvent(Event::Type type, NVPairMap &nvpairs,
+		     const string &eventString)
+ : Event(type, nvpairs, eventString),
+   m_devname(Value("devname"))
+{
+}
+
+GeomEvent::GeomEvent(const GeomEvent &src)
+ : Event(src),
+   m_devname(src.m_devname)
+{
+}
+
+/*--------------------------------- ZfsEvent ---------------------------------*/
+//- ZfsEvent Static Public Methods ---------------------------------------------
+Event *
+ZfsEvent::Builder(Event::Type type, NVPairMap &nvpairs,
+		  const string &eventString)
+{
+	return (new ZfsEvent(type, nvpairs, eventString));
+}
+
+//- ZfsEvent Virtual Public Methods --------------------------------------------
+Event *
+ZfsEvent::DeepCopy() const
+{
+	return (new ZfsEvent(*this));
+}
+
+bool
+ZfsEvent::DevName(std::string &name) const
+{
+	return (false);
+}
+
+//- ZfsEvent Protected Methods -------------------------------------------------
+ZfsEvent::ZfsEvent(Event::Type type, NVPairMap &nvpairs,
+		   const string &eventString)
+ : Event(type, nvpairs, eventString),
+   m_poolGUID(Guid(Value("pool_guid"))),
+   m_vdevGUID(Guid(Value("vdev_guid")))
+{
+}
+
+ZfsEvent::ZfsEvent(const ZfsEvent &src)
+ : Event(src),
+   m_poolGUID(src.m_poolGUID),
+   m_vdevGUID(src.m_vdevGUID)
+{
+}
+
+} // namespace DevdCtl
Index: lib/libdevdctl/event_factory.h
===================================================================
--- /dev/null
+++ lib/libdevdctl/event_factory.h
@@ -0,0 +1,94 @@
+/*-
+ * Copyright (c) 2011, 2012, 2013 Spectra Logic Corporation
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions, and the following disclaimer,
+ *    without modification.
+ * 2. Redistributions in binary form must reproduce at minimum a disclaimer
+ *    substantially similar to the "NO WARRANTY" disclaimer below
+ *    ("Disclaimer") and any redistribution must be conditioned upon
+ *    including a substantially similar Disclaimer requirement for further
+ *    binary redistribution.
+ *
+ * NO WARRANTY
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDERS OR CONTRIBUTORS BE LIABLE FOR SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
+ * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGES.
+ *
+ * Authors: Justin T. Gibbs     (Spectra Logic Corporation)
+ *
+ * $FreeBSD$
+ */
+
+/**
+ * \file devdctl_event_factory.h
+ */
+
+#ifndef _DEVDCTL_EVENT_FACTORY_H_
+#define	_DEVDCTL_EVENT_FACTORY_H_
+
+/*============================ Namespace Control =============================*/
+namespace DevdCtl
+{
+
+/*============================= Class Definitions ============================*/
+/*------------------------------- EventFactory -------------------------------*/
+/**
+ * \brief Container for "event type" => "event object" creation methods.
+ */
+class EventFactory
+{
+public:
+	/**
+	 * Event creation handlers are matched by event type and a
+	 * string representing the system emitting the event.
+	 */
+	typedef std::pair<Event::Type, std::string> Key;
+
+	/** Map type for Factory method lookups. */
+	typedef std::map<Key, Event::BuildMethod *> Registry;
+
+	/** Table record of factory methods to add to our registry. */
+	struct Record
+	{
+		Event::Type         m_type;
+		const char         *m_subsystem;
+		Event::BuildMethod *m_buildMethod;
+	};
+
+	const Registry &GetRegistry()				const;
+	Event *Build(Event::Type type, NVPairMap &nvpairs,
+		     const std::string eventString)		const;
+
+	EventFactory(Event::BuildMethod *defaultBuildMethod = NULL);
+
+	void UpdateRegistry(Record regEntries[], size_t numEntries);
+
+
+protected:
+	/** Registry of event factory methods providing O(log(n)) lookup. */
+	Registry	    m_registry;
+
+	Event::BuildMethod *m_defaultBuildMethod;
+};
+
+inline const EventFactory::Registry &
+EventFactory::GetRegistry() const
+{
+	return (m_registry);
+}
+
+} // namespace DevdCtl
+#endif /*_DEVDCTL_EVENT_FACTORY_H_ */
Index: lib/libdevdctl/event_factory.cc
===================================================================
--- /dev/null
+++ lib/libdevdctl/event_factory.cc
@@ -0,0 +1,99 @@
+/*-
+ * Copyright (c) 2011, 2012, 2013 Spectra Logic Corporation
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions, and the following disclaimer,
+ *    without modification.
+ * 2. Redistributions in binary form must reproduce at minimum a disclaimer
+ *    substantially similar to the "NO WARRANTY" disclaimer below
+ *    ("Disclaimer") and any redistribution must be conditioned upon
+ *    including a substantially similar Disclaimer requirement for further
+ *    binary redistribution.
+ *
+ * NO WARRANTY
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDERS OR CONTRIBUTORS BE LIABLE FOR SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
+ * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGES.
+ *
+ * Authors: Justin T. Gibbs     (Spectra Logic Corporation)
+ */
+
+/**
+ * \file event_factory.cc
+ */
+#include <sys/cdefs.h>
+#include <sys/time.h>
+
+#include <list>
+#include <map>
+#include <string>
+
+#include "guid.h"
+#include "event.h"
+#include "event_factory.h"
+
+__FBSDID("$FreeBSD$");
+
+/*================================== Macros ==================================*/
+#define NUM_ELEMENTS(x) (sizeof(x) / sizeof(*x))
+
+/*============================ Namespace Control =============================*/
+namespace DevdCtl
+{
+
+/*=========================== Class Implementations ==========================*/
+/*------------------------------- EventFactory -------------------------------*/
+//- Event Public Methods -------------------------------------------------------
+EventFactory::EventFactory(Event::BuildMethod *defaultBuildMethod)
+ : m_defaultBuildMethod(defaultBuildMethod)
+{
+}
+
+void
+EventFactory::UpdateRegistry(Record regEntries[], size_t numEntries)
+{
+	EventFactory::Record *rec(regEntries);
+	EventFactory::Record *lastRec(rec + numEntries - 1);
+
+	for (; rec <= lastRec; rec++) {
+		Key key(rec->m_type, rec->m_subsystem);
+
+		if (rec->m_buildMethod == NULL)
+			m_registry.erase(key);
+		else
+			m_registry[key] = rec->m_buildMethod;
+	}
+}
+
+Event *
+EventFactory::Build(Event::Type type, NVPairMap &nvpairs,
+		    const std::string eventString) const
+{
+	Key key(type, nvpairs["system"]);
+	Event::BuildMethod *buildMethod(m_defaultBuildMethod);
+
+	Registry::const_iterator foundMethod(m_registry.find(key));
+	if (foundMethod != m_registry.end())
+		buildMethod = foundMethod->second;
+	
+	if (buildMethod == NULL) {
+		delete &nvpairs;
+		return (NULL);
+	}
+
+	return (buildMethod(type, nvpairs, eventString));
+}
+
+} // namespace DevdCtl
Index: lib/libdevdctl/exception.h
===================================================================
--- /dev/null
+++ lib/libdevdctl/exception.h
@@ -0,0 +1,166 @@
+/*-
+ * Copyright (c) 2011, 2012, 2013 Spectra Logic Corporation
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions, and the following disclaimer,
+ *    without modification.
+ * 2. Redistributions in binary form must reproduce at minimum a disclaimer
+ *    substantially similar to the "NO WARRANTY" disclaimer below
+ *    ("Disclaimer") and any redistribution must be conditioned upon
+ *    including a substantially similar Disclaimer requirement for further
+ *    binary redistribution.
+ *
+ * NO WARRANTY
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDERS OR CONTRIBUTORS BE LIABLE FOR SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
+ * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGES.
+ *
+ * Authors: Justin T. Gibbs     (Spectra Logic Corporation)
+ */
+
+/**
+ * \file zfsd_exception.h
+ *
+ * Definition of the ZfsdException class hierarchy.  All exceptions
+ * explicitly thrown by Zfsd are defined here.
+ */
+#ifndef	_DEVDCTL_EXCEPTION_H_
+#define	_DEVDCTL_EXCEPTION_H_
+
+/*============================ Namespace Control =============================*/
+namespace DevdCtl
+{
+
+/*============================= Class Definitions ============================*/
+
+/*--------------------------------- Exception --------------------------------*/
+/**
+ * \brief Class allowing unified reporting/logging of exceptional events.
+ */
+class Exception
+{
+public:
+	/**
+	 * \brief Exception constructor allowing arbitrary string
+	 *        data to be reported.
+	 *
+	 * \param fmt  Printf-like string format specifier.
+	 */
+	Exception(const char *fmt, ...);
+
+	/**
+	 * \brief Augment/Modify a Exception's string data.
+	 */
+	std::string& GetString();
+
+	/**
+	 * \brief Emit exception data to syslog(3).
+	 */
+	virtual void Log() const;
+
+protected:
+	Exception();
+
+	/**
+	 * \brief Convert exception string format and arguments provided
+	 *        in event constructors into a linear string.
+	 */
+	void FormatLog(const char *fmt, va_list ap);
+
+	std::string   m_log;
+};
+
+inline std::string &
+Exception::GetString()
+{
+	return (m_log);
+}
+
+/*------------------------------ ParseException ------------------------------*/
+/**
+ * Exception thrown when an event string is not converted to an actionable
+ * Event object.
+ */
+class ParseException : public Exception
+{
+public:
+	enum Type
+	{
+		/** Improperly formatted event string encountered. */
+		INVALID_FORMAT,
+
+		/** No handlers for this event type. */
+		DISCARDED_EVENT_TYPE,
+
+		/** Unhandled event type. */
+		UNKNOWN_EVENT_TYPE
+	};
+
+	/**
+	 * Constructor
+	 *
+	 * \param type          The type of this exception.
+	 * \param parsedBuffer  The parsing buffer active at the time of
+	 *                      the exception.
+	 * \param offset        The location in the parse buffer where this
+	 *                      exception occurred.
+	 */
+	ParseException(Type type, const std::string &parsedBuffer,
+		       size_t offset = 0);
+
+	/**
+	 * Accessor
+	 *
+	 * \return  The classification for this exception.
+	 */
+	Type        GetType()   const;
+
+	/**
+	 * Accessor
+	 *
+	 * \return  The offset into the event string where this exception
+	 *          occurred.
+	 */
+	size_t      GetOffset() const;
+
+private:
+	/** The type of this exception. */
+	Type              m_type;
+
+	/** The parsing buffer that was active at the time of the exception. */
+	const std::string m_parsedBuffer;
+
+	/**
+	 * The offset into the event string buffer from where this
+	 * exception was triggered.
+	 */
+	size_t            m_offset;
+};
+
+//- ParseException Inline Const Public Methods ---------------------------------
+inline ParseException::Type
+ParseException::GetType() const
+{
+	return (m_type);
+}
+
+inline size_t
+ParseException::GetOffset() const
+{
+	return (m_offset);
+}
+
+} // namespace DevdCtl
+#endif /* _DEVDCTL_EXCEPTION_H_ */
Index: lib/libdevdctl/exception.cc
===================================================================
--- /dev/null
+++ lib/libdevdctl/exception.cc
@@ -0,0 +1,125 @@
+/*-
+ * Copyright (c) 2011, 2012, 2013 Spectra Logic Corporation
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions, and the following disclaimer,
+ *    without modification.
+ * 2. Redistributions in binary form must reproduce at minimum a disclaimer
+ *    substantially similar to the "NO WARRANTY" disclaimer below
+ *    ("Disclaimer") and any redistribution must be conditioned upon
+ *    including a substantially similar Disclaimer requirement for further
+ *    binary redistribution.
+ *
+ * NO WARRANTY
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDERS OR CONTRIBUTORS BE LIABLE FOR SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
+ * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGES.
+ *
+ * Authors: Justin T. Gibbs     (Spectra Logic Corporation)
+ */
+
+/**
+ * \file exception.cc
+ */
+#include <sys/cdefs.h>
+
+#include <syslog.h>
+
+#include <cstdio>
+#include <cstdarg>
+#include <sstream>
+#include <string>
+
+#include "exception.h"
+
+__FBSDID("$FreeBSD$");
+
+/*============================ Namespace Control =============================*/
+using std::string;
+using std::stringstream;
+using std::endl;
+namespace DevdCtl
+{
+
+/*=========================== Class Implementations ==========================*/
+/*--------------------------------- Exception --------------------------------*/
+void
+Exception::FormatLog(const char *fmt, va_list ap)
+{
+	char buf[256];
+
+	vsnprintf(buf, sizeof(buf), fmt, ap);
+	m_log = buf;
+}
+
+Exception::Exception(const char *fmt, ...)
+{
+	va_list ap;
+
+	va_start(ap, fmt);
+	FormatLog(fmt, ap);
+	va_end(ap);
+}
+
+Exception::Exception()
+{
+}
+
+void
+Exception::Log() const
+{
+	syslog(LOG_ERR, "%s", m_log.c_str());
+}
+
+/*------------------------------ ParseException ------------------------------*/
+//- ParseException Inline Public Methods ---------------------------------------
+ParseException::ParseException(Type type, const std::string &parsedBuffer,
+			       size_t offset)
+ : Exception(),
+   m_type(type),
+   m_parsedBuffer(parsedBuffer),
+   m_offset(offset)
+{
+        stringstream logstream;
+
+        logstream << "Parsing ";
+
+        switch (Type()) {
+        case INVALID_FORMAT:
+                logstream << "invalid format ";
+                break;
+        case DISCARDED_EVENT_TYPE:
+                logstream << "discarded event ";
+                break;
+        case UNKNOWN_EVENT_TYPE:
+                logstream << "unknown event ";
+                break;
+        default:
+                break;
+        }
+        logstream << "exception on buffer: \'";
+        if (GetOffset() == 0) {
+                logstream << m_parsedBuffer << '\'' << endl;
+        } else {
+                string markedBuffer(m_parsedBuffer);
+
+                markedBuffer.insert(GetOffset(), "<HERE-->");
+                logstream << markedBuffer << '\'' << endl;
+        }
+
+	GetString() = logstream.str();
+}
+
+} // namespace DevdCtl
Index: lib/libdevdctl/guid.h
===================================================================
--- /dev/null
+++ lib/libdevdctl/guid.h
@@ -0,0 +1,143 @@
+/*-
+ * Copyright (c) 2012, 2013 Spectra Logic Corporation
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions, and the following disclaimer,
+ *    without modification.
+ * 2. Redistributions in binary form must reproduce at minimum a disclaimer
+ *    substantially similar to the "NO WARRANTY" disclaimer below
+ *    ("Disclaimer") and any redistribution must be conditioned upon
+ *    including a substantially similar Disclaimer requirement for further
+ *    binary redistribution.
+ *
+ * NO WARRANTY
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDERS OR CONTRIBUTORS BE LIABLE FOR SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
+ * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGES.
+ *
+ * Authors: Alan Somers         (Spectra Logic Corporation)
+ *
+ * $FreeBSD$
+ */
+
+/**
+ * \file devdctl_guid.h
+ *
+ * Definition of the Guid class.
+ */
+#ifndef	_DEVDCTL_GUID_H_
+#define	_DEVDCTL_GUID_H_
+
+/*============================ Namespace Control =============================*/
+namespace DevdCtl
+{
+
+/*============================= Class Definitions ============================*/
+/*----------------------------------- Guid -----------------------------------*/
+/**
+ * \brief Object that represents guids.
+ *
+ * It can generally be manipulated as a uint64_t, but with a special
+ * value INVALID_GUID that does not equal any valid guid.
+ *
+ * As of this writing, this class is only used to represent ZFS
+ * guids in events and spa_generate_guid() in spa_misc.c explicitly
+ * refuses to return a guid of 0.  So this class uses 0 as the value
+ * for INVALID_GUID.  In the future, if 0 is allowed to be a valid
+ * guid, the implementation of this class must change.
+ */
+class Guid
+{
+public:
+	/* Constructors */
+	Guid();
+	Guid(uint64_t guid);
+	Guid(const std::string &guid);
+
+	/* Assignment */
+	Guid& operator=(const Guid& rhs);
+
+	/* Test the validity of this guid. */
+	bool IsValid()			 const;
+
+	/* Comparison to other Guid operators */
+	bool operator==(const Guid& rhs) const;
+	bool operator!=(const Guid& rhs) const;
+
+	/* Integer conversion operators */
+	operator uint64_t()		 const;
+	operator bool()			 const;
+
+	static const uint64_t INVALID_GUID = 0;
+protected:
+	/* The integer value of the GUID. */
+	uint64_t  m_GUID;
+};
+
+//- Guid Inline Public Methods ------------------------------------------------
+inline
+Guid::Guid()
+  : m_GUID(INVALID_GUID)
+{
+}
+
+inline
+Guid::Guid(uint64_t guid)
+  : m_GUID(guid)
+{
+}
+
+inline Guid&
+Guid::operator=(const Guid &rhs)
+{
+	m_GUID = rhs.m_GUID;
+	return (*this);
+}
+
+inline bool
+Guid::IsValid() const
+{
+	return (m_GUID != INVALID_GUID);
+}
+
+inline bool
+Guid::operator==(const Guid& rhs) const
+{
+	return (m_GUID == rhs.m_GUID);
+}
+
+inline bool
+Guid::operator!=(const Guid& rhs) const
+{
+	return (m_GUID != rhs.m_GUID);
+}
+
+inline
+Guid::operator uint64_t() const
+{
+	return (m_GUID);
+}
+
+inline
+Guid::operator bool() const
+{
+	return (m_GUID != INVALID_GUID);
+}
+
+/** Convert the GUID into its string representation */
+std::ostream& operator<< (std::ostream& out, Guid g);
+
+} // namespace DevdCtl
+#endif /* _DEVDCTL_GUID_H_ */
Index: lib/libdevdctl/guid.cc
===================================================================
--- /dev/null
+++ lib/libdevdctl/guid.cc
@@ -0,0 +1,82 @@
+/*-
+ * Copyright (c) 2012, 2013 Spectra Logic Corporation
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions, and the following disclaimer,
+ *    without modification.
+ * 2. Redistributions in binary form must reproduce at minimum a disclaimer
+ *    substantially similar to the "NO WARRANTY" disclaimer below
+ *    ("Disclaimer") and any redistribution must be conditioned upon
+ *    including a substantially similar Disclaimer requirement for further
+ *    binary redistribution.
+ *
+ * NO WARRANTY
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDERS OR CONTRIBUTORS BE LIABLE FOR SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
+ * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGES.
+ *
+ * Authors: Alan Somers         (Spectra Logic Corporation)
+ *
+ * $FreeBSD$
+ */
+
+/**
+ * \file guid.cc
+ *
+ * Implementation of the Guid class.
+ */
+#include <sys/cdefs.h>
+
+#include <stdlib.h>
+#include <limits.h>
+#include <inttypes.h>
+
+#include <iostream>
+#include <string>
+
+#include "guid.h"
+
+__FBSDID("$FreeBSD$");
+/*============================ Namespace Control =============================*/
+using std::string;
+namespace DevdCtl
+{
+
+/*=========================== Class Implementations ==========================*/
+/*----------------------------------- Guid -----------------------------------*/
+Guid::Guid(const string &guidString)
+{
+	if (guidString.empty()) {
+		m_GUID = INVALID_GUID;
+	} else {
+		/*
+		 * strtoumax() returns zero on conversion failure
+		 * which nicely matches our choice for INVALID_GUID.
+		 */
+		m_GUID = (uint64_t)strtoumax(guidString.c_str(), NULL, 0);
+	}
+}
+
+std::ostream&
+operator<< (std::ostream& out, Guid g)
+{
+	if (g.IsValid())
+		out << (uint64_t)g;
+	else
+		out << "None";
+	return (out);
+}
+
+} // namespace DevdCtl
Index: lib/libdevdctl/tests/Makefile
===================================================================
--- /dev/null
+++ lib/libdevdctl/tests/Makefile
@@ -0,0 +1,21 @@
+# $FreeBSD$
+
+TESTSDIR= ${TESTSBASE}/lib/libdevdctl
+
+.PATH:	${.CURDIR}/..
+
+PLAIN_TESTS_CXX= libdevdctl_unittest
+
+SRCS.libdevdctl_unittest+= 	event_factory.cc	\
+				libdevdctl_unittest.cc	\
+				event.cc exception.cc	\
+				guid.cc
+CFLAGS.libdevdctl_unittest+= -I ${LOCALBASE}/include -D_THREAD_SAFE -pthread
+DPADD.libdevdctl_unittest+= ${LIBDEVDCTL}
+LDADD.libdevdctl_unittest+= -L ${LOCALBASE}/lib -D_THREAD_SAFE -pthread -lgtest -lgtest_main
+
+# Googletest options
+LOCALBASE?=	/usr/local
+
+WARNS?= 3
+.include <bsd.test.mk>
Index: lib/libdevdctl/tests/libdevdctl_unittest.cc
===================================================================
--- /dev/null
+++ lib/libdevdctl/tests/libdevdctl_unittest.cc
@@ -0,0 +1,136 @@
+/*-
+ * Copyright (c) 2016 Spectra Logic Corporation
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions, and the following disclaimer,
+ *    without modification.
+ * 2. Redistributions in binary form must reproduce at minimum a disclaimer
+ *    substantially similar to the "NO WARRANTY" disclaimer below
+ *    ("Disclaimer") and any redistribution must be conditioned upon
+ *    including a substantially similar Disclaimer requirement for further
+ *    binary redistribution.
+ *
+ * NO WARRANTY
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDERS OR CONTRIBUTORS BE LIABLE FOR SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
+ * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGES.
+ *
+ * Authors: Alan Somers         (Spectra Logic Corporation)
+ */
+
+#include <gtest/gtest.h>
+
+#include <list>
+#include <map>
+#include <string>
+
+#include <devdctl/guid.h>
+#include <devdctl/event.h>
+#include <devdctl/event_factory.h>
+
+using namespace DevdCtl;
+using namespace std;
+using namespace testing;
+
+#define REGISTRY_SIZE 2
+
+struct DevNameTestParams
+{
+	const char* evs;
+	bool is_disk;
+	const char* devname;
+};
+
+class DevNameTest : public TestWithParam<DevNameTestParams>{
+protected:
+	virtual void SetUp()
+	{
+		m_factory = new EventFactory();
+		m_factory->UpdateRegistry(s_registry, REGISTRY_SIZE);
+	}
+
+	virtual void TearDown()
+	{
+		if (m_ev) delete m_ev;
+		if (m_factory) delete m_factory;
+	}
+
+	EventFactory *m_factory;
+	Event *m_ev;
+	static EventFactory::Record s_registry[REGISTRY_SIZE];
+};
+
+DevdCtl::EventFactory::Record DevNameTest::s_registry[REGISTRY_SIZE] = {
+	{ Event::NOTIFY, "DEVFS", &DevfsEvent::Builder },
+	{ Event::NOTIFY, "GEOM", &GeomEvent::Builder }
+};
+
+TEST_P(DevNameTest, TestDevname) {
+	std::string devname;
+	DevNameTestParams param = GetParam();
+	
+	string evString(param.evs);
+	m_ev = Event::CreateEvent(*m_factory, evString);
+	m_ev->DevName(devname);
+	EXPECT_STREQ(param.devname, devname.c_str());
+}
+
+TEST_P(DevNameTest, TestIsDiskDev) {
+	DevNameTestParams param = GetParam();
+
+	string evString(param.evs);
+	m_ev = Event::CreateEvent(*m_factory, evString);
+	EXPECT_EQ(param.is_disk, m_ev->IsDiskDev());
+}
+
+/* TODO: clean this up using C++-11 uniform initializers */
+INSTANTIATE_TEST_CASE_P(IsDiskDevTestInstantiation, DevNameTest, Values(
+	(DevNameTestParams){
+		.evs = "!system=DEVFS subsystem=CDEV type=CREATE cdev=da6\n",
+		.is_disk = true, .devname = "da6"},
+	(DevNameTestParams){.is_disk = false, .devname = "cuau0",
+		.evs = "!system=DEVFS subsystem=CDEV type=CREATE cdev=cuau0\n"},
+	(DevNameTestParams){.is_disk = true, .devname = "ada6",
+		.evs = "!system=DEVFS subsystem=CDEV type=CREATE cdev=ada6\n"},
+	(DevNameTestParams){.is_disk = true, .devname = "da6p1",
+		.evs = "!system=DEVFS subsystem=CDEV type=CREATE cdev=da6p1\n"},
+	(DevNameTestParams){.is_disk = true, .devname = "ada6p1",
+		.evs = "!system=DEVFS subsystem=CDEV type=CREATE cdev=ada6p1\n"},
+	(DevNameTestParams){.is_disk = true, .devname = "da6s0p1",
+		.evs = "!system=DEVFS subsystem=CDEV type=CREATE cdev=da6s0p1\n"},
+	(DevNameTestParams){.is_disk = true, .devname = "ada6s0p1",
+		.evs = "!system=DEVFS subsystem=CDEV type=CREATE cdev=ada6s0p1\n"},
+	/* 
+	 * Test physical path nodes.  These are currently all set to false since
+	 * physical path nodes are implemented with symlinks, and most CAM and
+	 * ZFS operations can't use symlinked device nodes
+	 */
+	/* A SpectraBSD-style physical path node*/
+	(DevNameTestParams){.is_disk = false, .devname = "enc@50030480019f53fd/elmtype@array_device/slot@18/da",
+		.evs = "!system=DEVFS subsystem=CDEV type=CREATE cdev=enc@50030480019f53fd/elmtype@array_device/slot@18/da\n"},
+	(DevNameTestParams){.is_disk = false, .devname = "enc@50030480019f53fd/elmtype@array_device/slot@18/pass",
+		.evs = "!system=DEVFS subsystem=CDEV type=CREATE cdev=enc@50030480019f53fd/elmtype@array_device/slot@18/pass\n"},
+	/* A FreeBSD-style physical path node */
+	(DevNameTestParams){.is_disk = true, .devname = "enc@n50030480019f53fd/type@0/slot@18/elmdesc@ArrayDevice18/da6",
+		.evs = "!system=DEVFS subsystem=CDEV type=CREATE cdev=enc@n50030480019f53fd/type@0/slot@18/elmdesc@ArrayDevice18/da6\n"},
+	(DevNameTestParams){.is_disk = false, .devname = "enc@n50030480019f53fd/type@0/slot@18/elmdesc@ArrayDevice18/pass6",
+		.evs = "!system=DEVFS subsystem=CDEV type=CREATE cdev=enc@n50030480019f53fd/type@0/slot@18/elmdesc@ArrayDevice18/pass6\n"},
+	
+	/*
+	 * Test some GEOM events
+	 */
+	(DevNameTestParams){.is_disk = true, .devname = "da5",
+		.evs = "!system=GEOM subsystem=disk type=GEOM::physpath devname=da5\n"})
+);
Index: share/mk/bsd.libnames.mk
===================================================================
--- share/mk/bsd.libnames.mk
+++ share/mk/bsd.libnames.mk
@@ -49,6 +49,7 @@
 LIBCXXRT?=	${DESTDIR}${LIBDIR}/libcxxrt.a
 LIBC_PIC?=	${DESTDIR}${LIBDIR}/libc_pic.a
 LIBDEVCTL?=	${DESTDIR}${LIBDIR}/libdevctl.a
+LIBDEVDCTL?=	${DESTDIR}${LIBDIR}/libdevdctl.a
 LIBDEVINFO?=	${DESTDIR}${LIBDIR}/libdevinfo.a
 LIBDEVSTAT?=	${DESTDIR}${LIBDIR}/libdevstat.a
 LIBDIALOG?=	${DESTDIR}${LIBDIR}/libdialog.a
Index: share/mk/src.libnames.mk
===================================================================
--- share/mk/src.libnames.mk
+++ share/mk/src.libnames.mk
@@ -16,6 +16,7 @@
 		atf_c \
 		atf_cxx \
 		bsdstat \
+		devdctl \
 		event \
 		heimipcc \
 		heimipcs \
@@ -82,6 +83,7 @@
 		cuse \
 		cxxrt \
 		devctl \
+		devdctl \
 		devinfo \
 		devstat \
 		dialog \
Index: sys/cddl/contrib/opensolaris/uts/common/fs/zfs/vdev.c
===================================================================
--- sys/cddl/contrib/opensolaris/uts/common/fs/zfs/vdev.c
+++ sys/cddl/contrib/opensolaris/uts/common/fs/zfs/vdev.c
@@ -3366,19 +3366,6 @@
 	    vd->vdev_ops->vdev_op_leaf)
 		vd->vdev_ops->vdev_op_close(vd);
 
-	/*
-	 * If we have brought this vdev back into service, we need
-	 * to notify fmd so that it can gracefully repair any outstanding
-	 * cases due to a missing device.  We do this in all cases, even those
-	 * that probably don't correlate to a repaired fault.  This is sure to
-	 * catch all cases, and we let the zfs-retire agent sort it out.  If
-	 * this is a transient state it's OK, as the retire agent will
-	 * double-check the state of the vdev before repairing it.
-	 */
-	if (state == VDEV_STATE_HEALTHY && vd->vdev_ops->vdev_op_leaf &&
-	    vd->vdev_prevstate != state)
-		zfs_post_state_change(spa, vd);
-
 	if (vd->vdev_removed &&
 	    state == VDEV_STATE_CANT_OPEN &&
 	    (aux == VDEV_AUX_OPEN_FAILED || vd->vdev_checkremove)) {
@@ -3459,6 +3446,16 @@
 		vd->vdev_removed = B_FALSE;
 	}
 
+	/*
+	* Notify the fmd of the state change.  Be verbose and post
+	* notifications even for stuff that's not important; the fmd agent can
+	* sort it out.  Don't emit state change events for non-leaf vdevs since
+	* they can't change state on their own.  The FMD can check their state
+	* if it wants to when it sees that a leaf vdev had a state change.
+	*/
+	if (vd->vdev_ops->vdev_op_leaf)
+		zfs_post_state_change(spa, vd);
+
 	if (!isopen && vd->vdev_parent)
 		vdev_propagate_state(vd->vdev_parent);
 }
Index: sys/fs/devfs/devfs_vnops.c
===================================================================
--- sys/fs/devfs/devfs_vnops.c
+++ sys/fs/devfs/devfs_vnops.c
@@ -1352,8 +1352,53 @@
 devfs_readlink(struct vop_readlink_args *ap)
 {
 	struct devfs_dirent *de;
+	struct cdev_priv *cdp;
 
 	de = ap->a_vp->v_data;
+	cdp = de->de_cdp;
+
+	if (cdp != NULL && (cdp->cdp_c.si_flags & SI_ALIAS) != 0) {
+		struct devfs_mount *dmp;
+		struct prison *pr;
+		char *mp;
+		int mp_len;
+		int pr_path_len;
+		int err;
+
+		/*
+		 * For device aliases, construct an absolute symlink (to
+		 * shorten its length and avoid the ugliness of a relative
+		 * link) by prepending the fully qualified path to the root
+		 * of this devfs.  For a non-jailed process, the devfs root
+		 * is our mount point.  For a jailed process, we must remove
+		 * any jail prefix in our mount point so that our response
+		 * matches the user process's world view.
+		 */
+		dmp = VFSTODEVFS(ap->a_vp->v_mount);
+		mp = dmp->dm_mount->mnt_stat.f_mntonname;
+		mp_len = strlen(mp);
+
+		pr = ap->a_cred->cr_prison;
+		pr_path_len = strlen(pr->pr_path);
+
+		if (strncmp(pr->pr_path, mp, pr_path_len) == 0
+		 && mp[pr_path_len] == '/') {
+			mp += pr_path_len;
+			mp_len -= pr_path_len;
+		}
+
+		err = uiomove(mp, mp_len, ap->a_uio);
+		if (err != 0)
+			return (err);
+
+		/*
+		 * Devfs cannot be the root file system, so its
+		 * mount point must always be terminated by a '/'.
+		 */
+		err = uiomove("/", 1, ap->a_uio);
+		if (err != 0)
+			return (err);
+	}
 	return (uiomove(de->de_symlink, strlen(de->de_symlink), ap->a_uio));
 }