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