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