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