hook.c revision f4b3ec61df05330d25f55a36b975b4d7519fdeb1
/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* or http://www.opensolaris.org/os/licensing.
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright 2007 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>
#include <sys/errno.h>
#include <sys/kmem.h>
#include <sys/mutex.h>
#include <sys/condvar.h>
#include <sys/modctl.h>
#include <sys/hook_impl.h>
#include <sys/sdt.h>
/*
* This file provides kernel hook framework.
*/
static struct modldrv modlmisc = {
&mod_miscops, /* drv_modops */
"Hooks Interface v1.0", /* drv_linkinfo */
};
static struct modlinkage modlinkage = {
MODREV_1, /* ml_rev */
&modlmisc, /* ml_linkage */
NULL
};
/*
* Hook internal functions
*/
static hook_int_t *hook_copy(hook_t *src);
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 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 hook_int_t *hook_find(hook_event_int_t *hei, hook_t *h);
static void hook_free(hook_int_t *hi);
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);
/*
* Module entry points.
*/
int
_init(void)
{
int error;
hook_init();
error = mod_install(&modlinkage);
if (error != 0)
hook_fini();
return (error);
}
int
_fini(void)
{
int error;
error = mod_remove(&modlinkage);
if (error == 0)
hook_fini();
return (error);
}
int
_info(struct modinfo *modinfop)
{
return (mod_info(&modlinkage, modinfop));
}
/*
* Function: hook_init
* Returns: None
* Parameters: None
*
* Initialize hooks
*/
static void
hook_init(void)
{
/*
* We want to be informed each time a stack is created or
* destroyed in the kernel.
*/
netstack_register(NS_HOOK, hook_stack_init, NULL,
hook_stack_fini);
}
/*
* Function: hook_fini
* Returns: None
* Parameters: None
*
* Deinitialize hooks
*/
static void
hook_fini(void)
{
netstack_unregister(NS_HOOK);
}
/*
* Initialize the hook stack instance.
*/
/*ARGSUSED*/
static void *
hook_stack_init(netstackid_t stackid, netstack_t *ns)
{
hook_stack_t *hks;
#ifdef NS_DEBUG
printf("hook_stack_init(stack %d)\n", stackid);
#endif
hks = (hook_stack_t *)kmem_zalloc(sizeof (*hks), KM_SLEEP);
hks->hk_netstack = ns;
CVW_INIT(&hks->hks_familylock);
SLIST_INIT(&hks->hks_familylist);
return (hks);
}
/*
* Free the hook stack instance.
*/
/*ARGSUSED*/
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);
kmem_free(hks, sizeof (*hks));
}
/*
* Function: hook_run
* Returns: int - return value according to callback func
* Parameters: token(I) - event pointer
* info(I) - message
*
* Run hooks for specific provider. The hooks registered are stepped through
* until either the end of the list is reached or a hook function returns a
* non-zero value. If a non-zero value is returned from a hook function, we
* return that value back to our caller. By design, a hook function can be
* called more than once, simultaneously.
*/
int
hook_run(hook_event_token_t token, hook_data_t info, netstack_t *ns)
{
hook_int_t *hi;
hook_event_int_t *hei;
hook_stack_t *hks = ns->netstack_hook;
int rval = 0;
ASSERT(token != NULL);
hei = (hook_event_int_t *)token;
DTRACE_PROBE2(hook__run__start,
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);
TAILQ_FOREACH(hi, &hei->hei_head, hi_entry) {
ASSERT(hi->hi_hook.h_func != NULL);
DTRACE_PROBE3(hook__func__start,
hook_event_token_t, token,
hook_data_t, info,
hook_int_t *, hi);
rval = (*hi->hi_hook.h_func)(token, info, ns);
DTRACE_PROBE4(hook__func__end,
hook_event_token_t, token,
hook_data_t, info,
hook_int_t *, hi,
int, rval);
if (rval != 0)
break;
}
CVW_EXIT_READ(&hei->hei_lock);
CVW_EXIT_READ(&hks->hks_familylock);
DTRACE_PROBE3(hook__run__end,
hook_event_token_t, token,
hook_data_t, info,
hook_int_t *, hi);
return (rval);
}
/*
* Function: hook_family_add
* Returns: internal family pointer - NULL = Fail
* Parameters: hf(I) - family pointer
*
* Add new family to family list
*/
hook_family_int_t *
hook_family_add(hook_family_t *hf, hook_stack_t *hks)
{
hook_family_int_t *hfi, *new;
ASSERT(hf != NULL);
ASSERT(hf->hf_name != NULL);
new = hook_family_copy(hf);
if (new == NULL)
return (NULL);
CVW_ENTER_WRITE(&hks->hks_familylock);
/* search family list */
hfi = hook_family_find(hf->hf_name, hks);
if (hfi != NULL) {
CVW_EXIT_WRITE(&hks->hks_familylock);
hook_family_free(new);
return (NULL);
}
new->hfi_ptr = (void *)hks;
/* Add to family list head */
SLIST_INSERT_HEAD(&hks->hks_familylist, new, hfi_entry);
CVW_EXIT_WRITE(&hks->hks_familylock);
return (new);
}
/*
* Function: hook_family_remove
* Returns: int - 0 = Succ, Else = Fail
* Parameters: hfi(I) - internal family pointer
*
* Remove family from family list
*/
int
hook_family_remove(hook_family_int_t *hfi)
{
hook_stack_t *hks;
ASSERT(hfi != NULL);
hks = (hook_stack_t *)hfi->hfi_ptr;
CVW_ENTER_WRITE(&hks->hks_familylock);
/* Check if there are events */
if (!SLIST_EMPTY(&hfi->hfi_head)) {
CVW_EXIT_WRITE(&hks->hks_familylock);
return (EBUSY);
}
/* Remove from family list */
SLIST_REMOVE(&hks->hks_familylist, hfi, hook_family_int, hfi_entry);
CVW_EXIT_WRITE(&hks->hks_familylock);
hook_family_free(hfi);
return (0);
}
/*
* Function: hook_family_copy
* Returns: internal family pointer - NULL = Failed
* Parameters: src(I) - family pointer
*
* Allocate internal family block and duplicate incoming family
* No locks should be held across this function as it may sleep.
*/
static hook_family_int_t *
hook_family_copy(hook_family_t *src)
{
hook_family_int_t *new;
hook_family_t *dst;
ASSERT(src != NULL);
ASSERT(src->hf_name != NULL);
new = (hook_family_int_t *)kmem_zalloc(sizeof (*new), KM_SLEEP);
/* Copy body */
SLIST_INIT(&new->hfi_head);
dst = &new->hfi_family;
*dst = *src;
/* Copy name */
dst->hf_name = (char *)kmem_alloc(strlen(src->hf_name) + 1, KM_SLEEP);
(void) strcpy(dst->hf_name, src->hf_name);
return (new);
}
/*
* 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.
*/
static hook_family_int_t *
hook_family_find(char *family, hook_stack_t *hks)
{
hook_family_int_t *hfi = NULL;
ASSERT(family != NULL);
SLIST_FOREACH(hfi, &hks->hks_familylist, hfi_entry) {
if (strcmp(hfi->hfi_family.hf_name, family) == 0)
break;
}
return (hfi);
}
/*
* 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)
{
ASSERT(hfi != NULL);
/* 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));
}
/*
* Function: hook_event_add
* Returns: internal event pointer - NULL = Fail
* Parameters: hfi(I) - internal family pointer
* he(I) - event pointer
*
* Add new event to event list on specific family.
* This function can fail to return successfully if (1) it cannot allocate
* enough memory for its own internal data structures, (2) the event has
* already been registered (for any hook family.)
*/
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;
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);
/* 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);
return (NULL);
}
/* Add to event list head */
SLIST_INSERT_HEAD(&hfi->hfi_head, new, hei_entry);
CVW_EXIT_WRITE(&hks->hks_familylock);
return (new);
}
/*
* Function: hook_event_remove
* Returns: int - 0 = Succ, Else = Fail
* Parameters: hfi(I) - internal family pointer
* he(I) - event pointer
*
* Remove event from event list on specific family
*/
int
hook_event_remove(hook_family_int_t *hfi, hook_event_t *he)
{
hook_stack_t *hks;
hook_event_int_t *hei;
ASSERT(hfi != NULL);
ASSERT(he != NULL);
hks = (hook_stack_t *)hfi->hfi_ptr;
CVW_ENTER_WRITE(&hks->hks_familylock);
hei = hook_event_find(hfi, he->he_name);
if (hei == NULL) {
CVW_EXIT_WRITE(&hks->hks_familylock);
return (ENXIO);
}
/* Check if there are registered hooks for this event */
if (!TAILQ_EMPTY(&hei->hei_head)) {
CVW_EXIT_WRITE(&hks->hks_familylock);
return (EBUSY);
}
/* Remove from event list */
SLIST_REMOVE(&hfi->hfi_head, hei, hook_event_int, hei_entry);
CVW_EXIT_WRITE(&hks->hks_familylock);
hook_event_free(hei);
return (0);
}
/*
* 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.
*/
static hook_event_int_t *
hook_event_checkdup(hook_event_t *he, hook_stack_t *hks)
{
hook_family_int_t *hfi;
hook_event_int_t *hei;
ASSERT(he != NULL);
SLIST_FOREACH(hfi, &hks->hks_familylist, hfi_entry) {
SLIST_FOREACH(hei, &hfi->hfi_head, hei_entry) {
if (hei->hei_event == he)
return (hei);
}
}
return (NULL);
}
/*
* Function: hook_event_copy
* Returns: internal event pointer - NULL = Failed
* Parameters: src(I) - event pointer
*
* Allocate internal event block and duplicate incoming event
* No locks should be held across this function as it may sleep.
*/
static hook_event_int_t *
hook_event_copy(hook_event_t *src)
{
hook_event_int_t *new;
ASSERT(src != NULL);
ASSERT(src->he_name != NULL);
new = (hook_event_int_t *)kmem_zalloc(sizeof (*new), KM_SLEEP);
/* Copy body */
TAILQ_INIT(&new->hei_head);
new->hei_event = src;
return (new);
}
/*
* Function: hook_event_find
* Returns: internal event pointer - NULL = Not match
* Parameters: hfi(I) - internal family pointer
* event(I) - event name string
*
* Search event list with event name
* A lock on hks->hks_familylock must be held when called.
*/
static hook_event_int_t *
hook_event_find(hook_family_int_t *hfi, char *event)
{
hook_event_int_t *hei = NULL;
ASSERT(hfi != NULL);
ASSERT(event != NULL);
SLIST_FOREACH(hei, &hfi->hfi_head, hei_entry) {
if (strcmp(hei->hei_event->he_name, event) == 0)
break;
}
return (hei);
}
/*
* 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)
{
ASSERT(hei != NULL);
/* Free container */
kmem_free(hei, sizeof (*hei));
}
/*
* Function: hook_register
* Returns: int- 0 = Succ, Else = Fail
* Parameters: hfi(I) - internal family pointer
* event(I) - event name string
* h(I) - hook pointer
*
* Add new hook to hook list on spefic family, 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;
ASSERT(hfi != NULL);
ASSERT(event != NULL);
ASSERT(h != NULL);
hks = (hook_stack_t *)hfi->hfi_ptr;
/* Alloc hook_int_t and copy hook */
new = hook_copy(h);
if (new == NULL)
return (ENOMEM);
/*
* Since hook add/remove only impact event, so it is unnecessary
* 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);
hei = hook_event_find(hfi, event);
if (hei == NULL) {
CVW_EXIT_READ(&hks->hks_familylock);
hook_free(new);
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) {
CVW_EXIT_WRITE(&hei->hei_lock);
CVW_EXIT_READ(&hks->hks_familylock);
hook_free(new);
return (EEXIST);
}
/* Add to hook list head */
TAILQ_INSERT_HEAD(&hei->hei_head, new, hi_entry);
hei->hei_event->he_interested = B_TRUE;
CVW_EXIT_WRITE(&hei->hei_lock);
CVW_EXIT_READ(&hks->hks_familylock);
return (0);
}
/*
* Function: hook_unregister
* Returns: int - 0 = Succ, Else = Fail
* Parameters: hfi(I) - internal family pointer
* event(I) - event name string
* h(I) - hook pointer
*
* Remove hook from hook list on specific family, event
*/
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;
ASSERT(hfi != NULL);
ASSERT(h != NULL);
hks = (hook_stack_t *)hfi->hfi_ptr;
CVW_ENTER_READ(&hks->hks_familylock);
hei = hook_event_find(hfi, event);
if (hei == NULL) {
CVW_EXIT_READ(&hks->hks_familylock);
return (ENXIO);
}
/* Hold write lock for event */
CVW_ENTER_WRITE(&hei->hei_lock);
hi = hook_find(hei, h);
if (hi == NULL) {
CVW_EXIT_WRITE(&hei->hei_lock);
CVW_EXIT_READ(&hks->hks_familylock);
return (ENXIO);
}
/* Remove from hook list */
TAILQ_REMOVE(&hei->hei_head, hi, hi_entry);
if (TAILQ_EMPTY(&hei->hei_head)) {
hei->hei_event->he_interested = B_FALSE;
}
CVW_EXIT_WRITE(&hei->hei_lock);
CVW_EXIT_READ(&hks->hks_familylock);
hook_free(hi);
return (0);
}
/*
* Function: hook_find
* Returns: internal hook pointer - NULL = Not match
* Parameters: hei(I) - internal event pointer
* h(I) - hook pointer
*
* Search hook list
* A lock on familylock must be held when called.
*/
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);
}
/*
* Function: hook_copy
* Returns: internal hook pointer - NULL = Failed
* Parameters: src(I) - hook pointer
*
* Allocate internal hook block and duplicate incoming hook.
* No locks should be held across this function as it may sleep.
*/
static hook_int_t *
hook_copy(hook_t *src)
{
hook_int_t *new;
hook_t *dst;
ASSERT(src != NULL);
ASSERT(src->h_name != NULL);
new = (hook_int_t *)kmem_zalloc(sizeof (*new), KM_SLEEP);
/* Copy body */
dst = &new->hi_hook;
*dst = *src;
/* Copy name */
dst->h_name = (char *)kmem_alloc(strlen(src->h_name) + 1, KM_SLEEP);
(void) strcpy(dst->h_name, src->h_name);
return (new);
}
/*
* Function: hook_free
* Returns: None
* Parameters: hi(I) - internal hook pointer
*
* Free alloc memory for hook
*/
static void
hook_free(hook_int_t *hi)
{
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);
}
/* Free container */
kmem_free(hi, sizeof (*hi));
}