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 */ 52struct hook_desc { 53 LIST_ENTRY(hook_desc) hk_list; 54 void (*hk_fn)(void *); 55 void *hk_arg; 56}; 57typedef LIST_HEAD(, hook_desc) hook_list_t; 58 59enum hook_list_st { 60 HKLIST_IDLE, 61 HKLIST_INUSE, 62}; 63 64struct 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 76int powerhook_debug = 0; 77 78static ONCE_DECL(hook_control); 79static krwlock_t exithook_lock; 80static krwlock_t forkhook_lock; 81 82static int 83hook_init(void) 84{ 85 86 rw_init(&exithook_lock); 87 rw_init(&forkhook_lock); 88 89 return 0; 90} 91 92static void * 93hook_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 114static void 115hook_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 137static void 138hook_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 148static void 149hook_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 176static hook_list_t shutdownhook_list = LIST_HEAD_INITIALIZER(shutdownhook_list); 177 178void * 179shutdownhook_establish(void (*fn)(void *), void *arg) 180{ 181 return hook_establish(&shutdownhook_list, NULL, fn, arg); 182} 183 184void 185shutdownhook_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 */ 198void 199doshutdownhooks(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 223static hook_list_t mountroothook_list=LIST_HEAD_INITIALIZER(mountroothook_list); 224 225void * 226mountroothook_establish(void (*fn)(device_t), device_t dev) 227{ 228 return hook_establish(&mountroothook_list, NULL, 229 __FPTRCAST(void (*), fn), dev); 230} 231 232void 233mountroothook_disestablish(void *vhook) 234{ 235 hook_disestablish(&mountroothook_list, NULL, vhook); 236} 237 238void 239mountroothook_destroy(void) 240{ 241 hook_destroy(&mountroothook_list); 242} 243 244void 245domountroothook(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 257static hook_list_t exechook_list = LIST_HEAD_INITIALIZER(exechook_list); 258 259void * 260exechook_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 266void 267exechook_disestablish(void *vhook) 268{ 269 hook_disestablish(&exechook_list, &exec_lock, vhook); 270} 271 272/* 273 * Run exec hooks. 274 */ 275void 276doexechooks(struct proc *p) 277{ 278 KASSERT(rw_lock_held(&exec_lock)); 279 280 hook_proc_run(&exechook_list, NULL, p); 281} 282 283static hook_list_t exithook_list = LIST_HEAD_INITIALIZER(exithook_list); 284 285void * 286exithook_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 293void 294exithook_disestablish(void *vhook) 295{ 296 297 hook_disestablish(&exithook_list, &exithook_lock, vhook); 298} 299 300/* 301 * Run exit hooks. 302 */ 303void 304doexithooks(struct proc *p) 305{ 306 hook_proc_run(&exithook_list, &exithook_lock, p); 307} 308 309static hook_list_t forkhook_list = LIST_HEAD_INITIALIZER(forkhook_list); 310 311void * 312forkhook_establish(void (*fn)(struct proc *, struct proc *)) 313{ 314 return hook_establish(&forkhook_list, &forkhook_lock, 315 __FPTRCAST(void (*)(void *), fn), NULL); 316} 317 318void 319forkhook_disestablish(void *vhook) 320{ 321 hook_disestablish(&forkhook_list, &forkhook_lock, vhook); 322} 323 324/* 325 * Run fork hooks. 326 */ 327void 328doforkhooks(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 342static hook_list_t critpollhook_list = LIST_HEAD_INITIALIZER(critpollhook_list); 343 344void * 345critpollhook_establish(void (*fn)(void *), void *arg) 346{ 347 return hook_establish(&critpollhook_list, NULL, fn, arg); 348} 349 350void 351critpollhook_disestablish(void *vhook) 352{ 353 hook_disestablish(&critpollhook_list, NULL, vhook); 354} 355 356/* 357 * Run critical polling hooks. 358 */ 359void 360docritpollhooks(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 */ 376struct 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 383static TAILQ_HEAD(powerhook_head, powerhook_desc) powerhook_list = 384 TAILQ_HEAD_INITIALIZER(powerhook_list); 385 386void * 387powerhook_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 405void 406powerhook_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 */ 426void 427dopowerhooks(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 460khook_list_t * 461simplehook_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 476void 477simplehook_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 493int 494simplehook_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 559khook_t * 560simplehook_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 575void 576simplehook_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 627bool 628simplehook_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