xref: /illumos-gate/usr/src/uts/common/io/hook.c (revision eda50310abb3984bab11856a2aca8936d26881cb)
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 2006 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 static hook_event_int_t *hook_event_copy(hook_event_t *src);
59 static hook_event_int_t *hook_event_find(hook_family_int_t *hfi, char *event);
60 static void hook_event_free(hook_event_int_t *hei);
61 static hook_family_int_t *hook_family_copy(hook_family_t *src);
62 static hook_family_int_t *hook_family_find(char *family);
63 static void hook_family_free(hook_family_int_t *hfi);
64 static hook_int_t *hook_find(hook_event_int_t *hei, hook_t *h);
65 static void hook_free(hook_int_t *hi);
66 static void hook_init(void);
67 
68 static cvwaitlock_t familylock;			/* global lock */
69 static hook_family_int_head_t familylist;	/* family list head */
70 
71 /*
72  * Module entry points.
73  */
74 int
75 _init(void)
76 {
77 	hook_init();
78 	return (mod_install(&modlinkage));
79 }
80 
81 
82 int
83 _fini(void)
84 {
85 	return (mod_remove(&modlinkage));
86 }
87 
88 
89 int
90 _info(struct modinfo *modinfop)
91 {
92 	return (mod_info(&modlinkage, modinfop));
93 }
94 
95 
96 /*
97  * Function:	hook_init
98  * Returns:	None
99  * Parameters:	None
100  *
101  * Initialize hooks
102  */
103 static void
104 hook_init(void)
105 {
106 	CVW_INIT(&familylock);
107 	SLIST_INIT(&familylist);
108 }
109 
110 
111 /*
112  * Function:	hook_run
113  * Returns:	int - return value according to callback func
114  * Parameters:	token(I) - event pointer
115  *		info(I) - message
116  *
117  * Run hooks for specific provider.  The hooks registered are stepped through
118  * until either the end of the list is reached or a hook function returns a
119  * non-zero value.  If a non-zero value is returned from a hook function, we
120  * return that value back to our caller.  By design, a hook function can be
121  * called more than once, simultaneously.
122  */
123 int
124 hook_run(hook_event_token_t token, hook_data_t info)
125 {
126 	hook_int_t *hi;
127 	hook_event_int_t *hei;
128 	int rval = 0;
129 
130 	ASSERT(token != NULL);
131 
132 	hei = (hook_event_int_t *)token;
133 	DTRACE_PROBE2(hook__run__start,
134 	    hook_event_token_t, token,
135 	    hook_data_t, info);
136 
137 	/* Hold global read lock to ensure event will not be deleted */
138 	CVW_ENTER_READ(&familylock);
139 
140 	/* Hold event read lock to ensure hook will not be changed */
141 	CVW_ENTER_READ(&hei->hei_lock);
142 
143 	TAILQ_FOREACH(hi, &hei->hei_head, hi_entry) {
144 		ASSERT(hi->hi_hook.h_func != NULL);
145 		DTRACE_PROBE3(hook__func__start,
146 		    hook_event_token_t, token,
147 		    hook_data_t, info,
148 		    hook_int_t *, hi);
149 		rval = (*hi->hi_hook.h_func)(token, info);
150 		DTRACE_PROBE4(hook__func__end,
151 		    hook_event_token_t, token,
152 		    hook_data_t, info,
153 		    hook_int_t *, hi,
154 		    int, rval);
155 		if (rval != 0)
156 			break;
157 	}
158 
159 	CVW_EXIT_READ(&hei->hei_lock);
160 	CVW_EXIT_READ(&familylock);
161 
162 	DTRACE_PROBE3(hook__run__end,
163 	    hook_event_token_t, token,
164 	    hook_data_t, info,
165 	    hook_int_t *, hi);
166 
167 	return (rval);
168 }
169 
170 
171 /*
172  * Function:	hook_family_add
173  * Returns:	internal family pointer - NULL = Fail
174  * Parameters:	hf(I) - family pointer
175  *
176  * Add new family to family list
177  */
178 hook_family_int_t *
179 hook_family_add(hook_family_t *hf)
180 {
181 	hook_family_int_t *hfi, *new;
182 
183 	ASSERT(hf != NULL);
184 	ASSERT(hf->hf_name != NULL);
185 
186 	new = hook_family_copy(hf);
187 	if (new == NULL)
188 		return (NULL);
189 
190 	CVW_ENTER_WRITE(&familylock);
191 
192 	/* search family list */
193 	hfi = hook_family_find(hf->hf_name);
194 	if (hfi != NULL) {
195 		CVW_EXIT_WRITE(&familylock);
196 		hook_family_free(new);
197 		return (NULL);
198 	}
199 
200 	/* Add to family list head */
201 	SLIST_INSERT_HEAD(&familylist, new, hfi_entry);
202 
203 	CVW_EXIT_WRITE(&familylock);
204 	return (new);
205 }
206 
207 
208 /*
209  * Function:	hook_family_remove
210  * Returns:	int - 0 = Succ, Else = Fail
211  * Parameters:	hfi(I) - internal family pointer
212  *
213  * Remove family from family list
214  */
215 int
216 hook_family_remove(hook_family_int_t *hfi)
217 {
218 
219 	ASSERT(hfi != NULL);
220 
221 	CVW_ENTER_WRITE(&familylock);
222 
223 	/* Check if there are events  */
224 	if (!SLIST_EMPTY(&hfi->hfi_head)) {
225 		CVW_EXIT_WRITE(&familylock);
226 		return (EBUSY);
227 	}
228 
229 	/* Remove from family list */
230 	SLIST_REMOVE(&familylist, hfi, hook_family_int, hfi_entry);
231 
232 	CVW_EXIT_WRITE(&familylock);
233 	hook_family_free(hfi);
234 
235 	return (0);
236 }
237 
238 
239 /*
240  * Function:	hook_family_copy
241  * Returns:	internal family pointer - NULL = Failed
242  * Parameters:	src(I) - family pointer
243  *
244  * Allocate internal family block and duplicate incoming family
245  * No locks should be held across this function as it may sleep.
246  */
247 static hook_family_int_t *
248 hook_family_copy(hook_family_t *src)
249 {
250 	hook_family_int_t *new;
251 	hook_family_t *dst;
252 
253 	ASSERT(src != NULL);
254 	ASSERT(src->hf_name != NULL);
255 
256 	new = (hook_family_int_t *)kmem_zalloc(sizeof (*new), KM_SLEEP);
257 
258 	/* Copy body */
259 	SLIST_INIT(&new->hfi_head);
260 	dst = &new->hfi_family;
261 	*dst = *src;
262 
263 	/* Copy name */
264 	dst->hf_name = (char *)kmem_alloc(strlen(src->hf_name) + 1, KM_SLEEP);
265 	(void) strcpy(dst->hf_name, src->hf_name);
266 
267 	return (new);
268 }
269 
270 
271 /*
272  * Function:	hook_family_find
273  * Returns:	internal family pointer - NULL = Not match
274  * Parameters:	family(I) - family name string
275  *
276  * Search family list with family name
277  * 	A lock on familylock must be held when called.
278  */
279 static hook_family_int_t *
280 hook_family_find(char *family)
281 {
282 	hook_family_int_t *hfi = NULL;
283 
284 	ASSERT(family != NULL);
285 
286 	SLIST_FOREACH(hfi, &familylist, hfi_entry) {
287 		if (strcmp(hfi->hfi_family.hf_name, family) == 0)
288 			break;
289 	}
290 	return (hfi);
291 }
292 
293 
294 /*
295  * Function:	hook_family_free
296  * Returns:	None
297  * Parameters:	hfi(I) - internal family pointer
298  *
299  * Free alloc memory for family
300  */
301 static void
302 hook_family_free(hook_family_int_t *hfi)
303 {
304 	ASSERT(hfi != NULL);
305 
306 	/* Free name space */
307 	if (hfi->hfi_family.hf_name != NULL) {
308 		kmem_free(hfi->hfi_family.hf_name,
309 		    strlen(hfi->hfi_family.hf_name) + 1);
310 	}
311 
312 	/* Free container */
313 	kmem_free(hfi, sizeof (*hfi));
314 }
315 
316 
317 /*
318  * Function:	hook_event_add
319  * Returns:	internal event pointer - NULL = Fail
320  * Parameters:	hfi(I) - internal family pointer
321  *		he(I) - event pointer
322  *
323  * Add new event to event list on specific family.
324  * This function can fail to return successfully if (1) it cannot allocate
325  * enough memory for its own internal data structures, (2) the event has
326  * already been registered (for any hook family.)
327  */
328 hook_event_int_t *
329 hook_event_add(hook_family_int_t *hfi, hook_event_t *he)
330 {
331 	hook_event_int_t *hei, *new;
332 
333 	ASSERT(hfi != NULL);
334 	ASSERT(he != NULL);
335 	ASSERT(he->he_name != NULL);
336 
337 	new = hook_event_copy(he);
338 	if (new == NULL)
339 		return (NULL);
340 
341 	CVW_ENTER_WRITE(&familylock);
342 
343 	/* Check whether this event pointer is already registered */
344 	hei = hook_event_checkdup(he);
345 	if (hei != NULL) {
346 		CVW_EXIT_WRITE(&familylock);
347 		hook_event_free(new);
348 		return (NULL);
349 	}
350 
351 	/* Add to event list head */
352 	SLIST_INSERT_HEAD(&hfi->hfi_head, new, hei_entry);
353 
354 	CVW_EXIT_WRITE(&familylock);
355 	return (new);
356 }
357 
358 
359 /*
360  * Function:	hook_event_remove
361  * Returns:	int - 0 = Succ, Else = Fail
362  * Parameters:	hfi(I) - internal family pointer
363  *		he(I) - event pointer
364  *
365  * Remove event from event list on specific family
366  */
367 int
368 hook_event_remove(hook_family_int_t *hfi, hook_event_t *he)
369 {
370 	hook_event_int_t *hei;
371 
372 	ASSERT(hfi != NULL);
373 	ASSERT(he != NULL);
374 
375 	CVW_ENTER_WRITE(&familylock);
376 
377 	hei = hook_event_find(hfi, he->he_name);
378 	if (hei == NULL) {
379 		CVW_EXIT_WRITE(&familylock);
380 		return (ENXIO);
381 	}
382 
383 	/* Check if there are registered hooks for this event */
384 	if (!TAILQ_EMPTY(&hei->hei_head)) {
385 		CVW_EXIT_WRITE(&familylock);
386 		return (EBUSY);
387 	}
388 
389 	/* Remove from event list */
390 	SLIST_REMOVE(&hfi->hfi_head, hei, hook_event_int, hei_entry);
391 
392 	CVW_EXIT_WRITE(&familylock);
393 	hook_event_free(hei);
394 
395 	return (0);
396 }
397 
398 
399 /*
400  * Function:    hook_event_checkdup
401  * Returns:     internal event pointer - NULL = Not match
402  * Parameters:  he(I) - event pointer
403  *
404  * Search whole list with event pointer
405  *      A lock on familylock must be held when called.
406  */
407 static hook_event_int_t *
408 hook_event_checkdup(hook_event_t *he)
409 {
410 	hook_family_int_t *hfi;
411 	hook_event_int_t *hei;
412 
413 	ASSERT(he != NULL);
414 
415 	SLIST_FOREACH(hfi, &familylist, hfi_entry) {
416 		SLIST_FOREACH(hei, &hfi->hfi_head, hei_entry) {
417 			if (hei->hei_event == he)
418 				return (hei);
419 		}
420 	}
421 
422 	return (NULL);
423 }
424 
425 
426 /*
427  * Function:	hook_event_copy
428  * Returns:	internal event pointer - NULL = Failed
429  * Parameters:	src(I) - event pointer
430  *
431  * Allocate internal event block and duplicate incoming event
432  * No locks should be held across this function as it may sleep.
433  */
434 static hook_event_int_t *
435 hook_event_copy(hook_event_t *src)
436 {
437 	hook_event_int_t *new;
438 
439 	ASSERT(src != NULL);
440 	ASSERT(src->he_name != NULL);
441 
442 	new = (hook_event_int_t *)kmem_zalloc(sizeof (*new), KM_SLEEP);
443 
444 	/* Copy body */
445 	TAILQ_INIT(&new->hei_head);
446 	new->hei_event = src;
447 
448 	return (new);
449 }
450 
451 
452 /*
453  * Function:	hook_event_find
454  * Returns:	internal event pointer - NULL = Not match
455  * Parameters:	hfi(I) - internal family pointer
456  *		event(I) - event name string
457  *
458  * Search event list with event name
459  * 	A lock on familylock must be held when called.
460  */
461 static hook_event_int_t *
462 hook_event_find(hook_family_int_t *hfi, char *event)
463 {
464 	hook_event_int_t *hei = NULL;
465 
466 	ASSERT(hfi != NULL);
467 	ASSERT(event != NULL);
468 
469 	SLIST_FOREACH(hei, &hfi->hfi_head, hei_entry) {
470 		if (strcmp(hei->hei_event->he_name, event) == 0)
471 			break;
472 	}
473 	return (hei);
474 }
475 
476 
477 /*
478  * Function:	hook_event_free
479  * Returns:	None
480  * Parameters:	hei(I) - internal event pointer
481  *
482  * Free alloc memory for event
483  */
484 static void
485 hook_event_free(hook_event_int_t *hei)
486 {
487 	ASSERT(hei != NULL);
488 
489 	/* Free container */
490 	kmem_free(hei, sizeof (*hei));
491 }
492 
493 
494 /*
495  * Function:	hook_register
496  * Returns:	int- 0 = Succ, Else = Fail
497  * Parameters:	hfi(I) - internal family pointer
498  *		event(I) - event name string
499  *		h(I) - hook pointer
500  *
501  * Add new hook to hook list on spefic family, event
502  */
503 int
504 hook_register(hook_family_int_t *hfi, char *event, hook_t *h)
505 {
506 	hook_event_int_t *hei;
507 	hook_int_t *hi, *new;
508 
509 	ASSERT(hfi != NULL);
510 	ASSERT(event != NULL);
511 	ASSERT(h != NULL);
512 
513 	/* Alloc hook_int_t and copy hook */
514 	new = hook_copy(h);
515 	if (new == NULL)
516 		return (ENOMEM);
517 
518 	/*
519 	 * Since hook add/remove only impact event, so it is unnecessary
520 	 * to hold global family write lock. Just get read lock here to
521 	 * ensure event will not be removed when doing hooks operation
522 	 */
523 	CVW_ENTER_READ(&familylock);
524 
525 	hei = hook_event_find(hfi, event);
526 	if (hei == NULL) {
527 		CVW_EXIT_READ(&familylock);
528 		hook_free(new);
529 		return (ENXIO);
530 	}
531 
532 	CVW_ENTER_WRITE(&hei->hei_lock);
533 
534 	/* Multiple hooks are only allowed for read-only events. */
535 	if (((hei->hei_event->he_flags & HOOK_RDONLY) == 0) &&
536 	    (!TAILQ_EMPTY(&hei->hei_head))) {
537 		CVW_EXIT_WRITE(&hei->hei_lock);
538 		CVW_EXIT_READ(&familylock);
539 		hook_free(new);
540 		return (EEXIST);
541 	}
542 
543 	hi = hook_find(hei, h);
544 	if (hi != NULL) {
545 		CVW_EXIT_WRITE(&hei->hei_lock);
546 		CVW_EXIT_READ(&familylock);
547 		hook_free(new);
548 		return (EEXIST);
549 	}
550 
551 	/* Add to hook list head */
552 	TAILQ_INSERT_HEAD(&hei->hei_head, new, hi_entry);
553 	hei->hei_event->he_interested = B_TRUE;
554 
555 	CVW_EXIT_WRITE(&hei->hei_lock);
556 	CVW_EXIT_READ(&familylock);
557 	return (0);
558 }
559 
560 
561 /*
562  * Function:	hook_unregister
563  * Returns:	int - 0 = Succ, Else = Fail
564  * Parameters:	hfi(I) - internal family pointer
565  *		event(I) - event name string
566  *		h(I) - hook pointer
567  *
568  * Remove hook from hook list on specific family, event
569  */
570 int
571 hook_unregister(hook_family_int_t *hfi, char *event, hook_t *h)
572 {
573 	hook_event_int_t *hei;
574 	hook_int_t *hi;
575 
576 	ASSERT(hfi != NULL);
577 	ASSERT(h != NULL);
578 
579 	CVW_ENTER_READ(&familylock);
580 
581 	hei = hook_event_find(hfi, event);
582 	if (hei == NULL) {
583 		CVW_EXIT_READ(&familylock);
584 		return (ENXIO);
585 	}
586 
587 	/* Hold write lock for event */
588 	CVW_ENTER_WRITE(&hei->hei_lock);
589 
590 	hi = hook_find(hei, h);
591 	if (hi == NULL) {
592 		CVW_EXIT_WRITE(&hei->hei_lock);
593 		CVW_EXIT_READ(&familylock);
594 		return (ENXIO);
595 	}
596 
597 	/* Remove from hook list */
598 	TAILQ_REMOVE(&hei->hei_head, hi, hi_entry);
599 	if (TAILQ_EMPTY(&hei->hei_head)) {
600 		hei->hei_event->he_interested = B_FALSE;
601 	}
602 
603 	CVW_EXIT_WRITE(&hei->hei_lock);
604 	CVW_EXIT_READ(&familylock);
605 
606 	hook_free(hi);
607 	return (0);
608 }
609 
610 
611 /*
612  * Function:	hook_find
613  * Returns:	internal hook pointer - NULL = Not match
614  * Parameters:	hei(I) - internal event pointer
615  *		h(I) - hook pointer
616  *
617  * Search hook list
618  * 	A lock on familylock must be held when called.
619  */
620 static hook_int_t *
621 hook_find(hook_event_int_t *hei, hook_t *h)
622 {
623 	hook_int_t *hi;
624 
625 	ASSERT(hei != NULL);
626 	ASSERT(h != NULL);
627 
628 	TAILQ_FOREACH(hi, &hei->hei_head, hi_entry) {
629 		if (strcmp(hi->hi_hook.h_name, h->h_name) == 0)
630 			break;
631 	}
632 	return (hi);
633 }
634 
635 
636 /*
637  * Function:	hook_copy
638  * Returns:	internal hook pointer - NULL = Failed
639  * Parameters:	src(I) - hook pointer
640  *
641  * Allocate internal hook block and duplicate incoming hook.
642  * No locks should be held across this function as it may sleep.
643  */
644 static hook_int_t *
645 hook_copy(hook_t *src)
646 {
647 	hook_int_t *new;
648 	hook_t *dst;
649 
650 	ASSERT(src != NULL);
651 	ASSERT(src->h_name != NULL);
652 
653 	new = (hook_int_t *)kmem_zalloc(sizeof (*new), KM_SLEEP);
654 
655 	/* Copy body */
656 	dst = &new->hi_hook;
657 	*dst = *src;
658 
659 	/* Copy name */
660 	dst->h_name = (char *)kmem_alloc(strlen(src->h_name) + 1, KM_SLEEP);
661 	(void) strcpy(dst->h_name, src->h_name);
662 
663 	return (new);
664 }
665 
666 /*
667  * Function:	hook_free
668  * Returns:	None
669  * Parameters:	hi(I) - internal hook pointer
670  *
671  * Free alloc memory for hook
672  */
673 static void
674 hook_free(hook_int_t *hi)
675 {
676 	ASSERT(hi != NULL);
677 
678 	/* Free name space */
679 	if (hi->hi_hook.h_name != NULL) {
680 		kmem_free(hi->hi_hook.h_name, strlen(hi->hi_hook.h_name) + 1);
681 	}
682 
683 	/* Free container */
684 	kmem_free(hi, sizeof (*hi));
685 }
686