From f449f278dd3c70e479a035f50a9bb817a9b433ba Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 17:24:08 +0200 Subject: Adding upstream version 3.2.6. Signed-off-by: Daniel Baumann --- src/knot/conf/module.c | 509 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 509 insertions(+) create mode 100644 src/knot/conf/module.c (limited to 'src/knot/conf/module.c') diff --git a/src/knot/conf/module.c b/src/knot/conf/module.c new file mode 100644 index 0000000..d5d9642 --- /dev/null +++ b/src/knot/conf/module.c @@ -0,0 +1,509 @@ +/* Copyright (C) 2023 CZ.NIC, z.s.p.o. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ + +#include +#include +#include +#include +#include +#include + +#include "knot/conf/conf.h" +#include "knot/conf/confio.h" +#include "knot/conf/module.h" +#include "knot/common/log.h" +#include "knot/modules/static_modules.h" +#include "knot/nameserver/query_module.h" +#include "contrib/openbsd/strlcat.h" +#include "contrib/string.h" + +#define LIB_EXTENSION ".so" + +knot_dynarray_define(mod, module_t *, DYNARRAY_VISIBILITY_NORMAL) +knot_dynarray_define(old_schema, yp_item_t *, DYNARRAY_VISIBILITY_NORMAL) + +static module_t STATIC_MODULES[] = { + STATIC_MODULES_INIT + { NULL } +}; + +module_t *conf_mod_find( + conf_t *conf, + const char *name, + size_t len, + bool temporary) +{ + if (conf == NULL || name == NULL) { + return NULL; + } + + // First, search in static modules. + for (module_t *mod = STATIC_MODULES; mod->api != NULL; mod++) { + if (strncmp(name, mod->api->name, len) == 0) { + return mod; + } + } + + module_type_t excluded_type = temporary ? MOD_EXPLICIT : MOD_TEMPORARY; + + // Second, search in dynamic modules. + knot_dynarray_foreach(mod, module_t *, module, conf->modules) { + if ((*module) != NULL && (*module)->type != excluded_type && + strncmp(name, (*module)->api->name, len) == 0) { + return (*module); + } + } + + return NULL; +} + +static int mod_load( + conf_t *conf, + module_t *mod) +{ + static const yp_item_t module_common[] = { + { C_ID, YP_TSTR, YP_VNONE, CONF_IO_FREF }, + { C_COMMENT, YP_TSTR, YP_VNONE }, + { NULL } + }; + + yp_item_t *sub_items = NULL; + + int ret; + if (mod->api->config != NULL) { + ret = yp_schema_merge(&sub_items, module_common, mod->api->config); + } else { + ret = yp_schema_copy(&sub_items, module_common); + } + if (ret != KNOT_EOK) { + return ret; + } + + /* Synthesise module config section name. */ + const size_t name_len = strlen(mod->api->name); + if (name_len > YP_MAX_ITEM_NAME_LEN) { + return KNOT_YP_EINVAL_ITEM; + } + char name[1 + YP_MAX_ITEM_NAME_LEN + 1]; + name[0] = name_len; + memcpy(name + 1, mod->api->name, name_len + 1); + + const yp_item_t schema[] = { + { name, YP_TGRP, YP_VGRP = { sub_items }, + YP_FALLOC | YP_FMULTI | CONF_IO_FRLD_MOD | CONF_IO_FRLD_ZONES, + { mod->api->config_check } }, + { NULL } + }; + + yp_item_t *merged = NULL; + ret = yp_schema_merge(&merged, conf->schema, schema); + yp_schema_free(sub_items); + if (ret != KNOT_EOK) { + return ret; + } + + // Update configuration schema (with lazy free). + yp_item_t **current_schema = &conf->schema; + yp_item_t *old_schema = rcu_xchg_pointer(current_schema, merged); + synchronize_rcu(); + old_schema_dynarray_add(&conf->old_schemas, &old_schema); + + return KNOT_EOK; +} + +int conf_mod_load_common( + conf_t *conf) +{ + if (conf == NULL) { + return KNOT_EINVAL; + } + + int ret = KNOT_EOK; + + // First, load static modules. + for (module_t *mod = STATIC_MODULES; mod->api != NULL; mod++) { + ret = mod_load(conf, mod); + if (ret != KNOT_EOK) { + log_error("module '%s', failed to load (%s)", + mod->api->name, knot_strerror(ret)); + break; + } + + log_debug("module '%s', loaded static", mod->api->name); + } + + // Second, try to load implicit shared modules if configured. + if (strlen(MODULE_DIR) > 0) { + struct stat path_stat; + glob_t glob_buf = { 0 }; + + char *path = sprintf_alloc("%s/*%s", MODULE_DIR, LIB_EXTENSION); + if (path == NULL) { + ret = KNOT_ENOMEM; + } else if (stat(MODULE_DIR, &path_stat) != 0 || + !S_ISDIR(path_stat.st_mode)) { + if (errno == ENOENT) { + // Module directory doesn't exist. + ret = KNOT_EOK; + } else { + log_error("module, invalid directory '%s'", + MODULE_DIR); + ret = KNOT_EINVAL; + } + } else if (access(MODULE_DIR, F_OK | R_OK) != 0) { + log_error("module, failed to access directory '%s'", + MODULE_DIR); + ret = KNOT_EACCES; + } else { + ret = glob(path, 0, NULL, &glob_buf); + if (ret != 0 && ret != GLOB_NOMATCH) { + log_error("module, failed to read directory '%s'", + MODULE_DIR); + ret = KNOT_EACCES; + } else { + ret = KNOT_EOK; + } + } + + // Process each module in the directory. + for (size_t i = 0; i < glob_buf.gl_pathc; i++) { + (void)conf_mod_load_extra(conf, NULL, glob_buf.gl_pathv[i], + MOD_IMPLICIT); + } + + globfree(&glob_buf); + free(path); + } + + conf_mod_load_purge(conf, false); + + return ret; +} + +int conf_mod_load_extra( + conf_t *conf, + const char *mod_name, + const char *file_name, + module_type_t type) +{ + if (conf == NULL || (mod_name == NULL && file_name == NULL)) { + return KNOT_EINVAL; + } + + // Synthesize module file name if not specified. + char *tmp_name = NULL; + if (file_name == NULL) { + tmp_name = sprintf_alloc("%s/%s%s", MODULE_INSTDIR, + mod_name + strlen(KNOTD_MOD_NAME_PREFIX), + LIB_EXTENSION); + if (tmp_name == NULL) { + return KNOT_ENOMEM; + } + file_name = tmp_name; + } + + void *handle = dlopen(file_name, RTLD_NOW | RTLD_LOCAL); + if (handle == NULL) { + log_error("module, failed to open '%s' (%s)", file_name, dlerror()); + free(tmp_name); + return KNOT_ENOENT; + } + (void)dlerror(); + + knotd_mod_api_t *api = dlsym(handle, "knotd_mod_api"); + if (api == NULL) { + char *err = dlerror(); + if (err == NULL) { + err = "empty symbol"; + } + log_error("module, invalid library '%s' (%s)", file_name, err); + dlclose(handle); + free(tmp_name); + return KNOT_ENOENT; + } + free(tmp_name); + + if (api->version != KNOTD_MOD_ABI_VERSION) { + log_error("module '%s', incompatible version", api->name); + dlclose(handle); + return KNOT_ENOTSUP; + } + + if (api->name == NULL || (mod_name != NULL && strcmp(api->name, mod_name) != 0)) { + log_error("module '%s', module name mismatch", api->name); + dlclose(handle); + return KNOT_ENOTSUP; + } + + // Check if the module is already loaded. + module_t *found = conf_mod_find(conf, api->name, strlen(api->name), + type == MOD_TEMPORARY); + if (found != NULL) { + log_error("module '%s', duplicate module", api->name); + dlclose(handle); + return KNOT_EEXIST; + } + + module_t *mod = calloc(1, sizeof(*mod)); + if (mod == NULL) { + dlclose(handle); + return KNOT_ENOMEM; + } + mod->api = api; + mod->lib_handle = handle; + mod->type = type; + + int ret = mod_load(conf, mod); + if (ret != KNOT_EOK) { + log_error("module '%s', failed to load (%s)", api->name, + knot_strerror(ret)); + dlclose(handle); + free(mod); + return ret; + } + + mod_dynarray_add(&conf->modules, &mod); + + log_debug("module '%s', loaded shared", api->name); + + return KNOT_EOK; +} + +static void unload_shared( + module_t *mod) +{ + if (mod != NULL) { + assert(mod->lib_handle); + (void)dlclose(mod->lib_handle); + free(mod); + } +} + +void conf_mod_load_purge( + conf_t *conf, + bool temporary) +{ + if (conf == NULL) { + return; + } + + // Switch the current temporary schema with the initial one. + if (temporary && conf->old_schemas.size > 0) { + yp_item_t **current_schema = &conf->schema; + yp_item_t **initial = &(conf->old_schemas.arr(&conf->old_schemas))[0]; + + yp_item_t *old_schema = rcu_xchg_pointer(current_schema, *initial); + synchronize_rcu(); + *initial = old_schema; + } + + knot_dynarray_foreach(old_schema, yp_item_t *, schema, conf->old_schemas) { + yp_schema_free(*schema); + } + old_schema_dynarray_free(&conf->old_schemas); + + knot_dynarray_foreach(mod, module_t *, module, conf->modules) { + if ((*module) != NULL && (*module)->type == MOD_TEMPORARY) { + unload_shared((*module)); + *module = NULL; // Cannot remove from dynarray. + } + } +} + +void conf_mod_unload_shared( + conf_t *conf) +{ + if (conf == NULL) { + return; + } + + knot_dynarray_foreach(mod, module_t *, module, conf->modules) { + unload_shared((*module)); + } + mod_dynarray_free(&conf->modules); +} + +#define LOG_ARGS(mod_id, msg) "module '%s%s%.*s', " msg, \ + mod_id->name + 1, (mod_id->len > 0) ? "/" : "", (int)mod_id->len, \ + mod_id->data + +#define MOD_ID_LOG(zone, level, mod_id, msg, ...) \ + if (zone != NULL) \ + log_zone_##level(zone, LOG_ARGS(mod_id, msg), ##__VA_ARGS__); \ + else \ + log_##level(LOG_ARGS(mod_id, msg), ##__VA_ARGS__); + +void conf_activate_modules( + conf_t *conf, + struct server *server, + const knot_dname_t *zone_name, + list_t *query_modules, + struct query_plan **query_plan) +{ + int ret = KNOT_EOK; + + if (conf == NULL || query_modules == NULL || query_plan == NULL) { + ret = KNOT_EINVAL; + goto activate_error; + } + + conf_val_t val; + + // Get list of associated modules. + if (zone_name != NULL) { + val = conf_zone_get(conf, C_MODULE, zone_name); + } else { + val = conf_default_get(conf, C_GLOBAL_MODULE); + } + + switch (val.code) { + case KNOT_EOK: + break; + case KNOT_ENOENT: // Check if a module is configured at all. + case KNOT_YP_EINVAL_ID: + return; + default: + ret = val.code; + goto activate_error; + } + + // Create query plan. + *query_plan = query_plan_create(); + if (*query_plan == NULL) { + ret = KNOT_ENOMEM; + goto activate_error; + } + + // Initialize query modules list. + init_list(query_modules); + + // Open the modules. + while (val.code == KNOT_EOK) { + conf_mod_id_t *mod_id = conf_mod_id(&val); + if (mod_id == NULL) { + ret = KNOT_ENOMEM; + goto activate_error; + } + + // Open the module. + knotd_mod_t *mod = query_module_open(conf, server, mod_id, *query_plan, + zone_name); + if (mod == NULL) { + MOD_ID_LOG(zone_name, error, mod_id, "failed to open"); + conf_free_mod_id(mod_id); + goto skip_module; + } + + // Check the module scope. + if ((zone_name == NULL && !(mod->api->flags & KNOTD_MOD_FLAG_SCOPE_GLOBAL)) || + (zone_name != NULL && !(mod->api->flags & KNOTD_MOD_FLAG_SCOPE_ZONE))) { + MOD_ID_LOG(zone_name, error, mod_id, "out of scope"); + query_module_close(mod); + goto skip_module; + } + + // Check if the module is loadable. + if (mod->api->load == NULL) { + MOD_ID_LOG(zone_name, debug, mod_id, "empty module, not loaded"); + query_module_close(mod); + goto skip_module; + } + + // Load the module. + ret = mod->api->load(mod); + if (ret != KNOT_EOK) { + MOD_ID_LOG(zone_name, error, mod_id, "failed to load (%s)", + knot_strerror(ret)); + query_module_close(mod); + goto skip_module; + } + mod->config = NULL; // Invalidate the current config. + + add_tail(query_modules, &mod->node); +skip_module: + conf_val_next(&val); + } + + return; +activate_error: + CONF_LOG(LOG_ERR, "failed to activate modules (%s)", knot_strerror(ret)); +} + +void conf_deactivate_modules( + list_t *query_modules, + struct query_plan **query_plan) +{ + if (query_modules == NULL || query_plan == NULL) { + return; + } + + // Free query plan. + query_plan_free(*query_plan); + *query_plan = NULL; + + // Free query modules list. + knotd_mod_t *mod, *next; + WALK_LIST_DELSAFE(mod, next, *query_modules) { + if (mod->api->unload != NULL) { + mod->api->unload(mod); + } + query_module_close(mod); + } + init_list(query_modules); +} + +void conf_reset_modules( + conf_t *conf, + list_t *query_modules, + struct query_plan **query_plan) +{ + if (query_modules == NULL || query_plan == NULL) { + return; + } + + struct query_plan *new_plan = query_plan_create(); + if (new_plan == NULL) { + CONF_LOG(LOG_ERR, "failed to activate modules (%s)", knot_strerror(KNOT_ENOMEM)); + return; + } + + struct query_plan *old_plan = rcu_xchg_pointer(query_plan, NULL); + synchronize_rcu(); + query_plan_free(old_plan); + + knotd_mod_t *mod; + WALK_LIST(mod, *query_modules) { + if (mod->api->unload != NULL) { + mod->api->unload(mod); + } + query_module_reset(conf, mod, new_plan); + } + + knotd_mod_t *next; + WALK_LIST_DELSAFE(mod, next, *query_modules) { + int ret = mod->api->load(mod); + if (ret != KNOT_EOK) { + MOD_ID_LOG(mod->zone, error, mod->id, "failed to load (%s)", + knot_strerror(ret)); + rem_node(&mod->node); + query_module_close(mod); + continue; + } + mod->config = NULL; // Invalidate the current config. + } + + (void)rcu_xchg_pointer(query_plan, new_plan); +} -- cgit v1.2.3