diff options
Diffstat (limited to 'src/utils/kjournalprint')
-rw-r--r-- | src/utils/kjournalprint/main.c | 491 |
1 files changed, 491 insertions, 0 deletions
diff --git a/src/utils/kjournalprint/main.c b/src/utils/kjournalprint/main.c new file mode 100644 index 0000000..3ba0019 --- /dev/null +++ b/src/utils/kjournalprint/main.c @@ -0,0 +1,491 @@ +/* Copyright (C) 2023 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 <getopt.h> +#include <stdlib.h> +#include <sys/stat.h> +#include <unistd.h> + +#include "libknot/libknot.h" +#include "knot/journal/journal_basic.h" +#include "knot/journal/journal_metadata.h" +#include "knot/journal/journal_read.h" +#include "knot/journal/serialization.h" +#include "knot/zone/zone-dump.h" +#include "utils/common/msg.h" +#include "utils/common/params.h" +#include "utils/common/signal.h" +#include "utils/common/util_conf.h" +#include "contrib/color.h" +#include "contrib/strtonum.h" +#include "contrib/string.h" +#include "contrib/time.h" + +#define PROGRAM_NAME "kjournalprint" + +knot_lmdb_db_t journal_db = { 0 }; +signal_ctx_t signal_ctx = { 0 }; // global, needed by signal handler + +static void print_help(void) +{ + printf("Usage:\n" + " %s [-c | -C | -D <path>] [options] <zone_name>\n" + " %s [-c | -C | -D <path>] -z\n" + "\n" + "Config options:\n" + " -c, --config <file> Path to a textual configuration file.\n" + " (default %s)\n" + " -C, --confdb <dir> Path to a configuration database directory.\n" + " (default %s)\n" + " -D, --dir <path> Path to a journal database directory, use default\n" + " configuration.\n" + "Options:\n" + " -z, --zone-list Instead of reading the journal, display the list\n" + " of zones in the DB.\n" + " -l, --limit <num> Read only <num> newest changes.\n" + " -s, --serial <soa> Start with a specific SOA serial.\n" + " -H, --check Additional journal semantic checks.\n" + " -d, --debug Debug mode output.\n" + " -x, --mono Get output without coloring.\n" + " -n, --no-color An alias for -x, deprecated.\n" + " -X, --color Force output coloring.\n" + " -h, --help Print the program help.\n" + " -V, --version Print the program version.\n", + PROGRAM_NAME, PROGRAM_NAME, CONF_DEFAULT_FILE, CONF_DEFAULT_DBDIR); +} + +typedef struct { + bool debug; + bool color; + bool check; + int limit; + int counter; + uint32_t serial; + bool from_serial; + size_t changes; +} print_params_t; + +static void print_changeset(const changeset_t *chs, uint64_t timestamp, print_params_t *params) +{ + char time_buf[64] = { 0 }; + (void)knot_time_print(TIME_PRINT_UNIX, timestamp, time_buf, sizeof(time_buf)); + + static size_t count = 1; + if (chs->soa_from == NULL) { + printf("%s;; Zone-in-journal, serial: %u, changeset: %zu, timestamp: %s%s\n", + COL_YELW(params->color), + knot_soa_serial(chs->soa_to->rrs.rdata), + count++, + time_buf, + COL_RST(params->color)); + } else { + printf("%s;; Changes between zone versions: %u -> %u, changeset: %zu, timestamp: %s%s\n", + COL_YELW(params->color), + knot_soa_serial(chs->soa_from->rrs.rdata), + knot_soa_serial(chs->soa_to->rrs.rdata), + count++, + time_buf, + COL_RST(params->color)); + } + changeset_print(chs, stdout, params->color); +} + +knot_dynarray_declare(rrtype, uint16_t, DYNARRAY_VISIBILITY_STATIC, 100) +knot_dynarray_define(rrtype, uint16_t, DYNARRAY_VISIBILITY_STATIC) + +typedef struct { + rrtype_dynarray_t *arr; + size_t *counter; +} rrtypelist_ctx_t; + +static void rrtypelist_add(rrtype_dynarray_t *arr, uint16_t add_type) +{ + bool already_present = false; + knot_dynarray_foreach(rrtype, uint16_t, i, *arr) { + if (*i == add_type) { + already_present = true; + break; + } + } + if (!already_present) { + rrtype_dynarray_add(arr, &add_type); + } +} + +static int rrtypelist_callback(zone_node_t *node, void *data) +{ + rrtypelist_ctx_t *ctx = data; + for (int i = 0; i < node->rrset_count; i++) { + knot_rrset_t rrset = node_rrset_at(node, i); + rrtypelist_add(ctx->arr, rrset.type); + *ctx->counter += rrset.rrs.count; + } + return KNOT_EOK; +} + +static void print_changeset_debugmode(const changeset_t *chs, uint64_t timestamp) +{ + char time_buf[64] = { 0 }; + (void)knot_time_print(TIME_PRINT_HUMAN_MIXED, timestamp, time_buf, sizeof(time_buf)); + + // detect all types + rrtype_dynarray_t types = { 0 }; + size_t count_minus = 1, count_plus = 1; // 1 for SOA which is always present but not iterated + rrtypelist_ctx_t ctx_minus = { &types, &count_minus }, ctx_plus = { &types, &count_plus }; + (void)zone_contents_apply(chs->remove, rrtypelist_callback, &ctx_minus); + (void)zone_contents_nsec3_apply(chs->remove, rrtypelist_callback, &ctx_minus); + (void)zone_contents_apply(chs->add, rrtypelist_callback, &ctx_plus); + (void)zone_contents_nsec3_apply(chs->add, rrtypelist_callback, &ctx_plus); + + if (chs->soa_from == NULL) { + printf("Zone-in-journal %u +++: %zu\t size: %zu\t timestamp: %s\t", knot_soa_serial(chs->soa_to->rrs.rdata), + count_plus, changeset_serialized_size(chs), time_buf); + } else { + printf("%u -> %u ---: %zu\t +++: %zu\t size: %zu\t timestamp: %s\t", knot_soa_serial(chs->soa_from->rrs.rdata), + knot_soa_serial(chs->soa_to->rrs.rdata), count_minus, count_plus, changeset_serialized_size(chs), time_buf); + } + + char temp[100]; + knot_dynarray_foreach(rrtype, uint16_t, i, types) { + (void)knot_rrtype_to_string(*i, temp, sizeof(temp)); + printf(" %s", temp); + } + printf("\n"); +} + +static int count_changeset_cb(_unused_ bool special, const changeset_t *ch, _unused_ uint64_t timestamp, void *ctx) +{ + print_params_t *params = ctx; + if (ch != NULL) { + params->counter++; + } + return KNOT_EOK; +} + +static int print_changeset_cb(bool special, const changeset_t *ch, uint64_t timestamp, void *ctx) +{ + print_params_t *params = ctx; + if (ch != NULL && params->counter++ >= params->limit) { + if (params->debug) { + print_changeset_debugmode(ch, timestamp); + params->changes++; + } else { + print_changeset(ch, timestamp, params); + } + if (special && params->debug) { + printf("---------------------------------------------\n"); + } + } + return KNOT_EOK; +} + +int print_journal(char *path, knot_dname_t *name, print_params_t *params) +{ + zone_journal_t j = { &journal_db, name }; + bool exists; + uint64_t occupied, occupied_all; + + knot_lmdb_init(&journal_db, path, 0, journal_env_flags(JOURNAL_MODE_ROBUST, true), NULL); + int ret = knot_lmdb_exists(&journal_db); + if (ret == KNOT_EOK) { + ret = knot_lmdb_open(&journal_db); + } + if (ret != KNOT_EOK) { + knot_lmdb_deinit(&journal_db); + return ret; + } + + ret = journal_info(j, &exists, NULL, NULL, NULL, NULL, NULL, &occupied, &occupied_all); + if (ret != KNOT_EOK || !exists) { + ERR2("zone not exists in the journal DB %s", path); + knot_lmdb_deinit(&journal_db); + return ret == KNOT_EOK ? KNOT_ENOENT : ret; + } + + if (params->check) { + ret = journal_sem_check(j); + if (ret > 0) { + ERR2("semantic check failed with code %d", ret); + } else if (ret != KNOT_EOK) { + ERR2("semantic check failed (%s)", knot_strerror(ret)); + } + } + + if (params->limit >= 0 && ret == KNOT_EOK) { + if (params->from_serial) { + ret = journal_walk_from(j, params->serial, count_changeset_cb, params); + } else { + ret = journal_walk(j, count_changeset_cb, params); + } + } + if (ret == KNOT_EOK) { + if (params->limit < 0 || params->counter <= params->limit) { + params->limit = 0; + } else { + params->limit = params->counter - params->limit; + } + params->counter = 0; + if (params->from_serial) { + ret = journal_walk_from(j, params->serial, print_changeset_cb, params); + } else { + ret = journal_walk(j, print_changeset_cb, params); + } + } + + if (params->debug && ret == KNOT_EOK) { + printf("Total number of changesets: %zu\n", params->changes); + printf("Occupied this zone (approx): %"PRIu64" KiB\n", occupied / 1024); + printf("Occupied all zones together: %"PRIu64" KiB\n", occupied_all / 1024); + } + + knot_lmdb_deinit(&journal_db); + return ret; +} + +static int add_zone_to_list(const knot_dname_t *zone, void *list) +{ + knot_dname_t *copy = knot_dname_copy(zone, NULL); + if (copy == NULL) { + return KNOT_ENOMEM; + } + return ptrlist_add(list, copy, NULL) == NULL ? KNOT_ENOMEM : KNOT_EOK; +} + +static int list_zone(const knot_dname_t *zone, bool detailed, knot_lmdb_db_t *jdb, uint64_t *occupied_all) +{ + knot_dname_txt_storage_t zone_str; + if (knot_dname_to_str(zone_str, zone, sizeof(zone_str)) == NULL) { + return KNOT_EINVAL; + } + + if (detailed) { + zone_journal_t j = { jdb, zone }; + uint32_t first_serial = 0, last_serial = 0; + bool exists = false, zij = false; + uint64_t occupied; + + int ret = journal_info(j, &exists, &first_serial, &zij, &last_serial, + NULL, NULL, &occupied, occupied_all); + if (ret != KNOT_EOK) { + return ret; + } + assert(exists); + printf("%-28s %8"PRIu64" %10u %10u %3s\n", + zone_str, occupied / 1024, first_serial, last_serial, zij ? "yes" : "no"); + } else { + printf("%s\n", zone_str); + } + return KNOT_EOK; +} + +int list_zones(char *path, bool detailed) +{ + knot_lmdb_init(&journal_db, path, 0, journal_env_flags(JOURNAL_MODE_ROBUST, true), NULL); + + list_t zones; + init_list(&zones); + ptrnode_t *zone; + uint64_t occupied_all = 0; + bool first = detailed; + + int ret = journals_walk(&journal_db, add_zone_to_list, &zones); + WALK_LIST(zone, zones) { + if (ret != KNOT_EOK) { + break; + } else if (first) { + printf(";; <zone name> <occupied KiB> <first serial> <last serial> <full zone>\n"); + first = false; + } + ret = list_zone(zone->d, detailed, &journal_db, &occupied_all); + } + + knot_lmdb_deinit(&journal_db); + ptrlist_deep_free(&zones, NULL); + + if (detailed && ret == KNOT_EOK) { + printf(";; Occupied all zones together: %"PRIu64" KiB\n", occupied_all / 1024); + } + return ret; +} + +int main(int argc, char *argv[]) +{ + bool justlist = false; + + print_params_t params = { + .debug = false, + .color = isatty(STDOUT_FILENO), + .check = false, + .limit = -1, + .from_serial = false, + }; + + struct option opts[] = { + { "config", required_argument, NULL, 'c' }, + { "confdb", required_argument, NULL, 'C' }, + { "dir", required_argument, NULL, 'D' }, + { "limit", required_argument, NULL, 'l' }, + { "serial", required_argument, NULL, 's' }, + { "zone-list", no_argument, NULL, 'z' }, + { "check", no_argument, NULL, 'H' }, + { "debug", no_argument, NULL, 'd' }, + { "no-color", no_argument, NULL, 'n' }, + { "mono", no_argument, NULL, 'x' }, + { "color", no_argument, NULL, 'X' }, + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, 'V' }, + { NULL } + }; + + signal_ctx.close_db = &journal_db; + signal_init_std(); + + int opt = 0; + while ((opt = getopt_long(argc, argv, "c:C:D:l:s:zHdnxXhV", opts, NULL)) != -1) { + switch (opt) { + case 'c': + if (util_conf_init_file(optarg) != KNOT_EOK) { + goto failure; + } + break; + case 'C': + if (util_conf_init_confdb(optarg) != KNOT_EOK) { + goto failure; + } + break; + case 'D': + if (util_conf_init_justdb("journal-db", optarg) != KNOT_EOK) { + goto failure; + } + break; + case 'l': + if (str_to_int(optarg, ¶ms.limit, 0, INT_MAX) != KNOT_EOK) { + print_help(); + goto failure; + } + break; + case 's': + if (str_to_u32(optarg, ¶ms.serial) != KNOT_EOK) { + print_help(); + goto failure; + } + params.from_serial = true; + break; + case 'z': + justlist = true; + break; + case 'H': + params.check = true; + break; + case 'd': + params.debug = true; + break; + case 'n': + case 'x': + params.color = false; + break; + case 'X': + params.color = true; + break; + case 'h': + print_help(); + goto success; + case 'V': + print_version(PROGRAM_NAME); + goto success; + default: + print_help(); + goto failure; + } + } + + // Backward compatibility. + if ((justlist && (argc - optind > 0)) || (!justlist && (argc - optind > 1))) { + WARN2("obsolete parameter specified"); + if (util_conf_init_justdb("journal-db", argv[optind]) != KNOT_EOK) { + goto failure; + } + optind++; + } + + signal_ctx.color = params.color; + + if (util_conf_init_default(true) != KNOT_EOK) { + goto failure; + } + + char *db = conf_db(conf(), C_JOURNAL_DB); + + if (justlist) { + int ret = list_zones(db, params.debug); + free(db); + switch (ret) { + case KNOT_ENOENT: + INFO2("No zones in journal DB"); + // FALLTHROUGH + case KNOT_EOK: + goto success; + case KNOT_ENODB: + ERR2("the journal DB does not exist"); + goto failure; + case KNOT_EMALF: + ERR2("the journal DB is broken"); + goto failure; + default: + ERR2("failed to load zone list (%s)", knot_strerror(ret)); + goto failure; + } + } else { + if (argc - optind != 1) { + print_help(); + free(db); + goto failure; + } + knot_dname_t *name = knot_dname_from_str_alloc(argv[optind]); + knot_dname_to_lower(name); + + int ret = print_journal(db, name, ¶ms); + free(name); + free(db); + switch (ret) { + case KNOT_ENOENT: + if (params.from_serial) { + INFO2("The journal is empty or the serial not present"); + } else { + INFO2("The journal is empty"); + } + break; + case KNOT_ENODB: + ERR2("the journal DB does not exist"); + goto failure; + case KNOT_EOUTOFZONE: + ERR2("the journal DB does not contain the specified zone"); + goto failure; + case KNOT_EOK: + break; + default: + ERR2("failed to load changesets (%s)", knot_strerror(ret)); + goto failure; + } + } + +success: + util_conf_deinit(); + return EXIT_SUCCESS; +failure: + util_conf_deinit(); + return EXIT_FAILURE; +} |