--- /dev/null
+/* Copyright (C) 2021 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#include "lib/cache/cdb_compat.h"
+#include "lib/cache/cdb_lmdb.h"
+
+#if KR_USE_MDBX
+ #include <libknot/error.h>
+ #include <stdlib.h>
+ #include <string.h>
+#else
+ #include <libknot/db/db_lmdb.h>
+#endif
+
+#if !KR_USE_MDBX
+const knot_db_api_t *kr_cdb_pt2knot_db_api_t(kr_cdb_pt db)
+{
+ return knot_db_lmdb_api();
+}
+#endif
+
+/* The following majority of this C file has only minor differences
+ * from Knot's src/libknot/db/db_lmdb.c
+ * Some mdbx differences get fully covered by cdb_compat.h defines, some don't.
+ */
+#if KR_USE_MDBX
+
+/*! Beware: same name but different struct than in resolver's cdb_lmdb.c */
+struct lmdb_env
+{
+ bool shared;
+ MDB_dbi dbi;
+ MDB_env *env;
+ knot_mm_t *pool;
+};
+
+/*!
+ * \brief Convert error code returned by LMDB to Knot DNS error code.
+ *
+ * LMDB defines own error codes but uses additional ones from libc:
+ * - LMDB errors do not conflict with Knot DNS ones.
+ * - Significant LMDB errors are mapped to Knot DNS ones.
+ * - Standard errors are converted to negative value to match Knot DNS mapping.
+ */
+static int lmdb_error_to_knot(int error)
+{
+ if (error == MDB_SUCCESS) {
+ return KNOT_EOK;
+ }
+
+ if (error == MDB_NOTFOUND) {
+ return KNOT_ENOENT;
+ }
+
+ if (error == MDB_TXN_FULL) {
+ return KNOT_ELIMIT;
+ }
+
+ if (error == MDB_MAP_FULL || error == ENOSPC) {
+ return KNOT_ESPACE;
+ }
+
+ return -abs(error);
+}
+
+static
+int knot_db_lmdb_txn_begin(knot_db_t *db, knot_db_txn_t *txn, knot_db_txn_t *parent,
+ unsigned flags)
+{
+ txn->db = db;
+ txn->txn = NULL;
+
+ unsigned txn_flags = 0;
+ if (flags & KNOT_DB_RDONLY) {
+ txn_flags |= MDB_RDONLY;
+ } else {
+ txn_flags |= MDB_RDWR;
+ }
+
+ MDB_txn *parent_txn = (parent != NULL) ? (MDB_txn *)parent->txn : NULL;
+
+ struct lmdb_env *env = db;
+ int ret = mdb_txn_begin(env->env, parent_txn, txn_flags, (MDB_txn **)&txn->txn);
+ if (ret != MDB_SUCCESS) {
+ return lmdb_error_to_knot(ret);
+ }
+
+ return KNOT_EOK;
+}
+
+static int txn_begin(knot_db_t *db, knot_db_txn_t *txn, unsigned flags)
+{
+ return knot_db_lmdb_txn_begin(db, txn, NULL, flags);
+}
+
+static int txn_commit(knot_db_txn_t *txn)
+{
+ int ret = mdb_txn_commit((MDB_txn *)txn->txn);
+ if (ret != MDB_SUCCESS) {
+ return lmdb_error_to_knot(ret);
+ }
+
+ return KNOT_EOK;
+}
+
+static void txn_abort(knot_db_txn_t *txn)
+{
+ mdb_txn_abort((MDB_txn *)txn->txn);
+}
+
+static knot_db_iter_t *iter_set(knot_db_iter_t *iter, knot_db_val_t *key, unsigned flags)
+{
+ MDB_cursor *cursor = iter;
+
+ MDBX_cursor_op op = MDBX_SET;
+ switch(flags) {
+ case KNOT_DB_NOOP: return cursor;
+ case KNOT_DB_FIRST: op = MDBX_FIRST; break;
+ case KNOT_DB_LAST: op = MDBX_LAST; break;
+ case KNOT_DB_NEXT: op = MDBX_NEXT; break;
+ case KNOT_DB_PREV: op = MDBX_PREV; break;
+ case KNOT_DB_LEQ:
+ case KNOT_DB_GEQ: op = MDBX_SET_RANGE; break;
+ default: break;
+ }
+
+ MDB_val db_key = { 0 };
+ if (key) {
+ db_key = val_knot2mdb(*key);
+ }
+ MDB_val unused_key = { 0 }, unused_val = { 0 };
+
+ int ret = mdb_cursor_get(cursor, key ? &db_key : &unused_key, &unused_val, op);
+
+ /* LEQ is not supported in LMDB, workaround using GEQ. */
+ if (flags == KNOT_DB_LEQ && key) {
+ /* Searched key is after the last key. */
+ if (ret != MDB_SUCCESS) {
+ return iter_set(iter, NULL, KNOT_DB_LAST);
+ }
+ /* If the searched key != matched, get previous. */
+ if ((key->len != db_key.iov_len) ||
+ (memcmp(key->data, db_key.iov_base, key->len) != 0)) {
+ return iter_set(iter, NULL, KNOT_DB_PREV);
+ }
+ }
+
+ if (ret != MDB_SUCCESS) {
+ mdb_cursor_close(cursor);
+ return NULL;
+ }
+
+ return cursor;
+}
+
+static knot_db_iter_t *iter_begin(knot_db_txn_t *txn, unsigned flags)
+{
+ struct lmdb_env *env = txn->db;
+ MDB_cursor *cursor = NULL;
+
+ int ret = mdb_cursor_open(txn->txn, env->dbi, &cursor);
+ if (ret != MDB_SUCCESS) {
+ return NULL;
+ }
+
+ /* Clear sorted flag, as it's always sorted. */
+ flags &= ~KNOT_DB_SORTED;
+
+ return iter_set(cursor, NULL, (flags == 0) ? KNOT_DB_FIRST : flags);
+}
+
+static knot_db_iter_t *iter_next(knot_db_iter_t *iter)
+{
+ return iter_set(iter, NULL, KNOT_DB_NEXT);
+}
+
+static int iter_key(knot_db_iter_t *iter, knot_db_val_t *key)
+{
+ MDB_cursor *cursor = iter;
+
+ MDB_val mdb_key, mdb_val;
+ int ret = mdb_cursor_get(cursor, &mdb_key, &mdb_val, MDB_GET_CURRENT);
+ if (ret != MDB_SUCCESS) {
+ return lmdb_error_to_knot(ret);
+ }
+
+ *key = val_mdb2knot(mdb_key);
+ return KNOT_EOK;
+}
+
+static int iter_val(knot_db_iter_t *iter, knot_db_val_t *val)
+{
+ MDB_cursor *cursor = iter;
+
+ MDB_val mdb_key, mdb_val;
+ int ret = mdb_cursor_get(cursor, &mdb_key, &mdb_val, MDB_GET_CURRENT);
+ if (ret != MDB_SUCCESS) {
+ return lmdb_error_to_knot(ret);
+ }
+
+ *val = val_mdb2knot(mdb_val);
+ return KNOT_EOK;
+}
+
+static void iter_finish(knot_db_iter_t *iter)
+{
+ if (iter == NULL) {
+ return;
+ }
+
+ MDB_cursor *cursor = iter;
+ mdb_cursor_close(cursor);
+}
+
+static int find(knot_db_txn_t *txn, knot_db_val_t *key, knot_db_val_t *val, unsigned flags)
+{
+ knot_db_iter_t *iter = iter_begin(txn, KNOT_DB_NOOP);
+ if (iter == NULL) {
+ return KNOT_ERROR;
+ }
+
+ int ret = KNOT_EOK;
+ if (iter_set(iter, key, flags) == NULL) {
+ return KNOT_ENOENT;
+ } else {
+ ret = iter_val(iter, val);
+ }
+
+ iter_finish(iter);
+ return ret;
+}
+
+static int insert(knot_db_txn_t *txn, knot_db_val_t *key, knot_db_val_t *val, unsigned flags)
+{
+ struct lmdb_env *env = txn->db;
+
+ MDB_val db_key = val_knot2mdb(*key);
+ MDB_val data = val_knot2mdb(*val);
+
+ /* Reserve if only size is declared. */
+ unsigned mdb_flags = 0;
+ if (val->len > 0 && val->data == NULL) {
+ mdb_flags |= MDB_RESERVE;
+ }
+
+ int ret = mdb_put(txn->txn, env->dbi, &db_key, &data, mdb_flags);
+ if (ret != MDB_SUCCESS) {
+ return lmdb_error_to_knot(ret);
+ }
+
+ /* Update the result. */
+ *val = val_mdb2knot(data);
+ return KNOT_EOK;
+}
+
+static int del(knot_db_txn_t *txn, knot_db_val_t *key)
+{
+ struct lmdb_env *env = txn->db;
+ MDB_val db_key = val_knot2mdb(*key);
+ MDB_val data = { 0 };
+
+ int ret = mdb_del(txn->txn, env->dbi, &db_key, &data);
+ if (ret != MDB_SUCCESS) {
+ return lmdb_error_to_knot(ret);
+ }
+
+ return KNOT_EOK;
+}
+
+const knot_db_api_t *kr_cdb_pt2knot_db_api_t(kr_cdb_pt db)
+{
+ static const knot_db_api_t api = {
+ "mdbx",
+ NULL, NULL, //init, deinit,
+ txn_begin, txn_commit, txn_abort,
+ NULL, NULL, //count, clear,
+ find, insert, del,
+ iter_begin, iter_set, iter_next, iter_key, iter_val, iter_finish
+ };
+ return &api;
+}
+
+#endif // KR_USE_MDBX
+
--- /dev/null
+/* Copyright (C) 2021 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#pragma once
+
+#include <libknot/db/db.h>
+
+#if KR_USE_MDBX
+ #include <mdbx.h>
+ // FIXME: investigate mysterious constant-sized memleaks
+#else
+ #include <lmdb.h>
+#endif
+
+#if KR_USE_MDBX
+ #define MDB_env MDBX_env
+ #define mdb_env_create mdbx_env_create
+ #define MDB_SUCCESS 0
+ #define MDB_WRITEMAP MDBX_WRITEMAP
+ #define MDB_MAPASYNC MDBX_UTTERLY_NOSYNC
+ #define MDB_NOTLS MDBX_NOTLS
+ #define mdb_env_open mdbx_env_open
+ #define mdb_filehandle_t mdbx_filehandle_t
+ #define mdb_env_get_fd mdbx_env_get_fd
+ #define MDB_txn MDBX_txn
+ #define mdb_txn_begin mdbx_txn_begin
+ #define mdb_dbi_open mdbx_dbi_open
+ #define MDB_dbi MDBX_dbi
+ #define mdb_txn_commit mdbx_txn_commit
+ #define mdb_env_close mdbx_env_close
+ // Avoid mdbx_env_sync() as it uses some macro magic.
+ #define mdb_env_sync(env, force) mdbx_env_sync_ex((env), (force), false)
+ #define mdb_dbi_close mdbx_dbi_close
+ #define MDB_cursor MDBX_cursor
+ #define MDB_NOTFOUND MDBX_NOTFOUND
+ #define MDB_MAP_FULL MDBX_MAP_FULL
+ #define MDB_TXN_FULL MDBX_TXN_FULL
+ #define mdb_strerror mdbx_strerror
+ // Different field names as well; see val_mdb2knot().
+ #define MDB_val MDBX_val
+ // TODO: can be improved
+ #define mdb_env_set_mapsize mdbx_env_set_mapsize
+ #define MDB_RDONLY MDBX_TXN_RDONLY
+ #define mdb_txn_renew mdbx_txn_renew
+ #define MDB_READERS_FULL MDBX_READERS_FULL
+ #define mdb_reader_check mdbx_reader_check
+ #define mdb_txn_reset mdbx_txn_reset
+ #define mdb_cursor_renew mdbx_cursor_renew
+ #define mdb_cursor_open mdbx_cursor_open
+ #define mdb_cursor_close mdbx_cursor_close
+ #define mdb_cursor_get mdbx_cursor_get
+ #define MDB_SET_RANGE MDBX_SET_RANGE
+ #define mdb_txn_abort mdbx_txn_abort
+ #define MDB_PREV MDBX_PREV
+ #define MDB_NEXT MDBX_NEXT
+ #define mdb_env_get_path mdbx_env_get_path
+ #define mdb_del mdbx_del
+ #define MDB_RESERVE MDBX_RESERVE
+ #define mdb_put mdbx_put
+ #define mdb_get mdbx_get
+ #define mdb_drop mdbx_drop
+ // just an extra field at the end
+ #define MDB_stat MDBX_stat
+ #define MDB_RDWR MDBX_TXN_READWRITE
+ #define MDB_DATANAME MDBX_DATANAME
+ #define mdb_env_info(env, info) \
+ mdbx_env_info_ex((env), NULL, (info), sizeof(MDBX_envinfo))
+ #define MDB_GET_CURRENT MDBX_GET_CURRENT
+#else
+ #define MDB_RDWR 0
+ #define MDB_DATANAME "/data.mdb"
+#endif
+
+/** Conversion between knot and lmdb structs for values. */
+static inline knot_db_val_t val_mdb2knot(MDB_val v)
+{
+ return (knot_db_val_t){
+ #if KR_USE_MDBX
+ .len = v.iov_len, .data = v.iov_base
+ #else
+ .len = v.mv_size, .data = v.mv_data
+ #endif
+ };
+}
+static inline MDB_val val_knot2mdb(knot_db_val_t v)
+{
+ return (MDB_val){
+ #if KR_USE_MDBX
+ .iov_len = v.len, .iov_base = v.data
+ #else
+ .mv_size = v.len, .mv_data = v.data
+ #endif
+ };
+}
+
#include "lib/cache/impl.h"
#include "lib/utils.h"
+#include "lib/cache/cdb_compat.h"
/* Defines */
#define LMDB_DIR_MODE 0770
#define LMDB_FILE_MODE 0660
-#if KR_USE_MDBX
- #include <mdbx.h>
- // FIXME: investigate mysterious constant-sized memleaks
-#else
- #include <lmdb.h>
-#endif
-
-#if KR_USE_MDBX
- #define MDB_env MDBX_env
- #define mdb_env_create mdbx_env_create
- #define MDB_SUCCESS 0
- #define MDB_WRITEMAP MDBX_WRITEMAP
- #define MDB_MAPASYNC MDBX_UTTERLY_NOSYNC
- #define MDB_NOTLS MDBX_NOTLS
- #define mdb_env_open mdbx_env_open
- #define mdb_filehandle_t mdbx_filehandle_t
- #define mdb_env_get_fd mdbx_env_get_fd
- #define MDB_txn MDBX_txn
- #define mdb_txn_begin mdbx_txn_begin
- #define mdb_dbi_open mdbx_dbi_open
- #define MDB_dbi MDBX_dbi
- #define mdb_txn_commit mdbx_txn_commit
- #define mdb_env_close mdbx_env_close
- // Avoid mdbx_env_sync() as it uses some macro magic.
- #define mdb_env_sync(env, force) mdbx_env_sync_ex((env), (force), false)
- #define mdb_dbi_close mdbx_dbi_close
- #define MDB_cursor MDBX_cursor
- #define MDB_NOTFOUND MDBX_NOTFOUND
- #define MDB_MAP_FULL MDBX_MAP_FULL
- #define MDB_TXN_FULL MDBX_TXN_FULL
- #define mdb_strerror mdbx_strerror
- // Different field names as well; see val_mdb2knot().
- #define MDB_val MDBX_val
- // TODO: can be improved
- #define mdb_env_set_mapsize mdbx_env_set_mapsize
- #define MDB_RDONLY MDBX_TXN_RDONLY
- #define mdb_txn_renew mdbx_txn_renew
- #define MDB_READERS_FULL MDBX_READERS_FULL
- #define mdb_reader_check mdbx_reader_check
- #define mdb_txn_reset mdbx_txn_reset
- #define mdb_cursor_renew mdbx_cursor_renew
- #define mdb_cursor_open mdbx_cursor_open
- #define mdb_cursor_close mdbx_cursor_close
- #define mdb_cursor_get mdbx_cursor_get
- #define MDB_SET_RANGE MDBX_SET_RANGE
- #define mdb_txn_abort mdbx_txn_abort
- #define MDB_PREV MDBX_PREV
- #define MDB_NEXT MDBX_NEXT
- #define mdb_env_get_path mdbx_env_get_path
- #define mdb_del mdbx_del
- #define MDB_RESERVE MDBX_RESERVE
- #define mdb_put mdbx_put
- #define mdb_get mdbx_get
- #define mdb_drop mdbx_drop
- // just an extra field at the end
- #define MDB_stat MDBX_stat
- #define MDB_RDWR MDBX_TXN_READWRITE
- #define MDB_DATANAME MDBX_DATANAME
- #define mdb_env_info(env, info) \
- mdbx_env_info_ex((env), NULL, (info), sizeof(MDBX_envinfo))
-#else
- #define MDB_RDWR 0
- #define MDB_DATANAME "/data.mdb"
-#endif
/* TODO: we rely on mirrors of these two structs not changing layout
* in libknot and knot resolver! */
}
}
-/** Conversion between knot and lmdb structs for values. */
-static inline knot_db_val_t val_mdb2knot(MDB_val v)
-{
- return (knot_db_val_t){
- #if KR_USE_MDBX
- .len = v.iov_len, .data = v.iov_base
- #else
- .len = v.mv_size, .data = v.mv_data
- #endif
- };
-}
-static inline MDB_val val_knot2mdb(knot_db_val_t v)
-{
- return (MDB_val){
- #if KR_USE_MDBX
- .iov_len = v.len, .iov_base = v.data
- #else
- .mv_size = v.len, .mv_data = v.data
- #endif
- };
-}
-
/** Refresh mapsize value from file, including env->mapsize.
* It's much lighter than reopen_env(). */
static int refresh_mapsize(struct lmdb_env *env)
/** Conversion between knot and lmdb structs. */
knot_db_t *kr_cdb_pt2knot_db_t(kr_cdb_pt db)
{
-#if KR_USE_MDBX
- #pragma GCC warning "FIXME: we can't convert mdbx to libknot_lmdb_env; GC needs rewriting"
- abort();
-#endif
const struct lmdb_env *kres_db = db2env(db);
struct libknot_lmdb_env *libknot_db = malloc(sizeof(*libknot_db));
if (libknot_db != NULL) {
return &api;
}
+
KR_EXPORT KR_CONST
const struct kr_cdb_api *kr_cdb_lmdb(void);
-/** Create a pointer for knot_db_lmdb_api. You free() it to release it. */
+/** Create a pointer for knot_db_api. You free() it to release it. */
KR_EXPORT
knot_db_t *kr_cdb_pt2knot_db_t(kr_cdb_pt db);
+/** Get a pointer for knot_db_api. You don't release it.
+ *
+ * Some operations aren't generally supported: init, deinit, count, clear.
+ */
+KR_EXPORT
+const knot_db_api_t *kr_cdb_pt2knot_db_api_t(kr_cdb_pt db);
+
libkres_src = files([
'cache/api.c',
+ 'cache/cdb_compat.c',
'cache/cdb_lmdb.c',
'cache/entry_list.c',
'cache/entry_pkt.c',
libkres_headers = files([
'cache/api.h',
'cache/cdb_api.h',
+ 'cache/cdb_compat.h',
'cache/cdb_lmdb.h',
'cache/impl.h',
'defines.h',
return NULL;
}
-int kr_gc_cache_iter(knot_db_t * knot_db, const kr_cache_gc_cfg_t *cfg,
- kr_gc_iter_callback callback, void *ctx)
+int kr_gc_cache_iter(const knot_db_api_t * api, knot_db_t * knot_db,
+ const kr_cache_gc_cfg_t *cfg, kr_gc_iter_callback callback, void *ctx)
{
unsigned int counter_iter = 0;
unsigned int counter_gc_consistent = 0;
knot_db_txn_t txn = { 0 };
knot_db_iter_t *it = NULL;
- const knot_db_api_t *api = knot_db_lmdb_api();
gc_record_info_t info = { 0 };
int64_t now = time(NULL);
typedef int (*kr_gc_iter_callback)(const knot_db_val_t * key,
gc_record_info_t * info, void *ctx);
-int kr_gc_cache_iter(knot_db_t * knot_db, const kr_cache_gc_cfg_t *cfg,
- kr_gc_iter_callback callback, void *ctx);
+int kr_gc_cache_iter(const knot_db_api_t * api, knot_db_t * knot_db,
+ const kr_cache_gc_cfg_t *cfg, kr_gc_iter_callback callback, void *ctx);
/** Return RR type corresponding to the key or negative error code.
*
return KNOT_EOK;
}
+ const knot_db_api_t *api = kr_cdb_pt2knot_db_api_t((*state)->kres_db.db);
//// 2. classify all cache items into categories
// and compute which categories to delete.
gc_timer_t timer_analyze = { 0 }, timer_choose = { 0 }, timer_delete =
gc_timer_start(&timer_analyze);
ctx_compute_categories_t cats = { { 0 }
};
- ret = kr_gc_cache_iter(db, cfg, cb_compute_categories, &cats);
+ ret = kr_gc_cache_iter(api, db, cfg, cb_compute_categories, &cats);
if (ret != KNOT_EOK) {
kr_cache_gc_free_state(state);
return ret;
ctx_delete_categories_t to_del = { 0 };
to_del.cfg_temp_keys_space = cfg->temp_keys_space;
to_del.limit_category = limit_category;
- ret = kr_gc_cache_iter(db, cfg, cb_delete_categories, &to_del);
+ ret = kr_gc_cache_iter(api, db, cfg, cb_delete_categories, &to_del);
if (ret != KNOT_EOK) {
entry_dynarray_deep_free(&to_del.to_delete);
kr_cache_gc_free_state(state);
to_del.oversize_records);
//// 4. execute the planned deletions.
- const knot_db_api_t *api = knot_db_lmdb_api();
knot_db_txn_t txn = { 0 };
size_t deleted_records = 0, already_gone = 0, rw_txn_count = 0;