xref: /illumos-gate/usr/src/uts/common/io/hook.c (revision 4e93fb0f6383eaac21897dcdae56b87118131e4d)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 /*
22  * Copyright 2007 Sun Microsystems, Inc.  All rights reserved.
23  * Use is subject to license terms.
24  */
25 #pragma ident	"%Z%%M%	%I%	%E% SMI"
26 
27 #include <sys/param.h>
28 #include <sys/types.h>
29 #include <sys/systm.h>
30 #include <sys/errno.h>
31 #include <sys/kmem.h>
32 #include <sys/mutex.h>
33 #include <sys/condvar.h>
34 #include <sys/modctl.h>
35 #include <sys/hook_impl.h>
36 #include <sys/sdt.h>
37 
38 /*
39  * This file provides kernel hook framework.
40  */
41 
42 static struct modldrv modlmisc = {
43 	&mod_miscops,				/* drv_modops */
44 	"Hooks Interface v1.0",			/* drv_linkinfo */
45 };
46 
47 static struct modlinkage modlinkage = {
48 	MODREV_1,				/* ml_rev */
49 	&modlmisc,				/* ml_linkage */
50 	NULL
51 };
52 
53 /*
54  * Hook internal functions
55  */
56 static hook_int_t *hook_copy(hook_t *src);
57 static hook_event_int_t *hook_event_checkdup(hook_event_t *he,
58     hook_stack_t *hks);
59 static hook_event_int_t *hook_event_copy(hook_event_t *src);
60 static hook_event_int_t *hook_event_find(hook_family_int_t *hfi, char *event);
61 static void hook_event_free(hook_event_int_t *hei);
62 static hook_family_int_t *hook_family_copy(hook_family_t *src);
63 static hook_family_int_t *hook_family_find(char *family, hook_stack_t *hks);
64 static void hook_family_free(hook_family_int_t *hfi);
65 static hook_int_t *hook_find(hook_event_int_t *hei, hook_t *h);
66 static void hook_free(hook_int_t *hi);
67 static void hook_init(void);
68 static void hook_fini(void);
69 static void *hook_stack_init(netstackid_t stackid, netstack_t *ns);
70 static void hook_stack_fini(netstackid_t stackid, void *arg);
71 
72 /*
73  * Module entry points.
74  */
75 int
76 _init(void)
77 {
78 	int error;
79 
80 	hook_init();
81 	error = mod_install(&modlinkage);
82 	if (error != 0)
83 		hook_fini();
84 
85 	return (error);
86 }
87 
88 
89 int
90 _fini(void)
91 {
92 	int error;
93 
94 	error = mod_remove(&modlinkage);
95 	if (error == 0)
96 		hook_fini();
97 
98 	return (error);
99 }
100 
101 
102 int
103 _info(struct modinfo *modinfop)
104 {
105 	return (mod_info(&modlinkage, modinfop));
106 }
107 
108 
109 /*
110  * Function:	hook_init
111  * Returns:	None
112  * Parameters:	None
113  *
114  * Initialize hooks
115  */
116 static void
117 hook_init(void)
118 {
119 	/*
120 	 * We want to be informed each time a stack is created or
121 	 * destroyed in the kernel.
122 	 */
123 	netstack_register(NS_HOOK, hook_stack_init, NULL,
124 	    hook_stack_fini);
125 }
126 
127 /*
128  * Function:	hook_fini
129  * Returns:	None
130  * Parameters:	None
131  *
132  * Deinitialize hooks
133  */
134 static void
135 hook_fini(void)
136 {
137 	netstack_unregister(NS_HOOK);
138 }
139 
140 /*
141  * Initialize the hook stack instance.
142  */
143 /*ARGSUSED*/
144 static void *
145 hook_stack_init(netstackid_t stackid, netstack_t *ns)
146 {
147 	hook_stack_t	*hks;
148 
149 #ifdef NS_DEBUG
150 	printf("hook_stack_init(stack %d)\n", stackid);
151 #endif
152 
153 	hks = (hook_stack_t *)kmem_zalloc(sizeof (*hks), KM_SLEEP);
154 	hks->hk_netstack = ns;
155 
156 	CVW_INIT(&hks->hks_familylock);
157 	SLIST_INIT(&hks->hks_familylist);
158 
159 	return (hks);
160 }
161 
162 /*
163  * Free the hook stack instance.
164  */
165 /*ARGSUSED*/
166 static void
167 hook_stack_fini(netstackid_t stackid, void *arg)
168 {
169 	hook_stack_t	*hks = (hook_stack_t *)arg;
170 #ifdef NS_DEBUG
171 	printf("hook_stack_fini(%p, stack %d)\n", arg, stackid);
172 #endif
173 	CVW_DESTROY(&hks->hks_familylock);
174 	kmem_free(hks, sizeof (*hks));
175 }
176 
177 /*
178  * Function:	hook_run
179  * Returns:	int - return value according to callback func
180  * Parameters:	token(I) - event pointer
181  *		info(I) - message
182  *
183  * Run hooks for specific provider.  The hooks registered are stepped through
184  * until either the end of the list is reached or a hook function returns a
185  * non-zero value.  If a non-zero value is returned from a hook function, we
186  * return that value back to our caller.  By design, a hook function can be
187  * called more than once, simultaneously.
188  */
189 int
190 hook_run(hook_event_token_t token, hook_data_t info, netstack_t *ns)
191 {
192 	hook_int_t *hi;
193 	hook_event_int_t *hei;
194 	hook_stack_t *hks = ns->netstack_hook;
195 	int rval = 0;
196 
197 	ASSERT(token != NULL);
198 
199 	hei = (hook_event_int_t *)token;
200 	DTRACE_PROBE2(hook__run__start,
201 	    hook_event_token_t, token,
202 	    hook_data_t, info);
203 
204 	/* Hold global read lock to ensure event will not be deleted */
205 	CVW_ENTER_READ(&hks->hks_familylock);
206 
207 	/* Hold event read lock to ensure hook will not be changed */
208 	CVW_ENTER_READ(&hei->hei_lock);
209 
210 	TAILQ_FOREACH(hi, &hei->hei_head, hi_entry) {
211 		ASSERT(hi->hi_hook.h_func != NULL);
212 		DTRACE_PROBE3(hook__func__start,
213 		    hook_event_token_t, token,
214 		    hook_data_t, info,
215 		    hook_int_t *, hi);
216 		rval = (*hi->hi_hook.h_func)(token, info, ns);
217 		DTRACE_PROBE4(hook__func__end,
218 		    hook_event_token_t, token,
219 		    hook_data_t, info,
220 		    hook_int_t *, hi,
221 		    int, rval);
222 		if (rval != 0)
223 			break;
224 	}
225 
226 	CVW_EXIT_READ(&hei->hei_lock);
227 	CVW_EXIT_READ(&hks->hks_familylock);
228 
229 	DTRACE_PROBE3(hook__run__end,
230 	    hook_event_token_t, token,
231 	    hook_data_t, info,
232 	    hook_int_t *, hi);
233 
234 	return (rval);
235 }
236 
237 
238 /*
239  * Function:	hook_family_add
240  * Returns:	internal family pointer - NULL = Fail
241  * Parameters:	hf(I) - family pointer
242  *
243  * Add new family to family list
244  */
245 hook_family_int_t *
246 hook_family_add(hook_family_t *hf, hook_stack_t *hks)
247 {
248 	hook_family_int_t *hfi, *new;
249 
250 	ASSERT(hf != NULL);
251 	ASSERT(hf->hf_name != NULL);
252 
253 	new = hook_family_copy(hf);
254 	if (new == NULL)
255 		return (NULL);
256 
257 	CVW_ENTER_WRITE(&hks->hks_familylock);
258 
259 	/* search family list */
260 	hfi = hook_family_find(hf->hf_name, hks);
261 	if (hfi != NULL) {
262 		CVW_EXIT_WRITE(&hks->hks_familylock);
263 		hook_family_free(new);
264 		return (NULL);
265 	}
266 
267 	new->hfi_ptr = (void *)hks;
268 
269 	/* Add to family list head */
270 	SLIST_INSERT_HEAD(&hks->hks_familylist, new, hfi_entry);
271 
272 	CVW_EXIT_WRITE(&hks->hks_familylock);
273 	return (new);
274 }
275 
276 
277 /*
278  * Function:	hook_family_remove
279  * Returns:	int - 0 = Succ, Else = Fail
280  * Parameters:	hfi(I) - internal family pointer
281  *
282  * Remove family from family list
283  */
284 int
285 hook_family_remove(hook_family_int_t *hfi)
286 {
287 	hook_stack_t *hks;
288 
289 	ASSERT(hfi != NULL);
290 	hks = (hook_stack_t *)hfi->hfi_ptr;
291 
292 	CVW_ENTER_WRITE(&hks->hks_familylock);
293 
294 	/* Check if there are events  */
295 	if (!SLIST_EMPTY(&hfi->hfi_head)) {
296 		CVW_EXIT_WRITE(&hks->hks_familylock);
297 		return (EBUSY);
298 	}
299 
300 	/* Remove from family list */
301 	SLIST_REMOVE(&hks->hks_familylist, hfi, hook_family_int, hfi_entry);
302 
303 	CVW_EXIT_WRITE(&hks->hks_familylock);
304 	hook_family_free(hfi);
305 
306 	return (0);
307 }
308 
309 
310 /*
311  * Function:	hook_family_copy
312  * Returns:	internal family pointer - NULL = Failed
313  * Parameters:	src(I) - family pointer
314  *
315  * Allocate internal family block and duplicate incoming family
316  * No locks should be held across this function as it may sleep.
317  */
318 static hook_family_int_t *
319 hook_family_copy(hook_family_t *src)
320 {
321 	hook_family_int_t *new;
322 	hook_family_t *dst;
323 
324 	ASSERT(src != NULL);
325 	ASSERT(src->hf_name != NULL);
326 
327 	new = (hook_family_int_t *)kmem_zalloc(sizeof (*new), KM_SLEEP);
328 
329 	/* Copy body */
330 	SLIST_INIT(&new->hfi_head);
331 	dst = &new->hfi_family;
332 	*dst = *src;
333 
334 	/* Copy name */
335 	dst->hf_name = (char *)kmem_alloc(strlen(src->hf_name) + 1, KM_SLEEP);
336 	(void) strcpy(dst->hf_name, src->hf_name);
337 
338 	return (new);
339 }
340 
341 
342 /*
343  * Returns:	internal family pointer - NULL = Not match
344  * Parameters:	family(I) - family name string
345  *
346  * Search family list with family name
347  * 	A lock on familylock must be held when called.
348  */
349 static hook_family_int_t *
350 hook_family_find(char *family, hook_stack_t *hks)
351 {
352 	hook_family_int_t *hfi = NULL;
353 
354 	ASSERT(family != NULL);
355 
356 	SLIST_FOREACH(hfi, &hks->hks_familylist, hfi_entry) {
357 		if (strcmp(hfi->hfi_family.hf_name, family) == 0)
358 			break;
359 	}
360 	return (hfi);
361 }
362 
363 
364 /*
365  * Function:	hook_family_free
366  * Returns:	None
367  * Parameters:	hfi(I) - internal family pointer
368  *
369  * Free alloc memory for family
370  */
371 static void
372 hook_family_free(hook_family_int_t *hfi)
373 {
374 	ASSERT(hfi != NULL);
375 
376 	/* Free name space */
377 	if (hfi->hfi_family.hf_name != NULL) {
378 		kmem_free(hfi->hfi_family.hf_name,
379 		    strlen(hfi->hfi_family.hf_name) + 1);
380 	}
381 
382 	/* Free container */
383 	kmem_free(hfi, sizeof (*hfi));
384 }
385 
386 
387 /*
388  * Function:	hook_event_add
389  * Returns:	internal event pointer - NULL = Fail
390  * Parameters:	hfi(I) - internal family pointer
391  *		he(I) - event pointer
392  *
393  * Add new event to event list on specific family.
394  * This function can fail to return successfully if (1) it cannot allocate
395  * enough memory for its own internal data structures, (2) the event has
396  * already been registered (for any hook family.)
397  */
398 hook_event_int_t *
399 hook_event_add(hook_family_int_t *hfi, hook_event_t *he)
400 {
401 	hook_stack_t *hks;
402 	hook_event_int_t *hei, *new;
403 
404 	ASSERT(hfi != NULL);
405 	ASSERT(he != NULL);
406 	ASSERT(he->he_name != NULL);
407 	hks = (hook_stack_t *)hfi->hfi_ptr;
408 
409 	new = hook_event_copy(he);
410 	if (new == NULL)
411 		return (NULL);
412 
413 	CVW_ENTER_WRITE(&hks->hks_familylock);
414 
415 	/* Check whether this event pointer is already registered */
416 	hei = hook_event_checkdup(he, hks);
417 	if (hei != NULL) {
418 		CVW_EXIT_WRITE(&hks->hks_familylock);
419 		hook_event_free(new);
420 		return (NULL);
421 	}
422 
423 	/* Add to event list head */
424 	SLIST_INSERT_HEAD(&hfi->hfi_head, new, hei_entry);
425 
426 	CVW_EXIT_WRITE(&hks->hks_familylock);
427 	return (new);
428 }
429 
430 
431 /*
432  * Function:	hook_event_remove
433  * Returns:	int - 0 = Succ, Else = Fail
434  * Parameters:	hfi(I) - internal family pointer
435  *		he(I) - event pointer
436  *
437  * Remove event from event list on specific family
438  */
439 int
440 hook_event_remove(hook_family_int_t *hfi, hook_event_t *he)
441 {
442 	hook_stack_t *hks;
443 	hook_event_int_t *hei;
444 
445 	ASSERT(hfi != NULL);
446 	ASSERT(he != NULL);
447 	hks = (hook_stack_t *)hfi->hfi_ptr;
448 
449 	CVW_ENTER_WRITE(&hks->hks_familylock);
450 
451 	hei = hook_event_find(hfi, he->he_name);
452 	if (hei == NULL) {
453 		CVW_EXIT_WRITE(&hks->hks_familylock);
454 		return (ENXIO);
455 	}
456 
457 	/* Check if there are registered hooks for this event */
458 	if (!TAILQ_EMPTY(&hei->hei_head)) {
459 		CVW_EXIT_WRITE(&hks->hks_familylock);
460 		return (EBUSY);
461 	}
462 
463 	/* Remove from event list */
464 	SLIST_REMOVE(&hfi->hfi_head, hei, hook_event_int, hei_entry);
465 
466 	CVW_EXIT_WRITE(&hks->hks_familylock);
467 	hook_event_free(hei);
468 
469 	return (0);
470 }
471 
472 
473 /*
474  * Function:    hook_event_checkdup
475  * Returns:     internal event pointer - NULL = Not match
476  * Parameters:  he(I) - event pointer
477  *
478  * Search whole list with event pointer
479  *      A lock on familylock must be held when called.
480  */
481 static hook_event_int_t *
482 hook_event_checkdup(hook_event_t *he, hook_stack_t *hks)
483 {
484 	hook_family_int_t *hfi;
485 	hook_event_int_t *hei;
486 
487 	ASSERT(he != NULL);
488 
489 	SLIST_FOREACH(hfi, &hks->hks_familylist, hfi_entry) {
490 		SLIST_FOREACH(hei, &hfi->hfi_head, hei_entry) {
491 			if (hei->hei_event == he)
492 				return (hei);
493 		}
494 	}
495 
496 	return (NULL);
497 }
498 
499 
500 /*
501  * Function:	hook_event_copy
502  * Returns:	internal event pointer - NULL = Failed
503  * Parameters:	src(I) - event pointer
504  *
505  * Allocate internal event block and duplicate incoming event
506  * No locks should be held across this function as it may sleep.
507  */
508 static hook_event_int_t *
509 hook_event_copy(hook_event_t *src)
510 {
511 	hook_event_int_t *new;
512 
513 	ASSERT(src != NULL);
514 	ASSERT(src->he_name != NULL);
515 
516 	new = (hook_event_int_t *)kmem_zalloc(sizeof (*new), KM_SLEEP);
517 
518 	/* Copy body */
519 	TAILQ_INIT(&new->hei_head);
520 	new->hei_event = src;
521 
522 	return (new);
523 }
524 
525 
526 /*
527  * Function:	hook_event_find
528  * Returns:	internal event pointer - NULL = Not match
529  * Parameters:	hfi(I) - internal family pointer
530  *		event(I) - event name string
531  *
532  * Search event list with event name
533  * 	A lock on hks->hks_familylock must be held when called.
534  */
535 static hook_event_int_t *
536 hook_event_find(hook_family_int_t *hfi, char *event)
537 {
538 	hook_event_int_t *hei = NULL;
539 
540 	ASSERT(hfi != NULL);
541 	ASSERT(event != NULL);
542 
543 	SLIST_FOREACH(hei, &hfi->hfi_head, hei_entry) {
544 		if (strcmp(hei->hei_event->he_name, event) == 0)
545 			break;
546 	}
547 	return (hei);
548 }
549 
550 
551 /*
552  * Function:	hook_event_free
553  * Returns:	None
554  * Parameters:	hei(I) - internal event pointer
555  *
556  * Free alloc memory for event
557  */
558 static void
559 hook_event_free(hook_event_int_t *hei)
560 {
561 	ASSERT(hei != NULL);
562 
563 	/* Free container */
564 	kmem_free(hei, sizeof (*hei));
565 }
566 
567 
568 /*
569  * Function:	hook_register
570  * Returns:	int- 0 = Succ, Else = Fail
571  * Parameters:	hfi(I) - internal family pointer
572  *		event(I) - event name string
573  *		h(I) - hook pointer
574  *
575  * Add new hook to hook list on spefic family, event
576  */
577 int
578 hook_register(hook_family_int_t *hfi, char *event, hook_t *h)
579 {
580 	hook_stack_t *hks;
581 	hook_event_int_t *hei;
582 	hook_int_t *hi, *new;
583 
584 	ASSERT(hfi != NULL);
585 	ASSERT(event != NULL);
586 	ASSERT(h != NULL);
587 	hks = (hook_stack_t *)hfi->hfi_ptr;
588 
589 	/* Alloc hook_int_t and copy hook */
590 	new = hook_copy(h);
591 	if (new == NULL)
592 		return (ENOMEM);
593 
594 	/*
595 	 * Since hook add/remove only impact event, so it is unnecessary
596 	 * to hold global family write lock. Just get read lock here to
597 	 * ensure event will not be removed when doing hooks operation
598 	 */
599 	CVW_ENTER_READ(&hks->hks_familylock);
600 
601 	hei = hook_event_find(hfi, event);
602 	if (hei == NULL) {
603 		CVW_EXIT_READ(&hks->hks_familylock);
604 		hook_free(new);
605 		return (ENXIO);
606 	}
607 
608 	CVW_ENTER_WRITE(&hei->hei_lock);
609 
610 	/* Multiple hooks are only allowed for read-only events. */
611 	if (((hei->hei_event->he_flags & HOOK_RDONLY) == 0) &&
612 	    (!TAILQ_EMPTY(&hei->hei_head))) {
613 		CVW_EXIT_WRITE(&hei->hei_lock);
614 		CVW_EXIT_READ(&hks->hks_familylock);
615 		hook_free(new);
616 		return (EEXIST);
617 	}
618 
619 	hi = hook_find(hei, h);
620 	if (hi != NULL) {
621 		CVW_EXIT_WRITE(&hei->hei_lock);
622 		CVW_EXIT_READ(&hks->hks_familylock);
623 		hook_free(new);
624 		return (EEXIST);
625 	}
626 
627 	/* Add to hook list head */
628 	TAILQ_INSERT_HEAD(&hei->hei_head, new, hi_entry);
629 	hei->hei_event->he_interested = B_TRUE;
630 
631 	CVW_EXIT_WRITE(&hei->hei_lock);
632 	CVW_EXIT_READ(&hks->hks_familylock);
633 	return (0);
634 }
635 
636 
637 /*
638  * Function:	hook_unregister
639  * Returns:	int - 0 = Succ, Else = Fail
640  * Parameters:	hfi(I) - internal family pointer
641  *		event(I) - event name string
642  *		h(I) - hook pointer
643  *
644  * Remove hook from hook list on specific family, event
645  */
646 int
647 hook_unregister(hook_family_int_t *hfi, char *event, hook_t *h)
648 {
649 	hook_stack_t *hks;
650 	hook_event_int_t *hei;
651 	hook_int_t *hi;
652 
653 	ASSERT(hfi != NULL);
654 	ASSERT(h != NULL);
655 	hks = (hook_stack_t *)hfi->hfi_ptr;
656 
657 	CVW_ENTER_READ(&hks->hks_familylock);
658 
659 	hei = hook_event_find(hfi, event);
660 	if (hei == NULL) {
661 		CVW_EXIT_READ(&hks->hks_familylock);
662 		return (ENXIO);
663 	}
664 
665 	/* Hold write lock for event */
666 	CVW_ENTER_WRITE(&hei->hei_lock);
667 
668 	hi = hook_find(hei, h);
669 	if (hi == NULL) {
670 		CVW_EXIT_WRITE(&hei->hei_lock);
671 		CVW_EXIT_READ(&hks->hks_familylock);
672 		return (ENXIO);
673 	}
674 
675 	/* Remove from hook list */
676 	TAILQ_REMOVE(&hei->hei_head, hi, hi_entry);
677 	if (TAILQ_EMPTY(&hei->hei_head)) {
678 		hei->hei_event->he_interested = B_FALSE;
679 	}
680 
681 	CVW_EXIT_WRITE(&hei->hei_lock);
682 	CVW_EXIT_READ(&hks->hks_familylock);
683 
684 	hook_free(hi);
685 	return (0);
686 }
687 
688 
689 /*
690  * Function:	hook_find
691  * Returns:	internal hook pointer - NULL = Not match
692  * Parameters:	hei(I) - internal event pointer
693  *		h(I) - hook pointer
694  *
695  * Search hook list
696  * 	A lock on familylock must be held when called.
697  */
698 static hook_int_t *
699 hook_find(hook_event_int_t *hei, hook_t *h)
700 {
701 	hook_int_t *hi;
702 
703 	ASSERT(hei != NULL);
704 	ASSERT(h != NULL);
705 
706 	TAILQ_FOREACH(hi, &hei->hei_head, hi_entry) {
707 		if (strcmp(hi->hi_hook.h_name, h->h_name) == 0)
708 			break;
709 	}
710 	return (hi);
711 }
712 
713 
714 /*
715  * Function:	hook_copy
716  * Returns:	internal hook pointer - NULL = Failed
717  * Parameters:	src(I) - hook pointer
718  *
719  * Allocate internal hook block and duplicate incoming hook.
720  * No locks should be held across this function as it may sleep.
721  */
722 static hook_int_t *
723 hook_copy(hook_t *src)
724 {
725 	hook_int_t *new;
726 	hook_t *dst;
727 
728 	ASSERT(src != NULL);
729 	ASSERT(src->h_name != NULL);
730 
731 	new = (hook_int_t *)kmem_zalloc(sizeof (*new), KM_SLEEP);
732 
733 	/* Copy body */
734 	dst = &new->hi_hook;
735 	*dst = *src;
736 
737 	/* Copy name */
738 	dst->h_name = (char *)kmem_alloc(strlen(src->h_name) + 1, KM_SLEEP);
739 	(void) strcpy(dst->h_name, src->h_name);
740 
741 	return (new);
742 }
743 
744 /*
745  * Function:	hook_free
746  * Returns:	None
747  * Parameters:	hi(I) - internal hook pointer
748  *
749  * Free alloc memory for hook
750  */
751 static void
752 hook_free(hook_int_t *hi)
753 {
754 	ASSERT(hi != NULL);
755 
756 	/* Free name space */
757 	if (hi->hi_hook.h_name != NULL) {
758 		kmem_free(hi->hi_hook.h_name, strlen(hi->hi_hook.h_name) + 1);
759 	}
760 
761 	/* Free container */
762 	kmem_free(hi, sizeof (*hi));
763 }
764