1 /*  Copyright (C) 2021 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
2 
3     This program is free software: you can redistribute it and/or modify
4     it under the terms of the GNU General Public License as published by
5     the Free Software Foundation, either version 3 of the License, or
6     (at your option) any later version.
7 
8     This program is distributed in the hope that it will be useful,
9     but WITHOUT ANY WARRANTY; without even the implied warranty of
10     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11     GNU General Public License for more details.
12 
13     You should have received a copy of the GNU General Public License
14     along with this program.  If not, see <https://www.gnu.org/licenses/>.
15  */
16 
17 #include <stdio.h>
18 #include <string.h>
19 #include <sys/stat.h>
20 #include <urcu.h>
21 
22 #include "contrib/files.h"
23 #include "knot/catalog/catalog_db.h"
24 #include "knot/common/log.h"
25 
26 static const MDB_val catalog_iter_prefix = { 1, "" };
27 
catalog_dname_append(knot_dname_storage_t storage,const knot_dname_t * name)28 size_t catalog_dname_append(knot_dname_storage_t storage, const knot_dname_t *name)
29 {
30 	size_t old_len = knot_dname_size(storage);
31 	size_t name_len = knot_dname_size(name);
32 	size_t new_len = old_len - 1 + name_len;
33 	if (old_len == 0 || name_len == 0 || new_len > KNOT_DNAME_MAXLEN) {
34 		return 0;
35 	}
36 	memcpy(storage + old_len - 1, name, name_len);
37 	return new_len;
38 }
39 
catalog_bailiwick_shift(const knot_dname_t * subname,const knot_dname_t * name)40 int catalog_bailiwick_shift(const knot_dname_t *subname, const knot_dname_t *name)
41 {
42 	const knot_dname_t *res = subname;
43 	while (!knot_dname_is_equal(res, name)) {
44 		if (*res == '\0') {
45 			return -1;
46 		}
47 		res = knot_wire_next_label(res, NULL);
48 	}
49 	return res - subname;
50 }
51 
catalog_init(catalog_t * cat,const char * path,size_t mapsize)52 void catalog_init(catalog_t *cat, const char *path, size_t mapsize)
53 {
54 	knot_lmdb_init(&cat->db, path, mapsize, MDB_NOTLS, NULL);
55 }
56 
ensure_cat_version(knot_lmdb_txn_t * ro_txn,knot_lmdb_txn_t * rw_txn)57 static void ensure_cat_version(knot_lmdb_txn_t *ro_txn, knot_lmdb_txn_t *rw_txn)
58 {
59 	MDB_val key = { 8, "\x01version" };
60 	if (knot_lmdb_find(ro_txn, &key, KNOT_LMDB_EXACT)) {
61 		if (strncmp(CATALOG_VERSION, ro_txn->cur_val.mv_data,
62 		            ro_txn->cur_val.mv_size) != 0) {
63 			log_warning("catalog version mismatch");
64 		}
65 	} else if (rw_txn != NULL) {
66 		MDB_val val = { strlen(CATALOG_VERSION), CATALOG_VERSION };
67 		knot_lmdb_insert(rw_txn, &key, &val);
68 	}
69 }
70 
71 // does NOT check for catalog zone version by RFC, this is Knot-specific in the cat LMDB !
check_cat_version(catalog_t * cat)72 static void check_cat_version(catalog_t *cat)
73 {
74 	if (cat->ro_txn->ret == KNOT_EOK) {
75 		ensure_cat_version(cat->ro_txn, cat->rw_txn);
76 	}
77 }
78 
catalog_open(catalog_t * cat)79 int catalog_open(catalog_t *cat)
80 {
81 	if (!knot_lmdb_is_open(&cat->db)) {
82 		int ret = knot_lmdb_open(&cat->db);
83 		if (ret != KNOT_EOK) {
84 			return ret;
85 		}
86 	}
87 	if (cat->ro_txn == NULL) {
88 		knot_lmdb_txn_t *ro_txn = calloc(1, sizeof(*ro_txn));
89 		if (ro_txn == NULL) {
90 			return KNOT_ENOMEM;
91 		}
92 		knot_lmdb_begin(&cat->db, ro_txn, false);
93 		cat->ro_txn = ro_txn;
94 	}
95 	check_cat_version(cat);
96 	return cat->ro_txn->ret;
97 }
98 
catalog_begin(catalog_t * cat)99 int catalog_begin(catalog_t *cat)
100 {
101 	int ret = catalog_open(cat);
102 	if (ret != KNOT_EOK) {
103 		return ret;
104 	}
105 	knot_lmdb_txn_t *rw_txn = calloc(1, sizeof(*rw_txn));
106 	if (rw_txn == NULL) {
107 		return KNOT_ENOMEM;
108 	}
109 	knot_lmdb_begin(&cat->db, rw_txn, true);
110 	if (rw_txn->ret != KNOT_EOK) {
111 		ret = rw_txn->ret;
112 		free(rw_txn);
113 		return ret;
114 	}
115 	assert(cat->rw_txn == NULL); // LMDB prevents two existing RW txns at a time
116 	cat->rw_txn = rw_txn;
117 	check_cat_version(cat);
118 	return cat->rw_txn->ret;
119 }
120 
catalog_commit(catalog_t * cat)121 int catalog_commit(catalog_t *cat)
122 {
123 	knot_lmdb_txn_t *rw_txn = rcu_xchg_pointer(&cat->rw_txn, NULL);
124 	knot_lmdb_commit(rw_txn);
125 	int ret = rw_txn->ret;
126 	free(rw_txn);
127 	if (ret != KNOT_EOK) {
128 		return ret;
129 	}
130 
131 	// now refresh RO txn
132 	knot_lmdb_txn_t *ro_txn = calloc(1, sizeof(*ro_txn));
133 	if (ro_txn == NULL) {
134 		return KNOT_ENOMEM;
135 	}
136 	knot_lmdb_begin(&cat->db, ro_txn, false);
137 	cat->old_ro_txn = rcu_xchg_pointer(&cat->ro_txn, ro_txn);
138 
139 	return KNOT_EOK;
140 }
141 
catalog_abort(catalog_t * cat)142 void catalog_abort(catalog_t *cat)
143 {
144 	knot_lmdb_txn_t *rw_txn = rcu_xchg_pointer(&cat->rw_txn, NULL);
145 	if (rw_txn != NULL) {
146 		knot_lmdb_abort(rw_txn);
147 		free(rw_txn);
148 	}
149 }
150 
catalog_commit_cleanup(catalog_t * cat)151 void catalog_commit_cleanup(catalog_t *cat)
152 {
153 	knot_lmdb_txn_t *old_ro_txn = rcu_xchg_pointer(&cat->old_ro_txn, NULL);
154 	if (old_ro_txn != NULL) {
155 		knot_lmdb_abort(old_ro_txn);
156 		free(old_ro_txn);
157 	}
158 }
159 
catalog_deinit(catalog_t * cat)160 int catalog_deinit(catalog_t *cat)
161 {
162 	assert(cat->rw_txn == NULL);
163 	if (cat->ro_txn != NULL) {
164 		knot_lmdb_abort(cat->ro_txn);
165 		free(cat->ro_txn);
166 	}
167 	if (cat->old_ro_txn != NULL) {
168 		knot_lmdb_abort(cat->old_ro_txn);
169 		free(cat->old_ro_txn);
170 	}
171 	knot_lmdb_deinit(&cat->db);
172 	return KNOT_EOK;
173 }
174 
catalog_add(catalog_t * cat,const knot_dname_t * member,const knot_dname_t * owner,const knot_dname_t * catzone,const char * group)175 int catalog_add(catalog_t *cat, const knot_dname_t *member,
176                 const knot_dname_t *owner, const knot_dname_t *catzone,
177                 const char *group)
178 {
179 	if (cat->rw_txn == NULL) {
180 		return KNOT_EINVAL;
181 	}
182 	int bail = catalog_bailiwick_shift(owner, catzone);
183 	if (bail < 0) {
184 		return KNOT_EOUTOFZONE;
185 	}
186 	assert(bail >= 0 && bail < 256);
187 	MDB_val key = knot_lmdb_make_key("BN", 0, member); // 0 for future purposes
188 	MDB_val val = knot_lmdb_make_key("BBNS", 0, bail, owner, group);
189 
190 	knot_lmdb_insert(cat->rw_txn, &key, &val);
191 	free(key.mv_data);
192 	free(val.mv_data);
193 	return cat->rw_txn->ret;
194 }
195 
catalog_del(catalog_t * cat,const knot_dname_t * member)196 int catalog_del(catalog_t *cat, const knot_dname_t *member)
197 {
198 	if (cat->rw_txn == NULL) {
199 		return KNOT_EINVAL;
200 	}
201 	MDB_val key = knot_lmdb_make_key("BN", 0, member);
202 	knot_lmdb_del_prefix(cat->rw_txn, &key); // deletes one record
203 	free(key.mv_data);
204 	return cat->rw_txn->ret;
205 }
206 
unmake_val(MDB_val * val,const knot_dname_t ** owner,const knot_dname_t ** catz,const char ** group)207 static void unmake_val(MDB_val *val, const knot_dname_t **owner,
208                        const knot_dname_t **catz, const char **group)
209 {
210 	uint8_t zero, shift;
211 	*group = ""; // backward compatibility with Knot 3.0
212 	knot_lmdb_unmake_key(val->mv_data, val->mv_size, "BBNS", &zero, &shift,
213 	                     owner, group);
214 	*catz = *owner + shift;
215 }
216 
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)217 static int find_threadsafe(catalog_t *cat, const knot_dname_t *member,
218                            const knot_dname_t **owner, const knot_dname_t **catz,
219                            const char **group, void **tofree)
220 {
221 	*tofree = NULL;
222 	if (cat->ro_txn == NULL) {
223 		return KNOT_ENOENT;
224 	}
225 
226 	MDB_val key = knot_lmdb_make_key("BN", 0, member), val = { 0 };
227 
228 	int ret = knot_lmdb_find_threadsafe(cat->ro_txn, &key, &val, KNOT_LMDB_EXACT);
229 	if (ret == KNOT_EOK) {
230 		unmake_val(&val, owner, catz, group);
231 		*tofree = val.mv_data;
232 	}
233 	free(key.mv_data);
234 	return ret;
235 }
236 
catalog_get_catz(catalog_t * cat,const knot_dname_t * member,const knot_dname_t ** catz,const char ** group,void ** tofree)237 int catalog_get_catz(catalog_t *cat, const knot_dname_t *member,
238                      const knot_dname_t **catz, const char **group, void **tofree)
239 {
240 	const knot_dname_t *unused;
241 	return find_threadsafe(cat, member, &unused, catz, group, tofree);
242 }
243 
catalog_has_member(catalog_t * cat,const knot_dname_t * member)244 bool catalog_has_member(catalog_t *cat, const knot_dname_t *member)
245 {
246 	const knot_dname_t *catz;
247 	const char *group;
248 	void *tofree = NULL;
249 	int ret = catalog_get_catz(cat, member, &catz, &group, &tofree);
250 	free(tofree);
251 	return (ret == KNOT_EOK);
252 }
253 
catalog_contains_exact(catalog_t * cat,const knot_dname_t * member,const knot_dname_t * owner,const knot_dname_t * catz)254 bool catalog_contains_exact(catalog_t *cat, const knot_dname_t *member,
255                             const knot_dname_t *owner, const knot_dname_t *catz)
256 {
257 	const knot_dname_t *found_owner, *found_catz;
258 	const char *found_group;
259 	void *tofree = NULL;
260 	int ret = find_threadsafe(cat, member, &found_owner, &found_catz, &found_group, &tofree);
261 	if (ret == KNOT_EOK && (!knot_dname_is_equal(owner, found_owner) ||
262 	    !knot_dname_is_equal(catz, found_catz))) {
263 		ret = KNOT_ENOENT;
264 	}
265 	free(tofree);
266 	return (ret == KNOT_EOK);
267 }
268 
269 typedef struct {
270 	catalog_apply_cb_t cb;
271 	void *ctx;
272 } catalog_apply_ctx_t;
273 
catalog_apply_cb(MDB_val * key,MDB_val * val,void * ctx)274 static int catalog_apply_cb(MDB_val *key, MDB_val *val, void *ctx)
275 {
276 	catalog_apply_ctx_t *iter_ctx = ctx;
277 	uint8_t zero;
278 	const knot_dname_t *mem = NULL, *ow = NULL, *cz = NULL;
279 	const char *gr = NULL;
280 	knot_lmdb_unmake_key(key->mv_data, key->mv_size, "BN", &zero, &mem);
281 	unmake_val(val, &ow, &cz, &gr);
282 	if (mem == NULL || ow == NULL || cz == NULL) {
283 		return KNOT_EMALF;
284 	}
285 	return iter_ctx->cb(mem, ow, cz, gr, iter_ctx->ctx);
286 }
287 
catalog_apply(catalog_t * cat,const knot_dname_t * for_member,catalog_apply_cb_t cb,void * ctx,bool rw)288 int catalog_apply(catalog_t *cat, const knot_dname_t *for_member,
289                   catalog_apply_cb_t cb, void *ctx, bool rw)
290 {
291 	MDB_val prefix = knot_lmdb_make_key(for_member == NULL ? "B" : "BN", 0, for_member);
292 	catalog_apply_ctx_t iter_ctx = { cb, ctx };
293 	knot_lmdb_txn_t *use_txn = rw ? cat->rw_txn : cat->ro_txn;
294 	int ret = knot_lmdb_apply_threadsafe(use_txn, &prefix, true, catalog_apply_cb, &iter_ctx);
295 	free(prefix.mv_data);
296 	return ret;
297 }
298 
same_catalog(knot_lmdb_txn_t * txn,const knot_dname_t * catalog)299 static bool same_catalog(knot_lmdb_txn_t *txn, const knot_dname_t *catalog)
300 {
301 	if (catalog == NULL) {
302 		return true;
303 	}
304 	const knot_dname_t *txn_cat = NULL, *unused;
305 	const char *grunused;
306 	unmake_val(&txn->cur_val, &unused, &txn_cat, &grunused);
307 	return knot_dname_is_equal(txn_cat, catalog);
308 }
309 
catalog_copy(knot_lmdb_db_t * from,knot_lmdb_db_t * to,const knot_dname_t * cat_only,bool read_rw_txn)310 int catalog_copy(knot_lmdb_db_t *from, knot_lmdb_db_t *to,
311                  const knot_dname_t *cat_only, bool read_rw_txn)
312 {
313 	if (!knot_lmdb_exists(from)) {
314 		return KNOT_EOK;
315 	}
316 	int ret = knot_lmdb_open(from);
317 	if (ret == KNOT_EOK) {
318 		ret = make_path(to->path, S_IRWXU | S_IRWXG);
319 		if (ret == KNOT_EOK) {
320 			ret = knot_lmdb_open(to);
321 		}
322 	}
323 	if (ret != KNOT_EOK) {
324 		return ret;
325 	}
326 	knot_lmdb_txn_t txn_r = { 0 }, txn_w = { 0 };
327 	knot_lmdb_begin(from, &txn_r, read_rw_txn); // using RW txn not to conflict with still-open RO txn
328 	knot_lmdb_begin(to, &txn_w, true);
329 	knot_lmdb_foreach(&txn_w, (MDB_val *)&catalog_iter_prefix) {
330 		if (same_catalog(&txn_w, cat_only)) {
331 			knot_lmdb_del_cur(&txn_w);
332 		}
333 	}
334 	knot_lmdb_foreach(&txn_r, (MDB_val *)&catalog_iter_prefix) {
335 		if (same_catalog(&txn_r, cat_only)) {
336 			knot_lmdb_insert(&txn_w, &txn_r.cur_key, &txn_r.cur_val);
337 		}
338 	}
339 	ensure_cat_version(&txn_w, &txn_w);
340 	if (txn_r.ret != KNOT_EOK) {
341 		knot_lmdb_abort(&txn_r);
342 		knot_lmdb_abort(&txn_w);
343 		return txn_r.ret;
344 	}
345 	knot_lmdb_commit(&txn_r);
346 	knot_lmdb_commit(&txn_w);
347 	return txn_w.ret;
348 }
349