--- old/usr/src/cmd/Makefile	Sun Aug 11 00:54:13 2013
+++ new/usr/src/cmd/Makefile	Sun Aug 11 00:54:13 2013
@@ -168,6 +168,7 @@
 	fold		\
 	format		\
 	fs.d		\
+	fsdadm		\
 	fstyp		\
 	fuser		\
 	fwflash		\
--- /dev/null	Sun Aug 11 00:54:13 2013
+++ new/usr/src/cmd/fsdadm/Makefile	Sun Aug 11 00:54:13 2013
@@ -0,0 +1,48 @@
+#
+# 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.
+#
+
+PROG = fsdadm
+OBJS = fsdadm.o
+SRCS = $(OBJS:%.o=%.c)
+
+FILEMODE = 0555
+
+include ../Makefile.cmd
+
+CLEANFILES += $(OBJS)
+
+CFLAGS += $(CCVERBOSE)
+CFLAG64 += $(CCVERBOSE)
+
+LDLIBS += -lfsd
+
+all: $(PROG)
+
+$(PROG): $(OBJS)
+	$(LINK.c) -o $@ $(OBJS) $(LDLIBS)
+	$(POST_PROCESS)
+
+%.o: %.c
+	$(COMPILE.c) $<
+
+clean:
+	-$(RM) $(CLEANFILES)
+
+lint: lint_PROG
+
+install: all $(ROOTUSRSBINPROG)
+
+include ../Makefile.targ
+
--- /dev/null	Sun Aug 11 00:54:13 2013
+++ new/usr/src/cmd/fsdadm/fsdadm.c	Sun Aug 11 00:54:13 2013
@@ -0,0 +1,132 @@
+/*
+ * 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.
+ */
+
+#include <libfsd.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+/*
+ * Examples of usage:
+ * fsdadm -m /mnt -c 15 -l 1 200	15% read less 1 to 200 for /mnt
+ * fsdadm -n -c 50 -l 1 10	50% read less of 1 to 10ms for every new mount
+ * fsdadm -m /mnt -x		clear all disturbances for /mnt
+ * fsdadm -n -x			clear all disturbances for every new mount
+ */
+
+#define	READ_LESS	0
+#define	OPS_NUM		1
+
+int clear;
+int disable;
+int enable;
+int newmount;
+int options[OPS_NUM];
+
+int
+main(int argc, char *argv[])
+{
+	extern char *optarg;
+	extern int optind;
+
+	char *mnt = NULL;
+	int fd;
+	int opt;
+	int error;
+	fsd_stat_t stat;
+
+	while ((opt = getopt(argc, argv, "edm:c:l:xn")) != -1)
+		switch (opt) {
+		case 'e':
+			enable = 1;
+			break;
+
+		case 'd':
+			disable = 1;
+			break;
+
+		case 'c':
+			stat.fsds_read_less_chance = atoi(optarg);
+			break;
+
+		case 'm':
+			mnt = optarg;
+			break;
+
+		case 'l':
+			options[READ_LESS] = 1;
+			if (optind > argc - 1) {
+				(void) fprintf(stderr,
+				    "Error: -l requires two arguments\n");
+				return (1);
+			}
+			stat.fsds_read_less_r[0] = atoi(argv[optind-1]);
+			stat.fsds_read_less_r[1] = atoi(argv[optind]);
+			break;
+
+		case 'x':
+			clear = 1;
+			break;
+
+		case 'n':
+			newmount = 1;
+			break;
+
+		case '?':
+			(void) fprintf(stderr,
+			    "Error: Unrecognized option: "
+			    "-%c\n", optopt);
+			return (1);
+		}
+
+	fd = fsd_open();
+	if (fd == -1) {
+		(void) fprintf(stderr, "Error: Cannot open fsd device.\n");
+		return (1);
+	}
+
+	error = 0;
+	if (enable) {
+		if (fsd_enable(fd))
+			error = fsd_errno;
+	} else if (disable) {
+		if (fsd_disable(fd))
+			error = fsd_errno;
+	} else {
+		if (newmount) {
+			if (clear) {
+				if (fsd_newdisturb_off(fd))
+					error = fsd_errno;
+			} else {
+				if (fsd_newdisturb(fd, &stat))
+					error = fsd_errno;
+			}
+		} else {
+			if (clear) {
+				if (fsd_disturb_off(fd, mnt))
+					error = fsd_errno;
+			} else {
+				if (fsd_disturb(fd, mnt, &stat))
+					error = fsd_errno;
+			}
+		}
+	}
+
+	fsd_close(fd);
+	if (error) {
+		(void) fprintf(stderr, "Error: %s\n", fsd_strerr(error));
+		return (1);
+	}
+	return (0);
+}
--- old/usr/src/lib/Makefile	Sun Aug 11 00:54:13 2013
+++ new/usr/src/lib/Makefile	Sun Aug 11 00:54:13 2013
@@ -159,7 +159,8 @@
 	librdc		\
 	libinstzones	\
 	libpkg		\
-	libpcidb
+	libpcidb	\
+	libfsd
 
 SUBDIRS += \
 	passwdutil	\
@@ -508,6 +509,7 @@
 	libsun_ima	\
 	mpapi		\
 	libreparse	\
+	libfsd		\
 	$($(MACH)_HDRSUBDIRS)
 
 $(CLOSED_BUILD)HDRSUBDIRS += \
