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