--- /dev/null	Sun Aug 11 00:54:15 2013
+++ new/usr/src/uts/common/io/fsd/fsd.c	Sun Aug 11 00:54:15 2013
@@ -0,0 +1,634 @@
+/*
+ * This file and its contents are supplied under the terms of the
+ * Common Development and Distribution License ("CDDL"), version 1.0.
+ * You may only use this file in accordance with the terms of version
+ * 1.0 of the CDDL.
+ *
+ * A full copy of the text of the CDDL should have accompanied this
+ * source.  A copy of the CDDL is also available via the Internet at
+ * http://www.illumos.org/license/CDDL.
+ */
+
+/*
+ * Copyright 2013 Damian Bogel.  All rights reserved.
+ */
+
+/*
+ * Filesystem disturber pseudo-device driver.
+ */
+
+#include <sys/conf.h>
+#include <sys/ddi.h>
+#include <sys/file.h>
+#include <sys/fsd.h>
+#include <sys/fsh.h>
+#include <sys/kmem.h>
+#include <sys/ksynch.h>
+#include <sys/list.h>
+#include <sys/mkdev.h>
+#include <sys/stat.h>
+#include <sys/sunddi.h>
+#include <sys/sysmacros.h>
+#include <sys/types.h>
+
+/*
+ * fsd - filesystem disturber
+ *
+ * 1. Abstract
+ * Filesystem disturber is a pseudo-device driver used to inject pathological
+ * behaviour into vfs calls. It is NOT a fuzzer. That kind of behaviour
+ * should be expected and correctly handled by software. A simple example of
+ * such behaviour is read() reading less bytes than it was requested. It's
+ * well documented and every read() caller should check the return value of
+ * this function before proceeding.
+ *
+ * 2. Features
+ * * per-vfs injections
+ * * injection installing on every newly mounted vfs
+ *
+ * 3. Usage
+ * A library (libfsd) is provided to drive fsd.
+ *
+ * 4. Internals
+ * fsd_enable() does the necessary fsh callbacks installing.
+ * These callbacks are used for both installing injections on newly mounted
+ * vfs' and cleaning up when a vfs is destroyed.
+ *
+ * The list of currently installed hooks is kept in fsd_slist.
+ *
+ * 5. Locking
+ * The locking is fairly simple. Every modification of fsd_enable, fsd_hooks,
+ * fsd_new_stat and fsd_slist is protected by fsd_lock. Hooks use only the
+ * elements of fsd_slist, nothing else. Before an element of fsd_slist
+ * is destroyed, a hook which uses it is removed.
+ *
+ * Every fsd_slist element is protected by it's own lock.
+ */
+
+static dev_info_t	*fsd_devi;
+
+/* procets: fsd_enabled, fsd_hooks, fsd_new_stat, fsd_slist */
+static kmutex_t		fsd_lock;
+
+/* fsd_stat for new filesystems. Don't do anything if NULL. */
+static fsd_stat_t	*fsd_new_stat;
+static int		fsd_enabled;
+static fsh_t		fsd_hooks;
+
+/*
+ * The fsd_slist exist for two purposes.
+ * 1. It's an argument for hooks.
+ * 2. For every vfs on which fsd installed a set of hooks there exist exactly
+ * one entry in fsd_slist.
+ */
+static list_t	fsd_slist;
+
+/* No need for lock here. It's unchanged between fsd_attach()/fsd_detach(). */
+static fsh_callback_t	fsd_callbacks;
+
+/*
+ * Although it's safe to use this kind of pseudo-random number generator,
+ * it behaves very regular when it comes to parity. Every fsd_rand() call
+ * changes the parity of the seed. That's why when a range of width 2 is set
+ * as a parameter, it's highly possible that the random value will always be
+ * the same, because fsd_rand() could be called the same number of times in a
+ * hook.
+ */
+static long	fsd_rand_seed;
+
+static int
+fsd_rand()
+{
+	fsd_rand_seed = fsd_rand_seed * 1103515245L + 12345;
+	return (fsd_rand_seed & 0x7ffffffff);
+}
+
+/* vnode hooks */
+/*
+ * A pointer to a given fsd_stat_int_t is valid always inside fsh_hook_xxx()
+ * call, because it's valid until the hooks associated with it are removed.
+ * If a hook is removed, it cannot be executing.
+ */
+static int
+fsd_hook_read(fsh_int_t *fshi, void *arg, vnode_t *vp, uio_t *uiop,
+	int ioflag, cred_t *cr, caller_context_t *ct)
+{
+	fsd_stat_int_t	*si;
+	uint64_t	cnt;
+	uint64_t	less;
+	uint64_t	less_chance;
+
+	/*
+	 * It is used to keep an odd number of fsd_rand() calls in every
+	 * fsd_hook_read() call. That is desired because when a range of width
+	 * 2 is set as a parameter, we don't want to make it a constant.
+	 * The pseudo-random number generator returns a number with different
+	 * parity with every call. If this function is called in every
+	 * fsd_hook_read() execution even number of times, it would always be
+	 * the same % 2.
+	 */
+	(void) fsd_rand();
+
+	si = (fsd_stat_int_t *)arg;
+	ASSERT(vp->v_vfsp == si->fsdsi_vfsp);
+
+	rw_enter(&si->fsdsi_lock, RW_READER);
+	less_chance = si->fsdsi_stat.fsds_read_less_chance;
+	less = (uint64_t)fsd_rand() %
+	    (si->fsdsi_stat.fsds_read_less_r[1] + 1 -
+	    si->fsdsi_stat.fsds_read_less_r[0]) +
+	    si->fsdsi_stat.fsds_read_less_r[0];
+
+	rw_exit(&si->fsdsi_lock);
+
+	cnt = uiop->uio_iov->iov_len;
+	if ((uint64_t)fsd_rand() % 100 < less_chance) {
+		extern size_t copyout_max_cached;
+		int ret;
+
+		if (cnt > less)
+			cnt -= less;
+		else
+			less = 0;
+
+		uiop->uio_iov->iov_len = cnt;
+		uiop->uio_resid = cnt;
+		if (cnt <= copyout_max_cached)
+			uiop->uio_extflg = UIO_COPY_CACHED;
+		else
+			uiop->uio_extflg = UIO_COPY_DEFAULT;
+
+		ret = fsh_next_read(fshi, vp, uiop, ioflag, cr, ct);
+		uiop->uio_resid += less;
+		return (ret);
+	}
+
+	return (fsh_next_read(fshi, vp, uiop, ioflag, cr, ct));
+}
+
+/*
+ * Finds and (optionally) locks a fsd_stat_int_t structure of a given vfs.
+ */
+static int
+fsd_get_stati(vfs_t *vfsp, fsd_stat_int_t **si, int lock, int rwmode)
+{
+	fsd_stat_int_t *stati;
+
+	ASSERT(MUTEX_HELD(&fsd_lock));
+
+	for (stati = list_head(&fsd_slist); stati;
+	    stati = list_next(&fsd_slist, stati))
+		if (stati->fsdsi_vfsp == vfsp) {
+			if (lock)
+				rw_enter(&stati->fsdsi_lock, rwmode);
+			*si = stati;
+			return (0);
+		}
+	return (FSD_ENTRY_NOT_FOUND);
+
+}
+
+static void
+fsd_install_disturber(vfs_t *vfsp, fsd_stat_t *stat)
+{
+	fsd_stat_int_t *si;
+
+	ASSERT(MUTEX_HELD(&fsd_lock));
+
+	if (fsd_get_stati(vfsp, &si, 1, RW_WRITER) == FSD_ENTRY_NOT_FOUND) {
+		si = kmem_alloc(sizeof (*si), KM_SLEEP);
+		si->fsdsi_vfsp = vfsp;
+		(void) memcpy(&si->fsdsi_stat, stat, sizeof (si->fsdsi_stat));
+		rw_init(&si->fsdsi_lock, NULL, RW_DRIVER, NULL);
+		list_insert_head(&fsd_slist, si);
+
+		fsd_hooks.arg = si;
+		fsh_hook_install(vfsp, &fsd_hooks);
+	} else {
+		(void) memcpy(&si->fsdsi_stat, stat, sizeof (si->fsdsi_stat));
+		rw_exit(&si->fsdsi_lock);
+	}
+}
+
+static int
+fsd_remove_disturber(vfs_t *vfsp)
+{
+	fsd_stat_int_t *si;
+	int error;
+
+	ASSERT(MUTEX_HELD(&fsd_lock));
+
+	error = fsd_get_stati(vfsp, &si, 0, 0);
+	if (error == FSD_ENTRY_NOT_FOUND)
+		return (error);
+
+	list_remove(&fsd_slist, si);
+	fsd_hooks.arg = si;
+	fsh_hook_remove(vfsp, &fsd_hooks);
+
+	rw_destroy(&si->fsdsi_lock);
+	kmem_free(si, sizeof (*si));
+
+	return (0);
+}
+
+static void
+fsd_callback_mount(vfs_t *vfsp, void *arg)
+{
+	_NOTE(ARGUNUSED(arg));
+
+	mutex_enter(&fsd_lock);
+	if (fsd_new_stat != NULL)
+		fsd_install_disturber(vfsp, fsd_new_stat);
+	mutex_exit(&fsd_lock);
+}
+
+static void
+fsd_callback_free(vfs_t *vfsp, void *arg)
+{
+	_NOTE(ARGUNUSED(arg));
+
+	mutex_enter(&fsd_lock);
+	(void) fsd_remove_disturber(vfsp);
+	mutex_exit(&fsd_lock);
+}
+
+static void
+fsd_enable()
+{
+	mutex_enter(&fsd_lock);
+	if (!fsd_enabled)
+		fsh_callback_install(&fsd_callbacks);
+	fsd_enabled = 1;
+	mutex_exit(&fsd_lock);
+}
+
+static void
+fsd_disable()
+{
+	fsd_stat_int_t *si;
+
+	mutex_enter(&fsd_lock);
+	if (fsd_enabled) {
+		fsd_enabled = 0;
+
+		fsh_callback_remove(&fsd_callbacks);
+
+		/* Removing all hooks and fsd_stat_int_t entries. */
+		while (!list_is_empty(&fsd_slist)) {
+			si = list_remove_head(&fsd_slist);
+
+			/*
+			 * After a fsh_hook_remove, we don't have to worry
+			 * about the fsd_stat_int_t. If we hold the fsd_lock,
+			 * nobody would try to use it. It's safe to destroy it.
+			 */
+			fsd_hooks.arg = si;
+			fsh_hook_remove(si->fsdsi_vfsp, &fsd_hooks);
+
+			rw_destroy(&si->fsdsi_lock);
+			kmem_free(si, sizeof (*si));
+		}
+
+		if (fsd_new_stat != NULL) {
+			kmem_free(fsd_new_stat, sizeof (*fsd_new_stat));
+			fsd_new_stat = NULL;
+		}
+	}
+	mutex_exit(&fsd_lock);
+}
+
+static int
+fsd_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
+{
+	minor_t instance;
+
+	if (cmd != DDI_ATTACH)
+		return (DDI_FAILURE);
+
+	if (fsd_devi != NULL)
+		return (DDI_FAILURE);
+
+	instance = ddi_get_instance(dip);
+	if (ddi_create_minor_node(dip, "fsd", S_IFCHR, instance,
+	    DDI_PSEUDO, 0) == DDI_FAILURE)
+		return (DDI_FAILURE);
+	fsd_devi = dip;
+	ddi_report_dev(fsd_devi);
+
+	/* Setting hooks */
+	fsd_hooks.hook_read = fsd_hook_read;
+
+	list_create(&fsd_slist, sizeof (fsd_stat_int_t),
+	    offsetof(fsd_stat_int_t, fsdsi_next));
+
+	fsd_rand_seed = gethrtime();
+
+	/* Setting callbacks */
+	fsd_callbacks.fshc_mount = fsd_callback_mount;
+	fsd_callbacks.fshc_free = fsd_callback_free;
+
+	mutex_init(&fsd_lock, NULL, MUTEX_DRIVER, NULL);
+
+	return (DDI_SUCCESS);
+}
+
+/*
+ * If fsd_enable() was called and there was no subsequent fsd_disable() call,
+ * detach will fail.
+ */
+static int
+fsd_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
+{
+	if (cmd != DDI_DETACH)
+		return (DDI_FAILURE);
+
+	ASSERT(dip == fsd_devi);
+
+	/* No need to hold fsd_lock here */
+	if (fsd_enabled)
+		return (DDI_FAILURE);
+
+	ddi_remove_minor_node(dip, NULL);
+	fsd_devi = NULL;
+
+	list_destroy(&fsd_slist);
+	mutex_destroy(&fsd_lock);
+
+	return (DDI_SUCCESS);
+}
+
+static int
+fsd_getinfo(dev_info_t *dip, ddi_info_cmd_t infocmd, void *arg, void **resultp)
+{
+	_NOTE(ARGUNUSED(dip));
+
+	switch (infocmd) {
+	case DDI_INFO_DEVT2DEVINFO:
+		*resultp = fsd_devi;
+		return (DDI_SUCCESS);
+	case DDI_INFO_DEVT2INSTANCE:
+		*resultp = (void *)(uintptr_t)getminor((dev_t)arg);
+		return (DDI_SUCCESS);
+	default:
+		return (DDI_FAILURE);
+	}
+}
+
+/* TODO: what policy should be checked here? */
+static int
+fsd_open(dev_t *devp, int oflag, int sflag, cred_t *cred_p)
+{
+	_NOTE(ARGUNUSED(devp));
+	_NOTE(ARGUNUSED(oflag));
+	_NOTE(ARGUNUSED(sflag));
+	_NOTE(ARGUNUSED(cred_p));
+
+	return (0);
+}
+
+static int
+fsd_close(dev_t dev, int flag, int otyp, cred_t *cred_p)
+{
+	_NOTE(ARGUNUSED(dev));
+	_NOTE(ARGUNUSED(flag));
+	_NOTE(ARGUNUSED(otyp));
+	_NOTE(ARGUNUSED(cred_p));
+
+	return (0);
+}
+
+static int
+fsd_check_stat(fsd_stat_t *stat)
+{
+	if (stat->fsds_read_less_chance > 100 ||
+	    stat->fsds_read_less_r[0] > stat->fsds_read_less_r[1])
+		return (FSD_BAD_STAT);
+	return (0);
+}
+
+static int
+fsd_ioctl_disturb(void *arg, int mode, int *rvalp)
+{
+	file_t *file;
+	fsd_ioc_stat_t iocs;
+	int rv;
+
+	if (ddi_copyin(arg, &iocs, sizeof (iocs), mode))
+		return (EFAULT);
+
+	if ((rv = fsd_check_stat(&iocs.fsdis_stat))) {
+		*rvalp = rv;
+		return (0);
+	}
+
+	if ((file = getf((int)iocs.fsdis_mnt)) == NULL) {
+		*rvalp = FSD_BAD_FD;
+		return (0);
+	}
+
+	mutex_enter(&fsd_lock);
+	fsd_install_disturber(file->f_vnode->v_vfsp, &iocs.fsdis_stat);
+	mutex_exit(&fsd_lock);
+
+	releasef((int)iocs.fsdis_mnt);
+
+	*rvalp = 0;
+	return (0);
+}
+
+static int
+fsd_ioctl_get_status(void *arg, int mode, int *rvalp)
+{
+	file_t *file;
+	fsd_stat_int_t *si;
+	int error;
+	int64_t fd;
+
+	if (ddi_copyin(arg, &fd, sizeof (fd), mode))
+		return (EFAULT);
+
+	if ((file = getf((int)fd)) == NULL) {
+		*rvalp = FSD_BAD_FD;
+		return (0);
+	}
+
+	mutex_enter(&fsd_lock);
+	error = fsd_get_stati(file->f_vnode->v_vfsp, &si, 1, RW_READER);
+	mutex_exit(&fsd_lock);
+
+	releasef((int)fd);
+
+	if (error == FSD_ENTRY_NOT_FOUND) {
+		*rvalp = FSD_ENTRY_NOT_FOUND;
+		return (0);
+	}
+
+	error = ddi_copyout(&si->fsdsi_stat, (void *)arg,
+	    sizeof (si->fsdsi_stat), mode);
+	rw_exit(&si->fsdsi_lock);
+	if (error)
+		return (EFAULT);
+
+	*rvalp = 0;
+	return (0);
+}
+
+static int
+fsd_ioctl_nodisturb(void *arg, int mode, int *rvalp)
+{
+	file_t *file;
+	int64_t fd;
+
+	if (ddi_copyin((void *)arg, &fd, sizeof (fd), mode))
+		return (EFAULT);
+
+	if ((file = getf((int)fd)) == NULL) {
+		*rvalp = FSD_BAD_FD;
+		return (0);
+	}
+
+	mutex_enter(&fsd_lock);
+	*rvalp = fsd_remove_disturber(file->f_vnode->v_vfsp);
+	releasef((int)fd);
+	mutex_exit(&fsd_lock);
+
+	return (0);
+}
+
+static int
+fsd_ioctl_disturb_new(void *arg, int mode, int *rvalp)
+{
+	fsd_stat_t stat;
+	int rv;
+
+	if (ddi_copyin(arg, &stat, sizeof (stat), mode))
+		return (EFAULT);
+
+	if ((rv = fsd_check_stat(&stat))) {
+		*rvalp = rv;
+		return (0);
+	}
+
+	mutex_enter(&fsd_lock);
+	if (fsd_new_stat == NULL)
+		fsd_new_stat = (fsd_stat_t *)kmem_alloc(sizeof (*fsd_new_stat),
+		    KM_SLEEP);
+	(void) memcpy(fsd_new_stat, &stat, sizeof (*fsd_new_stat));
+	mutex_exit(&fsd_lock);
+
+	*rvalp = 0;
+	return (0);
+}
+
+
+static int
+fsd_ioctl(dev_t dev, int cmd, intptr_t arg, int mode, cred_t *credp,
+	int *rvalp)
+{
+	_NOTE(ARGUNUSED(dev));
+	_NOTE(ARGUNUSED(credp));
+
+	if (!fsd_enabled && cmd != FSD_ENABLE) {
+		*rvalp = FSD_NOT_ENABLED;
+		return (0);
+	}
+
+	switch (cmd) {
+	case FSD_ENABLE:
+		fsd_enable();
+		*rvalp = 0;
+		return (0);
+
+	case FSD_DISABLE:
+		fsd_disable();
+		*rvalp = 0;
+		return (0);
+
+	case FSD_GET_STATUS:
+		return (fsd_ioctl_get_status((void *)arg, mode, rvalp));
+
+	case FSD_DISTURB:
+		return (fsd_ioctl_disturb((void *)arg, mode, rvalp));
+
+	case FSD_NODISTURB:
+		return (fsd_ioctl_nodisturb((void *)arg, mode, rvalp));
+
+	case FSD_DISTURB_NEW:
+		return (fsd_ioctl_disturb_new((void *)arg, mode, rvalp));
+
+	case FSD_NODISTURB_NEW:
+		mutex_enter(&fsd_lock);
+		if (fsd_new_stat != NULL)
+			kmem_free(fsd_new_stat, sizeof (*fsd_new_stat));
+		mutex_exit(&fsd_lock);
+
+		*rvalp = 0;
+		return (0);
+
+	default:
+		return (EINVAL);
+	}
+}
+
+static struct cb_ops cb_ops = {
+	fsd_open,	/* open(9E) */
+	fsd_close,	/* close(9E) */
+	nodev,		/* strategy(9E) */
+	nodev,		/* print(9E) */
+	nodev,		/* dump(9E) */
+	nodev,		/* read(9E) */
+	nodev,		/* write(9E) */
+	fsd_ioctl,	/* ioctl(9E) */
+	nodev,		/* devmap(9E) */
+	nodev,		/* mmap(9E) */
+	nodev,		/* segmap(9E) */
+	nochpoll,	/* chpoll(9E) */
+	ddi_prop_op,	/* prop_op(9E) */
+	NULL,		/* streamtab(9E) */
+	D_MP | D_64BIT,	/* cb_flag(9E) */
+	CB_REV,		/* cb_rev(9E) */
+	nodev,		/* aread(9E) */
+	nodev,		/* awrite(9E) */
+};
+
+static struct dev_ops dev_ops = {
+	DEVO_REV,		/* driver build version */
+	0,			/* reference count */
+	fsd_getinfo,		/* getinfo */
+	nulldev,
+	nulldev,		/* probe */
+	fsd_attach,		/* attach */
+	fsd_detach,		/* detach */
+	nodev,
+	&cb_ops,		/* cb_ops */
+	NULL,			/* bus_ops */
+	NULL,			/* power */
+	ddi_quiesce_not_needed,	/* quiesce */
+};
+
+static struct modldrv modldrv = {
+	&mod_driverops,	"Filesystem disturber", &dev_ops
+};
+
+static struct modlinkage modlinkage = {
+	MODREV_1, &modldrv, NULL
+};
+
+int
+_init(void)
+{
+	return (mod_install(&modlinkage));
+}
+
+int
+_info(struct modinfo *modinfop)
+{
+	return (mod_info(&modlinkage, modinfop));
+}
+
+int
+_fini(void)
+{
+	return (mod_remove(&modlinkage));
+}