--- /dev/null	Sun Aug 11 00:54:14 2013
+++ new/usr/src/lib/libfsd/Makefile	Sun Aug 11 00:54:14 2013
@@ -0,0 +1,53 @@
+#
+# CDDL HEADER START
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License (the "License").
+# You may not use this file except in compliance with the License.
+#
+# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+# or http://www.opensolaris.org/os/licensing.
+# See the License for the specific language governing permissions
+# and limitations under the License.
+#
+# When distributing Covered Code, include this CDDL HEADER in each
+# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+# If applicable, add the following below this CDDL HEADER, with the
+# fields enclosed by brackets "[]" replaced with your own identifying
+# information: Portions Copyright [yyyy] [name of copyright owner]
+#
+# CDDL HEADER END
+#
+# Copyright 2006 Sun Microsystems, Inc.  All rights reserved.
+# Use is subject to license terms.
+#
+# Copyright 2013 Damian Bogel. All rights reserved.
+#
+
+include ../Makefile.lib
+
+HDRS = libfsd.h
+HDRDIR = common
+SUBDIRS = $(MACH)
+$(BUILD64)SUBDIRS += $(MACH64)
+
+all	:= TARGET= all
+clean	:= TARGET= clean
+clobber	:= TARGET= clobber
+install	:= TARGET= install
+lint	:= TARGET= lint
+
+.KEEP_STATE:
+
+all clean clobber install lint: $(SUBDIRS)
+
+install_h: $(ROOTHDRS)
+
+check: $(CHECKHDRS)
+
+$(SUBDIRS): FRC
+	@cd $@; pwd; $(MAKE) $(TARGET)
+
+FRC:
+
+include ../Makefile.targ
--- /dev/null	Sun Aug 11 00:54:14 2013
+++ new/usr/src/lib/libfsd/Makefile.com	Sun Aug 11 00:54:14 2013
@@ -0,0 +1,47 @@
+#
+# CDDL HEADER START
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License (the "License").
+# You may not use this file except in compliance with the License.
+#
+# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+# or http://www.opensolaris.org/os/licensing.
+# See the License for the specific language governing permissions
+# and limitations under the License.
+#
+# When distributing Covered Code, include this CDDL HEADER in each
+# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+# If applicable, add the following below this CDDL HEADER, with the
+# fields enclosed by brackets "[]" replaced with your own identifying
+# information: Portions Copyright [yyyy] [name of copyright owner]
+#
+# CDDL HEADER END
+#
+# Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
+# Use is subject to license terms.
+#
+# Copyright 2013 Damian Bogel. All rights reserved.
+#
+
+LIBRARY = libfsd.a
+VERS    = .1
+OBJECTS = libfsd.o
+
+include ../../Makefile.lib
+
+SRCDIR =	../common
+LIBS =		$(DYNLIB) $(LINTLIB)
+$(LINTLIB) :=	SRCS = $(SRCDIR)/$(LINTSRC)
+LDLIBS +=	-lc
+
+CFLAGS +=	$(CCVERBOSE)
+CPPFLAGS +=	-I../common
+
+.KEEP_STATE:
+
+all:		$(LIBS)
+
+lint:		lintcheck
+
+include ../../Makefile.targ
--- /dev/null	Sun Aug 11 00:54:14 2013
+++ new/usr/src/lib/libfsd/amd64/Makefile	Sun Aug 11 00:54:14 2013
@@ -0,0 +1,19 @@
+#
+# 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.
+#
+include ../Makefile.com
+include ../../Makefile.lib.64
+
+install: all $(ROOTLIBS64) $(ROOTLINKS64)
+
--- /dev/null	Sun Aug 11 00:54:14 2013
+++ new/usr/src/lib/libfsd/common/libfsd.c	Sun Aug 11 00:54:14 2013
@@ -0,0 +1,176 @@
+/*
+ * 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.
+ */
+
+/*
+ * Library used to drive the fsd pseudo-device driver.
+ */
+
+#include <fcntl.h>
+#include <libfsd.h>
+#include <string.h>
+#include <stropts.h>
+#include <sys/errno.h>
+#include <sys/fsd.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+__thread int fsd_errno;
+
+const char *
+fsd_strerr(int e)
+{
+	switch (e) {
+	case EFSD_NOERROR:
+		return ("no error");
+	case EFSD_BAD_STAT:
+		return ("incorrect disturbance parameters");
+	case EFSD_INTERNAL:
+		return ("internal library error");
+	case EFSD_CANT_OPEN_DRIVER:
+		return ("cannot open fsd device");
+	case EFSD_CANT_OPEN_MOUNTPOINT:
+		return ("cannot open mountpoint");
+	case EFSD_ENTRY_NOT_FOUND:
+		return ("this vfs is not being disturbed");
+	case EFSD_FAULT:
+		return ("bad pointer");
+	case EFSD_UNKNOWN_ERROR:
+	default:
+		return ("unknown error");
+	}
+}
+
+static int
+fsd_ioctl_set_errno(int ioctl_ret)
+{
+	extern int errno;
+	int e;
+
+	switch (ioctl_ret) {
+	case 0:
+		fsd_errno = EFSD_NOERROR;
+		return (0);
+
+	case (-1):
+		e = errno;
+		switch (e) {
+		case 0:
+			fsd_errno = EFSD_NOERROR;
+			break;
+		case EFAULT:
+			fsd_errno = EFSD_FAULT;
+			break;
+		case EINVAL:
+			fsd_errno = EFSD_INTERNAL;
+			break;
+		default:
+			fsd_errno = EFSD_UNKNOWN_ERROR;
+			break;
+		}
+		break;
+	case FSD_ENTRY_NOT_FOUND:
+		fsd_errno = EFSD_ENTRY_NOT_FOUND;
+		break;
+	case FSD_BAD_STAT:
+		fsd_errno = EFSD_BAD_STAT;
+		break;
+	case FSD_BAD_FD:
+		fsd_errno = EFSD_CANT_OPEN_MOUNTPOINT;
+		break;
+	default:
+		fsd_errno = EFSD_UNKNOWN_ERROR;
+		break;
+	}
+
+	return (-1);
+
+}
+
+int
+fsd_open()
+{
+	int fd;
+
+	if ((fd = open(FSD_DEV_PATH, O_RDWR)) == -1) {
+		fsd_errno = EFSD_CANT_OPEN_DRIVER;
+		return (-1);
+	}
+	return (fd);
+}
+
+void
+fsd_close(int fd)
+{
+	(void) close(fd);
+}
+
+int
+fsd_enable(int fd)
+{
+	return (fsd_ioctl_set_errno(ioctl(fd, FSD_ENABLE)));
+}
+
+int
+fsd_disable(int fd)
+{
+	return (fsd_ioctl_set_errno(ioctl(fd, FSD_DISABLE)));
+}
+
+int
+fsd_disturb(int fd, const char *mntpt_path, fsd_stat_t *stat)
+{
+	fsd_ioc_stat_t iocs;
+	int error;
+
+	if ((iocs.fsdis_mnt = open(mntpt_path, O_RDONLY)) == -1) {
+		fsd_errno = EFSD_CANT_OPEN_MOUNTPOINT;
+		return (-1);
+	}
+
+	(void) memcpy(&iocs.fsdis_stat, stat, sizeof (iocs.fsdis_stat));
+
+	error = ioctl(fd, FSD_DISTURB, &iocs);
+	(void) close(iocs.fsdis_mnt);
+	return (fsd_ioctl_set_errno(error));
+}
+
+int
+fsd_disturb_off(int fd, const char *mntpt_path)
+{
+	int error;
+	int64_t mntfd;
+
+	if ((mntfd = open(mntpt_path, O_RDONLY)) == -1) {
+		fsd_errno = EFSD_CANT_OPEN_MOUNTPOINT;
+		return (-1);
+	}
+
+	error = ioctl(fd, FSD_NODISTURB, &mntfd);
+	(void) close(mntfd);
+	return (fsd_ioctl_set_errno(error));
+}
+
+int
+fsd_newdisturb(int fd, fsd_stat_t *stat)
+{
+	return (fsd_ioctl_set_errno(ioctl(fd, FSD_DISTURB_NEW, stat)));
+}
+
+int
+fsd_newdisturb_off(int fd)
+{
+	return (fsd_ioctl_set_errno(ioctl(fd, FSD_NODISTURB_NEW)));
+}
--- /dev/null	Sun Aug 11 00:54:14 2013
+++ new/usr/src/lib/libfsd/common/libfsd.h	Sun Aug 11 00:54:14 2013
@@ -0,0 +1,54 @@
+/*
+ * 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.
+ */
+
+#ifndef _LIBFSD_H
+#define	_LIBFSD_H
+
+#include <sys/fsd.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+extern __thread int fsd_errno;
+
+#define	EFSD_NOERROR			0
+#define	EFSD_BAD_STAT			1
+#define	EFSD_INTERNAL			2
+#define	EFSD_CANT_OPEN_DRIVER		3
+#define	EFSD_CANT_OPEN_MOUNTPOINT	4
+#define	EFSD_ENTRY_NOT_FOUND		5
+#define	EFSD_FAULT			6
+#define	EFSD_UNKNOWN_ERROR		7
+
+extern const char *fsd_strerr(int e);
+
+extern int fsd_open();
+extern void fsd_close(int fd);
+
+extern int fsd_enable(int fd);
+extern int fsd_disable(int fd);
+
+extern int fsd_disturb(int fd, const char *mntpt_path, fsd_stat_t *stat);
+extern int fsd_disturb_off(int fd, const char *mntpt_path);
+
+extern int fsd_newdisturb(int fd, fsd_stat_t *stat);
+extern int fsd_newdisturb_off(int fd);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _LIBFSD_H */
--- /dev/null	Sun Aug 11 00:54:14 2013
+++ new/usr/src/lib/libfsd/common/llib-lfsd	Sun Aug 11 00:54:14 2013
@@ -0,0 +1,19 @@
+/*
+ * 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.
+ */
+
+ /* LINTLIBRARY */
+ /* PROTOLIB1 */
+
+#include <libfsd.h>
--- /dev/null	Sun Aug 11 00:54:14 2013
+++ new/usr/src/lib/libfsd/common/mapfile-vers	Sun Aug 11 00:54:14 2013
@@ -0,0 +1,46 @@
+#
+# 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.
+#
+
+#
+# MAPFILE HEADER START
+#
+# WARNING:  STOP NOW.  DO NOT MODIFY THIS FILE.
+# Object versioning must comply with the rules detailed in
+#
+#	usr/src/lib/README.mapfiles
+#
+# You should not be making modifications here until you've read the most current
+# copy of that file. If you need help, contact a gatekeeper for guidance.
+#
+# MAPFILE HEADER END
+#
+
+$mapfile_version 2
+
+SYMBOL_VERSION ILLUMOSprivate {
+	global:
+		fsd_close;
+		fsd_disable;
+		fsd_disturb;
+		fsd_disturb_off;
+		fsd_enable;
+		fsd_errno;
+		fsd_newdisturb;
+		fsd_newdisturb_off;
+		fsd_open;
+		fsd_strerr;
+	local:
+		*;
+};
--- /dev/null	Sun Aug 11 00:54:14 2013
+++ new/usr/src/lib/libfsd/i386/Makefile	Sun Aug 11 00:54:14 2013
@@ -0,0 +1,18 @@
+#
+# 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.
+#
+include ../Makefile.com
+
+install: all $(ROOTLIBS) $(ROOTLINKS)
+
--- old/usr/src/uts/common/Makefile.files	Sun Aug 11 00:54:14 2013
+++ new/usr/src/uts/common/Makefile.files	Sun Aug 11 00:54:14 2013
@@ -178,6 +178,7 @@
 		flock.o		\
 		fm.o		\
 		fork.o		\
