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