summaryrefslogtreecommitdiffstats
path: root/src/knot/catalog
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 15:24:08 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 15:24:08 +0000
commitf449f278dd3c70e479a035f50a9bb817a9b433ba (patch)
tree8ca2bfb785dda9bb4d573acdf9b42aea9cd51383 /src/knot/catalog
parentInitial commit. (diff)
downloadknot-f449f278dd3c70e479a035f50a9bb817a9b433ba.tar.xz
knot-f449f278dd3c70e479a035f50a9bb817a9b433ba.zip
Adding upstream version 3.2.6.upstream/3.2.6upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/knot/catalog')
-rw-r--r--src/knot/catalog/catalog_db.c347
-rw-r--r--src/knot/catalog/catalog_db.h187
-rw-r--r--src/knot/catalog/catalog_update.c407
-rw-r--r--src/knot/catalog/catalog_update.h171
-rw-r--r--src/knot/catalog/generate.c346
-rw-r--r--src/knot/catalog/generate.h56
-rw-r--r--src/knot/catalog/interpret.c257
-rw-r--r--src/knot/catalog/interpret.h52
8 files changed, 1823 insertions, 0 deletions
diff --git a/src/knot/catalog/catalog_db.c b/src/knot/catalog/catalog_db.c
new file mode 100644
index 0000000..b483f4d
--- /dev/null
+++ b/src/knot/catalog/catalog_db.c
@@ -0,0 +1,347 @@
+/* Copyright (C) 2021 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 <stdio.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <urcu.h>
+
+#include "contrib/files.h"
+#include "knot/catalog/catalog_db.h"
+#include "knot/common/log.h"
+
+static const MDB_val catalog_iter_prefix = { 1, "" };
+
+size_t catalog_dname_append(knot_dname_storage_t storage, const knot_dname_t *name)
+{
+ size_t old_len = knot_dname_size(storage);
+ size_t name_len = knot_dname_size(name);
+ size_t new_len = old_len - 1 + name_len;
+ if (old_len == 0 || name_len == 0 || new_len > KNOT_DNAME_MAXLEN) {
+ return 0;
+ }
+ memcpy(storage + old_len - 1, name, name_len);
+ return new_len;
+}
+
+int catalog_bailiwick_shift(const knot_dname_t *subname, const knot_dname_t *name)
+{
+ const knot_dname_t *res = subname;
+ while (!knot_dname_is_equal(res, name)) {
+ if (*res == '\0') {
+ return -1;
+ }
+ res = knot_wire_next_label(res, NULL);
+ }
+ return res - subname;
+}
+
+void catalog_init(catalog_t *cat, const char *path, size_t mapsize)
+{
+ knot_lmdb_init(&cat->db, path, mapsize, MDB_NOTLS, NULL);
+}
+
+static void ensure_cat_version(knot_lmdb_txn_t *ro_txn, knot_lmdb_txn_t *rw_txn)
+{
+ MDB_val key = { 8, "\x01version" };
+ if (knot_lmdb_find(ro_txn, &key, KNOT_LMDB_EXACT)) {
+ if (strncmp(CATALOG_VERSION, ro_txn->cur_val.mv_data,
+ ro_txn->cur_val.mv_size) != 0) {
+ log_warning("catalog version mismatch");
+ }
+ } else if (rw_txn != NULL) {
+ MDB_val val = { strlen(CATALOG_VERSION), CATALOG_VERSION };
+ knot_lmdb_insert(rw_txn, &key, &val);
+ }
+}
+
+// does NOT check for catalog zone version by RFC, this is Knot-specific in the cat LMDB !
+static void check_cat_version(catalog_t *cat)
+{
+ if (cat->ro_txn->ret == KNOT_EOK) {
+ ensure_cat_version(cat->ro_txn, cat->rw_txn);
+ }
+}
+
+int catalog_open(catalog_t *cat)
+{
+ if (!knot_lmdb_is_open(&cat->db)) {
+ int ret = knot_lmdb_open(&cat->db);
+ if (ret != KNOT_EOK) {
+ return ret;
+ }
+ }
+ if (cat->ro_txn == NULL) {
+ knot_lmdb_txn_t *ro_txn = calloc(1, sizeof(*ro_txn));
+ if (ro_txn == NULL) {
+ return KNOT_ENOMEM;
+ }
+ knot_lmdb_begin(&cat->db, ro_txn, false);
+ cat->ro_txn = ro_txn;
+ }
+ check_cat_version(cat);
+ return cat->ro_txn->ret;
+}
+
+int catalog_begin(catalog_t *cat)
+{
+ int ret = catalog_open(cat);
+ if (ret != KNOT_EOK) {
+ return ret;
+ }
+ knot_lmdb_txn_t *rw_txn = calloc(1, sizeof(*rw_txn));
+ if (rw_txn == NULL) {
+ return KNOT_ENOMEM;
+ }
+ knot_lmdb_begin(&cat->db, rw_txn, true);
+ if (rw_txn->ret != KNOT_EOK) {
+ ret = rw_txn->ret;
+ free(rw_txn);
+ return ret;
+ }
+ assert(cat->rw_txn == NULL); // LMDB prevents two existing RW txns at a time
+ cat->rw_txn = rw_txn;
+ check_cat_version(cat);
+ return cat->rw_txn->ret;
+}
+
+int catalog_commit(catalog_t *cat)
+{
+ knot_lmdb_txn_t *rw_txn = rcu_xchg_pointer(&cat->rw_txn, NULL);
+ knot_lmdb_commit(rw_txn);
+ int ret = rw_txn->ret;
+ free(rw_txn);
+ if (ret != KNOT_EOK) {
+ return ret;
+ }
+
+ // now refresh RO txn
+ knot_lmdb_txn_t *ro_txn = calloc(1, sizeof(*ro_txn));
+ if (ro_txn == NULL) {
+ return KNOT_ENOMEM;
+ }
+ knot_lmdb_begin(&cat->db, ro_txn, false);
+ cat->old_ro_txn = rcu_xchg_pointer(&cat->ro_txn, ro_txn);
+
+ return KNOT_EOK;
+}
+
+void catalog_abort(catalog_t *cat)
+{
+ knot_lmdb_txn_t *rw_txn = rcu_xchg_pointer(&cat->rw_txn, NULL);
+ if (rw_txn != NULL) {
+ knot_lmdb_abort(rw_txn);
+ free(rw_txn);
+ }
+}
+
+void catalog_commit_cleanup(catalog_t *cat)
+{
+ knot_lmdb_txn_t *old_ro_txn = rcu_xchg_pointer(&cat->old_ro_txn, NULL);
+ if (old_ro_txn != NULL) {
+ knot_lmdb_abort(old_ro_txn);
+ free(old_ro_txn);
+ }
+}
+
+void catalog_deinit(catalog_t *cat)
+{
+ assert(cat->rw_txn == NULL);
+ if (cat->ro_txn != NULL) {
+ knot_lmdb_abort(cat->ro_txn);
+ free(cat->ro_txn);
+ }
+ if (cat->old_ro_txn != NULL) {
+ knot_lmdb_abort(cat->old_ro_txn);
+ free(cat->old_ro_txn);
+ }
+ knot_lmdb_deinit(&cat->db);
+}
+
+int catalog_add(catalog_t *cat, const knot_dname_t *member,
+ const knot_dname_t *owner, const knot_dname_t *catzone,
+ const char *group)
+{
+ if (cat->rw_txn == NULL) {
+ return KNOT_EINVAL;
+ }
+ int bail = catalog_bailiwick_shift(owner, catzone);
+ if (bail < 0) {
+ return KNOT_EOUTOFZONE;
+ }
+ assert(bail >= 0 && bail < 256);
+ MDB_val key = knot_lmdb_make_key("BN", 0, member); // 0 for future purposes
+ MDB_val val = knot_lmdb_make_key("BBNS", 0, bail, owner, group);
+
+ knot_lmdb_insert(cat->rw_txn, &key, &val);
+ free(key.mv_data);
+ free(val.mv_data);
+ return cat->rw_txn->ret;
+}
+
+int catalog_del(catalog_t *cat, const knot_dname_t *member)
+{
+ if (cat->rw_txn == NULL) {
+ return KNOT_EINVAL;
+ }
+ MDB_val key = knot_lmdb_make_key("BN", 0, member);
+ knot_lmdb_del_prefix(cat->rw_txn, &key); // deletes one record
+ free(key.mv_data);
+ return cat->rw_txn->ret;
+}
+
+static void unmake_val(MDB_val *val, const knot_dname_t **owner,
+ const knot_dname_t **catz, const char **group)
+{
+ uint8_t zero, shift;
+ *group = ""; // backward compatibility with Knot 3.0
+ knot_lmdb_unmake_key(val->mv_data, val->mv_size, "BBNS", &zero, &shift,
+ owner, group);
+ *catz = *owner + shift;
+}
+
+static int find_threadsafe(catalog_t *cat, const knot_dname_t *member,
+ const knot_dname_t **owner, const knot_dname_t **catz,
+ const char **group, void **tofree)
+{
+ *tofree = NULL;
+ if (cat->ro_txn == NULL) {
+ return KNOT_ENOENT;
+ }
+
+ MDB_val key = knot_lmdb_make_key("BN", 0, member), val = { 0 };
+
+ int ret = knot_lmdb_find_threadsafe(cat->ro_txn, &key, &val, KNOT_LMDB_EXACT);
+ if (ret == KNOT_EOK) {
+ unmake_val(&val, owner, catz, group);
+ *tofree = val.mv_data;
+ }
+ free(key.mv_data);
+ return ret;
+}
+
+int catalog_get_catz(catalog_t *cat, const knot_dname_t *member,
+ const knot_dname_t **catz, const char **group, void **tofree)
+{
+ const knot_dname_t *unused;
+ return find_threadsafe(cat, member, &unused, catz, group, tofree);
+}
+
+bool catalog_has_member(catalog_t *cat, const knot_dname_t *member)
+{
+ const knot_dname_t *catz;
+ const char *group;
+ void *tofree = NULL;
+ int ret = catalog_get_catz(cat, member, &catz, &group, &tofree);
+ free(tofree);
+ return (ret == KNOT_EOK);
+}
+
+bool catalog_contains_exact(catalog_t *cat, const knot_dname_t *member,
+ const knot_dname_t *owner, const knot_dname_t *catz)
+{
+ const knot_dname_t *found_owner, *found_catz;
+ const char *found_group;
+ void *tofree = NULL;
+ int ret = find_threadsafe(cat, member, &found_owner, &found_catz, &found_group, &tofree);
+ if (ret == KNOT_EOK && (!knot_dname_is_equal(owner, found_owner) ||
+ !knot_dname_is_equal(catz, found_catz))) {
+ ret = KNOT_ENOENT;
+ }
+ free(tofree);
+ return (ret == KNOT_EOK);
+}
+
+typedef struct {
+ catalog_apply_cb_t cb;
+ void *ctx;
+} catalog_apply_ctx_t;
+
+static int catalog_apply_cb(MDB_val *key, MDB_val *val, void *ctx)
+{
+ catalog_apply_ctx_t *iter_ctx = ctx;
+ uint8_t zero;
+ const knot_dname_t *mem = NULL, *ow = NULL, *cz = NULL;
+ const char *gr = NULL;
+ knot_lmdb_unmake_key(key->mv_data, key->mv_size, "BN", &zero, &mem);
+ unmake_val(val, &ow, &cz, &gr);
+ if (mem == NULL || ow == NULL || cz == NULL) {
+ return KNOT_EMALF;
+ }
+ return iter_ctx->cb(mem, ow, cz, gr, iter_ctx->ctx);
+}
+
+int catalog_apply(catalog_t *cat, const knot_dname_t *for_member,
+ catalog_apply_cb_t cb, void *ctx, bool rw)
+{
+ MDB_val prefix = knot_lmdb_make_key(for_member == NULL ? "B" : "BN", 0, for_member);
+ catalog_apply_ctx_t iter_ctx = { cb, ctx };
+ knot_lmdb_txn_t *use_txn = rw ? cat->rw_txn : cat->ro_txn;
+ int ret = knot_lmdb_apply_threadsafe(use_txn, &prefix, true, catalog_apply_cb, &iter_ctx);
+ free(prefix.mv_data);
+ return ret;
+}
+
+static bool same_catalog(knot_lmdb_txn_t *txn, const knot_dname_t *catalog)
+{
+ if (catalog == NULL) {
+ return true;
+ }
+ const knot_dname_t *txn_cat = NULL, *unused;
+ const char *grunused;
+ unmake_val(&txn->cur_val, &unused, &txn_cat, &grunused);
+ return knot_dname_is_equal(txn_cat, catalog);
+}
+
+int catalog_copy(knot_lmdb_db_t *from, knot_lmdb_db_t *to,
+ const knot_dname_t *cat_only, bool read_rw_txn)
+{
+ if (knot_lmdb_exists(from) == KNOT_ENODB) {
+ return KNOT_EOK;
+ }
+ int ret = knot_lmdb_open(from);
+ if (ret == KNOT_EOK) {
+ ret = make_path(to->path, S_IRWXU | S_IRWXG);
+ if (ret == KNOT_EOK) {
+ ret = knot_lmdb_open(to);
+ }
+ }
+ if (ret != KNOT_EOK) {
+ return ret;
+ }
+ knot_lmdb_txn_t txn_r = { 0 }, txn_w = { 0 };
+ knot_lmdb_begin(from, &txn_r, read_rw_txn); // using RW txn not to conflict with still-open RO txn
+ knot_lmdb_begin(to, &txn_w, true);
+ knot_lmdb_foreach(&txn_w, (MDB_val *)&catalog_iter_prefix) {
+ if (same_catalog(&txn_w, cat_only)) {
+ knot_lmdb_del_cur(&txn_w);
+ }
+ }
+ knot_lmdb_foreach(&txn_r, (MDB_val *)&catalog_iter_prefix) {
+ if (same_catalog(&txn_r, cat_only)) {
+ knot_lmdb_insert(&txn_w, &txn_r.cur_key, &txn_r.cur_val);
+ }
+ }
+ ensure_cat_version(&txn_w, &txn_w);
+ if (txn_r.ret != KNOT_EOK) {
+ knot_lmdb_abort(&txn_r);
+ knot_lmdb_abort(&txn_w);
+ return txn_r.ret;
+ }
+ knot_lmdb_commit(&txn_r);
+ knot_lmdb_commit(&txn_w);
+ return txn_w.ret;
+}
diff --git a/src/knot/catalog/catalog_db.h b/src/knot/catalog/catalog_db.h
new file mode 100644
index 0000000..d0abd3b
--- /dev/null
+++ b/src/knot/catalog/catalog_db.h
@@ -0,0 +1,187 @@
+/* Copyright (C) 2022 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/>.
+ */
+
+#pragma once
+
+#include "knot/journal/knot_lmdb.h"
+#include "libknot/libknot.h"
+
+#define CATALOG_VERSION "1.0"
+#define CATALOG_ZONE_VERSION "2" // must be just one char long
+#define CATALOG_ZONES_LABEL "\x05""zones"
+#define CATALOG_GROUP_LABEL "\x05""group"
+#define CATALOG_GROUP_MAXLEN 255
+
+typedef struct catalog {
+ knot_lmdb_db_t db;
+ knot_lmdb_txn_t *ro_txn; // persistent RO transaction
+ knot_lmdb_txn_t *rw_txn; // temporary RW transaction
+
+ // private
+ knot_lmdb_txn_t *old_ro_txn;
+} catalog_t;
+
+/*!
+ * \brief Append a prefix dname to a dname in a storage.
+ *
+ * \return New dname length.
+ */
+size_t catalog_dname_append(knot_dname_storage_t storage, const knot_dname_t *name);
+
+/*!
+ * \brief Return the number of bytes that subname has more than name.
+ *
+ * \return -1 if subname is not subname of name
+ */
+int catalog_bailiwick_shift(const knot_dname_t *subname, const knot_dname_t *name);
+
+/*!
+ * \brief Initialize catalog structure.
+ *
+ * \param cat Catalog structure.
+ * \param path Path to LMDB for catalog.
+ * \param mapsize Mapsize of the LMDB.
+ */
+void catalog_init(catalog_t *cat, const char *path, size_t mapsize);
+
+/*!
+ * \brief Open the catalog LMDB, create it if not exists.
+ *
+ * \param cat Catlog to be opened.
+ *
+ * \return KNOT_E*
+ */
+int catalog_open(catalog_t *cat);
+
+/*!
+ * \brief Start a temporary RW transaction in the catalog.
+ *
+ * \param cat Catalog in question.
+ *
+ * \return KNOT_E*
+ */
+int catalog_begin(catalog_t *cat);
+
+/*!
+ * \brief End using the temporary RW txn, refresh the persistent RO txn.
+ *
+ * \param cat Catalog in question.
+ *
+ * \return KNOT_E*
+ */
+int catalog_commit(catalog_t *cat);
+
+/*!
+ * \brief Abort temporary RW txn.
+ */
+void catalog_abort(catalog_t *cat);
+
+/*!
+ * \brief Free up old txns.
+ *
+ * \note This must be called after catalog_commit() with a delay of synchronize_rcu().
+ *
+ * \param cat Catalog.
+ */
+void catalog_commit_cleanup(catalog_t *cat);
+
+/*!
+ * \brief Close the catalog and de-init the structure.
+ *
+ * \param cat Catalog to be closed.
+ */
+void catalog_deinit(catalog_t *cat);
+
+/*!
+ * \brief Add a member zone to the catalog database.
+ *
+ * \param cat Catalog to be augmented.
+ * \param member Member zone name.
+ * \param owner Owner of the PTR record in catalog zone, respective to the member zone.
+ * \param catzone Name of the catalog zone whose it's the member.
+ * \param group Configuration group of the member.
+ *
+ * \return KNOT_E*
+ */
+int catalog_add(catalog_t *cat, const knot_dname_t *member,
+ const knot_dname_t *owner, const knot_dname_t *catzone,
+ const char *group);
+
+/*!
+ * \brief Delete a member zone from the catalog database.
+ *
+ * \param cat Catalog to be removed from.
+ * \param member Member zone to be removed.
+ *
+ * \return KNOT_E*
+ */
+int catalog_del(catalog_t *cat, const knot_dname_t *member);
+
+/*!
+ * \brief Find catz name of the catalog owning this member.
+ *
+ * \note This function may be called in multithreaded operation.
+ *
+ * \param cat Catalog database.
+ * \param member Member to search for.
+ * \param catz Out: name of catalog zone it resides in.
+ * \param group Out: configuration group the member resides in.
+ * \param tofree Out: a pointer that has to be freed later.
+ *
+ * \return KNOT_E*
+ */
+int catalog_get_catz(catalog_t *cat, const knot_dname_t *member,
+ const knot_dname_t **catz, const char **group, void **tofree);
+
+/*!
+ * \brief Check if this member exists in any catalog zone.
+ */
+bool catalog_has_member(catalog_t *cat, const knot_dname_t *member);
+
+/*!
+ * \brief Check if exactly this record (member, owner, catz) is in catalog DB.
+ */
+bool catalog_contains_exact(catalog_t *cat, const knot_dname_t *member,
+ const knot_dname_t *owner, const knot_dname_t *catz);
+
+typedef int (*catalog_apply_cb_t)(const knot_dname_t *member, const knot_dname_t *owner,
+ const knot_dname_t *catz, const char *group, void *ctx);
+/*!
+ * \brief Iterate through catalog database, applying callback.
+ *
+ * \param cat Catalog to be iterated.
+ * \param for_member (Optional) Iterate only on records for this member name.
+ * \param cb Callback to be called.
+ * \param ctx Context for this callback.
+ * \param rw Use read-write transaction.
+ *
+ * \return KNOT_E*
+ */
+int catalog_apply(catalog_t *cat, const knot_dname_t *for_member,
+ catalog_apply_cb_t cb, void *ctx, bool rw);
+
+/*!
+ * \brief Copy records from one catalog database to other.
+ *
+ * \param from Catalog DB to copy from.
+ * \param to Catalog DB to copy to.
+ * \param cat_only Optional: copy only records for this catalog zone.
+ * \param read_rw_txn Use RW txn for read operations.
+ *
+ * \return KNOT_E*
+ */
+int catalog_copy(knot_lmdb_db_t *from, knot_lmdb_db_t *to,
+ const knot_dname_t *cat_only, bool read_rw_txn);
diff --git a/src/knot/catalog/catalog_update.c b/src/knot/catalog/catalog_update.c
new file mode 100644
index 0000000..50f38cb
--- /dev/null
+++ b/src/knot/catalog/catalog_update.c
@@ -0,0 +1,407 @@
+/* Copyright (C) 2022 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 <signal.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "knot/catalog/catalog_update.h"
+#include "knot/common/log.h"
+#include "knot/conf/base.h"
+#include "knot/server/server.h"
+
+int catalog_update_init(catalog_update_t *u)
+{
+ u->upd = trie_create(NULL);
+ if (u->upd == NULL) {
+ return KNOT_ENOMEM;
+ }
+ pthread_mutex_init(&u->mutex, 0);
+ u->error = KNOT_EOK;
+ return KNOT_EOK;
+}
+
+catalog_update_t *catalog_update_new(void)
+{
+ catalog_update_t *u = calloc(1, sizeof(*u));
+ if (u != NULL) {
+ int ret = catalog_update_init(u);
+ if (ret != KNOT_EOK) {
+ free(u);
+ u = NULL;
+ }
+ }
+ return u;
+}
+
+static void catalog_upd_val_free(catalog_upd_val_t *val)
+{
+ free(val->add_owner);
+ free(val->rem_owner);
+ free(val->new_group);
+ free(val);
+}
+
+static int freecb(trie_val_t *tval, _unused_ void *unused)
+{
+ catalog_upd_val_t *val = *tval;
+ if (val != NULL) {
+ catalog_upd_val_free(val);
+ }
+ return 0;
+}
+
+void catalog_update_clear(catalog_update_t *u)
+{
+ trie_apply(u->upd, freecb, NULL);
+ trie_clear(u->upd);
+ u->error = KNOT_EOK;
+}
+
+void catalog_update_deinit(catalog_update_t *u)
+{
+ pthread_mutex_destroy(&u->mutex);
+ trie_free(u->upd);
+}
+
+void catalog_update_free(catalog_update_t *u)
+{
+ if (u != NULL) {
+ catalog_update_deinit(u);
+ free(u);
+ }
+}
+
+static catalog_upd_val_t *upd_val_new(const knot_dname_t *member, int bail,
+ const knot_dname_t *owner, catalog_upd_type_t type)
+{
+ assert(bail <= (int)knot_dname_size(owner));
+ size_t member_size = knot_dname_size(member);
+
+ catalog_upd_val_t *val = malloc(sizeof(*val) + member_size);
+ if (val == NULL) {
+ return NULL;
+ }
+ val->member = (knot_dname_t *)(val + 1);
+ memcpy(val->member, member, member_size);
+ knot_dname_t *owner_cpy = knot_dname_copy(owner, NULL);
+ if (owner_cpy == NULL) {
+ free(val);
+ return NULL;
+ }
+ val->type = type;
+ val->new_group = NULL;
+ if (type == CAT_UPD_REM) {
+ val->add_owner = NULL;
+ val->add_catz = NULL;
+ val->rem_owner = owner_cpy;
+ val->rem_catz = owner_cpy + bail;
+ } else {
+ val->add_owner = owner_cpy;
+ val->add_catz = owner_cpy + bail;
+ val->rem_owner = NULL;
+ val->rem_catz = NULL;
+ }
+ return val;
+}
+
+static const knot_dname_t *get_uniq(const knot_dname_t *ptr_owner,
+ const knot_dname_t *catz)
+{
+ int labels = knot_dname_labels(ptr_owner, NULL);
+ labels -= knot_dname_labels(catz, NULL);
+ assert(labels >= 2);
+ return ptr_owner + knot_dname_prefixlen(ptr_owner, labels - 2, NULL);
+}
+
+static bool same_uniq(const knot_dname_t *owner1, const knot_dname_t *catz1,
+ const knot_dname_t *owner2, const knot_dname_t *catz2)
+{
+ const knot_dname_t *uniq1 = get_uniq(owner1, catz1), *uniq2 = get_uniq(owner2, catz2);
+ if (*uniq1 != *uniq2) {
+ return false;
+ }
+ return memcmp(uniq1 + 1, uniq2 + 1, *uniq1) == 0;
+}
+
+static int upd_val_update(catalog_upd_val_t *val, int bail,
+ const knot_dname_t *owner, bool rem)
+{
+ if ((rem && val->type != CAT_UPD_ADD) ||
+ (!rem && val->type != CAT_UPD_REM)) {
+ log_zone_error(val->member, "duplicate addition/removal of the member node, ignoring");
+ return KNOT_EOK;
+ }
+ knot_dname_t *owner_cpy = knot_dname_copy(owner, NULL);
+ if (owner_cpy == NULL) {
+ return KNOT_ENOMEM;
+ }
+ if (rem) {
+ val->rem_owner = owner_cpy;
+ val->rem_catz = owner_cpy + bail;
+ } else {
+ val->add_owner = owner_cpy;
+ val->add_catz = owner_cpy + bail;
+ }
+ if (same_uniq(val->rem_owner, val->rem_catz, val->add_owner, val->add_catz)) {
+ val->type = CAT_UPD_MINOR;
+ } else {
+ val->type = CAT_UPD_UNIQ;
+ }
+ return KNOT_EOK;
+}
+
+static int upd_val_set_prop(catalog_upd_val_t *val, const knot_dname_t *check_ow,
+ const knot_dname_t *check_catz, const char *group,
+ size_t group_len)
+{
+ if (check_catz != NULL) {
+ if (val->type == CAT_UPD_REM ||
+ !knot_dname_is_equal(check_ow, val->add_owner) || // TODO consider removing those checks. Are they worth the performance?
+ !knot_dname_is_equal(check_catz, val->add_catz)) {
+ return KNOT_EOK; // ignore invalid property set
+ }
+ }
+ if (val->new_group != NULL) {
+ free(val->new_group);
+ }
+ val->new_group = strndup(group, group_len);
+ return val->new_group == NULL ? KNOT_ENOMEM : KNOT_EOK;
+}
+
+int catalog_update_add(catalog_update_t *u, const knot_dname_t *member,
+ const knot_dname_t *owner, const knot_dname_t *catzone,
+ catalog_upd_type_t type, const char *group,
+ size_t group_len, catalog_t *check_rem)
+{
+ int bail = catalog_bailiwick_shift(owner, catzone);
+ if (bail < 0) {
+ return KNOT_EOUTOFZONE;
+ }
+ assert(bail >= 0 && bail <= KNOT_DNAME_MAXLEN);
+
+ knot_dname_storage_t lf_storage;
+ uint8_t *lf = knot_dname_lf(member, lf_storage);
+
+ trie_val_t *found = trie_get_try(u->upd, lf + 1, lf[0]);
+
+ if ((type == CAT_UPD_REM || type == CAT_UPD_PROP) && check_rem != NULL &&
+ !catalog_contains_exact(check_rem, member, owner, catzone)) {
+ if (found == NULL) {
+ // we need to perform this check immediately because
+ // garbage removal would block legitimate removal
+ return KNOT_EOK;
+ }
+ if (type == CAT_UPD_REM) {
+ catalog_upd_val_t *val = *found;
+ catalog_upd_val_free(val);
+ trie_del(u->upd, lf + 1, lf[0], NULL);
+ return KNOT_EOK;
+ }
+ }
+
+ if (found != NULL) {
+ catalog_upd_val_t *val = *found;
+ assert(knot_dname_is_equal(val->member, member));
+ if (type == CAT_UPD_PROP) {
+ return upd_val_set_prop(val, owner, catzone, group, group_len);
+ } else {
+ return upd_val_update(val, bail, owner, type == CAT_UPD_REM);
+ }
+ }
+
+ catalog_upd_val_t *val = upd_val_new(member, bail, owner, type);
+ if (val == NULL) {
+ return KNOT_ENOMEM;
+ }
+ if (group_len > 0) {
+ int ret = upd_val_set_prop(val, NULL, NULL, group, group_len);
+ if (ret != KNOT_EOK) {
+ catalog_upd_val_free(val);
+ return ret;
+ }
+ }
+ trie_val_t *added = trie_get_ins(u->upd, lf + 1, lf[0]);
+ if (added == NULL) {
+ catalog_upd_val_free(val);
+ return KNOT_ENOMEM;
+ }
+ assert(*added == NULL);
+ *added = val;
+ return KNOT_EOK;
+}
+
+catalog_upd_val_t *catalog_update_get(catalog_update_t *u, const knot_dname_t *member)
+{
+ knot_dname_storage_t lf_storage;
+ uint8_t *lf = knot_dname_lf(member, lf_storage);
+
+ trie_val_t *found = trie_get_try(u->upd, lf + 1, lf[0]);
+ return found == NULL ? NULL : *(catalog_upd_val_t **)found;
+}
+
+static bool check_member(catalog_upd_val_t *val, conf_t *conf, catalog_t *cat)
+{
+ if (val->type == CAT_UPD_REM || val->type == CAT_UPD_INVALID || val->type == CAT_UPD_PROP) {
+ return true;
+ }
+ if (!conf_rawid_exists(conf, C_ZONE, val->add_catz, knot_dname_size(val->add_catz))) {
+ knot_dname_txt_storage_t cat_str;
+ (void)knot_dname_to_str(cat_str, val->add_catz, sizeof(cat_str));
+ log_zone_error(val->member, "catalog template zone '%s' not configured, ignoring", cat_str);
+ return false;
+ }
+ if (conf_rawid_exists(conf, C_ZONE, val->member, knot_dname_size(val->member))) {
+ log_zone_error(val->member, "member zone already configured, ignoring");
+ return false;
+ }
+ if (val->type == CAT_UPD_ADD && catalog_has_member(cat, val->member)) {
+ log_zone_error(val->member, "member zone already configured by catalog, ignoring");
+ return false;
+ }
+ return true;
+}
+
+typedef struct {
+ conf_t *conf;
+ catalog_update_t *cup;
+} rem_conflict_ctx_t;
+
+static int rem_conf_conflict(const knot_dname_t *mem, const knot_dname_t *ow,
+ const knot_dname_t *cz, _unused_ const char *gr, void *ctx)
+{
+ rem_conflict_ctx_t *rcctx = ctx;
+
+ if (conf_rawid_exists(rcctx->conf, C_ZONE, mem, knot_dname_size(mem))) {
+ return catalog_update_add(rcctx->cup, mem, ow, cz, CAT_UPD_REM, NULL, 0, NULL);
+ }
+ return KNOT_EOK;
+}
+
+void catalog_update_finalize(catalog_update_t *u, catalog_t *cat, conf_t *conf)
+{
+ catalog_it_t *it = catalog_it_begin(u);
+ while (!catalog_it_finished(it)) {
+ catalog_upd_val_t *val = catalog_it_val(it);
+ if (!check_member(val, conf, cat)) {
+ val->type = (val->type == CAT_UPD_ADD ? CAT_UPD_INVALID : CAT_UPD_REM);
+ }
+ catalog_it_next(it);
+ }
+ catalog_it_free(it);
+
+ // This checks if the configuration file has not changed in the way
+ // it conflicts with existing member zone and let config take precedence.
+ if (cat->ro_txn != NULL) {
+ rem_conflict_ctx_t rcctx = { conf, u };
+ (void)catalog_apply(cat, NULL, rem_conf_conflict, &rcctx, false);
+ }
+}
+
+int catalog_update_commit(catalog_update_t *u, catalog_t *cat)
+{
+ catalog_it_t *it = catalog_it_begin(u);
+ if (catalog_it_finished(it)) {
+ catalog_it_free(it);
+ return KNOT_EOK;
+ }
+ int ret = catalog_begin(cat);
+ while (!catalog_it_finished(it) && ret == KNOT_EOK) {
+ catalog_upd_val_t *val = catalog_it_val(it);
+ switch (val->type) {
+ case CAT_UPD_ADD:
+ case CAT_UPD_MINOR: // catalog_add will simply update/overwrite existing data
+ case CAT_UPD_UNIQ:
+ case CAT_UPD_PROP:
+ ret = catalog_add(cat, val->member, val->add_owner, val->add_catz,
+ val->new_group == NULL ? "" : val->new_group);
+ break;
+ case CAT_UPD_REM:
+ ret = catalog_del(cat, val->member);
+ break;
+ case CAT_UPD_INVALID:
+ break; // no action
+ default:
+ assert(0);
+ ret = KNOT_ERROR;
+ }
+ catalog_it_next(it);
+ }
+ catalog_it_free(it);
+ if (ret == KNOT_EOK) {
+ ret = catalog_commit(cat);
+ } else {
+ catalog_abort(cat);
+ }
+ return ret;
+}
+
+typedef struct {
+ const knot_dname_t *zone;
+ catalog_update_t *u;
+} del_all_ctx_t;
+
+static int del_all_cb(const knot_dname_t *member, const knot_dname_t *owner,
+ const knot_dname_t *catz, _unused_ const char *group, void *dactx)
+{
+ del_all_ctx_t *ctx = dactx;
+ if (knot_dname_is_equal(catz, ctx->zone)) {
+ // TODO possible speedup by indexing which member zones belong to a catalog zone
+ return catalog_update_add(ctx->u, member, owner, catz, CAT_UPD_REM, NULL, 0, NULL);
+ } else {
+ return KNOT_EOK;
+ }
+}
+
+int catalog_update_del_all(catalog_update_t *u, catalog_t *cat, const knot_dname_t *zone, ssize_t *upd_count)
+{
+ pthread_mutex_lock(&u->mutex);
+ del_all_ctx_t ctx = { zone, u };
+ *upd_count -= trie_weight(u->upd);
+ int ret = catalog_apply(cat, NULL, del_all_cb, &ctx, false);
+ *upd_count += trie_weight(u->upd);
+ pthread_mutex_unlock(&u->mutex);
+ return ret;
+}
+
+int catalog_zone_purge(server_t *server, conf_t *conf, const knot_dname_t *zone)
+{
+ assert(server);
+ assert(zone);
+
+ if (server->catalog.ro_txn == NULL) {
+ return KNOT_EOK; // no catalog at all
+ }
+
+ if (conf != NULL) {
+ conf_val_t role = conf_zone_get(conf, C_CATALOG_ROLE, zone);
+ if (conf_opt(&role) != CATALOG_ROLE_INTERPRET) {
+ return KNOT_EOK;
+ }
+ }
+
+ ssize_t members = 0;
+ int ret = catalog_update_del_all(&server->catalog_upd, &server->catalog, zone, &members);
+ if (ret == KNOT_EOK && members > 0) {
+ log_zone_info(zone, "catalog zone purged, %zd member zones deconfigured", members);
+ server->catalog_upd_signal = true;
+ if (kill(getpid(), SIGUSR1) != 0) {
+ ret = knot_map_errno();
+ }
+ }
+ return ret;
+}
diff --git a/src/knot/catalog/catalog_update.h b/src/knot/catalog/catalog_update.h
new file mode 100644
index 0000000..3726372
--- /dev/null
+++ b/src/knot/catalog/catalog_update.h
@@ -0,0 +1,171 @@
+/* Copyright (C) 2021 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/>.
+ */
+
+#pragma once
+
+#include "contrib/qp-trie/trie.h"
+#include "knot/catalog/catalog_db.h"
+#include "knot/conf/conf.h"
+
+struct server; // "knot/server/server.h" causes preprocessor problems when included.
+
+typedef enum {
+ CAT_UPD_INVALID, // invalid value
+ CAT_UPD_ADD, // member addition
+ CAT_UPD_REM, // member removal
+ CAT_UPD_MINOR, // owner or catzone change, uniqID preserved
+ CAT_UPD_UNIQ, // uniqID change
+ CAT_UPD_PROP, // ONLY change of properties of existing member
+ CAT_UPD_MAX, // number of options in the enum
+} catalog_upd_type_t;
+
+typedef struct catalog_upd_val {
+ knot_dname_t *member; // name of catalog member zone
+ catalog_upd_type_t type; // what kind of update this is
+
+ knot_dname_t *rem_owner; // owner of PTR record being removed
+ knot_dname_t *rem_catz; // catalog zone the member being removed from
+ knot_dname_t *add_owner; // owner of PTR record being added
+ knot_dname_t *add_catz; // catalog zone the member being added to
+
+ char *new_group; // the desired configuration group for the member
+} catalog_upd_val_t;
+
+typedef struct {
+ trie_t *upd; // tree of catalog_upd_val_t, that gonna be changed in catalog
+ int error; // error occurred during generating of upd
+ pthread_mutex_t mutex; // lock for accessing this struct
+} catalog_update_t;
+
+/*!
+ * \brief Initialize catalog update structure.
+ *
+ * \param u Catalog update to be initialized.
+ *
+ * \return KNOT_EOK, KNOT_ENOMEM
+ */
+int catalog_update_init(catalog_update_t *u);
+catalog_update_t *catalog_update_new(void);
+
+/*!
+ * \brief Clear contents of catalog update structure.
+ *
+ * \param u Catalog update structure to be cleared.
+ */
+void catalog_update_clear(catalog_update_t *u);
+
+/*!
+ * \brief Free catalog update structure.
+ *
+ * \param u Catalog update structure.
+ */
+void catalog_update_deinit(catalog_update_t *u);
+void catalog_update_free(catalog_update_t *u);
+
+/*!
+ * \brief Add a new record to catalog update structure.
+ *
+ * \param u Catalog update.
+ * \param member Member zone name to be added.
+ * \param owner Owner of respective PTR record.
+ * \param catzone Catalog zone holding the member.
+ * \param type CAT_UPD_REM, CAT_UPD_ADD, CAT_UPD_PROP.
+ * \param group Optional: member group property value.
+ * \param group_len Length of 'group' string (if not NULL).
+ * \param check_rem Check catalog DB for existing record to be removed.
+ *
+ * \return KNOT_E*
+ */
+int catalog_update_add(catalog_update_t *u, const knot_dname_t *member,
+ const knot_dname_t *owner, const knot_dname_t *catzone,
+ catalog_upd_type_t type, const char *group,
+ size_t group_len, catalog_t *check_rem);
+
+/*!
+ * \brief Read catalog update record for given member zone.
+ *
+ * \param u Catalog update.
+ * \param member Member zone name.
+ * \param remove Search in remove section.
+ *
+ * \return Found update record for given member zone; or NULL.
+ */
+catalog_upd_val_t *catalog_update_get(catalog_update_t *u, const knot_dname_t *member);
+
+/*!
+ * \brief Catalog update iteration.
+ */
+typedef trie_it_t catalog_it_t;
+
+inline static catalog_it_t *catalog_it_begin(catalog_update_t *u)
+{
+ return trie_it_begin(u->upd);
+}
+
+inline static catalog_upd_val_t *catalog_it_val(catalog_it_t *it)
+{
+ return *(catalog_upd_val_t **)trie_it_val(it);
+}
+
+inline static bool catalog_it_finished(catalog_it_t *it)
+{
+ return it == NULL || trie_it_finished(it);
+}
+
+#define catalog_it_next trie_it_next
+#define catalog_it_free trie_it_free
+
+/*!
+ * \brief Check Catalog update for conflicts with conf or other catalogs.
+ *
+ * \param u Catalog update to be aligned in-place.
+ * \param cat Catalog DB to check against.
+ * \param conf Relevant configuration.
+ */
+void catalog_update_finalize(catalog_update_t *u, catalog_t *cat, conf_t *conf);
+
+/*!
+ * \brief Put changes from Catalog Update into persistent Catalog database.
+ *
+ * \param u Catalog update to be committed.
+ * \param cat Catalog to be updated.
+ *
+ * \return KNOT_E*
+ */
+int catalog_update_commit(catalog_update_t *u, catalog_t *cat);
+
+/*!
+ * \brief Add to catalog update removals of all member zones of a single catalog zone.
+ *
+ * \param u Catalog update to be updated.
+ * \param cat Catalog database to be iterated.
+ * \param zone Name of catalog zone whose members gonna be removed.
+ * \param upd_count Output: number of resulting updates to catalog database.
+ *
+ * \return KNOT_E*
+ */
+int catalog_update_del_all(catalog_update_t *u, catalog_t *cat, const knot_dname_t *zone, ssize_t *upd_count);
+
+/*!
+ * \brief Destroy all members of specified catalog zone.
+ *
+ * \param server Server with catalog DB.
+ * \param conf Optional: check conf to skip if zone not catalog.
+ * \param zone Catalog zone name.
+ *
+ * \return KNOT_E*
+ */
+int catalog_zone_purge(struct server *server, conf_t *conf, const knot_dname_t *zone);
diff --git a/src/knot/catalog/generate.c b/src/knot/catalog/generate.c
new file mode 100644
index 0000000..e05442b
--- /dev/null
+++ b/src/knot/catalog/generate.c
@@ -0,0 +1,346 @@
+/* Copyright (C) 2022 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 <string.h>
+
+#include "knot/catalog/generate.h"
+#include "knot/common/log.h"
+#include "knot/updates/zone-update.h"
+#include "knot/zone/zonedb.h"
+#include "contrib/openbsd/siphash.h"
+#include "contrib/wire_ctx.h"
+
+static knot_dname_t *catalog_member_owner(const knot_dname_t *member,
+ const knot_dname_t *catzone,
+ time_t member_time)
+{
+ SIPHASH_CTX hash;
+ SIPHASH_KEY shkey = { 0 }; // only used for hashing -> zero key
+ SipHash24_Init(&hash, &shkey);
+ SipHash24_Update(&hash, member, knot_dname_size(member));
+ uint64_t u64time = htobe64(member_time);
+ SipHash24_Update(&hash, &u64time, sizeof(u64time));
+ uint64_t hashres = SipHash24_End(&hash);
+
+ char *hexhash = bin_to_hex((uint8_t *)&hashres, sizeof(hashres), false);
+ if (hexhash == NULL) {
+ return NULL;
+ }
+ size_t hexlen = strlen(hexhash);
+ assert(hexlen == 16);
+ size_t zoneslen = knot_dname_size((uint8_t *)CATALOG_ZONES_LABEL);
+ assert(hexlen <= KNOT_DNAME_MAXLABELLEN && zoneslen <= KNOT_DNAME_MAXLABELLEN);
+ size_t catzlen = knot_dname_size(catzone);
+
+ size_t outlen = hexlen + zoneslen + catzlen;
+ knot_dname_t *out;
+ if (outlen > KNOT_DNAME_MAXLEN || (out = malloc(outlen)) == NULL) {
+ free(hexhash);
+ return NULL;
+ }
+
+ wire_ctx_t wire = wire_ctx_init(out, outlen);
+ wire_ctx_write_u8(&wire, hexlen);
+ wire_ctx_write(&wire, hexhash, hexlen);
+ wire_ctx_write(&wire, CATALOG_ZONES_LABEL, zoneslen);
+ wire_ctx_skip(&wire, -1);
+ wire_ctx_write(&wire, catzone, catzlen);
+ assert(wire.error == KNOT_EOK);
+
+ free(hexhash);
+ return out;
+}
+
+static bool same_group(zone_t *old_z, zone_t *new_z)
+{
+ if (old_z->catalog_group == NULL || new_z->catalog_group == NULL) {
+ return (old_z->catalog_group == new_z->catalog_group);
+ } else {
+ return (strcmp(old_z->catalog_group, new_z->catalog_group) == 0);
+ }
+}
+
+void catalogs_generate(struct knot_zonedb *db_new, struct knot_zonedb *db_old)
+{
+ // general comment: catz->contents!=NULL means incremental update of catalog
+
+ if (db_old != NULL) {
+ knot_zonedb_iter_t *it = knot_zonedb_iter_begin(db_old);
+ while (!knot_zonedb_iter_finished(it)) {
+ zone_t *zone = knot_zonedb_iter_val(it);
+ knot_dname_t *cg = zone->catalog_gen;
+ if (cg != NULL && knot_zonedb_find(db_new, zone->name) == NULL) {
+ zone_t *catz = knot_zonedb_find(db_new, cg);
+ if (catz != NULL && catz->contents != NULL) {
+ assert(catz->cat_members != NULL); // if this failed to allocate, catz wasn't added to zonedb
+ knot_dname_t *owner = catalog_member_owner(zone->name, cg, zone->timers.catalog_member);
+ if (owner == NULL) {
+ catz->cat_members->error = KNOT_ENOENT;
+ knot_zonedb_iter_next(it);
+ continue;
+ }
+ int ret = catalog_update_add(catz->cat_members, zone->name, owner,
+ cg, CAT_UPD_REM, NULL, 0, NULL);
+ free(owner);
+ if (ret != KNOT_EOK) {
+ catz->cat_members->error = ret;
+ } else {
+ zone_events_schedule_now(catz, ZONE_EVENT_LOAD);
+ }
+ }
+ }
+ knot_zonedb_iter_next(it);
+ }
+ knot_zonedb_iter_free(it);
+ }
+
+ knot_zonedb_iter_t *it = knot_zonedb_iter_begin(db_new);
+ while (!knot_zonedb_iter_finished(it)) {
+ zone_t *zone = knot_zonedb_iter_val(it);
+ knot_dname_t *cg = zone->catalog_gen;
+ if (cg == NULL) {
+ knot_zonedb_iter_next(it);
+ continue;
+ }
+ zone_t *catz = knot_zonedb_find(db_new, cg);
+ zone_t *old = knot_zonedb_find(db_old, zone->name);
+ knot_dname_t *owner = catalog_member_owner(zone->name, cg, zone->timers.catalog_member);
+ size_t cgroup_size = zone->catalog_group == NULL ? 0 : strlen(zone->catalog_group);
+ if (catz == NULL) {
+ log_zone_warning(zone->name, "member zone belongs to non-existing catalog zone");
+ } else if (catz->contents == NULL || old == NULL) {
+ assert(catz->cat_members != NULL);
+ if (owner == NULL) {
+ catz->cat_members->error = KNOT_ENOENT;
+ knot_zonedb_iter_next(it);
+ continue;
+ }
+ int ret = catalog_update_add(catz->cat_members, zone->name, owner,
+ cg, CAT_UPD_ADD, zone->catalog_group,
+ cgroup_size, NULL);
+ if (ret != KNOT_EOK) {
+ catz->cat_members->error = ret;
+ } else {
+ zone_events_schedule_now(catz, ZONE_EVENT_LOAD);
+ }
+ } else if (!same_group(zone, old)) {
+ int ret = catalog_update_add(catz->cat_members, zone->name, owner,
+ cg, CAT_UPD_PROP, zone->catalog_group,
+ cgroup_size, NULL);
+ if (ret != KNOT_EOK) {
+ catz->cat_members->error = ret;
+ } else {
+ zone_events_schedule_now(catz, ZONE_EVENT_LOAD);
+ }
+ }
+ free(owner);
+ knot_zonedb_iter_next(it);
+ }
+ knot_zonedb_iter_free(it);
+}
+
+static void set_rdata(knot_rrset_t *rrset, uint8_t *data, uint16_t len)
+{
+ knot_rdata_init(rrset->rrs.rdata, len, data);
+ rrset->rrs.size = knot_rdata_size(len);
+}
+
+#define def_txt_owner(ptr_owner) \
+ knot_dname_storage_t txt_owner = "\x05""group"; \
+ size_t _ptr_ow_len = knot_dname_size(ptr_owner); \
+ size_t _ptr_ow_ind = strlen((const char *)txt_owner); \
+ if (_ptr_ow_ind + _ptr_ow_len > sizeof(txt_owner)) { \
+ return KNOT_ERANGE; \
+ } \
+ memcpy(txt_owner + _ptr_ow_ind, (ptr_owner), _ptr_ow_len);
+
+static int add_group_txt(const knot_dname_t *ptr_owner, const char *group,
+ zone_contents_t *conts, zone_update_t *up)
+{
+ assert((conts == NULL) != (up == NULL));
+ size_t group_len;
+ if (group == NULL || (group_len = strlen(group)) < 1) {
+ return KNOT_EOK;
+ }
+ assert(group_len <= 255);
+
+ def_txt_owner(ptr_owner);
+
+ uint8_t data[256] = { group_len };
+ memcpy(data + 1, group, group_len);
+
+ knot_rrset_t txt;
+ knot_rrset_init(&txt, txt_owner, KNOT_RRTYPE_TXT, KNOT_CLASS_IN, 0);
+ uint8_t txt_rd[256] = { 0 };
+ txt.rrs.rdata = (knot_rdata_t *)txt_rd;
+ txt.rrs.count = 1;
+ set_rdata(&txt, data, 1 + group_len );
+
+ int ret;
+ if (conts != NULL) {
+ zone_node_t *unused = NULL;
+ ret = zone_contents_add_rr(conts, &txt, &unused);
+ } else {
+ ret = zone_update_add(up, &txt);
+ }
+
+ return ret;
+}
+
+static int rem_group_txt(const knot_dname_t *ptr_owner, zone_update_t *up)
+{
+ def_txt_owner(ptr_owner);
+
+ int ret = zone_update_remove_rrset(up, txt_owner, KNOT_RRTYPE_TXT);
+ if (ret == KNOT_ENOENT || ret == KNOT_ENONODE) {
+ ret = KNOT_EOK;
+ }
+
+ return ret;
+}
+
+struct zone_contents *catalog_update_to_zone(catalog_update_t *u, const knot_dname_t *catzone,
+ uint32_t soa_serial)
+{
+ if (u->error != KNOT_EOK) {
+ return NULL;
+ }
+ zone_contents_t *c = zone_contents_new(catzone, true);
+ if (c == NULL) {
+ return c;
+ }
+
+ zone_node_t *unused = NULL;
+ uint8_t invalid[9] = "\x07""invalid";
+ uint8_t version[9] = "\x07""version";
+ uint8_t cat_version[2] = "\x01" CATALOG_ZONE_VERSION;
+
+ // prepare common rrset with one rdata item
+ uint8_t rdata[256] = { 0 };
+ knot_rrset_t rrset;
+ knot_rrset_init(&rrset, (knot_dname_t *)catzone, KNOT_RRTYPE_SOA, KNOT_CLASS_IN, 0);
+ rrset.rrs.rdata = (knot_rdata_t *)rdata;
+ rrset.rrs.count = 1;
+
+ // set catalog zone's SOA
+ uint8_t data[250];
+ assert(sizeof(knot_rdata_t) + sizeof(data) <= sizeof(rdata));
+ wire_ctx_t wire = wire_ctx_init(data, sizeof(data));
+ wire_ctx_write(&wire, invalid, sizeof(invalid));
+ wire_ctx_write(&wire, invalid, sizeof(invalid));
+ wire_ctx_write_u32(&wire, soa_serial);
+ wire_ctx_write_u32(&wire, CATALOG_SOA_REFRESH);
+ wire_ctx_write_u32(&wire, CATALOG_SOA_RETRY);
+ wire_ctx_write_u32(&wire, CATALOG_SOA_EXPIRE);
+ wire_ctx_write_u32(&wire, 0);
+ set_rdata(&rrset, data, wire_ctx_offset(&wire));
+ if (zone_contents_add_rr(c, &rrset, &unused) != KNOT_EOK) {
+ goto fail;
+ }
+
+ // set catalog zone's NS
+ unused = NULL;
+ rrset.type = KNOT_RRTYPE_NS;
+ set_rdata(&rrset, invalid, sizeof(invalid));
+ if (zone_contents_add_rr(c, &rrset, &unused) != KNOT_EOK) {
+ goto fail;
+ }
+
+ // set catalog zone's version TXT
+ unused = NULL;
+ knot_dname_storage_t owner;
+ if (knot_dname_store(owner, version) == 0 || catalog_dname_append(owner, catzone) == 0) {
+ goto fail;
+ }
+ rrset.owner = owner;
+ rrset.type = KNOT_RRTYPE_TXT;
+ set_rdata(&rrset, cat_version, sizeof(cat_version));
+ if (zone_contents_add_rr(c, &rrset, &unused) != KNOT_EOK) {
+ goto fail;
+ }
+
+ // insert member zone PTR records
+ rrset.type = KNOT_RRTYPE_PTR;
+ catalog_it_t *it = catalog_it_begin(u);
+ while (!catalog_it_finished(it)) {
+ catalog_upd_val_t *val = catalog_it_val(it);
+ if (val->add_owner == NULL) {
+ continue;
+ }
+ rrset.owner = val->add_owner;
+ set_rdata(&rrset, val->member, knot_dname_size(val->member));
+ unused = NULL;
+ if (zone_contents_add_rr(c, &rrset, &unused) != KNOT_EOK ||
+ add_group_txt(val->add_owner, val->new_group, c, NULL) != KNOT_EOK) {
+ catalog_it_free(it);
+ goto fail;
+ }
+ catalog_it_next(it);
+ }
+ catalog_it_free(it);
+
+ return c;
+fail:
+ zone_contents_deep_free(c);
+ return NULL;
+}
+
+int catalog_update_to_update(catalog_update_t *u, struct zone_update *zu)
+{
+ knot_rrset_t ptr;
+ knot_rrset_init(&ptr, NULL, KNOT_RRTYPE_PTR, KNOT_CLASS_IN, 0);
+ uint8_t tmp[KNOT_DNAME_MAXLEN + sizeof(knot_rdata_t)];
+ ptr.rrs.rdata = (knot_rdata_t *)tmp;
+ ptr.rrs.count = 1;
+
+ int ret = u->error;
+ catalog_it_t *it = catalog_it_begin(u);
+ while (!catalog_it_finished(it) && ret == KNOT_EOK) {
+ catalog_upd_val_t *val = catalog_it_val(it);
+ if (val->type == CAT_UPD_INVALID) {
+ catalog_it_next(it);
+ continue;
+ }
+
+ if (val->type == CAT_UPD_PROP && knot_dname_is_equal(zu->zone->name, val->add_catz)) {
+ ret = rem_group_txt(val->add_owner, zu);
+ if (ret == KNOT_EOK) {
+ ret = add_group_txt(val->add_owner, val->new_group, NULL, zu);
+ }
+ catalog_it_next(it);
+ continue;
+ }
+
+ set_rdata(&ptr, val->member, knot_dname_size(val->member));
+ if (val->type == CAT_UPD_REM && knot_dname_is_equal(zu->zone->name, val->rem_catz)) {
+ ptr.owner = val->rem_owner;
+ ret = zone_update_remove(zu, &ptr);
+ if (ret == KNOT_EOK) {
+ ret = rem_group_txt(val->rem_owner, zu);
+ }
+ }
+ if (val->type == CAT_UPD_ADD && knot_dname_is_equal(zu->zone->name, val->add_catz)) {
+ ptr.owner = val->add_owner;
+ ret = zone_update_add(zu, &ptr);
+ if (ret == KNOT_EOK) {
+ ret = add_group_txt(val->add_owner, val->new_group, NULL, zu);
+ }
+ }
+ catalog_it_next(it);
+ }
+ catalog_it_free(it);
+ return ret;
+}
diff --git a/src/knot/catalog/generate.h b/src/knot/catalog/generate.h
new file mode 100644
index 0000000..721c1ef
--- /dev/null
+++ b/src/knot/catalog/generate.h
@@ -0,0 +1,56 @@
+/* Copyright (C) 2021 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/>.
+ */
+
+#pragma once
+
+#include "knot/catalog/catalog_update.h"
+
+#define CATALOG_SOA_REFRESH 3600
+#define CATALOG_SOA_RETRY 600
+#define CATALOG_SOA_EXPIRE (INT32_MAX - 1)
+
+struct knot_zonedb;
+
+/*!
+ * \brief Compare old and new zonedb, create incremental catalog upd in each catz->cat_members
+ */
+void catalogs_generate(struct knot_zonedb *db_new, struct knot_zonedb *db_old);
+
+struct zone_contents;
+
+/*!
+ * \brief Generate catalog zone contents from (full) catalog update.
+ *
+ * \param u Catalog update to read.
+ * \param catzone Catalog zone name.
+ * \param soa_serial SOA serial of the generated zone.
+ *
+ * \return Catalog zone contents, or NULL if ENOMEM.
+ */
+struct zone_contents *catalog_update_to_zone(catalog_update_t *u, const knot_dname_t *catzone,
+ uint32_t soa_serial);
+
+struct zone_update;
+
+/*!
+ * \brief Incrementally update catalog zone from catalog update.
+ *
+ * \param u Catalog update to read.
+ * \param zu Zone update to be updated.
+ *
+ * \return KNOT_E*
+ */
+int catalog_update_to_update(catalog_update_t *u, struct zone_update *zu);
diff --git a/src/knot/catalog/interpret.c b/src/knot/catalog/interpret.c
new file mode 100644
index 0000000..e7a5cf0
--- /dev/null
+++ b/src/knot/catalog/interpret.c
@@ -0,0 +1,257 @@
+/* Copyright (C) 2022 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 <pthread.h>
+#include <stdio.h>
+
+#include "knot/catalog/interpret.h"
+#include "knot/journal/serialization.h"
+
+struct cat_upd_ctx;
+typedef int (*cat_interpret_cb_t)(zone_node_t *node, struct cat_upd_ctx *ctx);
+
+typedef struct cat_upd_ctx {
+ catalog_update_t *u;
+ const zone_contents_t *complete_conts;
+ int apex_labels;
+ bool remove;
+ bool zone_diff;
+ catalog_t *check;
+ cat_interpret_cb_t member_cb;
+ cat_interpret_cb_t property_cb;
+} cat_upd_ctx_t;
+
+static bool label_eq(const knot_dname_t *a, const char *_b)
+{
+ const knot_dname_t *b = (const knot_dname_t *)_b;
+ return a[0] == b[0] && memcmp(a + 1, b + 1, a[0]) == 0;
+}
+
+static bool check_zone_version(const zone_contents_t *zone)
+{
+ size_t zone_size = knot_dname_size(zone->apex->owner);
+ knot_dname_t sub[zone_size + 8];
+ memcpy(sub, "\x07""version", 8);
+ memcpy(sub + 8, zone->apex->owner, zone_size);
+
+ const zone_node_t *ver_node = zone_contents_find_node(zone, sub);
+ knot_rdataset_t *ver_rr = node_rdataset(ver_node, KNOT_RRTYPE_TXT);
+ if (ver_rr == NULL) {
+ return false;
+ }
+
+ knot_rdata_t *rdata = ver_rr->rdata;
+ for (int i = 0; i < ver_rr->count; i++) {
+ if (rdata->len == 2 && rdata->data[1] == CATALOG_ZONE_VERSION[0]) {
+ return true;
+ }
+ rdata = knot_rdataset_next(rdata);
+ }
+ return false;
+}
+
+static int interpret_node(zone_node_t *node, void * _ctx)
+{
+ cat_upd_ctx_t *ctx = _ctx;
+
+ int labels_diff = knot_dname_labels(node->owner, NULL) - ctx->apex_labels
+ - 1 /* "zones" label */ - 1 /* unique-N label */;
+ assert(labels_diff >= 0);
+
+ switch (labels_diff) {
+ case 0:
+ return ctx->member_cb(node, ctx);
+ case 1:
+ return ctx->property_cb(node, ctx);
+ default:
+ return KNOT_EOK;
+ }
+}
+
+static int interpret_zone(zone_diff_t *zdiff, cat_upd_ctx_t *ctx)
+{
+ knot_dname_storage_t sub;
+ if (knot_dname_store(sub, (uint8_t *)CATALOG_ZONES_LABEL) == 0 ||
+ catalog_dname_append(sub, zdiff->apex->owner) == 0) {
+ return KNOT_EINVAL;
+ }
+
+ if (zone_tree_get(&zdiff->nodes, sub) == NULL) {
+ return KNOT_EOK;
+ }
+
+ return zone_tree_sub_apply(&zdiff->nodes, sub, true, interpret_node, ctx);
+}
+
+static const knot_dname_t *property_get_member(const zone_node_t *prop_node,
+ const zone_contents_t *complete_conts,
+ const knot_dname_t **owner)
+{
+ assert(prop_node != NULL);
+ knot_rdataset_t *ptr = node_rdataset(prop_node->parent, KNOT_RRTYPE_PTR);
+ if (ptr == NULL) {
+ // fallback: search in provided complete zone contents
+ const knot_dname_t *memb_name = knot_wire_next_label(prop_node->owner, NULL);
+ const zone_node_t *memb_node = zone_contents_find_node(complete_conts, memb_name);
+ ptr = node_rdataset(memb_node, KNOT_RRTYPE_PTR);
+ if (memb_node != NULL) {
+ *owner = memb_node->owner;
+ }
+ } else {
+ *owner = prop_node->parent->owner;
+ }
+ if (*owner == NULL || ptr == NULL || ptr->count != 1) {
+ return NULL;
+ }
+ return knot_ptr_name(ptr->rdata);
+}
+
+static int cat_update_add_memb(zone_node_t *node, cat_upd_ctx_t *ctx)
+{
+ const knot_rdataset_t *ptr = node_rdataset(node, KNOT_RRTYPE_PTR);
+ if (ptr == NULL) {
+ return KNOT_EOK;
+ } else if (ptr->count != 1) {
+ return KNOT_ERROR;
+ }
+
+ const knot_rdataset_t *counter_ptr = node_rdataset(binode_counterpart(node), KNOT_RRTYPE_PTR);
+ if (knot_rdataset_subset(ptr, counter_ptr)) {
+ return KNOT_EOK;
+ }
+
+ knot_rdata_t *rdata = ptr->rdata;
+ int ret = KNOT_EOK;
+ for (int i = 0; ret == KNOT_EOK && i < ptr->count; i++) {
+ const knot_dname_t *member = knot_ptr_name(rdata);
+ ret = catalog_update_add(ctx->u, member, node->owner, ctx->complete_conts->apex->owner,
+ ctx->remove ? CAT_UPD_REM : CAT_UPD_ADD,
+ NULL, 0, ctx->check);
+ rdata = knot_rdataset_next(rdata);
+ }
+ return ret;
+}
+
+static int cat_update_add_grp(zone_node_t *node, cat_upd_ctx_t *ctx)
+{
+ if (!label_eq(node->owner, CATALOG_GROUP_LABEL)) {
+ return KNOT_EOK;
+ }
+
+ const knot_dname_t *owner = NULL;
+ const knot_dname_t *member = property_get_member(node, ctx->complete_conts, &owner);
+ if (member == NULL) {
+ return KNOT_EOK; // just ignore property w/o member
+ }
+
+ const knot_rdataset_t *txt = node_rdataset(node, KNOT_RRTYPE_TXT);
+ if (txt == NULL) {
+ return KNOT_EOK;
+ } else if (txt->count != 1) {
+ return KNOT_ERROR;
+ }
+
+ const knot_rdataset_t *counter_txt = node_rdataset(binode_counterpart(node), KNOT_RRTYPE_TXT);
+ if (knot_rdataset_subset(txt, counter_txt)) {
+ return KNOT_EOK;
+ }
+
+ const char *newgr = "";
+ size_t grlen = 0;
+ if (!ctx->remove) {
+ assert(txt->count == 1);
+ // TXT rdata consists of one or more 1-byte prefixed strings.
+ if (txt->rdata->len != txt->rdata->data[0] + 1) {
+ return KNOT_EMALF;
+ }
+ newgr = (const char *)txt->rdata->data + 1;
+ grlen = txt->rdata->data[0];
+ assert(grlen <= CATALOG_GROUP_MAXLEN);
+ }
+
+ return catalog_update_add(ctx->u, member, owner, ctx->complete_conts->apex->owner,
+ CAT_UPD_PROP, newgr, grlen, ctx->check);
+}
+
+int catalog_update_from_zone(catalog_update_t *u, struct zone_contents *zone,
+ const zone_diff_t *zone_diff,
+ const struct zone_contents *complete_contents,
+ bool remove, catalog_t *check, ssize_t *upd_count)
+{
+ int ret = KNOT_EOK;
+ zone_diff_t zdiff;
+ assert(zone == NULL || zone_diff == NULL);
+ if (zone != NULL) {
+ zone_diff_from_zone(&zdiff, zone);
+ } else {
+ zdiff = *zone_diff;
+ }
+ cat_upd_ctx_t ctx = { u, complete_contents, knot_dname_labels(zdiff.apex->owner, NULL),
+ remove, zone_diff != NULL, check, cat_update_add_memb, cat_update_add_grp };
+
+ pthread_mutex_lock(&u->mutex);
+ *upd_count -= trie_weight(u->upd);
+ if (zone_diff != NULL) {
+ zone_diff_reverse(&zdiff);
+ ctx.remove = true;
+ ret = interpret_zone(&zdiff, &ctx);
+ zone_diff_reverse(&zdiff);
+ ctx.remove = false;
+ ctx.check = NULL;
+ }
+ if (ret == KNOT_EOK) {
+ ret = interpret_zone(&zdiff, &ctx);
+ }
+ *upd_count += trie_weight(u->upd);
+ pthread_mutex_unlock(&u->mutex);
+ return ret;
+}
+
+static int rr_count(const zone_node_t *node, uint16_t type)
+{
+ const knot_rdataset_t *rd = node_rdataset(node, type);
+ return rd == NULL ? 0 : rd->count;
+}
+
+static int member_verify(zone_node_t *node, cat_upd_ctx_t *ctx)
+{
+ return rr_count(node, KNOT_RRTYPE_PTR) > 1 ? KNOT_EISRECORD : KNOT_EOK;
+}
+
+static int prop_verify(zone_node_t *node, cat_upd_ctx_t *ctx)
+{
+ if (label_eq(node->owner, CATALOG_GROUP_LABEL) &&
+ rr_count(node, KNOT_RRTYPE_TXT) > 1) {
+ return KNOT_EISRECORD;
+ }
+
+ return KNOT_EOK;
+}
+
+int catalog_zone_verify(const struct zone_contents *zone)
+{
+ cat_upd_ctx_t ctx = { NULL, zone, knot_dname_labels(zone->apex->owner, NULL),
+ false, false, NULL, member_verify, prop_verify };
+
+ if (!check_zone_version(zone)) {
+ return KNOT_EZONEINVAL;
+ }
+
+ zone_diff_t zdiff;
+ zone_diff_from_zone(&zdiff, zone);
+
+ return interpret_zone(&zdiff, &ctx);
+}
diff --git a/src/knot/catalog/interpret.h b/src/knot/catalog/interpret.h
new file mode 100644
index 0000000..20928b7
--- /dev/null
+++ b/src/knot/catalog/interpret.h
@@ -0,0 +1,52 @@
+/* Copyright (C) 2022 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/>.
+ */
+
+#pragma once
+
+#include "knot/catalog/catalog_update.h"
+
+struct zone_contents;
+struct zone_diff;
+
+/*!
+ * \brief Validate if given zone is valid catalog.
+ *
+ * \param zone Catalog zone in question.
+ *
+ * \retval KNOT_EZONEINVAL Invalid version record.
+ * \retval KNOT_EISRECORD Some of single-record RRSets has multiple RRs.
+ * \return KNOT_EOK All OK.
+ */
+int catalog_zone_verify(const struct zone_contents *zone);
+
+/*!
+ * \brief Iterate over PTR records in given zone contents and add members to catalog update.
+ *
+ * \param u Catalog update to be updated.
+ * \param zone Zone contents to be searched for member PTR records.
+ * \param zone_diff Zone diff to interpret for removals and additions.
+ * \param complete_contents Complete zone contents (zone might be from a changeset).
+ * \param remove Add removals of found member zones.
+ * \param check Optional: existing catalog database to be checked for existence
+ * of such record (useful for removals).
+ * \param upd_count Output: number of resulting updates to catalog database.
+ *
+ * \return KNOT_E*
+ */
+int catalog_update_from_zone(catalog_update_t *u, struct zone_contents *zone,
+ const struct zone_diff *zone_diff,
+ const struct zone_contents *complete_contents,
+ bool remove, catalog_t *check, ssize_t *upd_count);