+		fsh.o		\
 		vpm.o		\
 		fs_reparse.o	\
 		fs_subr.o	\
@@ -933,6 +934,8 @@
 PMCS8001FW_C_OBJS +=	pmcs_fw_hdr.o
 PMCS8001FW_OBJS +=		$(PMCS8001FW_C_OBJS) SPCBoot.o ila.o firmware.o
 
+FSD_OBJS +=	fsd.o
+
 #
 #	Build up defines and paths.
 
--- old/usr/src/uts/common/Makefile.rules	Sun Aug 11 00:54:14 2013
+++ new/usr/src/uts/common/Makefile.rules	Sun Aug 11 00:54:14 2013
@@ -729,6 +729,10 @@
 	$(COMPILE.c) -o $@ $<
 	$(CTFCONVERT_O)
 
+$(OBJS_DIR)/%.o:		$(UTSBASE)/common/io/fsd/%.c
+	$(COMPILE.c) -o $@ $<
+	$(CTFCONVERT_O)
+
 $(OBJS_DIR)/%.o:		$(UTSBASE)/common/io/hme/%.c
 	$(COMPILE.c) -o $@ $<
 	$(CTFCONVERT_O)
@@ -2091,6 +2095,9 @@
 $(LINTS_DIR)/%.ln:		$(UTSBASE)/common/io/fcoe/%.c
 	@($(LHEAD) $(LINT.c) $< $(LTAIL))
 
+$(LINTS_DIR)/%.ln:		$(UTSBASE)/common/io/fsd/%.c
+	@($(LHEAD) $(LINT.c) $< $(LTAIL))
+
 $(LINTS_DIR)/%.ln:		$(UTSBASE)/common/io/hme/%.c
 	@($(LHEAD) $(LINT.c) $< $(LTAIL))
 
