summaryrefslogtreecommitdiffstats
path: root/utils/cache_gc/db.c
blob: c31ff220dcff093169632ecd554335aef3d36a47 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
/* 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;
}