--- /dev/null	Mon Aug 26 21:53:20 2013
+++ new/usr/src/uts/common/fs/fsh.c	Mon Aug 26 21:53:20 2013
@@ -0,0 +1,879 @@
+/*
+ * 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/errno.h>
+#include <sys/fsh.h>
+#include <sys/fsh_impl.h>
+#include <sys/id_space.h>
+#include <sys/kmem.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 client-defined behaviour into vfs/vnode calls. fsh works on
+ * vfs_t granularity.
+ *
+ *
+ * 2. Overview.
+ * fsh_t is the main object in the fsh. An fsh_t is a structure containing:
+ * 	- pointers to hooking functions (named after corresponding
+ *	vnodeops/vfsops)
+ * 	- a pointer to an argument to pass (this is shared for all the
+ * 	hooks in a given fsh_t)
+ *
+ * The information from fsh_t is copied by the fsh and an fsh_handle_t
+ * is returned. It should be used for further removing.
+ *
+ *
+ * 3. Usage.
+ * It is expected that vfs_t/vnode_t that are passed to fsh_foo() functions
+ * are held by the caller when needed. fsh does no vfs_t/vnode_t locking.
+ *
+ * fsh_t is a structure filled out by the client. If a client does not want
+ * to add/remove a hook for function foo(), he should fill the foo field of
+ * fsh_t with NULL. Every hook has a type of corresponding vfsop/vnodeop with
+ * two additional arguments:
+ *	- fsh_int_t *fsh_int - this argument MUST be passed to
+ *	hook_next_foo(). fsh wouldn't know which hook to execute next
+ *	without it
+ *	- void *arg - this is the argument passed with fsh_t during
+ *	installation
+ * After installation, an fsh_handle_t is returned to the caller.
+ *
+ * A client might want to fire callbacks when vfs_t's are being mounted
+ * or freed. There's an fsh_callback_t structure provided to install such
+ * callbacks along with the API.
+ * It is legal to call fsh_hook_{install,remove}() inside a mount callback
+ * WITHOUT holding the vfs_t.
+ *
+ * After vfs_t's free callback returns, all the handles associated with the
+ * hooks installed on this vfs_t are invalid and must not be used.
+ *
+ *
+ * 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));
+ * }
+ *
+ *
+ * 4. API
+ * None of the APIs should be called during interrupt context above lock
+ * level. The only exceptions are fsh_next_foo() functions, which do not use
+ * locks.
+ *
+ * a) fsh.h
+ * None of the functions listed below should be called inside of a hook
+ * Doing so will cause a deadlock. The only exceptions are fsh_next_foo() and
+ * fsh_callback_{install,remove}().
+ *
+ * fsh_callback_{install,remove}() should not be called inside of a {mount,free}
+ * callback. Doing so will cause a deadlock.
+ *
+ * fsh_fs_enable(vfs_t *vfsp)
+ * fsh_fs_disable(vfs_t *vfsp)
+ * 	Enables/disables fsh for a given vfs_t.
+ *
+ * fsh_hook_install(vfs_t *vfsp, fsh_t *hooks)
+ * 	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 executed
+ * 	before A. It returns a correct handle, or (-1) if hook/callback
+ * 	limit exceeded. The handle is valid until a free callback returns
+ *	or an explicit call to fsh_hook_remove().
+ *
+ * fsh_hook_remove(fsh_handle_t handle)
+ * 	Removes a hook and invalidates the handle. It is guaranteed that after
+ * 	this funcion returns, hook associated with this handle won't be
+ * 	executing.
+ *
+ * fsh_next_foo(fsh_int_t *fsh_int, 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 *callback)
+ * fsh_callback_remove(fsh_callback_handle_t handle)
+ * 	Installs/removes callbacks 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. The
+ * 	fsh_callback_install() returns a correct handle, or (-1) if
+ * 	hook/callback limit exceeded.
+ *
+ * b) fsh_impl.h (for vfs.c and vnode.c only)
+ * 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 callbacks for {mount,free} of a vfs_t.
+ *
+ * fsh_fsrec_destroy(struct fsh_fsrecord *fsrecp)
+ * 	Destroys an fsh_fsrecord structure. All the hooks installed on this
+ * 	vfs_t are then destroyed. free callback is called before this function.
+ *
+ * fsh_foo(ARGUMENTS)
+ * 	Function used to start executing the hook chain for a given call.
+ *
+ * 5. Internals.
+ * fsh_fsrecord_t is a structure which lives inside a vfs_t.
+ * fsh_fsrecord_t contains:
+ *	- an rw-lock that protects the structure
+ *	- a list of hooks installed on this vfs_t
+ * 	- a flag which tells whether fsh is enabled on this vfs_t
+ *
+ * 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_t 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 function
+ * that MUST be used whenever a vfsp->vfs_fshrecord needs to be accessed.  After
+ * doing that, it's completely safe to keep this pointer locally, because it
+ * won't be changed until vfs_free() is called.
+ *
+ * The only exception from the fsh_prepare_fsrec() rule is vfs_free(),
+ * where there is expected that no other fsh calls would be made for the
+ * vfs_t that's being freed. That's why vfs_fshrecord could be only NULL or a
+ * valid pointer and could not be concurrently accessed.
+ *
+ * When there are no fsh functions (that use a particular fsh_fsrecord_t)
+ * executing, the vfs_fshrecord pointer won't be equal to fsh_res_ptr. It
+ * would be NULL or a pointer to an initialised fsh_fsrecord_t.
+ *
+ *
+ * Mount callbacks are executed by a call to fsh_exec_mount_callbacks() right
+ * before returning from domount()@vfs.c.
+ *
+ * Free callbacks are executed by a call to fsh_exec_free_callbacks() right
+ * before calling VFS_FREEVFS(), after vfs_t's reference count drops to 0.
+ *
+ *
+ * fsh_next_foo(fsh_int_t *fshi, ARGUMENTS)
+ * 	This function is quite simple. It takes the fsh_int_t and passes control
+ * 	to the next hook or to the underlying vnodeop/vfsop. fshi is always the
+ * 	next hook to be executed.
+ *
+ *
+ * 6. Concurrency
+ * fsh does no vfs_t nor vnode_t locking. It is expected that whenever it is
+ * needed, the client does that.
+ *
+ * An fsh_fsrecord_t of a vfs_t is read-locked (fshfsr_lock) by every
+ * fsh_foo() function (with the mentioned vfs_t as a parameter, of course).
+ * This means that fsh_hook_{install,remove}() must NOT be called inside of
+ * a hook, because it will cause a deadlock.
+ *
+ * The same thing applies to callbacks. fsh_cblist is read-locked by
+ * fsh_exec_{mount,free}(). This means that fsh_callback_{install,remove}
+ * must not be called inside a callback, because it will cause a deadlock.
+ *
+ * Solution to concurrency issues involving vfs_fshrecord are explained
+ * both in chapter 5th "Internals" and before fsh_prepare_fsrec() function.
+ *
+ * Concurrency issues between fsh_hook_remove() and fsh_fsrec_destroy() are
+ * solved by fsh_remove_lock. For more info see: fsh_remove_lock,
+ * fsh_hook_remove(), fsh_fsrec_destroy().
+ */
+
+/* Internals */
+/* Used for mapping an fsh_handle_t to fsh_int_t. */
+typedef struct fsh_mapping {
+	fsh_handle_t	fshm_handle;
+	fsh_int_t	*fshm_fshi;
+	vfs_t		*fshm_vfsp;
+	list_node_t	fshm_next;
+} fsh_mapping_t;
+
+struct fsh_int {
+	fsh_handle_t	fshi_handle;
+	fsh_t		fshi_hooks;
+	fsh_mapping_t	*fshi_mapping;
+	list_node_t	fshi_next;
+};
+
+typedef struct fsh_callback_int {
+	fsh_callback_t	fshci_cb;
+	fsh_callback_handle_t fshci_handle;
+	list_node_t	fshci_next;
+} fsh_callback_int_t;
+
+
+/*
+ * fsh_fsrecord_t is the main internal structure. It's content is protected
+ * by fshfsr_lock. The fshfsr_list is a list of fsh_int_t hook entries for
+ * the vfs_t that contains the fsh_fsrecord_t.
+ *
+ * It is guaranteed by the fsh_prepare_fsrec() that outside the fsh,
+ * a pointer to fsh_fsrecord inside a vfs_t is never equal to fsh_res_ptr.
+ */
+struct fsh_fsrecord {
+	krwlock_t	fshfsr_lock;
+	int		fshfsr_enabled;
+	list_t		fshfsr_list;	/* list of fsh_int_t */
+};
+
+/*
+ * It's a list of fsh_mapping_t's used to map fsh_handle_t's to
+ * fsh_int_t's. This is needed because of the fact that we'd like an opaque
+ * handle returned to the fsh API client after a hook is successfully
+ * installed. We'd like to make the handle the only thing that is needed
+ * after the hooks are installed, for futher actions on them. This means,
+ * that there is no easy way to search for the hooks matching a handle,
+ * without having the vfs_t on which they are installed.
+ * The same problem doesn't apply to callbacks, that's why fsh_map handles
+ * only fsh_handle_t to fsh_int_t translation.
+ */
+static kmutex_t fsh_map_lock;
+static list_t fsh_map;
+
+/*
+ * The lock is used when there is a need to lock both the fsh_fsrecord_t and
+ * fsh_map.
+ * fsh_hook_remove() and fsh_fsrec_destroy() use this lock to protect both the
+ * fsh_map and the fsh_fsrecord_t associated with a vfs_t on which the removing
+ * is performed. Because fsh_hook_remove() starts with handle, through fsh_map
+ * to vfs_fshrecord and fsh_fsrec_destroy() from vfs_fshrecord to fsh_map, such
+ * lock is necessary.
+ * For more info see: fsh_fsrec_destroy() and fsh_hook_remove()
+ */
+static kmutex_t fsh_remove_lock;
+
+/*
+ * It's a list of fsh_callback_int_t's. Unlike hooks, there is no need to
+ * keep a separate list for translating handles to fsh_callback_int_t's,
+ * because a callback list is global for all the vfs_t's.
+ */
+static krwlock_t fsh_cblist_lock;
+static list_t fsh_cblist;
+
+/*
+ * A reserved pointer for fsh purposes. It is used because of the method
+ * chosen for solving concurrency issues with 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 fsh_fsrecord_t *fsh_fsrec_create();
+
+int fsh_limit = INT_MAX;
+static id_space_t *fsh_idspace;
+
+/*
+ * Important note:
+ * Before using this function, 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) do not
+ * call vfs_init() or even vfs_alloc(), It's possible that some unbundled
+ * filesystems could do the same thing. That's why this solution 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 the spinlock isn't bad at all.
+ *
+ * How it works:
+ * a) if vfsp->vfs_fshrecord equals NULL, atomic_cas_ptr() changes it to
+ *	fsh_res_ptr. That's a signal for other threads, that the structure
+ *	is being initialised.
+ * b) if vfsp->vfs_fshrecord equals 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. It won't change
+ *	until vfs_free() is called. It can't happen when someone is holding
+ *	the vfs_t, which is expected from the caller of fsh API.
+ */
+static void
+fsh_prepare_fsrec(vfs_t *vfsp)
+{
+	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());
+}
+
+/*
+ * API for enabling/disabling fsh per vfs_t.
+ *
+ * A newly created vfs_t has fsh enabled by default. If one would want to change
+ * this behaviour, mount callbacks could be used.
+ *
+ * The caller is expected to hold the vfs_t.
+ *
+ * These functions must NOT be called in a hook.
+ */
+void
+fsh_fs_enable(vfs_t *vfsp)
+{
+	fsh_prepare_fsrec(vfsp);
+
+	rw_enter(&vfsp->vfs_fshrecord->fshfsr_lock, RW_WRITER);
+	vfsp->vfs_fshrecord->fshfsr_enabled = 1;
+	rw_exit(&vfsp->vfs_fshrecord->fshfsr_lock);
+}
+
+void
+fsh_fs_disable(vfs_t *vfsp)
+{
+	fsh_prepare_fsrec(vfsp);
+
+	rw_enter(&vfsp->vfs_fshrecord->fshfsr_lock, RW_WRITER);
+	vfsp->vfs_fshrecord->fshfsr_enabled = 0;
+	rw_exit(&vfsp->vfs_fshrecord->fshfsr_lock);
+}
+
+/*
+ * API used for installing hooks. fsh_handle_t is returned for further
+ * actions (currently just removing) on this set of hooks.
+ *
+ * fsh_t fields:
+ * - arg - argument passed to every hook
+ * - read, write, ... - pointers to hooks for corresponding vnodeops/vfsops;
+ *	if there is no hook desired for an operation, it should be set to
+ *	NULL
+ *
+ * It's important that the hooks are executed in LIFO installation order (they
+ * are added to the head of the hook list).
+ *
+ * The caller is expected to hold the vfs_t.
+ *
+ * This function must NOT be called in a hook.
+ *
+ * Returns (-1) if hook/callback limit exceeded, handle otherwise.
+ */
+fsh_handle_t
+fsh_hook_install(vfs_t *vfsp, fsh_t *hooks)
+{
+	fsh_handle_t	handle;
+	fsh_int_t	*fshi;
+	fsh_mapping_t	*mapping;
+
+	fsh_prepare_fsrec(vfsp);
+
+	if ((handle = id_alloc(fsh_idspace)) == -1)
+		return (-1);
+
+	fshi = kmem_alloc(sizeof (*fshi), KM_SLEEP);
+	(void) memcpy(&fshi->fshi_hooks, hooks, sizeof (fshi->fshi_hooks));
+	fshi->fshi_handle = handle;
+
+	/*
+	 * Although we lock vfs_fshrecord and fsh_map in this function,
+	 * there is no need to use fsh_remove_lock. Since it is expected that a
+	 * vfs_t is held across this call, fsh_fsrec_destroy() cannot be
+	 * executing concurrently. fsh_hook_remove() cannot be called for this
+	 * hook set, because a handle passed to that function doesn't yet exist.
+	 */
+
+	/* If it is called inside of a hook, causes deadlock. */
+	rw_enter(&vfsp->vfs_fshrecord->fshfsr_lock, RW_WRITER);
+	list_insert_head(&vfsp->vfs_fshrecord->fshfsr_list, fshi);
+	rw_exit(&vfsp->vfs_fshrecord->fshfsr_lock);
+
+	mapping = kmem_alloc(sizeof (*mapping), KM_SLEEP);
+	mapping->fshm_handle = handle;
+	mapping->fshm_vfsp = vfsp;
+	mapping->fshm_fshi = fshi;
+	fshi->fshi_mapping = mapping;
+
+	mutex_enter(&fsh_map_lock);
+	list_insert_head(&fsh_map, mapping);
+	mutex_exit(&fsh_map_lock);
+
+	return (handle);
+}
+
+/*
+ * Used for removing a hook set.
+ *
+ * This function must NOT be called in a hook.
+ *
+ * Returns (-1) if hook wasn't found, 0 otherwise.
+ */
+int
+fsh_hook_remove(fsh_handle_t handle)
+{
+	fsh_fsrecord_t	*fsrecp;
+	fsh_mapping_t	*mapping;
+
+	/* For more info about the fsh_remove_lock, see fsh_fsrec_destroy() */
+	mutex_enter(&fsh_remove_lock);
+	mutex_enter(&fsh_map_lock);
+	for (mapping = list_head(&fsh_map); mapping != NULL;
+	    mapping = list_next(&fsh_map, mapping)) {
+		if (mapping->fshm_handle == handle) {
+			list_remove(&fsh_map, mapping);
+			break;
+		}
+	}
+	mutex_exit(&fsh_map_lock);
+	if (mapping == NULL) {
+		mutex_exit(&fsh_remove_lock);
+		return (-1);
+	}
+
+	ASSERT(mapping->fshm_fshi->fshi_handle == handle);
+
+	/*
+	 * We don't have to call fsh_prepare_fsrec() here. fsh_fsrecord_t
+	 * is already initialised, because we've found a mapping for the given
+	 * handle. We instead make two ASSERTs.
+	 */
+	fsrecp = mapping->fshm_vfsp->vfs_fshrecord;
+	ASSERT(fsrecp != NULL);
+	ASSERT(fsrecp != fsh_res_ptr);
+
+	/* If it is called inside of a hook, causes deadlock. */
+	rw_enter(&fsrecp->fshfsr_lock, RW_WRITER);
+	list_remove(&fsrecp->fshfsr_list, mapping->fshm_fshi);
+	rw_exit(&fsrecp->fshfsr_lock);
+
+	mutex_exit(&fsh_remove_lock);
+
+	id_free(fsh_idspace, handle);
+
+	kmem_free(mapping->fshm_fshi, sizeof (*mapping->fshm_fshi));
+	kmem_free(mapping, sizeof (*mapping));
+
+	return (0);
+}
+
+/*
+ * API for installing global mount/free callbacks.
+ *
+ * fsh_callback_t fields:
+ * fshc_arg - argument passed to the callbacks
+ * fshc_free - callback fired before VFS_FREEVFS() is called, after vfs_count
+ *	drops to 0
+ * fshc_mount - callback fired right before returning from domount()
+ * The first argument of these callbacks is the vfs_t that is mounted/freed.
+ * The second one is the fshc_arg.
+ *
+ * fsh_callback_handle_t is filled out by this function.
+ *
+ * This function must NOT be called in a callback, because it will cause
+ * a deadlock.
+ *
+ * Returns (-1) if hook/callback limit exceeded.
+ */
+fsh_callback_handle_t
+fsh_callback_install(fsh_callback_t *callback)
+{
+	fsh_callback_int_t *fshci;
+	fsh_callback_handle_t handle;
+
+	if ((handle = id_alloc(fsh_idspace)) == -1)
+		return (-1);
+
+	fshci = (fsh_callback_int_t *)kmem_alloc(sizeof (*fshci), KM_SLEEP);
+	(void) memcpy(&fshci->fshci_cb, callback, sizeof (fshci->fshci_cb));
+	fshci->fshci_handle = handle;
+
+	/* If it is called in a {mount,free} callback, causes deadlock. */
+	rw_enter(&fsh_cblist_lock, RW_WRITER);
+	list_insert_head(&fsh_cblist, fshci);
+	rw_exit(&fsh_cblist_lock);
+
+	return (handle);
+}
+
+/*
+ * API for removing global mount/free callbacks.
+ *
+ * This function must NOT be called in a callback, because it will cause
+ * a deadlock.
+ *
+ * Returns (-1) if callback wasn't found, 0 otherwise.
+ */
+int
+fsh_callback_remove(fsh_callback_handle_t handle)
+{
+	fsh_callback_int_t *fshci;
+
+	/* If it is called in a {mount,free} callback, causes deadlock. */
+	rw_enter(&fsh_cblist_lock, RW_WRITER);
+	for (fshci = list_head(&fsh_cblist); fshci != NULL;
+	    fshci = list_next(&fsh_cblist, fshci)) {
+		if (fshci->fshci_handle == handle) {
+			list_remove(&fsh_cblist, fshci);
+			break;
+		}
+	}
+	rw_exit(&fsh_cblist_lock);
+
+	if (fshci == NULL)
+		return (-1);
+
+	kmem_free(fshci, sizeof (*fshci));
+	id_free(fsh_idspace, handle);
+
+	return (0);
+}
+
+/*
+ * This function is executed right before returning from domount()@vfs.c.
+ * We are sure that it's called only after fsh_init().
+ * It executes all the mount callbacks installed in the fsh.
+ *
+ * Since fsh_exec_mount_callbacks() is called only inside domount(), it is legal
+ * to call fsh_hook_{install,remove}() inside a mount callback WITHOUT holding
+ * this vfs_t. This guarantee should be preserved, because it's in the "Usage"
+ * section in the big theory statement at the top of this file.
+ */
+void
+fsh_exec_mount_callbacks(vfs_t *vfsp)
+{
+	fsh_callback_int_t *fshci;
+	fsh_callback_t *cb;
+
+	rw_enter(&fsh_cblist_lock, RW_READER);
+	for (fshci = list_head(&fsh_cblist); fshci != NULL;
+	    fshci = list_next(&fsh_cblist, fshci)) {
+		cb = &fshci->fshci_cb;
+		if (cb->fshc_mount != NULL)
+			(*(cb->fshc_mount))(vfsp, cb->fshc_arg);
+	}
+	rw_exit(&fsh_cblist_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 executes all the free callbacks installed in the fsh.
+ *
+ * free() callback is the point where the handles associated with the hooks
+ * installed on this vfs_t become invalid
+ */
+void
+fsh_exec_free_callbacks(vfs_t *vfsp)
+{
+	fsh_callback_int_t *fshci;
+	fsh_callback_t *cb;
+
+	rw_enter(&fsh_cblist_lock, RW_READER);
+	for (fshci = list_head(&fsh_cblist); fshci != NULL;
+	    fshci = list_next(&fsh_cblist, fshci)) {
+		cb = &fshci->fshci_cb;
+		if (cb->fshc_free != NULL)
+			(*(cb->fshc_free))(vfsp, cb->fshc_arg);
+	}
+	rw_exit(&fsh_cblist_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_foo()
+ * alive and valid.
+ * All these expectations are met because these functions are used only in
+ * correspondng {fop,fsop}_foo() functions.
+ */
+int
+fsh_read(vnode_t *vp, uio_t *uiop, int ioflag, cred_t *cr,
+	caller_context_t *ct)
+{
+	int ret;
+	fsh_fsrecord_t *fsrecp;
+
+	fsh_prepare_fsrec(vp->v_vfsp);
+	fsrecp = vp->v_vfsp->vfs_fshrecord;
+
+	rw_enter(&fsrecp->fshfsr_lock, RW_READER);
+	if (!(fsrecp->fshfsr_enabled)) {
+		rw_exit(&fsrecp->fshfsr_lock);
+		return ((*(vp->v_op->vop_read))(vp, uiop, ioflag, cr, ct));
+	}
+
+	ret = fsh_next_read(list_head(&fsrecp->fshfsr_list), vp, uiop, ioflag,
+	    cr, ct);
+	rw_exit(&fsrecp->fshfsr_lock);
+
+	return (ret);
+}
+
+int
+fsh_write(vnode_t *vp, uio_t *uiop, int ioflag, cred_t *cr,
+	caller_context_t *ct)
+{
+	int ret;
+	fsh_fsrecord_t *fsrecp;
+
+	fsh_prepare_fsrec(vp->v_vfsp);
+	fsrecp = vp->v_vfsp->vfs_fshrecord;
+
+	rw_enter(&fsrecp->fshfsr_lock, RW_READER);
+	if (!(vp->v_vfsp->vfs_fshrecord->fshfsr_enabled)) {
+		rw_exit(&fsrecp->fshfsr_lock);
+		return ((*(vp->v_op->vop_write))(vp, uiop, ioflag, cr, ct));
+	}
+
+	ret = fsh_next_write(list_head(&fsrecp->fshfsr_list), vp, uiop, ioflag,
+	    cr, ct);
+	rw_exit(&fsrecp->fshfsr_lock);
+
+	return (ret);
+}
+
+int
+fsh_mount(vfs_t *vfsp, vnode_t *mvp, struct mounta *uap, cred_t *cr)
+{
+	fsh_fsrecord_t *fsrecp;
+	int ret;
+
+	fsh_prepare_fsrec(vfsp);
+	fsrecp = vfsp->vfs_fshrecord;
+
+	rw_enter(&fsrecp->fshfsr_lock, RW_READER);
+	if (!(fsrecp->fshfsr_enabled)) {
+		rw_exit(&fsrecp->fshfsr_lock);
+		return ((*(vfsp->vfs_op->vfs_mount))(vfsp, mvp, uap, cr));
+	}
+
+	ret = fsh_next_mount(list_head(&fsrecp->fshfsr_list), vfsp, mvp, uap,
+	    cr);
+	rw_exit(&fsrecp->fshfsr_lock);
+
+	return (ret);
+}
+
+int
+fsh_unmount(vfs_t *vfsp, int flag, cred_t *cr)
+{
+	fsh_fsrecord_t *fsrecp;
+	int ret;
+
+	fsh_prepare_fsrec(vfsp);
+	fsrecp = vfsp->vfs_fshrecord;
+
+	rw_enter(&fsrecp->fshfsr_lock, RW_READER);
+	if (!(fsrecp->fshfsr_enabled)) {
+		rw_exit(&fsrecp->fshfsr_lock);
+		return ((*(vfsp->vfs_op->vfs_unmount))(vfsp, flag, cr));
+	}
+
+	ret = fsh_next_unmount(list_head(&fsrecp->fshfsr_list), vfsp, flag, cr);
+	rw_exit(&fsrecp->fshfsr_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 fsh_fsrecord_t *
+fsh_fsrec_create()
+{
+	fsh_fsrecord_t *fsrecp;
+
+	fsrecp = (fsh_fsrecord_t *)kmem_zalloc(sizeof (*fsrecp), KM_SLEEP);
+	list_create(&fsrecp->fshfsr_list, sizeof (fsh_int_t),
+	    offsetof(fsh_int_t, fshi_next));
+	rw_init(&fsrecp->fshfsr_lock, NULL, RW_DRIVER, NULL);
+	fsrecp->fshfsr_enabled = 1;
+	return (fsrecp);
+}
+
+
+/*
+ * This call can be used ONLY in vfs_free(). It's assumed that no other
+ * fsh calls using the vfs_t that owns the fsh_fsrecord to be destroyed
+ * are executing while a call to fsh_fsrec_destroy() is made. With this
+ * assumptions, no concurrency issues occur.
+ *
+ * Before calling this function outside the fsh, it's sufficient and
+ * required to check if the passed fsh_fsrecord * is not NULL. We don't
+ * have to check if it is not equal to fsh_res_ptr, because all the fsh API
+ * calls involving this vfs_t should end before vfs_free() is called
+ * (outside the fsh, fsh_fsrecord is never equal to fsh_res_ptr). That is
+ * guaranteed by the explicit requirement that the caller of fsh API holds
+ * the vfs_t when needed.
+ *
+ * All the remaining hooks are being removed.
+ */
+void
+fsh_fsrec_destroy(struct fsh_fsrecord *volatile fsrecp)
+{
+	fsh_int_t *fshi;
+
+	VERIFY(fsrecp != NULL);
+
+	/*
+	 * Although it is expected that no fsh calls using this vfs_t
+	 * would be executing, we would not like to panic if that happens.
+	 * That's a client's bug obviously, but we'd like to minimise the
+	 * possibility of a system crash. That's why fsh_remove_lock is used
+	 * here. Even if fsh_hook_remove() would be called with a handle that is
+	 * invalid, it will just return (-1), because it won't find the handle
+	 * in fsh_map.  here is of course a possibility, that an invalid handle
+	 * (id_t internally) would become valid (it would be assigned again),
+	 * but this is very rare, because of the fact that id_alloc() uses next
+	 * fit strategy to alloc the id_t's.
+	 * For more info see fsh_hook_remove()
+	 */
+	mutex_enter(&fsh_remove_lock);
+	while ((fshi = list_remove_head(&fsrecp->fshfsr_list)) != NULL) {
+		fsh_mapping_t *mapping = fshi->fshi_mapping;
+
+		ASSERT(mapping->fshm_handle == fshi->fshi_handle);
+		ASSERT(mapping->fshm_fshi == fshi);
+
+		mutex_enter(&fsh_map_lock);
+		list_remove(&fsh_map, mapping);
+		mutex_exit(&fsh_map_lock);
+
+		id_free(fsh_idspace, fshi->fshi_handle);
+
+		kmem_free(fshi, sizeof (*fshi));
+		kmem_free(mapping, sizeof (*mapping));
+	}
+	mutex_exit(&fsh_remove_lock);
+
+	list_destroy(&fsrecp->fshfsr_list);
+	rw_destroy(&fsrecp->fshfsr_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_cblist_lock, NULL, RW_DRIVER, NULL);
+	list_create(&fsh_cblist, sizeof (fsh_callback_int_t),
+	    offsetof(fsh_callback_int_t, fshci_next));
+
+	mutex_init(&fsh_map_lock, NULL, MUTEX_DRIVER, NULL);
+	mutex_init(&fsh_remove_lock, NULL, MUTEX_DRIVER, NULL);
+
+	list_create(&fsh_map, sizeof (fsh_mapping_t),
+	    offsetof(fsh_mapping_t, fshm_next));
+
+	/* See comment above fsh_prepare_fsrec() */
+	fsh_res_ptr = (void *)-1;
+
+	fsh_idspace = id_space_create("fsh", 0, fsh_limit);
+}
+
+/*
+ * These functions are used to pass control to the next hook or underlying
+ * vop or vfsop. It's client doesn't have to worry about any locking, because
+ * all the necessities are guaranteed by the fsh_foo().
+ *
+ * In fsh_next_foo() we execute the hook passed in the first argument and
+ * try to find the next one. It is guaranteed that the passed hook is still
+ * valid, because of fshfsr_lock held by fsh_foo().
+ */
+int
+fsh_next_read(fsh_int_t *fshi, vnode_t *vp, uio_t *uiop, int ioflag,
+	cred_t *cr, caller_context_t *ct)
+{
+	while (fshi != NULL && fshi->fshi_hooks.read == NULL)
+		fshi = list_next(&vp->v_vfsp->vfs_fshrecord->fshfsr_list,
+		    fshi);
+
+	if (fshi != NULL)
+		return ((*(fshi->fshi_hooks.read))(
+		    list_next(&vp->v_vfsp->vfs_fshrecord->fshfsr_list, fshi),
+		    fshi->fshi_hooks.arg, vp, uiop, ioflag, cr, ct));
+	else
+		return ((*(vp->v_op->vop_read))(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)
+{
+	while (fshi != NULL && fshi->fshi_hooks.write == NULL)
+		fshi = list_next(&vp->v_vfsp->vfs_fshrecord->fshfsr_list,
+		    fshi);
+
+	if (fshi != NULL)
+		return ((*(fshi->fshi_hooks.write))(
+		    list_next(&vp->v_vfsp->vfs_fshrecord->fshfsr_list, fshi),
+		    fshi->fshi_hooks.arg, vp, uiop, ioflag, cr, ct));
+	else
+		return ((*(vp->v_op->vop_write))(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)
+{
+	while (fshi != NULL && fshi->fshi_hooks.mount == NULL)
+		fshi = list_next(&vfsp->vfs_fshrecord->fshfsr_list, fshi);
+
+	if (fshi != NULL)
+		return ((*(fshi->fshi_hooks.mount))(
+		    list_next(&vfsp->vfs_fshrecord->fshfsr_list, fshi),
+		    fshi->fshi_hooks.arg, vfsp, mvp, uap, cr));
+	else
+		return ((*(vfsp->vfs_op->vfs_mount))(vfsp, mvp, uap, cr));
+}
+
+int
+fsh_next_unmount(fsh_int_t *fshi, vfs_t *vfsp, int flag, cred_t *cr)
+{
+
+	while (fshi != NULL && fshi->fshi_hooks.unmount == NULL)
+		fshi = list_next(&vfsp->vfs_fshrecord->fshfsr_list, fshi);
+
+	if (fshi != NULL)
+		return ((*(fshi->fshi_hooks.unmount))(
+		    list_next(&vfsp->vfs_fshrecord->fshfsr_list, fshi),
+		    fshi->fshi_hooks.arg, vfsp, flag, cr));
+	else
+		return ((*(vfsp->vfs_op->vfs_unmount))(vfsp, flag, cr));
+}