--- /dev/null	Sun Aug 11 00:54:14 2013
+++ new/usr/src/uts/common/fs/fsh.c	Sun Aug 11 00:54:14 2013
@@ -0,0 +1,678 @@
+/*
+ * 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.
+ */
+
+#include <sys/debug.h>
+#include <sys/fsh.h>
+#include <sys/fsh_impl.h>
+#include <sys/ksynch.h>
+#include <sys/list.h>
+#include <sys/sunddi.h>
+#include <sys/sysmacros.h>
+#include <sys/types.h>
+#include <sys/vfs.h>
+#include <sys/vnode.h>
+
+/*
+ * Filesystem hook framework (FSH)
+ *
+ * 1. Abstract.
+ * The main goal of the filesystem hook framework is to provide an easy way to
+ * inject consumer-defined behaviour into vfs/vnode calls. Because of what
+ * zones and ZFS offer, we narrow our hooking system to whole filesystems, not
+ * single vnodes or filesystem subtrees.
+ *
+ * 2. Overview.
+ * fsh_t is the main object in the FSH. An fsh_t is a structure containing:
+ * 	- pointers to hooking functions (hook_foo, where foo is the name of
+ * 	a corresponding vnodeop/vfsop)
+ * 	- a pointer to an argument to pass (this is shared for all the
+ * 	hooks in a given fsh_t)
+ *
+ * We install a fsh_t on a whole filesystem, but one fsh_t can be installed on
+ * many filesystems.
+ *
+ * 3. Usage.
+ * It is assumed that vfs_t/vnode_t that are passed to fsh_foo() functions are
+ * held by the caller.
+ *
+ * fsh_t is a structure filled out by the consumer. If a consumer does not
+ * want to add/remove a hook for function foo(), he should fill the
+ * hook_foo() field of fsh_t with NULL before calling
+ * fsh_hook_install/remove(). The type of hook_foo() is the type of foo() with
+ * two additional arguments:
+ * 	- fsh_int_t *fsh_int - this argument MUST be passed to
+ *	hook_next_foo(). FSH would't know which hook to execute next
+ * 	without it.
+ * 	- void *arg - this is the argument passed with fsh_t during
+ * 	installation
+ * All the information passed with fsh_t is copied by FSH, so it's safe for
+ * the caller to do anything with it. Keep in mind that this structure is also
+ * used for removing hooks, so the consumer should somehow remember all of
+ * it's contents.
+ *
+ * Every hook function is responsible for passing the control to the next
+ * hook associated with a particular call. In order to provide an easy way to
+ * modify the behaviour of a function call both before and after the
+ * underlying vfsop/vnodeop (or next hook) execution, a hook has to call
+ * fsh_next_foo() at some point. This function does necessary internal
+ * operations and calls the next hook, until there's no hook left, then it
+ * calls the underlying vfsop/vnodeop.
+ * Example:
+ * my_freefs(fsh_int_t *fsh_int, void *arg, vfs_t *vfsp) {
+ * 	cmn_err(CE_NOTE, "freefs called!\n");
+ * 	return (fsh_next_freefs(fsh_int, vfsp));
+ * }
+ *
+ * A consumer might want to receive some notifications about vfs_t entries
+ * being created/destroyed. There's a fsh_callback_t structure provided to
+ * install such callbacks.
+ *
+ * 4. API (fsh.h)
+ * fsh_fs_enable(vfs_t *vfsp)
+ * fsh_fs_disable(vfs_t *vfsp)
+ * 	Enables/disables fshook per filesystem.
+ *
+ * fsh_hook_install(vfs_t *vfsp, fsh_t *fsh)
+ * 	Installs hooks on vfsp filesystem. It's important that hooks are
+ * 	executed in LIFO installation order, which means that if there are
+ * 	hooks A and B installed in this order, B is going to be execute
+ * 	before A.
+ *
+ * fsh_hook_remove(vfs_t *vfsp, fsh_t *fsh)
+ * 	Removes hooks from vfsp filesystem.
+ *
+ * fsh_next_xxx(fsh_node_t *fsh_node, void *arg, ARGUMENTS)
+ * 	This is the function which should be called once in every hook. It
+ * 	does the necessary internal operations and passes control to the
+ * 	next hook or, if there's no hook left, to the underlying
+ * 	vfsop/vnodeop.
+ *
+ * fsh_callback_install(fsh_callback_t *fsh_callback)
+ * fsh_callback_remove(fsh_callback_t *fsh_callback)
+ * 	Installs/remove a callback for vfs_t mount/free. The mount callback
+ * 	is executed right before domount() returns. The free callback is
+ * 	called right before VFS_FREEVFS() is called.
+ *
+ * 5. API for vfs.c and vnode.c (fsh_impl.h)
+ * fsh_init()
+ * 	This call has to be done in vfsinit(). It initialises the FSH. It
+ * 	is absolutely necessary that this call is made before any other FSH
+ * 	operation.
+ *
+ * fsh_exec_mount_callbacks(vfs_t *vfsp)
+ * fsh_exec_free_callbacks(vfs_t *vfsp)
+ * 	Used to execute all FSH callback for vfs_t mount/free.
+ *
+ * fsh_fsrec_destroy(struct fsh_fsrecord *fsrecp)
+ * 	Destroys a fsh_fsrecord structure.
+ *
+ * fsh_foo(ARGUMENTS)
+ * 	Function used to start executing the hook chain for a given call
+ * 	(foo).
+ *
+ * 6. Internals.
+ * fsh_fsrecord_t is a structure which lives inside vfs_t.
+ * fsh_fsrecord_t contains:
+ * 	- an array of fsh_list_t, one for each vfsop/vnodeop. fsh_list_t
+ * 	is just a list of hooks for a particular vfsop/vnodeop
+ * 	- a flag which tells if FSH is enabled on this filesystem
+ *
+ * Unfortunately, because of unexpected behaviour of some filesystems (no
+ * use of vfs_alloc()/vfs_init()) there's no good place to initialise the
+ * fsh_fshrecord structure. The approach being used here is to check if it's
+ * initialised in every call. Because of the fact that no lock could be used
+ * here (the same problem with initialisation), a spinlock is used. This is
+ * explained in more detail in a comment before FSH_PREPARE_FSREC(), a macro
+ * that should be used whenever a vfsp->vfs_fshrecord needs to be fetched.
+ * After doing that, it's completely safe to keep this pointer locally,
+ * because it won't be changed until vfs_free() is called.
+ *
+ * fsh_list_t is a RW locked hook list, designed to contain hooks for one
+ * operation (that's why it's nodes don't contain such information). Every
+ * hook is internally kept as an fsh_int_t structure, which is filled out
+ * using information from a consumer-provided fsh_t. The fsh_node_t is just
+ * this list node containig fsh_int_t. Since fsh_list_t is an RW locked list,
+ * installing and removing hooks may cause delays in filesystem operations.
+ *
+ * fsh_next_xxx()
+ * This function is quite simple. It takes the fsh_int_t passed to this
+ * function and passes control to the next hook or to the underlying
+ * vnodeop/vfsop.
+ *
+ * Callbacks installed with fsh_callback_install/remove() are executed by
+ * calling fsh_exec_mount/free_callbacks() in domount()/vfs_rele() (when
+ * vfs_t's reference count drops to 0).
+ *
+ * 7. Concurrency
+ * FSH does no vfs_t nor vnode_t locking. It is expected that whenever it is
+ * needed, the consumer does that before/after calling FSH API.
+ *
+ */
+
+
+/* Structure used for mount/free callbacks. */
+static fsh_callback_list_t fsh_clist;
+
+/*
+ * A reserved pointer to FSH. It is used because of the method chosen for
+ * solving concurrency issues for vfs_fshrecord. The full explanation
+ * is in the big theory statement at the beginning of this file.
+ * It is initialised in fsh_init().
+ */
+static void *fsh_res_ptr;
+
+static struct fsh_fsrecord *fsh_fsrec_create();
+
+/*
+ * Important note:
+ * Before using this macro, fsh_init() MUST be called. We do that in
+ * vfsinit()@vfs.c.
+ *
+ * One would ask, why isn't the vfsp->vfs_fshrecord initialised when the
+ * vfs_t is created. Unfortunately, some filesystems (e.g. fifofs) does not
+ * call vfs_init() or even vfs_alloc(), It's possible that some unbundled
+ * filesystems could do the same thing. That's why this macro is introduced.
+ * It should be called before any code that needs access to vfs_fshrecord.
+ *
+ * Locking:
+ * There are no locks here, because there's no good place to initialise
+ * the lock. Concurrency issues are solved by using atomic instructions
+ * and a spinlock, which is spinning only once for a given vfs_t. Because
+ * of that, the usage of spinlock isn't bad at all.
+ *
+ * How it works:
+ * a) if vfsp->vfs_fshrecord is NULL, atomic_cas_ptr changes it to
+ *	fsh_res_ptr. That's a signal for other calls, that the structure
+ *	is being initialised. Then the creation of vfs_fshrecord is done.
+ * b) if vfsp->vfs_fshrecord is fsh_res_ptr, that means we have to wait,
+ *	because vfs_fshrecord is being initialised by another call.
+ * c) other cases:
+ *	vfs_fshrecord is already initialised, so we can use it.
+ */
+#define	FSH_PREPARE_FSREC(vfsp)						\
+do {									\
+	fsh_fsrecord_t *fsrec;						\
+									\
+	while ((fsrec = atomic_cas_ptr(&(vfsp)->vfs_fshrecord, NULL,	\
+		fsh_res_ptr)) == fsh_res_ptr);				\
+	if ((fsrec) == NULL) {						\
+		atomic_swap_ptr(&(vfsp)->vfs_fshrecord,			\
+			fsh_fsrec_create());				\
+	}								\
+_NOTE(CONSTCOND)							\
+} while (0)
+
+/*
+ * API for enabling/disabling FSH per vfs_t.
+ * Atomic operations are used for changing vfs_fshrecord->fshfsr_enabled.
+ */
+void
+fsh_fs_enable(vfs_t *vfsp)
+{
+	FSH_PREPARE_FSREC(vfsp);
+	atomic_or_uint(&vfsp->vfs_fshrecord->fshfsr_enabled, 1);
+}
+
+void
+fsh_fs_disable(vfs_t *vfsp)
+{
+	FSH_PREPARE_FSREC(vfsp);
+	atomic_and_uint(&vfsp->vfs_fshrecord->fshfsr_enabled, 0);
+}
+
+/*
+ * This macro, like FSH_REMOVE is introduced because of the fact that
+ * the code for installing a hook looks almost exactly the same for
+ * every vop/vfsop.
+ * The usage of these macros is pretty simple. One has to provide pointers to
+ * fsh_t, fsh_fsrecord_t and a name of operation to be installed both in
+ * lowercase and uppercase.
+ */
+#define	FSH_INSTALL(type, hooks, fsrec, lower, upper)			\
+do {									\
+	fsh_int_t *fshi;						\
+	fsh_list_t *list;						\
+									\
+	if (hooks->hook_##lower) {					\
+		fshi = (fsh_int_t *)kmem_alloc(sizeof (*fshi),		\
+		    KM_SLEEP);						\
+		fshi->fshi_fn.hook_##lower = hooks->hook_##lower;	\
+		fshi->fshi_arg = hooks->arg;				\
+									\
+		list = &fsrec->fshfsr_opv[FSH_##type##_##upper];	\
+		rw_enter(&list->fshl_lock, RW_WRITER);			\
+		list_insert_head(&list->fshl_list, fshi);		\
+		rw_exit(&list->fshl_lock);				\
+	}								\
+_NOTE(CONSTCOND)							\
+} while (0)
+
+#define	FSH_INSTALL_VN(hooks, fsrec, lower, upper)			\
+	FSH_INSTALL(VOP, hooks, fsrec, lower, upper)
+
+#define	FSH_INSTALL_VFS(hooks, fsrec, lower, upper)			\
+	FSH_INSTALL(VFS, hooks, fsrec, lower, upper)
+
+void
+fsh_hook_install(vfs_t *vfsp, fsh_t *hooks)
+{
+	fsh_fsrecord_t	*fsrec;
+
+	FSH_PREPARE_FSREC(vfsp);
+	fsrec = vfsp->vfs_fshrecord;
+
+	FSH_INSTALL_VN(hooks, fsrec, read, READ);
+	FSH_INSTALL_VN(hooks, fsrec, write, WRITE);
+	FSH_INSTALL_VFS(hooks, fsrec, mount, MOUNT);
+	FSH_INSTALL_VFS(hooks, fsrec, unmount, UNMOUNT);
+}
+
+/*
+ * See comment above FSH_INSTALL.
+ * Note: This macro does nothing when a hook to remove was not found.
+ */
+#define	FSH_REMOVE(type, hooks, fsrec, lower, upper)			\
+do {									\
+	fsh_int_t *fshi;						\
+	fsh_list_t *list;						\
+	if (hooks->hook_##lower == NULL)				\
+		break;							\
+									\
+	list = &fsrec->fshfsr_opv[FSH_##type##_##upper];		\
+	rw_enter(&list->fshl_lock, RW_WRITER);				\
+									\
+	fshi = list_head(&list->fshl_list);				\
+									\
+	while (fshi && 							\
+	    !(fshi->fshi_fn.hook_##lower == hooks->hook_##lower &&	\
+	    fshi->fshi_arg == hooks->arg))				\
+		fshi = list_next(&list->fshl_list, fshi);		\
+									\
+	if (fshi == NULL) {						\
+		rw_exit(&list->fshl_lock);				\
+		break;							\
+	}								\
+									\
+	list_remove(&list->fshl_list, fshi);				\
+									\
+	rw_exit(&list->fshl_lock);					\
+									\
+	kmem_free(fshi, sizeof (*fshi));				\
+_NOTE(CONSTCOND)							\
+} while (0)
+
+#define	FSH_REMOVE_VN(hooks, fsrec, lower, upper)			\
+	FSH_REMOVE(VOP, hooks, fsrec, lower, upper)
+
+#define	FSH_REMOVE_VFS(hooks, fsrec, lower, upper)			\
+	FSH_REMOVE(VFS, hooks, fsrec, lower, upper)
+
+void
+fsh_hook_remove(vfs_t *vfsp, fsh_t *hooks)
+{
+	fsh_fsrecord_t	*fsrec;
+
+	FSH_PREPARE_FSREC(vfsp);
+	fsrec = vfsp->vfs_fshrecord;
+
+	FSH_REMOVE_VN(hooks, fsrec, read, READ);
+	FSH_REMOVE_VN(hooks, fsrec, write, WRITE);
+	FSH_REMOVE_VFS(hooks, fsrec, mount, MOUNT);
+	FSH_REMOVE_VFS(hooks, fsrec, unmount, UNMOUNT);
+}
+
+/*
+ * API for installing/removing global mount/free callbacks.
+ * It's safe to call these functions whenever after fsh_init() was called.
+ * The fsh_clist is rwlocked. fsh_callback_install/remove() are
+ * the only writers and fsh_exec_mount/free_callbacks() are the only readers.
+ */
+void
+fsh_callback_install(fsh_callback_t *fsh_callback)
+{
+	fsh_callback_int_t *fshci;
+
+	fshci = (fsh_callback_int_t *)kmem_alloc(sizeof (*fshci), KM_SLEEP);
+	(void) memcpy(&fshci->fshci_callback, fsh_callback,
+	    sizeof (fshci->fshci_callback));
+
+	rw_enter(&fsh_clist.fshcl_lock, RW_WRITER);
+	list_insert_head(&fsh_clist.fshcl_list, fshci);
+	rw_exit(&fsh_clist.fshcl_lock);
+}
+
+/*
+ * fsh_callback_t objects are compared by a simple memcmp() here. That's
+ * by design, because we'd like to be absolutely sure that we delete the
+ * callbacks we wanted (the same argument and callback functions).
+ */
+void
+fsh_callback_remove(fsh_callback_t *fsh_callback)
+{
+	fsh_callback_int_t *fshci;
+
+	rw_enter(&fsh_clist.fshcl_lock, RW_WRITER);
+	fshci = list_head(&fsh_clist.fshcl_list);
+
+	while (fshci && memcmp(fsh_callback, &fshci->fshci_callback,
+	    sizeof (*fsh_callback)))
+		fshci = list_next(&fsh_clist.fshcl_list, fshci);
+
+	if (fshci == NULL) {
+		rw_exit(&fsh_clist.fshcl_lock);
+		return;
+	}
+
+	list_remove(&fsh_clist.fshcl_list, fshci);
+	rw_exit(&fsh_clist.fshcl_lock);
+
+	kmem_free(fshci, sizeof (*fshci));
+}
+
+/*
+ * This function is executed right before returning from domount()@vfs.c.
+ * We are sure that it's called only after fsh_init().
+ * It does all the mount callbacks installed in the FSH.
+ */
+void
+fsh_exec_mount_callbacks(vfs_t *vfsp)
+{
+	fsh_callback_int_t *fshci;
+	fsh_callback_t *callback;
+
+	rw_enter(&fsh_clist.fshcl_lock, RW_READER);
+	fshci = list_head(&fsh_clist.fshcl_list);
+	while (fshci) {
+		callback = &fshci->fshci_callback;
+		if (callback->fshc_mount)
+			(*(callback->fshc_mount))(vfsp, callback->fshc_arg);
+		fshci = list_next(&fsh_clist.fshcl_list, fshci);
+	}
+	rw_exit(&fsh_clist.fshcl_lock);
+}
+
+/*
+ * This function is executed right before VFS_FREEVFS() is called in
+ * vfs_rele()@vfs.c. We are sure that it's called only after fsh_init().
+ * It does all the free callbacks installed in the FSH.
+ */
+void
+fsh_exec_free_callbacks(vfs_t *vfsp)
+{
+	fsh_callback_int_t *fshci;
+	fsh_callback_t *callback;
+
+	rw_enter(&fsh_clist.fshcl_lock, RW_READER);
+	fshci = list_head(&fsh_clist.fshcl_list);
+	while (fshci) {
+		callback = &fshci->fshci_callback;
+		if (callback->fshc_free)
+			(*(callback->fshc_free))(vfsp, callback->fshc_arg);
+		fshci = list_next(&fsh_clist.fshcl_list, fshci);
+	}
+	rw_exit(&fsh_clist.fshcl_lock);
+}
+
+/*
+ * API for vnode.c/vfs.c to start executing the FSH for a given operation.
+ *
+ * These interfaces are using fsh_res_ptr (in FSH_PREPARE_FSREC()), so it's
+ * absolutely necessary to call fsh_init() before using them. That's done in
+ * vfsinit().
+ *
+ * While these functions are executing, it's expected that necessary vfs_t's
+ * are held so that vfs_free() isn't called. vfs_free() expects that noone
+ * else accesses vfs_fshrecord of a given vfs_t.
+ * It's also the caller responsibility to keep vnode_t passed to fsh_xxx()
+ * alive and valid.
+ * All these expectations are met because these functions are used only in
+ * correspondng fop/fsop_xxx().
+ *
+ * Although fsrec->fshfsr_enabled is shared by everyone who uses a given
+ * vfs_t, it can be accessed in the usual way. There's no locking involved
+ * because all the changes of this field are made by atomic operations in
+ * fsh_fs_enable/disable().
+ */
+int
+fsh_read(vnode_t *vp, uio_t *uiop, int ioflag, cred_t *cr,
+	caller_context_t *ct)
+{
+	fsh_fsrecord_t *fsrec;
+	fsh_list_t *list;
+	int ret;
+
+	FSH_PREPARE_FSREC(vp->v_vfsp);
+	fsrec = vp->v_vfsp->vfs_fshrecord;
+	if (!(fsrec->fshfsr_enabled))
+		return ((*(vp->v_op->vop_read))(vp, uiop, ioflag, cr, ct));
+
+	list = &fsrec->fshfsr_opv[FSH_VOP_READ];
+	rw_enter(&list->fshl_lock, RW_READER);
+	if (list_head(&list->fshl_list) == NULL)
+		ret =  (*(vp->v_op->vop_read))(vp, uiop, ioflag, cr, ct);
+	else
+		ret = fsh_next_read(list_head(&list->fshl_list), vp, uiop,
+		    ioflag, cr, ct);
+	rw_exit(&list->fshl_lock);
+
+	return (ret);
+}
+
+int
+fsh_write(vnode_t *vp, uio_t *uiop, int ioflag, cred_t *cr,
+	caller_context_t *ct)
+{
+	fsh_fsrecord_t *fsrec;
+	fsh_list_t *list;
+	int ret;
+
+	FSH_PREPARE_FSREC(vp->v_vfsp);
+	fsrec = vp->v_vfsp->vfs_fshrecord;
+	if (!(fsrec->fshfsr_enabled))
+		return ((*(vp->v_op->vop_write))(vp, uiop, ioflag, cr, ct));
+
+	list = &fsrec->fshfsr_opv[FSH_VOP_WRITE];
+	rw_enter(&list->fshl_lock, RW_READER);
+	if (list_head(&list->fshl_list) == NULL)
+		ret =  (*(vp->v_op->vop_write))(vp, uiop, ioflag, cr, ct);
+	else
+		ret = fsh_next_write(list_head(&list->fshl_list), vp, uiop,
+		    ioflag, cr, ct);
+	rw_exit(&list->fshl_lock);
+
+	return (ret);
+}
+
+int
+fsh_mount(vfs_t *vfsp, vnode_t *mvp, struct mounta *uap, cred_t *cr)
+{
+	fsh_fsrecord_t *fsrec;
+	fsh_list_t *list;
+	int ret;
+
+	FSH_PREPARE_FSREC(vfsp);
+	fsrec = vfsp->vfs_fshrecord;
+	if (!(fsrec->fshfsr_enabled))
+		return ((*(vfsp->vfs_op->vfs_mount))(vfsp, mvp, uap, cr));
+
+	list = &fsrec->fshfsr_opv[FSH_VFS_MOUNT];
+	rw_enter(&list->fshl_lock, RW_READER);
+	if (list_head(&list->fshl_list) == NULL)
+		ret = (*(vfsp->vfs_op->vfs_mount))(vfsp, mvp, uap, cr);
+	else
+		ret = fsh_next_mount(list_head(&list->fshl_list), vfsp, mvp,
+		    uap, cr);
+	rw_exit(&list->fshl_lock);
+
+	return (ret);
+}
+
+int
+fsh_unmount(vfs_t *vfsp, int flag, cred_t *cr)
+{
+	fsh_fsrecord_t *fsrec;
+	fsh_list_t *list;
+	int ret;
+
+	FSH_PREPARE_FSREC(vfsp);
+	fsrec = vfsp->vfs_fshrecord;
+	if (!(fsrec->fshfsr_enabled))
+		return ((*(vfsp->vfs_op->vfs_unmount))(vfsp, flag, cr));
+
+	list = &fsrec->fshfsr_opv[FSH_VFS_UNMOUNT];
+	rw_enter(&list->fshl_lock, RW_READER);
+	if (list_head(&list->fshl_list) == NULL)
+		ret = (*(vfsp->vfs_op->vfs_unmount))(vfsp, flag, cr);
+	else
+		ret = fsh_next_unmount(list_head(&list->fshl_list), vfsp,
+		    flag, cr);
+	rw_exit(&list->fshl_lock);
+
+	return (ret);
+}
+
+/*
+ * This is the funtion used by FSH_PREPARE_FSREC() to allocate a new
+ * fsh_fsrecord. This function is called by the first function which
+ * access the vfs_fshrecord and finds out it's NULL.
+ */
+static struct fsh_fsrecord *
+fsh_fsrec_create()
+{
+	struct fsh_fsrecord *fsrecp;
+	int i;
+
+	fsrecp = (struct fsh_fsrecord *)kmem_zalloc(sizeof (*fsrecp),
+	    KM_SLEEP);
+	fsrecp->fshfsr_enabled = 1;
+	for (i = 0; i < FSH_SUPPORTED_OPS_COUNT; i++) {
+		rw_init(&fsrecp->fshfsr_opv[i].fshl_lock, NULL, RW_DRIVER,
+		    NULL);
+		list_create(&fsrecp->fshfsr_opv[i].fshl_list,
+		    sizeof (fsh_int_t), offsetof(fsh_int_t, fshi_next));
+	}
+	return (fsrecp);
+}
+
+/*
+ * This call can be used ONLY in vfs_free(). It's assumed that no other
+ * fsh_xxx() using the vfs_t that owns the fsh_fsrecord to be destroyed
+ * is executing while call to fsh_fsrec_destroy() is made. With this
+ * assumptions, no concurrency issues occur. All rwlocks of fshfsr_opv
+ * elements are released.
+ */
+void
+fsh_fsrec_destroy(struct fsh_fsrecord *volatile fsrecp)
+{
+	int i;
+	fsh_int_t *fshi;
+
+	for (i = 0; i < FSH_SUPPORTED_OPS_COUNT; i++) {
+		while (fshi = list_head(&fsrecp->fshfsr_opv[i].fshl_list)) {
+			fshi =
+			    list_remove_head(&fsrecp->fshfsr_opv[i].fshl_list);
+			kmem_free(fshi, sizeof (*fshi));
+		}
+		list_destroy(&fsrecp->fshfsr_opv[i].fshl_list);
+		rw_destroy(&fsrecp->fshfsr_opv[i].fshl_lock);
+	}
+	kmem_free(fsrecp, sizeof (*fsrecp));
+}
+
+/*
+ * fsh_init() is called in vfsinit()@vfs.c. This function MUST be called
+ * before every other FSH call.
+ */
+void
+fsh_init(void)
+{
+	rw_init(&fsh_clist.fshcl_lock, NULL, RW_DRIVER, NULL);
+	list_create(&fsh_clist.fshcl_list, sizeof (fsh_callback_int_t),
+	    offsetof(fsh_callback_int_t, fshci_next));
+
+	fsh_res_ptr = kmem_alloc(1, KM_SLEEP);
+}
+
+/*
+ * These functions are used to pass control to the next hook or underlying
+ * vop/vfsop. It's consumer doesn't have to worry about any locking, because
+ * all the necessities are guaranteed by the fsh_xxx (xxx == open, read etc.)
+ */
+int
+fsh_next_read(fsh_int_t *fshi, vnode_t *vp, uio_t *uiop, int ioflag,
+	cred_t *cr, caller_context_t *ct)
+{
+	fsh_int_t *next;
+
+	if (fshi == NULL)
+		return ((*(vp->v_op->vop_read))(vp, uiop, ioflag, cr, ct));
+
+	next = list_next(
+	    &vp->v_vfsp->vfs_fshrecord->fshfsr_opv[FSH_VOP_READ].fshl_list,
+	    fshi);
+
+	return ((*(fshi->fshi_fn.hook_read))(next, fshi->fshi_arg, vp, uiop,
+	    ioflag, cr, ct));
+}
+
+int
+fsh_next_write(fsh_int_t *fshi, vnode_t *vp, uio_t *uiop, int ioflag,
+	cred_t *cr, caller_context_t *ct)
+{
+	fsh_int_t *next;
+
+	if (fshi == NULL)
+		return ((*(vp->v_op->vop_write))(vp, uiop, ioflag, cr, ct));
+
+	next = list_next(
+	    &vp->v_vfsp->vfs_fshrecord->fshfsr_opv[FSH_VOP_WRITE].fshl_list,
+	    fshi);
+
+	return ((*(fshi->fshi_fn.hook_write))(next, fshi->fshi_arg, vp,
+	    uiop, ioflag, cr, ct));
+}
+
+int
+fsh_next_mount(fsh_int_t *fshi, vfs_t *vfsp, vnode_t *mvp, struct mounta *uap,
+	cred_t *cr)
+{
+	fsh_int_t *next;
+
+	if (fshi == NULL)
+		return ((*(vfsp->vfs_op->vfs_mount))(vfsp, mvp, uap, cr));
+
+	next = list_next(
+	    &vfsp->vfs_fshrecord->fshfsr_opv[FSH_VFS_MOUNT].fshl_list,
+	    fshi);
+
+	return ((*(fshi->fshi_fn.hook_mount))(next, fshi->fshi_arg, vfsp, mvp,
+	    uap, cr));
+}
+
+int
+fsh_next_unmount(fsh_int_t *fshi, vfs_t *vfsp, int flag, cred_t *cr)
+{
+	fsh_int_t *next;
+
+	if (fshi == NULL)
+		return ((*(vfsp->vfs_op->vfs_unmount))(vfsp, flag, cr));
+
+	next = list_next(
+	    &vfsp->vfs_fshrecord->fshfsr_opv[FSH_VFS_UNMOUNT].fshl_list,
+	    fshi);
+
+	return ((*(fshi->fshi_fn.hook_unmount))(next, fshi->fshi_arg, vfsp,
+	    flag, cr));
+}
--- old/usr/src/uts/common/fs/vfs.c	Sun Aug 11 00:54:15 2013
+++ new/usr/src/uts/common/fs/vfs.c	Sun Aug 11 00:54:15 2013
@@ -84,6 +84,8 @@
 #include <sys/spa.h>
 #include <sys/lofi.h>
 #include <sys/bootprops.h>
+#include <sys/fsh.h>
+#include <sys/fsh_impl.h>
 
 #include <vm/page.h>
 
@@ -216,13 +218,13 @@
 int
 fsop_mount(vfs_t *vfsp, vnode_t *mvp, struct mounta *uap, cred_t *cr)
 {
-	return (*(vfsp)->vfs_op->vfs_mount)(vfsp, mvp, uap, cr);
+	return (fsh_mount(vfsp, mvp, uap, cr));
 }
 
 int
 fsop_unmount(vfs_t *vfsp, int flag, cred_t *cr)
 {
-	return (*(vfsp)->vfs_op->vfs_unmount)(vfsp, flag, cr);
+	return (fsh_unmount(vfsp, flag, cr));
 }
 
 int
@@ -1785,6 +1787,7 @@
 		}
 		/* Return vfsp to caller. */
 		*vfspp = vfsp;
+		fsh_exec_mount_callbacks(vfsp);
 	}
 errout:
 	vfs_freeopttbl(&mnt_mntopts);
@@ -4235,6 +4238,9 @@
 	/* Setup event monitor framework */
 	fem_init();
 
+	/* Setup filesystem hook framework */
+	fsh_init();
+
 	/* Initialize the dummy stray file system type. */
 	error = vfs_setfsops(0, stray_vfsops_template, NULL);
 
@@ -4319,6 +4325,13 @@
 		vfsp->vfs_femhead = NULL;
 	}
 
