#include <dns/rdataset.h>
#include <dns/result.h>
#include <dns/types.h>
+#include <dns/view.h>
#define CHECK(op) \
do { \
uint32_t flags;
} filter_data_t;
-/*
- * Memory pool for use with persistent data.
- */
-static isc_mempool_t *datapool = NULL;
+typedef struct filter_instance {
+ ns_module_t *module;
+ isc_mem_t *mctx;
-/*
- * Hash table associating a client object with its persistent data.
- */
-static isc_ht_t *client_ht = NULL;
+ /*
+ * Memory pool for use with persistent data.
+ */
+ isc_mempool_t *datapool;
+
+ /*
+ * Hash table associating a client object with its persistent data.
+ */
+ isc_ht_t *ht;
+
+ /*
+ * Values configured when the module is loaded.
+ */
+ filter_aaaa_t v4_aaaa;
+ filter_aaaa_t v6_aaaa;
+ dns_acl_t *aaaa_acl;
+} filter_instance_t;
/*
* Per-client flags set by this module
NS_QUERYATTR_RECURSIONOK) != 0)
/*
- * Hook registration structures: pointers to these structures will
- * be added to a hook table when this module is registered.
+ * Forward declarations of functions referenced in install_hooks().
*/
static bool
filter_qctx_initialize(void *arg, void *cbdata, isc_result_t *resp);
-static const ns_hook_t filter_init = {
- .action = filter_qctx_initialize,
- .action_data = &client_ht,
-};
-
static bool
filter_respond_begin(void *arg, void *cbdata, isc_result_t *resp);
-static const ns_hook_t filter_respbegin = {
- .action = filter_respond_begin,
- .action_data = &client_ht,
-};
-
static bool
filter_respond_any_found(void *arg, void *cbdata, isc_result_t *resp);
-static const ns_hook_t filter_respanyfound = {
- .action = filter_respond_any_found,
- .action_data = &client_ht,
-};
-
static bool
filter_prep_response_begin(void *arg, void *cbdata, isc_result_t *resp);
-static const ns_hook_t filter_prepresp = {
- .action = filter_prep_response_begin,
- .action_data = &client_ht,
-};
-
static bool
filter_query_done_send(void *arg, void *cbdata, isc_result_t *resp);
-static const ns_hook_t filter_donesend = {
- .action = filter_query_done_send,
- .action_data = &client_ht,
-};
-
static bool
filter_qctx_destroy(void *arg, void *cbdata, isc_result_t *resp);
-static const ns_hook_t filter_destroy = {
- .action = filter_qctx_destroy,
- .action_data = &client_ht,
-};
+
+/*%
+ * Register the functions to be called at each hook point in 'hooktable', using
+ * memory context 'mctx' for allocating copies of stack-allocated structures
+ * passed to ns_hook_add(). Make sure 'inst' will be passed as the 'cbdata'
+ * argument to every callback.
+ */
+static void
+install_hooks(ns_hooktable_t *hooktable, isc_mem_t *mctx,
+ filter_instance_t *inst)
+{
+ const ns_hook_t filter_init = {
+ .action = filter_qctx_initialize,
+ .action_data = inst,
+ };
+
+ const ns_hook_t filter_respbegin = {
+ .action = filter_respond_begin,
+ .action_data = inst,
+ };
+
+ const ns_hook_t filter_respanyfound = {
+ .action = filter_respond_any_found,
+ .action_data = inst,
+ };
+
+ const ns_hook_t filter_prepresp = {
+ .action = filter_prep_response_begin,
+ .action_data = inst,
+ };
+
+ const ns_hook_t filter_donesend = {
+ .action = filter_query_done_send,
+ .action_data = inst,
+ };
+
+ const ns_hook_t filter_destroy = {
+ .action = filter_qctx_destroy,
+ .action_data = inst,
+ };
+
+ ns_hook_add(hooktable, mctx, -
+ NS_QUERY_QCTX_INITIALIZED, &filter_init);
+ ns_hook_add(hooktable, mctx,
+ NS_QUERY_RESPOND_BEGIN, &filter_respbegin);
+ ns_hook_add(hooktable, mctx,
+ NS_QUERY_RESPOND_ANY_FOUND, &filter_respanyfound);
+ ns_hook_add(hooktable, mctx,
+ NS_QUERY_PREP_RESPONSE_BEGIN, &filter_prepresp);
+ ns_hook_add(hooktable, mctx,
+ NS_QUERY_DONE_SEND, &filter_donesend);
+ ns_hook_add(hooktable, mctx,
+ NS_QUERY_QCTX_DESTROYED, &filter_destroy);
+}
/**
** Support for parsing of parameters and configuration of the module.
**/
-/*
- * Values configured when the module is loaded.
- */
-static filter_aaaa_t v4_aaaa = NONE;
-static filter_aaaa_t v6_aaaa = NONE;
-static dns_acl_t *aaaa_acl = NULL;
-
/*
* Support for parsing of parameters.
*/
}
static isc_result_t
-parse_parameters(const char *parameters, const void *cfg,
- void *actx, ns_hookctx_t *hctx)
+parse_parameters(filter_instance_t *inst, const char *parameters,
+ const void *cfg, void *actx, ns_hookctx_t *hctx)
{
isc_result_t result = ISC_R_SUCCESS;
cfg_parser_t *parser = NULL;
CHECK(cfg_parse_buffer(parser, &b, &cfg_type_parameters,
¶m_obj));
- CHECK(parse_filter_aaaa_on(param_obj, "filter-aaaa-on-v4", &v4_aaaa));
- CHECK(parse_filter_aaaa_on(param_obj, "filter-aaaa-on-v6", &v6_aaaa));
+ CHECK(parse_filter_aaaa_on(param_obj, "filter-aaaa-on-v4",
+ &inst->v4_aaaa));
+ CHECK(parse_filter_aaaa_on(param_obj, "filter-aaaa-on-v6",
+ &inst->v6_aaaa));
- obj = NULL;
result = cfg_map_get(param_obj, "filter-aaaa", &obj);
if (result == ISC_R_SUCCESS) {
CHECK(cfg_acl_fromconfig(obj, (const cfg_obj_t *) cfg,
- hctx->lctx,
- (cfg_aclconfctx_t *) actx,
- hctx->mctx, 0, &aaaa_acl));
+ hctx->lctx, (cfg_aclconfctx_t *) actx,
+ hctx->mctx, 0, &inst->aaaa_acl));
} else {
- CHECK(dns_acl_any(hctx->mctx, &aaaa_acl));
+ CHECK(dns_acl_any(hctx->mctx, &inst->aaaa_acl));
}
cleanup:
**/
/*
- * Called by ns_hookmodule_load() to register hook functions into
+ * Called by ns_module_load() to register hook functions into
* a hook table.
*/
isc_result_t
hook_register(const char *parameters,
const char *cfg_file, unsigned long cfg_line,
- const void *cfg, void *actx,
- ns_hookctx_t *hctx, ns_hooktable_t *hooktable, void **instp)
+ const void *cfg, void *actx, ns_hookctx_t *hctx,
+ ns_hooktable_t *hooktable, void **instp)
{
+ filter_instance_t *inst = NULL;
isc_result_t result;
- UNUSED(instp);
isc_log_write(hctx->lctx, NS_LOGCATEGORY_GENERAL,
NS_LOGMODULE_HOOKS, ISC_LOG_INFO,
- "loading 'filter-aaaa' "
+ "registering 'filter-aaaa' "
"module from %s:%lu, %s parameters",
cfg_file, cfg_line, parameters != NULL ? "with" : "no");
+ inst = isc_mem_get(hctx->mctx, sizeof(*inst));
+ memset(inst, 0, sizeof(*inst));
+ isc_mem_attach(hctx->mctx, &inst->mctx);
+
if (parameters != NULL) {
- CHECK(parse_parameters(parameters, cfg, actx, hctx));
+ CHECK(parse_parameters(inst, parameters, cfg, actx, hctx));
}
- ns_hook_add(hooktable, hctx->mctx, NS_QUERY_QCTX_INITIALIZED,
- &filter_init);
- ns_hook_add(hooktable, hctx->mctx, NS_QUERY_RESPOND_BEGIN,
- &filter_respbegin);
- ns_hook_add(hooktable, hctx->mctx, NS_QUERY_RESPOND_ANY_FOUND,
- &filter_respanyfound);
- ns_hook_add(hooktable, hctx->mctx, NS_QUERY_PREP_RESPONSE_BEGIN,
- &filter_prepresp);
- ns_hook_add(hooktable, hctx->mctx, NS_QUERY_DONE_SEND,
- &filter_donesend);
- ns_hook_add(hooktable, hctx->mctx, NS_QUERY_QCTX_DESTROYED,
- &filter_destroy);
-
CHECK(isc_mempool_create(hctx->mctx, sizeof(filter_data_t),
- &datapool));
-
- CHECK(isc_ht_init(&client_ht, hctx->mctx, 16));
+ &inst->datapool));
+ CHECK(isc_ht_init(&inst->ht, hctx->mctx, 16));
/*
* Fill the mempool with 1K filter_aaaa state objects at
* so that they'll always be returned to the pool and not
* freed until the pool is destroyed on shutdown.
*/
- isc_mempool_setfillcount(datapool, 1024);
- isc_mempool_setfreemax(datapool, UINT_MAX);
+ isc_mempool_setfillcount(inst->datapool, 1024);
+ isc_mempool_setfreemax(inst->datapool, UINT_MAX);
+
+ /*
+ * Set hook points in the view's hooktable.
+ */
+ install_hooks(hooktable, hctx->mctx, inst);
+
+ *instp = inst;
cleanup:
- if (result != ISC_R_SUCCESS) {
- if (datapool != NULL) {
- isc_mempool_destroy(&datapool);
- }
+ if (result != ISC_R_SUCCESS && inst != NULL) {
+ hook_destroy((void **) &inst);
}
+
return (result);
}
/*
- * Called by ns_hookmodule_unload_all(); frees memory allocated by
+ * Called by ns_module_unload(); frees memory allocated by
* the module when it was registered.
*/
void
hook_destroy(void **instp) {
- UNUSED(instp);
+ filter_instance_t *inst = (filter_instance_t *) *instp;
- if (client_ht != NULL) {
- isc_ht_destroy(&client_ht);
+ if (inst->ht != NULL) {
+ isc_ht_destroy(&inst->ht);
}
- if (datapool != NULL) {
- isc_mempool_destroy(&datapool);
+ if (inst->datapool != NULL) {
+ isc_mempool_destroy(&inst->datapool);
}
- if (aaaa_acl != NULL) {
- dns_acl_detach(&aaaa_acl);
+ if (inst->aaaa_acl != NULL) {
+ dns_acl_detach(&inst->aaaa_acl);
}
+ isc_mem_putanddetach(&inst->mctx, inst, sizeof(*inst));
+ *instp = NULL;
+
return;
}
}
static filter_data_t *
-client_state_get(const query_ctx_t *qctx, isc_ht_t **htp) {
+client_state_get(const query_ctx_t *qctx, filter_instance_t *inst) {
filter_data_t *client_state = NULL;
isc_result_t result;
- result = isc_ht_find(*htp, (const unsigned char *)&qctx->client,
+ result = isc_ht_find(inst->ht, (const unsigned char *)&qctx->client,
sizeof(qctx->client), (void **)&client_state);
return (result == ISC_R_SUCCESS ? client_state : NULL);
}
static void
-client_state_create(const query_ctx_t *qctx, isc_ht_t **htp) {
+client_state_create(const query_ctx_t *qctx, filter_instance_t *inst) {
filter_data_t *client_state;
isc_result_t result;
- client_state = isc_mempool_get(datapool);
+ client_state = isc_mempool_get(inst->datapool);
if (client_state == NULL) {
return;
}
client_state->mode = NONE;
client_state->flags = 0;
- result = isc_ht_add(*htp, (const unsigned char *)&qctx->client,
+ result = isc_ht_add(inst->ht, (const unsigned char *)&qctx->client,
sizeof(qctx->client), client_state);
RUNTIME_CHECK(result == ISC_R_SUCCESS);
}
static void
-client_state_destroy(const query_ctx_t *qctx, isc_ht_t **htp) {
- filter_data_t *client_state = client_state_get(qctx, htp);
+client_state_destroy(const query_ctx_t *qctx, filter_instance_t *inst) {
+ filter_data_t *client_state = client_state_get(qctx, inst);
isc_result_t result;
if (client_state == NULL) {
return;
}
- result = isc_ht_delete(*htp, (const unsigned char *)&qctx->client,
+ result = isc_ht_delete(inst->ht, (const unsigned char *)&qctx->client,
sizeof(qctx->client));
RUNTIME_CHECK(result == ISC_R_SUCCESS);
- isc_mempool_put(datapool, client_state);
+ isc_mempool_put(inst->datapool, client_state);
}
/*%
static bool
filter_qctx_initialize(void *arg, void *cbdata, isc_result_t *resp) {
query_ctx_t *qctx = (query_ctx_t *) arg;
- isc_ht_t **htp = (isc_ht_t **) cbdata;
+ filter_instance_t *inst = (filter_instance_t *) cbdata;
filter_data_t *client_state;
*resp = ISC_R_UNSET;
- client_state = client_state_get(qctx, htp);
+ client_state = client_state_get(qctx, inst);
if (client_state == NULL) {
- client_state_create(qctx, htp);
+ client_state_create(qctx, inst);
}
return (false);
static bool
filter_prep_response_begin(void *arg, void *cbdata, isc_result_t *resp) {
query_ctx_t *qctx = (query_ctx_t *) arg;
- isc_ht_t **htp = (isc_ht_t **) cbdata;
- filter_data_t *client_state = client_state_get(qctx, htp);
+ filter_instance_t *inst = (filter_instance_t *) cbdata;
+ filter_data_t *client_state = client_state_get(qctx, inst);
isc_result_t result;
*resp = ISC_R_UNSET;
return (false);
}
- if (v4_aaaa != NONE || v6_aaaa != NONE) {
+ if (inst->v4_aaaa != NONE || inst->v6_aaaa != NONE) {
result = ns_client_checkaclsilent(qctx->client, NULL,
- aaaa_acl, true);
+ inst->aaaa_acl, true);
if (result == ISC_R_SUCCESS &&
- v4_aaaa != NONE &&
+ inst->v4_aaaa != NONE &&
is_v4_client(qctx->client))
{
- client_state->mode = v4_aaaa;
+ client_state->mode = inst->v4_aaaa;
} else if (result == ISC_R_SUCCESS &&
- v6_aaaa != NONE &&
+ inst->v6_aaaa != NONE &&
is_v6_client(qctx->client))
{
- client_state->mode = v6_aaaa;
+ client_state->mode = inst->v6_aaaa;
}
}
static bool
filter_respond_begin(void *arg, void *cbdata, isc_result_t *resp) {
query_ctx_t *qctx = (query_ctx_t *) arg;
- isc_ht_t **htp = (isc_ht_t **) cbdata;
- filter_data_t *client_state = client_state_get(qctx, htp);
+ filter_instance_t *inst = (filter_instance_t *) cbdata;
+ filter_data_t *client_state = client_state_get(qctx, inst);
isc_result_t result = ISC_R_UNSET;
*resp = ISC_R_UNSET;
static bool
filter_respond_any_found(void *arg, void *cbdata, isc_result_t *resp) {
query_ctx_t *qctx = (query_ctx_t *) arg;
- isc_ht_t **htp = (isc_ht_t **) cbdata;
- filter_data_t *client_state = client_state_get(qctx, htp);
+ filter_instance_t *inst = (filter_instance_t *) cbdata;
+ filter_data_t *client_state = client_state_get(qctx, inst);
*resp = ISC_R_UNSET;
static bool
filter_query_done_send(void *arg, void *cbdata, isc_result_t *resp) {
query_ctx_t *qctx = (query_ctx_t *) arg;
- isc_ht_t **htp = (isc_ht_t **) cbdata;
- filter_data_t *client_state = client_state_get(qctx, htp);
+ filter_instance_t *inst = (filter_instance_t *) cbdata;
+ filter_data_t *client_state = client_state_get(qctx, inst);
*resp = ISC_R_UNSET;
static bool
filter_qctx_destroy(void *arg, void *cbdata, isc_result_t *resp) {
query_ctx_t *qctx = (query_ctx_t *) arg;
- isc_ht_t **htp = (isc_ht_t **) cbdata;
+ filter_instance_t *inst = (filter_instance_t *) cbdata;
*resp = ISC_R_UNSET;
return (false);
}
- client_state_destroy(qctx, htp);
+ client_state_destroy(qctx, inst);
return (false);
}
} \
} while (0)
-typedef struct ns_hook_module ns_hook_module_t;
-struct ns_hook_module {
- isc_mem_t *mctx;
- void *handle;
- char *modpath;
- ns_hook_register_t *register_func;
- ns_hook_destroy_t *destroy_func;
- void *inst;
- LINK(ns_hook_module_t) link;
+struct ns_module {
+ isc_mem_t *mctx;
+ void *handle;
+ void *inst;
+ char *modpath;
+ ns_hook_register_t *register_func;
+ ns_hook_destroy_t *destroy_func;
+ LINK(ns_module_t) link;
};
static ns_hooklist_t default_hooktable[NS_HOOKPOINTS_COUNT];
LIBNS_EXTERNAL_DATA ns_hooktable_t *ns__hook_table = &default_hooktable;
-/*
- * List of hook modules.
- *
- * These are stored here so they can be cleaned up on shutdown.
- * (The order in which they are stored is not important.)
- */
-static ISC_LIST(ns_hook_module_t) hook_modules;
-static bool hook_modules_initialized = false;
-
#if HAVE_DLFCN_H && HAVE_DLOPEN
static isc_result_t
load_symbol(void *handle, const char *modpath,
}
static isc_result_t
-load_library(isc_mem_t *mctx, const char *modpath, ns_hook_module_t **hmodp) {
+load_library(isc_mem_t *mctx, const char *modpath, ns_module_t **hmodp) {
isc_result_t result;
void *handle = NULL;
- ns_hook_module_t *hmod = NULL;
+ ns_module_t *hmod = NULL;
ns_hook_register_t *register_func = NULL;
ns_hook_destroy_t *destroy_func = NULL;
ns_hook_version_t *version_func = NULL;
hmod->modpath = isc_mem_strdup(hmod->mctx, modpath);
hmod->register_func = register_func;
hmod->destroy_func = destroy_func;
- hmod->inst = NULL;
ISC_LINK_INIT(hmod, link);
}
static void
-unload_library(ns_hook_module_t **hmodp) {
- ns_hook_module_t *hmod = NULL;
+unload_library(ns_module_t **hmodp) {
+ ns_module_t *hmod = NULL;
REQUIRE(hmodp != NULL && *hmodp != NULL);
hmod = *hmodp;
*hmodp = NULL;
+ isc_log_write(ns_lctx, NS_LOGCATEGORY_GENERAL,
+ NS_LOGMODULE_HOOKS, ISC_LOG_INFO,
+ "unloading module '%s'", hmod->modpath);
+
+ if (hmod->inst != NULL) {
+ hmod->destroy_func(&hmod->inst);
+ }
if (hmod->handle != NULL) {
(void) dlclose(hmod->handle);
}
}
static isc_result_t
-load_library(isc_mem_t *mctx, const char *modpath, ns_hook_module_t **hmodp) {
+load_library(isc_mem_t *mctx, const char *modpath, ns_module_t **hmodp) {
isc_result_t result;
HMODULE handle;
- ns_hook_module_t *hmod = NULL;
+ ns_module_t *hmod = NULL;
ns_hook_register_t *register_func = NULL;
ns_hook_destroy_t *destroy_func = NULL;
ns_hook_version_t *version_func = NULL;
hmod->modpath = isc_mem_strdup(hmod->mctx, modpath);
hmod->register_func = register_func;
hmod->destroy_func = destroy_func;
- hmod->inst = NULL;
ISC_LINK_INIT(hmod, link);
}
static void
-unload_library(ns_hook_module_t **hmodp) {
- ns_hook_module_t *hmod = NULL;
+unload_library(ns_module_t **hmodp) {
+ ns_module_t *hmod = NULL;
REQUIRE(hmodp != NULL && *hmodp != NULL);
hmod = *hmodp;
*hmodp = NULL;
+ isc_log_write(ns_lctx, NS_LOGCATEGORY_GENERAL,
+ NS_LOGMODULE_HOOKS, ISC_LOG_INFO,
+ "unloading module '%s'", hmod->modpath);
+
+ if (hmod->inst != NULL) {
+ hmod->destroy_func(&hmod->inst);
+ }
if (hmod->handle != NULL) {
FreeLibrary(hmod->handle);
}
}
#else /* HAVE_DLFCN_H || _WIN32 */
static isc_result_t
-load_library(isc_mem_t *mctx, const char *modpath, ns_hook_module_t **hmodp) {
+load_library(isc_mem_t *mctx, const char *modpath, ns_module_t **hmodp) {
UNUSED(mctx);
UNUSED(modpath);
UNUSED(hmodp);
}
static void
-unload_library(ns_hook_module_t **hmodp) {
+unload_library(ns_module_t **hmodp) {
UNUSED(hmodp);
}
#endif /* HAVE_DLFCN_H */
isc_result_t
-ns_hookmodule_load(const char *modpath, const char *parameters,
- const char *cfg_file, unsigned long cfg_line,
- const void *cfg, void *actx,
- ns_hookctx_t *hctx, ns_hooktable_t *hooktable)
+ns_module_load(const char *modpath, const char *parameters,
+ const char *cfg_file, unsigned long cfg_line,
+ const void *cfg, void *actx, ns_hookctx_t *hctx,
+ ns_modlist_t *modlist, ns_hooktable_t *hooktable)
{
isc_result_t result;
- ns_hook_module_t *hmod = NULL;
+ ns_module_t *hmod = NULL;
- REQUIRE(hook_modules_initialized);
REQUIRE(NS_HOOKCTX_VALID(hctx));
+ REQUIRE(modlist != NULL);
REQUIRE(hooktable != NULL);
isc_log_write(ns_lctx, NS_LOGCATEGORY_GENERAL,
"loading module '%s'", modpath);
CHECK(load_library(hctx->mctx, modpath, &hmod));
+
+ isc_log_write(ns_lctx, NS_LOGCATEGORY_GENERAL,
+ NS_LOGMODULE_HOOKS, ISC_LOG_INFO,
+ "registering module '%s'", modpath);
+
CHECK(hmod->register_func(parameters, cfg_file, cfg_line,
cfg, actx, hctx, hooktable, &hmod->inst));
- ISC_LIST_APPEND(hook_modules, hmod, link);
+ ISC_LIST_APPEND(*modlist, hmod, link);
cleanup:
if (result != ISC_R_SUCCESS && hmod != NULL) {
return (result);
}
-void
-ns_hookmodule_unload_all(void) {
- ns_hook_module_t *hmod = NULL, *prev = NULL;
-
- if (!hook_modules_initialized) {
- return;
- }
-
- hmod = ISC_LIST_TAIL(hook_modules);
- while (hmod != NULL) {
- prev = ISC_LIST_PREV(hmod, link);
- ISC_LIST_UNLINK(hook_modules, hmod, link);
- isc_log_write(ns_lctx, NS_LOGCATEGORY_GENERAL,
- NS_LOGMODULE_HOOKS, ISC_LOG_INFO,
- "unloading module '%s'", hmod->modpath);
- hmod->destroy_func(&hmod->inst);
- ENSURE(hmod->inst == NULL);
- unload_library(&hmod);
- hmod = prev;
- }
-}
-
isc_result_t
ns_hook_createctx(isc_mem_t *mctx, ns_hookctx_t **hctxp) {
ns_hookctx_t *hctx = NULL;
ns_hooktable_init(ns_hooktable_t *hooktable) {
int i;
- if (!hook_modules_initialized) {
- ISC_LIST_INIT(hook_modules);
- hook_modules_initialized = true;
- }
-
for (i = 0; i < NS_HOOKPOINTS_COUNT; i++) {
ISC_LIST_INIT((*hooktable)[i]);
}
ISC_LINK_INIT(copy, link);
ISC_LIST_APPEND((*hooktable)[hookpoint], copy, link);
}
+
+void
+ns_modlist_create(isc_mem_t *mctx, ns_modlist_t **listp) {
+ ns_modlist_t *modlist = NULL;
+
+ REQUIRE(listp != NULL && *listp == NULL);
+
+ modlist = isc_mem_get(mctx, sizeof(*modlist));
+ memset(modlist, 0, sizeof(*modlist));
+ ISC_LIST_INIT(*modlist);
+
+ *listp = modlist;
+}
+
+void
+ns_modlist_free(isc_mem_t *mctx, void **listp) {
+ ns_modlist_t *list = NULL;
+ ns_module_t *hmod = NULL, *next = NULL;
+
+ REQUIRE(listp != NULL && *listp != NULL);
+
+ list = *listp;
+ *listp = NULL;
+
+ for (hmod = ISC_LIST_HEAD(*list);
+ hmod != NULL;
+ hmod = next)
+ {
+ next = ISC_LIST_NEXT(hmod, link);
+ ISC_LIST_UNLINK(*list, hmod, link);
+ unload_library(&hmod);
+ }
+
+ isc_mem_put(mctx, list, sizeof(*list));
+}