timers.c 7.24 KB
Newer Older
1
/*  Copyright (C) 2017 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include "knot/zone/timers.h"

19 20 21 22 23 24 25 26
#include "contrib/wire_ctx.h"
#include "libknot/db/db.h"
#include "libknot/db/db_lmdb.h"

/*
 * # Timer database
 *
 * Timer database stores timestaps of events which need to be retained
27
 * across server restarts. The key in the database is the zone name in
28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
 * wire format. The value contains serialized timers.
 *
 * # Serialization format
 *
 * The value is a sequence of timers. Each timer consists of the timer
 * identifier (1 byte, unsigned integer) and timer value (8 bytes, unsigned
 * integer, network order).
 *
 * For example, the following byte sequence:
 *
 *     81 00 00 00 00 57 e3 e8 0a 82 00 00 00 00 57 e3 e9 a1
 *
 * Encodes the following timers:
 *
 *     last_flush = 1474553866
 *     last_refresh = 1474554273
 */
45

46 47 48 49 50 51 52 53 54 55
/**
 * \brief Timer database fields identifiers.
 *
 * Valid ID starts with '1' in MSB to avoid conflicts with "old timers".
 */
enum timer_id {
	TIMER_INVALID = 0,
	TIMER_SOA_EXPIRE = 0x80,
	TIMER_LAST_FLUSH,
	TIMER_LAST_REFRESH,
56 57 58
	TIMER_NEXT_REFRESH,
	TIMER_LAST_RESALT,
	TIMER_NEXT_PARENT_DS_Q
59 60
};

61
#define TIMER_COUNT 6
62 63
#define TIMER_SIZE (sizeof(uint8_t) + sizeof(uint64_t))
#define SERIALIZED_SIZE (TIMER_COUNT * TIMER_SIZE)
64

65 66 67 68
/*!
 * \brief Serialize timers into a binary buffer.
 */
static int serialize_timers(const zone_timers_t *timers, uint8_t *data, size_t size)
69
{
70 71 72
	if (!timers || !data || size != SERIALIZED_SIZE) {
		return KNOT_EINVAL;
	}
73

74
	wire_ctx_t wire = wire_ctx_init(data, size);
75

76 77 78 79 80 81 82 83
	wire_ctx_write_u8(&wire, TIMER_SOA_EXPIRE);
	wire_ctx_write_u64(&wire, timers->soa_expire);
	wire_ctx_write_u8(&wire, TIMER_LAST_FLUSH);
	wire_ctx_write_u64(&wire, timers->last_flush);
	wire_ctx_write_u8(&wire, TIMER_LAST_REFRESH);
	wire_ctx_write_u64(&wire, timers->last_refresh);
	wire_ctx_write_u8(&wire, TIMER_NEXT_REFRESH);
	wire_ctx_write_u64(&wire, timers->next_refresh);
84 85 86 87
	wire_ctx_write_u8(&wire, TIMER_LAST_RESALT);
	wire_ctx_write_u64(&wire, timers->last_resalt);
	wire_ctx_write_u8(&wire, TIMER_NEXT_PARENT_DS_Q);
	wire_ctx_write_u64(&wire, timers->next_parent_ds_q);
88

89 90 91 92
	assert(wire.error == KNOT_EOK);
	assert(wire_ctx_available(&wire) == 0);

	return KNOT_EOK;
93 94
}

95 96 97
/*!
 * \brief Deserialize timers from a binary buffer.
 *
98
 * \note Unknown timers are ignored.
99 100 101
 */
static int deserialize_timers(zone_timers_t *timers_ptr,
                              const uint8_t *data, size_t size)
102
{
103 104 105
	if (!timers_ptr || !data) {
		return KNOT_EINVAL;
	}
106

107 108 109 110 111 112 113
	zone_timers_t timers = { 0 };

	wire_ctx_t wire = wire_ctx_init_const(data, size);
	while (wire_ctx_available(&wire) >= TIMER_SIZE) {
		uint8_t id = wire_ctx_read_u8(&wire);
		uint64_t value = wire_ctx_read_u64(&wire);
		switch (id) {
114 115 116 117 118 119
		case TIMER_SOA_EXPIRE:       timers.soa_expire = value; break;
		case TIMER_LAST_FLUSH:       timers.last_flush = value; break;
		case TIMER_LAST_REFRESH:     timers.last_refresh = value; break;
		case TIMER_NEXT_REFRESH:     timers.next_refresh = value; break;
		case TIMER_LAST_RESALT:      timers.last_resalt = value; break;
		case TIMER_NEXT_PARENT_DS_Q: timers.next_parent_ds_q = value; break;
120
		default:                 break; // ignore
121
		}
122
	}
123

124 125
	if (wire_ctx_available(&wire) != 0) {
		return KNOT_EMALF;
126 127
	}

128
	assert(wire.error == KNOT_EOK);
129

130 131
	*timers_ptr = timers;
	return KNOT_EOK;
132 133
}

134 135
static int txn_write_timers(knot_db_txn_t *txn, const knot_dname_t *zone,
                            const zone_timers_t *timers)
