diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-06 00:53:35 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-06 00:53:35 +0000 |
commit | 69c6a41ffb878ef98c9378ed4b1634a404cfaa7f (patch) | |
tree | b2a4f704565d62fbb129ab9dc3b35977c50e6e7f /src/utils/knotc | |
parent | Initial commit. (diff) | |
download | knot-upstream.tar.xz knot-upstream.zip |
Adding upstream version 2.7.6.upstream/2.7.6upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/utils/knotc')
-rw-r--r-- | src/utils/knotc/commands.c | 1168 | ||||
-rw-r--r-- | src/utils/knotc/commands.h | 68 | ||||
-rw-r--r-- | src/utils/knotc/estimator.c | 147 | ||||
-rw-r--r-- | src/utils/knotc/estimator.h | 64 | ||||
-rw-r--r-- | src/utils/knotc/interactive.c | 433 | ||||
-rw-r--r-- | src/utils/knotc/interactive.h | 26 | ||||
-rw-r--r-- | src/utils/knotc/main.c | 145 | ||||
-rw-r--r-- | src/utils/knotc/process.c | 238 | ||||
-rw-r--r-- | src/utils/knotc/process.h | 69 |
9 files changed, 2358 insertions, 0 deletions
diff --git a/src/utils/knotc/commands.c b/src/utils/knotc/commands.c new file mode 100644 index 0000000..ea73012 --- /dev/null +++ b/src/utils/knotc/commands.c @@ -0,0 +1,1168 @@ +/* Copyright (C) 2018 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> + + 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 <http://www.gnu.org/licenses/>. +*/ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "libknot/libknot.h" +#include "knot/common/log.h" +#include "knot/ctl/commands.h" +#include "knot/conf/conf.h" +#include "knot/conf/confdb.h" +#include "knot/zone/zonefile.h" +#include "knot/zone/zone-load.h" +#include "contrib/macros.h" +#include "contrib/string.h" +#include "contrib/openbsd/strlcat.h" +#include "utils/knotc/commands.h" +#include "utils/knotc/estimator.h" + +#define CMD_EXIT "exit" + +#define CMD_STATUS "status" +#define CMD_STOP "stop" +#define CMD_RELOAD "reload" +#define CMD_STATS "stats" + +#define CMD_ZONE_CHECK "zone-check" +#define CMD_ZONE_MEMSTATS "zone-memstats" +#define CMD_ZONE_STATUS "zone-status" +#define CMD_ZONE_RELOAD "zone-reload" +#define CMD_ZONE_REFRESH "zone-refresh" +#define CMD_ZONE_RETRANSFER "zone-retransfer" +#define CMD_ZONE_NOTIFY "zone-notify" +#define CMD_ZONE_FLUSH "zone-flush" +#define CMD_ZONE_SIGN "zone-sign" +#define CMD_ZONE_KSK_SBM "zone-ksk-submitted" +#define CMD_ZONE_FREEZE "zone-freeze" +#define CMD_ZONE_THAW "zone-thaw" + +#define CMD_ZONE_READ "zone-read" +#define CMD_ZONE_BEGIN "zone-begin" +#define CMD_ZONE_COMMIT "zone-commit" +#define CMD_ZONE_ABORT "zone-abort" +#define CMD_ZONE_DIFF "zone-diff" +#define CMD_ZONE_GET "zone-get" +#define CMD_ZONE_SET "zone-set" +#define CMD_ZONE_UNSET "zone-unset" +#define CMD_ZONE_PURGE "zone-purge" +#define CMD_ZONE_STATS "zone-stats" + +#define CMD_CONF_INIT "conf-init" +#define CMD_CONF_CHECK "conf-check" +#define CMD_CONF_IMPORT "conf-import" +#define CMD_CONF_EXPORT "conf-export" +#define CMD_CONF_LIST "conf-list" +#define CMD_CONF_READ "conf-read" +#define CMD_CONF_BEGIN "conf-begin" +#define CMD_CONF_COMMIT "conf-commit" +#define CMD_CONF_ABORT "conf-abort" +#define CMD_CONF_DIFF "conf-diff" +#define CMD_CONF_GET "conf-get" +#define CMD_CONF_SET "conf-set" +#define CMD_CONF_UNSET "conf-unset" + +#define CTL_LOG_STR "failed to control" + +#define CTL_SEND(type, data) \ + ret = knot_ctl_send(args->ctl, (type), (data)); \ + if (ret != KNOT_EOK) { \ + log_error(CTL_LOG_STR" (%s)", knot_strerror(ret)); \ + return ret; \ + } + +#define CTL_SEND_DATA CTL_SEND(KNOT_CTL_TYPE_DATA, &data) +#define CTL_SEND_BLOCK CTL_SEND(KNOT_CTL_TYPE_BLOCK, NULL) + +static int check_args(cmd_args_t *args, int min, int max) +{ + if (max == 0 && args->argc > 0) { + log_error("command doesn't take arguments"); + return KNOT_EINVAL; + } else if (min == max && args->argc != min) { + log_error("command requires %i arguments", min); + return KNOT_EINVAL; + } else if (args->argc < min) { + log_error("command requires at least %i arguments", min); + return KNOT_EINVAL; + } else if (max > 0 && args->argc > max) { + log_error("command takes at most %i arguments", max); + return KNOT_EINVAL; + } + + return KNOT_EOK; +} + +static int check_conf_args(cmd_args_t *args) +{ + // Mask relevant flags. + cmd_flag_t flags = args->desc->flags; + flags &= CMD_FOPT_ITEM | CMD_FREQ_ITEM | CMD_FOPT_DATA; + + switch (args->argc) { + case 0: + if (flags == CMD_FNONE || (flags & CMD_FOPT_ITEM)) { + return KNOT_EOK; + } + break; + case 1: + if (flags & (CMD_FOPT_ITEM | CMD_FREQ_ITEM)) { + return KNOT_EOK; + } + break; + default: + if (flags != CMD_FNONE) { + return KNOT_EOK; + } + break; + } + + log_error("invalid number of arguments"); + + return KNOT_EINVAL; +} + +static int get_conf_key(const char *key, knot_ctl_data_t *data) +{ + // Get key0. + const char *key0 = key; + + // Check for id. + char *id = strchr(key, '['); + if (id != NULL) { + // Separate key0 and id. + *id++ = '\0'; + + // Check for id end. + char *id_end = id; + while ((id_end = strchr(id_end, ']')) != NULL) { + // Check for escaped character. + if (*(id_end - 1) != '\\') { + break; + } + id_end++; + } + + // Check for unclosed id. + if (id_end == NULL) { + log_error("(missing bracket after identifier) %s", id); + return KNOT_EINVAL; + } + + // Separate id and key1. + *id_end = '\0'; + + key = id_end + 1; + + // Key1 or nothing must follow. + if (*key != '.' && *key != '\0') { + log_error("(unexpected token) %s", key); + return KNOT_EINVAL; + } + } + + // Check for key1. + char *key1 = strchr(key, '.'); + if (key1 != NULL) { + // Separate key0/id and key1. + *key1++ = '\0'; + + if (*key1 == '\0') { + log_error("(missing item specification)"); + return KNOT_EINVAL; + } + } + + (*data)[KNOT_CTL_IDX_SECTION] = key0; + (*data)[KNOT_CTL_IDX_ITEM] = key1; + (*data)[KNOT_CTL_IDX_ID] = id; + + return KNOT_EOK; +} + +static void format_data(ctl_cmd_t cmd, knot_ctl_type_t data_type, + knot_ctl_data_t *data, bool *empty) +{ + const char *error = (*data)[KNOT_CTL_IDX_ERROR]; + const char *flags = (*data)[KNOT_CTL_IDX_FLAGS]; + const char *key0 = (*data)[KNOT_CTL_IDX_SECTION]; + const char *key1 = (*data)[KNOT_CTL_IDX_ITEM]; + const char *id = (*data)[KNOT_CTL_IDX_ID]; + const char *zone = (*data)[KNOT_CTL_IDX_ZONE]; + const char *owner = (*data)[KNOT_CTL_IDX_OWNER]; + const char *ttl = (*data)[KNOT_CTL_IDX_TTL]; + const char *type = (*data)[KNOT_CTL_IDX_TYPE]; + const char *value = (*data)[KNOT_CTL_IDX_DATA]; + + const char *sign = NULL; + if (ctl_has_flag(flags, CTL_FLAG_ADD)) { + sign = CTL_FLAG_ADD; + } else if (ctl_has_flag(flags, CTL_FLAG_REM)) { + sign = CTL_FLAG_REM; + } + + switch (cmd) { + case CTL_STATUS: + if (error != NULL) { + printf("error: (%s)%s%s", error, + (type != NULL) ? " " : "", + (type != NULL) ? type : ""); + } else if (value != NULL) { + printf("%s", value); + *empty = false; + } + break; + case CTL_STOP: + case CTL_RELOAD: + case CTL_CONF_BEGIN: + case CTL_CONF_ABORT: + // Only error message is expected here. + if (error != NULL) { + printf("error: (%s)", error); + } + break; + case CTL_ZONE_STATUS: + case CTL_ZONE_RELOAD: + case CTL_ZONE_REFRESH: + case CTL_ZONE_RETRANSFER: + case CTL_ZONE_NOTIFY: + case CTL_ZONE_FLUSH: + case CTL_ZONE_SIGN: + case CTL_ZONE_KSK_SBM: + case CTL_ZONE_BEGIN: + case CTL_ZONE_COMMIT: + case CTL_ZONE_ABORT: + case CTL_ZONE_PURGE: + if (data_type == KNOT_CTL_TYPE_DATA) { + printf("%s%s%s%s%s%s%s%s", + (!(*empty) ? "\n" : ""), + (error != NULL ? "error: " : ""), + (zone != NULL ? "[" : ""), + (zone != NULL ? zone : ""), + (zone != NULL ? "]" : ""), + (error != NULL ? " (" : ""), + (error != NULL ? error : ""), + (error != NULL ? ")" : "")); + *empty = false; + } + if (cmd == CTL_ZONE_STATUS && type != NULL) { + printf("%s %s: %s", + (data_type != KNOT_CTL_TYPE_DATA ? " |" : ""), + type, value); + } + break; + case CTL_CONF_COMMIT: // Can return a check error context. + case CTL_CONF_LIST: + case CTL_CONF_READ: + case CTL_CONF_DIFF: + case CTL_CONF_GET: + case CTL_CONF_SET: + case CTL_CONF_UNSET: + if (data_type == KNOT_CTL_TYPE_DATA) { + printf("%s%s%s%s%s%s%s%s%s%s%s%s", + (!(*empty) ? "\n" : ""), + (error != NULL ? "error: (" : ""), + (error != NULL ? error : ""), + (error != NULL ? ") " : ""), + (sign != NULL ? sign : ""), + (key0 != NULL ? key0 : ""), + (id != NULL ? "[" : ""), + (id != NULL ? id : ""), + (id != NULL ? "]" : ""), + (key1 != NULL ? "." : ""), + (key1 != NULL ? key1 : ""), + (value != NULL ? " =" : "")); + *empty = false; + } + if (value != NULL) { + printf(" %s", value); + } + break; + case CTL_ZONE_READ: + case CTL_ZONE_DIFF: + case CTL_ZONE_GET: + case CTL_ZONE_SET: + case CTL_ZONE_UNSET: + printf("%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s", + (!(*empty) ? "\n" : ""), + (error != NULL ? "error: (" : ""), + (error != NULL ? error : ""), + (error != NULL ? ") " : ""), + (zone != NULL ? "[" : ""), + (zone != NULL ? zone : ""), + (zone != NULL ? "] " : ""), + (sign != NULL ? sign : ""), + (owner != NULL ? owner : ""), + (ttl != NULL ? " " : ""), + (ttl != NULL ? ttl : ""), + (type != NULL ? " " : ""), + (type != NULL ? type : ""), + (value != NULL ? " " : ""), + (value != NULL ? value : "")); + *empty = false; + break; + case CTL_STATS: + case CTL_ZONE_STATS: + printf("%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s", + (!(*empty) ? "\n" : ""), + (error != NULL ? "error: (" : ""), + (error != NULL ? error : ""), + (error != NULL ? ") " : ""), + (zone != NULL ? "[" : ""), + (zone != NULL ? zone : ""), + (zone != NULL ? "] " : ""), + (key0 != NULL ? key0 : ""), + (key1 != NULL ? "." : ""), + (key1 != NULL ? key1 : ""), + (id != NULL ? "[" : ""), + (id != NULL ? id : ""), + (id != NULL ? "]" : ""), + (value != NULL ? " = " : ""), + (value != NULL ? value : "")); + *empty = false; + break; + default: + assert(0); + } +} + +static void format_block(ctl_cmd_t cmd, bool failed, bool empty) +{ + switch (cmd) { + case CTL_STATUS: + printf("%s\n", (failed || !empty) ? "" : "Running"); + break; + case CTL_STOP: + printf("%s\n", failed ? "" : "Stopped"); + break; + case CTL_RELOAD: + printf("%s\n", failed ? "" : "Reloaded"); + break; + case CTL_CONF_BEGIN: + case CTL_CONF_COMMIT: + case CTL_CONF_ABORT: + case CTL_CONF_SET: + case CTL_CONF_UNSET: + case CTL_ZONE_RELOAD: + case CTL_ZONE_REFRESH: + case CTL_ZONE_RETRANSFER: + case CTL_ZONE_NOTIFY: + case CTL_ZONE_FLUSH: + case CTL_ZONE_SIGN: + case CTL_ZONE_KSK_SBM: + case CTL_ZONE_FREEZE: + case CTL_ZONE_THAW: + case CTL_ZONE_BEGIN: + case CTL_ZONE_COMMIT: + case CTL_ZONE_ABORT: + case CTL_ZONE_SET: + case CTL_ZONE_UNSET: + case CTL_ZONE_PURGE: + printf("%s\n", failed ? "" : "OK"); + break; + case CTL_ZONE_STATUS: + case CTL_ZONE_READ: + case CTL_ZONE_DIFF: + case CTL_ZONE_GET: + case CTL_CONF_LIST: + case CTL_CONF_READ: + case CTL_CONF_DIFF: + case CTL_CONF_GET: + case CTL_ZONE_STATS: + case CTL_STATS: + printf("%s", empty ? "" : "\n"); + break; + default: + assert(0); + } +} + +static int ctl_receive(cmd_args_t *args) +{ + bool failed = false; + bool empty = true; + + while (true) { + knot_ctl_type_t type; + knot_ctl_data_t data; + + int ret = knot_ctl_receive(args->ctl, &type, &data); + if (ret != KNOT_EOK) { + log_error(CTL_LOG_STR" (%s)", knot_strerror(ret)); + return ret; + } + + switch (type) { + case KNOT_CTL_TYPE_END: + log_error(CTL_LOG_STR" (%s)", knot_strerror(KNOT_EMALF)); + return KNOT_EMALF; + case KNOT_CTL_TYPE_BLOCK: + format_block(args->desc->cmd, failed, empty); + return failed ? KNOT_ERROR : KNOT_EOK; + case KNOT_CTL_TYPE_DATA: + case KNOT_CTL_TYPE_EXTRA: + format_data(args->desc->cmd, type, &data, &empty); + break; + default: + assert(0); + return KNOT_EINVAL; + } + + if (data[KNOT_CTL_IDX_ERROR] != NULL) { + failed = true; + } + } + + return KNOT_EOK; +} + +static int cmd_ctl(cmd_args_t *args) +{ + int ret = check_args(args, 0, (args->desc->cmd == CTL_STATUS ? 1 : 0)); + if (ret != KNOT_EOK) { + return ret; + } + + knot_ctl_data_t data = { + [KNOT_CTL_IDX_CMD] = ctl_cmd_to_str(args->desc->cmd), + [KNOT_CTL_IDX_FLAGS] = args->force ? CTL_FLAG_FORCE : NULL, + [KNOT_CTL_IDX_TYPE] = args->argc > 0 ? args->argv[0] : NULL + }; + + CTL_SEND_DATA + CTL_SEND_BLOCK + + return ctl_receive(args); +} + +static int set_stats_items(cmd_args_t *args, knot_ctl_data_t *data) +{ + int min_args, max_args; + switch (args->desc->cmd) { + case CTL_STATS: min_args = 0; max_args = 1; break; + case CTL_ZONE_STATS: min_args = 1; max_args = 2; break; + default: + assert(0); + return KNOT_EINVAL; + } + + // Check the number of arguments. + int ret = check_args(args, min_args, max_args); + if (ret != KNOT_EOK) { + return ret; + } + + int idx = 0; + + // Set ZONE name. + if (args->argc > idx && args->desc->cmd == CTL_ZONE_STATS) { + if (strcmp(args->argv[idx], "--") != 0) { + (*data)[KNOT_CTL_IDX_ZONE] = args->argv[idx]; + } + idx++; + } + + if (args->argc > idx) { + (*data)[KNOT_CTL_IDX_SECTION] = args->argv[idx]; + + char *item = strchr(args->argv[idx], '.'); + if (item != NULL) { + // Separate section and item. + *item++ = '\0'; + (*data)[KNOT_CTL_IDX_ITEM] = item; + } + } + + return KNOT_EOK; +} + +static int cmd_stats_ctl(cmd_args_t *args) +{ + knot_ctl_data_t data = { + [KNOT_CTL_IDX_CMD] = ctl_cmd_to_str(args->desc->cmd), + [KNOT_CTL_IDX_FLAGS] = args->force ? CTL_FLAG_FORCE : NULL + }; + + int ret = set_stats_items(args, &data); + if (ret != KNOT_EOK) { + return ret; + } + + CTL_SEND_DATA + CTL_SEND_BLOCK + + return ctl_receive(args); +} + +static int zone_exec(cmd_args_t *args, int (*fcn)(const knot_dname_t *, void *), + void *data) +{ + bool failed = false; + + // Process specified zones. + if (args->argc > 0) { + uint8_t id[KNOT_DNAME_MAXLEN]; + + for (int i = 0; i < args->argc; i++) { + if (knot_dname_from_str(id, args->argv[i], sizeof(id)) == NULL) { + log_zone_str_error(args->argv[i], "invalid name"); + failed = true; + continue; + } + knot_dname_to_lower(id); + + if (!conf_rawid_exists(conf(), C_ZONE, id, knot_dname_size(id))) { + log_zone_error(id, "%s", knot_strerror(KNOT_ENOZONE)); + failed = true; + continue; + } + + if (fcn(id, data) != KNOT_EOK) { + failed = true; + } + } + // Process all configured zones. + } else { + for (conf_iter_t iter = conf_iter(conf(), C_ZONE); + iter.code == KNOT_EOK; conf_iter_next(conf(), &iter)) { + conf_val_t val = conf_iter_id(conf(), &iter); + const knot_dname_t *id = conf_dname(&val); + + if (fcn(id, data) != KNOT_EOK) { + failed = true; + } + } + } + + return failed ? KNOT_ERROR : KNOT_EOK; +} + +static int zone_check(const knot_dname_t *dname, void *data) +{ + UNUSED(data); + + zone_contents_t *contents; + int ret = zone_load_contents(conf(), dname, &contents); + if (ret == KNOT_EOK) { + zone_contents_deep_free(contents); + } + return ret; +} + +static int cmd_zone_check(cmd_args_t *args) +{ + return zone_exec(args, zone_check, NULL); +} + +static int zone_memstats(const knot_dname_t *dname, void *data) +{ + // Init malloc wrapper for trie size estimation. + size_t malloc_size = 0; + knot_mm_t mem_ctx = { + .ctx = &malloc_size, + .alloc = estimator_malloc, + .free = estimator_free + }; + + // Init memory estimation context. + zone_estim_t est = { + .node_table = trie_create(&mem_ctx), + }; + + char buff[KNOT_DNAME_TXT_MAXLEN + 1]; + char *zone_name = knot_dname_to_str(buff, dname, sizeof(buff)); + char *zone_file = conf_zonefile(conf(), dname); + zs_scanner_t *zs = malloc(sizeof(zs_scanner_t)); + + if (est.node_table == NULL || zone_name == NULL || zone_file == NULL || + zs == NULL) { + log_zone_error(dname, "%s", knot_strerror(KNOT_ENOMEM)); + trie_free(est.node_table); + free(zone_file); + free(zs); + return KNOT_ENOMEM; + } + + // Do a parser run, but do not actually create the zone. + if (zs_init(zs, zone_name, KNOT_CLASS_IN, 3600) != 0 || + zs_set_processing(zs, estimator_rrset_memsize_wrap, NULL, &est) != 0 || + zs_set_input_file(zs, zone_file) != 0 || + zs_parse_all(zs) != 0) { + log_zone_error(dname, "failed to parse zone file '%s' (%s)", + zone_file, zs_errorname(zs->error.code)); + trie_apply(est.node_table, estimator_free_trie_node, NULL); + trie_free(est.node_table); + free(zone_file); + zs_deinit(zs); + free(zs); + return KNOT_EPARSEFAIL; + } + free(zone_file); + zs_deinit(zs); + free(zs); + + // Cleanup. + trie_apply(est.node_table, estimator_free_trie_node, NULL); + trie_free(est.node_table); + + double zone_size = (est.rdata_size + est.node_size + est.dname_size + + malloc_size) / (1024.0 * 1024.0); + + log_zone_info(dname, "%zu records, %.1f MiB memory", + est.record_count, zone_size); + + double *total_size = (double *)data; + *total_size += zone_size; + + return KNOT_EOK; +} + +static int cmd_zone_memstats(cmd_args_t *args) +{ + double total_size = 0; + + int ret = zone_exec(args, zone_memstats, &total_size); + + if (args->argc != 1) { + log_info("Total %.1f MiB memory", total_size); + } + + return ret; +} + +static int cmd_zone_ctl(cmd_args_t *args) +{ + knot_ctl_data_t data = { + [KNOT_CTL_IDX_CMD] = ctl_cmd_to_str(args->desc->cmd), + [KNOT_CTL_IDX_FLAGS] = args->force ? CTL_FLAG_FORCE : NULL + }; + + // Check the number of arguments. + int ret = check_args(args, (args->desc->flags & CMD_FREQ_ZONE) ? 1 : 0, -1); + if (ret != KNOT_EOK) { + return ret; + } + + if (args->desc->cmd == CTL_ZONE_PURGE && !args->force) { + log_error("force option required!"); + return KNOT_EDENIED; + } + + // Ignore all zones argument. + if (args->argc == 1 && strcmp(args->argv[0], "--") == 0) { + args->argc = 0; + } + + if (args->argc == 0) { + CTL_SEND_DATA + } + for (int i = 0; i < args->argc; i++) { + data[KNOT_CTL_IDX_ZONE] = args->argv[i]; + + CTL_SEND_DATA + } + + CTL_SEND_BLOCK + + return ctl_receive(args); +} + +#define MAX_FILTERS 7 + +typedef struct { + const char *name; + char id; + bool with_data; // Only ONE filter of each filter_desc_t may have data! +} filter_desc_t; + +const filter_desc_t zone_flush_filters[MAX_FILTERS] = { + { "+outdir", CTL_FILTER_FLUSH_OUTDIR, true }, +}; + +const filter_desc_t zone_status_filters[MAX_FILTERS] = { + { "+role", CTL_FILTER_STATUS_ROLE }, + { "+serial", CTL_FILTER_STATUS_SERIAL }, + { "+transaction", CTL_FILTER_STATUS_TRANSACTION }, + { "+freeze", CTL_FILTER_STATUS_FREEZE }, + { "+events", CTL_FILTER_STATUS_EVENTS }, +}; + +const filter_desc_t zone_purge_filters[MAX_FILTERS] = { + { "+expire", CTL_FILTER_PURGE_EXPIRE }, + { "+timers", CTL_FILTER_PURGE_TIMERS }, + { "+zonefile", CTL_FILTER_PURGE_ZONEFILE }, + { "+journal", CTL_FILTER_PURGE_JOURNAL }, + { "+kaspdb", CTL_FILTER_PURGE_KASPDB }, + { "+orphan", CTL_FILTER_PURGE_ORPHAN }, +}; + +const filter_desc_t null_filter = { 0 }; + +static const filter_desc_t *get_filter(ctl_cmd_t cmd, const char *filter_name) +{ + const filter_desc_t *fd = NULL; + switch (cmd) { + case CTL_ZONE_FLUSH: + fd = zone_flush_filters; + break; + case CTL_ZONE_STATUS: + fd = zone_status_filters; + break; + case CTL_ZONE_PURGE: + fd = zone_purge_filters; + break; + default: + return &null_filter; + } + for (size_t i = 0; i < MAX_FILTERS && fd[i].name != NULL; i++) { + if (strcmp(fd[i].name, filter_name) == 0) { + return &fd[i]; + } + } + return &null_filter; +} + +static int cmd_zone_filter_ctl(cmd_args_t *args) +{ + knot_ctl_data_t data = { + [KNOT_CTL_IDX_CMD] = ctl_cmd_to_str(args->desc->cmd), + [KNOT_CTL_IDX_FLAGS] = args->force ? CTL_FLAG_FORCE : NULL + }; + + if (args->desc->cmd == CTL_ZONE_PURGE && !args->force) { + log_error("force option required!"); + return KNOT_EDENIED; + } + + char filter_buff[MAX_FILTERS + 1] = { 0 }; + + // First, process the filters. + for (int i = 0; i < args->argc; i++) { + if (args->argv[i][0] == '+') { + if (data[KNOT_CTL_IDX_FILTER] == NULL) { + data[KNOT_CTL_IDX_FILTER] = filter_buff; + } + char filter_id[2] = { get_filter(args->desc->cmd, args->argv[i])->id, 0 }; + if (filter_id[0] == '\0') { + log_error("unknown filter: %s", args->argv[i]); + return KNOT_EINVAL; + } + if (strchr(filter_buff, filter_id[0]) == NULL) { + assert(strlen(filter_buff) < MAX_FILTERS); + strlcat(filter_buff, filter_id, sizeof(filter_buff)); + } + if (get_filter(args->desc->cmd, args->argv[i])->with_data) { + data[KNOT_CTL_IDX_DATA] = args->argv[++i]; + } + } + } + + // Second, process zones. + int ret; + int sentzones = 0; + bool twodash = false; + for (int i = 0; i < args->argc; i++) { + // Skip filters. + if (args->argv[i][0] == '+') { + if (get_filter(args->desc->cmd, args->argv[i])->with_data) { + i++; + } + continue; + } + + if (strcmp(args->argv[i], "--") != 0) { + data[KNOT_CTL_IDX_ZONE] = args->argv[i]; + CTL_SEND_DATA + sentzones++; + } else { + twodash = true; + } + } + + if ((args->desc->flags & CMD_FREQ_ZONE) && sentzones == 0 && !twodash) { + log_error("zone must be specified (or -- for all zones)"); + return KNOT_EDENIED; + } + + if (sentzones == 0) { + CTL_SEND_DATA + } + CTL_SEND_BLOCK + + return ctl_receive(args); +} + +static int set_rdata(cmd_args_t *args, int pos, char *rdata, size_t rdata_len) +{ + rdata[0] = '\0'; + + for (int i = pos; i < args->argc; i++) { + if (i > pos && strlcat(rdata, " ", rdata_len) >= rdata_len) { + return KNOT_ESPACE; + } + if (strlcat(rdata, args->argv[i], rdata_len) >= rdata_len) { + return KNOT_ESPACE; + } + } + + return KNOT_EOK; +} + +static int set_node_items(cmd_args_t *args, knot_ctl_data_t *data, char *rdata, + size_t rdata_len) +{ + int min_args, max_args; + switch (args->desc->cmd) { + case CTL_ZONE_READ: + case CTL_ZONE_GET: min_args = 1; max_args = 3; break; + case CTL_ZONE_DIFF: min_args = 1; max_args = 1; break; + case CTL_ZONE_SET: min_args = 3; max_args = -1; break; + case CTL_ZONE_UNSET: min_args = 2; max_args = -1; break; + default: + assert(0); + return KNOT_EINVAL; + } + + // Check the number of arguments. + int ret = check_args(args, min_args, max_args); + if (ret != KNOT_EOK) { + return ret; + } + + int idx = 0; + + // Set ZONE name. + assert(args->argc > idx); + if (strcmp(args->argv[idx], "--") != 0) { + (*data)[KNOT_CTL_IDX_ZONE] = args->argv[idx]; + } + idx++; + + // Set OWNER name if specified. + if (args->argc > idx) { + (*data)[KNOT_CTL_IDX_OWNER] = args->argv[idx]; + idx++; + } + + // Set TTL only with an editing operation. + if (args->argc > idx) { + uint16_t type; + if (knot_rrtype_from_string(args->argv[idx], &type) != 0) { + switch (args->desc->cmd) { + case CTL_ZONE_SET: + case CTL_ZONE_UNSET: + (*data)[KNOT_CTL_IDX_TTL] = args->argv[idx]; + idx++; + break; + default: + return KNOT_EINVAL; + } + } + } + + // Set record TYPE if specified. + if (args->argc > idx) { + (*data)[KNOT_CTL_IDX_TYPE] = args->argv[idx]; + idx++; + } + + // Set record DATA if specified. + if (args->argc > idx) { + ret = set_rdata(args, idx, rdata, rdata_len); + if (ret != KNOT_EOK) { + return ret; + } + (*data)[KNOT_CTL_IDX_DATA] = rdata; + } + + return KNOT_EOK; +} + +static int cmd_zone_node_ctl(cmd_args_t *args) +{ + knot_ctl_data_t data = { + [KNOT_CTL_IDX_CMD] = ctl_cmd_to_str(args->desc->cmd), + [KNOT_CTL_IDX_FLAGS] = args->force ? CTL_FLAG_FORCE : NULL + }; + + char rdata[65536]; // Maximum item size in libknot control interface. + + int ret = set_node_items(args, &data, rdata, sizeof(rdata)); + if (ret != KNOT_EOK) { + return ret; + } + + CTL_SEND_DATA + CTL_SEND_BLOCK + + return ctl_receive(args); +} + +static int cmd_conf_init(cmd_args_t *args) +{ + int ret = check_args(args, 0, 0); + if (ret != KNOT_EOK) { + return ret; + } + + ret = conf_db_check(conf(), &conf()->read_txn); + if ((ret >= KNOT_EOK || ret == KNOT_CONF_EVERSION)) { + if (ret != KNOT_EOK && !args->force) { + log_error("use force option to overwrite the existing " + "destination and ensure the server is not running!"); + return KNOT_EDENIED; + } + + ret = conf_import(conf(), "", false); + } + + if (ret == KNOT_EOK) { + log_info("OK"); + } else { + log_error("init (%s)", knot_strerror(ret)); + } + + return ret; +} + +static int cmd_conf_check(cmd_args_t *args) +{ + int ret = check_args(args, 0, 0); + if (ret != KNOT_EOK) { + return ret; + } + + log_info("Configuration is valid"); + + return 0; +} + +static int cmd_conf_import(cmd_args_t *args) +{ + int ret = check_args(args, 1, 1); + if (ret != KNOT_EOK) { + return ret; + } + + ret = conf_db_check(conf(), &conf()->read_txn); + if ((ret >= KNOT_EOK || ret == KNOT_CONF_EVERSION)) { + if (ret != KNOT_EOK && !args->force) { + log_error("use force option to overwrite the existing " + "destination and ensure the server is not running!"); + return KNOT_EDENIED; + } + + log_debug("importing confdb from file '%s'", args->argv[0]); + + ret = conf_import(conf(), args->argv[0], true); + } + + if (ret == KNOT_EOK) { + log_info("OK"); + } else { + log_error("import (%s)", knot_strerror(ret)); + } + + return ret; +} + +static int cmd_conf_export(cmd_args_t *args) +{ + int ret = check_args(args, 0, 1); + if (ret != KNOT_EOK) { + return ret; + } + + // Stdout is the default output file. + const char *file_name = NULL; + if (args->argc > 0) { + file_name = args->argv[0]; + log_debug("exporting confdb into file '%s'", file_name); + } + + ret = conf_export(conf(), file_name, YP_SNONE); + + if (ret == KNOT_EOK) { + if (args->argc > 0) { + log_info("OK"); + } + } else { + log_error("export (%s)", knot_strerror(ret)); + } + + return ret; +} + +static int cmd_conf_ctl(cmd_args_t *args) +{ + // Check the number of arguments. + int ret = check_conf_args(args); + if (ret != KNOT_EOK) { + return ret; + } + + knot_ctl_data_t data = { + [KNOT_CTL_IDX_CMD] = ctl_cmd_to_str(args->desc->cmd), + [KNOT_CTL_IDX_FLAGS] = args->force ? CTL_FLAG_FORCE : NULL + }; + + // Send the command without parameters. + if (args->argc == 0) { + CTL_SEND_DATA + // Set the first item argument. + } else { + ret = get_conf_key(args->argv[0], &data); + if (ret != KNOT_EOK) { + return ret; + } + + // Send if only one argument or item without values. + if (args->argc == 1 || !(args->desc->flags & CMD_FOPT_DATA)) { + CTL_SEND_DATA + } + } + + // Send the item values or the other items. + for (int i = 1; i < args->argc; i++) { + if (args->desc->flags & CMD_FOPT_DATA) { + data[KNOT_CTL_IDX_DATA] = args->argv[i]; + } else { + ret = get_conf_key(args->argv[i], &data); + if (ret != KNOT_EOK) { + return ret; + } + } + + CTL_SEND_DATA + } + + CTL_SEND_BLOCK + + return ctl_receive(args); +} + +const cmd_desc_t cmd_table[] = { + { CMD_EXIT, NULL, CTL_NONE }, + + { CMD_STATUS, cmd_ctl, CTL_STATUS, CMD_FOPT_DATA}, + { CMD_STOP, cmd_ctl, CTL_STOP }, + { CMD_RELOAD, cmd_ctl, CTL_RELOAD }, + { CMD_STATS, cmd_stats_ctl, CTL_STATS }, + + { CMD_ZONE_CHECK, cmd_zone_check, CTL_NONE, CMD_FOPT_ZONE | CMD_FREAD }, + { CMD_ZONE_MEMSTATS, cmd_zone_memstats, CTL_NONE, CMD_FOPT_ZONE | CMD_FREAD }, + { CMD_ZONE_STATUS, cmd_zone_filter_ctl, CTL_ZONE_STATUS, CMD_FOPT_ZONE }, + { CMD_ZONE_RELOAD, cmd_zone_ctl, CTL_ZONE_RELOAD, CMD_FOPT_ZONE }, + { CMD_ZONE_REFRESH, cmd_zone_ctl, CTL_ZONE_REFRESH, CMD_FOPT_ZONE }, + { CMD_ZONE_RETRANSFER, cmd_zone_ctl, CTL_ZONE_RETRANSFER, CMD_FOPT_ZONE }, + { CMD_ZONE_NOTIFY, cmd_zone_ctl, CTL_ZONE_NOTIFY, CMD_FOPT_ZONE }, + { CMD_ZONE_FLUSH, cmd_zone_filter_ctl, CTL_ZONE_FLUSH, CMD_FOPT_ZONE }, + { CMD_ZONE_SIGN, cmd_zone_ctl, CTL_ZONE_SIGN, CMD_FOPT_ZONE }, + { CMD_ZONE_KSK_SBM, cmd_zone_ctl, CTL_ZONE_KSK_SBM, CMD_FREQ_ZONE | CMD_FOPT_ZONE }, + { CMD_ZONE_FREEZE, cmd_zone_ctl, CTL_ZONE_FREEZE, CMD_FOPT_ZONE }, + { CMD_ZONE_THAW, cmd_zone_ctl, CTL_ZONE_THAW, CMD_FOPT_ZONE }, + + { CMD_ZONE_READ, cmd_zone_node_ctl, CTL_ZONE_READ, CMD_FREQ_ZONE }, + { CMD_ZONE_BEGIN, cmd_zone_ctl, CTL_ZONE_BEGIN, CMD_FREQ_ZONE | CMD_FOPT_ZONE }, + { CMD_ZONE_COMMIT, cmd_zone_ctl, CTL_ZONE_COMMIT, CMD_FREQ_ZONE | CMD_FOPT_ZONE }, + { CMD_ZONE_ABORT, cmd_zone_ctl, CTL_ZONE_ABORT, CMD_FREQ_ZONE | CMD_FOPT_ZONE }, + { CMD_ZONE_DIFF, cmd_zone_node_ctl, CTL_ZONE_DIFF, CMD_FREQ_ZONE }, + { CMD_ZONE_GET, cmd_zone_node_ctl, CTL_ZONE_GET, CMD_FREQ_ZONE }, + { CMD_ZONE_SET, cmd_zone_node_ctl, CTL_ZONE_SET, CMD_FREQ_ZONE }, + { CMD_ZONE_UNSET, cmd_zone_node_ctl, CTL_ZONE_UNSET, CMD_FREQ_ZONE }, + { CMD_ZONE_PURGE, cmd_zone_filter_ctl, CTL_ZONE_PURGE, CMD_FREQ_ZONE }, + { CMD_ZONE_STATS, cmd_stats_ctl, CTL_ZONE_STATS, CMD_FREQ_ZONE }, + + { CMD_CONF_INIT, cmd_conf_init, CTL_NONE, CMD_FWRITE }, + { CMD_CONF_CHECK, cmd_conf_check, CTL_NONE, CMD_FREAD | CMD_FREQ_MOD }, + { CMD_CONF_IMPORT, cmd_conf_import, CTL_NONE, CMD_FWRITE | CMD_FOPT_MOD }, + { CMD_CONF_EXPORT, cmd_conf_export, CTL_NONE, CMD_FREAD | CMD_FOPT_MOD }, + { CMD_CONF_LIST, cmd_conf_ctl, CTL_CONF_LIST, CMD_FOPT_ITEM }, + { CMD_CONF_READ, cmd_conf_ctl, CTL_CONF_READ, CMD_FOPT_ITEM }, + { CMD_CONF_BEGIN, cmd_conf_ctl, CTL_CONF_BEGIN }, + { CMD_CONF_COMMIT, cmd_conf_ctl, CTL_CONF_COMMIT }, + { CMD_CONF_ABORT, cmd_conf_ctl, CTL_CONF_ABORT }, + { CMD_CONF_DIFF, cmd_conf_ctl, CTL_CONF_DIFF, CMD_FOPT_ITEM | CMD_FREQ_TXN }, + { CMD_CONF_GET, cmd_conf_ctl, CTL_CONF_GET, CMD_FOPT_ITEM | CMD_FREQ_TXN }, + { CMD_CONF_SET, cmd_conf_ctl, CTL_CONF_SET, CMD_FREQ_ITEM | CMD_FOPT_DATA | CMD_FREQ_TXN }, + { CMD_CONF_UNSET, cmd_conf_ctl, CTL_CONF_UNSET, CMD_FOPT_ITEM | CMD_FOPT_DATA | CMD_FREQ_TXN }, + { NULL } +}; + +static const cmd_help_t cmd_help_table[] = { + { CMD_EXIT, "", "Exit interactive mode." }, + { "", "", "" }, + { CMD_STATUS, "[<detail>]", "Check if the server is running." }, + { CMD_STOP, "", "Stop the server if running." }, + { CMD_RELOAD, "", "Reload the server configuration and modified zones." }, + { CMD_STATS, "[<module>[.<counter>]]", "Show global statistics counter(s)." }, + { "", "", "" }, + { CMD_ZONE_CHECK, "[<zone>...]", "Check if the zone can be loaded. (*)" }, + { CMD_ZONE_MEMSTATS, "[<zone>...]", "Estimate memory use for the zone. (*)" }, + { CMD_ZONE_RELOAD, "[<zone>...]", "Reload a zone from a disk." }, + { CMD_ZONE_REFRESH, "[<zone>...]", "Force slave zone refresh." }, + { CMD_ZONE_NOTIFY, "[<zone>...]", "Send a NOTIFY message to all configured remotes." }, + { CMD_ZONE_RETRANSFER, "[<zone>...]", "Force slave zone retransfer (no serial check)." }, + { CMD_ZONE_FLUSH, "[<zone>...] [<filter>...]", "Flush zone journal into the zone file." }, + { CMD_ZONE_SIGN, "[<zone>...]", "Re-sign the automatically signed zone." }, + { CMD_ZONE_KSK_SBM, "<zone>...", "When KSK submission, confirm parent's DS presence." }, + { CMD_ZONE_FREEZE, "[<zone>...]", "Temporarily postpone automatic zone-changing events." }, + { CMD_ZONE_THAW, "[<zone>...]", "Dismiss zone freeze." }, + { "", "", "" }, + { CMD_ZONE_READ, "<zone> [<owner> [<type>]]", "Get zone data that are currently being presented." }, + { CMD_ZONE_BEGIN, "<zone>...", "Begin a zone transaction." }, + { CMD_ZONE_COMMIT, "<zone>...", "Commit the zone transaction." }, + { CMD_ZONE_ABORT, "<zone>...", "Abort the zone transaction." }, + { CMD_ZONE_DIFF, "<zone>", "Get zone changes within the transaction." }, + { CMD_ZONE_GET, "<zone> [<owner> [<type>]]", "Get zone data within the transaction." }, + { CMD_ZONE_SET, "<zone> <owner> [<ttl>] <type> <rdata>", "Add zone record within the transaction." }, + { CMD_ZONE_UNSET, "<zone> <owner> [<type> [<rdata>]]", "Remove zone data within the transaction." }, + { CMD_ZONE_PURGE, "<zone>... [<filter>...]", "Purge zone data, zone file, journal, timers, and KASP data." }, + { CMD_ZONE_STATS, "<zone> [<module>[.<counter>]]", "Show zone statistics counter(s)."}, + { CMD_ZONE_STATUS, "<zone> [<filter>...]", "Show the zone status." }, + { "", "", "" }, + { CMD_CONF_INIT, "", "Initialize the confdb. (*)" }, + { CMD_CONF_CHECK, "", "Check the server configuration. (*)" }, + { CMD_CONF_IMPORT, "<filename>", "Import a config file into the confdb. (*)" }, + { CMD_CONF_EXPORT, "[<filename>]", "Export the confdb into a config file or stdout. (*)" }, + { CMD_CONF_LIST, "[<item>...]", "List the confdb sections or section items." }, + { CMD_CONF_READ, "[<item>...]", "Get the item from the active confdb." }, + { CMD_CONF_BEGIN, "", "Begin a writing confdb transaction." }, + { CMD_CONF_COMMIT, "", "Commit the confdb transaction." }, + { CMD_CONF_ABORT, "", "Rollback the confdb transaction." }, + { CMD_CONF_DIFF, "[<item>...]", "Get the item difference within the transaction." }, + { CMD_CONF_GET, "[<item>...]", "Get the item data within the transaction." }, + { CMD_CONF_SET, " <item> [<data>...]", "Set the item data within the transaction." }, + { CMD_CONF_UNSET, "[<item>] [<data>...]", "Unset the item data within the transaction." }, + { NULL } +}; + +void print_commands(void) +{ + printf("\nActions:\n"); + + for (const cmd_help_t *cmd = cmd_help_table; cmd->name != NULL; cmd++) { + printf(" %-18s %-38s %s\n", cmd->name, cmd->params, cmd->desc); + } + + printf("\n" + "Note:\n" + " Use @ owner to denote the zone name.\n" + " Empty or '--' <zone> parameter means all zones or all zones with a transaction.\n" + " Type <item> parameter in the form of <section>[<identifier>].<name>.\n" + " (*) indicates a local operation which requires a configuration.\n"); +} diff --git a/src/utils/knotc/commands.h b/src/utils/knotc/commands.h new file mode 100644 index 0000000..5cabaf1 --- /dev/null +++ b/src/utils/knotc/commands.h @@ -0,0 +1,68 @@ +/* Copyright (C) 2018 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> + + 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 <http://www.gnu.org/licenses/>. +*/ + +#pragma once + +#include "libknot/control/control.h" +#include "knot/ctl/commands.h" + +/*! \brief Command condition flags. */ +typedef enum { + CMD_FNONE = 0, /*!< Empty flag. */ + CMD_FREAD = 1 << 0, /*!< Required read access to config or confdb. */ + CMD_FWRITE = 1 << 1, /*!< Required write access to confdb. */ + CMD_FOPT_ITEM = 1 << 2, /*!< Optional item argument. */ + CMD_FREQ_ITEM = 1 << 3, /*!< Required item argument. */ + CMD_FOPT_DATA = 1 << 4, /*!< Optional item data argument. */ + CMD_FOPT_ZONE = 1 << 5, /*!< Optional zone name argument. */ + CMD_FREQ_ZONE = 1 << 6, /*!< Required zone name argument. */ + CMD_FREQ_TXN = 1 << 7, /*!< Required open confdb transaction. */ + CMD_FOPT_MOD = 1 << 8, /*!< Optional configured modules dependency. */ + CMD_FREQ_MOD = 1 << 9, /*!< Required configured modules dependency. */ +} cmd_flag_t; + +struct cmd_desc; +typedef struct cmd_desc cmd_desc_t; + +/*! \brief Command callback arguments. */ +typedef struct { + const cmd_desc_t *desc; + knot_ctl_t *ctl; + int argc; + const char **argv; + bool force; +} cmd_args_t; + +/*! \brief Command callback description. */ +struct cmd_desc { + const char *name; + int (*fcn)(cmd_args_t *); + ctl_cmd_t cmd; + cmd_flag_t flags; +}; + +/*! \brief Command description. */ +typedef struct { + const char *name; + const char *params; + const char *desc; +} cmd_help_t; + +/*! \brief Table of commands. */ +extern const cmd_desc_t cmd_table[]; + +/*! \brief Prints commands help. */ +void print_commands(void); diff --git a/src/utils/knotc/estimator.c b/src/utils/knotc/estimator.c new file mode 100644 index 0000000..f1fa1a7 --- /dev/null +++ b/src/utils/knotc/estimator.c @@ -0,0 +1,147 @@ +/* Copyright (C) 2018 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> + + 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 <http://www.gnu.org/licenses/>. +*/ + +#include <assert.h> + +#include "utils/knotc/estimator.h" +#include "knot/zone/node.h" +#include "libknot/libknot.h" +#include "contrib/macros.h" +#include "contrib/string.h" +#include "contrib/ucw/lists.h" + +// Addition constants used for tweaking, mostly malloc overhead +enum estim_consts { + MALLOC_OVERHEAD = sizeof(size_t), // set according to malloc.c + MALLOC_MIN = MALLOC_OVERHEAD * 3 // minimum size of malloc'd chunk +}; + +typedef struct { + node_t n; + uint16_t type; +} type_list_item_t; + +static size_t add_overhead(size_t size) +{ + return MALLOC_OVERHEAD + size + size % MALLOC_MIN; +} + +// return: 0 not present, 1 - present +static int find_in_list(list_t *node_list, uint16_t type) +{ + node_t *n = NULL; + WALK_LIST(n, *node_list) { + type_list_item_t *l_entr = (type_list_item_t *)n; + assert(l_entr); + if (l_entr->type == type) { + return 1; + } + } + + type_list_item_t *new_entry = malloc(sizeof(type_list_item_t)); + assert(new_entry != NULL); + new_entry->type = type; + + add_head(node_list, (node_t *)new_entry); + return 0; +} + +// return: 0 not present (added), 1 - present +static int dummy_node_add_type(list_t *l, uint16_t t) +{ + return find_in_list(l, t); +} + +// return: 0 - unique, 1 - duplicate +static int insert_dname_into_table(trie_t *table, const knot_dname_t *d, + list_t **dummy_node) +{ + size_t d_size = knot_dname_size(d); + + trie_val_t *val = trie_get_try(table, (char *)d, d_size); + if (val == NULL) { + // Create new dummy node to use for this dname + *dummy_node = malloc(sizeof(list_t)); + assert(dummy_node != NULL); + init_list(*dummy_node); + *trie_get_ins(table, (char *)d, d_size) = *dummy_node; + return 0; + } else { + // Return previously found dummy node + *dummy_node = (list_t *)(*val); + return 1; + } +} + +static void rrset_memsize(zone_estim_t *est, const zs_scanner_t *scanner) +{ + // Handle RRSet's owner + list_t *dummy_node = NULL; + if (insert_dname_into_table(est->node_table, scanner->r_owner, &dummy_node) == 0) { + // First time we see this name == new node + est->node_size += add_overhead(sizeof(zone_node_t)); + // Also, node has an owner. + est->dname_size += add_overhead(knot_dname_size(scanner->r_owner)); + // Trie's nodes handled at the end of computation + } + assert(dummy_node); + + // Add RDATA + size + size_t rdlen = knot_rdata_size(scanner->r_data_length); + est->rdata_size += add_overhead(rdlen); + est->record_count++; + + /* + * RDATA size done, now add static part of RRSet to size. + * Do not add for RRs that would be merged. + * All possible duplicates will be added to total size. + */ + if (dummy_node_add_type(dummy_node, scanner->r_type) == 0) { + /* + * New RR type, add actual rr_data struct's size. No way to + * guess the actual overhead taken up by the array, so we add + * it each time. + */ + est->node_size += add_overhead(sizeof(struct rr_data)); + } +} + +void *estimator_malloc(void *ctx, size_t len) +{ + size_t *count = (size_t *)ctx; + *count += add_overhead(len); + return malloc(len); +} + +void estimator_free(void *p) +{ + free(p); +} + +void estimator_rrset_memsize_wrap(zs_scanner_t *scanner) +{ + rrset_memsize(scanner->process.data, scanner); +} + +int estimator_free_trie_node(trie_val_t *val, void *data) +{ + UNUSED(data); + list_t *trie_n = (list_t *)(*val); + WALK_LIST_FREE(*trie_n); + free(trie_n); + + return KNOT_EOK; +} diff --git a/src/utils/knotc/estimator.h b/src/utils/knotc/estimator.h new file mode 100644 index 0000000..ee05d5f --- /dev/null +++ b/src/utils/knotc/estimator.h @@ -0,0 +1,64 @@ +/* Copyright (C) 2018 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> + + 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 <http://www.gnu.org/licenses/>. +*/ + +#pragma once + +#include "contrib/qp-trie/trie.h" +#include "libzscanner/scanner.h" + +/*! + * \brief Memory estimation context. + */ +typedef struct { + trie_t *node_table; /*!< Same trie is in actual zone. */ + size_t rdata_size; /*!< Estimated RDATA size. */ + size_t dname_size; /*!< Estimated DNAME size. */ + size_t node_size; /*!< Estimated node size. */ + size_t record_count; /*!< Total record count for zone. */ +} zone_estim_t; + +/*! + * \brief Size counting malloc wrapper. + * + * \param ctx Data for malloc wrapper. + * \param len Size to allocate. + * + * \retval Alloc'd data on succes. + * \retval NULL on error. + */ +void *estimator_malloc(void *ctx, size_t len); + +/*! + * \brief Size counting free wrapper. + * + * \param p Data to free. + */ +void estimator_free(void *p); + +/*! + * \brief For use with scanner - counts memsize of RRSets. + * + * \param scanner Scanner context. + */ +void estimator_rrset_memsize_wrap(zs_scanner_t *scanner); + +/*! + * \brief Cleanup function for use with trie. + * + * \param val Data to free. + * \param data Unused variable. + */ +int estimator_free_trie_node(trie_val_t *val, void *data); diff --git a/src/utils/knotc/interactive.c b/src/utils/knotc/interactive.c new file mode 100644 index 0000000..ad14104 --- /dev/null +++ b/src/utils/knotc/interactive.c @@ -0,0 +1,433 @@ +/* Copyright (C) 2016 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> + + 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 <http://www.gnu.org/licenses/>. +*/ + +#include <stdio.h> +#include <histedit.h> + +#include "knot/common/log.h" +#include "utils/common/lookup.h" +#include "utils/knotc/interactive.h" +#include "utils/knotc/commands.h" +#include "contrib/string.h" + +#define PROGRAM_NAME "knotc" +#define HISTORY_FILE ".knotc_history" + +extern params_t params; + +static void cmds_lookup(EditLine *el, const char *str, size_t str_len) +{ + lookup_t lookup; + int ret = lookup_init(&lookup); + if (ret != KNOT_EOK) { + return; + } + + // Fill the lookup with command names. + for (const cmd_desc_t *desc = cmd_table; desc->name != NULL; desc++) { + ret = lookup_insert(&lookup, desc->name, NULL); + if (ret != KNOT_EOK) { + goto cmds_lookup_finish; + } + } + + lookup_complete(&lookup, str, str_len, el, true); + +cmds_lookup_finish: + lookup_deinit(&lookup); +} + +static void local_zones_lookup(EditLine *el, const char *str, size_t str_len) +{ + lookup_t lookup; + int ret = lookup_init(&lookup); + if (ret != KNOT_EOK) { + return; + } + + char buff[KNOT_DNAME_TXT_MAXLEN + 1]; + + // Fill the lookup with local zone names. + for (conf_iter_t iter = conf_iter(conf(), C_ZONE); + iter.code == KNOT_EOK; conf_iter_next(conf(), &iter)) { + conf_val_t val = conf_iter_id(conf(), &iter); + char *name = knot_dname_to_str(buff, conf_dname(&val), sizeof(buff)); + + ret = lookup_insert(&lookup, name, NULL); + if (ret != KNOT_EOK) { + conf_iter_finish(conf(), &iter); + goto local_zones_lookup_finish; + } + } + + lookup_complete(&lookup, str, str_len, el, true); + +local_zones_lookup_finish: + lookup_deinit(&lookup); +} + +static char *get_id_name(const char *section) +{ + const cmd_desc_t *desc = cmd_table; + while (desc->name != NULL && desc->cmd != CTL_CONF_LIST) { + desc++; + } + assert(desc->name != NULL); + + knot_ctl_data_t query = { + [KNOT_CTL_IDX_CMD] = ctl_cmd_to_str(desc->cmd), + [KNOT_CTL_IDX_SECTION] = section + }; + + knot_ctl_t *ctl = NULL; + knot_ctl_type_t type; + knot_ctl_data_t reply; + + // Try to get the first group item (possible id). + if (set_ctl(&ctl, desc, ¶ms) != KNOT_EOK || + knot_ctl_send(ctl, KNOT_CTL_TYPE_DATA, &query) != KNOT_EOK || + knot_ctl_send(ctl, KNOT_CTL_TYPE_BLOCK, NULL) != KNOT_EOK || + knot_ctl_receive(ctl, &type, &reply) != KNOT_EOK || + type != KNOT_CTL_TYPE_DATA || reply[KNOT_CTL_IDX_ERROR] != NULL) { + unset_ctl(ctl); + return NULL; + } + + char *id_name = strdup(reply[KNOT_CTL_IDX_ITEM]); + + unset_ctl(ctl); + + return id_name; +} + +static void id_lookup(EditLine *el, const char *str, size_t str_len, + const cmd_desc_t *cmd_desc, const char *section, bool add_space) +{ + // Decide which confdb transaction to ask. + unsigned ctl_code = (cmd_desc->flags & CMD_FREQ_TXN) ? + CTL_CONF_GET : CTL_CONF_READ; + + const cmd_desc_t *desc = cmd_table; + while (desc->name != NULL && desc->cmd != ctl_code) { + desc++; + } + assert(desc->name != NULL); + + char *id_name = get_id_name(section); + if (id_name == NULL) { + return; + } + + knot_ctl_data_t query = { + [KNOT_CTL_IDX_CMD] = ctl_cmd_to_str(desc->cmd), + [KNOT_CTL_IDX_SECTION] = section, + [KNOT_CTL_IDX_ITEM] = id_name + }; + + lookup_t lookup; + knot_ctl_t *ctl = NULL; + + if (set_ctl(&ctl, desc, ¶ms) != KNOT_EOK || + knot_ctl_send(ctl, KNOT_CTL_TYPE_DATA, &query) != KNOT_EOK || + knot_ctl_send(ctl, KNOT_CTL_TYPE_BLOCK, NULL) != KNOT_EOK || + lookup_init(&lookup) != KNOT_EOK) { + unset_ctl(ctl); + free(id_name); + return; + } + + free(id_name); + + while (true) { + knot_ctl_type_t type; + knot_ctl_data_t reply; + + // Receive one section id. + if (knot_ctl_receive(ctl, &type, &reply) != KNOT_EOK) { + goto id_lookup_finish; + } + + // Stop if finished transfer. + if (type != KNOT_CTL_TYPE_DATA) { + break; + } + + // Insert the id into the lookup. + if (reply[KNOT_CTL_IDX_ERROR] != NULL || + lookup_insert(&lookup, reply[KNOT_CTL_IDX_DATA], NULL) != KNOT_EOK) { + goto id_lookup_finish; + } + } + + lookup_complete(&lookup, str, str_len, el, add_space); + +id_lookup_finish: + lookup_deinit(&lookup); + unset_ctl(ctl); +} + +static void list_lookup(EditLine *el, const char *section, const char *item) +{ + const cmd_desc_t *desc = cmd_table; + while (desc->name != NULL && desc->cmd != CTL_CONF_LIST) { + desc++; + } + assert(desc->name != NULL); + + knot_ctl_data_t query = { + [KNOT_CTL_IDX_CMD] = ctl_cmd_to_str(desc->cmd), + [KNOT_CTL_IDX_SECTION] = section + }; + + lookup_t lookup; + knot_ctl_t *ctl = NULL; + + if (set_ctl(&ctl, desc, ¶ms) != KNOT_EOK || + knot_ctl_send(ctl, KNOT_CTL_TYPE_DATA, &query) != KNOT_EOK || + knot_ctl_send(ctl, KNOT_CTL_TYPE_BLOCK, NULL) != KNOT_EOK || + lookup_init(&lookup) != KNOT_EOK) { + unset_ctl(ctl); + return; + } + + while (true) { + knot_ctl_type_t type; + knot_ctl_data_t reply; + + // Receive one section/item name. + if (knot_ctl_receive(ctl, &type, &reply) != KNOT_EOK) { + goto list_lookup_finish; + } + + // Stop if finished transfer. + if (type != KNOT_CTL_TYPE_DATA) { + break; + } + + const char *str = (section == NULL) ? reply[KNOT_CTL_IDX_SECTION] : + reply[KNOT_CTL_IDX_ITEM]; + + // Insert the name into the lookup. + if (reply[KNOT_CTL_IDX_ERROR] != NULL || + lookup_insert(&lookup, str, NULL) != KNOT_EOK) { + goto list_lookup_finish; + } + } + + lookup_complete(&lookup, item, strlen(item), el, section != NULL); + +list_lookup_finish: + lookup_deinit(&lookup); + unset_ctl(ctl); +} + +static void item_lookup(EditLine *el, const char *str, const cmd_desc_t *cmd_desc) +{ + // List all sections. + if (str == NULL) { + list_lookup(el, NULL, ""); + return; + } + + // Check for id specification. + char *id = (strchr(str, '[')); + if (id != NULL) { + char *section = strndup(str, id - str); + + // Check for completed id specification. + char *id_stop = (strchr(id, ']')); + if (id_stop != NULL) { + // Complete the item name. + if (*(id_stop + 1) == '.') { + list_lookup(el, section, id_stop + 2); + } + } else { + // Complete the section id. + id_lookup(el, id + 1, strlen(id + 1), cmd_desc, section, false); + } + + free(section); + } else { + // Check for item specification. + char *dot = (strchr(str, '.')); + if (dot != NULL) { + // Complete the item name. + char *section = strndup(str, dot - str); + list_lookup(el, section, dot + 1); + free(section); + } else { + // Complete the section name. + list_lookup(el, NULL, str); + } + } +} + +static unsigned char complete(EditLine *el, int ch) +{ + int argc, token, pos; + const char **argv; + + const LineInfo *li = el_line(el); + Tokenizer *tok = tok_init(NULL); + + // Parse the line. + int ret = tok_line(tok, li, &argc, &argv, &token, &pos); + if (ret != 0) { + goto complete_exit; + } + + // Show possible commands. + if (argc == 0) { + print_commands(); + goto complete_exit; + } + + // Complete the command name. + if (token == 0) { + cmds_lookup(el, argv[0], pos); + goto complete_exit; + } + + // Find the command descriptor. + const cmd_desc_t *desc = cmd_table; + while (desc->name != NULL && strcmp(desc->name, argv[0]) != 0) { + desc++; + } + if (desc->name == NULL) { + goto complete_exit; + } + + // Finish if a command with no or unsupported arguments. + if (desc->flags == CMD_FNONE || desc->flags == CMD_FREAD || + desc->flags == CMD_FWRITE) { + goto complete_exit; + } + + ret = set_config(desc, ¶ms); + if (ret != KNOT_EOK) { + goto complete_exit; + } + + // Complete the zone name. + if (desc->flags & (CMD_FREQ_ZONE | CMD_FOPT_ZONE)) { + if (token > 1 && !(desc->flags & CMD_FOPT_ZONE)) { + goto complete_exit; + } + + if (desc->flags & CMD_FREAD) { + local_zones_lookup(el, argv[token], pos); + } else { + id_lookup(el, argv[token], pos, desc, "zone", true); + } + goto complete_exit; + // Complete the section/id/item name. + } else if (desc->flags & (CMD_FOPT_ITEM | CMD_FREQ_ITEM)) { + if (token == 1) { + item_lookup(el, argv[1], desc); + } + goto complete_exit; + } + +complete_exit: + conf_update(NULL, CONF_UPD_FNONE); + tok_reset(tok); + tok_end(tok); + + return CC_REDISPLAY; +} + +static char *prompt(EditLine *el) +{ + return PROGRAM_NAME"> "; +} + +int interactive_loop(params_t *params) +{ + char *hist_file = NULL; + const char *home = getenv("HOME"); + if (home != NULL) { + hist_file = sprintf_alloc("%s/%s", home, HISTORY_FILE); + } + if (hist_file == NULL) { + log_notice("failed to get home directory"); + } + + EditLine *el = el_init(PROGRAM_NAME, stdin, stdout, stderr); + if (el == NULL) { + log_error("interactive mode not available"); + free(hist_file); + return KNOT_ERROR; + } + + History *hist = history_init(); + if (hist == NULL) { + log_error("interactive mode not available"); + el_end(el); + free(hist_file); + return KNOT_ERROR; + } + + HistEvent hev = { 0 }; + history(hist, &hev, H_SETSIZE, 100); + el_set(el, EL_HIST, history, hist); + history(hist, &hev, H_LOAD, hist_file); + + el_set(el, EL_TERMINAL, NULL); + el_set(el, EL_EDITOR, "emacs"); + el_set(el, EL_PROMPT, prompt); + el_set(el, EL_SIGNAL, 1); + el_source(el, NULL); + + el_set(el, EL_ADDFN, PROGRAM_NAME"-complete", + "Perform "PROGRAM_NAME" completion.", complete); + el_set(el, EL_BIND, "^I", PROGRAM_NAME"-complete", NULL); + + int count; + const char *line; + while ((line = el_gets(el, &count)) != NULL && count > 0) { + history(hist, &hev, H_ENTER, line); + + Tokenizer *tok = tok_init(NULL); + + // Tokenize the current line. + int argc; + const char **argv; + const LineInfo *li = el_line(el); + int ret = tok_line(tok, li, &argc, &argv, NULL, NULL); + if (ret != 0) { + continue; + } + + // Process the command. + ret = process_cmd(argc, argv, params); + + history(hist, &hev, H_SAVE, hist_file); + tok_reset(tok); + tok_end(tok); + + // Check for the exit command. + if (ret == KNOT_CTL_ESTOP) { + break; + } + } + + history_end(hist); + free(hist_file); + + el_end(el); + + return KNOT_EOK; +} diff --git a/src/utils/knotc/interactive.h b/src/utils/knotc/interactive.h new file mode 100644 index 0000000..6c7b3be --- /dev/null +++ b/src/utils/knotc/interactive.h @@ -0,0 +1,26 @@ +/* Copyright (C) 2018 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> + + 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 <http://www.gnu.org/licenses/>. +*/ + +#pragma once + +#include "utils/knotc/process.h" + +/*! + * Executes an interactive processing loop. + * + * \param[in] params Utility parameters. + */ +int interactive_loop(params_t *params); diff --git a/src/utils/knotc/main.c b/src/utils/knotc/main.c new file mode 100644 index 0000000..826c7bf --- /dev/null +++ b/src/utils/knotc/main.c @@ -0,0 +1,145 @@ +/* Copyright (C) 2019 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> + + 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 <http://www.gnu.org/licenses/>. +*/ + +#include <getopt.h> +#include <stdio.h> + +#include "contrib/strtonum.h" +#include "knot/common/log.h" +#include "utils/common/params.h" +#include "utils/knotc/commands.h" +#include "utils/knotc/interactive.h" +#include "utils/knotc/process.h" + +#define PROGRAM_NAME "knotc" +#define SPACE " " +#define DEFAULT_CTL_TIMEOUT 10 + +static void print_help(void) +{ + printf("Usage: %s [parameters] <action> [action_args]\n" + "\n" + "Parameters:\n" + " -c, --config <file> "SPACE"Use a textual configuration file.\n" + " "SPACE" (default %s)\n" + " -C, --confdb <dir> "SPACE"Use a binary configuration database directory.\n" + " "SPACE" (default %s)\n" + " -m, --max-conf-size <MiB>"SPACE"Set maximum configuration size (max 10000 MiB).\n" + " "SPACE" (default %d MiB)\n" + " -s, --socket <path> "SPACE"Use a control UNIX socket path.\n" + " "SPACE" (default %s)\n" + " -t, --timeout <sec> "SPACE"Use a control socket timeout (max 7200 seconds).\n" + " "SPACE" (default %u seconds)\n" + " -f, --force "SPACE"Forced operation. Overrides some checks.\n" + " -v, --verbose "SPACE"Enable debug output.\n" + " -h, --help "SPACE"Print the program help.\n" + " -V, --version "SPACE"Print the program version.\n", + PROGRAM_NAME, CONF_DEFAULT_FILE, CONF_DEFAULT_DBDIR, + CONF_MAPSIZE, RUN_DIR "/knot.sock", DEFAULT_CTL_TIMEOUT); + + print_commands(); +} + +params_t params = { + .max_conf_size = (size_t)CONF_MAPSIZE * 1024 * 1024, + .timeout = DEFAULT_CTL_TIMEOUT * 1000 +}; + +int main(int argc, char **argv) +{ + /* Long options. */ + struct option opts[] = { + { "config", required_argument, NULL, 'c' }, + { "confdb", required_argument, NULL, 'C' }, + { "max-conf-size", required_argument, NULL, 'm' }, + { "socket", required_argument, NULL, 's' }, + { "timeout", required_argument, NULL, 't' }, + { "force", no_argument, NULL, 'f' }, + { "verbose", no_argument, NULL, 'v' }, + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, 'V' }, + { NULL } + }; + + /* Parse command line arguments */ + int opt = 0; + while ((opt = getopt_long(argc, argv, "+c:C:m:s:t:fvhV", opts, NULL)) != -1) { + switch (opt) { + case 'c': + params.config = optarg; + break; + case 'C': + params.confdb = optarg; + break; + case 'm': + if (str_to_size(optarg, ¶ms.max_conf_size, 1, 10000) != KNOT_EOK) { + print_help(); + return EXIT_FAILURE; + } + /* Convert to bytes. */ + params.max_conf_size *= 1024 * 1024; + break; + case 's': + params.socket = optarg; + break; + case 't': + if (str_to_int(optarg, ¶ms.timeout, 0, 7200) != KNOT_EOK) { + print_help(); + return EXIT_FAILURE; + } + /* Convert to milliseconds. */ + params.timeout *= 1000; + break; + case 'f': + params.force = true; + break; + case 'v': + params.verbose = true; + break; + case 'h': + print_help(); + return EXIT_SUCCESS; + case 'V': + print_version(PROGRAM_NAME); + return EXIT_SUCCESS; + default: + print_help(); + return EXIT_FAILURE; + } + } + + /* Set up simplified logging just to stdout/stderr. */ + log_init(); + log_levels_set(LOG_TARGET_STDOUT, LOG_SOURCE_ANY, + LOG_MASK(LOG_INFO) | LOG_MASK(LOG_NOTICE)); + log_levels_set(LOG_TARGET_STDERR, LOG_SOURCE_ANY, LOG_UPTO(LOG_WARNING)); + log_levels_set(LOG_TARGET_SYSLOG, LOG_SOURCE_ANY, 0); + log_flag_set(LOG_FLAG_NOTIMESTAMP | LOG_FLAG_NOINFO); + if (params.verbose) { + log_levels_add(LOG_TARGET_STDOUT, LOG_SOURCE_ANY, LOG_MASK(LOG_DEBUG)); + } + + int ret; + if (argc - optind < 1) { + ret = interactive_loop(¶ms); + } else { + ret = process_cmd(argc - optind, (const char **)argv + optind, ¶ms); + } + + log_close(); + + return (ret == KNOT_EOK) ? EXIT_SUCCESS : EXIT_FAILURE; +} diff --git a/src/utils/knotc/process.c b/src/utils/knotc/process.c new file mode 100644 index 0000000..7f0502f --- /dev/null +++ b/src/utils/knotc/process.c @@ -0,0 +1,238 @@ +/* Copyright (C) 2018 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> + + 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 <http://www.gnu.org/licenses/>. +*/ + +#include <sys/stat.h> + +#include "knot/conf/conf.h" +#include "knot/common/log.h" +#include "utils/knotc/commands.h" +#include "utils/knotc/process.h" + +static const cmd_desc_t *get_cmd_desc(const char *command) +{ + /* Find requested command. */ + const cmd_desc_t *desc = cmd_table; + while (desc->name != NULL) { + if (strcmp(desc->name, command) == 0) { + break; + } + desc++; + } + if (desc->name == NULL) { + log_error("invalid command '%s'", command); + return NULL; + } + + return desc; +} + +int set_config(const cmd_desc_t *desc, params_t *params) +{ + if (params->config != NULL && params->confdb != NULL) { + log_error("ambiguous configuration source"); + return KNOT_EINVAL; + } + + /* Mask relevant flags. */ + cmd_flag_t flags = desc->flags & (CMD_FREAD | CMD_FWRITE); + cmd_flag_t mod_flags = desc->flags & (CMD_FOPT_MOD | CMD_FREQ_MOD); + + /* Choose the optimal config source. */ + struct stat st; + bool import = false; + if (flags == CMD_FNONE && params->socket != NULL) { + /* Control operation, known socket, skip configuration. */ + return KNOT_EOK; + } else if (params->confdb != NULL) { + import = false; + } else if (flags == CMD_FWRITE) { + import = false; + params->confdb = CONF_DEFAULT_DBDIR; + } else if (params->config != NULL){ + import = true; + } else if (stat(CONF_DEFAULT_DBDIR, &st) == 0) { + import = false; + params->confdb = CONF_DEFAULT_DBDIR; + } else if (stat(CONF_DEFAULT_FILE, &st) == 0) { + import = true; + params->config = CONF_DEFAULT_FILE; + } else if (flags != CMD_FNONE) { + log_error("no configuration source available"); + return KNOT_EINVAL; + } + + const char *src = import ? params->config : params->confdb; + log_debug("%s '%s'", import ? "config" : "confdb", + (src != NULL) ? src : "empty"); + + /* Prepare config flags. */ + conf_flag_t conf_flags = CONF_FNOHOSTNAME; + if (params->confdb != NULL && !(flags & CMD_FWRITE)) { + conf_flags |= CONF_FREADONLY; + } + if (import || mod_flags & CMD_FOPT_MOD) { + conf_flags |= CONF_FOPTMODULES; + } else if (mod_flags & CMD_FREQ_MOD) { + conf_flags |= CONF_FREQMODULES; + } + + /* Open confdb. */ + conf_t *new_conf = NULL; + int ret = conf_new(&new_conf, conf_schema, params->confdb, + params->max_conf_size, conf_flags); + if (ret != KNOT_EOK) { + log_error("failed to open configuration database '%s' (%s)", + (params->confdb != NULL) ? params->confdb : "", + knot_strerror(ret)); + return ret; + } + + /* Import the config file. */ + if (import) { + ret = conf_import(new_conf, params->config, true); + if (ret != KNOT_EOK) { + log_error("failed to load configuration file '%s' (%s)", + params->config, knot_strerror(ret)); + conf_free(new_conf); + return ret; + } + } + + /* Update to the new config. */ + conf_update(new_conf, CONF_UPD_FNONE); + + return KNOT_EOK; +} + +int set_ctl(knot_ctl_t **ctl, const cmd_desc_t *desc, params_t *params) +{ + if (desc == NULL) { + *ctl = NULL; + return KNOT_EINVAL; + } + + /* Mask relevant flags. */ + cmd_flag_t flags = desc->flags & (CMD_FREAD | CMD_FWRITE); + + /* Check if control socket is required. */ + if (flags != CMD_FNONE) { + *ctl = NULL; + return KNOT_EOK; + } + + /* Get control socket path. */ + char *path = NULL; + if (params->socket != NULL) { + path = strdup(params->socket); + } else { + conf_val_t listen_val = conf_get(conf(), C_CTL, C_LISTEN); + conf_val_t rundir_val = conf_get(conf(), C_SRV, C_RUNDIR); + char *rundir = conf_abs_path(&rundir_val, NULL); + path = conf_abs_path(&listen_val, rundir); + free(rundir); + } + if (path == NULL) { + log_error("empty control socket path"); + return KNOT_EINVAL; + } + + log_debug("socket '%s'", path); + + *ctl = knot_ctl_alloc(); + if (*ctl == NULL) { + free(path); + return KNOT_ENOMEM; + } + + knot_ctl_set_timeout(*ctl, params->timeout); + + int ret = knot_ctl_connect(*ctl, path); + if (ret != KNOT_EOK) { + knot_ctl_free(*ctl); + *ctl = NULL; + log_error("failed to connect to socket '%s' (%s)", path, + knot_strerror(ret)); + free(path); + return ret; + } + + free(path); + + return KNOT_EOK; +} + +void unset_ctl(knot_ctl_t *ctl) +{ + if (ctl == NULL) { + return; + } + + int ret = knot_ctl_send(ctl, KNOT_CTL_TYPE_END, NULL); + if (ret != KNOT_EOK && ret != KNOT_ECONN) { + log_error("failed to finish control (%s)", knot_strerror(ret)); + } + + knot_ctl_close(ctl); + knot_ctl_free(ctl); +} + +int process_cmd(int argc, const char **argv, params_t *params) +{ + if (argc == 0) { + return KNOT_ENOTSUP; + } + + /* Check the command name. */ + const cmd_desc_t *desc = get_cmd_desc(argv[0]); + if (desc == NULL) { + return KNOT_ENOENT; + } + + /* Check for exit. */ + if (desc->fcn == NULL) { + return KNOT_CTL_ESTOP; + } + + /* Set up the configuration. */ + int ret = set_config(desc, params); + if (ret != KNOT_EOK) { + return ret; + } + + /* Prepare command parameters. */ + cmd_args_t args = { + .desc = desc, + .argc = argc - 1, + .argv = argv + 1, + .force = params->force + }; + + /* Set control interface if necessary. */ + ret = set_ctl(&args.ctl, desc, params); + if (ret != KNOT_EOK) { + conf_update(NULL, CONF_UPD_FNONE); + return ret; + } + + /* Execute the command. */ + ret = desc->fcn(&args); + + /* Cleanup */ + unset_ctl(args.ctl); + conf_update(NULL, CONF_UPD_FNONE); + + return ret; +} diff --git a/src/utils/knotc/process.h b/src/utils/knotc/process.h new file mode 100644 index 0000000..20b5357 --- /dev/null +++ b/src/utils/knotc/process.h @@ -0,0 +1,69 @@ +/* Copyright (C) 2018 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> + + 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 <http://www.gnu.org/licenses/>. +*/ + +#pragma once + +#include "utils/knotc/commands.h" + +/*! Utility command line parameters. */ +typedef struct { + const char *config; + const char *confdb; + size_t max_conf_size; + const char *socket; + bool verbose; + bool force; + int timeout; +} params_t; + +/*! + * Prepares a proper configuration according to the specified command. + * + * \param[in] desc Utility command descriptor. + * \param[in] params Utility parameters. + * + * \return Error code, KNOT_EOK if successful. + */ +int set_config(const cmd_desc_t *desc, params_t *params); + +/*! + * Estabilishes a control interface if necessary. + * + * \param[in] ctl Control context. + * \param[in] desc Utility command descriptor. + * \param[in] params Utility parameters. + * + * \return Error code, KNOT_EOK if successful. + */ +int set_ctl(knot_ctl_t **ctl, const cmd_desc_t *desc, params_t *params); + +/*! + * Cleans up the control context. + * + * \param[in] ctl Control context. + */ +void unset_ctl(knot_ctl_t *ctl); + +/*! + * Processes the given utility command. + * + * \param[in] argc Number of command arguments. + * \param[in] argv Command arguments. + * \param[in] params Utility parameters. + * + * \return Error code, KNOT_EOK if successful. + */ +int process_cmd(int argc, const char **argv, params_t *params); |