+	/*
+	 * FSH cleanup
+	 * There's no need here to use atomic operations on vfs_fshrecord.
+	 */
+	fsh_fsrec_destroy(vfsp->vfs_fshrecord);
+	vfsp->vfs_fshrecord = NULL;
+
 	if (vfsp->vfs_implp)
 		vfsimpl_teardown(vfsp);
 	sema_destroy(&vfsp->vfs_reflock);
@@ -4345,6 +4358,7 @@
 {
 	ASSERT(vfsp->vfs_count != 0);
 	if (atomic_add_32_nv(&vfsp->vfs_count, -1) == 0) {
+		fsh_exec_free_callbacks(vfsp);
 		VFS_FREEVFS(vfsp);
 		lofi_remove(vfsp);
 		if (vfsp->vfs_zone)
--- old/usr/src/uts/common/fs/vnode.c	Sun Aug 11 00:54:15 2013
+++ new/usr/src/uts/common/fs/vnode.c	Sun Aug 11 00:54:15 2013
@@ -65,6 +65,7 @@
 #include <fs/fs_subr.h>
 #include <sys/taskq.h>
 #include <fs/fs_reparse.h>
+#include <sys/fsh_impl.h>
 
 /* Determine if this vnode is a file that is read-only */
 #define	ISROFILE(vp)	\
@@ -3225,7 +3226,7 @@
 
 	VOPXID_MAP_CR(vp, cr);
 
-	err = (*(vp)->v_op->vop_read)(vp, uiop, ioflag, cr, ct);
+	err = fsh_read(vp, uiop, ioflag, cr, ct);
 	VOPSTATS_UPDATE_IO(vp, read,
 	    read_bytes, (resid_start - uiop->uio_resid));
 	return (err);
@@ -3244,7 +3245,7 @@
 
 	VOPXID_MAP_CR(vp, cr);
 
-	err = (*(vp)->v_op->vop_write)(vp, uiop, ioflag, cr, ct);
+	err = fsh_write(vp, uiop, ioflag, cr, ct);
 	VOPSTATS_UPDATE_IO(vp, write,
 	    write_bytes, (resid_start - uiop->uio_resid));
 	return (err);
--- /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));
+}
--- /dev/null	Sun Aug 11 00:54:15 2013
+++ new/usr/src/uts/common/io/fsd/fsd.conf	Sun Aug 11 00:54:15 2013
@@ -0,0 +1,25 @@
+#
+# CDDL HEADER START
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License, Version 1.0 only
+# (the "License"). You may not use this file except in compliance
+# with the License.
+#
+# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+# or http://www.opensolaris.org/os/licensing.
+# See the License for the specific language governing permissions
+# and limitations under the License.
+#
+# When distributing Covered Code, include this CDDL HEADER in each
+# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+# If applicable, add the following below this CDDL HEADER, with the
+# fields enclosed by brackets "[]" replaced with your own identifying
+# information: Portions Copyright [yyyy] [name of copyright owner]
+#
+# CDDL HEADER END
+#
+#
+# Copyright 2013 Damian Bogel. All rights reserved.
+#
+name="fsd" parent="pseudo" instance=0;
--- old/usr/src/uts/common/sys/Makefile	Sun Aug 11 00:54:15 2013
+++ new/usr/src/uts/common/sys/Makefile	Sun Aug 11 00:54:15 2013
@@ -241,6 +241,9 @@
 	flock.h			\
 	flock_impl.h		\
 	fork.h			\
