Home | History | Annotate | Line # | Download | only in kern
      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
     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 *
     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
    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
    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
    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 *
    179 shutdownhook_establish(void (*fn)(void *), void *arg)
    180 {
    181 	return hook_establish(&shutdownhook_list, NULL, fn, arg);
    182 }
    183 
    184 void
    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
    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 *
    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
    233 mountroothook_disestablish(void *vhook)
    234 {
    235 	hook_disestablish(&mountroothook_list, NULL, vhook);
    236 }
    237 
    238 void
    239 mountroothook_destroy(void)
    240 {
    241 	hook_destroy(&mountroothook_list);
    242 }
    243 
    244 void
    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 *
    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
    267 exechook_disestablish(void *vhook)
    268 {
    269 	hook_disestablish(&exechook_list, &exec_lock, vhook);
    270 }
    271 
    272 /*
    273  * Run exec hooks.
    274  */
    275 void
    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 *
    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
    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
    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 *
    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
    319 forkhook_disestablish(void *vhook)
    320 {
    321 	hook_disestablish(&forkhook_list, &forkhook_lock, vhook);
    322 }
    323 
    324 /*
    325  * Run fork hooks.
    326  */
    327 void
    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 *
    345 critpollhook_establish(void (*fn)(void *), void *arg)
    346 {
    347 	return hook_establish(&critpollhook_list, NULL, fn, arg);
    348 }
    349 
    350 void
    351 critpollhook_disestablish(void *vhook)
    352 {
    353 	hook_disestablish(&critpollhook_list, NULL, vhook);
    354 }
    355 
    356 /*
    357  * Run critical polling hooks.
    358  */
    359 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 *
    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
    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
    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 *
    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
    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
    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 *
    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
    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
    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