1 /* $NetBSD: hooks.h,v 1.9 2025/01/26 16:25:46 christos Exp $ */ 2 3 /* 4 * Copyright (C) Internet Systems Consortium, Inc. ("ISC") 5 * 6 * SPDX-License-Identifier: MPL-2.0 7 * 8 * This Source Code Form is subject to the terms of the Mozilla Public 9 * License, v. 2.0. If a copy of the MPL was not distributed with this 10 * file, you can obtain one at https://mozilla.org/MPL/2.0/. 11 * 12 * See the COPYRIGHT file distributed with this work for additional 13 * information regarding copyright ownership. 14 */ 15 16 #pragma once 17 18 /*! \file */ 19 20 #include <stdbool.h> 21 22 #include <isc/list.h> 23 #include <isc/magic.h> 24 #include <isc/mem.h> 25 #include <isc/result.h> 26 27 #include <dns/rdatatype.h> 28 29 #include <ns/client.h> 30 #include <ns/query.h> 31 /* 32 * "Hooks" are a mechanism to call a defined function or set of functions once 33 * a certain place in code is reached. Hook actions can inspect and alter the 34 * state of an ongoing process, allowing processing to continue afterward or 35 * triggering an early return. 36 * 37 * Currently hooks are used in two ways: in plugins, which use them to 38 * add functionality to query processing, and in the unit tests for libns, 39 * where they are used to inspect state before and after certain functions have 40 * run. 41 * 42 * Both of these uses are limited to libns, so hooks are currently defined in 43 * the ns/hooks.h header file, and hook-related macro and function names are 44 * prefixed with `NS_` and `ns_`. However, the design is fairly generic and 45 * could be repurposed for general use, e.g. as part of libisc, after some 46 * further customization. 47 * 48 * Hooks are created by defining a hook point identifier in the ns_hookpoint_t 49 * enum below, and placing a special call at a corresponding location in the 50 * code which invokes the action(s) for that hook; there are two such special 51 * calls currently implemented, namely the CALL_HOOK() and CALL_HOOK_NORETURN() 52 * macros in query.c. The former macro contains a "goto cleanup" statement 53 * which is inlined into the function into which the hook has been inserted; 54 * this enables the hook action to cause the calling function to return from 55 * the hook insertion point. For functions returning isc_result_t, if a hook 56 * action intends to cause a return at hook insertion point, it also has to set 57 * the value to be returned by the calling function. 58 * 59 * A hook table is an array (indexed by the value of the hook point identifier) 60 * in which each cell contains a linked list of structures, each of which 61 * contains a function pointer to a hook action and a pointer to data which is 62 * to be passed to the action function when it is called. 63 * 64 * Each view has its own separate hook table, populated by loading plugin 65 * modules specified in the "plugin" statements in named.conf. There is also a 66 * special, global hook table (ns__hook_table) that is only used by libns unit 67 * tests and whose existence can be safely ignored by plugin modules. 68 * 69 * Hook actions are functions which: 70 * 71 * - return an ns_hookresult_t value: 72 * - if NS_HOOK_RETURN is returned by the hook action, the function 73 * into which the hook is inserted will return and no further hook 74 * actions at the same hook point will be invoked, 75 * - if NS_HOOK_CONTINUE is returned by the hook action and there are 76 * further hook actions set up at the same hook point, they will be 77 * processed; if NS_HOOK_CONTINUE is returned and there are no 78 * further hook actions set up at the same hook point, execution of 79 * the function into which the hook has been inserted will be 80 * resumed. 81 * 82 * - accept three pointers as arguments: 83 * - a pointer specified by the special call at the hook insertion point, 84 * - a pointer specified upon inserting the action into the hook table, 85 * - a pointer to an isc_result_t value which will be returned by the 86 * function into which the hook is inserted if the action returns 87 * NS_HOOK_RETURN. 88 * 89 * In order for a hook action to be called for a given hook, a pointer to that 90 * action function (along with an optional pointer to action-specific data) has 91 * to be inserted into the relevant hook table entry for that hook using an 92 * ns_hook_add() call. If multiple actions are set up at a single hook point 93 * (e.g. by multiple plugin modules), they are processed in FIFO order, that is 94 * they are performed in the same order in which their relevant ns_hook_add() 95 * calls were issued. Since the configuration is loaded from a single thread, 96 * this means that multiple actions at a single hook point are determined by 97 * the order in which the relevant plugin modules were declared in the 98 * configuration file(s). The hook API currently does not support changing 99 * this order. 100 * 101 * As an example, consider the following hypothetical function in query.c: 102 * 103 * ---------------------------------------------------------------------------- 104 * static isc_result_t 105 * query_foo(query_ctx_t *qctx) { 106 * isc_result_t result; 107 * 108 * CALL_HOOK(NS_QUERY_FOO_BEGIN, qctx); 109 * 110 * ns_client_log(qctx->client, NS_LOGCATEGORY_CLIENT, NS_LOGMODULE_QUERY, 111 * ISC_LOG_DEBUG(99), "Lorem ipsum dolor sit amet..."); 112 * 113 * result = ISC_R_COMPLETE; 114 * 115 * cleanup: 116 * return (result); 117 * } 118 * ---------------------------------------------------------------------------- 119 * 120 * and the following hook action: 121 * 122 * ---------------------------------------------------------------------------- 123 * static ns_hookresult_t 124 * cause_failure(void *hook_data, void *action_data, isc_result_t *resultp) { 125 * UNUSED(hook_data); 126 * UNUSED(action_data); 127 * 128 * *resultp = ISC_R_FAILURE; 129 * 130 * return (NS_HOOK_RETURN); 131 * } 132 * ---------------------------------------------------------------------------- 133 * 134 * If this hook action was installed in the hook table using: 135 * 136 * ---------------------------------------------------------------------------- 137 * const ns_hook_t foo_fail = { 138 * .action = cause_failure, 139 * }; 140 * 141 * ns_hook_add(..., NS_QUERY_FOO_BEGIN, &foo_fail); 142 * ---------------------------------------------------------------------------- 143 * 144 * then query_foo() would return ISC_R_FAILURE every time it is called due 145 * to the cause_failure() hook action returning NS_HOOK_RETURN and setting 146 * '*resultp' to ISC_R_FAILURE. query_foo() would also never log the 147 * "Lorem ipsum dolor sit amet..." message. 148 * 149 * Consider a different hook action: 150 * 151 * ---------------------------------------------------------------------------- 152 * static ns_hookresult_t 153 * log_qtype(void *hook_data, void *action_data, isc_result_t *resultp) { 154 * query_ctx_t *qctx = (query_ctx_t *)hook_data; 155 * FILE *stream = (FILE *)action_data; 156 * 157 * UNUSED(resultp); 158 * 159 * fprintf(stream, "QTYPE=%u\n", qctx->qtype); 160 * 161 * return (NS_HOOK_CONTINUE); 162 * } 163 * ---------------------------------------------------------------------------- 164 * 165 * If this hook action was installed in the hook table instead of 166 * cause_failure(), using: 167 * 168 * ---------------------------------------------------------------------------- 169 * const ns_hook_t foo_log_qtype = { 170 * .action = log_qtype, 171 * .action_data = stderr, 172 * }; 173 * 174 * ns_hook_add(..., NS_QUERY_FOO_BEGIN, &foo_log_qtype); 175 * ---------------------------------------------------------------------------- 176 * 177 * then the QTYPE stored in the query context passed to query_foo() would be 178 * logged to stderr upon each call to that function; 'qctx' would be passed to 179 * the hook action in 'hook_data' since it is specified in the CALL_HOOK() call 180 * inside query_foo() while stderr would be passed to the hook action in 181 * 'action_data' since it is specified in the ns_hook_t structure passed to 182 * ns_hook_add(). As the hook action returns NS_HOOK_CONTINUE, 183 * query_foo() would also be logging the "Lorem ipsum dolor sit amet..." 184 * message before returning ISC_R_COMPLETE. 185 * 186 * ASYNCHRONOUS EVENT HANDLING IN QUERY HOOKS 187 * 188 * Usually a hook action works synchronously; it completes some particular 189 * job in the middle of query processing, thus blocking the caller (and the 190 * worker thread handling the query). But sometimes an action can be time 191 * consuming and the blocking behavior may not be acceptable. For example, 192 * a hook may need to send some kind of query (like a DB lookup) to an 193 * external backend server and wait for the response to complete the hook's 194 * action. Depending on the network condition, the external server's load, 195 * etc, it may take several seconds or more. 196 * 197 * In order to handle such a situation, a hook action can start an 198 * asynchronous event by calling ns_query_hookasync(). This is similar 199 * to ns_query_recurse(), but more generic. ns_query_hookasync() will 200 * call the 'runasync' function with a specified 'arg' (both passed to 201 * ns_query_hookasync()) and a set of loop and associated event arguments 202 * to be called to resume query handling upon completion of the 203 * asynchronous event. 204 * 205 * The implementation of 'runasync' is assumed to allocate and build an 206 * instance of ns_hook_resume_t whose callback, arg, and loop are set to 207 * the passed values from ns_query_hookasync(). Other fields of 208 * ns_hook_resume_t must be correctly set in the hook implementation 209 * by the time it's sent to the specified loop: 210 * 211 * - hookpoint: the point from which the query handling should be resumed 212 * (which should usually be the hook point that triggered the asynchronous 213 * event). 214 * - origresult: the result code passed to the hook action that triggers the 215 * asynchronous event through the 'resultp' pointer. Some hook points need 216 * this value to correctly resume the query handling. 217 * - saved_qctx: the 'qctx' passed to 'runasync'. This holds some 218 * intermediate data for resolving the query, and will be used to resume the 219 * query handling. The 'runasync' implementation must not modify it. 220 * 221 * The hook implementation should somehow maintain the created event 222 * instance so that it can eventually send the event. 223 * 224 * 'runasync' then creates an instance of ns_hookasync_t with specifying its 225 * own cancel and destroy function, and returns it to ns_query_hookasync() 226 * in the passed pointer. 227 * 228 * On return from ns_query_hookasync(), the hook action MUST return 229 * NS_HOOK_RETURN to suspend the query handling. 230 * 231 * On the completion of the asynchronous event, the hook implementation is 232 * supposed to send the resumeevent to the corresponding loop. The query 233 * module resumes the query handling so that the hook action of the 234 * specified hook point will be called, skipping some intermediate query 235 * handling steps. So, typically, the same hook action will be called 236 * twice. The hook implementation must somehow remember the context, and 237 * handle the second call to complete its action using the result of the 238 * asynchronous event. 239 * 240 * Example: assume the following hook-specific structure to manage 241 * asynchronous events: 242 * 243 * typedef struct hookstate { 244 * bool async; 245 * ns_hook_resume_t *rev 246 * ns_hookpoint_t hookpoint; 247 * isc_result_t origresult; 248 * } hookstate_t; 249 * 250 * 'async' is supposed to be true if and only if hook-triggered 251 * asynchronous processing is taking place. 252 * 253 * A hook action that uses an asynchronous event would look something 254 * like this: 255 * 256 * hook_recurse(void *hook_data, void *action_data, isc_result_t *resultp) { 257 * hookstate_t *state = somehow_retrieve_from(action_data); 258 * if (state->async) { 259 * // just resumed from an asynchronous hook action. 260 * // complete the hook's action using the result of the 261 * // internal asynchronous event. 262 * state->async = false; 263 * return (NS_HOOK_CONTINUE); 264 * } 265 * 266 * // Initial call to the hook action. Start the internal 267 * // asynchronous event, and have the query module suspend 268 * // its own handling by returning NS_HOOK_RETURN. 269 * state->hookpoint = ...; // would be hook point for this hook 270 * state->origresult = *resultp; 271 * ns_query_hookasync(hook_data, runasync, state); 272 * state->async = true; 273 * return (NS_HOOK_RETURN); 274 * } 275 * 276 * And the 'runasync' function would be something like this: 277 * 278 * static isc_result_t 279 * runasync(query_ctx_t *qctx, void *arg, isc_job_cb cb, void *evarg, 280 * isc_loop_t *loop, ns_hookasync_t **ctxp) { 281 * hookstate_t *state = arg; 282 * ns_hook_resume_t *rev isc_mem_get(mctx, sizeof(*rev)); 283 * ns_hookasync_t *ctx = isc_mem_get(mctx, sizeof(*ctx)); 284 * 285 * *ctx = (ns_hookasync_t){ .private = NULL 286 * .hookpoint = state->hookpoint, 287 * .origresult = state->origresult, 288 * .saved_ctx = qctx, 289 * .ctx = ctx, 290 * .loop = loop, 291 * .cb = cb, 292 * .arg = cbarg 293 * }; 294 * isc_mem_attach(mctx, &ctx->mctx); 295 * 296 * ctx->cancel = ...; // set the cancel function, which cancels the 297 * // internal asynchronous event (if necessary). 298 * // it should eventually result in sending 299 * // the 'rev' event to the calling loop. 300 * ctx->destroy = ...; // set the destroy function, which frees 'ctx' 301 * 302 * state->rev = rev; // store the resume state so we can send it later 303 * 304 * // initiate some asynchronous process here - for example, a 305 * // recursive fetch. 306 * 307 * *ctxp = ctx; 308 * return (ISC_R_SUCCESS); 309 * } 310 * 311 * Finally, in the completion handler for the asynchronous process, we 312 * need to send a resumption event so that query processing can resume. 313 * For example, the completion handler might call this function: 314 * 315 * static void 316 * asyncproc_done(hookstate_t *state) { 317 * isc_async_run(state->rev->loop, state->rev->cb, state->rev->arg); 318 * } 319 * 320 * Caveats: 321 * - On resuming from a hook-initiated asynchronous process, code in 322 * the query module before the hook point needs to be exercised. 323 * So if this part has side effects, it's possible that the resuming 324 * doesn't work well. Currently, NS_QUERY_RESPOND_ANY_FOUND is 325 * explicitly prohibited to be used as the resume point. 326 * - In general, hooks other than those called at the beginning of the 327 * caller function may not work safely with asynchronous processing for 328 * the reason stated in the previous bullet. For example, a hook action 329 * for NS_QUERY_DONE_SEND may not be able to start an asychronous 330 * function safely. 331 * - Hook-triggered asynchronous processing is not allowed to be running 332 * while the standard DNS recursive fetch is taking place (starting 333 * from a call to dns_resolver_createfetch()), as the two would be 334 * using some of the same context resources. For this reason the 335 * NS_QUERY_NOTFOUND_RECURSE and NS_QUERY_ZEROTTL_RECURSE hook points 336 * are explicitly prohibited from being used for asynchronous hook 337 * actions. 338 * - Specifying multiple hook actions for the same hook point at the 339 * same time may cause problems, as resumption from one hook action 340 * could cause another hook to be called twice unintentionally. 341 * It's generally not safe to assume such a use case works, 342 * especially if the hooks are developed independently. (Note that 343 * that's not necessarily specific to the use of asynchronous hook 344 * actions. As long as hook actions have side effects, including 345 * modifying the internal query state, it's not guaranteed safe 346 * to use multiple independent hooks at the same time.) 347 */ 348 349 /*! 350 * Currently-defined hook points. So long as these are unique, the order in 351 * which they are declared is unimportant, but it currently matches the 352 * order in which they are referenced in query.c. 353 */ 354 typedef enum { 355 /* hookpoints from query.c */ 356 NS_QUERY_QCTX_INITIALIZED, 357 NS_QUERY_QCTX_DESTROYED, 358 NS_QUERY_SETUP, 359 NS_QUERY_START_BEGIN, 360 NS_QUERY_LOOKUP_BEGIN, 361 NS_QUERY_RESUME_BEGIN, 362 NS_QUERY_RESUME_RESTORED, 363 NS_QUERY_GOT_ANSWER_BEGIN, 364 NS_QUERY_RESPOND_ANY_BEGIN, 365 NS_QUERY_RESPOND_ANY_FOUND, 366 NS_QUERY_ADDANSWER_BEGIN, 367 NS_QUERY_RESPOND_BEGIN, 368 NS_QUERY_NOTFOUND_BEGIN, 369 NS_QUERY_NOTFOUND_RECURSE, 370 NS_QUERY_PREP_DELEGATION_BEGIN, 371 NS_QUERY_ZONE_DELEGATION_BEGIN, 372 NS_QUERY_DELEGATION_BEGIN, 373 NS_QUERY_DELEGATION_RECURSE_BEGIN, 374 NS_QUERY_NODATA_BEGIN, 375 NS_QUERY_NXDOMAIN_BEGIN, 376 NS_QUERY_NCACHE_BEGIN, 377 NS_QUERY_ZEROTTL_RECURSE, 378 NS_QUERY_CNAME_BEGIN, 379 NS_QUERY_DNAME_BEGIN, 380 NS_QUERY_PREP_RESPONSE_BEGIN, 381 NS_QUERY_DONE_BEGIN, 382 NS_QUERY_DONE_SEND, 383 384 /* XXX other files could be added later */ 385 386 NS_HOOKPOINTS_COUNT /* MUST BE LAST */ 387 } ns_hookpoint_t; 388 389 /* 390 * Returned by a hook action to indicate how to proceed after it has 391 * been called: continue processing, or return immediately. 392 */ 393 typedef enum { 394 NS_HOOK_CONTINUE, 395 NS_HOOK_RETURN, 396 } ns_hookresult_t; 397 398 typedef ns_hookresult_t (*ns_hook_action_t)(void *arg, void *data, 399 isc_result_t *resultp); 400 401 typedef struct ns_hook { 402 isc_mem_t *mctx; 403 ns_hook_action_t action; 404 void *action_data; 405 ISC_LINK(struct ns_hook) link; 406 } ns_hook_t; 407 408 typedef ISC_LIST(ns_hook_t) ns_hooklist_t; 409 typedef ns_hooklist_t ns_hooktable_t[NS_HOOKPOINTS_COUNT]; 410 411 /*% 412 * ns__hook_table is a global hook table, which is used if view->hooktable 413 * is NULL. It's intended only for use by unit tests. 414 */ 415 extern ns_hooktable_t *ns__hook_table; 416 417 typedef void (*ns_hook_cancelasync_t)(ns_hookasync_t *); 418 typedef void (*ns_hook_destroyasync_t)(ns_hookasync_t **); 419 420 /*% 421 * Context for a hook-initiated asynchronous process. This works 422 * similarly to dns_fetch_t. 423 */ 424 struct ns_hookasync { 425 isc_mem_t *mctx; 426 427 /* 428 * The following two are equivalent to dns_resolver_cancelfetch and 429 * dns_resolver_destroyfetch, respectively, but specified as function 430 * pointers since they can be hook-specific. 431 */ 432 ns_hook_cancelasync_t cancel; 433 ns_hook_destroyasync_t destroy; 434 435 void *private; /* hook-specific data */ 436 }; 437 438 /* 439 * isc_event to be sent on the completion of a hook-initiated asyncronous 440 * process, similar to dns_fetchresponse_t. 441 */ 442 typedef struct ns_hook_resume { 443 ns_hookasync_t *ctx; /* asynchronous processing context */ 444 ns_hookpoint_t hookpoint; /* hook point from which to resume */ 445 isc_result_t origresult; /* result code at the point of call to hook */ 446 query_ctx_t *saved_qctx; /* qctx at the point of call to hook */ 447 isc_loop_t *loop; /* loopmgr loop to resume in */ 448 isc_job_cb cb; /* callback function */ 449 void *arg; /* argument to pass to the callback */ 450 } ns_hook_resume_t; 451 452 /* 453 * Plugin API version 454 * 455 * When the API changes, increment NS_PLUGIN_VERSION. If the 456 * change is backward-compatible (e.g., adding a new function call 457 * but not changing or removing an old one), increment NS_PLUGIN_AGE 458 * as well; if not, set NS_PLUGIN_AGE to 0. 459 */ 460 #ifndef NS_PLUGIN_VERSION 461 #define NS_PLUGIN_VERSION 2 462 #define NS_PLUGIN_AGE 0 463 #endif /* ifndef NS_PLUGIN_VERSION */ 464 465 typedef isc_result_t 466 ns_plugin_register_t(const char *parameters, const void *cfg, const char *file, 467 unsigned long line, isc_mem_t *mctx, isc_log_t *lctx, 468 void *actx, ns_hooktable_t *hooktable, void **instp); 469 /*%< 470 * Called when registering a new plugin. 471 * 472 * 'parameters' contains the plugin configuration text. 473 * 474 * '*instp' will be set to the module instance handle if the function 475 * is successful. 476 * 477 * Returns: 478 *\li #ISC_R_SUCCESS 479 *\li #ISC_R_NOMEMORY 480 *\li Other errors are possible 481 */ 482 483 typedef void 484 ns_plugin_destroy_t(void **instp); 485 /*%< 486 * Destroy a plugin instance. 487 * 488 * '*instp' must be set to NULL by the function before it returns. 489 */ 490 491 typedef isc_result_t 492 ns_plugin_check_t(const char *parameters, const void *cfg, const char *file, 493 unsigned long line, isc_mem_t *mctx, isc_log_t *lctx, 494 void *actx); 495 /*%< 496 * Check the validity of 'parameters'. 497 */ 498 499 typedef int 500 ns_plugin_version_t(void); 501 /*%< 502 * Return the API version number a plugin was compiled with. 503 * 504 * If the returned version number is no greater than 505 * NS_PLUGIN_VERSION, and no less than NS_PLUGIN_VERSION - NS_PLUGIN_AGE, 506 * then the module is API-compatible with named. 507 */ 508 509 /*% 510 * Prototypes for API functions to be defined in each module. 511 */ 512 ns_plugin_check_t plugin_check; 513 ns_plugin_destroy_t plugin_destroy; 514 ns_plugin_register_t plugin_register; 515 ns_plugin_version_t plugin_version; 516 517 isc_result_t 518 ns_plugin_expandpath(const char *src, char *dst, size_t dstsize); 519 /*%< 520 * Prepare the plugin location to be passed to dlopen() based on the plugin 521 * path or filename found in the configuration file ('src'). Store the result 522 * in 'dst', which is 'dstsize' bytes large. 523 * 524 * On Unix systems, two classes of 'src' are recognized: 525 * 526 * - If 'src' is an absolute or relative path, it will be copied to 'dst' 527 * verbatim. 528 * 529 * - If 'src' is a filename (i.e. does not contain a path separator), the 530 * path to the directory into which named plugins are installed will be 531 * prepended to it and the result will be stored in 'dst'. 532 * 533 * Returns: 534 *\li #ISC_R_SUCCESS Success 535 *\li #ISC_R_NOSPACE 'dst' is not large enough to hold the output string 536 *\li Other result snprintf() returned a negative value 537 */ 538 539 isc_result_t 540 ns_plugin_register(const char *modpath, const char *parameters, const void *cfg, 541 const char *cfg_file, unsigned long cfg_line, 542 isc_mem_t *mctx, isc_log_t *lctx, void *actx, 543 dns_view_t *view); 544 /*%< 545 * Load the plugin module specified from the file 'modpath', and 546 * register an instance using 'parameters'. 547 * 548 * 'cfg_file' and 'cfg_line' specify the location of the plugin 549 * declaration in the configuration file. 550 * 551 * 'cfg' and 'actx' are the configuration context and ACL configuration 552 * context, respectively; they are passed as void * here in order to 553 * prevent this library from having a dependency on libisccfg). 554 * 555 * 'instp' will be left pointing to the instance of the plugin 556 * created by the module's plugin_register function. 557 */ 558 559 isc_result_t 560 ns_plugin_check(const char *modpath, const char *parameters, const void *cfg, 561 const char *cfg_file, unsigned long cfg_line, isc_mem_t *mctx, 562 isc_log_t *lctx, void *actx); 563 /*%< 564 * Open the plugin module at 'modpath' and check the validity of 565 * 'parameters', logging any errors or warnings found, then 566 * close it without configuring it. 567 */ 568 569 void 570 ns_plugins_create(isc_mem_t *mctx, ns_plugins_t **listp); 571 /*%< 572 * Create and initialize a plugin list. 573 */ 574 575 void 576 ns_plugins_free(isc_mem_t *mctx, void **listp); 577 /*%< 578 * Close each plugin module in a plugin list, then free the list object. 579 */ 580 581 void 582 ns_hooktable_free(isc_mem_t *mctx, void **tablep); 583 /*%< 584 * Free a hook table. 585 */ 586 587 void 588 ns_hook_add(ns_hooktable_t *hooktable, isc_mem_t *mctx, 589 ns_hookpoint_t hookpoint, const ns_hook_t *hook); 590 /*%< 591 * Allocate (using memory context 'mctx') a copy of the 'hook' structure 592 * describing a hook action and append it to the list of hooks at 'hookpoint' 593 * in 'hooktable'. 594 * 595 * Requires: 596 *\li 'hooktable' is not NULL 597 * 598 *\li 'mctx' is not NULL 599 * 600 *\li 'hookpoint' is less than NS_QUERY_HOOKS_COUNT 601 * 602 *\li 'hook' is not NULL 603 */ 604 605 void 606 ns_hooktable_init(ns_hooktable_t *hooktable); 607 /*%< 608 * Initialize a hook table. 609 */ 610 611 isc_result_t 612 ns_hooktable_create(isc_mem_t *mctx, ns_hooktable_t **tablep); 613 /*%< 614 * Allocate and initialize a hook table. 615 */ 616