summaryrefslogtreecommitdiff
path: root/usr/src/uts/common/io/hook.c
diff options
context:
space:
mode:
Diffstat (limited to 'usr/src/uts/common/io/hook.c')
-rw-r--r--usr/src/uts/common/io/hook.c1659
1 files changed, 1522 insertions, 137 deletions
diff --git a/usr/src/uts/common/io/hook.c b/usr/src/uts/common/io/hook.c
index 323503498c..8b7129a35b 100644
--- a/usr/src/uts/common/io/hook.c
+++ b/usr/src/uts/common/io/hook.c
@@ -19,11 +19,9 @@
* CDDL HEADER END
*/
/*
- * Copyright 2007 Sun Microsystems, Inc. All rights reserved.
+ * Copyright 2008 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
-#pragma ident "%Z%%M% %I% %E% SMI"
-
#include <sys/param.h>
#include <sys/types.h>
#include <sys/systm.h>
@@ -34,6 +32,7 @@
#include <sys/modctl.h>
#include <sys/hook_impl.h>
#include <sys/sdt.h>
+#include <sys/cmn_err.h>
/*
* This file provides kernel hook framework.
@@ -51,6 +50,109 @@ static struct modlinkage modlinkage = {
};
/*
+ * How it works.
+ * =============
+ * Use of the hook framework here is tied up with zones - when a new zone
+ * is created, we create a new hook_stack_t and are open to business for
+ * allowing new hook families and their events.
+ *
+ * A consumer of these hooks is expected to operate in this fashion:
+ * 1) call hook_family_add() to create a new family of hooks. It is a
+ * current requirement that this call must be made with the value
+ * returned from hook_stack_init, by way of infrastructure elsewhere.
+ * 2) add events to the registered family with calls to hook_event_add.
+ *
+ * At this point, the structures in place should be open to others to
+ * add hooks to the event or add notifiers for when the contents of the
+ * hook stack changes.
+ *
+ * The interesting stuff happens on teardown.
+ *
+ * It is a requirement that the provider of hook events work in the reverse
+ * order to the above, so that the first step is:
+ * 1) remove events from each hook family created earlier
+ * 2) remove hook families from the hook stack.
+ *
+ * When doing teardown of both events and families, a check is made to see
+ * if either structure is still "busy". If so then a boolean flag is set to
+ * say that the structure is condemned. The presence of this flag being set
+ * must be checked for in _add()/_register()/ functions and a failure returned
+ * if it is set. It is ignored by the _find() functions because they're
+ * used by _remove()/_unregister(). While setting the condemned flag when
+ * trying to delete a structure would normally be keyed from the presence
+ * of a reference count being greater than 1, in this implementation there
+ * are no reference counts required: instead the presence of objects on
+ * linked lists is taken to mean something is still "busy."
+ *
+ * ONLY the caller that adds the family and the events ever has a direct
+ * reference to the internal structures and thus ONLY it should be doing
+ * the removal of either the event or family. In practise, what this means
+ * is that in ip_netinfo.c, we have calls to net_protocol_register(), followed
+ * by net_event_register() (these interface to hook_family_add() and
+ * hook_event_add(), respectively) that are made when we create an instance
+ * of IP and when the IP instance is shutdown/destroyed, it calls
+ * net_event_unregister() and net_protocol_unregister(), which in turn call
+ * hook_event_remove() and hook_family_remove() respectively. Nobody else
+ * is entitled to call the _unregister() functions. It is imperative that
+ * there be only one _remove() call for every _add() call.
+ *
+ * It is possible that code which is interfacing with this hook framework
+ * won't do all the cleaning up that it needs to at the right time. While
+ * we can't prevent programmers from creating memory leaks, we can synchronise
+ * when we clean up data structures to prevent code accessing free'd memory.
+ *
+ * A simple diagram showing the ownership is as follows:
+ *
+ * Owned +--------------+
+ * by | hook_stack_t |
+ * the +--------------+
+ * Instance |
+ * - - - - - - - -|- - - - - - - - - - - - - - - - - -
+ * V
+ * Owned +-------------------+ +-------------------+
+ * | hook_family_int_t |---->| hook_family_int_t |
+ * by +-------------------+ +-------------------+
+ * | \+---------------+ \+---------------+
+ * network | | hook_family_t | | hook_family_t |
+ * V +---------------+ +---------------+
+ * protocol +------------------+ +------------------+
+ * | hook_event_int_t |---->| hook_event_int_t |
+ * (ipv4,ipv6) +------------------+ +------------------+
+ * | \+--------------+ \+--------------+
+ * | | hook_event_t | | hook_event_t |
+ * | +--------------+ +--------------+
+ * - - - - - - - -|- - - - - - - - - - - - - - - - - -
+ * V
+ * Owned +------------+
+ * | hook_int_t |
+ * by +------------+
+ * \+--------+
+ * the consumer | hook_t |
+ * +--------+
+ *
+ * The consumers, such as IPFilter, do not have any pointers or hold any
+ * references to hook_int_t, hook_event_t or hook_event_int_t. By placing
+ * a hook on an event through net_hook_register(), an implicit reference
+ * to the hook_event_int_t is returned with a successful call. Additionally,
+ * IPFilter does not see the hook_family_int_t or hook_family_t directly.
+ * Rather it is returned a net_handle_t (from net_protocol_lookup()) that
+ * contains a pointer to hook_family_int_t. The structure behind the
+ * net_handle_t (struct net_data) *is* reference counted and managed
+ * appropriately.
+ *
+ * A more detailed picture that describes how the family/event structures
+ * are linked together can be found in <sys/hook_impl.h>
+ */
+
+/*
+ * Locking
+ * =======
+ * The use of CVW_* macros to do locking is driven by the need to allow
+ * recursive locking with read locks when we're processing packets. This
+ * is necessary because various netinfo functions need to hold read locks,
+ * by design, as they can be called in or out of packet context.
+ */
+/*
* Hook internal functions
*/
static hook_int_t *hook_copy(hook_t *src);
@@ -58,16 +160,45 @@ static hook_event_int_t *hook_event_checkdup(hook_event_t *he,
hook_stack_t *hks);
static hook_event_int_t *hook_event_copy(hook_event_t *src);
static hook_event_int_t *hook_event_find(hook_family_int_t *hfi, char *event);
-static void hook_event_free(hook_event_int_t *hei);
+static void hook_event_free(hook_event_int_t *hei, hook_family_int_t *hfi);
static hook_family_int_t *hook_family_copy(hook_family_t *src);
static hook_family_int_t *hook_family_find(char *family, hook_stack_t *hks);
-static void hook_family_free(hook_family_int_t *hfi);
+static void hook_family_free(hook_family_int_t *hfi, hook_stack_t *hks);
static hook_int_t *hook_find(hook_event_int_t *hei, hook_t *h);
-static void hook_free(hook_int_t *hi);
+static void hook_int_free(hook_int_t *hi, netstackid_t);
static void hook_init(void);
static void hook_fini(void);
static void *hook_stack_init(netstackid_t stackid, netstack_t *ns);
static void hook_stack_fini(netstackid_t stackid, void *arg);
+static void hook_stack_shutdown(netstackid_t stackid, void *arg);
+static int hook_insert(hook_int_head_t *head, hook_int_t *new);
+static void hook_insert_plain(hook_int_head_t *head, hook_int_t *new);
+static int hook_insert_afterbefore(hook_int_head_t *head, hook_int_t *new);
+static hook_int_t *hook_find_byname(hook_int_head_t *head, char *name);
+static void hook_event_init_kstats(hook_family_int_t *, hook_event_int_t *);
+static void hook_event_notify_run(hook_event_int_t *, hook_family_int_t *,
+ char *event, char *name, hook_notify_cmd_t cmd);
+static void hook_init_kstats(hook_family_int_t *hfi, hook_event_int_t *hei,
+ hook_int_t *hi);
+static int hook_notify_register(cvwaitlock_t *lock, hook_notify_head_t *head,
+ hook_notify_fn_t callback, void *arg);
+static int hook_notify_unregister(cvwaitlock_t *lock,
+ hook_notify_head_t *head, hook_notify_fn_t callback);
+static void hook_notify_run(hook_notify_head_t *head, char *family,
+ char *event, char *name, hook_notify_cmd_t cmd);
+static void hook_stack_notify_run(hook_stack_t *hks, char *name,
+ hook_notify_cmd_t cmd);
+static void hook_stack_remove(hook_stack_t *hks);
+
+/*
+ * A list of the hook stacks is kept here because we need to enable
+ * net_instance_notify_register() to be called during the creation
+ * of a new instance. Previously hook_stack_get() would just use
+ * the netstack functions for this work but they will return NULL
+ * until the zone has been fully initialised.
+ */
+static hook_stack_head_t hook_stacks;
+static kmutex_t hook_stack_lock;
/*
* Module entry points.
@@ -85,7 +216,6 @@ _init(void)
return (error);
}
-
int
_fini(void)
{
@@ -98,14 +228,12 @@ _fini(void)
return (error);
}
-
int
_info(struct modinfo *modinfop)
{
return (mod_info(&modlinkage, modinfop));
}
-
/*
* Function: hook_init
* Returns: None
@@ -116,11 +244,14 @@ _info(struct modinfo *modinfop)
static void
hook_init(void)
{
+ mutex_init(&hook_stack_lock, NULL, MUTEX_DRIVER, NULL);
+ SLIST_INIT(&hook_stacks);
+
/*
* We want to be informed each time a stack is created or
* destroyed in the kernel.
*/
- netstack_register(NS_HOOK, hook_stack_init, NULL,
+ netstack_register(NS_HOOK, hook_stack_init, hook_stack_shutdown,
hook_stack_fini);
}
@@ -135,6 +266,134 @@ static void
hook_fini(void)
{
netstack_unregister(NS_HOOK);
+
+ mutex_destroy(&hook_stack_lock);
+ ASSERT(SLIST_EMPTY(&hook_stacks));
+}
+
+/*
+ * Function: hook_wait_setflag
+ * Returns: -1 = setting flag is disallowed, 0 = flag set and did
+ * not have to wait (ie no lock droped), 1 = flag set but
+ * it was necessary to drop locks to set it.
+ * Parameters: waiter(I) - control data structure
+ * busyset(I) - set of flags that we don't want set while
+ * we are active.
+ * wanted(I) - flag associated with newflag to indicate
+ * what we want to do.
+ * newflag(I) - the new ACTIVE flag we want to set that
+ * indicates what we are doing.
+ *
+ * The set of functions hook_wait_* implement an API that builds on top of
+ * the kcondvar_t to provide controlled execution through a critical region.
+ * For each flag that indicates work is being done (FWF_*_ACTIVE) there is
+ * also a flag that we set to indicate that we want to do it (FWF_*_WANTED).
+ * The combination of flags is required as when this function exits to do
+ * the task, the structure is then free for another caller to use and
+ * to indicate that it wants to do work. The trump flags here are those
+ * that indicate someone wants to destroy the structure that owns this
+ * flagwait_t. In this case, we don't try to secure the ability to run
+ * and return with an error.
+ *
+ * wanted - the FWF_*_WANTED flag that describes the action being requested
+ * busyset- the set of FWF_* flags we don't want set when we run
+ * newflag- the FWF_*_ACTIVE flag we will set to indicate we are busy
+ */
+int
+hook_wait_setflag(flagwait_t *waiter, uint32_t busyset, fwflag_t wanted,
+ fwflag_t newflag)
+{
+ int waited = 0;
+
+ mutex_enter(&waiter->fw_lock);
+ if (waiter->fw_flags & FWF_DESTROY) {
+ mutex_exit(&waiter->fw_lock);
+ return (-1);
+ }
+ while (waiter->fw_flags & busyset) {
+ waiter->fw_flags |= wanted;
+ CVW_EXIT_WRITE(waiter->fw_owner);
+ cv_wait(&waiter->fw_cv, &waiter->fw_lock);
+ waited = 1;
+ CVW_ENTER_WRITE(waiter->fw_owner);
+ if (waiter->fw_flags & FWF_DESTROY) {
+ waiter->fw_flags &= ~wanted;
+ mutex_exit(&waiter->fw_lock);
+ return (-1);
+ }
+ waiter->fw_flags |= wanted;
+ }
+ waiter->fw_flags &= ~wanted;
+ waiter->fw_flags |= newflag;
+ mutex_exit(&waiter->fw_lock);
+ return (waited);
+}
+
+/*
+ * Function: hook_wait_unsetflag
+ * Returns: None
+ * Parameters: waiter(I) - control data structure
+ * oldflag(I) - flag to reset
+ *
+ * Turn off the bit that we had set to run and let others know that
+ * they should now check to see if they can run.
+ */
+void
+hook_wait_unsetflag(flagwait_t *waiter, uint32_t oldflag)
+{
+ mutex_enter(&waiter->fw_lock);
+ waiter->fw_flags &= ~oldflag;
+ cv_signal(&waiter->fw_cv);
+ mutex_exit(&waiter->fw_lock);
+}
+
+/*
+ * Function: hook_wait_destroy
+ * Returns: None
+ * Parameters: waiter(I) - control data structure
+ *
+ * Since outer locking (on fw_owner) should ensure that only one function
+ * at a time gets to call hook_wait_destroy() on a given object, there is
+ * no need to guard against setting FWF_DESTROY_WANTED already being set.
+ * It is, however, necessary to wait for all activity on the owning
+ * structure to cease.
+ */
+void
+hook_wait_destroy(flagwait_t *waiter)
+{
+ ASSERT((waiter->fw_flags & FWF_DESTROY_WANTED) == 0);
+ waiter->fw_flags |= FWF_DESTROY_WANTED;
+ while (!FWF_DESTROY_OK(waiter)) {
+ CVW_EXIT_WRITE(waiter->fw_owner);
+ cv_wait(&waiter->fw_cv, &waiter->fw_lock);
+ CVW_ENTER_WRITE(waiter->fw_owner);
+ }
+ /*
+ * There should now be nothing else using "waiter" or its
+ * owner, so we can safely assign here without risk of wiiping
+ * out someone's bit.
+ */
+ waiter->fw_flags = FWF_DESTROY_ACTIVE;
+}
+
+/*
+ * Function: hook_wait_init
+ * Returns: None
+ * Parameters: waiter(I) - control data structure
+ * ownder(I) - pointer to lock that the owner of this
+ * waiter uses
+ *
+ * "owner" gets passed in here so that when we need to call cv_wait,
+ * for example in hook_wait_setflag(), we can drop the lock for the
+ * next layer out, which is likely to be held in an exclusive manner.
+ */
+void
+hook_wait_init(flagwait_t *waiter, cvwaitlock_t *owner)
+{
+ cv_init(&waiter->fw_cv, NULL, CV_DRIVER, NULL);
+ mutex_init(&waiter->fw_lock, NULL, MUTEX_DRIVER, NULL);
+ waiter->fw_flags = FWF_NONE;
+ waiter->fw_owner = owner;
}
/*
@@ -151,14 +410,37 @@ hook_stack_init(netstackid_t stackid, netstack_t *ns)
#endif
hks = (hook_stack_t *)kmem_zalloc(sizeof (*hks), KM_SLEEP);
- hks->hk_netstack = ns;
+ hks->hks_netstack = ns;
+ hks->hks_netstackid = stackid;
- CVW_INIT(&hks->hks_familylock);
+ CVW_INIT(&hks->hks_lock);
+ TAILQ_INIT(&hks->hks_nhead);
SLIST_INIT(&hks->hks_familylist);
+ hook_wait_init(&hks->hks_waiter, &hks->hks_lock);
+
+ mutex_enter(&hook_stack_lock);
+ SLIST_INSERT_HEAD(&hook_stacks, hks, hks_entry);
+ mutex_exit(&hook_stack_lock);
+
return (hks);
}
+/*ARGSUSED*/
+static void
+hook_stack_shutdown(netstackid_t stackid, void *arg)
+{
+ hook_stack_t *hks = (hook_stack_t *)arg;
+
+ mutex_enter(&hook_stack_lock);
+ /*
+ * Once this flag gets set to one, no more additions are allowed
+ * to any of the structures that make up this stack.
+ */
+ hks->hks_shutdown = 1;
+ mutex_exit(&hook_stack_lock);
+}
+
/*
* Free the hook stack instance.
*/
@@ -166,14 +448,143 @@ hook_stack_init(netstackid_t stackid, netstack_t *ns)
static void
hook_stack_fini(netstackid_t stackid, void *arg)
{
- hook_stack_t *hks = (hook_stack_t *)arg;
-#ifdef NS_DEBUG
- printf("hook_stack_fini(%p, stack %d)\n", arg, stackid);
-#endif
- CVW_DESTROY(&hks->hks_familylock);
+ hook_stack_t *hks = (hook_stack_t *)arg;
+
+ mutex_enter(&hook_stack_lock);
+ hks->hks_shutdown = 2;
+ hook_stack_remove(hks);
+ mutex_exit(&hook_stack_lock);
+}
+
+/*
+ * This function assumes that it is called with hook_stack_lock held.
+ * It functions differently to hook_family/event_remove in that it does
+ * the checks to see if it can be removed. This difference exists
+ * because this structure has nothing higher up that depends on it.
+ */
+static void
+hook_stack_remove(hook_stack_t *hks)
+{
+
+ ASSERT(mutex_owned(&hook_stack_lock));
+
+ /*
+ * Is the structure still in use?
+ */
+ if (!SLIST_EMPTY(&hks->hks_familylist) ||
+ !TAILQ_EMPTY(&hks->hks_nhead))
+ return;
+
+ SLIST_REMOVE(&hook_stacks, hks, hook_stack, hks_entry);
+
+ hook_wait_destroy(&hks->hks_waiter);
+ CVW_DESTROY(&hks->hks_lock);
kmem_free(hks, sizeof (*hks));
}
+static hook_stack_t *
+hook_stack_get(netstackid_t stackid)
+{
+ hook_stack_t *hks;
+
+ SLIST_FOREACH(hks, &hook_stacks, hks_entry) {
+ if (hks->hks_netstackid == stackid)
+ break;
+ }
+
+ return (hks);
+}
+
+/*
+ * Function: hook_stack_notify_register
+ * Returns: 0 = success, else failure
+ * Parameters: stackid(I) - netstack identifier
+ * callback(I)- function to be called
+ * arg(I) - arg to provide callback when it is called
+ *
+ * If we're not shutting down this instance, append a new function to the
+ * list of those to call when a new family of hooks is added to this stack.
+ */
+int
+hook_stack_notify_register(netstackid_t stackid, hook_notify_fn_t callback,
+ void *arg)
+{
+ hook_stack_t *hks;
+ int error;
+
+ mutex_enter(&hook_stack_lock);
+ hks = hook_stack_get(stackid);
+ if (hks != NULL) {
+ if (hks->hks_shutdown != 0) {
+ error = ESHUTDOWN;
+ } else {
+ error = hook_notify_register(&hks->hks_lock,
+ &hks->hks_nhead, callback, arg);
+ }
+ } else {
+ error = ESRCH;
+ }
+ mutex_exit(&hook_stack_lock);
+
+ return (error);
+}
+
+/*
+ * Function: hook_stack_notify_unregister
+ * Returns: 0 = success, else failure
+ * Parameters: stackid(I) - netstack identifier
+ * callback(I) - function to be called
+ *
+ * Attempt to remove a registered function from a hook stack's list of
+ * callbacks to activiate when protocols are added/deleted.
+ */
+int
+hook_stack_notify_unregister(netstackid_t stackid, hook_notify_fn_t callback)
+{
+ hook_stack_t *hks;
+ int error;
+
+ mutex_enter(&hook_stack_lock);
+ hks = hook_stack_get(stackid);
+ if (hks != NULL) {
+ error = hook_notify_unregister(&hks->hks_lock,
+ &hks->hks_nhead, callback);
+ if ((error == 0) && (hks->hks_shutdown == 2))
+ hook_stack_remove(hks);
+ } else {
+ error = ESRCH;
+ }
+ mutex_exit(&hook_stack_lock);
+
+ return (error);
+}
+
+/*
+ * Function: hook_stack_notify_run
+ * Returns: None
+ * Parameters: hks(I) - hook stack pointer to execute callbacks for
+ * name(I) - name of a hook family
+ * cmd(I) - either HN_UNREGISTER or HN_REGISTER
+ *
+ * Run through the list of callbacks on the hook stack to be called when
+ * a new hook family is added
+ *
+ * As hook_notify_run() expects 3 names, one for the family, one for the
+ * event and one for the object being introduced and we really only have
+ * one name (that of the new hook family), fake the hook stack's name by
+ * converting the integer to a string and for the event just pass NULL.
+ */
+static void
+hook_stack_notify_run(hook_stack_t *hks, char *name,
+ hook_notify_cmd_t cmd)
+{
+ char buffer[16];
+
+ (void) snprintf(buffer, sizeof (buffer), "%u", hks->hks_netstackid);
+
+ hook_notify_run(&hks->hks_nhead, buffer, NULL, name, cmd);
+}
+
/*
* Function: hook_run
* Returns: int - return value according to callback func
@@ -187,11 +598,10 @@ hook_stack_fini(netstackid_t stackid, void *arg)
* called more than once, simultaneously.
*/
int
-hook_run(hook_event_token_t token, hook_data_t info, netstack_t *ns)
+hook_run(hook_family_int_t *hfi, hook_event_token_t token, hook_data_t info)
{
- hook_int_t *hi;
hook_event_int_t *hei;
- hook_stack_t *hks = ns->netstack_hook;
+ hook_int_t *hi;
int rval = 0;
ASSERT(token != NULL);
@@ -201,11 +611,17 @@ hook_run(hook_event_token_t token, hook_data_t info, netstack_t *ns)
hook_event_token_t, token,
hook_data_t, info);
- /* Hold global read lock to ensure event will not be deleted */
- CVW_ENTER_READ(&hks->hks_familylock);
-
- /* Hold event read lock to ensure hook will not be changed */
- CVW_ENTER_READ(&hei->hei_lock);
+ /*
+ * Hold global read lock to ensure event will not be deleted.
+ * While it might be expected that we should also hold a read lock
+ * on the event lock (hei_lock) to prevent the hook list from
+ * changing while we're executing this function, both addition
+ * to and removal from the hook list on the event is done with
+ * a write lock held on hfi_lock. This is by design so that we
+ * only need to get one of these locks to process a packet.
+ * - locking is not a cheap thing to do for every packet.
+ */
+ CVW_ENTER_READ(&hfi->hfi_lock);
TAILQ_FOREACH(hi, &hei->hei_head, hi_entry) {
ASSERT(hi->hi_hook.h_func != NULL);
@@ -213,18 +629,20 @@ hook_run(hook_event_token_t token, hook_data_t info, netstack_t *ns)
hook_event_token_t, token,
hook_data_t, info,
hook_int_t *, hi);
- rval = (*hi->hi_hook.h_func)(token, info, ns);
+ rval = (*hi->hi_hook.h_func)(token, info, hi->hi_hook.h_arg);
DTRACE_PROBE4(hook__func__end,
hook_event_token_t, token,
hook_data_t, info,
hook_int_t *, hi,
int, rval);
+ hi->hi_kstats.hook_hits.value.ui64++;
if (rval != 0)
break;
}
- CVW_EXIT_READ(&hei->hei_lock);
- CVW_EXIT_READ(&hks->hks_familylock);
+ hei->hei_kstats.events.value.ui64++;
+
+ CVW_EXIT_READ(&hfi->hfi_lock);
DTRACE_PROBE3(hook__run__end,
hook_event_token_t, token,
@@ -234,7 +652,6 @@ hook_run(hook_event_token_t token, hook_data_t info, netstack_t *ns)
return (rval);
}
-
/*
* Function: hook_family_add
* Returns: internal family pointer - NULL = Fail
@@ -254,32 +671,62 @@ hook_family_add(hook_family_t *hf, hook_stack_t *hks)
if (new == NULL)
return (NULL);
- CVW_ENTER_WRITE(&hks->hks_familylock);
+ mutex_enter(&hook_stack_lock);
+ CVW_ENTER_WRITE(&hks->hks_lock);
+
+ if (hks->hks_shutdown != 0) {
+ CVW_EXIT_WRITE(&hks->hks_lock);
+ mutex_exit(&hook_stack_lock);
+ hook_family_free(new, NULL);
+ return (NULL);
+ }
/* search family list */
hfi = hook_family_find(hf->hf_name, hks);
if (hfi != NULL) {
- CVW_EXIT_WRITE(&hks->hks_familylock);
- hook_family_free(new);
+ CVW_EXIT_WRITE(&hks->hks_lock);
+ mutex_exit(&hook_stack_lock);
+ hook_family_free(new, NULL);
return (NULL);
}
- new->hfi_ptr = (void *)hks;
+ if (hook_wait_setflag(&hks->hks_waiter, FWF_WAIT_MASK,
+ FWF_ADD_WANTED, FWF_ADD_ACTIVE) == -1) {
+ CVW_EXIT_WRITE(&hks->hks_lock);
+ mutex_exit(&hook_stack_lock);
+ hook_family_free(new, NULL);
+ return (NULL);
+ }
+
+ CVW_INIT(&new->hfi_lock);
+ SLIST_INIT(&new->hfi_head);
+ TAILQ_INIT(&new->hfi_nhead);
+
+ hook_wait_init(&new->hfi_waiter, &new->hfi_lock);
+
+ new->hfi_stack = hks;
/* Add to family list head */
SLIST_INSERT_HEAD(&hks->hks_familylist, new, hfi_entry);
- CVW_EXIT_WRITE(&hks->hks_familylock);
+ CVW_EXIT_WRITE(&hks->hks_lock);
+ mutex_exit(&hook_stack_lock);
+
+ hook_stack_notify_run(hks, hf->hf_name, HN_REGISTER);
+
+ hook_wait_unsetflag(&hks->hks_waiter, FWF_ADD_ACTIVE);
+
return (new);
}
-
/*
* Function: hook_family_remove
* Returns: int - 0 = Succ, Else = Fail
* Parameters: hfi(I) - internal family pointer
*
- * Remove family from family list
+ * Remove family from family list. This function has been designed to be
+ * called once and once only per hook_family_int_t. Thus when cleaning up
+ * this structure as an orphan, callers should only call hook_family_free.
*/
int
hook_family_remove(hook_family_int_t *hfi)
@@ -287,27 +734,99 @@ hook_family_remove(hook_family_int_t *hfi)
hook_stack_t *hks;
ASSERT(hfi != NULL);
- hks = (hook_stack_t *)hfi->hfi_ptr;
+ hks = hfi->hfi_stack;
- CVW_ENTER_WRITE(&hks->hks_familylock);
+ CVW_ENTER_WRITE(&hks->hks_lock);
+
+ if (hook_wait_setflag(&hks->hks_waiter, FWF_WAIT_MASK,
+ FWF_DEL_WANTED, FWF_DEL_ACTIVE) == -1) {
+ /*
+ * If we're trying to destroy the hook_stack_t...
+ */
+ return (ENXIO);
+ }
- /* Check if there are events */
- if (!SLIST_EMPTY(&hfi->hfi_head)) {
- CVW_EXIT_WRITE(&hks->hks_familylock);
- return (EBUSY);
+ /*
+ * Check if the family is in use by the presence of either events
+ * or notify callbacks on the hook family.
+ */
+ if (!SLIST_EMPTY(&hfi->hfi_head) || !TAILQ_EMPTY(&hfi->hfi_nhead)) {
+ hfi->hfi_condemned = B_TRUE;
+ } else {
+ /*
+ * Although hfi_condemned = B_FALSE is implied from creation,
+ * putting a comment here inside the else upsets lint.
+ */
+ hfi->hfi_condemned = B_FALSE;
}
- /* Remove from family list */
- SLIST_REMOVE(&hks->hks_familylist, hfi, hook_family_int, hfi_entry);
+ CVW_ENTER_WRITE(&hfi->hfi_lock);
+ hook_wait_destroy(&hfi->hfi_waiter);
+ CVW_EXIT_WRITE(&hfi->hfi_lock);
+
+ CVW_EXIT_WRITE(&hks->hks_lock);
+
+ hook_stack_notify_run(hks, hfi->hfi_family.hf_name, HN_UNREGISTER);
- CVW_EXIT_WRITE(&hks->hks_familylock);
- hook_family_free(hfi);
+ hook_wait_unsetflag(&hks->hks_waiter, FWF_DEL_ACTIVE);
+
+ /*
+ * If we don't have to wait for anything else to disappear from this
+ * structure then we can free it up.
+ */
+ if (!hfi->hfi_condemned)
+ hook_family_free(hfi, hks);
return (0);
}
/*
+ * Function: hook_family_free
+ * Returns: None
+ * Parameters: hfi(I) - internal family pointer
+ *
+ * Free alloc memory for family
+ */
+static void
+hook_family_free(hook_family_int_t *hfi, hook_stack_t *hks)
+{
+
+ /*
+ * This lock gives us possession of the hks pointer after the
+ * SLIST_REMOVE, for which it is not needed, when hks_shutdown
+ * is checked and hook_stack_remove called.
+ */
+ mutex_enter(&hook_stack_lock);
+
+ ASSERT(hfi != NULL);
+
+ if (hks != NULL) {
+ CVW_ENTER_WRITE(&hks->hks_lock);
+ /* Remove from family list */
+ SLIST_REMOVE(&hks->hks_familylist, hfi, hook_family_int,
+ hfi_entry);
+
+ CVW_EXIT_WRITE(&hks->hks_lock);
+ }
+
+ /* Free name space */
+ if (hfi->hfi_family.hf_name != NULL) {
+ kmem_free(hfi->hfi_family.hf_name,
+ strlen(hfi->hfi_family.hf_name) + 1);
+ }
+
+ /* Free container */
+ kmem_free(hfi, sizeof (*hfi));
+
+ if (hks->hks_shutdown == 2)
+ hook_stack_remove(hks);
+
+ mutex_exit(&hook_stack_lock);
+}
+
+
+/*
* Function: hook_family_copy
* Returns: internal family pointer - NULL = Failed
* Parameters: src(I) - family pointer
@@ -327,10 +846,12 @@ hook_family_copy(hook_family_t *src)
new = (hook_family_int_t *)kmem_zalloc(sizeof (*new), KM_SLEEP);
/* Copy body */
- SLIST_INIT(&new->hfi_head);
dst = &new->hfi_family;
*dst = *src;
+ SLIST_INIT(&new->hfi_head);
+ TAILQ_INIT(&new->hfi_nhead);
+
/* Copy name */
dst->hf_name = (char *)kmem_alloc(strlen(src->hf_name) + 1, KM_SLEEP);
(void) strcpy(dst->hf_name, src->hf_name);
@@ -338,13 +859,13 @@ hook_family_copy(hook_family_t *src)
return (new);
}
-
/*
+ * Function: hook_family_find
* Returns: internal family pointer - NULL = Not match
* Parameters: family(I) - family name string
*
* Search family list with family name
- * A lock on familylock must be held when called.
+ * A lock on hfi_lock must be held when called.
*/
static hook_family_int_t *
hook_family_find(char *family, hook_stack_t *hks)
@@ -360,29 +881,90 @@ hook_family_find(char *family, hook_stack_t *hks)
return (hfi);
}
-
/*
- * Function: hook_family_free
- * Returns: None
- * Parameters: hfi(I) - internal family pointer
+ * Function: hook_family_notify_register
+ * Returns: 0 = success, else failure
+ * Parameters: hfi(I) - hook family
+ * callback(I) - function to be called
+ * arg(I) - arg to provide callback when it is called
*
- * Free alloc memory for family
+ * So long as this hook stack isn't being shut down, register a new
+ * callback to be activated each time a new event is added to this
+ * family.
+ *
+ * To call this function we must have an active handle in use on the family,
+ * so if we take this into account, then neither the hook_family_int_t nor
+ * the hook_stack_t that owns it can disappear. We have to put some trust
+ * in the callers to be properly synchronised...
+ *
+ * Holding hks_lock is required to provide synchronisation for hks_shutdown.
*/
-static void
-hook_family_free(hook_family_int_t *hfi)
+int
+hook_family_notify_register(hook_family_int_t *hfi,
+ hook_notify_fn_t callback, void *arg)
{
- ASSERT(hfi != NULL);
+ hook_stack_t *hks;
+ int error;
- /* Free name space */
- if (hfi->hfi_family.hf_name != NULL) {
- kmem_free(hfi->hfi_family.hf_name,
- strlen(hfi->hfi_family.hf_name) + 1);
+ hks = hfi->hfi_stack;
+
+ CVW_ENTER_READ(&hks->hks_lock);
+ CVW_ENTER_WRITE(&hfi->hfi_lock);
+
+ if (hfi->hfi_stack->hks_shutdown != 0) {
+ CVW_EXIT_WRITE(&hfi->hfi_lock);
+ CVW_EXIT_READ(&hks->hks_lock);
+ return (ESHUTDOWN);
}
- /* Free container */
- kmem_free(hfi, sizeof (*hfi));
+ error = hook_notify_register(&hfi->hfi_lock, &hfi->hfi_nhead,
+ callback, arg);
+
+ CVW_EXIT_WRITE(&hfi->hfi_lock);
+ CVW_EXIT_READ(&hks->hks_lock);
+
+ return (error);
}
+/*
+ * Function: hook_family_notify_unregister
+ * Returns: 0 = success, else failure
+ * Parameters: hfi(I) - hook family
+ * callback(I) - function to be called
+ *
+ * Remove a callback from the list of those executed when a new event is
+ * added to a hook family.
+ */
+int
+hook_family_notify_unregister(hook_family_int_t *hfi,
+ hook_notify_fn_t callback)
+{
+ boolean_t free_family;
+ int error;
+
+ CVW_ENTER_WRITE(&hfi->hfi_lock);
+
+ error = hook_notify_unregister(&hfi->hfi_lock, &hfi->hfi_nhead,
+ callback);
+
+ /*
+ * If hook_family_remove has been called but the structure was still
+ * "busy" ... but we might have just made it "unbusy"...
+ */
+ if ((error == 0) && hfi->hfi_condemned &&
+ SLIST_EMPTY(&hfi->hfi_head) && TAILQ_EMPTY(&hfi->hfi_nhead)) {
+ free_family = B_TRUE;
+ } else {
+ free_family = B_FALSE;
+ }
+
+ CVW_EXIT_WRITE(&hfi->hfi_lock);
+
+ if (free_family)
+ hook_family_free(hfi, hfi->hfi_stack);
+
+ return (error);
+}
/*
* Function: hook_event_add
@@ -398,35 +980,110 @@ hook_family_free(hook_family_int_t *hfi)
hook_event_int_t *
hook_event_add(hook_family_int_t *hfi, hook_event_t *he)
{
- hook_stack_t *hks;
hook_event_int_t *hei, *new;
+ hook_stack_t *hks;
ASSERT(hfi != NULL);
ASSERT(he != NULL);
ASSERT(he->he_name != NULL);
- hks = (hook_stack_t *)hfi->hfi_ptr;
new = hook_event_copy(he);
if (new == NULL)
return (NULL);
- CVW_ENTER_WRITE(&hks->hks_familylock);
+ hks = hfi->hfi_stack;
+ CVW_ENTER_READ(&hks->hks_lock);
+
+ hks = hfi->hfi_stack;
+ if (hks->hks_shutdown != 0) {
+ CVW_EXIT_READ(&hks->hks_lock);
+ hook_event_free(new, NULL);
+ return (NULL);
+ }
/* Check whether this event pointer is already registered */
hei = hook_event_checkdup(he, hks);
if (hei != NULL) {
- CVW_EXIT_WRITE(&hks->hks_familylock);
- hook_event_free(new);
+ CVW_EXIT_READ(&hks->hks_lock);
+ hook_event_free(new, NULL);
return (NULL);
}
+ CVW_ENTER_WRITE(&hfi->hfi_lock);
+
+ if (hfi->hfi_condemned) {
+ CVW_EXIT_WRITE(&hfi->hfi_lock);
+ CVW_EXIT_READ(&hks->hks_lock);
+ hook_event_free(new, NULL);
+ return (NULL);
+ }
+
+ if (hook_wait_setflag(&hfi->hfi_waiter, FWF_WAIT_MASK,
+ FWF_ADD_WANTED, FWF_ADD_ACTIVE) == -1) {
+ CVW_EXIT_WRITE(&hfi->hfi_lock);
+ CVW_EXIT_READ(&hks->hks_lock);
+ hook_event_free(new, NULL);
+ return (NULL);
+ }
+
+ TAILQ_INIT(&new->hei_nhead);
+
+ hook_event_init_kstats(hfi, new);
+ hook_wait_init(&new->hei_waiter, &new->hei_lock);
+
/* Add to event list head */
SLIST_INSERT_HEAD(&hfi->hfi_head, new, hei_entry);
- CVW_EXIT_WRITE(&hks->hks_familylock);
+ CVW_EXIT_WRITE(&hfi->hfi_lock);
+
+ CVW_EXIT_READ(&hks->hks_lock);
+
+ hook_notify_run(&hfi->hfi_nhead,
+ hfi->hfi_family.hf_name, NULL, he->he_name, HN_REGISTER);
+
+ hook_wait_unsetflag(&hfi->hfi_waiter, FWF_ADD_ACTIVE);
+
return (new);
}
+/*
+ * Function: hook_event_init_kstats
+ * Returns: None
+ * Parameters: hfi(I) - pointer to the family that owns this event.
+ * hei(I) - pointer to the hook event that needs some kstats.
+ *
+ * Create a set of kstats that relate to each event registered with
+ * the hook framework. A counter is kept for each time the event is
+ * activated and for each time a hook is added or removed. As the
+ * kstats just count the events as they happen, the total number of
+ * hooks registered must be obtained by subtractived removed from added.
+ */
+static void
+hook_event_init_kstats(hook_family_int_t *hfi, hook_event_int_t *hei)
+{
+ hook_event_kstat_t template = {
+ { "hooksAdded", KSTAT_DATA_UINT64 },
+ { "hooksRemoved", KSTAT_DATA_UINT64 },
+ { "events", KSTAT_DATA_UINT64 }
+ };
+ hook_stack_t *hks;
+
+ hks = hfi->hfi_stack;
+ hei->hei_kstatp = kstat_create_netstack(hfi->hfi_family.hf_name, 0,
+ hei->hei_event->he_name, "hook_event", KSTAT_TYPE_NAMED,
+ sizeof (hei->hei_kstats) / sizeof (kstat_named_t),
+ KSTAT_FLAG_VIRTUAL, hks->hks_netstackid);
+
+ bcopy((char *)&template, &hei->hei_kstats, sizeof (template));
+
+ if (hei->hei_kstatp != NULL) {
+ hei->hei_kstatp->ks_data = (void *)&hei->hei_kstats;
+ hei->hei_kstatp->ks_private =
+ (void *)(uintptr_t)hks->hks_netstackid;
+
+ kstat_install(hei->hei_kstatp);
+ }
+}
/*
* Function: hook_event_remove
@@ -435,48 +1092,136 @@ hook_event_add(hook_family_int_t *hfi, hook_event_t *he)
* he(I) - event pointer
*
* Remove event from event list on specific family
+ *
+ * This function assumes that the caller has received a pointer to a the
+ * hook_family_int_t via a call to net_protocol_lookup or net_protocol_unreg'.
+ * This the hook_family_int_t is guaranteed to be around for the life of this
+ * call, unless the caller has decided to call net_protocol_release or
+ * net_protocol_unregister before calling net_event_unregister - an error.
*/
int
hook_event_remove(hook_family_int_t *hfi, hook_event_t *he)
{
- hook_stack_t *hks;
+ boolean_t free_family;
hook_event_int_t *hei;
ASSERT(hfi != NULL);
ASSERT(he != NULL);
- hks = (hook_stack_t *)hfi->hfi_ptr;
- CVW_ENTER_WRITE(&hks->hks_familylock);
+ CVW_ENTER_WRITE(&hfi->hfi_lock);
+
+ /*
+ * Set the flag so that we can call hook_event_notify_run without
+ * holding any locks but at the same time prevent other changes to
+ * the event at the same time.
+ */
+ if (hook_wait_setflag(&hfi->hfi_waiter, FWF_WAIT_MASK,
+ FWF_DEL_WANTED, FWF_DEL_ACTIVE) == -1) {
+ CVW_EXIT_WRITE(&hfi->hfi_lock);
+ return (ENXIO);
+ }
hei = hook_event_find(hfi, he->he_name);
if (hei == NULL) {
- CVW_EXIT_WRITE(&hks->hks_familylock);
- return (ENXIO);
+ hook_wait_unsetflag(&hfi->hfi_waiter, FWF_DEL_ACTIVE);
+ CVW_EXIT_WRITE(&hfi->hfi_lock);
+ return (ESRCH);
}
- /* Check if there are registered hooks for this event */
- if (!TAILQ_EMPTY(&hei->hei_head)) {
- CVW_EXIT_WRITE(&hks->hks_familylock);
- return (EBUSY);
+ free_family = B_FALSE;
+
+ CVW_ENTER_WRITE(&hei->hei_lock);
+ /*
+ * If there are any hooks still registered for this event or
+ * there are any notifiers registered, return an error indicating
+ * that the event is still busy.
+ */
+ if (!TAILQ_EMPTY(&hei->hei_head) || !TAILQ_EMPTY(&hei->hei_nhead)) {
+ hei->hei_condemned = B_TRUE;
+ CVW_EXIT_WRITE(&hei->hei_lock);
+ } else {
+ /* hei_condemned = B_FALSE is implied from creation */
+ /*
+ * Even though we know the notify list is empty, we call
+ * hook_wait_destroy here to synchronise wait removing a
+ * hook from an event.
+ */
+ hook_wait_destroy(&hei->hei_waiter);
+
+ CVW_EXIT_WRITE(&hei->hei_lock);
+
+ if (hfi->hfi_condemned && SLIST_EMPTY(&hfi->hfi_head) &&
+ TAILQ_EMPTY(&hfi->hfi_nhead))
+ free_family = B_TRUE;
}
- /* Remove from event list */
- SLIST_REMOVE(&hfi->hfi_head, hei, hook_event_int, hei_entry);
+ CVW_EXIT_WRITE(&hfi->hfi_lock);
+
+ hook_notify_run(&hfi->hfi_nhead,
+ hfi->hfi_family.hf_name, NULL, he->he_name, HN_UNREGISTER);
- CVW_EXIT_WRITE(&hks->hks_familylock);
- hook_event_free(hei);
+ hook_wait_unsetflag(&hfi->hfi_waiter, FWF_DEL_ACTIVE);
+
+ if (!hei->hei_condemned) {
+ hook_event_free(hei, hfi);
+ if (free_family)
+ hook_family_free(hfi, hfi->hfi_stack);
+ }
return (0);
}
+/*
+ * Function: hook_event_free
+ * Returns: None
+ * Parameters: hei(I) - internal event pointer
+ *
+ * Free alloc memory for event
+ */
+static void
+hook_event_free(hook_event_int_t *hei, hook_family_int_t *hfi)
+{
+ boolean_t free_family;
+
+ ASSERT(hei != NULL);
+
+ if (hfi != NULL) {
+ CVW_ENTER_WRITE(&hfi->hfi_lock);
+ /*
+ * Remove the event from the hook family's list.
+ */
+ SLIST_REMOVE(&hfi->hfi_head, hei, hook_event_int, hei_entry);
+ if (hfi->hfi_condemned && SLIST_EMPTY(&hfi->hfi_head) &&
+ TAILQ_EMPTY(&hfi->hfi_nhead)) {
+ free_family = B_TRUE;
+ } else {
+ free_family = B_FALSE;
+ }
+ CVW_EXIT_WRITE(&hfi->hfi_lock);
+ }
+
+ if (hei->hei_kstatp != NULL) {
+ ASSERT(hfi != NULL);
+
+ kstat_delete_netstack(hei->hei_kstatp,
+ hfi->hfi_stack->hks_netstackid);
+ hei->hei_kstatp = NULL;
+ }
+
+ /* Free container */
+ kmem_free(hei, sizeof (*hei));
+
+ if (free_family)
+ hook_family_free(hfi, hfi->hfi_stack);
+}
/*
* Function: hook_event_checkdup
* Returns: internal event pointer - NULL = Not match
* Parameters: he(I) - event pointer
*
- * Search whole list with event pointer
- * A lock on familylock must be held when called.
+ * Search all of the hook families to see if the event being passed in
+ * has already been associated with one.
*/
static hook_event_int_t *
hook_event_checkdup(hook_event_t *he, hook_stack_t *hks)
@@ -486,17 +1231,20 @@ hook_event_checkdup(hook_event_t *he, hook_stack_t *hks)
ASSERT(he != NULL);
+ CVW_ENTER_READ(&hks->hks_lock);
SLIST_FOREACH(hfi, &hks->hks_familylist, hfi_entry) {
SLIST_FOREACH(hei, &hfi->hfi_head, hei_entry) {
- if (hei->hei_event == he)
+ if (hei->hei_event == he) {
+ CVW_EXIT_READ(&hks->hks_lock);
return (hei);
+ }
}
}
+ CVW_EXIT_READ(&hks->hks_lock);
return (NULL);
}
-
/*
* Function: hook_event_copy
* Returns: internal event pointer - NULL = Failed
@@ -522,7 +1270,6 @@ hook_event_copy(hook_event_t *src)
return (new);
}
-
/*
* Function: hook_event_find
* Returns: internal event pointer - NULL = Not match
@@ -530,7 +1277,7 @@ hook_event_copy(hook_event_t *src)
* event(I) - event name string
*
* Search event list with event name
- * A lock on hks->hks_familylock must be held when called.
+ * A lock on hfi->hfi_lock must be held when called.
*/
static hook_event_int_t *
hook_event_find(hook_family_int_t *hfi, char *event)
@@ -541,30 +1288,157 @@ hook_event_find(hook_family_int_t *hfi, char *event)
ASSERT(event != NULL);
SLIST_FOREACH(hei, &hfi->hfi_head, hei_entry) {
- if (strcmp(hei->hei_event->he_name, event) == 0)
+ if ((strcmp(hei->hei_event->he_name, event) == 0) &&
+ ((hei->hei_waiter.fw_flags & FWF_UNSAFE) == 0))
break;
}
return (hei);
}
+/*
+ * Function: hook_event_notify_register
+ * Returns: 0 = success, else failure
+ * Parameters: hfi(I) - hook family
+ * event(I) - name of the event
+ * callback(I) - function to be called
+ * arg(I) - arg to provide callback when it is called
+ *
+ * Adds a new callback to the event named by "event" (we must find it)
+ * that will be executed each time a new hook is added to the event.
+ * Of course, if the stack is being shut down, this call should fail.
+ */
+int
+hook_event_notify_register(hook_family_int_t *hfi, char *event,
+ hook_notify_fn_t callback, void *arg)
+{
+ hook_event_int_t *hei;
+ hook_stack_t *hks;
+ int error;
+
+ hks = hfi->hfi_stack;
+ CVW_ENTER_READ(&hks->hks_lock);
+ if (hks->hks_shutdown != 0) {
+ CVW_EXIT_READ(&hks->hks_lock);
+ return (ESHUTDOWN);
+ }
+
+ CVW_ENTER_READ(&hfi->hfi_lock);
+
+ if (hfi->hfi_condemned) {
+ CVW_EXIT_READ(&hfi->hfi_lock);
+ CVW_EXIT_READ(&hks->hks_lock);
+ return (ESHUTDOWN);
+ }
+
+ hei = hook_event_find(hfi, event);
+ if (hei == NULL) {
+ CVW_EXIT_READ(&hfi->hfi_lock);
+ CVW_EXIT_READ(&hks->hks_lock);
+ return (ESRCH);
+ }
+
+ /*
+ * Grabbing the read lock on hei_lock is only so that we can
+ * synchronise access to hei_condemned.
+ */
+ CVW_ENTER_WRITE(&hei->hei_lock);
+ if (hei->hei_condemned) {
+ CVW_EXIT_WRITE(&hei->hei_lock);
+ CVW_EXIT_READ(&hfi->hfi_lock);
+ CVW_EXIT_READ(&hks->hks_lock);
+ return (ESHUTDOWN);
+ }
+
+ error = hook_notify_register(&hei->hei_lock, &hei->hei_nhead,
+ callback, arg);
+
+ CVW_EXIT_WRITE(&hei->hei_lock);
+ CVW_EXIT_READ(&hfi->hfi_lock);
+ CVW_EXIT_READ(&hks->hks_lock);
+
+ return (error);
+}
/*
- * Function: hook_event_free
+ * Function: hook_event_notify_unregister
+ * Returns: 0 = success, else failure
+ * Parameters: hfi(I) - hook family
+ * event(I) - name of the event
+ * callback(I) - function to be called
+ *
+ * Remove the given callback from the named event's list of functions
+ * to call when a hook is added or removed.
+ */
+int
+hook_event_notify_unregister(hook_family_int_t *hfi, char *event,
+ hook_notify_fn_t callback)
+{
+ hook_event_int_t *hei;
+ boolean_t free_event;
+ int error;
+
+ CVW_ENTER_READ(&hfi->hfi_lock);
+
+ hei = hook_event_find(hfi, event);
+ if (hei == NULL) {
+ CVW_EXIT_READ(&hfi->hfi_lock);
+ return (ESRCH);
+ }
+
+ CVW_ENTER_WRITE(&hei->hei_lock);
+
+ error = hook_notify_unregister(&hei->hei_lock, &hei->hei_nhead,
+ callback);
+
+ /*
+ * hei_condemned has been set if someone tried to remove the
+ * event but couldn't because there were still things attached to
+ * it. Now that we've done a successful remove, if it is now empty
+ * then by all rights we should be free'ing it too. Note that the
+ * expectation is that only the caller of hook_event_add will ever
+ * call hook_event_remove.
+ */
+ if ((error == 0) && hei->hei_condemned &&
+ TAILQ_EMPTY(&hei->hei_head) && TAILQ_EMPTY(&hei->hei_nhead)) {
+ free_event = B_TRUE;
+ } else {
+ free_event = B_FALSE;
+ }
+
+ CVW_EXIT_WRITE(&hei->hei_lock);
+ CVW_EXIT_READ(&hfi->hfi_lock);
+
+ if (free_event) {
+ /*
+ * It is safe to pass in hfi here, without a lock, because
+ * our structure (hei) is still on one of its lists and thus
+ * it won't be able to disappear yet...
+ */
+ hook_event_free(hei, hfi);
+ }
+
+ return (error);
+}
+
+/*
+ * Function: hook_event_notify_run
* Returns: None
- * Parameters: hei(I) - internal event pointer
+ * Parameters: nrun(I) - pointer to the list of callbacks to execute
+ * hfi(I) - hook stack pointer to execute callbacks for
+ * name(I) - name of a hook family
+ * cmd(I) - either HN_UNREGISTER or HN_REGISTER
*
- * Free alloc memory for event
+ * Execute all of the callbacks registered for this event.
*/
static void
-hook_event_free(hook_event_int_t *hei)
+hook_event_notify_run(hook_event_int_t *hei, hook_family_int_t *hfi,
+ char *event, char *name, hook_notify_cmd_t cmd)
{
- ASSERT(hei != NULL);
- /* Free container */
- kmem_free(hei, sizeof (*hei));
+ hook_notify_run(&hei->hei_nhead, hfi->hfi_family.hf_name,
+ event, name, cmd);
}
-
/*
* Function: hook_register
* Returns: int- 0 = Succ, Else = Fail
@@ -572,19 +1446,21 @@ hook_event_free(hook_event_int_t *hei)
* event(I) - event name string
* h(I) - hook pointer
*
- * Add new hook to hook list on spefic family, event
+ * Add new hook to hook list on the specified family and event.
*/
int
hook_register(hook_family_int_t *hfi, char *event, hook_t *h)
{
- hook_stack_t *hks;
hook_event_int_t *hei;
hook_int_t *hi, *new;
+ int error;
ASSERT(hfi != NULL);
ASSERT(event != NULL);
ASSERT(h != NULL);
- hks = (hook_stack_t *)hfi->hfi_ptr;
+
+ if (hfi->hfi_stack->hks_shutdown)
+ return (NULL);
/* Alloc hook_int_t and copy hook */
new = hook_copy(h);
@@ -596,43 +1472,255 @@ hook_register(hook_family_int_t *hfi, char *event, hook_t *h)
* to hold global family write lock. Just get read lock here to
* ensure event will not be removed when doing hooks operation
*/
- CVW_ENTER_READ(&hks->hks_familylock);
+ CVW_ENTER_WRITE(&hfi->hfi_lock);
hei = hook_event_find(hfi, event);
if (hei == NULL) {
- CVW_EXIT_READ(&hks->hks_familylock);
- hook_free(new);
+ CVW_EXIT_WRITE(&hfi->hfi_lock);
+ hook_int_free(new, hfi->hfi_stack->hks_netstackid);
return (ENXIO);
}
CVW_ENTER_WRITE(&hei->hei_lock);
- /* Multiple hooks are only allowed for read-only events. */
- if (((hei->hei_event->he_flags & HOOK_RDONLY) == 0) &&
- (!TAILQ_EMPTY(&hei->hei_head))) {
- CVW_EXIT_WRITE(&hei->hei_lock);
- CVW_EXIT_READ(&hks->hks_familylock);
- hook_free(new);
- return (EEXIST);
- }
-
hi = hook_find(hei, h);
if (hi != NULL) {
+ error = EEXIST;
+ goto bad_add;
+ }
+
+ if (hook_wait_setflag(&hei->hei_waiter, FWF_WAIT_MASK,
+ FWF_ADD_WANTED, FWF_ADD_ACTIVE) == -1) {
+ error = ENOENT;
+bad_add:
CVW_EXIT_WRITE(&hei->hei_lock);
- CVW_EXIT_READ(&hks->hks_familylock);
- hook_free(new);
- return (EEXIST);
+ CVW_EXIT_WRITE(&hfi->hfi_lock);
+ hook_int_free(new, hfi->hfi_stack->hks_netstackid);
+ return (error);
}
/* Add to hook list head */
- TAILQ_INSERT_HEAD(&hei->hei_head, new, hi_entry);
- hei->hei_event->he_interested = B_TRUE;
+ error = hook_insert(&hei->hei_head, new);
+ if (error == 0) {
+ hei->hei_event->he_interested = B_TRUE;
+ hei->hei_kstats.hooks_added.value.ui64++;
+
+ hook_init_kstats(hfi, hei, new);
+ }
CVW_EXIT_WRITE(&hei->hei_lock);
- CVW_EXIT_READ(&hks->hks_familylock);
+ CVW_EXIT_WRITE(&hfi->hfi_lock);
+
+ /*
+ * Note that the name string passed through to the notify callbacks
+ * is from the original hook being registered, not the copy being
+ * inserted.
+ */
+ if (error == 0) {
+ hook_event_notify_run(hei, hfi, event, h->h_name, HN_REGISTER);
+ hook_wait_unsetflag(&hei->hei_waiter, FWF_ADD_ACTIVE);
+ }
+
+ return (error);
+}
+
+/*
+ * Function: hook_insert
+ * Returns: int- 0 = Succ, else = Fail
+ * Parameters: head(I) - pointer to hook list to insert hook onto
+ * new(I) - pointer to hook to be inserted
+ *
+ * Try to insert the hook onto the list of hooks according to the hints
+ * given in the hook to be inserted and those that already exist on the
+ * list. For now, the implementation permits only a single hook to be
+ * either first or last and names provided with before or after are only
+ * loosely coupled with the action.
+ */
+static int
+hook_insert(hook_int_head_t *head, hook_int_t *new)
+{
+ hook_int_t *before;
+ hook_int_t *hi;
+ hook_t *hih;
+ hook_t *h = &new->hi_hook;
+
+ switch (new->hi_hook.h_hint) {
+ case HH_NONE :
+ before = NULL;
+ /*
+ * If there is no hint present (or not one that can be
+ * satisfied now) then try to at least respect the wishes
+ * of those that want to be last. If there are none wanting
+ * to be last then add the new hook to the tail of the
+ * list - this means we keep any wanting to be first
+ * happy without having to search for HH_FIRST.
+ */
+ TAILQ_FOREACH(hi, head, hi_entry) {
+ hih = &hi->hi_hook;
+ if ((hih->h_hint == HH_AFTER) &&
+ (strcmp(h->h_name,
+ (char *)hih->h_hintvalue) == 0)) {
+ TAILQ_INSERT_BEFORE(hi, new, hi_entry);
+ return (0);
+ }
+ if ((hih->h_hint == HH_BEFORE) && (before == NULL) &&
+ (strcmp(h->h_name,
+ (char *)hih->h_hintvalue) == 0)) {
+ before = hi;
+ }
+ }
+ if (before != NULL) {
+ TAILQ_INSERT_AFTER(head, before, new, hi_entry);
+ return (0);
+ }
+ hook_insert_plain(head, new);
+ break;
+
+ case HH_FIRST :
+ hi = TAILQ_FIRST(head);
+ if ((hi != NULL) && (hi->hi_hook.h_hint == HH_FIRST))
+ return (EBUSY);
+ TAILQ_INSERT_HEAD(head, new, hi_entry);
+ break;
+
+ case HH_LAST :
+ hi = TAILQ_LAST(head, hook_int_head);
+ if ((hi != NULL) && (hi->hi_hook.h_hint == HH_LAST))
+ return (EBUSY);
+ TAILQ_INSERT_TAIL(head, new, hi_entry);
+ break;
+
+ case HH_BEFORE :
+ hi = hook_find_byname(head, (char *)new->hi_hook.h_hintvalue);
+ if (hi == NULL)
+ return (hook_insert_afterbefore(head, new));
+
+ if (hi->hi_hook.h_hint == HH_FIRST)
+ return (EBUSY);
+
+ TAILQ_INSERT_BEFORE(hi, new, hi_entry);
+ break;
+
+ case HH_AFTER :
+ hi = hook_find_byname(head, (char *)new->hi_hook.h_hintvalue);
+ if (hi == NULL)
+ return (hook_insert_afterbefore(head, new));
+
+ if (hi->hi_hook.h_hint == HH_LAST)
+ return (EBUSY);
+
+ TAILQ_INSERT_AFTER(head, hi, new, hi_entry);
+ break;
+
+ default :
+ return (EINVAL);
+ }
+
return (0);
}
+/*
+ * Function: hook_insert_plain
+ * Returns: int- 0 = success, else = failure
+ * Parameters: head(I) - pointer to hook list to insert hook onto
+ * new(I) - pointer to hook to be inserted
+ *
+ * Insert a hook such that it respects the wishes of those that want to
+ * be last. If there are none wanting to be last then add the new hook
+ * to the tail of the list - this means we keep any wanting to be first
+ * happy without having to search for HH_FIRST.
+ */
+static void
+hook_insert_plain(hook_int_head_t *head, hook_int_t *new)
+{
+ hook_int_t *hi;
+
+ hi = TAILQ_FIRST(head);
+ if (hi != NULL) {
+ if (hi->hi_hook.h_hint == HH_LAST) {
+ TAILQ_INSERT_BEFORE(hi, new, hi_entry);
+ } else {
+ TAILQ_INSERT_TAIL(head, new, hi_entry);
+ }
+ } else {
+ TAILQ_INSERT_TAIL(head, new, hi_entry);
+ }
+}
+
+/*
+ * Function: hook_insert_afterbefore
+ * Returns: int- 0 = success, else = failure
+ * Parameters: head(I) - pointer to hook list to insert hook onto
+ * new(I) - pointer to hook to be inserted
+ *
+ * Simple insertion of a hook specifying a HH_BEFORE or HH_AFTER was not
+ * possible, so now we need to be more careful. The first pass is to go
+ * through the list and look for any other hooks that also specify the
+ * same hint name as the new one. The object of this exercise is to make
+ * sure that hooks with HH_BEFORE always appear on the list before those
+ * with HH_AFTER so that when said hook arrives, it can be placed in the
+ * middle of the BEFOREs and AFTERs. If this condition does not arise,
+ * just use hook_insert_plain() to try and insert the hook somewhere that
+ * is innocuous to existing efforts.
+ */
+static int
+hook_insert_afterbefore(hook_int_head_t *head, hook_int_t *new)
+{
+ hook_int_t *hi;
+ hook_t *nh;
+ hook_t *h;
+
+ nh = &new->hi_hook;
+ ASSERT(new->hi_hook.h_hint != HH_NONE);
+ ASSERT(new->hi_hook.h_hint != HH_LAST);
+ ASSERT(new->hi_hook.h_hint != HH_FIRST);
+
+ /*
+ * First, look through the list to see if there are any other
+ * before's or after's that have a matching hint name.
+ */
+ TAILQ_FOREACH(hi, head, hi_entry) {
+ h = &hi->hi_hook;
+ switch (h->h_hint) {
+ case HH_FIRST :
+ case HH_LAST :
+ case HH_NONE :
+ break;
+ case HH_BEFORE :
+ if ((nh->h_hint == HH_BEFORE) &&
+ (strcmp((char *)h->h_hintvalue,
+ (char *)nh->h_hintvalue) == 0)) {
+ TAILQ_INSERT_AFTER(head, hi, new, hi_entry);
+ return (0);
+ }
+ if ((nh->h_hint == HH_AFTER) &&
+ (strcmp((char *)h->h_hintvalue,
+ (char *)nh->h_hintvalue) == 0)) {
+ TAILQ_INSERT_BEFORE(hi, new, hi_entry);
+ return (0);
+ }
+ break;
+ case HH_AFTER :
+ if ((nh->h_hint == HH_AFTER) &&
+ (strcmp((char *)h->h_hintvalue,
+ (char *)nh->h_hintvalue) == 0)) {
+ TAILQ_INSERT_AFTER(head, hi, new, hi_entry);
+ return (0);
+ }
+ if ((nh->h_hint == HH_BEFORE) &&
+ (strcmp((char *)h->h_hintvalue,
+ (char *)nh->h_hintvalue) == 0)) {
+ TAILQ_INSERT_BEFORE(hi, new, hi_entry);
+ return (0);
+ }
+ break;
+ }
+ }
+
+ hook_insert_plain(head, new);
+
+ return (0);
+}
/*
* Function: hook_unregister
@@ -646,19 +1734,18 @@ hook_register(hook_family_int_t *hfi, char *event, hook_t *h)
int
hook_unregister(hook_family_int_t *hfi, char *event, hook_t *h)
{
- hook_stack_t *hks;
hook_event_int_t *hei;
hook_int_t *hi;
+ boolean_t free_event;
ASSERT(hfi != NULL);
ASSERT(h != NULL);
- hks = (hook_stack_t *)hfi->hfi_ptr;
- CVW_ENTER_READ(&hks->hks_familylock);
+ CVW_ENTER_WRITE(&hfi->hfi_lock);
hei = hook_event_find(hfi, event);
if (hei == NULL) {
- CVW_EXIT_READ(&hks->hks_familylock);
+ CVW_EXIT_WRITE(&hfi->hfi_lock);
return (ENXIO);
}
@@ -668,23 +1755,72 @@ hook_unregister(hook_family_int_t *hfi, char *event, hook_t *h)
hi = hook_find(hei, h);
if (hi == NULL) {
CVW_EXIT_WRITE(&hei->hei_lock);
- CVW_EXIT_READ(&hks->hks_familylock);
+ CVW_EXIT_WRITE(&hfi->hfi_lock);
return (ENXIO);
}
+ if (hook_wait_setflag(&hei->hei_waiter, FWF_WAIT_MASK,
+ FWF_DEL_WANTED, FWF_DEL_ACTIVE) == -1) {
+ CVW_EXIT_WRITE(&hei->hei_lock);
+ CVW_EXIT_WRITE(&hfi->hfi_lock);
+ return (ENOENT);
+ }
+
/* Remove from hook list */
TAILQ_REMOVE(&hei->hei_head, hi, hi_entry);
+
+ free_event = B_FALSE;
if (TAILQ_EMPTY(&hei->hei_head)) {
hei->hei_event->he_interested = B_FALSE;
+ /*
+ * If the delete pending flag has been set and there are
+ * no notifiers on the event (and we've removed the last
+ * hook) then we need to free this event after we're done.
+ */
+ if (hei->hei_condemned && TAILQ_EMPTY(&hei->hei_nhead))
+ free_event = B_TRUE;
}
+ hei->hei_kstats.hooks_removed.value.ui64++;
CVW_EXIT_WRITE(&hei->hei_lock);
- CVW_EXIT_READ(&hks->hks_familylock);
+ CVW_EXIT_WRITE(&hfi->hfi_lock);
+ /*
+ * While the FWF_DEL_ACTIVE flag is set, the hook_event_int_t
+ * will not be free'd and thus the hook_family_int_t wil not
+ * be free'd either.
+ */
+ hook_event_notify_run(hei, hfi, event, h->h_name, HN_UNREGISTER);
+ hook_wait_unsetflag(&hei->hei_waiter, FWF_DEL_ACTIVE);
+
+ hook_int_free(hi, hfi->hfi_stack->hks_netstackid);
+
+ if (free_event)
+ hook_event_free(hei, hfi);
- hook_free(hi);
return (0);
}
+/*
+ * Function: hook_find_byname
+ * Returns: internal hook pointer - NULL = Not match
+ * Parameters: hei(I) - internal event pointer
+ * name(I)- hook name
+ *
+ * Search an event's list of hooks to see if there is a hook present that
+ * has a matching name to the one being looked for.
+ */
+static hook_int_t *
+hook_find_byname(hook_int_head_t *head, char *name)
+{
+ hook_int_t *hi;
+
+ TAILQ_FOREACH(hi, head, hi_entry) {
+ if (strcmp(hi->hi_hook.h_name, name) == 0)
+ return (hi);
+ }
+
+ return (NULL);
+}
/*
* Function: hook_find
@@ -692,25 +1828,20 @@ hook_unregister(hook_family_int_t *hfi, char *event, hook_t *h)
* Parameters: hei(I) - internal event pointer
* h(I) - hook pointer
*
- * Search hook list
- * A lock on familylock must be held when called.
+ * Search an event's list of hooks to see if there is already one that
+ * matches the hook being passed in. Currently the only criteria for a
+ * successful search here is for the names to be the same.
*/
static hook_int_t *
hook_find(hook_event_int_t *hei, hook_t *h)
{
- hook_int_t *hi;
ASSERT(hei != NULL);
ASSERT(h != NULL);
- TAILQ_FOREACH(hi, &hei->hei_head, hi_entry) {
- if (strcmp(hi->hi_hook.h_name, h->h_name) == 0)
- break;
- }
- return (hi);
+ return (hook_find_byname(&hei->hei_head, h->h_name));
}
-
/*
* Function: hook_copy
* Returns: internal hook pointer - NULL = Failed
@@ -718,12 +1849,17 @@ hook_find(hook_event_int_t *hei, hook_t *h)
*
* Allocate internal hook block and duplicate incoming hook.
* No locks should be held across this function as it may sleep.
+ * Because hook_copy() is responsible for the creation of the internal
+ * hook structure that is used here, it takes on population the structure
+ * with the kstat information. Note that while the kstat bits are
+ * seeded here, their installation of the kstats is handled elsewhere.
*/
static hook_int_t *
hook_copy(hook_t *src)
{
hook_int_t *new;
hook_t *dst;
+ int len;
ASSERT(src != NULL);
ASSERT(src->h_name != NULL);
@@ -735,29 +1871,278 @@ hook_copy(hook_t *src)
*dst = *src;
/* Copy name */
- dst->h_name = (char *)kmem_alloc(strlen(src->h_name) + 1, KM_SLEEP);
+ len = strlen(src->h_name);
+ dst->h_name = (char *)kmem_alloc(len + 1, KM_SLEEP);
(void) strcpy(dst->h_name, src->h_name);
+ /*
+ * This is initialised in this manner to make it safer to use the
+ * same pointer in the kstats field.
+ */
+ dst->h_hintvalue = (uintptr_t)"";
+
+ if (dst->h_hint == HH_BEFORE || dst->h_hint == HH_AFTER) {
+ len = strlen((char *)src->h_hintvalue);
+ if (len > 0) {
+ dst->h_hintvalue = (uintptr_t)kmem_alloc(len + 1,
+ KM_SLEEP);
+ (void) strcpy((char *)dst->h_hintvalue,
+ (char *)src->h_hintvalue);
+ }
+ }
+
return (new);
}
/*
- * Function: hook_free
+ * Function: hook_init_kstats
+ * Returns: None
+ * Parameters: hfi(I) - pointer to the family that owns the event.
+ * hei(I) - pointer to the event that owns this hook
+ * hi(I) - pointer to the hook for which we create kstats for
+ *
+ * Each hook that is registered with this framework has its own kstats
+ * set up so that we can provide an easy way in which to observe the
+ * look of hooks (using the kstat command.) The position is set to 0
+ * here but is recalculated after we know the insertion has been a
+ * success.
+ */
+static void
+hook_init_kstats(hook_family_int_t *hfi, hook_event_int_t *hei, hook_int_t *hi)
+{
+ hook_hook_kstat_t template = {
+ { "version", KSTAT_DATA_INT32 },
+ { "flags", KSTAT_DATA_UINT32 },
+ { "hint", KSTAT_DATA_INT32 },
+ { "hint_value", KSTAT_DATA_UINT64 },
+ { "position", KSTAT_DATA_INT32 },
+ { "hook_hits", KSTAT_DATA_UINT64 }
+ };
+ hook_stack_t *hks;
+ size_t kslen;
+ int position;
+ hook_int_t *h;
+
+ kslen = strlen(hfi->hfi_family.hf_name) +
+ strlen(hei->hei_event->he_name) + 2;
+
+ hi->hi_ksname = (char *)kmem_zalloc(kslen, KM_SLEEP);
+ (void) snprintf(hi->hi_ksname, kslen, "%s/%s",
+ hfi->hfi_family.hf_name, hei->hei_event->he_name);
+
+ hks = hfi->hfi_stack;
+ hi->hi_kstatp = kstat_create_netstack(hi->hi_ksname, 0,
+ hi->hi_hook.h_name, "hook", KSTAT_TYPE_NAMED,
+ sizeof (hi->hi_kstats) / sizeof (kstat_named_t),
+ KSTAT_FLAG_VIRTUAL, hks->hks_netstackid);
+
+ /* Initialise the kstats for the structure */
+ bcopy(&template, &hi->hi_kstats, sizeof (template));
+ hi->hi_kstats.hook_version.value.i32 = hi->hi_hook.h_version;
+ hi->hi_kstats.hook_flags.value.ui32 = hi->hi_hook.h_flags;
+ hi->hi_kstats.hook_hint.value.i32 = hi->hi_hook.h_hint;
+ hi->hi_kstats.hook_position.value.i32 = 0;
+ hi->hi_kstats.hook_hits.value.ui64 = 0;
+
+ switch (hi->hi_hook.h_hint) {
+ case HH_BEFORE :
+ case HH_AFTER :
+ hi->hi_kstats.hook_hintvalue.data_type = KSTAT_DATA_STRING;
+ hi->hi_kstats.hook_hintvalue.value.ui64 =
+ hi->hi_hook.h_hintvalue;
+ break;
+ default :
+ break;
+ }
+
+ if (hi->hi_kstatp != NULL) {
+ hi->hi_kstatp->ks_data = (void *)&hi->hi_kstats;
+ hi->hi_kstatp->ks_private =
+ (void *)(uintptr_t)hks->hks_netstackid;
+
+ kstat_install(hi->hi_kstatp);
+ }
+
+ position = 1;
+ TAILQ_FOREACH(h, &hei->hei_head, hi_entry) {
+ h->hi_kstats.hook_position.value.ui32 = position++;
+ }
+}
+
+/*
+ * Function: hook_int_free
* Returns: None
* Parameters: hi(I) - internal hook pointer
*
* Free alloc memory for hook
*/
static void
-hook_free(hook_int_t *hi)
+hook_int_free(hook_int_t *hi, netstackid_t stackid)
{
+ int len;
+
ASSERT(hi != NULL);
/* Free name space */
if (hi->hi_hook.h_name != NULL) {
kmem_free(hi->hi_hook.h_name, strlen(hi->hi_hook.h_name) + 1);
}
+ if (hi->hi_ksname != NULL) {
+ kmem_free(hi->hi_ksname, strlen(hi->hi_ksname) + 1);
+ }
+
+ /* Free the name used with the before/after hints. */
+ switch (hi->hi_hook.h_hint) {
+ case HH_BEFORE :
+ case HH_AFTER :
+ len = strlen((char *)hi->hi_hook.h_hintvalue);
+ if (len > 0)
+ kmem_free((void *)hi->hi_hook.h_hintvalue, len + 1);
+ break;
+ default :
+ break;
+ }
+
+ if (hi->hi_kstatp != NULL)
+ kstat_delete_netstack(hi->hi_kstatp, stackid);
/* Free container */
kmem_free(hi, sizeof (*hi));
}
+
+/*
+ * Function: hook_alloc
+ * Returns: hook_t * - pointer to new hook structure
+ * Parameters: version(I) - version number of the API when compiled
+ *
+ * This function serves as the interface for consumers to obtain a hook_t
+ * structure. At this point in time, there is only a single "version" of
+ * it, leading to a straight forward function. In a perfect world the
+ * h_vesion would be a protected data structure member, but C isn't that
+ * advanced...
+ */
+hook_t *
+hook_alloc(const int h_version)
+{
+ hook_t *h;
+
+ h = kmem_zalloc(sizeof (hook_t), KM_SLEEP);
+ h->h_version = h_version;
+ return (h);
+}
+
+/*
+ * Function: hook_free
+ * Returns: None
+ * Parameters: h(I) - external hook pointer
+ *
+ * This function only free's memory allocated with hook_alloc(), so that if
+ * (for example) kernel memory was allocated for h_name, this needs to be
+ * free'd before calling hook_free().
+ */
+void
+hook_free(hook_t *h)
+{
+ kmem_free(h, sizeof (*h));
+}
+
+/*
+ * Function: hook_notify_register
+ * Returns: 0 = success, else failure
+ * Parameters: lock(I) - netstack identifier
+ * head(I) - top of the list of callbacks
+ * callback(I) - function to be called
+ * arg(I) - arg to pass back to the function
+ *
+ * This function implements the modification of the list of callbacks
+ * that are registered when someone wants to be advised of a change
+ * that has happened.
+ */
+static int
+hook_notify_register(cvwaitlock_t *lock, hook_notify_head_t *head,
+ hook_notify_fn_t callback, void *arg)
+{
+ hook_notify_t *hn;
+
+ CVW_ENTER_WRITE(lock);
+
+ TAILQ_FOREACH(hn, head, hn_entry) {
+ if (hn->hn_func == callback) {
+ CVW_EXIT_WRITE(lock);
+ return (EEXIST);
+ }
+ }
+
+ hn = (hook_notify_t *)kmem_alloc(sizeof (*hn), KM_SLEEP);
+ hn->hn_func = callback;
+ hn->hn_arg = arg;
+ TAILQ_INSERT_TAIL(head, hn, hn_entry);
+
+ CVW_EXIT_WRITE(lock);
+
+ return (0);
+}
+
+/*
+ * Function: hook_stack_notify_register
+ * Returns: 0 = success, else failure
+ * Parameters: stackid(I) - netstack identifier
+ * callback(I) - function to be called
+ *
+ */
+static int
+hook_notify_unregister(cvwaitlock_t *lock, hook_notify_head_t *head,
+ hook_notify_fn_t callback)
+{
+ hook_notify_t *hn;
+
+ CVW_ENTER_WRITE(lock);
+
+ TAILQ_FOREACH(hn, head, hn_entry) {
+ if (hn->hn_func == callback)
+ break;
+ }
+ if (hn == NULL) {
+ CVW_EXIT_WRITE(lock);
+ return (ESRCH);
+ }
+
+ TAILQ_REMOVE(head, hn, hn_entry);
+
+ CVW_EXIT_WRITE(lock);
+
+ kmem_free(hn, sizeof (*hn));
+
+ return (0);
+}
+
+/*
+ * Function: hook_notify_run
+ * Returns: None
+ * Parameters: head(I) - top of the list of callbacks
+ * family(I) - name of the hook family that owns the event
+ * event(I) - name of the event being changed
+ * name(I) - name of the object causing change
+ * cmd(I) - either HN_UNREGISTER or HN_REGISTER
+ *
+ * This function walks through the list of registered callbacks and
+ * executes each one, passing back the arg supplied when registered
+ * and the name of the family (that owns the event), event (the thing
+ * to which we're making a change) and finally a name that describes
+ * what is being added or removed, as indicated by cmd.
+ *
+ * This function does not acquire or release any lock as it is required
+ * that code calling it do so before hand. The use of hook_notify_head_t
+ * is protected by the use of flagwait_t in the structures that own this
+ * list and with the use of the FWF_ADD/DEL_ACTIVE flags.
+ */
+static void
+hook_notify_run(hook_notify_head_t *head, char *family, char *event,
+ char *name, hook_notify_cmd_t cmd)
+{
+ hook_notify_t *hn;
+
+ TAILQ_FOREACH(hn, head, hn_entry) {
+ (*hn->hn_func)(cmd, hn->hn_arg, family, event, name);
+ }
+}