xref: /netbsd/sys/kern/kern_hook.c (revision befc315d)
1 /*	$NetBSD: kern_hook.c,v 1.14 2022/10/26 23:21:06 riastradh Exp $	*/
2 
3 /*-
4  * Copyright (c) 1997, 1998, 1999, 2002, 2007, 2008 The NetBSD Foundation, Inc.
5  * All rights reserved.
6  *
7  * This code is derived from software contributed to The NetBSD Foundation
8  * by Jason R. Thorpe of the Numerical Aerospace Simulation Facility,
9  * NASA Ames Research Center, and by Luke Mewburn.
10  *
11  * Redistribution and use in source and binary forms, with or without
12  * modification, are permitted provided that the following conditions
13  * are met:
14  * 1. Redistributions of source code must retain the above copyright
15  *    notice, this list of conditions and the following disclaimer.
16  * 2. Redistributions in binary form must reproduce the above copyright
17  *    notice, this list of conditions and the following disclaimer in the
18  *    documentation and/or other materials provided with the distribution.
19  *
20  * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
21  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
22  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
23  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
24  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
25  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
26  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
27  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
28  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
29  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
30  * POSSIBILITY OF SUCH DAMAGE.
31  */
32 
33 #include <sys/cdefs.h>
34 __KERNEL_RCSID(0, "$NetBSD: kern_hook.c,v 1.14 2022/10/26 23:21:06 riastradh Exp $");
35 
36 #include <sys/param.h>
37 
38 #include <sys/condvar.h>
39 #include <sys/cpu.h>
40 #include <sys/device.h>
41 #include <sys/exec.h>
42 #include <sys/hook.h>
43 #include <sys/kmem.h>
44 #include <sys/malloc.h>
45 #include <sys/rwlock.h>
46 #include <sys/systm.h>
47 
48 /*
49  * A generic linear hook.
50  */
51 struct hook_desc {
52 	LIST_ENTRY(hook_desc) hk_list;
53 	void	(*hk_fn)(void *);
54 	void	*hk_arg;
55 };
56 typedef LIST_HEAD(, hook_desc) hook_list_t;
57 
58 enum hook_list_st {
59 	HKLIST_IDLE,
60 	HKLIST_INUSE,
61 };
62 
63 struct khook_list {
64 	hook_list_t	 hl_list;
65 	kmutex_t	 hl_lock;
66 	kmutex_t	*hl_cvlock;
67 	struct lwp	*hl_lwp;
68 	kcondvar_t	 hl_cv;
69 	enum hook_list_st
70 			 hl_state;
71 	khook_t		*hl_active_hk;
72 	char		 hl_namebuf[HOOKNAMSIZ];
73 };
74 
75 int	powerhook_debug = 0;
76 
77 static void *
hook_establish(hook_list_t * list,void (* fn)(void *),void * arg)78 hook_establish(hook_list_t *list, void (*fn)(void *), void *arg)
79 {
80 	struct hook_desc *hd;
81 
82 	hd = malloc(sizeof(*hd), M_DEVBUF, M_NOWAIT);
83 	if (hd == NULL)
84 		return (NULL);
85 
86 	hd->hk_fn = fn;
87 	hd->hk_arg = arg;
88 	LIST_INSERT_HEAD(list, hd, hk_list);
89 
90 	return (hd);
91 }
92 
93 static void
hook_disestablish(hook_list_t * list,void * vhook)94 hook_disestablish(hook_list_t *list, void *vhook)
95 {
96 #ifdef DIAGNOSTIC
97 	struct hook_desc *hd;
98 
99 	LIST_FOREACH(hd, list, hk_list) {
100                 if (hd == vhook)
101 			break;
102 	}
103 
104 	if (hd == NULL)
105 		panic("hook_disestablish: hook %p not established", vhook);
106 #endif
107 	LIST_REMOVE((struct hook_desc *)vhook, hk_list);
108 	free(vhook, M_DEVBUF);
109 }
110 
111 static void
hook_destroy(hook_list_t * list)112 hook_destroy(hook_list_t *list)
113 {
114 	struct hook_desc *hd;
115 
116 	while ((hd = LIST_FIRST(list)) != NULL) {
117 		LIST_REMOVE(hd, hk_list);
118 		free(hd, M_DEVBUF);
119 	}
120 }
121 
122 static void
hook_proc_run(hook_list_t * list,struct proc * p)123 hook_proc_run(hook_list_t *list, struct proc *p)
124 {
125 	struct hook_desc *hd;
126 
127 	LIST_FOREACH(hd, list, hk_list) {
128 		__FPTRCAST(void (*)(struct proc *, void *), *hd->hk_fn)(p,
129 		    hd->hk_arg);
130 	}
131 }
132 
133 /*
134  * "Shutdown hook" types, functions, and variables.
135  *
136  * Should be invoked immediately before the
137  * system is halted or rebooted, i.e. after file systems unmounted,
138  * after crash dump done, etc.
139  *
140  * Each shutdown hook is removed from the list before it's run, so that
141  * it won't be run again.
142  */
143 
144 static hook_list_t shutdownhook_list = LIST_HEAD_INITIALIZER(shutdownhook_list);
145 
146 void *
shutdownhook_establish(void (* fn)(void *),void * arg)147 shutdownhook_establish(void (*fn)(void *), void *arg)
148 {
149 	return hook_establish(&shutdownhook_list, fn, arg);
150 }
151 
152 void
shutdownhook_disestablish(void * vhook)153 shutdownhook_disestablish(void *vhook)
154 {
155 	hook_disestablish(&shutdownhook_list, vhook);
156 }
157 
158 /*
159  * Run shutdown hooks.  Should be invoked immediately before the
160  * system is halted or rebooted, i.e. after file systems unmounted,
161  * after crash dump done, etc.
162  *
163  * Each shutdown hook is removed from the list before it's run, so that
164  * it won't be run again.
165  */
166 void
doshutdownhooks(void)167 doshutdownhooks(void)
168 {
169 	struct hook_desc *dp;
170 
171 	while ((dp = LIST_FIRST(&shutdownhook_list)) != NULL) {
172 		LIST_REMOVE(dp, hk_list);
173 		(*dp->hk_fn)(dp->hk_arg);
174 #if 0
175 		/*
176 		 * Don't bother freeing the hook structure,, since we may
177 		 * be rebooting because of a memory corruption problem,
178 		 * and this might only make things worse.  It doesn't
179 		 * matter, anyway, since the system is just about to
180 		 * reboot.
181 		 */
182 		free(dp, M_DEVBUF);
183 #endif
184 	}
185 }
186 
187 /*
188  * "Mountroot hook" types, functions, and variables.
189  */
190 
191 static hook_list_t mountroothook_list=LIST_HEAD_INITIALIZER(mountroothook_list);
192 
193 void *
mountroothook_establish(void (* fn)(device_t),device_t dev)194 mountroothook_establish(void (*fn)(device_t), device_t dev)
195 {
196 	return hook_establish(&mountroothook_list, __FPTRCAST(void (*), fn),
197 	    dev);
198 }
199 
200 void
mountroothook_disestablish(void * vhook)201 mountroothook_disestablish(void *vhook)
202 {
203 	hook_disestablish(&mountroothook_list, vhook);
204 }
205 
206 void
mountroothook_destroy(void)207 mountroothook_destroy(void)
208 {
209 	hook_destroy(&mountroothook_list);
210 }
211 
212 void
domountroothook(device_t therootdev)213 domountroothook(device_t therootdev)
214 {
215 	struct hook_desc *hd;
216 
217 	LIST_FOREACH(hd, &mountroothook_list, hk_list) {
218 		if (hd->hk_arg == therootdev) {
219 			(*hd->hk_fn)(hd->hk_arg);
220 			return;
221 		}
222 	}
223 }
224 
225 static hook_list_t exechook_list = LIST_HEAD_INITIALIZER(exechook_list);
226 
227 void *
exechook_establish(void (* fn)(struct proc *,void *),void * arg)228 exechook_establish(void (*fn)(struct proc *, void *), void *arg)
229 {
230 	return hook_establish(&exechook_list, __FPTRCAST(void (*)(void *), fn),
231 	    arg);
232 }
233 
234 void
exechook_disestablish(void * vhook)235 exechook_disestablish(void *vhook)
236 {
237 	hook_disestablish(&exechook_list, vhook);
238 }
239 
240 /*
241  * Run exec hooks.
242  */
243 void
doexechooks(struct proc * p)244 doexechooks(struct proc *p)
245 {
246 	hook_proc_run(&exechook_list, p);
247 }
248 
249 static hook_list_t exithook_list = LIST_HEAD_INITIALIZER(exithook_list);
250 
251 void *
exithook_establish(void (* fn)(struct proc *,void *),void * arg)252 exithook_establish(void (*fn)(struct proc *, void *), void *arg)
253 {
254 	void *rv;
255 
256 	rw_enter(&exec_lock, RW_WRITER);
257 	rv = hook_establish(&exithook_list, __FPTRCAST(void (*)(void *), fn),
258 	    arg);
259 	rw_exit(&exec_lock);
260 	return rv;
261 }
262 
263 void
exithook_disestablish(void * vhook)264 exithook_disestablish(void *vhook)
265 {
266 
267 	rw_enter(&exec_lock, RW_WRITER);
268 	hook_disestablish(&exithook_list, vhook);
269 	rw_exit(&exec_lock);
270 }
271 
272 /*
273  * Run exit hooks.
274  */
275 void
doexithooks(struct proc * p)276 doexithooks(struct proc *p)
277 {
278 	hook_proc_run(&exithook_list, p);
279 }
280 
281 static hook_list_t forkhook_list = LIST_HEAD_INITIALIZER(forkhook_list);
282 
283 void *
forkhook_establish(void (* fn)(struct proc *,struct proc *))284 forkhook_establish(void (*fn)(struct proc *, struct proc *))
285 {
286 	return hook_establish(&forkhook_list, __FPTRCAST(void (*)(void *), fn),
287 	    NULL);
288 }
289 
290 void
forkhook_disestablish(void * vhook)291 forkhook_disestablish(void *vhook)
292 {
293 	hook_disestablish(&forkhook_list, vhook);
294 }
295 
296 /*
297  * Run fork hooks.
298  */
299 void
doforkhooks(struct proc * p2,struct proc * p1)300 doforkhooks(struct proc *p2, struct proc *p1)
301 {
302 	struct hook_desc *hd;
303 
304 	LIST_FOREACH(hd, &forkhook_list, hk_list) {
305 		__FPTRCAST(void (*)(struct proc *, struct proc *), *hd->hk_fn)
306 		    (p2, p1);
307 	}
308 }
309 
310 static hook_list_t critpollhook_list = LIST_HEAD_INITIALIZER(critpollhook_list);
311 
312 void *
critpollhook_establish(void (* fn)(void *),void * arg)313 critpollhook_establish(void (*fn)(void *), void *arg)
314 {
315 	return hook_establish(&critpollhook_list, fn, arg);
316 }
317 
318 void
critpollhook_disestablish(void * vhook)319 critpollhook_disestablish(void *vhook)
320 {
321 	hook_disestablish(&critpollhook_list, vhook);
322 }
323 
324 /*
325  * Run critical polling hooks.
326  */
327 void
docritpollhooks(void)328 docritpollhooks(void)
329 {
330 	struct hook_desc *hd;
331 
332 	LIST_FOREACH(hd, &critpollhook_list, hk_list) {
333 		(*hd->hk_fn)(hd->hk_arg);
334 	}
335 }
336 
337 /*
338  * "Power hook" types, functions, and variables.
339  * The list of power hooks is kept ordered with the last registered hook
340  * first.
341  * When running the hooks on power down the hooks are called in reverse
342  * registration order, when powering up in registration order.
343  */
344 struct powerhook_desc {
345 	TAILQ_ENTRY(powerhook_desc) sfd_list;
346 	void	(*sfd_fn)(int, void *);
347 	void	*sfd_arg;
348 	char	sfd_name[16];
349 };
350 
351 static TAILQ_HEAD(powerhook_head, powerhook_desc) powerhook_list =
352     TAILQ_HEAD_INITIALIZER(powerhook_list);
353 
354 void *
powerhook_establish(const char * name,void (* fn)(int,void *),void * arg)355 powerhook_establish(const char *name, void (*fn)(int, void *), void *arg)
356 {
357 	struct powerhook_desc *ndp;
358 
359 	ndp = (struct powerhook_desc *)
360 	    malloc(sizeof(*ndp), M_DEVBUF, M_NOWAIT);
361 	if (ndp == NULL)
362 		return (NULL);
363 
364 	ndp->sfd_fn = fn;
365 	ndp->sfd_arg = arg;
366 	strlcpy(ndp->sfd_name, name, sizeof(ndp->sfd_name));
367 	TAILQ_INSERT_HEAD(&powerhook_list, ndp, sfd_list);
368 
369 	aprint_error("%s: WARNING: powerhook_establish is deprecated\n", name);
370 	return (ndp);
371 }
372 
373 void
powerhook_disestablish(void * vhook)374 powerhook_disestablish(void *vhook)
375 {
376 #ifdef DIAGNOSTIC
377 	struct powerhook_desc *dp;
378 
379 	TAILQ_FOREACH(dp, &powerhook_list, sfd_list)
380                 if (dp == vhook)
381 			goto found;
382 	panic("powerhook_disestablish: hook %p not established", vhook);
383  found:
384 #endif
385 
386 	TAILQ_REMOVE(&powerhook_list, (struct powerhook_desc *)vhook,
387 	    sfd_list);
388 	free(vhook, M_DEVBUF);
389 }
390 
391 /*
392  * Run power hooks.
393  */
394 void
dopowerhooks(int why)395 dopowerhooks(int why)
396 {
397 	struct powerhook_desc *dp;
398 	const char *why_name;
399 	static const char * pwr_names[] = {PWR_NAMES};
400 	why_name = why < __arraycount(pwr_names) ? pwr_names[why] : "???";
401 
402 	if (why == PWR_RESUME || why == PWR_SOFTRESUME) {
403 		TAILQ_FOREACH_REVERSE(dp, &powerhook_list, powerhook_head,
404 		    sfd_list)
405 		{
406 			if (powerhook_debug)
407 				printf("dopowerhooks %s: %s (%p)\n",
408 				    why_name, dp->sfd_name, dp);
409 			(*dp->sfd_fn)(why, dp->sfd_arg);
410 		}
411 	} else {
412 		TAILQ_FOREACH(dp, &powerhook_list, sfd_list) {
413 			if (powerhook_debug)
414 				printf("dopowerhooks %s: %s (%p)\n",
415 				    why_name, dp->sfd_name, dp);
416 			(*dp->sfd_fn)(why, dp->sfd_arg);
417 		}
418 	}
419 
420 	if (powerhook_debug)
421 		printf("dopowerhooks: %s done\n", why_name);
422 }
423 
424 /*
425  * A simple linear hook.
426  */
427 
428 khook_list_t *
simplehook_create(int ipl,const char * wmsg)429 simplehook_create(int ipl, const char *wmsg)
430 {
431 	khook_list_t *l;
432 
433 	l = kmem_zalloc(sizeof(*l), KM_SLEEP);
434 
435 	mutex_init(&l->hl_lock, MUTEX_DEFAULT, ipl);
436 	strlcpy(l->hl_namebuf, wmsg, sizeof(l->hl_namebuf));
437 	cv_init(&l->hl_cv, l->hl_namebuf);
438 	LIST_INIT(&l->hl_list);
439 	l->hl_state = HKLIST_IDLE;
440 
441 	return l;
442 }
443 
444 void
simplehook_destroy(khook_list_t * l)445 simplehook_destroy(khook_list_t *l)
446 {
447 	struct hook_desc *hd;
448 
449 	KASSERT(l->hl_state == HKLIST_IDLE);
450 
451 	while ((hd = LIST_FIRST(&l->hl_list)) != NULL) {
452 		LIST_REMOVE(hd, hk_list);
453 		kmem_free(hd, sizeof(*hd));
454 	}
455 
456 	cv_destroy(&l->hl_cv);
457 	mutex_destroy(&l->hl_lock);
458 	kmem_free(l, sizeof(*l));
459 }
460 
461 int
simplehook_dohooks(khook_list_t * l)462 simplehook_dohooks(khook_list_t *l)
463 {
464 	struct hook_desc *hd, *nexthd;
465 	kmutex_t *cv_lock;
466 	void (*fn)(void *);
467 	void *arg;
468 
469 	mutex_enter(&l->hl_lock);
470 	if (l->hl_state != HKLIST_IDLE) {
471 		mutex_exit(&l->hl_lock);
472 		return EBUSY;
473 	}
474 
475 	/* stop removing hooks */
476 	l->hl_state = HKLIST_INUSE;
477 	l->hl_lwp = curlwp;
478 
479 	LIST_FOREACH(hd, &l->hl_list, hk_list) {
480 		if (hd->hk_fn == NULL)
481 			continue;
482 
483 		fn = hd->hk_fn;
484 		arg = hd->hk_arg;
485 		l->hl_active_hk = hd;
486 		l->hl_cvlock = NULL;
487 
488 		mutex_exit(&l->hl_lock);
489 
490 		/* do callback without l->hl_lock */
491 		(*fn)(arg);
492 
493 		mutex_enter(&l->hl_lock);
494 		l->hl_active_hk = NULL;
495 		cv_lock = l->hl_cvlock;
496 
497 		if (hd->hk_fn == NULL) {
498 			if (cv_lock != NULL) {
499 				mutex_exit(&l->hl_lock);
500 				mutex_enter(cv_lock);
501 			}
502 
503 			cv_broadcast(&l->hl_cv);
504 
505 			if (cv_lock != NULL) {
506 				mutex_exit(cv_lock);
507 				mutex_enter(&l->hl_lock);
508 			}
509 		}
510 	}
511 
512 	/* remove marked node while running hooks */
513 	LIST_FOREACH_SAFE(hd, &l->hl_list, hk_list, nexthd) {
514 		if (hd->hk_fn == NULL) {
515 			LIST_REMOVE(hd, hk_list);
516 			kmem_free(hd, sizeof(*hd));
517 		}
518 	}
519 
520 	l->hl_lwp = NULL;
521 	l->hl_state = HKLIST_IDLE;
522 	mutex_exit(&l->hl_lock);
523 
524 	return 0;
525 }
526 
527 khook_t *
simplehook_establish(khook_list_t * l,void (* fn)(void *),void * arg)528 simplehook_establish(khook_list_t *l, void (*fn)(void *), void *arg)
529 {
530 	struct hook_desc *hd;
531 
532 	hd = kmem_zalloc(sizeof(*hd), KM_SLEEP);
533 	hd->hk_fn = fn;
534 	hd->hk_arg = arg;
535 
536 	mutex_enter(&l->hl_lock);
537 	LIST_INSERT_HEAD(&l->hl_list, hd, hk_list);
538 	mutex_exit(&l->hl_lock);
539 
540 	return hd;
541 }
542 
543 void
simplehook_disestablish(khook_list_t * l,khook_t * hd,kmutex_t * lock)544 simplehook_disestablish(khook_list_t *l, khook_t *hd, kmutex_t *lock)
545 {
546 	struct hook_desc *hd0 __diagused;
547 	kmutex_t *cv_lock;
548 
549 	KASSERT(lock == NULL || mutex_owned(lock));
550 	mutex_enter(&l->hl_lock);
551 
552 #ifdef DIAGNOSTIC
553 	LIST_FOREACH(hd0, &l->hl_list, hk_list) {
554 		if (hd == hd0)
555 			break;
556 	}
557 
558 	if (hd0 == NULL)
559 		panic("hook_disestablish: hook %p not established", hd);
560 #endif
561 
562 	/* The hook is not referred, remove immediately */
563 	if (l->hl_state == HKLIST_IDLE) {
564 		LIST_REMOVE(hd, hk_list);
565 		kmem_free(hd, sizeof(*hd));
566 		mutex_exit(&l->hl_lock);
567 		return;
568 	}
569 
570 	/* remove callback. hd will be removed in dohooks */
571 	hd->hk_fn = NULL;
572 	hd->hk_arg = NULL;
573 
574 	/* If the hook is running, wait for the completion */
575 	if (l->hl_active_hk == hd &&
576 	    l->hl_lwp != curlwp) {
577 		if (lock != NULL) {
578 			cv_lock = lock;
579 			KASSERT(l->hl_cvlock == NULL);
580 			l->hl_cvlock = lock;
581 			mutex_exit(&l->hl_lock);
582 		} else {
583 			cv_lock = &l->hl_lock;
584 		}
585 
586 		cv_wait(&l->hl_cv, cv_lock);
587 
588 		if (lock == NULL)
589 			mutex_exit(&l->hl_lock);
590 	} else {
591 		mutex_exit(&l->hl_lock);
592 	}
593 }
594 
595 bool
simplehook_has_hooks(khook_list_t * l)596 simplehook_has_hooks(khook_list_t *l)
597 {
598 	bool empty;
599 
600 	mutex_enter(&l->hl_lock);
601 	empty = LIST_EMPTY(&l->hl_list);
602 	mutex_exit(&l->hl_lock);
603 
604 	return !empty;
605 }
606