summaryrefslogtreecommitdiffstats
path: root/src/knot/catalog/catalog_db.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/knot/catalog/catalog_db.c')
-rw-r--r--src/knot/catalog/catalog_db.c347
1 files changed, 347 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;
+}