+	fsd.h			\
+	fsh.h			\
+	fsh_impl.h		\
 	fss.h			\
 	fsspriocntl.h		\
 	fsid.h			\
--- /dev/null	Sun Aug 11 00:54:15 2013
+++ new/usr/src/uts/common/sys/fsd.h	Sun Aug 11 00:54:15 2013
@@ -0,0 +1,80 @@
+/*
+ * 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.
+ */
+
+#ifndef _FSD_H
+#define	_FSD_H
+
+/*
+ * TODO: Describe the purpose of the file here.
+ */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <sys/ksynch.h>
+#include <sys/list.h>
+#include <sys/types.h>
+#include <sys/vfs.h>
+
+
+#define	FSD_DEV_PATH	"/dev/fsd"
+
+/*
+ * Commands for ioctl().
+ */
+#define	FSDIOC	(('f' << 24) | ('s' << 16) | ('d' << 8))
+#define	FSD_GET_STATUS		(FSDIOC | 1)
+#define	FSD_ENABLE		(FSDIOC | 2)
+#define	FSD_DISABLE		(FSDIOC | 3)
+#define	FSD_DISTURB		(FSDIOC | 4)
+#define	FSD_NODISTURB		(FSDIOC | 5)
+#define	FSD_DISTURB_NEW		(FSDIOC | 6)
+#define	FSD_NODISTURB_NEW	(FSDIOC | 7)
+
+#define	FSD_ENTRY_NOT_FOUND	1
+#define	FSD_BAD_STAT		2
+#define	FSD_BAD_FD		3
+#define	FSD_NOT_ENABLED		4
+
+typedef struct fsd_stat {
+	uint64_t	fsds_read_less_chance;
+	uint64_t	fsds_read_less_r[2];	/* range */
+} fsd_stat_t;
+
+
+typedef struct fsd_ioc_stat {
+	int64_t		fsdis_mnt;
+	fsd_stat_t	fsdis_stat;
+} fsd_ioc_stat_t;
+
+/*
+ * Once a set of hooks is installed on a filesystem, there's no need
+ * to bother fsh if we want to change the parameters of disturbance.
+ * Intead, we use fsdsi_lock to protect the fsd_stat_int_t when it's being
+ * used and changed.
+ */
+typedef struct fsd_stat_int {
+	krwlock_t	fsdsi_lock; /* protects fsdsi_stat */
+	vfs_t		*fsdsi_vfsp;
+	fsd_stat_t	fsdsi_stat;
+	list_node_t	fsdsi_next;
+} fsd_stat_int_t;
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _FSD_H */
--- /dev/null	Sun Aug 11 00:54:16 2013
+++ new/usr/src/uts/common/sys/fsh.h	Sun Aug 11 00:54:16 2013
@@ -0,0 +1,78 @@
+/*
+ * 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.
+ */
+
+#ifndef _FSH_H
+#define	_FSH_H
+
+#include <sys/types.h>
+#include <sys/vfs.h>
+#include <sys/vnode.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct fsh_int;
+typedef struct fsh_int fsh_int_t;
+
+#define	FSH_OPS								\
+/* vnode */								\
+int (*hook_read)(fsh_int_t *fsh_int, void *arg, vnode_t *vp,		\
+	uio_t *uiop, int ioflag, cred_t *cr, caller_context_t *ct);	\
+int (*hook_write)(fsh_int_t *fsh_int, void *arg, vnode_t *vp,		\
+	uio_t *uiop, int ioflag, cred_t *cr, caller_context_t *ct);	\
+/* vfs */								\
+int (*hook_mount)(fsh_int_t *fsh_int, void *arg, vfs_t *vfsp,		\
+	vnode_t *mvp, struct mounta *uap, cred_t *cr);			\
+int (*hook_unmount)(fsh_int_t *fsh_int, void *arg, vfs_t *vfsp,		\
+	int flag, cred_t *cr)						\
+
+/* API */
+typedef struct fsh {
+	void	*arg;
+	FSH_OPS;
+} fsh_t;
+
+extern void fsh_hook_install(vfs_t *vfsp, fsh_t *hooks);
+extern void fsh_hook_remove(vfs_t *vfsp, fsh_t *hooks);
+
+typedef struct fsh_callback {
+	void	*fshc_arg;
+	void	(*fshc_free)(vfs_t *vfsp, void *arg);
+	void	(*fshc_mount)(vfs_t *vfsp, void *arg);
+} fsh_callback_t;
+
+extern void fsh_callback_install(fsh_callback_t *fsh_callback);
+extern void fsh_callback_remove(fsh_callback_t *fsh_callback);
+
+extern void fsh_fs_enable(vfs_t *vfsp);
+extern void fsh_fs_disable(vfs_t *vfsp);
+
+/* FSH control passing */
+extern int fsh_next_read(fsh_int_t *fsh_int, vnode_t *vp,
+		uio_t *uiop, int ioflag, cred_t *cr, caller_context_t *ct);
+extern int fsh_next_write(fsh_int_t *fsh_int, vnode_t *vp,
+		uio_t *uiop, int ioflag, cred_t *cr, caller_context_t *ct);
+
+extern int fsh_next_mount(fsh_int_t *fsh_int, vfs_t *vfsp,
+		vnode_t *mvp, struct mounta *uap, cred_t *cr);
+extern int fsh_next_unmount(fsh_int_t *fsh_int, vfs_t *vfsp,
+		int flag, cred_t *cr);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _FSH_H */
--- /dev/null	Sun Aug 11 00:54:16 2013
+++ new/usr/src/uts/common/sys/fsh_impl.h	Sun Aug 11 00:54:16 2013
@@ -0,0 +1,121 @@
+/*
+ * 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.
+ */
+
+/*
+ * This file includes all the necessary declarations for fsh.c, vfs.c
+ * and vnode.c.
+ */
+
+#ifndef _FSH_IMPL_H
+#define	_FSH_IMPL_H
+
+#include <sys/atomic.h>
+#include <sys/fsh.h>
+#include <sys/list.h>
+#include <sys/pathname.h>
+#include <sys/types.h>
+#include <sys/vfs.h>
+#include <sys/vnode.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define	FSH_VFS_MOUNT		0
+#define	FSH_VFS_UNMOUNT		1
+
+#define	FSH_VOP_READ		2
+#define	FSH_VOP_WRITE		3
+
+#define	FSH_SUPPORTED_OPS_COUNT	4
+
+/*
+ * TODO:
+ * - change all defined lists to the ones used in illumos kernel
+ * - add comments
+ */
+
+typedef union fsh_fn {
+	FSH_OPS;
+} fsh_fn_t;
+
+/*
+ * fsh_int_t is the internal fsh_t structure. Instead of using a big
+ * structure containing hooks for all the operations, fsh_int_t contains
+ * a pointer (in union fsh_fn_t) to one function. Thanks to this approach,
+ * we don't have to manipulate such big structures. Instead, we keep
+ * a fsh_int_t list for every operation.
+ */
+struct fsh_int {
+	fsh_fn_t	fshi_fn;
+	void		*fshi_arg;
+	list_node_t	fshi_next;
+};
+/* typedef struct fsh_int fsh_int_t; in fsh.h */
+
+/*
+ * fshl_lock is read-locked by every call to a fsh_vop/vfsop() for
+ * entire execution. This way, we guarantee that a list of hooks is unchanged
+ * during one fop_foo()/fsop_foo() execution.
+ */
+typedef struct fsh_list {
+	krwlock_t	fshl_lock;
+	list_t		fshl_list;
+} fsh_list_t;
+
+/*
+ * fshfsr_enabled informs whether FSH is enabled for this vfs_t.
+ * There are no locks involved. This field should be change only via
+ * atomic operations.
+ */
+typedef struct fsh_fsrecord {
+	volatile uint_t	fshfsr_enabled;
+	fsh_list_t	fshfsr_opv[FSH_SUPPORTED_OPS_COUNT];
+} fsh_fsrecord_t;
+
+typedef struct fsh_callback_int {
+	fsh_callback_t	fshci_callback;
+	list_node_t	fshci_next;
+} fsh_callback_int_t;
+
+typedef struct fsh_callback_list {
+	krwlock_t	fshcl_lock;
+	list_t		fshcl_list;
+} fsh_callback_list_t;
+
+/* API for vnode.c and vfs.c only */
+/* vnode.c */
+extern int fsh_read(vnode_t *vp, uio_t *uiop, int ioflag, cred_t *cr,
+		caller_context_t *ct);
+extern int fsh_write(vnode_t *vp, uio_t *uiop, int ioflag, cred_t *cr,
+		caller_context_t *ct);
+
+/* vfs.c */
+extern void fsh_init(void);
+
+extern int fsh_mount(vfs_t *vfsp, vnode_t *mvp, struct mounta *uap,
+		cred_t *cr);
+extern int fsh_unmount(vfs_t *vfsp, int flag, cred_t *cr);
+
+extern void fsh_exec_mount_callbacks(vfs_t *vfsp);
+extern void fsh_exec_free_callbacks(vfs_t *vfsp);
+
+extern void fsh_fsrec_destroy(struct fsh_fsrecord *volatile fsrecp);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _FSH_IMPL_H */
--- old/usr/src/uts/common/sys/vfs.h	Sun Aug 11 00:54:16 2013
+++ new/usr/src/uts/common/sys/vfs.h	Sun Aug 11 00:54:16 2013
@@ -198,6 +198,7 @@
  */
 struct zone;		/* from zone.h */
 struct fem_head;	/* from fem.h */
