From 96b10e167c79a61a9e89f8801862f16c6c6bbeae Mon Sep 17 00:00:00 2001 From: Daniel Salzman <daniel.salzman@nic.cz> Date: Fri, 30 Oct 2015 14:12:53 +0100 Subject: [PATCH] conf: add dynamic interface --- Knot.files | 2 + src/Makefile.am | 2 + src/knot/conf/confio.c | 1418 ++++++++++++++++++++++++++++++++ src/knot/conf/confio.h | 233 ++++++ src/knot/server/server.c | 7 + src/libknot/errcode.c | 9 +- src/libknot/internal/errcode.h | 3 + 7 files changed, 1671 insertions(+), 3 deletions(-) create mode 100644 src/knot/conf/confio.c create mode 100644 src/knot/conf/confio.h diff --git a/Knot.files b/Knot.files index 0e44226bf..b5fac11b2 100644 --- a/Knot.files +++ b/Knot.files @@ -197,6 +197,8 @@ src/knot/conf/conf.c src/knot/conf/conf.h src/knot/conf/confdb.c src/knot/conf/confdb.h +src/knot/conf/confio.c +src/knot/conf/confio.h src/knot/conf/scheme.c src/knot/conf/scheme.h src/knot/conf/tools.c diff --git a/src/Makefile.am b/src/Makefile.am index 32ae4275b..79a036937 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -213,6 +213,8 @@ libknotd_la_SOURCES = \ knot/conf/conf.h \ knot/conf/confdb.c \ knot/conf/confdb.h \ + knot/conf/confio.c \ + knot/conf/confio.h \ knot/conf/scheme.c \ knot/conf/scheme.h \ knot/conf/tools.c \ diff --git a/src/knot/conf/confio.c b/src/knot/conf/confio.c new file mode 100644 index 000000000..8d570b40b --- /dev/null +++ b/src/knot/conf/confio.c @@ -0,0 +1,1418 @@ +/* Copyright (C) 2015 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 <pthread.h> + +#include "knot/conf/confdb.h" +#include "knot/conf/confio.h" +#include "knot/conf/tools.h" +#include "libknot/yparser/yptrafo.h" +#include "libknot/internal/mem.h" +#include "libknot/internal/strlcat.h" + +#define FCN(io) (io->fcn != NULL) ? io->fcn(io) : KNOT_EOK + +static void io_reset_val( + conf_io_t *io, + const yp_item_t *key0, + const yp_item_t *key1, + const uint8_t *id, + size_t id_len, + bool id_as_data, + conf_val_t *val) +{ + io->key0 = key0; + io->key1 = key1; + io->id = id; + io->id_len = id_len; + io->id_as_data = id_as_data; + io->data.val = val; + io->data.bin = NULL; +} + +static void io_reset_bin( + conf_io_t *io, + const yp_item_t *key0, + const yp_item_t *key1, + const uint8_t *id, + size_t id_len, + const uint8_t *bin, + size_t bin_len) +{ + io_reset_val(io, key0, key1, id, id_len, false, NULL); + io->data.bin = bin; + io->data.bin_len = bin_len; +} + +int conf_io_begin( + bool child) +{ + assert(conf() != NULL); + + if (conf()->io.txn != NULL && !child) { + return KNOT_CONF_ETXN; + } else if (conf()->io.txn == NULL && child) { + return KNOT_CONF_ENOTXN; + } + + namedb_txn_t *parent = conf()->io.txn; + namedb_txn_t *txn = (parent == NULL) ? conf()->io.txn_stack : parent + 1; + if (txn >= conf()->io.txn_stack + CONF_MAX_TXN_DEPTH) { + return KNOT_CONF_EMANYTXN; + } + + // Start the writing transaction. + int ret = namedb_lmdb_txn_begin(conf()->db, txn, parent, 0); + if (ret != KNOT_EOK) { + return ret; + } + + conf()->io.txn = txn; + + return KNOT_EOK; +} + +int conf_io_commit( + bool child) +{ + assert(conf() != NULL); + + if (conf()->io.txn == NULL || + (child && conf()->io.txn == conf()->io.txn_stack)) { + return KNOT_CONF_ENOTXN; + } + + namedb_txn_t *txn = child ? conf()->io.txn : conf()->io.txn_stack; + + // Commit the writing transaction. + int ret = conf()->api->txn_commit(txn); + conf()->io.txn = child ? txn - 1 : NULL; + if (ret != KNOT_EOK) { + return ret; + } + + // Don't reload the configuration if child transaction. + if (child) { + return KNOT_EOK; + } + + // Clone new configuration. + conf_t *new_conf = NULL; + ret = conf_clone(&new_conf); + if (ret != KNOT_EOK) { + return ret; + } + + // Update read-only transaction. + new_conf->api->txn_abort(&new_conf->read_txn); + ret = new_conf->api->txn_begin(new_conf->db, &new_conf->read_txn, + NAMEDB_RDONLY); + if (ret != KNOT_EOK) { + conf_free(new_conf, true); + return ret; + } + + // Run post-open config operations. + ret = conf_post_open(new_conf); + if (ret != KNOT_EOK) { + conf_free(new_conf, true); + return ret; + } + + // Update to the new config. + conf_update(new_conf); + + return KNOT_EOK; +} + +int conf_io_abort( + bool child) +{ + assert(conf() != NULL); + + if (conf()->io.txn == NULL || + (child && conf()->io.txn == conf()->io.txn_stack)) { + return KNOT_CONF_ENOTXN; + } + + namedb_txn_t *txn = child ? conf()->io.txn : conf()->io.txn_stack; + + // Abort the writing transaction. + conf()->api->txn_abort(txn); + conf()->io.txn = child ? txn - 1 : NULL; + + return KNOT_EOK; +} + +static int desc_section( + const yp_item_t *items, + const yp_item_t **item, + conf_io_t *io) +{ + for (*item = items; (*item)->name != NULL; (*item)++) { + int ret = FCN(io); + if (ret != KNOT_EOK) { + return ret; + } + } + + return KNOT_EOK; +} + +int conf_io_desc( + const char *key0, + conf_io_t *io) +{ + if (io == NULL) { + return KNOT_EINVAL; + } + + assert(conf() != NULL); + + // List schema sections by default. + if (key0 == NULL) { + io_reset_val(io, NULL, NULL, NULL, 0, false, NULL); + + return desc_section(conf()->scheme, &io->key0, io); + } + + yp_check_ctx_t *ctx = yp_scheme_check_init(conf()->scheme); + if (ctx == NULL) { + return KNOT_ENOMEM; + } + + // Check the input. + int ret = yp_scheme_check_str(ctx, key0, NULL, NULL, NULL); + if (ret != KNOT_EOK) { + goto desc_error; + } + + yp_node_t *node = &ctx->nodes[ctx->current]; + + // Check for non-group item. + if (node->item->type != YP_TGRP) { + ret = KNOT_ENOTSUP; + goto desc_error; + } + + io_reset_val(io, node->item, NULL, NULL, 0, false, NULL); + + ret = desc_section(node->item->sub_items, &io->key1, io); +desc_error: + yp_scheme_check_deinit(ctx); + + return ret; +} + +static int diff_item( + conf_io_t *io) +{ + // Process an identifier item. + if ((io->key0->flags & YP_FMULTI) != 0 && io->key0->var.g.id == io->key1) { + bool old_id, new_id; + + // Check if a removed identifier. + int ret = conf_db_get(conf(), &conf()->read_txn, io->key0->name, + NULL, io->id, io->id_len, NULL); + switch (ret) { + case KNOT_EOK: + old_id = true; + break; + case KNOT_ENOENT: + case KNOT_YP_EINVAL_ID: + old_id = false; + break; + default: + return ret; + } + + // Check if an added identifier. + ret = conf_db_get(conf(), conf()->io.txn, io->key0->name, NULL, + io->id, io->id_len, NULL); + switch (ret) { + case KNOT_EOK: + new_id = true; + break; + case KNOT_ENOENT: + case KNOT_YP_EINVAL_ID: + new_id = false; + break; + default: + return ret; + } + + // Check if valid identifier. + if (!old_id && !new_id) { + return KNOT_YP_EINVAL_ID; + } + + if (old_id != new_id) { + io->id_as_data = true; + io->type = old_id ? OLD : NEW; + + // Process the callback. + ret = FCN(io); + + // Reset the modified parameters. + io->id_as_data = false; + io->type = NONE; + + if (ret != KNOT_EOK) { + return ret; + } + } + + return KNOT_EOK; + } + + conf_val_t old_val, new_val; + + // Get the old item value. + conf_db_get(conf(), &conf()->read_txn, io->key0->name, io->key1->name, + io->id, io->id_len, &old_val); + switch (old_val.code) { + case KNOT_EOK: + break; + case KNOT_ENOENT: + case KNOT_YP_EINVAL_ID: + break; + default: + return old_val.code; + } + + // Get the new item value. + conf_db_get(conf(), conf()->io.txn, io->key0->name, io->key1->name, + io->id, io->id_len, &new_val); + switch (new_val.code) { + case KNOT_EOK: + break; + case KNOT_ENOENT: + case KNOT_YP_EINVAL_ID: + if (old_val.code != KNOT_EOK) { + return KNOT_EOK; + } + break; + default: + return new_val.code; + } + + // Process the value difference. + if (old_val.code != KNOT_EOK) { + io->data.val = &new_val; + io->type = NEW; + int ret = FCN(io); + if (ret != KNOT_EOK) { + return ret; + } + } else if (new_val.code != KNOT_EOK) { + io->data.val = &old_val; + io->type = OLD; + int ret = FCN(io); + if (ret != KNOT_EOK) { + return ret; + } + } else if (!conf_val_equal(&old_val, &new_val)) { + io->data.val = &old_val; + io->type = OLD; + int ret = FCN(io); + if (ret != KNOT_EOK) { + return ret; + } + + io->data.val = &new_val; + io->type = NEW; + ret = FCN(io); + if (ret != KNOT_EOK) { + return ret; + } + } + + // Reset the modified parameters. + io->data.val = NULL; + io->type = NONE; + + return KNOT_EOK; +} + +static int diff_section( + conf_io_t *io) +{ + // Get the value for the specified item. + if (io->key1 != NULL) { + return diff_item(io); + } + + // Get the values for all items. + for (yp_item_t *i = io->key0->sub_items; i->name != NULL; i++) { + io->key1 = i; + + int ret = diff_item(io); + + // Reset the modified parameters. + io->key1 = NULL; + + if (ret != KNOT_EOK) { + return ret; + } + } + + return KNOT_EOK; +} + +int conf_io_diff( + const char *key0, + const char *key1, + const char *id, + conf_io_t *io) +{ + if (io == NULL) { + return KNOT_EINVAL; + } + + assert(conf() != NULL); + + if (conf()->io.txn == NULL) { + return KNOT_CONF_ENOTXN; + } + + // Compare all sections by default. + if (key0 == NULL) { + for (yp_item_t *i = conf()->scheme; i->name != NULL; i++) { + // Skip non-group item. + if (i->type != YP_TGRP) { + continue; + } + + int ret = conf_io_diff(i->name + 1, key1, NULL, io); + + // Reset parameters after each section. + io_reset_val(io, NULL, NULL, NULL, 0, false, NULL); + + if (ret != KNOT_EOK) { + return ret; + } + } + + return KNOT_EOK; + } + + yp_check_ctx_t *ctx = yp_scheme_check_init(conf()->scheme); + if (ctx == NULL) { + return KNOT_ENOMEM; + } + + // Check the input. + int ret = yp_scheme_check_str(ctx, key0, key1, id, NULL); + if (ret != KNOT_EOK) { + goto diff_error; + } + + yp_node_t *node = &ctx->nodes[ctx->current]; + yp_node_t *parent = node->parent; + + // Key1 is not a group identifier. + if (parent != NULL) { + io_reset_val(io, parent->item, node->item, parent->id, + parent->id_len, false, NULL); + // Key1 is a group identifier. + } else if (key1 != NULL && strlen(key1) != 0) { + assert(node->item->type == YP_TGRP && + (node->item->flags & YP_FMULTI) != 0); + + io_reset_val(io, node->item, node->item->var.g.id, node->id, + node->id_len, true, NULL); + // No key1 specified. + } else { + io_reset_val(io, node->item, NULL, node->id, node->id_len, + false, NULL); + } + + // Check for a non-group item. + if (io->key0->type != YP_TGRP) { + ret = KNOT_ENOTSUP; + goto diff_error; + } + + // Compare the section with all identifiers by default. + if ((io->key0->flags & YP_FMULTI) != 0 && io->id_len == 0) { + // First compare the section with the old and common identifiers. + conf_iter_t iter; + ret = conf_db_iter_begin(conf(), &conf()->read_txn, + io->key0->name, &iter); + switch (ret) { + case KNOT_EOK: + case KNOT_ENOENT: + break; + default: + goto diff_error; + } + + while (ret == KNOT_EOK) { + // Set the section identifier. + ret = conf_db_iter_id(conf(), &iter, &io->id, &io->id_len); + if (ret != KNOT_EOK) { + conf_db_iter_finish(conf(), &iter); + return ret; + } + + ret = diff_section(io); + if (ret != KNOT_EOK) { + conf_db_iter_finish(conf(), &iter); + return ret; + } + + ret = conf_db_iter_next(conf(), &iter); + } + + // Second compare the section with the new identifiers. + ret = conf_db_iter_begin(conf(), conf()->io.txn, io->key0->name, + &iter); + switch (ret) { + case KNOT_EOK: + case KNOT_ENOENT: + break; + default: + goto diff_error; + } + + while (ret == KNOT_EOK) { + // Set the section identifier. + ret = conf_db_iter_id(conf(), &iter, &io->id, &io->id_len); + if (ret != KNOT_EOK) { + conf_db_iter_finish(conf(), &iter); + return ret; + } + + // Ignore old and common identifiers. + ret = conf_db_get(conf(), &conf()->read_txn, io->key0->name, + NULL, io->id, io->id_len, NULL); + switch (ret) { + case KNOT_EOK: + ret = conf_db_iter_next(conf(), &iter); + continue; + case KNOT_ENOENT: + case KNOT_YP_EINVAL_ID: + break; + default: + conf_db_iter_finish(conf(), &iter); + return ret; + } + + ret = diff_section(io); + if (ret != KNOT_EOK) { + conf_db_iter_finish(conf(), &iter); + return ret; + } + + ret = conf_db_iter_next(conf(), &iter); + } + + ret = KNOT_EOK; + goto diff_error; + } + + // Compare the section with a possible identifier. + ret = diff_section(io); +diff_error: + yp_scheme_check_deinit(ctx); + + return ret; +} + +static int get_section( + namedb_txn_t *txn, + conf_io_t *io) +{ + conf_val_t data; + + // Get the value for the specified item. + if (io->key1 != NULL) { + if (!io->id_as_data) { + // Get the item value. + conf_db_get(conf(), txn, io->key0->name, io->key1->name, + io->id, io->id_len, &data); + switch (data.code) { + case KNOT_EOK: + break; + case KNOT_ENOENT: + return KNOT_EOK; + default: + return data.code; + } + + io->data.val = &data; + } + + // Process the callback. + int ret = FCN(io); + + // Reset the modified parameters. + io->data.val = NULL; + + return ret; + } + + // Get the values for all section items by default. + for (yp_item_t *i = io->key0->sub_items; i->name != NULL; i++) { + // Process the (first) identifier item. + if ((io->key0->flags & YP_FMULTI) != 0 && io->key0->var.g.id == i) { + // Check if existing identifier. + conf_db_get(conf(), txn, io->key0->name, NULL, io->id, + io->id_len, &data); + switch (data.code) { + case KNOT_EOK: + break; + case KNOT_ENOENT: + continue; + default: + return data.code; + } + + io->key1 = i; + io->id_as_data = true; + + // Process the callback. + int ret = FCN(io); + + // Reset the modified parameters. + io->key1 = NULL; + io->id_as_data = false; + + if (ret != KNOT_EOK) { + return ret; + } + + continue; + } + + // Get the item value. + conf_db_get(conf(), txn, io->key0->name, i->name, io->id, + io->id_len, &data); + switch (data.code) { + case KNOT_EOK: + break; + case KNOT_ENOENT: + continue; + default: + return data.code; + } + + io->key1 = i; + io->data.val = &data; + + // Process the callback. + int ret = FCN(io); + + // Reset the modified parameters. + io->key1 = NULL; + io->data.val = NULL; + + if (ret != KNOT_EOK) { + return ret; + } + } + + return KNOT_EOK; +} + +int conf_io_get( + const char *key0, + const char *key1, + const char *id, + bool get_current, + conf_io_t *io) +{ + if (io == NULL) { + return KNOT_EINVAL; + } + + assert(conf() != NULL); + + if (conf()->io.txn == NULL && !get_current) { + return KNOT_CONF_ENOTXN; + } + + // List all sections by default. + if (key0 == NULL) { + for (yp_item_t *i = conf()->scheme; i->name != NULL; i++) { + // Skip non-group item. + if (i->type != YP_TGRP) { + continue; + } + + int ret = conf_io_get(i->name + 1, key1, NULL, + get_current, io); + // Reset parameters after each section. + io_reset_val(io, NULL, NULL, NULL, 0, false, NULL); + if (ret != KNOT_EOK) { + return ret; + } + } + + return KNOT_EOK; + } + + yp_check_ctx_t *ctx = yp_scheme_check_init(conf()->scheme); + if (ctx == NULL) { + return KNOT_ENOMEM; + } + + // Check the input. + int ret = yp_scheme_check_str(ctx, key0, key1, id, NULL); + if (ret != KNOT_EOK) { + goto get_error; + } + + yp_node_t *node = &ctx->nodes[ctx->current]; + yp_node_t *parent = node->parent; + + // Key1 is not a group identifier. + if (parent != NULL) { + io_reset_val(io, parent->item, node->item, parent->id, + parent->id_len, false, NULL); + // Key1 is a group identifier. + } else if (key1 != NULL && strlen(key1) != 0) { + assert(node->item->type == YP_TGRP && + (node->item->flags & YP_FMULTI) != 0); + + io_reset_val(io, node->item, node->item->var.g.id, node->id, + node->id_len, true, NULL); + // No key1 specified. + } else { + io_reset_val(io, node->item, NULL, node->id, node->id_len, false, + NULL); + } + + namedb_txn_t *txn = get_current ? &conf()->read_txn : conf()->io.txn; + + // Check for a non-group item. + if (io->key0->type != YP_TGRP) { + ret = KNOT_ENOTSUP; + goto get_error; + } + + // List the section with all identifiers by default. + if ((io->key0->flags & YP_FMULTI) != 0 && io->id_len == 0) { + conf_iter_t iter; + ret = conf_db_iter_begin(conf(), txn, io->key0->name, &iter); + switch (ret) { + case KNOT_EOK: + break; + case KNOT_ENOENT: + ret = KNOT_EOK; + goto get_error; + default: + goto get_error; + } + + while (ret == KNOT_EOK) { + // Set the section identifier. + ret = conf_db_iter_id(conf(), &iter, &io->id, &io->id_len); + if (ret != KNOT_EOK) { + conf_db_iter_finish(conf(), &iter); + goto get_error; + } + + ret = get_section(txn, io); + if (ret != KNOT_EOK) { + conf_db_iter_finish(conf(), &iter); + goto get_error; + } + + ret = conf_db_iter_next(conf(), &iter); + } + + ret = KNOT_EOK; + goto get_error; + } + + // List the section with a possible identifier. + ret = get_section(txn, io); +get_error: + yp_scheme_check_deinit(ctx); + + return ret; +} + +static int set_item( + conf_io_t *io) +{ + int ret = conf_db_set(conf(), conf()->io.txn, io->key0->name, + (io->key1 != NULL) ? io->key1->name : NULL, + io->id, io->id_len, io->data.bin, io->data.bin_len); + if (ret != KNOT_EOK) { + return ret; + } + + // Postpone group callbacks to config check. + if (io->key0->type != YP_TGRP) { + conf_check_t args = { + .conf = conf(), + .txn = conf()->io.txn, + .item = (io->key1 != NULL) ? io->key1 : io->key0, + .id = io->id, + .id_len = io->id_len, + .data = io->data.bin, + .data_len = io->data.bin_len + }; + + // Call the item callbacks. + io->error.code = conf_exec_callbacks(args.item, &args); + if (io->error.code != KNOT_EOK) { + io->error.str = args.err_str; + ret = FCN(io); + if (ret == KNOT_EOK) { + ret = io->error.code; + } + } + } + + return ret; +} + +int conf_io_set( + const char *key0, + const char *key1, + const char *id, + const char *data, + conf_io_t *io) +{ + if (io == NULL) { + return KNOT_EINVAL; + } + + assert(conf() != NULL); + + if (conf()->io.txn == NULL) { + return KNOT_CONF_ENOTXN; + } + + // At least key0 must be specified. + if (key0 == NULL) { + return KNOT_EINVAL; + } + + yp_check_ctx_t *ctx = yp_scheme_check_init(conf()->scheme); + if (ctx == NULL) { + return KNOT_ENOMEM; + } + + // Check the input. + int ret = yp_scheme_check_str(ctx, key0, key1, id, data); + if (ret != KNOT_EOK) { + goto set_error; + } + + yp_node_t *node = &ctx->nodes[ctx->current]; + yp_node_t *parent = node->parent; + + // Key1 is not a group identifier. + if (parent != NULL) { + io_reset_bin(io, parent->item, node->item, parent->id, + parent->id_len, node->data, node->data_len); + // No key1 but a group identifier. + } else if (node->id_len != 0) { + assert(node->item->type == YP_TGRP && + (node->item->flags & YP_FMULTI) != 0); + assert(node->data_len == 0); + + io_reset_bin(io, node->item, node->item->var.g.id, node->id, + node->id_len, NULL, 0); + // Ensure some data for non-group items (include). + } else if (node->item->type == YP_TGRP || node->data_len != 0) { + io_reset_bin(io, node->item, NULL, node->id, node->id_len, + node->data, node->data_len); + // Non-group without data. + } else { + ret = KNOT_YP_ENODATA; + goto set_error; + } + + // Set the item for all identifiers by default. + if (io->key0->type == YP_TGRP && io->key1 != NULL && + (io->key0->flags & YP_FMULTI) != 0 && io->id_len == 0) { + conf_iter_t iter; + ret = conf_db_iter_begin(conf(), conf()->io.txn, io->key0->name, + &iter); + switch (ret) { + case KNOT_EOK: + break; + case KNOT_ENOENT: + ret = KNOT_EOK; + goto set_error; + default: + goto set_error; + } + + while (ret == KNOT_EOK) { + // Get the identifier. + ret = conf_db_iter_id(conf(), &iter, &io->id, &io->id_len); + if (ret != KNOT_EOK) { + conf_db_iter_finish(conf(), &iter); + goto set_error; + } + + // Set the data. + ret = set_item(io); + if (ret != KNOT_EOK) { + conf_db_iter_finish(conf(), &iter); + goto set_error; + } + + ret = conf_db_iter_next(conf(), &iter); + } + if (ret != KNOT_EOF) { + goto set_error; + } + + ret = KNOT_EOK; + goto set_error; + } + + // Set the item with a possible identifier. + ret = set_item(io); +set_error: + yp_scheme_check_deinit(ctx); + + return ret; +} + +static int unset_section_data( + conf_io_t *io) +{ + // Delete the value for the specified item. + if (io->key1 != NULL) { + return conf_db_unset(conf(), conf()->io.txn, io->key0->name, + io->key1->name, io->id, io->id_len, + io->data.bin, io->data.bin_len, false); + } + + // Delete the whole section by default. + for (yp_item_t *i = io->key0->sub_items; i->name != NULL; i++) { + // Skip the identifier item. + if ((io->key0->flags & YP_FMULTI) != 0 && io->key0->var.g.id == i) { + continue; + } + + int ret = conf_db_unset(conf(), conf()->io.txn, io->key0->name, + i->name, io->id, io->id_len, io->data.bin, + io->data.bin_len, false); + switch (ret) { + case KNOT_EOK: + case KNOT_ENOENT: + continue; + default: + return ret; + } + } + + return KNOT_EOK; +} + +static int unset_section( + const yp_item_t *key0) +{ + // Delete the section items. + for (yp_item_t *i = key0->sub_items; i->name != NULL; i++) { + // Skip the identifier item. + if ((key0->flags & YP_FMULTI) != 0 && key0->var.g.id == i) { + continue; + } + + int ret = conf_db_unset(conf(), conf()->io.txn, key0->name, + i->name, NULL, 0, NULL, 0, true); + switch (ret) { + case KNOT_EOK: + case KNOT_ENOENT: + continue; + default: + return ret; + } + } + + // Delete the section. + int ret = conf_db_unset(conf(), conf()->io.txn, key0->name, NULL, NULL, + 0, NULL, 0, false); + switch (ret) { + case KNOT_EOK: + case KNOT_ENOENT: + return KNOT_EOK; + default: + return ret; + } +} + +int conf_io_unset( + const char *key0, + const char *key1, + const char *id, + const char *data) +{ + assert(conf() != NULL); + + if (conf()->io.txn == NULL) { + return KNOT_CONF_ENOTXN; + } + + // Delete all sections by default. + if (key0 == NULL) { + for (yp_item_t *i = conf()->scheme; i->name != NULL; i++) { + // Skip non-group item. + if (i->type != YP_TGRP) { + continue; + } + + int ret = conf_io_unset(i->name + 1, key1, NULL, NULL); + switch (ret) { + case KNOT_EOK: + case KNOT_ENOENT: + break; + default: + return ret; + } + } + + return KNOT_EOK; + } + + yp_check_ctx_t *ctx = yp_scheme_check_init(conf()->scheme); + if (ctx == NULL) { + return KNOT_ENOMEM; + } + + // Check the input. + int ret = yp_scheme_check_str(ctx, key0, key1, id, data); + if (ret != KNOT_EOK) { + goto unset_error; + } + + yp_node_t *node = &ctx->nodes[ctx->current]; + yp_node_t *parent = node->parent; + + conf_io_t io = { NULL }; + + if (parent != NULL) { + io_reset_bin(&io, parent->item, node->item, parent->id, + parent->id_len, node->data, node->data_len); + } else { + io_reset_bin(&io, node->item, NULL, node->id, node->id_len, + node->data, node->data_len); + } + + // Check for a non-group item. + if (io.key0->type != YP_TGRP) { + ret = KNOT_ENOTSUP; + goto unset_error; + } + + // Delete the section with all identifiers by default. + if ((io.key0->flags & YP_FMULTI) != 0 && io.id_len == 0) { + conf_iter_t iter; + ret = conf_db_iter_begin(conf(), conf()->io.txn, io.key0->name, + &iter); + switch (ret) { + case KNOT_EOK: + break; + case KNOT_ENOENT: + ret = KNOT_EOK; + goto unset_error; + default: + goto unset_error; + } + + while (ret == KNOT_EOK) { + // Get the identifier. + ret = conf_db_iter_id(conf(), &iter, &io.id, &io.id_len); + if (ret != KNOT_EOK) { + conf_db_iter_finish(conf(), &iter); + goto unset_error; + } + + // Delete the section data. + ret = unset_section_data(&io); + switch (ret) { + case KNOT_EOK: + case KNOT_ENOENT: + break; + default: + conf_db_iter_finish(conf(), &iter); + goto unset_error; + } + + ret = conf_db_iter_next(conf(), &iter); + } + if (ret != KNOT_EOF) { + goto unset_error; + } + + if (io.key1 == NULL) { + // Delete all identifiers. + ret = conf_db_iter_begin(conf(), conf()->io.txn, + io.key0->name, &iter); + switch (ret) { + case KNOT_EOK: + break; + case KNOT_ENOENT: + ret = KNOT_EOK; + goto unset_error; + default: + goto unset_error; + } + + while (ret == KNOT_EOK) { + ret = conf_db_iter_del(conf(), &iter); + if (ret != KNOT_EOK) { + conf_db_iter_finish(conf(), &iter); + goto unset_error; + } + + ret = conf_db_iter_next(conf(), &iter); + } + if (ret != KNOT_EOF) { + goto unset_error; + } + + // Delete the section. + ret = unset_section(io.key0); + if (ret != KNOT_EOK) { + goto unset_error; + } + } + + ret = KNOT_EOK; + goto unset_error; + } + + // Delete the section data. + ret = unset_section_data(&io); + if (ret != KNOT_EOK) { + goto unset_error; + } + + if (io.key1 == NULL) { + // Delete the identifier. + if (io.id_len != 0) { + ret = conf_db_unset(conf(), conf()->io.txn, io.key0->name, + NULL, io.id, io.id_len, NULL, 0, false); + if (ret != KNOT_EOK) { + goto unset_error; + } + // Delete the section. + } else { + ret = unset_section(io.key0); + if (ret != KNOT_EOK) { + goto unset_error; + } + } + } +unset_error: + yp_scheme_check_deinit(ctx); + + return ret; +} + +static int check_section( + const yp_item_t *group, + const uint8_t *id, + size_t id_len, + conf_io_t *io) +{ + conf_check_t args = { + .conf = conf(), + .txn = conf()->io.txn, + .id = id, + .id_len = id_len + }; + + bool non_empty = false; + + for (yp_item_t *item = group->sub_items; item->name != NULL; item++) { + args.item = item; + + // Check the identifier. + if ((group->flags & YP_FMULTI) != 0 && group->var.g.id == item) { + io->error.code = conf_exec_callbacks(item, &args); + if (io->error.code != KNOT_EOK) { + io_reset_val(io, group, item, NULL, 0, false, NULL); + goto check_section_error; + } + continue; + } + + // Get the item value. + conf_val_t bin; + conf_db_get(conf(), conf()->io.txn, group->name, item->name, id, + id_len, &bin); + if (bin.code == KNOT_ENOENT) { + continue; + } else if (bin.code != KNOT_EOK) { + return bin.code; + } + + non_empty = true; + + // Check the item value(s). + size_t values = conf_val_count(&bin); + for (size_t i = 1; i <= values; i++) { + conf_val(&bin); + args.data = bin.data; + args.data_len = bin.len; + + io->error.code = conf_exec_callbacks(item, &args); + if (io->error.code != KNOT_EOK) { + io_reset_val(io, group, item, id, id_len, false, + &bin); + io->data.index = i; + goto check_section_error; + } + + if (values > 1) { + conf_val_next(&bin); + } + } + } + + // Check the whole section if not empty. + if (id != NULL || non_empty) { + args.item = group; + args.data = NULL; + args.data_len = 0; + + io->error.code = conf_exec_callbacks(group, &args); + if (io->error.code != KNOT_EOK) { + io_reset_val(io, group, NULL, id, id_len, false, NULL); + goto check_section_error; + } + } + + return KNOT_EOK; + +check_section_error: + io->error.str = args.err_str; + int ret = FCN(io); + if (ret == KNOT_EOK) { + return io->error.code; + } + return ret; +} + +int conf_io_check( + conf_io_t *io) +{ + if (io == NULL) { + return KNOT_EINVAL; + } + + assert(conf() != NULL); + + if (conf()->io.txn == NULL) { + return KNOT_CONF_ENOTXN; + } + + int ret; + + // Iterate over the scheme. + for (yp_item_t *item = conf()->scheme; item->name != NULL; item++) { + // Skip non-group items (include). + if (item->type != YP_TGRP) { + continue; + } + + // Check simple group without identifiers. + if ((item->flags & YP_FMULTI) == 0) { + ret = check_section(item, NULL, 0, io); + if (ret != KNOT_EOK) { + goto check_error; + } + + continue; + } + + // Iterate over all identifiers. + conf_iter_t iter; + ret = conf_db_iter_begin(conf(), conf()->io.txn, item->name, &iter); + switch (ret) { + case KNOT_EOK: + break; + case KNOT_ENOENT: + continue; + default: + goto check_error; + } + + while (ret == KNOT_EOK) { + const uint8_t *id; + size_t id_len; + ret = conf_db_iter_id(conf(), &iter, &id, &id_len); + if (ret != KNOT_EOK) { + conf_db_iter_finish(conf(), &iter); + goto check_error; + } + + // Check group with identifiers. + ret = check_section(item, id, id_len, io); + if (ret != KNOT_EOK) { + conf_db_iter_finish(conf(), &iter); + goto check_error; + } + + ret = conf_db_iter_next(conf(), &iter); + } + if (ret != KNOT_EOF) { + goto check_error; + } + } + + ret = KNOT_EOK; +check_error: + + return ret; +} + +char *conf_io_txt_key( + conf_io_t *io) +{ + if (io == NULL || io->key0 == NULL) { + return NULL; + } + + char id[KNOT_DNAME_TXT_MAXLEN + 1] = "\0"; + size_t id_len = sizeof(id); + + // Get the textual item id. + if (io->id_len > 0 && !io->id_as_data) { + if (yp_item_to_txt(io->key0->var.g.id, io->id, io->id_len, id, + &id_len, YP_SNOQUOTE) != KNOT_EOK) { + return NULL; + } + } + + // Get the item prefix. + const char *prefix = ""; + switch (io->type) { + case NEW: + prefix = "+"; + break; + case OLD: + prefix = "-"; + break; + default: + break; + } + + // Format the item key. + return sprintf_alloc( + "%s%.*s%s%.*s%s%s%.*s", + prefix, (int)io->key0->name[0], io->key0->name + 1, + (io->id_len > 0 && !io->id_as_data ? "[" : ""), + (io->id_len > 0 && !io->id_as_data ? (int)id_len : 0), id, + (io->id_len > 0 && !io->id_as_data ? "]" : ""), + (io->key1 != NULL ? "." : ""), + (io->key1 != NULL ? (int)io->key1->name[0] : 0), + (io->key1 != NULL ? io->key1->name + 1 : "")); +} + +static int append_data( + const yp_item_t *item, + const uint8_t *bin, + size_t bin_len, + char *out, + size_t out_len) +{ + char buf[YP_MAX_TXT_DATA_LEN + 1] = "\0"; + size_t buf_len = sizeof(buf); + + int ret = yp_item_to_txt(item, bin, bin_len, buf, &buf_len, YP_SNONE); + if (ret != KNOT_EOK) { + return ret; + } + + if (strlcat(out, buf, out_len) >= out_len) { + return KNOT_ESPACE; + } + + return KNOT_EOK; +} + +char *conf_io_txt_data( + conf_io_t *io) +{ + if (io == NULL || io->key0 == NULL) { + return NULL; + } + + char out[YP_MAX_TXT_DATA_LEN + 1] = "\0"; + + // Return the item identifier as the item data. + if (io->id_as_data) { + if (append_data(io->key0->var.g.id, io->id, io->id_len, out, + sizeof(out)) != KNOT_EOK) { + return NULL; + } + + return strdup(out); + } + + // Check for no data. + if (io->data.val == NULL && io->data.bin == NULL) { + return NULL; + } + + const yp_item_t *item = (io->key1 != NULL) ? io->key1 : io->key0; + + // Format explicit binary data value. + if (io->data.bin != NULL) { + if (append_data(item, io->data.bin, io->data.bin_len, out, + sizeof(out)) != KNOT_EOK) { + return NULL; + } + // Format multivalued item data. + } else if (item->flags & YP_FMULTI) { + size_t values = conf_val_count(io->data.val); + for (size_t i = 0; i < values; i++) { + // Skip other values if known index (counted from 1). + if (io->data.index > 0 && + io->data.index != i + 1) { + conf_val_next(io->data.val); + continue; + } + + if (i > 0) { + if (strlcat(out, " ", sizeof(out)) >= sizeof(out)) { + return NULL; + } + } + + conf_val(io->data.val); + if (append_data(item, io->data.val->data, io->data.val->len, + out, sizeof(out)) != KNOT_EOK) { + return NULL; + } + + conf_val_next(io->data.val); + } + // Format singlevalued item data. + } else { + conf_val(io->data.val); + if (append_data(item, io->data.val->data, io->data.val->len, out, + sizeof(out)) != KNOT_EOK) { + return NULL; + } + } + + return strdup(out); +} diff --git a/src/knot/conf/confio.h b/src/knot/conf/confio.h new file mode 100644 index 000000000..1d61ddfb6 --- /dev/null +++ b/src/knot/conf/confio.h @@ -0,0 +1,233 @@ +/* Copyright (C) 2015 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/>. +*/ +/*! + * \file + * + * Configuration interface. + * + * \addtogroup config + * + * @{ + */ + +#pragma once + +#include "knot/conf/conf.h" + +/*! Configuration interface output. */ +typedef struct conf_io conf_io_t; +struct conf_io { + /*! Section. */ + const yp_item_t *key0; + /*! Section item. */ + const yp_item_t *key1; + /*! Section identifier. */ + const uint8_t *id; + /*! Section identifier length. */ + size_t id_len; + /*! Consider item identifier as item data. */ + bool id_as_data; + + enum { + /*! Default item state. */ + NONE, + /*! New item indicator. */ + NEW, + /*! Old item indicator. */ + OLD + } type; + + struct { + /*! Section item data (NULL if not used). */ + conf_val_t *val; + /*! Index of data value to format (counted from 1, 0 means all). */ + size_t index; + /*! Binary data value (NULL if not used). */ + const uint8_t *bin; + /*! Length of the binary data value. */ + size_t bin_len; + } data; + + struct { + /*! Edit operation return code. */ + int code; + /*! Edit operation return error message. */ + const char *str; + } error; + + /*! Optional processing callback. */ + int (*fcn)(conf_io_t *); + /*! Miscellaneous data useful for the callback. */ + void *misc; +}; + +/*! + * Starts new writing transaction. + * + * \param[in] child Nested transaction indicator. + * + * \return Error code, KNOT_EOK if success. + */ +int conf_io_begin( + bool child +); + +/*! + * Commits the current writing transaction. + * + * \param[in] child Nested transaction indicator. + * + * \return Error code, KNOT_EOK if success. + */ +int conf_io_commit( + bool child +); + +/*! + * Aborts the current writing transaction. + * + * \param[in] child Nested transaction indicator. + * + * \return Error code, KNOT_EOK if success. + */ +int conf_io_abort( + bool child +); + +/*! + * Gets the configuration group item subsection list. + * + * \param[in] key0 Section name (NULL to get section list). + * \param[out] io Operation output. + * + * \return Error code, KNOT_EOK if success. + */ +int conf_io_desc( + const char *key0, + conf_io_t *io +); + +/*! + * Gets the configuration difference between the current configuration and + * the active transaction. + * + * \param[in] key0 Section name (NULL to diff all sections). + * \param[in] key1 Item name (NULL to diff all section items). + * \param[in] id Section identifier name (NULL to consider all section identifiers). + * \param[out] io Operation output. + * + * \return Error code, KNOT_EOK if success. + */ +int conf_io_diff( + const char *key0, + const char *key1, + const char *id, + conf_io_t *io +); + +/*! + * Gets the configuration item(s) value(s). + * + * \param[in] key0 Section name (NULL to get all sections). + * \param[in] key1 Item name (NULL to get all section items). + * \param[in] id Section identifier name (NULL to consider all section identifiers). + * \param[in] get_current The current configuration or the active transaction switch. + * \param[out] io Operation output. + * + * \return Error code, KNOT_EOK if success. + */ +int conf_io_get( + const char *key0, + const char *key1, + const char *id, + bool get_current, + conf_io_t *io +); + +/*! + * Sets the configuration item(s) value. + * + * \param[in] key0 Section name. + * \param[in] key1 Item name (NULL to add identifier only). + * \param[in] id Section identifier name (NULL to consider all section identifiers). + * \param[in] data Item data to set/add. + * \param[out] io Operation output (callback error output). + * + * \return Error code, KNOT_EOK if success. + */ +int conf_io_set( + const char *key0, + const char *key1, + const char *id, + const char *data, + conf_io_t *io +); + +/*! + * Unsets the configuration item(s) value(s). + * + * \param[in] key0 Section name (NULL to unset all sections). + * \param[in] key1 Item name (NULL to unset the whole section). + * \param[in] id Section identifier name (NULL to consider all section identifiers). + * \param[in] data Item data (NULL to unset all data). + * + * \return Error code, KNOT_EOK if success. + */ +int conf_io_unset( + const char *key0, + const char *key1, + const char *id, + const char *data +); + +/*! + * Checks the configuration database semantics in the current writing transaction. + * + * \param[out] io Operation output. + * + * \return Error code, KNOT_EOK if success. + */ +int conf_io_check( + conf_io_t *io +); + +/*! + * Returns textual item key part of the operation result. + * + * \note The result must be deallocated. + * + * \param[out] io Operation output. + * + * \return String or NULL. + */ +char *conf_io_txt_key( + conf_io_t *io +); + +/*! + * Returns textual item data part of the operation result. + * + * \note The result must be deallocated. + * + * \param[out] io Operation output. + * + * \return String or NULL. + */ +char *conf_io_txt_data( + conf_io_t *io +); + +/*! @} */ diff --git a/src/knot/server/server.c b/src/knot/server/server.c index 72d6e8c1b..7043ed89e 100644 --- a/src/knot/server/server.c +++ b/src/knot/server/server.c @@ -482,7 +482,14 @@ int server_reload(server_t *server, const char *cf) } if (cf != NULL) { + // Check for no edit mode. + if (conf()->io.txn != NULL) { + log_warning("reload aborted due to active config DB transaction"); + return KNOT_CONF_ETXN; + } + log_info("reloading configuration file '%s'", cf); + conf_t *new_conf = NULL; int ret = conf_clone(&new_conf); if (ret != KNOT_EOK) { diff --git a/src/libknot/errcode.c b/src/libknot/errcode.c index 92b83b555..fefe9caab 100644 --- a/src/libknot/errcode.c +++ b/src/libknot/errcode.c @@ -133,9 +133,12 @@ static const struct error errors[] = { { KNOT_YP_ENOID, "missing identifier" }, /* Configuration errors. */ - { KNOT_CONF_EMPTY, "empty configuration database" }, - { KNOT_CONF_EVERSION, "invalid configuration database version" }, - { KNOT_CONF_EREDEFINE, "duplicate identifier" }, + { KNOT_CONF_EMPTY, "empty configuration database" }, + { KNOT_CONF_EVERSION, "invalid configuration database version" }, + { KNOT_CONF_EREDEFINE, "duplicate identifier" }, + { KNOT_CONF_ETXN, "failed to open another config DB transaction" }, + { KNOT_CONF_ENOTXN, "no active config DB transaction" }, + { KNOT_CONF_EMANYTXN, "too many nested config DB transactions" }, /* Processing errors. */ { KNOT_LAYER_ERROR, "processing layer error" }, diff --git a/src/libknot/internal/errcode.h b/src/libknot/internal/errcode.h index 44af093a8..8ca7d35ac 100644 --- a/src/libknot/internal/errcode.h +++ b/src/libknot/internal/errcode.h @@ -140,6 +140,9 @@ enum knot_error { KNOT_CONF_EMPTY, KNOT_CONF_EVERSION, KNOT_CONF_EREDEFINE, + KNOT_CONF_ETXN, + KNOT_CONF_ENOTXN, + KNOT_CONF_EMANYTXN, /* Processing error. */ KNOT_LAYER_ERROR, -- GitLab