diff options
Diffstat (limited to '')
-rw-r--r-- | src/knot/conf/confdb.c | 951 |
1 files changed, 951 insertions, 0 deletions
diff --git a/src/knot/conf/confdb.c b/src/knot/conf/confdb.c new file mode 100644 index 0000000..e1262c2 --- /dev/null +++ b/src/knot/conf/confdb.c @@ -0,0 +1,951 @@ +/* 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 <https://www.gnu.org/licenses/>. + */ + +#include <assert.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <stdbool.h> +#include <string.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <time.h> +#include <unistd.h> + +#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; +} |