282 lines
7.5 KiB
C
282 lines
7.5 KiB
C
/* SPDX-License-Identifier: GPL-3.0-or-later */
|
|
|
|
#include "db.h"
|
|
|
|
#include "lib/cache/cdb_lmdb.h"
|
|
#include "lib/cache/impl.h"
|
|
|
|
#include <ctype.h>
|
|
#include <time.h>
|
|
#include <sys/stat.h>
|
|
|
|
#define MDB_FILE "/data.mdb"
|
|
|
|
int kr_gc_cache_open(const char *cache_path, struct kr_cache *kres_db,
|
|
knot_db_t ** libknot_db)
|
|
{
|
|
char cache_data[strlen(cache_path) + sizeof(MDB_FILE)];
|
|
(void)snprintf(cache_data, sizeof(cache_data), "%s" MDB_FILE, cache_path);
|
|
|
|
struct stat st = { 0 };
|
|
if (stat(cache_path, &st) || !(st.st_mode & S_IFDIR)
|
|
|| stat(cache_data, &st)) {
|
|
printf("Error: %s does not exist or is not a LMDB.\n", cache_path);
|
|
return -ENOENT;
|
|
}
|
|
|
|
struct kr_cdb_opts opts = { .path = cache_path, .maxsize = 0/*don't resize*/ };
|
|
|
|
int ret = kr_cache_open(kres_db, NULL, &opts, NULL);
|
|
if (ret || kres_db->db == NULL) {
|
|
printf("Error opening Resolver cache (%s).\n", kr_strerror(ret));
|
|
return -EINVAL;
|
|
}
|
|
|
|
*libknot_db = kr_cdb_pt2knot_db_t(kres_db->db);
|
|
if (*libknot_db == NULL) {
|
|
printf("Out of memory.\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int kr_gc_cache_check_health(struct kr_cache *kres_db, knot_db_t ** libknot_db)
|
|
{
|
|
int ret = kr_cache_check_health(kres_db, 0);
|
|
if (ret == 0) {
|
|
return 0;
|
|
} else if (ret != 1) {
|
|
kr_gc_cache_close(kres_db, *libknot_db);
|
|
return ret;
|
|
}
|
|
/* Cache was reopen. */
|
|
free(*libknot_db);
|
|
*libknot_db = kr_cdb_pt2knot_db_t(kres_db->db);
|
|
if (*libknot_db == NULL) {
|
|
printf("Out of memory.\n");
|
|
return -ENOMEM;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void kr_gc_cache_close(struct kr_cache *kres_db, knot_db_t * knot_db)
|
|
{
|
|
free(knot_db);
|
|
kr_cache_close(kres_db);
|
|
}
|
|
|
|
int kr_gc_key_consistent(knot_db_val_t key)
|
|
{
|
|
const uint8_t *kd = key.data;
|
|
ssize_t i;
|
|
/* CACHE_KEY_DEF */
|
|
if (key.len >= 2 && kd[0] == '\0') {
|
|
/* Beware: root zone is special and starts with
|
|
* a single \0 followed by type sign */
|
|
i = 1;
|
|
} else {
|
|
/* find the first double zero in the key */
|
|
for (i = 2; kd[i - 1] || kd[i - 2]; ++i) {
|
|
if (kr_fails_assert(i < key.len))
|
|
return kr_error(EINVAL);
|
|
}
|
|
}
|
|
// the next character can be used for classification
|
|
switch (kd[i]) {
|
|
case 'E':
|
|
(void)0; // C can't have a variable definition following a label
|
|
uint16_t type;
|
|
if (kr_fails_assert(i + 1 + sizeof(type) <= key.len))
|
|
return kr_error(EINVAL);
|
|
memcpy(&type, kd + i + 1, sizeof(type));
|
|
return type;
|
|
case '1':
|
|
return KNOT_RRTYPE_NSEC;
|
|
case '3':
|
|
return KNOT_RRTYPE_NSEC3;
|
|
case 'S': // the rtt_state entries are considered inconsistent, at least for now
|
|
return -1;
|
|
default:
|
|
kr_assert(!EINVAL);
|
|
return kr_error(EINVAL);
|
|
}
|
|
}
|
|
|
|
/// expects that key is consistent! CACHE_KEY_DEF
|
|
static uint8_t entry_labels(knot_db_val_t * key, uint16_t rrtype)
|
|
{
|
|
uint8_t lab = 0, *p = key->data;
|
|
while (*p != 0) {
|
|
while (*p++ != 0) {
|
|
if (p - (uint8_t *) key->data >= key->len) {
|
|
return 0;
|
|
}
|
|
}
|
|
lab++;
|
|
}
|
|
if (rrtype == KNOT_RRTYPE_NSEC3) {
|
|
// We don't know the number of labels so easily,
|
|
// but let's classify everything as directly
|
|
// below the zone apex (that's most common).
|
|
++lab;
|
|
}
|
|
return lab;
|
|
}
|
|
|
|
void debug_printbin(const char *str, unsigned int len)
|
|
{
|
|
putchar('"');
|
|
for (int idx = 0; idx < len; idx++) {
|
|
char c = str[idx];
|
|
if (isprint(c))
|
|
putchar(c);
|
|
else
|
|
printf("`%02hhx`", c);
|
|
}
|
|
putchar('"');
|
|
}
|
|
|
|
/** Return one entry_h reference from a cache DB value. NULL if not consistent/suitable. */
|
|
static const struct entry_h *val2entry(const knot_db_val_t val, uint16_t ktype)
|
|
{
|
|
if (ktype != KNOT_RRTYPE_NS)
|
|
return entry_h_consistent(val, ktype);
|
|
/* Otherwise we have a multi-purpose entry.
|
|
* Well, for now we simply choose the most suitable entry;
|
|
* the only realistic collision is DNAME in apex where we'll prefer NS. */
|
|
entry_list_t el;
|
|
if (entry_list_parse(val, el))
|
|
return NULL;
|
|
for (int i = ENTRY_APEX_NSECS_CNT; i < EL_LENGTH; ++i) {
|
|
if (el[i].len)
|
|
return entry_h_consistent(el[i], EL2RRTYPE(i));
|
|
}
|
|
/* Only NSEC* meta-data inside. */
|
|
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)
|
|
{
|
|
unsigned int counter_iter = 0;
|
|
unsigned int counter_gc_consistent = 0;
|
|
unsigned int counter_kr_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);
|
|
|
|
int ret = api->txn_begin(knot_db, &txn, KNOT_DB_RDONLY);
|
|
if (ret != KNOT_EOK) {
|
|
printf("Error starting DB transaction (%s).\n", knot_strerror(ret));
|
|
return ret;
|
|
}
|
|
|
|
it = api->iter_begin(&txn, KNOT_DB_NOOP); // _FIRST is split for easier debugging
|
|
if (it == NULL) {
|
|
printf("Error: failed to create an iterator.\n");
|
|
api->txn_abort(&txn);
|
|
return KNOT_ERROR;
|
|
}
|
|
it = api->iter_seek(it, NULL, KNOT_DB_FIRST);
|
|
if (it == NULL)
|
|
printf("Suspicious: completely empty LMDB at this moment?\n");
|
|
|
|
int txn_steps = 0;
|
|
while (it != NULL) {
|
|
knot_db_val_t key = { 0 }, val = { 0 };
|
|
ret = api->iter_key(it, &key);
|
|
if (ret == KNOT_EOK && key.len == 4 && memcmp("VERS", key.data, 4) == 0) {
|
|
/* skip DB metadata */
|
|
goto skip;
|
|
}
|
|
if (ret == KNOT_EOK) {
|
|
ret = api->iter_val(it, &val);
|
|
}
|
|
if (ret != KNOT_EOK) {
|
|
goto error;
|
|
}
|
|
|
|
info.entry_size = key.len + val.len;
|
|
info.valid = false;
|
|
const int entry_type = kr_gc_key_consistent(key);
|
|
const struct entry_h *entry = NULL;
|
|
if (entry_type >= 0) {
|
|
counter_gc_consistent++;
|
|
entry = val2entry(val, entry_type);
|
|
}
|
|
/* TODO: perhaps improve some details around here:
|
|
* - rtt_state entries are considered gc_inconsistent;
|
|
* therefore they'll be the first to get freed (see kr_gc_categorize())
|
|
* - xNAME have .rrtype NS
|
|
* - DNAME hidden on NS name will not be considered here
|
|
* - if zone has NSEC* meta-data but no NS, it will be seen
|
|
* here as kr_inconsistent */
|
|
if (entry != NULL) {
|
|
info.valid = true;
|
|
info.rrtype = entry_type;
|
|
info.expires_in = entry->time + entry->ttl - now;
|
|
info.no_labels = entry_labels(&key, entry_type);
|
|
}
|
|
counter_iter++;
|
|
counter_kr_consistent += info.valid;
|
|
if (VERBOSE_STATUS) {
|
|
if (!entry_type || !entry) { // don't log fully consistent entries
|
|
printf
|
|
("GC %sconsistent, KR %sconsistent, size %zu, key len %zu: ",
|
|
entry_type ? "" : "in", entry ? "" : "IN",
|
|
(key.len + val.len), key.len);
|
|
debug_printbin(key.data, key.len);
|
|
printf("\n");
|
|
}
|
|
}
|
|
ret = callback(&key, &info, ctx);
|
|
|
|
if (ret != KNOT_EOK) {
|
|
error:
|
|
printf("Error iterating database (%s).\n",
|
|
knot_strerror(ret));
|
|
api->iter_finish(it);
|
|
api->txn_abort(&txn);
|
|
return ret;
|
|
}
|
|
|
|
skip: // Advance to the next GC item.
|
|
if (++txn_steps < cfg->ro_txn_items || !cfg->ro_txn_items/*unlimited*/) {
|
|
it = api->iter_next(it);
|
|
} else {
|
|
/* The transaction has been too long; let's reopen it. */
|
|
txn_steps = 0;
|
|
uint8_t key_storage[key.len];
|
|
memcpy(key_storage, key.data, key.len);
|
|
key.data = key_storage;
|
|
|
|
api->iter_finish(it);
|
|
api->txn_abort(&txn);
|
|
|
|
ret = api->txn_begin(knot_db, &txn, KNOT_DB_RDONLY);
|
|
if (ret != KNOT_EOK) {
|
|
printf("Error restarting DB transaction (%s).\n",
|
|
knot_strerror(ret));
|
|
return ret;
|
|
}
|
|
it = api->iter_begin(&txn, KNOT_DB_NOOP);
|
|
if (it == NULL) {
|
|
printf("Error: failed to create an iterator.\n");
|
|
api->txn_abort(&txn);
|
|
return KNOT_ERROR;
|
|
}
|
|
it = api->iter_seek(it, &key, KNOT_DB_GEQ);
|
|
// NULL here means we'we reached the end
|
|
}
|
|
}
|
|
|
|
api->txn_abort(&txn);
|
|
|
|
kr_log_debug(CACHE, "iterated %u items, gc consistent %u, kr consistent %u\n",
|
|
counter_iter, counter_gc_consistent, counter_kr_consistent);
|
|
return KNOT_EOK;
|
|
}
|