+struct fsh_fsrecord;	/* from fsh_impl.h */
 
 typedef struct vfs {
 	struct vfs	*vfs_next;		/* next VFS in VFS list */
@@ -235,6 +236,9 @@
 
 	struct fem_head	*vfs_femhead;		/* fs monitoring */
 	minor_t		vfs_lofi_minor;		/* minor if lofi mount */
+
+	struct fsh_fsrecord *volatile
+		vfs_fshrecord;			/* fs hooking */
 } vfs_t;
 
 #define	vfs_featureset	vfs_implp->vi_featureset
--- old/usr/src/uts/intel/Makefile.intel.shared	Sun Aug 11 00:54:16 2013
+++ new/usr/src/uts/intel/Makefile.intel.shared	Sun Aug 11 00:54:16 2013
@@ -239,6 +239,7 @@
 DRV_KMODS	+= fd
 DRV_KMODS	+= fdc
 DRV_KMODS	+= fm
+DRV_KMODS	+= fsd
 DRV_KMODS	+= fssnap
 DRV_KMODS	+= hxge
 DRV_KMODS	+= i8042
--- /dev/null	Sun Aug 11 00:54:16 2013
+++ new/usr/src/uts/intel/fsd/Makefile	Sun Aug 11 00:54:16 2013
@@ -0,0 +1,65 @@
+#
+# CDDL HEADER START
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License, Version 1.0 only
+# (the "License"). You may not use this file except in compliance
+# with the License.
+#
+# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+# or http://www.opensolaris.org/os/licensing.
+# See the License for the specific language governing permissions
+# and limitations under the License.
+#
+# When distributing Covered Code, include this CDDL HEADER in each
+# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+# If applicable, add the following below this CDDL HEADER, with the
+# fields enclosed by brackets "[]" replaced with your own identifying
+# information: Portions Copyright [yyyy] [name of copyright owner]
+#
+# CDDL HEADER END
+#
+#
+# Copyright 2012 Joyent, Inc. All rights reserved.
+# Use is subject to license terms.
+#
+#
+# Copyright 2013 Damian Bogel. All rights reserved.
+#
+
+UTSBASE	= ../..
+
+MODULE	= fsd
+OBJECTS	= $(FSD_OBJS:%=$(OBJS_DIR)/%)
+LINTS	= $(FSD_OBJS:%.o=$(LINTS_DIR)/%.ln)
+ROOTMODULE	= $(ROOT_DRV_DIR)/$(MODULE)
+CONF_SRCDIR	= $(UTSBASE)/common/io/fsd
+
+include $(UTSBASE)/intel/Makefile.intel
+
+ALL_TARGET	= $(BINARY) $(SRC_CONFILE)
+LINT_TARGET	= $(MODULE).lint
+INSTALL_TARGET	= $(BINARY) $(ROOTMODULE) $(ROOT_CONFFILE)
+
+LDFLAGS += -dy
+
+.KEEP_STATE:
+
+def:	$(DEF_DEPS)
+
+all:	$(ALL_DEPS)
+
+clean:	$(CLEAN_DEPS)
+
+clobber:	$(CLOBBER_DEPS)
+
+lint:	$(LINT_DEPS)
+
+modlintlib:	$(MODLINTLIB_DEPS)
+
+clean.lint:	$(CLEAN_LINT_DEPS)
+
+install:	$(INSTALL_DEPS)
+
+include $(UTSBASE)/intel/Makefile.targ
+
