--- /dev/null	Fri Jul 26 14:40:07 2013
+++ new/usr/src/uts/common/fs/fsh.c	Fri Jul 26 14:40:07 2013
@@ -0,0 +1,881 @@
+/*
+ * 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/sunddi.h>
+#include <sys/types.h>
+#include <sys/vfs.h>
+#include <sys/vnode.h>
+
+/*
+ * TODO:
+ * - support more operations
+ * - adjust the code for use of kernel list operations
+ * - add big theory about callbacks
+ */
+
+/*
+ * 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 hookin 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:
+ * 	- const fsh_node_t *fsh_node - 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(const fsh_node_t *fsh_node, void *arg, vfs_t *vfsp) {
+ * 	cmn_err(CE_NOTE, "freefs called!\n");
+ * 	return (fsh_next_freefs(fsh_node, 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 change until vfs_free() is called.
+ *
+ * fsh_list_t is a RW locked hook list, designed to containt 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 next node pointer from
+ * fsh_node_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/fre_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.
+ *
+ * It is unsafe to call fsh_foo() for a given vfs_t when it's being destroyed.
+ * It's because of the fact that it's expected that vfs_fshrecord is set only
+ * once for the whole vfs_t lifetime.
+ */
+
+
+/* Structure used for mount/free callbacks. */
+static fsh_callback_list_t fsh_global_callback_list;
+
+/*
+ * 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_node_t *node;						\
+	fsh_list_t *list;						\
+									\
+	if (hooks->hook_##lower) {					\
+		node = (fsh_node_t *)kmem_alloc(sizeof (*node),	\
+					KM_SLEEP);			\
+		node->fshn_hooki.fshi_fn.hook_##lower = 		\
+			hooks->hook_##lower;				\
+		node->fshn_hooki.fshi_arg = hooks->arg;			\
+									\
+		list = &fsrec->fshfsr_opv[FSH_##type##_##upper];	\
+		rw_enter(&list->fshl_lock, RW_WRITER);			\
+		node->fshn_next =					\
+			fsrec						\
+			->fshfsr_opv[FSH_##type##_##upper].fshl_head;	\
+		fsrec->fshfsr_opv[FSH_##type##_##upper].fshl_head 	\
+			= node;						\
+		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, open, OPEN);
+	FSH_INSTALL_VN(hooks, fsrec, close, CLOSE);
+	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);
+	FSH_INSTALL_VFS(hooks, fsrec, root, ROOT);
+	FSH_INSTALL_VFS(hooks, fsrec, vget, VGET);
+	FSH_INSTALL_VFS(hooks, fsrec, statfs, STATFS);
+}
+
+/*
+ * 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_node_t *node, *prev;					\
+	fsh_list_t *list;						\
+	if (hooks->hook_##lower == NULL)				\
+		break;							\
+									\
+	list = &fsrec->fshfsr_opv[FSH_##type##_##upper];		\
+	rw_enter(&list->fshl_lock, RW_WRITER);				\
+	node = list->fshl_head;						\
+									\
+	if (node == NULL) {						\
+		rw_exit(&list->fshl_lock);				\
+		break;							\
+	}								\
+									\
+	while (node && 							\
+		!(node->fshn_hooki.fshi_fn.hook_##lower ==		\
+		    hooks->hook_##lower &&				\
+		    node->fshn_hooki.fshi_arg == hooks->arg)) {		\
+		prev = node;						\
+		node = node->fshn_next;					\
+	}								\
+									\
+	if (node == NULL) {						\
+		rw_exit(&list->fshl_lock);				\
+		break;							\
+	}								\
+									\
+	if (node == list->fshl_head)					\
+		list->fshl_head = node->fshn_next;			\
+	else								\
+		prev->fshn_next = node->fshn_next;			\
+	rw_exit(&list->fshl_lock);					\
+									\
+	kmem_free(node, sizeof (*node));				\
+_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, open, OPEN);
+	FSH_REMOVE_VN(hooks, fsrec, close, CLOSE);
+	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);
+	FSH_REMOVE_VFS(hooks, fsrec, root, ROOT);
+	FSH_REMOVE_VFS(hooks, fsrec, vget, VGET);
+	FSH_REMOVE_VFS(hooks, fsrec, statfs, STATFS);
+}
+
+/* TODO: check which hooks are installed */
+void
+fsh_hook_check(vfs_t *vfsp, fsh_t *hooks, fsh_t *mask)
+{
+	_NOTE(ARGUNUSED(vfsp));
+	_NOTE(ARGUNUSED(hooks));
+	_NOTE(ARGUNUSED(mask));
+}
+
+/*
+ * API for installing/removing global mount/free callbacks.
+ * It's safe to call these functions whenever after fsh_init() was called.
+ * The fsh_global_callback_list 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_node_t *node;
+
+	node = (fsh_callback_node_t *)kmem_alloc(sizeof (*node), KM_SLEEP);
+	node->fshcn_callback = *fsh_callback;
+
+	rw_enter(&fsh_global_callback_list.fshcl_lock, RW_WRITER);
+	node->fshcn_next = fsh_global_callback_list.fshcl_head;
+	fsh_global_callback_list.fshcl_head = node;
+	rw_exit(&fsh_global_callback_list.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).
+ *
+ * TODO: Adding an unique ID to the fsh_callback_t is worth considering,
+ * but it's not yet implemented.
+ */
+void
+fsh_callback_remove(fsh_callback_t *fsh_callback)
+{
+	fsh_callback_node_t *node;
+	fsh_callback_node_t *prev;
+	fsh_callback_list_t *list;
+
+	list = &fsh_global_callback_list;
+
+	rw_enter(&list->fshcl_lock, RW_WRITER);
+	node = list->fshcl_head;
+
+	if (node == NULL) {
+		rw_exit(&list->fshcl_lock);
+		return;
+	}
+
+	while (node && memcmp(fsh_callback, &node->fshcn_callback,
+	    sizeof (*fsh_callback))) {
+		prev = node;
+		node = node->fshcn_next;
+	}
+
+	if (node == NULL) {
+		rw_exit(&list->fshcl_lock);
+		return;
+	}
+
+	prev->fshcn_next = node->fshcn_next;
+	kmem_free(node, sizeof (*node));
+
+	rw_exit(&list->fshcl_lock);
+}
+
+/*
+ * 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_node_t *node;
+	fsh_callback_t *callback;
+
+	rw_enter(&fsh_global_callback_list.fshcl_lock, RW_READER);
+	node = fsh_global_callback_list.fshcl_head;
+	while (node) {
+		callback = &node->fshcn_callback;
+		(*(callback->fshc_mount))(vfsp, callback->fshc_arg);
+		node = node->fshcn_next;
+	}
+	rw_exit(&fsh_global_callback_list.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_node_t *node;
+	fsh_callback_t *callback;
+
+	rw_enter(&fsh_global_callback_list.fshcl_lock, RW_READER);
+	node = fsh_global_callback_list.fshcl_head;
+	while (node) {
+		callback = &node->fshcn_callback;
+		(*(callback->fshc_free))(vfsp, callback->fshc_arg);
+		node = node->fshcn_next;
+	}
+	rw_exit(&fsh_global_callback_list.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_open(vnode_t **vpp, int mode, cred_t *cr, caller_context_t *ct)
+{
+	fsh_fsrecord_t *fsrec;
+	fsh_list_t *list;
+	int ret;
+
+	FSH_PREPARE_FSREC((*vpp)->v_vfsp);
+	fsrec = (*vpp)->v_vfsp->vfs_fshrecord;
+	if (!(fsrec->fshfsr_enabled))
+		return ((*((*vpp)->v_op->vop_open))(vpp, mode, cr, ct));
+
+	list = &fsrec->fshfsr_opv[FSH_VOP_OPEN];
+	rw_enter(&list->fshl_lock, RW_READER);
+	if (list->fshl_head == NULL)
+		ret =  (*((*vpp)->v_op->vop_open))(vpp, mode, cr, ct);
+	else
+		ret = fsh_next_open(list->fshl_head, vpp, mode, cr, ct);
+	rw_exit(&list->fshl_lock);
+
+	return (ret);
+}
+
+int
+fsh_close(vnode_t *vp, int flag, int count, offset_t offset, 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_close))(vp, flag, count, offset,
+		    cr, ct));
+
+	list = &fsrec->fshfsr_opv[FSH_VOP_CLOSE];
+	rw_enter(&list->fshl_lock, RW_READER);
+	if (list->fshl_head == NULL)
+		ret =  (*(vp->v_op->vop_close))(vp, flag, count, offset,
+		    cr, ct);
+	else
+		ret = fsh_next_close(list->fshl_head, vp, flag, count,
+		    offset, cr, ct);
+	rw_exit(&list->fshl_lock);
+
+	return (ret);
+}
+
+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->fshl_head == NULL)
+		ret =  (*(vp->v_op->vop_read))(vp, uiop, ioflag, cr, ct);
+	else
+		ret = fsh_next_read(list->fshl_head, 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->fshl_head == NULL)
+		ret =  (*(vp->v_op->vop_write))(vp, uiop, ioflag, cr, ct);
+	else
+		ret = fsh_next_write(list->fshl_head, 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->fshl_head == NULL)
+		ret = (*(vfsp->vfs_op->vfs_mount))(vfsp, mvp, uap, cr);
+	else
+		ret = fsh_next_mount(list->fshl_head, 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->fshl_head == NULL)
+		ret = (*(vfsp->vfs_op->vfs_unmount))(vfsp, flag, cr);
+	else
+		ret = fsh_next_unmount(list->fshl_head, vfsp, flag, cr);
+	rw_exit(&list->fshl_lock);
+
+	return (ret);
+}
+
+int
+fsh_root(vfs_t *vfsp, vnode_t **vpp)
+{
+	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_root))(vfsp, vpp));
+
+	list = &fsrec->fshfsr_opv[FSH_VFS_ROOT];
+	rw_enter(&list->fshl_lock, RW_READER);
+	if (list->fshl_head == NULL)
+		ret = (*(vfsp->vfs_op->vfs_root))(vfsp, vpp);
+	else
+		ret = fsh_next_root(list->fshl_head, vfsp, vpp);
+	rw_exit(&list->fshl_lock);
+
+	return (ret);
+}
+
+int
+fsh_statfs(vfs_t *vfsp, statvfs64_t *sp)
+{
+	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_statvfs))(vfsp, sp));
+
+	list = &fsrec->fshfsr_opv[FSH_VFS_STATFS];
+	rw_enter(&list->fshl_lock, RW_READER);
+	if (list->fshl_head == NULL)
+		ret = (*(vfsp->vfs_op->vfs_statvfs))(vfsp, sp);
+	else
+		ret = fsh_next_statfs(list->fshl_head, vfsp, sp);
+	rw_exit(&list->fshl_lock);
+
+	return (ret);
+}
+
+int
+fsh_vget(vfs_t *vfsp, vnode_t **vpp, fid_t *fidp)
+{
+	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_vget))(vfsp, vpp, fidp));
+
+	list = &fsrec->fshfsr_opv[FSH_VFS_VGET];
+	rw_enter(&list->fshl_lock, RW_READER);
+	if (list->fshl_head == NULL)
+		ret = (*(vfsp->vfs_op->vfs_vget))(vfsp, vpp, fidp);
+	else
+		ret = fsh_next_vget(list->fshl_head, vfsp, vpp, fidp);
+	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);
+	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_node_t *node, *next_node;
+
+	for (i = 0; i < FSH_SUPPORTED_OPS_COUNT; i++) {
+		node = fsrecp->fshfsr_opv[i].fshl_head;
+		while (node) {
+			next_node = node->fshn_next;
+			kmem_free(node, sizeof (*node));
+			node = next_node;
+		}
+		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_global_callback_list.fshcl_lock, NULL, RW_DRIVER, NULL);
+	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_
+ */
+int
+fsh_next_open(fsh_node_t *fsh_node, vnode_t **vpp, int mode, cred_t *cr,
+	caller_context_t *ct)
+{
+	if (fsh_node == NULL)
+		return ((*(*vpp)->v_op->vop_open)(vpp, mode, cr, ct));
+
+	return ((*(fsh_node->fshn_hooki.fshi_fn.hook_open))(
+	    fsh_node->fshn_next, fsh_node->fshn_hooki.fshi_arg,
+	    vpp, mode, cr, ct));
+}
+
+int
+fsh_next_close(fsh_node_t *fsh_node, vnode_t *vp, int flag, int count,
+	offset_t offset, cred_t *cr, caller_context_t *ct)
+{
+	if (fsh_node == NULL)
+		return ((*(vp->v_op->vop_close))(vp, flag, count, offset,
+		    cr, ct));
+
+	return ((*(fsh_node->fshn_hooki.fshi_fn.hook_close))(
+	    fsh_node->fshn_next, fsh_node->fshn_hooki.fshi_arg,
+	    vp, flag, count, offset, cr, ct));
+}
+
+int
+fsh_next_read(fsh_node_t *fsh_node, vnode_t *vp, uio_t *uiop, int ioflag,
+	cred_t *cr, caller_context_t *ct)
+{
+	if (fsh_node == NULL)
+		return ((*(vp->v_op->vop_read))(vp, uiop, ioflag, cr, ct));
+
+	return ((*(fsh_node->fshn_hooki.fshi_fn.hook_read))(
+	    fsh_node->fshn_next, fsh_node->fshn_hooki.fshi_arg,
+	    vp, uiop, ioflag, cr, ct));
+}
+
+int
+fsh_next_write(fsh_node_t *fsh_node, vnode_t *vp, uio_t *uiop, int ioflag,
+	cred_t *cr, caller_context_t *ct)
+{
+	if (fsh_node == NULL)
+		return ((*(vp->v_op->vop_write))(vp, uiop, ioflag, cr, ct));
+
+	return ((*(fsh_node->fshn_hooki.fshi_fn.hook_write))(
+	    fsh_node->fshn_next, fsh_node->fshn_hooki.fshi_arg,
+	    vp, uiop, ioflag, cr, ct));
+}
+
+int
+fsh_next_mount(fsh_node_t *fsh_node, vfs_t *vfsp, vnode_t *mvp,
+	struct mounta *uap, cred_t *cr)
+{
+	if (fsh_node == NULL)
+		return ((*(vfsp->vfs_op->vfs_mount))(vfsp, mvp, uap, cr));
+
+	return ((*(fsh_node->fshn_hooki.fshi_fn.hook_mount))(
+	    fsh_node->fshn_next, fsh_node->fshn_hooki.fshi_arg,
+	    vfsp, mvp, uap, cr));
+}
+
+int
+fsh_next_unmount(fsh_node_t *fsh_node, vfs_t *vfsp, int flag, cred_t *cr)
+{
+	if (fsh_node == NULL)
+		return ((*(vfsp->vfs_op->vfs_unmount))(vfsp, flag, cr));
+
+	return ((*(fsh_node->fshn_hooki.fshi_fn.hook_unmount))(
+	    fsh_node->fshn_next, fsh_node->fshn_hooki.fshi_arg,
+	    vfsp, flag, cr));
+}
+
+int
+fsh_next_root(fsh_node_t *fsh_node, vfs_t *vfsp, vnode_t **vpp)
+{
+	if (fsh_node == NULL)
+		return ((*(vfsp->vfs_op->vfs_root))(vfsp, vpp));
+
+	return ((*(fsh_node->fshn_hooki.fshi_fn.hook_root))(
+	    fsh_node->fshn_next, fsh_node->fshn_hooki.fshi_arg,
+	    vfsp, vpp));
+}
+
+int
+fsh_next_statfs(fsh_node_t *fsh_node, vfs_t *vfsp, statvfs64_t *sp)
+{
+	if (fsh_node == NULL)
+		return ((*(vfsp->vfs_op->vfs_statvfs))(vfsp, sp));
+
+	return ((*(fsh_node->fshn_hooki.fshi_fn.hook_statfs))(
+	    fsh_node->fshn_next, fsh_node->fshn_hooki.fshi_arg,
+	    vfsp, sp));
+}
+
+int
+fsh_next_vget(fsh_node_t *fsh_node, vfs_t *vfsp, vnode_t **vpp, fid_t *fidp)
+{
+	if (fsh_node == NULL)
+		return ((*(vfsp->vfs_op->vfs_vget))(vfsp, vpp, fidp));
+
+	return ((*(fsh_node->fshn_hooki.fshi_fn.hook_vget))(
+	    fsh_node->fshn_next, fsh_node->fshn_hooki.fshi_arg,
+	    vfsp, vpp, fidp));
+}
