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