136
{
137 138 139
	uint8_t data[SERIALIZED_SIZE] = { 0 };
	int ret = serialize_timers(timers, data, sizeof(data));
	if (ret != KNOT_EOK) {
140 141
		return ret;
	}
142

143 144
	knot_db_val_t key = { (uint8_t *)zone, knot_dname_size(zone) };
	knot_db_val_t val = { data, sizeof(data) };
145

146 147 148 149 150 151 152 153 154 155 156
	return knot_db_lmdb_api()->insert(txn, &key, &val, 0);
}

static int txn_read_timers(knot_db_txn_t *txn, const knot_dname_t *zone,
                           zone_timers_t *timers)
{
	knot_db_val_t key = { (uint8_t *)zone, knot_dname_size(zone) };
	knot_db_val_t val = { 0 };
	int ret = knot_db_lmdb_api()->find(txn, &key, &val, 0);
	if (ret != KNOT_EOK) {
		return ret;
157 158
	}

159
	return deserialize_timers(timers, val.data, val.len);
160 161
}

162
int zone_timers_open(const char *path, knot_db_t **db, size_t mapsize)
163
{
164
	if (path == NULL || db == NULL) {
165 166 167
		return KNOT_EINVAL;
	}

168
	struct knot_db_lmdb_opts opts = KNOT_DB_LMDB_OPTS_INITIALIZER;
169
	opts.mapsize = mapsize;
170
	opts.path = path;
171

172
	return knot_db_lmdb_api()->init(db, NULL, &opts);
173 174
}

175
void zone_timers_close(knot_db_t *db)
176
{
177
	if (db == NULL) {
178
		return;
179
	}
180

181
	knot_db_lmdb_api()->deinit(db);
182 183
}

184 185
int zone_timers_read(knot_db_t *db, const knot_dname_t *zone,
                     zone_timers_t *timers)
186
{
187
	if (!db || !zone || !timers) {
188 189 190
		return KNOT_EINVAL;
	}

191
	const knot_db_api_t *db_api = knot_db_lmdb_api();
192
	assert(db_api);
193

194 195
	knot_db_txn_t txn = { 0 };
	int ret = db_api->txn_begin(db, &txn, KNOT_DB_RDONLY);
196 197 198 199
	if (ret != KNOT_EOK) {
		return ret;
	}

200
	ret = txn_read_timers(&txn, zone, timers);
201
	db_api->txn_abort(&txn);
202

203
	return ret;
204 205
}

206 207 208 209 210 211
int zone_timers_write_begin(knot_db_t *db, knot_db_txn_t *txn)
{
	memset(txn, 0, sizeof(*txn));
	return knot_db_lmdb_api()->txn_begin(db, txn, KNOT_DB_SORTED);
}

212
int zone_timers_write_end(knot_db_txn_t *txn)
213
{
214
	return knot_db_lmdb_api()->txn_commit(txn);
215 216
}

217
int zone_timers_write(knot_db_t *db, const knot_dname_t *zone,
218
                      const zone_timers_t *timers, knot_db_txn_t *txn)
219
{
220
	if (!zone || !timers || (!db && !txn)) {
221 222 223
		return KNOT_EINVAL;
	}

224
	const knot_db_api_t *db_api = knot_db_lmdb_api();
225
	assert(db_api);
226

227 228 229 230 231 232 233
	knot_db_txn_t static_txn, *mytxn = txn;
	if (txn == NULL) {
		mytxn = &static_txn;
		int ret = db_api->txn_begin(db, mytxn, KNOT_DB_SORTED);
		if (ret != KNOT_EOK) {
			return ret;
		}
234 235
	}

236
	int ret = txn_write_timers(mytxn, zone, timers);
237
	if (ret != KNOT_EOK) {
238
		db_api->txn_abort(mytxn);
239 240
		return ret;
	}
241

242 243 244 245
	if (txn == NULL) {
		db_api->txn_commit(mytxn);
	}

246
	return KNOT_EOK;
247 248
}

249
int zone_timers_sweep(knot_db_t *db, sweep_cb keep_zone, void *cb_data)
250
{
251
	if (!db || !keep_zone) {
252 253 254
		return KNOT_EINVAL;
	}

255
	const knot_db_api_t *db_api = knot_db_lmdb_api();
256
	assert(db_api);
257

258 259
	knot_db_txn_t txn = { 0 };
	int ret = db_api->txn_begin(db, &txn, KNOT_DB_SORTED);
260 261 262 263
	if (ret != KNOT_EOK) {
		return ret;
	}

264 265 266
	knot_db_iter_t *it = NULL;
	for (it = db_api->iter_begin(&txn, 0); it != NULL; it = db_api->iter_next(it)) {
		knot_db_val_t key = { 0 };
267 268
		ret = db_api->iter_key(it, &key);
		if (ret != KNOT_EOK) {
269
			break;
270
		}
271

272 273 274 275 276 277 278
		const knot_dname_t *zone = (const knot_dname_t *)key.data;
		if (!keep_zone(zone, cb_data)) {
			ret = db_api->del(&txn, &key);
			if (ret != KNOT_EOK) {
				break;
			}
		}
279 280 281
	}
	db_api->iter_finish(it);

282 283 284 285 286
	if (ret != KNOT_EOK) {
		db_api->txn_abort(&txn);
		return ret;
	}

287 288
	return db_api->txn_commit(&txn);
}