| /* |
| * 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)); |
| } |