/* Copyright (C) 2019 CZ.NIC, z.s.p.o. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include #include #include #include #include #include #include #include #include #include #include "knot/conf/confdb.h" #include "libknot/errcode.h" #include "libknot/yparser/yptrafo.h" #include "contrib/openbsd/strlcpy.h" #include "contrib/wire_ctx.h" /* * A simple configuration: * * server.identity: "knot" * server.version: "version" * template[tpl1].storage: "directory1" * template[tpl2].storage: "directory2" * template[tpl2].master: [ "master1", "master2" ] * * And the corresponding configuration DB content: * * # DB structure version. * [00][FF]: [02] * # Sections codes. * [00][00]server: [02] * [00][00]template: [03] * # Server section items codes. * [02][00]identity: [02] * [02][00]version: [03] * # Server items values. * [02][02]: knot\0 * [02][03]: version\0 * # Template section items codes. * [03][00]master: [03] * [03][00]storage: [02] * # Template identifiers. * [03][01]tpl1\0 * [03][01]tpl2\0 * # Template items values. * [03][02]tpl1\0: directory1\0 * [03][02]tpl2\0: directory2\0 * [03][03]tpl2\0: [00][08]master1\0 [00][08]master2\0 */ typedef enum { KEY0_ROOT = 0, KEY1_ITEMS = 0, KEY1_ID = 1, KEY1_FIRST = 2, KEY1_LAST = 200, KEY1_VERSION = 255 } db_code_t; typedef enum { KEY0_POS = 0, KEY1_POS = 1, NAME_POS = 2 } db_code_pos_t; typedef enum { DB_GET, DB_SET, DB_DEL } db_action_t; static int db_check_version( conf_t *conf, knot_db_txn_t *txn) { uint8_t k[2] = { KEY0_ROOT, KEY1_VERSION }; knot_db_val_t key = { k, sizeof(k) }; knot_db_val_t data; // Get conf-DB version. int ret = conf->api->find(txn, &key, &data, 0); if (ret != KNOT_EOK) { return ret; } // Check conf-DB version. if (data.len != 1 || ((uint8_t *)data.data)[0] != CONF_DB_VERSION) { return KNOT_CONF_EVERSION; } return KNOT_EOK; } int conf_db_init( conf_t *conf, knot_db_txn_t *txn, bool purge) { if (conf == NULL || txn == NULL) { return KNOT_EINVAL; } uint8_t k[2] = { KEY0_ROOT, KEY1_VERSION }; knot_db_val_t key = { k, sizeof(k) }; int ret = conf->api->count(txn); if (ret == 0) { // Initialize empty DB with DB version. uint8_t d[1] = { CONF_DB_VERSION }; knot_db_val_t data = { d, sizeof(d) }; return conf->api->insert(txn, &key, &data, 0); } else if (ret > 0) { // Non-empty DB. if (purge) { // Purge the DB. ret = conf->api->clear(txn); if (ret != KNOT_EOK) { return ret; } return conf_db_init(conf, txn, false); } return KNOT_EOK; } else { // DB error. return ret; } } int conf_db_check( conf_t *conf, knot_db_txn_t *txn) { int ret = conf->api->count(txn); if (ret == 0) { // Not initialized DB. return KNOT_CONF_ENOTINIT; } else if (ret > 0) { // Check the DB. int count = ret; ret = db_check_version(conf, txn); if (ret != KNOT_EOK) { return ret; } else if (count == 1) { return KNOT_EOK; // Empty but initialized DB. } else { return count - 1; // Non-empty DB. } } else { // DB error. return ret; } } static int db_code( conf_t *conf, knot_db_txn_t *txn, uint8_t section_code, const yp_name_t *name, db_action_t action, uint8_t *code) { if (name == NULL) { return KNOT_EINVAL; } knot_db_val_t key; uint8_t k[CONF_MIN_KEY_LEN + YP_MAX_ITEM_NAME_LEN]; k[KEY0_POS] = section_code; k[KEY1_POS] = KEY1_ITEMS; memcpy(k + NAME_POS, name + 1, name[0]); key.data = k; key.len = CONF_MIN_KEY_LEN + name[0]; // Check if the item is already registered. knot_db_val_t data; int ret = conf->api->find(txn, &key, &data, 0); switch (ret) { case KNOT_EOK: if (action == DB_DEL) { return conf->api->del(txn, &key); } if (code != NULL) { *code = ((uint8_t *)data.data)[0]; } return KNOT_EOK; case KNOT_ENOENT: if (action != DB_SET) { return KNOT_ENOENT; } break; default: return ret; } // Reduce the key to common prefix only. key.len = CONF_MIN_KEY_LEN; bool codes[KEY1_LAST + 1] = { false }; // Find all used item codes. knot_db_iter_t *it = conf->api->iter_begin(txn, KNOT_DB_NOOP); it = conf->api->iter_seek(it, &key, KNOT_DB_GEQ); while (it != NULL) { knot_db_val_t iter_key; ret = conf->api->iter_key(it, &iter_key); if (ret != KNOT_EOK) { conf->api->iter_finish(it); return ret; } uint8_t *key_data = (uint8_t *)iter_key.data; // Check for database prefix end. if (key_data[KEY0_POS] != k[KEY0_POS] || key_data[KEY1_POS] != k[KEY1_POS]) { break; } knot_db_val_t iter_val; ret = conf->api->iter_val(it, &iter_val); if (ret != KNOT_EOK) { conf->api->iter_finish(it); return ret; } uint8_t used_code = ((uint8_t *)iter_val.data)[0]; codes[used_code] = true; it = conf->api->iter_next(it); } conf->api->iter_finish(it); // Find the smallest unused item code. uint8_t new_code = KEY1_FIRST; while (codes[new_code]) { new_code++; if (new_code > KEY1_LAST) { return KNOT_ESPACE; } } // Restore the full key. key.len = CONF_MIN_KEY_LEN + name[0]; // Fill the data with a new code. data.data = &new_code; data.len = sizeof(new_code); // Register new item code. ret = conf->api->insert(txn, &key, &data, 0); if (ret != KNOT_EOK) { return ret; } if (code != NULL) { *code = new_code; } return KNOT_EOK; } static uint8_t *find_data( const knot_db_val_t *value, const knot_db_val_t *current) { wire_ctx_t ctx = wire_ctx_init_const(current->data, current->len); // Loop over the data array. Each item has 2B length prefix. while (wire_ctx_available(&ctx) > 0) { uint16_t len = wire_ctx_read_u16(&ctx); assert(ctx.error == KNOT_EOK); // Check for the same data. if (len == value->len && (len == 0 || memcmp(ctx.position, value->data, value->len) == 0)) { wire_ctx_skip(&ctx, -sizeof(uint16_t)); assert(ctx.error == KNOT_EOK); return ctx.position; } wire_ctx_skip(&ctx, len); } assert(ctx.error == KNOT_EOK && wire_ctx_available(&ctx) == 0); return NULL; } static int db_set( conf_t *conf, knot_db_txn_t *txn, knot_db_val_t *key, knot_db_val_t *data, bool multi) { if (!multi) { if (data->len > CONF_MAX_DATA_LEN) { return KNOT_ERANGE; } // Insert new (overwrite old) data. return conf->api->insert(txn, key, data, 0); } knot_db_val_t d; if (data->len > UINT16_MAX) { return KNOT_ERANGE; } int ret = conf->api->find(txn, key, &d, 0); if (ret == KNOT_ENOENT) { d.len = 0; } else if (ret == KNOT_EOK) { // Check for duplicate data. if (find_data(data, &d) != NULL) { return KNOT_EOK; } } else { return ret; } // Prepare buffer for all data. size_t new_len = d.len + sizeof(uint16_t) + data->len; if (new_len > CONF_MAX_DATA_LEN) { return KNOT_ESPACE; } uint8_t *new_data = malloc(new_len); if (new_data == NULL) { return KNOT_ENOMEM; } wire_ctx_t ctx = wire_ctx_init(new_data, new_len); // Copy current data array. wire_ctx_write(&ctx, d.data, d.len); // Copy length prefix for the new data item. wire_ctx_write_u16(&ctx, data->len); // Copy the new data item. wire_ctx_write(&ctx, data->data, data->len); assert(ctx.error == KNOT_EOK && wire_ctx_available(&ctx) == 0); d.data = new_data; d.len = new_len; // Insert new (or append) data. ret = conf->api->insert(txn, key, &d, 0); free(new_data); return ret; } int conf_db_set( conf_t *conf, knot_db_txn_t *txn, const yp_name_t *key0, const yp_name_t *key1, const uint8_t *id, size_t id_len, const uint8_t *data, size_t data_len) { if (conf == NULL || txn == NULL || key0 == NULL || (id == NULL && id_len > 0) || (data == NULL && data_len > 0)) { return KNOT_EINVAL; } // Check for valid keys. const yp_item_t *item = yp_schema_find(key1 != NULL ? key1 : key0, key1 != NULL ? key0 : NULL, conf->schema); if (item == NULL) { return KNOT_YP_EINVAL_ITEM; } // Ignore alone key0 insertion. if (key1 == NULL && id_len == 0) { return KNOT_EOK; } // Ignore group id as a key1. if (item->parent != NULL && (item->parent->flags & YP_FMULTI) != 0 && item->parent->var.g.id == item) { key1 = NULL; } uint8_t k[CONF_MAX_KEY_LEN] = { 0 }; knot_db_val_t key = { k, CONF_MIN_KEY_LEN }; // Set key0 code. int ret = db_code(conf, txn, KEY0_ROOT, key0, DB_SET, &k[KEY0_POS]); if (ret != KNOT_EOK) { return ret; } // Set id part. if (id_len > 0) { if (id_len > YP_MAX_ID_LEN) { return KNOT_YP_EINVAL_ID; } memcpy(k + CONF_MIN_KEY_LEN, id, id_len); key.len += id_len; k[KEY1_POS] = KEY1_ID; knot_db_val_t val = { NULL }; // Insert id. if (key1 == NULL) { ret = conf->api->find(txn, &key, &val, 0); if (ret == KNOT_EOK) { return KNOT_CONF_EREDEFINE; } ret = db_set(conf, txn, &key, &val, false); if (ret != KNOT_EOK) { return ret; } // Check for existing id. } else { ret = conf->api->find(txn, &key, &val, 0); if (ret != KNOT_EOK) { return KNOT_YP_EINVAL_ID; } } } // Insert key1 data. if (key1 != NULL) { // Set key1 code. ret = db_code(conf, txn, k[KEY0_POS], key1, DB_SET, &k[KEY1_POS]); if (ret != KNOT_EOK) { return ret; } knot_db_val_t val = { (uint8_t *)data, data_len }; ret = db_set(conf, txn, &key, &val, item->flags & YP_FMULTI); if (ret != KNOT_EOK) { return ret; } } return KNOT_EOK; } static int db_unset( conf_t *conf, knot_db_txn_t *txn, knot_db_val_t *key, knot_db_val_t *data, bool multi) { // No item data can be zero length. if (data->len == 0) { return conf->api->del(txn, key); } knot_db_val_t d; int ret = conf->api->find(txn, key, &d, 0); if (ret != KNOT_EOK) { return ret; } // Process singlevalued data. if (!multi) { if (d.len != data->len || memcmp((uint8_t *)d.data, data->data, d.len) != 0) { return KNOT_ENOENT; } return conf->api->del(txn, key); } // Check if the data exists. uint8_t *pos = find_data(data, &d); if (pos == NULL) { return KNOT_ENOENT; } // Prepare buffer for reduced data. size_t total_len = d.len - sizeof(uint16_t) - data->len; if (total_len == 0) { return conf->api->del(txn, key); } uint8_t *new_data = malloc(total_len); if (new_data == NULL) { return KNOT_ENOMEM; } size_t new_len = 0; // Copy leading data block. assert(pos >= (uint8_t *)d.data); size_t head_len = pos - (uint8_t *)d.data; if (head_len > 0) { memcpy(new_data, d.data, head_len); new_len += head_len; } pos += sizeof(uint16_t) + data->len; // Copy trailing data block. assert(pos <= (uint8_t *)d.data + d.len); size_t tail_len = (uint8_t *)d.data + d.len - pos; if (tail_len > 0) { memcpy(new_data + new_len, pos, tail_len); new_len += tail_len; } d.data = new_data; d.len = new_len; // Insert reduced data. ret = conf->api->insert(txn, key, &d, 0); free(new_data); return ret; } int conf_db_unset( conf_t *conf, knot_db_txn_t *txn, const yp_name_t *key0, const yp_name_t *key1, const uint8_t *id, size_t id_len, const uint8_t *data, size_t data_len, bool delete_key1) { if (conf == NULL || txn == NULL || key0 == NULL || (id == NULL && id_len > 0) || (data == NULL && data_len > 0)) { return KNOT_EINVAL; } // Check for valid keys. const yp_item_t *item = yp_schema_find(key1 != NULL ? key1 : key0, key1 != NULL ? key0 : NULL, conf->schema); if (item == NULL) { return KNOT_YP_EINVAL_ITEM; } // Delete the key0. if (key1 == NULL && id_len == 0) { return db_code(conf, txn, KEY0_ROOT, key0, DB_DEL, NULL); } // Ignore group id as a key1. if (item->parent != NULL && (item->parent->flags & YP_FMULTI) != 0 && item->parent->var.g.id == item) { key1 = NULL; } uint8_t k[CONF_MAX_KEY_LEN] = { 0 }; knot_db_val_t key = { k, CONF_MIN_KEY_LEN }; // Set the key0 code. int ret = db_code(conf, txn, KEY0_ROOT, key0, DB_GET, &k[KEY0_POS]); if (ret != KNOT_EOK) { return ret; } // Set the id part. if (id_len > 0) { if (id_len > YP_MAX_ID_LEN) { return KNOT_YP_EINVAL_ID; } memcpy(k + CONF_MIN_KEY_LEN, id, id_len); key.len += id_len; k[KEY1_POS] = KEY1_ID; knot_db_val_t val = { NULL }; // Delete the id. if (key1 == NULL) { return conf->api->del(txn, &key); // Check for existing id. } else { ret = conf->api->find(txn, &key, &val, 0); if (ret != KNOT_EOK) { return KNOT_YP_EINVAL_ID; } } } if (key1 != NULL) { // Set the key1 code. ret = db_code(conf, txn, k[KEY0_POS], key1, DB_GET, &k[KEY1_POS]); if (ret != KNOT_EOK) { return ret; } // Delete the key1. if (data_len == 0 && delete_key1) { ret = db_code(conf, txn, k[KEY0_POS], key1, DB_DEL, NULL); if (ret != KNOT_EOK) { return ret; } // Delete the item data. } else { knot_db_val_t val = { (uint8_t *)data, data_len }; ret = db_unset(conf, txn, &key, &val, item->flags & YP_FMULTI); if (ret != KNOT_EOK) { return ret; } } } return KNOT_EOK; } int conf_db_get( conf_t *conf, knot_db_txn_t *txn, const yp_name_t *key0, const yp_name_t *key1, const uint8_t *id, size_t id_len, conf_val_t *data) { conf_val_t out = { NULL }; if (conf == NULL || txn == NULL || key0 == NULL || (id == NULL && id_len > 0)) { out.code = KNOT_EINVAL; goto get_error; } // Check for valid keys. out.item = yp_schema_find(key1 != NULL ? key1 : key0, key1 != NULL ? key0 : NULL, conf->schema); if (out.item == NULL) { out.code = KNOT_YP_EINVAL_ITEM; goto get_error; } // At least key1 or id must be specified. if (key1 == NULL && id_len == 0) { out.code = KNOT_EINVAL; goto get_error; } // Ignore group id as a key1. if (out.item->parent != NULL && (out.item->parent->flags & YP_FMULTI) != 0 && out.item->parent->var.g.id == out.item) { key1 = NULL; } uint8_t k[CONF_MAX_KEY_LEN] = { 0 }; knot_db_val_t key = { k, CONF_MIN_KEY_LEN }; knot_db_val_t val = { NULL }; // Set the key0 code. out.code = db_code(conf, txn, KEY0_ROOT, key0, DB_GET, &k[KEY0_POS]); if (out.code != KNOT_EOK) { if (id_len > 0) { out.code = KNOT_YP_EINVAL_ID; } goto get_error; } // Set the id part. if (id_len > 0) { if (id_len > YP_MAX_ID_LEN) { out.code = KNOT_YP_EINVAL_ID; goto get_error; } memcpy(k + CONF_MIN_KEY_LEN, id, id_len); key.len += id_len; k[KEY1_POS] = KEY1_ID; // Check for existing id. out.code = conf->api->find(txn, &key, &val, 0); if (out.code != KNOT_EOK) { out.code = KNOT_YP_EINVAL_ID; goto get_error; } } // Set the key1 code. if (key1 != NULL) { out.code = db_code(conf, txn, k[KEY0_POS], key1, DB_GET, &k[KEY1_POS]); if (out.code != KNOT_EOK) { goto get_error; } } // Get the data. out.code = conf->api->find(txn, &key, &val, 0); if (out.code == KNOT_EOK) { out.blob = val.data; out.blob_len = val.len; } get_error: // Set the output. if (data != NULL) { *data = out; } return out.code; } static int check_iter( conf_t *conf, conf_iter_t *iter) { knot_db_val_t key; // Get the current key. int ret = conf->api->iter_key(iter->iter, &key); if (ret != KNOT_EOK) { return KNOT_ENOENT; } uint8_t *key_data = (uint8_t *)key.data; // Check for key overflow. if (key_data[KEY0_POS] != iter->key0_code || key_data[KEY1_POS] != KEY1_ID) { return KNOT_EOF; } return KNOT_EOK; } int conf_db_iter_begin( conf_t *conf, knot_db_txn_t *txn, const yp_name_t *key0, conf_iter_t *iter) { conf_iter_t out = { NULL }; if (conf == NULL || txn == NULL || key0 == NULL || iter == NULL) { out.code = KNOT_EINVAL; goto iter_begin_error; } // Look-up group id item in the schema. const yp_item_t *grp = yp_schema_find(key0, NULL, conf->schema); if (grp == NULL) { out.code = KNOT_YP_EINVAL_ITEM; goto iter_begin_error; } if (grp->type != YP_TGRP || (grp->flags & YP_FMULTI) == 0) { out.code = KNOT_ENOTSUP; goto iter_begin_error; } out.item = grp->var.g.id; // Get key0 code. out.code = db_code(conf, txn, KEY0_ROOT, key0, DB_GET, &out.key0_code); if (out.code != KNOT_EOK) { goto iter_begin_error; } // Prepare key prefix. uint8_t k[2] = { out.key0_code, KEY1_ID }; knot_db_val_t key = { k, sizeof(k) }; // Get the data. out.iter = conf->api->iter_begin(txn, KNOT_DB_NOOP); out.iter = conf->api->iter_seek(out.iter, &key, KNOT_DB_GEQ); // Check for no section id. out.code = check_iter(conf, &out); if (out.code != KNOT_EOK) { out.code = KNOT_ENOENT; // Treat all errors as no entry. conf_db_iter_finish(conf, &out); goto iter_begin_error; } iter_begin_error: // Set the output. if (iter != NULL) { *iter = out; } return out.code; } int conf_db_iter_next( conf_t *conf, conf_iter_t *iter) { if (conf == NULL || iter == NULL) { return KNOT_EINVAL; } if (iter->code != KNOT_EOK) { return iter->code; } assert(iter->iter != NULL); // Move to the next key-value. iter->iter = conf->api->iter_next(iter->iter); if (iter->iter == NULL) { conf_db_iter_finish(conf, iter); iter->code = KNOT_EOF; return iter->code; } // Check for key overflow. iter->code = check_iter(conf, iter); if (iter->code != KNOT_EOK) { conf_db_iter_finish(conf, iter); return iter->code; } return KNOT_EOK; } int conf_db_iter_id( conf_t *conf, conf_iter_t *iter, const uint8_t **data, size_t *data_len) { if (conf == NULL || iter == NULL || iter->iter == NULL || data == NULL || data_len == NULL) { return KNOT_EINVAL; } knot_db_val_t key; int ret = conf->api->iter_key(iter->iter, &key); if (ret != KNOT_EOK) { return ret; } *data = (uint8_t *)key.data + CONF_MIN_KEY_LEN; *data_len = key.len - CONF_MIN_KEY_LEN; return KNOT_EOK; } int conf_db_iter_del( conf_t *conf, conf_iter_t *iter) { if (conf == NULL || iter == NULL || iter->iter == NULL) { return KNOT_EINVAL; } return knot_db_lmdb_iter_del(iter->iter); } void conf_db_iter_finish( conf_t *conf, conf_iter_t *iter) { if (conf == NULL || iter == NULL) { return; } if (iter->iter != NULL) { conf->api->iter_finish(iter->iter); iter->iter = NULL; } } int conf_db_raw_dump( conf_t *conf, knot_db_txn_t *txn, const char *file_name) { if (conf == NULL) { return KNOT_EINVAL; } // Use the current config read transaction if not specified. if (txn == NULL) { txn = &conf->read_txn; } FILE *fp = stdout; if (file_name != NULL) { fp = fopen(file_name, "w"); if (fp == NULL) { return KNOT_ERROR; } } int ret = KNOT_EOK; knot_db_iter_t *it = conf->api->iter_begin(txn, KNOT_DB_FIRST); while (it != NULL) { knot_db_val_t key; ret = conf->api->iter_key(it, &key); if (ret != KNOT_EOK) { break; } knot_db_val_t data; ret = conf->api->iter_val(it, &data); if (ret != KNOT_EOK) { break; } uint8_t *k = (uint8_t *)key.data; uint8_t *d = (uint8_t *)data.data; if (k[1] == KEY1_ITEMS) { fprintf(fp, "[%i][%i]%.*s", k[0], k[1], (int)key.len - 2, k + 2); fprintf(fp, ": %u\n", d[0]); } else if (k[1] == KEY1_ID) { fprintf(fp, "[%i][%i](%zu){", k[0], k[1], key.len - 2); for (size_t i = 2; i < key.len; i++) { fprintf(fp, "%02x", (uint8_t)k[i]); } fprintf(fp, "}\n"); } else { fprintf(fp, "[%i][%i]", k[0], k[1]); if (key.len > 2) { fprintf(fp, "(%zu){", key.len - 2); for (size_t i = 2; i < key.len; i++) { fprintf(fp, "%02x", (uint8_t)k[i]); } fprintf(fp, "}"); } fprintf(fp, ": (%zu)<", data.len); for (size_t i = 0; i < data.len; i++) { fprintf(fp, "%02x", (uint8_t)d[i]); } fprintf(fp, ">\n"); } it = conf->api->iter_next(it); } conf->api->iter_finish(it); if (file_name != NULL) { fclose(fp); } else { fflush(fp); } return ret; }