• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

PowerDNS / pdns / 18554180908

16 Oct 2025 07:48AM UTC coverage: 65.813% (-0.01%) from 65.825%
18554180908

Pull #16271

github

web-flow
Merge 4dcb65194 into feeb24672
Pull Request #16271: auth-5.0.x: Backport 15267: Fix the build-packages workflow

42046 of 92452 branches covered (45.48%)

Branch coverage included in aggregate %.

127953 of 165855 relevant lines covered (77.15%)

5719692.12 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

73.74
/modules/lmdbbackend/lmdbbackend.cc
1
/*
2
 * This file is part of PowerDNS or dnsdist.
3
 * Copyright -- PowerDNS.COM B.V. and its contributors
4
 *
5
 * This program is free software; you can redistribute it and/or modify
6
 * it under the terms of version 2 of the GNU General Public License as
7
 * published by the Free Software Foundation.
8
 *
9
 * In addition, for the avoidance of any doubt, permission is granted to
10
 * link this program with OpenSSL and to (re)distribute the binaries
11
 * produced as the result of such linking.
12
 *
13
 * This program is distributed in the hope that it will be useful,
14
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16
 * GNU General Public License for more details.
17
 *
18
 * You should have received a copy of the GNU General Public License
19
 * along with this program; if not, write to the Free Software
20
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21
 */
22

23
#include "lmdbbackend.hh"
24

25
#include "config.h"
26
#include "ext/lmdb-safe/lmdb-safe.hh"
27
#include "pdns/arguments.hh"
28
#include "pdns/base32.hh"
29
#include "pdns/dns.hh"
30
#include "pdns/dnsbackend.hh"
31
#include "pdns/dnsname.hh"
32
#include "pdns/dnspacket.hh"
33
#include "pdns/dnssecinfra.hh"
34
#include "pdns/logger.hh"
35
#include "pdns/misc.hh"
36
#include "pdns/pdnsexception.hh"
37
#include "pdns/uuid-utils.hh"
38
#include <boost/archive/binary_iarchive.hpp>
39
#include <boost/archive/binary_oarchive.hpp>
40
#include <boost/iostreams/device/back_inserter.hpp>
41
#include <boost/serialization/string.hpp>
42
#include <boost/serialization/utility.hpp>
43
#include <boost/serialization/vector.hpp>
44
#include <boost/uuid/uuid_serialize.hpp>
45
#include <cstdio>
46
#include <cstring>
47
#include <lmdb.h>
48
#include <memory>
49
#include <stdexcept>
50
#include <unistd.h>
51
#include <utility>
52

53
#ifdef HAVE_SYSTEMD
54
#include <systemd/sd-daemon.h>
55
#endif
56

57
constexpr unsigned int SCHEMAVERSION{6};
58

59
// List the class version here. Default is 0
60
BOOST_CLASS_VERSION(LMDBBackend::KeyDataDB, 1)
61
BOOST_CLASS_VERSION(ZoneName, 1)
62
BOOST_CLASS_VERSION(DomainInfo, 2)
63

64
static bool s_first = true;
65
static uint32_t s_shards = 0;
66
static std::mutex s_lmdbStartupLock;
67

68
std::pair<uint32_t, uint32_t> LMDBBackend::getSchemaVersionAndShards(std::string& filename)
69
{
2,483✔
70
  // cerr << "getting schema version for path " << filename << endl;
71

72
  uint32_t schemaversion = 0;
2,483✔
73

74
  MDB_env* tmpEnv = nullptr;
2,483✔
75

76
  if (int retCode = mdb_env_create(&tmpEnv); retCode != 0) {
2,483!
77
    throw std::runtime_error("mdb_env_create failed: " + MDBError(retCode));
×
78
  }
×
79

80
  std::unique_ptr<MDB_env, decltype(&mdb_env_close)> env{tmpEnv, mdb_env_close};
2,483✔
81

82
  if (int retCode = mdb_env_set_mapsize(tmpEnv, 0); retCode != 0) {
2,483!
83
    throw std::runtime_error("mdb_env_set_mapsize failed: " + MDBError(retCode));
×
84
  }
×
85

86
  if (int retCode = mdb_env_set_maxdbs(tmpEnv, 20); retCode != 0) { // we need 17: 1 {"pdns"} + 4 {"domains", "keydata", "tsig", "metadata"} * 2 {v4, v5} * 2 {main, index in _0}
2,483!
87
    throw std::runtime_error("mdb_env_set_maxdbs failed: " + MDBError(retCode));
×
88
  }
×
89

90
  {
2,483✔
91
    int retCode = mdb_env_open(tmpEnv, filename.c_str(), MDB_NOSUBDIR | MDB_RDONLY, 0600);
2,483✔
92
    if (retCode != 0) {
2,483✔
93
      if (retCode == ENOENT) {
53!
94
        // we don't have a database yet! report schema 0, with 0 shards
95
        return {0U, 0U};
53✔
96
      }
53✔
97
      throw std::runtime_error("mdb_env_open failed: " + MDBError(retCode));
×
98
    }
53✔
99
  }
2,483✔
100

101
  MDB_txn* txn = nullptr;
2,430✔
102

103
  if (int retCode = mdb_txn_begin(tmpEnv, nullptr, MDB_RDONLY, &txn); retCode != 0) {
2,430!
104
    throw std::runtime_error("mdb_txn_begin failed: " + MDBError(retCode));
×
105
  }
×
106

107
  MDB_dbi dbi;
2,430✔
108

109
  {
2,430✔
110
    int retCode = MDBDbi::mdb_dbi_open(txn, "pdns", 0, &dbi);
2,430✔
111
    if (retCode != 0) {
2,430!
112
      if (retCode == MDB_NOTFOUND) {
×
113
        // this means nothing has been inited yet
114
        // we pretend this means the latest schema
115
        mdb_txn_abort(txn);
×
116
        return {SCHEMAVERSION, 0U};
×
117
      }
×
118
      mdb_txn_abort(txn);
×
119
      throw std::runtime_error("mdb_dbi_open failed: " + MDBError(retCode));
×
120
    }
×
121
  }
2,430✔
122

123
  MDB_val key, data;
2,430✔
124

125
  key.mv_data = (char*)"schemaversion";
2,430✔
126
  key.mv_size = strlen((char*)key.mv_data);
2,430✔
127

128
  {
2,430✔
129
    int retCode = mdb_get(txn, dbi, &key, &data);
2,430✔
130
    if (retCode != 0) {
2,430!
131
      if (retCode == MDB_NOTFOUND) {
×
132
        // this means nothing has been inited yet
133
        // we pretend this means the latest schema
134
        mdb_txn_abort(txn);
×
135
        return {SCHEMAVERSION, 0U};
×
136
      }
×
137

138
      throw std::runtime_error("mdb_get pdns.schemaversion failed: " + MDBError(retCode));
×
139
    }
×
140
  }
2,430✔
141

142
  if (data.mv_size == 4) {
2,430✔
143
    // schemaversion is < 5 and is stored in 32 bits, in host order
144

145
    memcpy(&schemaversion, data.mv_data, data.mv_size);
4✔
146
  }
4✔
147
  else if (data.mv_size >= LMDBLS::LS_MIN_HEADER_SIZE + sizeof(schemaversion)) {
2,426!
148
    // schemaversion is >= 5, stored in 32 bits, network order, after the LS header
149

150
    // FIXME: get actual header size (including extension blocks) instead of just reading from the back
151
    // FIXME: add a test for reading schemaversion and shards (and actual data, later) when there are variably sized headers
152
    memcpy(&schemaversion, (char*)data.mv_data + data.mv_size - sizeof(schemaversion), sizeof(schemaversion));
2,426✔
153
    schemaversion = ntohl(schemaversion);
2,426✔
154
  }
2,426✔
155
  else {
×
156
    throw std::runtime_error("pdns.schemaversion had unexpected size");
×
157
  }
×
158

159
  uint32_t shards = 0;
2,430✔
160

161
  key.mv_data = (char*)"shards";
2,430✔
162
  key.mv_size = strlen((char*)key.mv_data);
2,430✔
163

164
  {
2,430✔
165
    int retCode = mdb_get(txn, dbi, &key, &data);
2,430✔
166
    if (retCode != 0) {
2,430!
167
      if (retCode == MDB_NOTFOUND) {
×
168
        cerr << "schemaversion was set, but shards was not. Dazed and confused, trying to exit." << endl;
×
169
        mdb_txn_abort(txn);
×
170
        // NOLINTNEXTLINE(concurrency-mt-unsafe)
171
        exit(1);
×
172
      }
×
173

174
      throw std::runtime_error("mdb_get pdns.shards failed: " + MDBError(retCode));
×
175
    }
×
176
  }
2,430✔
177

178
  if (data.mv_size == 4) {
2,430✔
179
    // 'shards' is stored in 32 bits, in host order
180

181
    memcpy(&shards, data.mv_data, data.mv_size);
4✔
182
  }
4✔
183
  else if (data.mv_size >= LMDBLS::LS_MIN_HEADER_SIZE + sizeof(shards)) {
2,426!
184
    // FIXME: get actual header size (including extension blocks) instead of just reading from the back
185
    memcpy(&shards, (char*)data.mv_data + data.mv_size - sizeof(shards), sizeof(shards));
2,426✔
186
    shards = ntohl(shards);
2,426✔
187
  }
2,426✔
188
  else {
×
189
    throw std::runtime_error("pdns.shards had unexpected size");
×
190
  }
×
191

192
  mdb_txn_abort(txn);
2,430✔
193

194
  return {schemaversion, shards};
2,430✔
195
}
2,430✔
196

197
namespace
198
{
199
// copy sdbi to tdbi, prepending an empty LS header (24 bytes of '\0') to all values
200
void copyDBIAndAddLSHeader(MDB_txn* txn, MDB_dbi sdbi, MDB_dbi tdbi)
201
{
4✔
202
  // FIXME: clear out target dbi first
203

204
  std::string header(LMDBLS::LS_MIN_HEADER_SIZE, '\0');
4✔
205
  int rc;
4✔
206

207
  MDB_cursor* cur;
4✔
208

209
  if ((rc = mdb_cursor_open(txn, sdbi, &cur)) != 0) {
4!
210
    throw std::runtime_error("mdb_cursor_open failed: " + MDBError(rc));
×
211
  }
×
212

213
  MDB_val key, data;
4✔
214

215
  rc = mdb_cursor_get(cur, &key, &data, MDB_FIRST);
4✔
216

217
  while (rc == 0) {
40,448✔
218
    std::string skey(reinterpret_cast<const char*>(key.mv_data), key.mv_size);
40,444✔
219
    std::string sdata(reinterpret_cast<const char*>(data.mv_data), data.mv_size);
40,444✔
220

221
    std::string stdata = header + sdata;
40,444✔
222

223
    // cerr<<"got key="<<makeHexDump(skey)<<", data="<<makeHexDump(sdata)<<", sdata="<<makeHexDump(stdata)<<endl;
224

225
    MDB_val tkey;
40,444✔
226
    MDB_val tdata;
40,444✔
227

228
    tkey.mv_data = const_cast<char*>(skey.c_str());
40,444✔
229
    tkey.mv_size = skey.size();
40,444✔
230
    tdata.mv_data = const_cast<char*>(stdata.c_str());
40,444✔
231
    tdata.mv_size = stdata.size();
40,444✔
232

233
    if ((rc = mdb_put(txn, tdbi, &tkey, &tdata, 0)) != 0) {
40,444!
234
      throw std::runtime_error("mdb_put failed: " + MDBError(rc));
×
235
    }
×
236

237
    rc = mdb_cursor_get(cur, &key, &data, MDB_NEXT);
40,444✔
238
  }
40,444✔
239
  if (rc != MDB_NOTFOUND) {
4!
240
    throw std::runtime_error("error while iterating dbi: " + MDBError(rc));
×
241
  }
×
242
}
4✔
243

244
// migrated a typed DBI:
245
// 1. change keys (uint32_t) from host to network order
246
// 2. prepend empty LS header to values
247
void copyTypedDBI(MDB_txn* txn, MDB_dbi sdbi, MDB_dbi tdbi)
248
{
8✔
249
  // FIXME: clear out target dbi first
250

251
  std::string header(LMDBLS::LS_MIN_HEADER_SIZE, '\0');
8✔
252
  int rc;
8✔
253

254
  MDB_cursor* cur;
8✔
255

256
  if ((rc = mdb_cursor_open(txn, sdbi, &cur)) != 0) {
8!
257
    throw std::runtime_error("mdb_cursor_open failed: " + MDBError(rc));
×
258
  }
×
259

260
  MDB_val key, data;
8✔
261

262
  rc = mdb_cursor_get(cur, &key, &data, MDB_FIRST);
8✔
263

264
  while (rc == 0) {
40✔
265
    // std::string skey((char*) key.mv_data, key.mv_size);
266
    std::string sdata(reinterpret_cast<const char*>(data.mv_data), data.mv_size);
32✔
267

268
    std::string stdata = header + sdata;
32✔
269

270
    uint32_t id;
32✔
271

272
    if (key.mv_size != sizeof(uint32_t)) {
32!
273
      throw std::runtime_error("got non-uint32_t key in TypedDBI");
×
274
    }
×
275

276
    memcpy(&id, key.mv_data, sizeof(uint32_t));
32✔
277

278
    id = htonl(id);
32✔
279

280
    // cerr<<"got key="<<makeHexDump(skey)<<", data="<<makeHexDump(sdata)<<", sdata="<<makeHexDump(stdata)<<endl;
281

282
    MDB_val tkey;
32✔
283
    MDB_val tdata;
32✔
284

285
    tkey.mv_data = reinterpret_cast<char*>(&id);
32✔
286
    tkey.mv_size = sizeof(uint32_t);
32✔
287
    tdata.mv_data = const_cast<char*>(stdata.c_str());
32✔
288
    tdata.mv_size = stdata.size();
32✔
289

290
    if ((rc = mdb_put(txn, tdbi, &tkey, &tdata, 0)) != 0) {
32!
291
      throw std::runtime_error("mdb_put failed: " + MDBError(rc));
×
292
    }
×
293

294
    rc = mdb_cursor_get(cur, &key, &data, MDB_NEXT);
32✔
295
  }
32✔
296
  if (rc != MDB_NOTFOUND) {
8!
297
    throw std::runtime_error("error while iterating dbi: " + MDBError(rc));
×
298
  }
×
299
}
8✔
300

301
// migrating an index DBI:
302
// newkey = oldkey.len(), oldkey, htonl(oldvalue)
303
// newvalue = empty lsheader
304
void copyIndexDBI(MDB_txn* txn, MDB_dbi sdbi, MDB_dbi tdbi)
305
{
8✔
306
  // FIXME: clear out target dbi first
307

308
  std::string header(LMDBLS::LS_MIN_HEADER_SIZE, '\0');
8✔
309
  int rc;
8✔
310

311
  MDB_cursor* cur;
8✔
312

313
  if ((rc = mdb_cursor_open(txn, sdbi, &cur)) != 0) {
8!
314
    throw std::runtime_error("mdb_cursor_open failed: " + MDBError(rc));
×
315
  }
×
316

317
  MDB_val key, data;
8✔
318

319
  rc = mdb_cursor_get(cur, &key, &data, MDB_FIRST);
8✔
320

321
  while (rc == 0) {
40✔
322
    std::string lenprefix(sizeof(uint16_t), '\0');
32✔
323
    std::string skey((char*)key.mv_data, key.mv_size);
32✔
324

325
    uint32_t id;
32✔
326

327
    if (data.mv_size != sizeof(uint32_t)) {
32!
328
      throw std::runtime_error("got non-uint32_t ID value in IndexDBI");
×
329
    }
×
330

331
    memcpy((void*)&id, data.mv_data, sizeof(uint32_t));
32✔
332
    id = htonl(id);
32✔
333

334
    uint16_t len = htons(skey.size());
32✔
335
    memcpy((void*)lenprefix.data(), &len, sizeof(len));
32✔
336
    std::string stkey = lenprefix + skey + std::string((char*)&id, sizeof(uint32_t));
32✔
337

338
    MDB_val tkey;
32✔
339
    MDB_val tdata;
32✔
340

341
    tkey.mv_data = (char*)stkey.c_str();
32✔
342
    tkey.mv_size = stkey.size();
32✔
343
    tdata.mv_data = (char*)header.c_str();
32✔
344
    tdata.mv_size = header.size();
32✔
345

346
    if ((rc = mdb_put(txn, tdbi, &tkey, &tdata, 0)) != 0) {
32!
347
      throw std::runtime_error("mdb_put failed: " + MDBError(rc));
×
348
    }
×
349

350
    rc = mdb_cursor_get(cur, &key, &data, MDB_NEXT);
32✔
351
  }
32✔
352
  if (rc != MDB_NOTFOUND) {
8!
353
    throw std::runtime_error("error while iterating dbi: " + MDBError(rc));
×
354
  }
×
355
}
8✔
356

357
}
358

359
bool LMDBBackend::upgradeToSchemav5(std::string& filename)
360
{
2✔
361
  auto currentSchemaVersionAndShards = getSchemaVersionAndShards(filename);
2✔
362
  uint32_t currentSchemaVersion = currentSchemaVersionAndShards.first;
2✔
363
  uint32_t shards = currentSchemaVersionAndShards.second;
2✔
364

365
  if (currentSchemaVersion != 3 && currentSchemaVersion != 4) {
2!
366
    throw std::runtime_error("upgrade to v5 requested but current schema is not v3 or v4, stopping");
×
367
  }
×
368

369
  MDB_env* env = nullptr;
2✔
370

371
  if (int retCode = mdb_env_create(&env); retCode != 0) {
2!
372
    throw std::runtime_error("mdb_env_create failed: " + MDBError(retCode));
×
373
  }
×
374

375
  if (int retCode = mdb_env_set_maxdbs(env, 20); retCode != 0) {
2!
376
    mdb_env_close(env);
×
377
    throw std::runtime_error("mdb_env_set_maxdbs failed: " + MDBError(retCode));
×
378
  }
×
379

380
  if (int retCode = mdb_env_open(env, filename.c_str(), MDB_NOSUBDIR, 0600); retCode != 0) {
2!
381
    mdb_env_close(env);
×
382
    throw std::runtime_error("mdb_env_open failed: " + MDBError(retCode));
×
383
  }
×
384

385
  MDB_txn* txn = nullptr;
2✔
386

387
  if (int retCode = mdb_txn_begin(env, nullptr, 0, &txn); retCode != 0) {
2!
388
    mdb_env_close(env);
×
389
    throw std::runtime_error("mdb_txn_begin failed: " + MDBError(retCode));
×
390
  }
×
391

392
#ifdef HAVE_SYSTEMD
2✔
393
  /* A schema migration may take a long time. Extend the startup service timeout to 1 day,
394
   * but only if this is beyond the original maximum time of TimeoutStartSec=.
395
   */
396
  sd_notify(0, "EXTEND_TIMEOUT_USEC=86400000000");
2✔
397
#endif
2✔
398

399
  std::cerr << "migrating shards" << std::endl;
2✔
400
  for (uint32_t i = 0; i < shards; i++) {
6✔
401
    string shardfile = filename + "-" + std::to_string(i);
4✔
402
    if (access(shardfile.c_str(), F_OK) < 0) {
4!
403
      if (errno == ENOENT) {
×
404
        // apparently this shard doesn't exist yet, moving on
405
        std::cerr << "shard " << shardfile << " not found, continuing" << std::endl;
×
406
        continue;
×
407
      }
×
408
    }
×
409

410
    std::cerr << "migrating shard " << shardfile << std::endl;
4✔
411
    MDB_env* shenv = nullptr;
4✔
412

413
    if (int retCode = mdb_env_create(&shenv); retCode != 0) {
4!
414
      throw std::runtime_error("mdb_env_create failed: " + MDBError(retCode));
×
415
    }
×
416

417
    if (int retCode = mdb_env_set_maxdbs(shenv, 8); retCode != 0) {
4!
418
      mdb_env_close(env);
×
419
      throw std::runtime_error("mdb_env_set_maxdbs failed: " + MDBError(retCode));
×
420
    }
×
421

422
    if (int retCode = mdb_env_open(shenv, shardfile.c_str(), MDB_NOSUBDIR, 0600); retCode != 0) {
4!
423
      mdb_env_close(env);
×
424
      throw std::runtime_error("mdb_env_open failed: " + MDBError(retCode));
×
425
    }
×
426

427
    MDB_txn* shtxn = nullptr;
4✔
428

429
    if (int retCode = mdb_txn_begin(shenv, nullptr, 0, &shtxn); retCode != 0) {
4!
430
      mdb_env_close(env);
×
431
      throw std::runtime_error("mdb_txn_begin failed: " + MDBError(retCode));
×
432
    }
×
433

434
    MDB_dbi shdbi = 0;
4✔
435

436
    const auto dbiOpenRc = MDBDbi::mdb_dbi_open(shtxn, "records", 0, &shdbi);
4✔
437
    if (dbiOpenRc != 0) {
4!
438
      if (dbiOpenRc == MDB_NOTFOUND) {
×
439
        mdb_txn_abort(shtxn);
×
440
        mdb_env_close(shenv);
×
441
        continue;
×
442
      }
×
443
      mdb_txn_abort(shtxn);
×
444
      mdb_env_close(shenv);
×
445
      throw std::runtime_error("mdb_dbi_open shard records failed: " + MDBError(dbiOpenRc));
×
446
    }
×
447

448
    MDB_dbi shdbi2 = 0;
4✔
449

450
    if (int retCode = MDBDbi::mdb_dbi_open(shtxn, "records_v5", MDB_CREATE, &shdbi2); retCode != 0) {
4!
451
      mdb_dbi_close(shenv, shdbi);
×
452
      mdb_txn_abort(shtxn);
×
453
      mdb_env_close(shenv);
×
454
      throw std::runtime_error("mdb_dbi_open shard records_v5 failed: " + MDBError(retCode));
×
455
    }
×
456

457
    try {
4✔
458
      copyDBIAndAddLSHeader(shtxn, shdbi, shdbi2);
4✔
459
    }
4✔
460
    catch (std::exception& e) {
4✔
461
      mdb_dbi_close(shenv, shdbi2);
×
462
      mdb_dbi_close(shenv, shdbi);
×
463
      mdb_txn_abort(shtxn);
×
464
      mdb_env_close(shenv);
×
465
      throw std::runtime_error("copyDBIAndAddLSHeader failed");
×
466
    }
×
467

468
    cerr << "shard mbd_drop=" << mdb_drop(shtxn, shdbi, 1) << endl;
4✔
469
    mdb_txn_commit(shtxn);
4✔
470
    mdb_dbi_close(shenv, shdbi2);
4✔
471
    mdb_env_close(shenv);
4✔
472
  }
4✔
473

474
  std::array<MDB_dbi, 4> fromtypeddbi{};
2✔
475
  std::array<MDB_dbi, 4> totypeddbi{};
2✔
476

477
  int index = 0;
2✔
478

479
  for (const std::string dbname : {"domains", "keydata", "tsig", "metadata"}) {
8✔
480
    std::cerr << "migrating " << dbname << std::endl;
8✔
481
    std::string tdbname = dbname + "_v5";
8✔
482

483
    // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-constant-array-index)
484
    if (int retCode = MDBDbi::mdb_dbi_open(txn, dbname.c_str(), 0, &fromtypeddbi[index]); retCode != 0) {
8!
485
      mdb_txn_abort(txn);
×
486
      mdb_env_close(env);
×
487
      throw std::runtime_error("MDBDbi::mdb_dbi_open typeddbi failed: " + MDBError(retCode));
×
488
    }
×
489

490
    // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-constant-array-index)
491
    if (int retCode = MDBDbi::mdb_dbi_open(txn, tdbname.c_str(), MDB_CREATE, &totypeddbi[index]); retCode != 0) {
8!
492
      // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-constant-array-index)
493
      mdb_dbi_close(env, fromtypeddbi[index]);
×
494
      mdb_txn_abort(txn);
×
495
      mdb_env_close(env);
×
496
      throw std::runtime_error("mdb_dbi_open typeddbi target failed: " + MDBError(retCode));
×
497
    }
×
498

499
    try {
8✔
500
      // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-constant-array-index)
501
      copyTypedDBI(txn, fromtypeddbi[index], totypeddbi[index]);
8✔
502
    }
8✔
503
    catch (std::exception& e) {
8✔
504
      // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-constant-array-index)
505
      mdb_dbi_close(env, totypeddbi[index]);
×
506
      // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-constant-array-index)
507
      mdb_dbi_close(env, fromtypeddbi[index]);
×
508
      mdb_txn_abort(txn);
×
509
      mdb_env_close(env);
×
510
      throw std::runtime_error("copyTypedDBI failed");
×
511
    }
×
512

513
    // mdb_dbi_close(env, dbi2);
514
    // mdb_dbi_close(env, dbi);
515
    std::cerr << "migrated " << dbname << std::endl;
8✔
516

517
    index++;
8✔
518
  }
8✔
519

520
  std::array<MDB_dbi, 4> fromindexdbi{};
2✔
521
  std::array<MDB_dbi, 4> toindexdbi{};
2✔
522

523
  index = 0;
2✔
524

525
  for (const std::string dbname : {"domains", "keydata", "tsig", "metadata"}) {
8✔
526
    std::string fdbname = dbname + "_0";
8✔
527
    std::cerr << "migrating " << dbname << std::endl;
8✔
528
    std::string tdbname = dbname + "_v5_0";
8✔
529

530
    // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-constant-array-index)
531
    if (int retCode = MDBDbi::mdb_dbi_open(txn, fdbname.c_str(), 0, &fromindexdbi[index]); retCode != 0) {
8!
532
      mdb_txn_abort(txn);
×
533
      mdb_env_close(env);
×
534
      throw std::runtime_error("mdb_dbi_open indexdbi failed: " + MDBError(retCode));
×
535
    }
×
536

537
    // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-constant-array-index)
538
    if (int retCode = MDBDbi::mdb_dbi_open(txn, tdbname.c_str(), MDB_CREATE, &toindexdbi[index]); retCode != 0) {
8!
539
      // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-constant-array-index)
540
      mdb_dbi_close(env, fromindexdbi[index]);
×
541
      mdb_txn_abort(txn);
×
542
      mdb_env_close(env);
×
543
      throw std::runtime_error("mdb_dbi_open indexdbi target failed: " + MDBError(retCode));
×
544
    }
×
545

546
    try {
8✔
547
      // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-constant-array-index)
548
      copyIndexDBI(txn, fromindexdbi[index], toindexdbi[index]);
8✔
549
    }
8✔
550
    catch (std::exception& e) {
8✔
551
      // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-constant-array-index)
552
      mdb_dbi_close(env, toindexdbi[index]);
×
553
      // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-constant-array-index)
554
      mdb_dbi_close(env, fromindexdbi[index]);
×
555
      mdb_txn_abort(txn);
×
556
      mdb_env_close(env);
×
557
      throw std::runtime_error("copyIndexDBI failed");
×
558
    }
×
559

560
    // mdb_dbi_close(env, dbi2);
561
    // mdb_dbi_close(env, dbi);
562
    std::cerr << "migrated " << dbname << std::endl;
8✔
563

564
    index++;
8✔
565
  }
8✔
566

567
  MDB_dbi dbi = 0;
2✔
568

569
  // finally, migrate the pdns db
570
  if (int retCode = MDBDbi::mdb_dbi_open(txn, "pdns", 0, &dbi); retCode != 0) {
2!
571
    mdb_txn_abort(txn);
×
572
    mdb_env_close(env);
×
573
    throw std::runtime_error("mdb_dbi_open pdns failed: " + MDBError(retCode));
×
574
  }
×
575

576
  MDB_val key;
2✔
577
  MDB_val data;
2✔
578

579
  std::string header(LMDBLS::LS_MIN_HEADER_SIZE, '\0');
2✔
580

581
  for (const std::string keyname : {"schemaversion", "shards"}) {
4✔
582
    cerr << "migrating pdns." << keyname << endl;
4✔
583

584
    key.mv_data = (char*)keyname.c_str();
4✔
585
    key.mv_size = keyname.size();
4✔
586

587
    if (int retCode = mdb_get(txn, dbi, &key, &data); retCode != 0) {
4!
588
      throw std::runtime_error("mdb_get pdns.shards failed: " + MDBError(retCode));
×
589
    }
×
590

591
    if (data.mv_size != sizeof(uint32_t)) {
4!
592
      throw std::runtime_error("got non-uint32_t key");
×
593
    }
×
594

595
    uint32_t value = 0;
4✔
596
    memcpy((void*)&value, data.mv_data, sizeof(uint32_t));
4✔
597

598
    value = htonl(value);
4✔
599
    if (keyname == "schemaversion") {
4✔
600
      value = htonl(5);
2✔
601
    }
2✔
602

603
    std::string sdata(static_cast<char*>(data.mv_data), data.mv_size);
4✔
604
    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-cstyle-cast)
605
    std::string stdata = header + std::string((char*)&value, sizeof(uint32_t));
4✔
606

607
    MDB_val tdata;
4✔
608

609
    tdata.mv_data = (char*)stdata.c_str();
4✔
610
    tdata.mv_size = stdata.size();
4✔
611

612
    if (int retCode = mdb_put(txn, dbi, &key, &tdata, 0); retCode != 0) {
4!
613
      throw std::runtime_error("mdb_put failed: " + MDBError(retCode));
×
614
    }
×
615
  }
4✔
616

617
  for (const std::string keyname : {"uuid"}) {
2✔
618
    cerr << "migrating pdns." << keyname << endl;
2✔
619

620
    key.mv_data = (char*)keyname.c_str();
2✔
621
    key.mv_size = keyname.size();
2✔
622

623
    if (int retCode = mdb_get(txn, dbi, &key, &data); retCode != 0) {
2!
624
      throw std::runtime_error("mdb_get pdns.shards failed: " + MDBError(retCode));
×
625
    }
×
626

627
    std::string sdata((char*)data.mv_data, data.mv_size);
2✔
628

629
    std::string stdata = header + sdata;
2✔
630

631
    MDB_val tdata;
2✔
632

633
    tdata.mv_data = (char*)stdata.c_str();
2✔
634
    tdata.mv_size = stdata.size();
2✔
635

636
    if (int retCode = mdb_put(txn, dbi, &key, &tdata, 0); retCode != 0) {
2!
637
      throw std::runtime_error("mdb_put failed: " + MDBError(retCode));
×
638
    }
×
639
  }
2✔
640

641
  for (int i = 0; i < 4; i++) {
10✔
642
    // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-constant-array-index)
643
    mdb_drop(txn, fromtypeddbi[i], 1);
8✔
644
    // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-constant-array-index)
645
    mdb_drop(txn, fromindexdbi[i], 1);
8✔
646
  }
8✔
647

648
  cerr << "txn commit=" << mdb_txn_commit(txn) << endl;
2✔
649

650
  for (int i = 0; i < 4; i++) {
10✔
651
    // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-constant-array-index)
652
    mdb_dbi_close(env, totypeddbi[i]);
8✔
653
    // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-constant-array-index)
654
    mdb_dbi_close(env, toindexdbi[i]);
8✔
655
  }
8✔
656
  mdb_env_close(env);
2✔
657

658
  cerr << "migration done" << endl;
2✔
659
  return true;
2✔
660
}
2✔
661

662
bool LMDBBackend::upgradeToSchemav6(std::string& /* filename */)
663
{
3✔
664
  // a v6 reader can read v5 databases just fine
665
  // so this function currently does nothing
666
  // - except rely on the caller to write '6' to pdns.schemaversion,
667
  // as a v5 reader will be unable to handle domain objects once we've touched them
668
  return true;
3✔
669
}
3✔
670

671
// Serial number cache
672
bool LMDBBackend::SerialCache::get(domainid_t domainid, uint32_t& serial) const
673
{
×
674
  if (auto iter = d_serials.find(domainid); iter != d_serials.end()) {
×
675
    serial = iter->second;
×
676
    return true;
×
677
  }
×
678
  return false;
×
679
}
×
680

681
void LMDBBackend::SerialCache::remove(domainid_t domainid)
682
{
31✔
683
  if (auto iter = d_serials.find(domainid); iter != d_serials.end()) {
31!
684
    d_serials.erase(iter);
×
685
  }
×
686
}
31✔
687

688
void LMDBBackend::SerialCache::update(domainid_t domainid, uint32_t serial)
689
{
×
690
  d_serials.insert_or_assign(domainid, serial);
×
691
}
×
692

693
SharedLockGuarded<LMDBBackend::SerialCache> LMDBBackend::s_notified_serial;
694

695
LMDBBackend::LMDBBackend(const std::string& suffix)
696
{
4,428✔
697
  // overlapping domain ids in combination with relative names are a recipe for disaster
698
  if (!suffix.empty()) {
4,428!
699
    throw std::runtime_error("LMDB backend does not support multiple instances");
×
700
  }
×
701

702
  d_views = ::arg().mustDo("views"); // This is a global setting
4,428✔
703

704
  setArgPrefix("lmdb" + suffix);
4,428✔
705

706
  string syncMode = toLower(getArg("sync-mode"));
4,428✔
707

708
  if (syncMode == "nosync")
4,428!
709
    d_asyncFlag = MDB_NOSYNC;
×
710
  else if (syncMode == "nometasync")
4,428!
711
    d_asyncFlag = MDB_NOMETASYNC;
×
712
  else if (syncMode.empty() || syncMode == "sync")
4,428!
713
    d_asyncFlag = 0;
4,428✔
714
  else
×
715
    throw std::runtime_error("Unknown sync mode " + syncMode + " requested for LMDB backend");
×
716

717
  d_mapsize = 0;
4,428✔
718
  try {
4,428✔
719
    d_mapsize = std::stoll(getArg("map-size"));
4,428✔
720
  }
4,428✔
721
  catch (const std::exception& e) {
4,428✔
722
    throw std::runtime_error(std::string("Unable to parse the 'map-size' LMDB value: ") + e.what());
×
723
  }
×
724

725
  d_write_notification_update = mustDo("write-notification-update");
4,428✔
726

727
  if (mustDo("lightning-stream")) {
4,428!
728
    d_random_ids = true;
×
729
    d_handle_dups = true;
×
730
    LMDBLS::s_flag_deleted = true;
×
731

732
    if (atoi(getArg("shards").c_str()) != 1) {
×
733
      throw std::runtime_error(std::string("running with Lightning Stream support requires shards=1"));
×
734
    }
×
735
  }
×
736
  else {
4,428✔
737
    d_random_ids = mustDo("random-ids");
4,428✔
738
    d_handle_dups = false;
4,428✔
739
    LMDBLS::s_flag_deleted = mustDo("flag-deleted");
4,428✔
740
  }
4,428✔
741

742
  bool opened = false;
4,428✔
743

744
  if (s_first) {
4,428✔
745
    auto lock = std::scoped_lock(s_lmdbStartupLock);
2,481✔
746
    if (s_first) {
2,481!
747
      auto filename = getArg("filename");
2,481✔
748

749
      auto currentSchemaVersionAndShards = getSchemaVersionAndShards(filename);
2,481✔
750
      uint32_t currentSchemaVersion = currentSchemaVersionAndShards.first;
2,481✔
751
      // std::cerr<<"current schema version: "<<currentSchemaVersion<<", shards="<<currentSchemaVersionAndShards.second<<std::endl;
752

753
      if (getArgAsNum("schema-version") != SCHEMAVERSION) {
2,481!
754
        throw std::runtime_error("This version of the lmdbbackend only supports schema version 6. Configuration demands a lower version. Not starting up.");
×
755
      }
×
756

757
      if (currentSchemaVersion > 0 && currentSchemaVersion < 3) {
2,481!
758
        throw std::runtime_error("this version of the lmdbbackend can only upgrade from schema v3 and up. Upgrading from older schemas is not supported.");
×
759
      }
×
760

761
      if (currentSchemaVersion == 0) {
2,481✔
762
        // no database is present yet, we can just create them
763
        currentSchemaVersion = 6;
53✔
764
      }
53✔
765

766
      if (currentSchemaVersion == 3 || currentSchemaVersion == 4) {
2,481✔
767
        if (!upgradeToSchemav5(filename)) {
2!
768
          throw std::runtime_error("Failed to perform LMDB schema version upgrade from v4 to v5");
×
769
        }
×
770
        currentSchemaVersion = 5;
2✔
771
      }
2✔
772

773
      if (currentSchemaVersion == 5) {
2,481✔
774
        if (!upgradeToSchemav6(filename)) {
3!
775
          throw std::runtime_error("Failed to perform LMDB schema version upgrade from v5 to v6");
×
776
        }
×
777
        currentSchemaVersion = 6;
3✔
778
      }
3✔
779

780
      if (currentSchemaVersion != 6) {
2,481!
781
        throw std::runtime_error("Somehow, we are not at schema version 6. Giving up");
×
782
      }
×
783

784
      openAllTheDatabases();
2,481✔
785
      opened = true;
2,481✔
786
      auto pdnsdbi = d_tdomains->getEnv()->openDB("pdns", MDB_CREATE);
2,481✔
787

788
      auto txn = d_tdomains->getEnv()->getRWTransaction();
2,481✔
789

790
      const auto configShardsTemp = atoi(getArg("shards").c_str());
2,481✔
791
      if (configShardsTemp < 0) {
2,481!
792
        throw std::runtime_error("a negative shards value is not supported");
×
793
      }
×
794
      if (configShardsTemp == 0) {
2,481!
795
        throw std::runtime_error("a shards value of 0 is not supported");
×
796
      }
×
797
      const auto configShards = static_cast<uint32_t>(configShardsTemp);
2,481✔
798

799
      MDBOutVal shards{};
2,481✔
800
      if (txn->get(pdnsdbi, "shards", shards) == 0) {
2,481✔
801
        s_shards = shards.get<uint32_t>();
2,428✔
802

803
        if (mustDo("lightning-stream") && s_shards != 1) {
2,428!
804
          throw std::runtime_error("running with Lightning Stream support enabled requires a database with exactly 1 shard");
×
805
        }
×
806

807
        if (s_shards != configShards) {
2,428!
808
          g_log << Logger::Warning
×
809
                << "Note: configured number of lmdb shards ("
×
810
                << atoi(getArg("shards").c_str())
×
811
                << ") is different from on-disk ("
×
812
                << s_shards
×
813
                << "). Using on-disk shard number"
×
814
                << endl;
×
815
        }
×
816
      }
2,428✔
817
      else {
53✔
818
        s_shards = configShards;
53✔
819
        txn->put(pdnsdbi, "shards", s_shards);
53✔
820
      }
53✔
821

822
      MDBOutVal gotuuid{};
2,481✔
823
      if (txn->get(pdnsdbi, "uuid", gotuuid) != 0) {
2,481✔
824
        const auto uuid = getUniqueID();
53✔
825
        const string uuids(uuid.begin(), uuid.end());
53✔
826
        txn->put(pdnsdbi, "uuid", uuids);
53✔
827
      }
53✔
828

829
      MDBOutVal _schemaversion{};
2,481✔
830
      if (txn->get(pdnsdbi, "schemaversion", _schemaversion) != 0 || _schemaversion.get<uint32_t>() != currentSchemaVersion) {
2,481✔
831
        txn->put(pdnsdbi, "schemaversion", currentSchemaVersion);
56✔
832
      }
56✔
833
      txn->commit();
2,481✔
834

835
      s_first = false;
2,481✔
836
    }
2,481✔
837
  }
2,481✔
838

839
  if (!opened) {
4,428✔
840
    openAllTheDatabases();
1,947✔
841
  }
1,947✔
842
  d_trecords.resize(s_shards);
4,428✔
843
  d_dolog = ::arg().mustDo("query-logging");
4,428✔
844
}
4,428✔
845

846
LMDBBackend::~LMDBBackend()
847
{
4,303✔
848
  // LMDB internals require that, if we have multiple transactions active,
849
  // we destroy them in the reverse order of their creation, thus we can't
850
  // let the default destructor take care of d_rotxn and d_rwtxn.
851
  if (d_txnorder) {
4,303✔
852
    // RO transaction more recent than RW transaction
853
    d_rotxn.reset();
1,261✔
854
    d_rwtxn.reset();
1,261✔
855
  }
1,261✔
856
  else {
3,042✔
857
    // RW transaction more recent than RO transaction
858
    d_rwtxn.reset();
3,042✔
859
    d_rotxn.reset();
3,042✔
860
  }
3,042✔
861
}
4,303✔
862

863
void LMDBBackend::openAllTheDatabases()
864
{
4,428✔
865
  d_tdomains = std::make_shared<tdomains_t>(getMDBEnv(getArg("filename").c_str(), MDB_NOSUBDIR | MDB_NORDAHEAD | d_asyncFlag, 0600, d_mapsize), "domains_v5");
4,428✔
866
  d_tmeta = std::make_shared<tmeta_t>(d_tdomains->getEnv(), "metadata_v5");
4,428✔
867
  d_tkdb = std::make_shared<tkdb_t>(d_tdomains->getEnv(), "keydata_v5");
4,428✔
868
  d_ttsig = std::make_shared<ttsig_t>(d_tdomains->getEnv(), "tsig_v5");
4,428✔
869
  d_tnetworks = d_tdomains->getEnv()->openDB("networks_v6", MDB_CREATE);
4,428✔
870
  d_tviews = d_tdomains->getEnv()->openDB("views_v6", MDB_CREATE);
4,428✔
871
}
4,428✔
872

873
unsigned int LMDBBackend::getCapabilities()
874
{
23,184✔
875
  unsigned int caps = CAP_DNSSEC | CAP_DIRECT | CAP_LIST | CAP_CREATE;
23,184✔
876
  if (d_views) {
23,184✔
877
    caps |= CAP_VIEWS;
21,711✔
878
  }
21,711✔
879
  return caps;
23,184✔
880
}
23,184✔
881

882
namespace boost
883
{
884
namespace serialization
885
{
886

887
  template <class Archive>
888
  void save(Archive& ar, const DNSName& g, const unsigned int /* version */)
889
  {
3,709✔
890
    if (g.empty()) {
3,709✔
891
      ar& std::string();
935✔
892
    }
935✔
893
    else {
2,774✔
894
      ar & g.toDNSStringLC();
2,774✔
895
    }
2,774✔
896
  }
3,709✔
897

898
  template <class Archive>
899
  void load(Archive& ar, DNSName& g, const unsigned int /* version */)
900
  {
353,636✔
901
    string tmp;
353,636✔
902
    ar & tmp;
353,636✔
903
    if (tmp.empty()) {
353,636✔
904
      g = DNSName();
130,852✔
905
    }
130,852✔
906
    else {
222,784✔
907
      g = DNSName(tmp.c_str(), tmp.size(), 0, false);
222,784✔
908
    }
222,784✔
909
  }
353,636✔
910

911
  template <class Archive>
912
  void save(Archive& arc, const ZoneName& zone, const unsigned int /* version */)
913
  {
3,655✔
914
    arc & zone.operator const DNSName&();
3,655✔
915
    arc & zone.getVariant();
3,655✔
916
  }
3,655✔
917

918
  template <class Archive>
919
  void load(Archive& arc, ZoneName& zone, const unsigned int version)
920
  {
353,359✔
921
    if (version == 0) { // for schemas up to 5, ZoneName serialized as DNSName
353,359!
922
      std::string tmp{};
×
923
      arc & tmp;
×
924
      if (tmp.empty()) {
×
925
        zone = ZoneName();
×
926
      }
×
927
      else {
×
928
        zone = ZoneName(DNSName(tmp.c_str(), tmp.size(), 0, false));
×
929
      }
×
930
      return;
×
931
    }
×
932
    DNSName tmp;
353,359✔
933
    std::string variant{};
353,359✔
934
    arc & tmp;
353,359✔
935
    arc & variant;
353,359✔
936
    zone = ZoneName(tmp, variant);
353,359✔
937
  }
353,359✔
938

939
  template <class Archive>
940
  void save(Archive& ar, const DomainInfo& g, const unsigned int /* version */)
941
  {
1,303✔
942
    ar & g.zone;
1,303✔
943
    ar & g.last_check;
1,303✔
944
    ar & g.account;
1,303✔
945
    ar & g.primaries;
1,303✔
946
    ar& static_cast<uint32_t>(g.id);
1,303✔
947
    ar & g.notified_serial;
1,303✔
948
    ar & g.kind;
1,303✔
949
    ar & g.options;
1,303✔
950
    ar & g.catalog;
1,303✔
951
  }
1,303✔
952

953
  template <class Archive>
954
  void load(Archive& ar, DomainInfo& g, const unsigned int version)
955
  {
169,003✔
956
    if (version >= 2) {
169,004✔
957
      ar & g.zone;
168,956✔
958
    }
168,956✔
959
    else {
2,147,483,695✔
960
      DNSName tmp;
2,147,483,695✔
961
      ar & tmp;
2,147,483,695✔
962
      new (&g.zone) ZoneName(tmp);
2,147,483,695✔
963
    }
2,147,483,695✔
964
    ar & g.last_check;
169,003✔
965
    ar & g.account;
169,003✔
966
    ar & g.primaries;
169,003✔
967
    uint32_t domainId{0};
169,003✔
968
    ar & domainId;
169,003✔
969
    g.id = static_cast<domainid_t>(domainId);
169,003✔
970
    ar & g.notified_serial;
169,003✔
971
    ar & g.kind;
169,003✔
972
    switch (version) {
169,003✔
973
    case 0:
47✔
974
      // These fields did not exist.
975
      g.options.clear();
47✔
976
      g.catalog.clear();
47✔
977
      break;
47✔
978
    case 1:
1✔
979
      // These fields did exist, but catalog as DNSName only.
980
      ar & g.options;
1✔
981
      {
1✔
982
        DNSName tmp;
1✔
983
        ar & tmp;
1✔
984
        g.catalog = ZoneName(tmp);
1✔
985
      }
1✔
986
      break;
1✔
987
    default:
168,956✔
988
      // These fields exist, with catalog as ZoneName.
989
      ar & g.options;
168,956✔
990
      ar & g.catalog;
168,956✔
991
      break;
168,956✔
992
    }
169,003✔
993
  }
169,003✔
994

995
  template <class Archive>
996
  void serialize(Archive& ar, LMDBBackend::DomainMeta& g, const unsigned int /* version */)
997
  {
15,438✔
998
    ar & g.domain & g.key & g.value;
15,438✔
999
  }
15,438✔
1000

1001
  template <class Archive>
1002
  void save(Archive& ar, const LMDBBackend::KeyDataDB& g, const unsigned int /* version */)
1003
  {
237✔
1004
    ar & g.domain & g.content & g.flags & g.active & g.published;
237✔
1005
  }
237✔
1006

1007
  template <class Archive>
1008
  void load(Archive& ar, LMDBBackend::KeyDataDB& g, const unsigned int version)
1009
  {
823✔
1010
    ar & g.domain & g.content & g.flags & g.active;
823✔
1011
    if (version >= 1) {
823!
1012
      ar & g.published;
823✔
1013
    }
823✔
1014
    else {
×
1015
      g.published = true;
×
1016
    }
×
1017
  }
823✔
1018

1019
  template <class Archive>
1020
  void serialize(Archive& ar, TSIGKey& g, const unsigned int /* version */)
1021
  {
141✔
1022
    ar & g.name;
141✔
1023
    ar & g.algorithm; // this is the ordername
141✔
1024
    ar & g.key;
141✔
1025
  }
141✔
1026

1027
} // namespace serialization
1028
} // namespace boost
1029

1030
BOOST_SERIALIZATION_SPLIT_FREE(DNSName);
1031
BOOST_SERIALIZATION_SPLIT_FREE(ZoneName);
1032
BOOST_SERIALIZATION_SPLIT_FREE(LMDBBackend::KeyDataDB);
1033
BOOST_SERIALIZATION_SPLIT_FREE(DomainInfo);
1034
BOOST_IS_BITWISE_SERIALIZABLE(ComboAddress);
1035

1036
template <>
1037
std::string serializeToBuffer(const LMDBBackend::LMDBResourceRecord& value)
1038
{
778,668✔
1039
  std::string buffer;
778,668✔
1040

1041
  // Data size of the resource record.
1042
  uint16_t len = value.content.length();
778,668✔
1043

1044
  // Reserve space to store the size of the resource record + the content of the resource
1045
  // record + a few other things.
1046
  buffer.reserve(sizeof(len) + len + sizeof(value.ttl) + sizeof(value.auth) + sizeof(value.disabled) + sizeof(value.hasOrderName));
778,668✔
1047

1048
  // Store the size of the resource record.
1049
  // NOLINTNEXTLINE.
1050
  buffer.assign((const char*)&len, sizeof(len));
778,668✔
1051

1052
  // Store the contents of the resource record.
1053
  buffer += value.content;
778,668✔
1054

1055
  // The few other things.
1056
  // NOLINTNEXTLINE.
1057
  buffer.append((const char*)&value.ttl, sizeof(value.ttl));
778,668✔
1058
  buffer.append(1, (char)value.auth);
778,668✔
1059
  buffer.append(1, (char)value.disabled);
778,668✔
1060
  buffer.append(1, (char)value.hasOrderName);
778,668✔
1061

1062
  return buffer;
778,668✔
1063
}
778,668✔
1064

1065
template <>
1066
std::string serializeToBuffer(const vector<LMDBBackend::LMDBResourceRecord>& value)
1067
{
84,608✔
1068
  std::string ret;
84,608✔
1069
  for (const auto& lrr : value) {
104,783✔
1070
    ret += serializeToBuffer(lrr);
104,783✔
1071
  }
104,783✔
1072
  return ret;
84,608✔
1073
}
84,608✔
1074

1075
static inline size_t deserializeRRFromBuffer(const string_view& str, LMDBBackend::LMDBResourceRecord& lrr)
1076
{
904,792✔
1077
  uint16_t len;
904,792✔
1078
  memcpy(&len, &str[0], 2);
904,792✔
1079
  lrr.content.assign(&str[2], len); // len bytes
904,792✔
1080
  memcpy(&lrr.ttl, &str[2] + len, 4);
904,792✔
1081
  lrr.auth = str[2 + len + 4];
904,792✔
1082
  lrr.disabled = str[2 + len + 4 + 1];
904,792✔
1083
  lrr.hasOrderName = str[2 + len + 4 + 2] != 0;
904,792✔
1084
  lrr.wildcardname.clear();
904,792✔
1085

1086
  return 2 + len + 7;
904,792✔
1087
}
904,792✔
1088

1089
template <>
1090
void deserializeFromBuffer(const string_view& buffer, LMDBBackend::LMDBResourceRecord& value)
1091
{
46,566✔
1092
  deserializeRRFromBuffer(buffer, value);
46,566✔
1093
}
46,566✔
1094

1095
template <>
1096
void deserializeFromBuffer(const string_view& buffer, vector<LMDBBackend::LMDBResourceRecord>& value)
1097
{
692,060✔
1098
  auto str_copy = buffer;
692,060✔
1099
  while (str_copy.size() >= 9) { // minimum length for a record is 10
1,550,288✔
1100
    LMDBBackend::LMDBResourceRecord lrr;
858,228✔
1101
    auto rrLength = deserializeRRFromBuffer(str_copy, lrr);
858,228✔
1102
    value.emplace_back(lrr);
858,228✔
1103
    str_copy.remove_prefix(rrLength);
858,228✔
1104
  }
858,228✔
1105
}
692,060✔
1106

1107
static std::string serializeContent(uint16_t qtype, const DNSName& domain, const std::string& content)
1108
{
430,939✔
1109
  auto drc = DNSRecordContent::make(qtype, QClass::IN, content);
430,939✔
1110
  return drc->serialize(domain, false);
430,939✔
1111
}
430,939✔
1112

1113
static std::shared_ptr<DNSRecordContent> deserializeContentZR(uint16_t qtype, const DNSName& qname, const std::string& content)
1114
{
651,856✔
1115
  if (qtype == QType::A && content.size() == 4) {
651,856!
1116
    return std::make_shared<ARecordContent>(*((uint32_t*)content.c_str()));
519,162✔
1117
  }
519,162✔
1118
  return DNSRecordContent::deserialize(qname, qtype, content, QClass::IN, true);
132,694✔
1119
}
651,856✔
1120

1121
/* design. If you ask a question without a zone id, we lookup the best
1122
   zone id for you, and answer from that. This is different than other backends, but I can't see why it would not work.
1123

1124
   The index we use is "zoneid,canonical relative name". This index is also used
1125
   for AXFR.
1126

1127
   Note - domain_id, name and type are ONLY present on the index!
1128
*/
1129

1130
#if BOOST_VERSION >= 106100
1131
#define StringView string_view
1132
#else
1133
#define StringView string
1134
#endif
1135

1136
void LMDBBackend::deleteDomainRecords(RecordsRWTransaction& txn, const std::string& match)
1137
{
953✔
1138
  auto cursor = txn.txn->getCursor(txn.db->dbi);
953✔
1139
  MDBOutVal key{};
953✔
1140
  MDBOutVal val{};
953✔
1141

1142
  if (cursor.prefix(match, key, val) == 0) {
953✔
1143
    do {
797✔
1144
      cursor.del(key);
797✔
1145
    } while (cursor.next(key, val) == 0);
797✔
1146
  }
88✔
1147
}
953✔
1148

1149
bool LMDBBackend::findDomain(const ZoneName& domain, DomainInfo& info) const
1150
{
8,509✔
1151
  auto rotxn = d_tdomains->getROTransaction();
8,509✔
1152
  auto domain_id = rotxn.get<0>(domain, info);
8,509✔
1153
  if (domain_id == 0) {
8,509✔
1154
    return false;
1,029✔
1155
  }
1,029✔
1156
  info.id = static_cast<domainid_t>(domain_id);
7,480✔
1157
  return true;
7,480✔
1158
}
8,509✔
1159

1160
bool LMDBBackend::findDomain(domainid_t domainid, DomainInfo& info) const
1161
{
159,057✔
1162
  auto rotxn = d_tdomains->getROTransaction();
159,057✔
1163
  if (!rotxn.get(domainid, info)) {
159,057✔
1164
    return false;
8✔
1165
  }
8✔
1166
  info.id = domainid;
159,049✔
1167
  return true;
159,049✔
1168
}
159,057✔
1169

1170
void LMDBBackend::consolidateDomainInfo(DomainInfo& info) const
1171
{
7,076✔
1172
  // Update the notified_serial value if we have a cached value in memory.
1173
  if (!d_write_notification_update) {
7,076!
1174
    auto container = s_notified_serial.read_lock();
×
1175
    container->get(info.id, info.notified_serial);
×
1176
  }
×
1177
}
7,076✔
1178

1179
void LMDBBackend::writeDomainInfo(const DomainInfo& info)
1180
{
801✔
1181
  if (!d_write_notification_update) {
801!
1182
    uint32_t last_notified_serial{0};
×
1183
    auto container = s_notified_serial.write_lock();
×
1184
    container->get(info.id, last_notified_serial);
×
1185
    // Only remove the in-memory value if it has not been modified since the
1186
    // DomainInfo data was set up.
1187
    if (last_notified_serial == info.notified_serial) {
×
1188
      container->remove(info.id);
×
1189
    }
×
1190
  }
×
1191
  auto txn = d_tdomains->getRWTransaction();
801✔
1192
  txn.put(info, info.id);
801✔
1193
  txn.commit();
801✔
1194
}
801✔
1195

1196
/* Here's the complicated story. Other backends have just one transaction, which is either
1197
   on or not.
1198

1199
   You can't call feedRecord without a transaction started with startTransaction.
1200

1201
   However, other functions can be called after startTransaction() or without startTransaction()
1202
     (like updateDNSSECOrderNameAndAuth)
1203

1204

1205

1206
*/
1207

1208
bool LMDBBackend::startTransaction(const ZoneName& domain, domainid_t domain_id)
1209
{
2,583✔
1210
  // cout <<"startTransaction("<<domain<<", "<<domain_id<<")"<<endl;
1211
  domainid_t real_id = domain_id;
2,583✔
1212
  if (real_id == UnknownDomainID) {
2,583✔
1213
    DomainInfo info;
1,690✔
1214
    if (!findDomain(domain, info)) {
1,690!
1215
      return false;
×
1216
    }
×
1217
    real_id = info.id;
1,690✔
1218
  }
1,690✔
1219
  if (d_rwtxn) {
2,583!
1220
    throw DBException("Attempt to start a transaction while one was open already");
×
1221
  }
×
1222
  d_rwtxn = getRecordsRWTransaction(real_id);
2,583✔
1223
  d_txnorder = false;
2,583✔
1224

1225
  d_transactiondomain = domain;
2,583✔
1226
  d_transactiondomainid = real_id;
2,583✔
1227
  if (domain_id != UnknownDomainID) {
2,583✔
1228
    compoundOrdername order;
893✔
1229
    string match = order(domain_id);
893✔
1230
    LMDBBackend::deleteDomainRecords(*d_rwtxn, match);
893✔
1231
  }
893✔
1232

1233
  return true;
2,583✔
1234
}
2,583✔
1235

1236
bool LMDBBackend::commitTransaction()
1237
{
2,321✔
1238
  // cout<<"Commit transaction" <<endl;
1239
  if (!d_rwtxn) {
2,321!
1240
    throw DBException("Attempt to commit a transaction while there isn't one open");
×
1241
  }
×
1242

1243
  d_rwtxn->txn->commit();
2,321✔
1244
  d_rwtxn.reset();
2,321✔
1245
  return true;
2,321✔
1246
}
2,321✔
1247

1248
bool LMDBBackend::abortTransaction()
1249
{
239✔
1250
  // cout<<"Abort transaction"<<endl;
1251
  if (!d_rwtxn) {
239!
1252
    throw DBException("Attempt to abort a transaction while there isn't one open");
×
1253
  }
×
1254

1255
  d_rwtxn->txn->abort();
239✔
1256
  d_rwtxn.reset();
239✔
1257

1258
  return true;
239✔
1259
}
239✔
1260

1261
// Remove the NSEC3 records pair found at the given `qname', if any.
1262
void LMDBBackend::deleteNSEC3RecordPair(const std::shared_ptr<RecordsRWTransaction>& txn, domainid_t domain_id, const DNSName& qname)
1263
{
44,345✔
1264
  compoundOrdername co; // NOLINT(readability-identifier-length)
44,345✔
1265
  MDBOutVal val{};
44,345✔
1266

1267
  auto key = co(domain_id, qname, QType::NSEC3);
44,345✔
1268
  if (txn->txn->get(txn->db->dbi, key, val) == 0) {
44,345✔
1269
    LMDBResourceRecord lrr;
489✔
1270
    deserializeFromBuffer(val.get<string_view>(), lrr);
489✔
1271
    DNSName ordername(lrr.content.c_str(), lrr.content.size(), 0, false);
489✔
1272
    txn->txn->del(txn->db->dbi, co(domain_id, ordername, QType::NSEC3));
489✔
1273
    txn->txn->del(txn->db->dbi, key);
489✔
1274
  }
489✔
1275
}
44,345✔
1276

1277
// Write a pair of NSEC3 records referencing each other, between `qname' and
1278
// `ordername'.
1279
void LMDBBackend::writeNSEC3RecordPair(const std::shared_ptr<RecordsRWTransaction>& txn, domainid_t domain_id, const DNSName& qname, const DNSName& ordername)
1280
{
123,134✔
1281
  // We can only write one NSEC3 record par qname; do not attempt to write
1282
  // records pointing to ourselves, as only the last record of the pair would
1283
  // end up in the database.
1284
  if (ordername == qname) {
123,134!
1285
    return;
×
1286
  }
×
1287

1288
  compoundOrdername co; // NOLINT(readability-identifier-length)
123,134✔
1289

1290
  // Check for an existing NSEC3 record. If one exists, either it points to the
1291
  // same ordername and we have nothing to do, or the ordername has changed and
1292
  // we need to remove the about-to-become-dangling back chain record.
1293
  MDBOutVal val{};
123,134✔
1294
  if (txn->txn->get(txn->db->dbi, co(domain_id, qname, QType::NSEC3), val) == 0) {
123,134✔
1295
    LMDBResourceRecord lrr;
1,436✔
1296
    deserializeFromBuffer(val.get<string_view>(), lrr);
1,436✔
1297
    DNSName prevordername(lrr.content.c_str(), lrr.content.size(), 0, false);
1,436✔
1298
    if (prevordername == ordername) {
1,436✔
1299
      return; // nothing to do! (assuming the other record also exists)
1,288✔
1300
    }
1,288✔
1301
    txn->txn->del(txn->db->dbi, co(domain_id, prevordername, QType::NSEC3));
148✔
1302
  }
148✔
1303

1304
  LMDBResourceRecord lrr;
121,846✔
1305
  lrr.auth = false;
121,846✔
1306

1307
  // Write ordername -> qname back chain record with ttl set to 0
1308
  lrr.ttl = 0;
121,846✔
1309
  lrr.content = qname.toDNSStringLC();
121,846✔
1310
  string ser = serializeToBuffer(lrr);
121,846✔
1311
  txn->txn->put(txn->db->dbi, co(domain_id, ordername, QType::NSEC3), ser);
121,846✔
1312

1313
  // Write qname -> ordername forward chain record with ttl set to 1
1314
  lrr.ttl = 1;
121,846✔
1315
  lrr.content = ordername.toDNSString();
121,846✔
1316
  ser = serializeToBuffer(lrr);
121,846✔
1317
  txn->txn->put(txn->db->dbi, co(domain_id, qname, QType::NSEC3), ser);
121,846✔
1318
}
121,846✔
1319

1320
// Check if the only records found for this particular name are a single NSEC3
1321
// record. (in which case there is no actual data for than qname and that
1322
// record needs to be deleted)
1323
bool LMDBBackend::hasOrphanedNSEC3Record(MDBRWCursor& cursor, domainid_t domain_id, const DNSName& qname)
1324
{
428✔
1325
  compoundOrdername co; // NOLINT(readability-identifier-length)
428✔
1326
  bool seenNSEC3{false};
428✔
1327
  bool seenOther{false};
428✔
1328
  MDBOutVal key{};
428✔
1329
  MDBOutVal val{};
428✔
1330

1331
  if (cursor.prefix(co(domain_id, qname), key, val) == 0) {
428✔
1332
    do {
341✔
1333
      if (compoundOrdername::getQType(key.getNoStripHeader<StringView>()) == QType::NSEC3) {
341✔
1334
        seenNSEC3 = true;
150✔
1335
      }
150✔
1336
      else {
191✔
1337
        seenOther = true;
191✔
1338
      }
191✔
1339
      if (seenNSEC3 && seenOther) {
341✔
1340
        break;
36✔
1341
      }
36✔
1342
    } while (cursor.next(key, val) == 0);
341✔
1343
  }
205✔
1344
  return seenNSEC3 && !seenOther;
428✔
1345
}
428✔
1346

1347
// d_rwtxn must be set here
1348
bool LMDBBackend::feedRecord(const DNSResourceRecord& r, const DNSName& ordername, bool ordernameIsNSEC3)
1349
{
429,490✔
1350
  LMDBResourceRecord lrr(r);
429,490✔
1351
  lrr.qname.makeUsRelative(d_transactiondomain);
429,490✔
1352
  lrr.content = serializeContent(lrr.qtype.getCode(), r.qname, lrr.content);
429,490✔
1353
  // Note that this is safe, as ordernameIsNSEC3 will NOT be set if NSEC3
1354
  // but narrow.
1355
  lrr.hasOrderName = ordernameIsNSEC3 && !ordername.empty();
429,490!
1356

1357
  compoundOrdername co;
429,490✔
1358
  string matchName = co(lrr.domain_id, lrr.qname, lrr.qtype.getCode());
429,490✔
1359

1360
  string rrs;
429,490✔
1361
  MDBOutVal _rrs;
429,490✔
1362
  if (!d_rwtxn->txn->get(d_rwtxn->db->dbi, matchName, _rrs)) {
429,490✔
1363
    rrs = _rrs.get<string>();
23,472✔
1364
  }
23,472✔
1365
  rrs += serializeToBuffer(lrr);
429,490✔
1366

1367
  d_rwtxn->txn->put(d_rwtxn->db->dbi, matchName, rrs);
429,490✔
1368

1369
  if (lrr.hasOrderName) {
429,490✔
1370
    writeNSEC3RecordPair(d_rwtxn, lrr.domain_id, lrr.qname, ordername);
40,785✔
1371
  }
40,785✔
1372
  return true;
429,490✔
1373
}
429,490✔
1374

1375
bool LMDBBackend::feedEnts(domainid_t domain_id, map<DNSName, bool>& nonterm)
1376
{
22✔
1377
  LMDBResourceRecord lrr;
22✔
1378
  lrr.ttl = 0;
22✔
1379
  compoundOrdername co;
22✔
1380
  for (const auto& nt : nonterm) {
96✔
1381
    lrr.qname = nt.first.makeRelative(d_transactiondomain);
96✔
1382
    lrr.auth = nt.second;
96✔
1383
    lrr.hasOrderName = false;
96✔
1384

1385
    std::string ser = serializeToBuffer(lrr);
96✔
1386
    d_rwtxn->txn->put(d_rwtxn->db->dbi, co(domain_id, lrr.qname, QType::ENT), ser);
96✔
1387
  }
96✔
1388
  return true;
22✔
1389
}
22✔
1390

1391
bool LMDBBackend::feedEnts3(domainid_t domain_id, const DNSName& domain, map<DNSName, bool>& nonterm, const NSEC3PARAMRecordContent& ns3prc, bool narrow)
1392
{
18✔
1393
  string ser;
18✔
1394
  DNSName ordername;
18✔
1395
  LMDBResourceRecord lrr;
18✔
1396
  compoundOrdername co;
18✔
1397
  for (const auto& nt : nonterm) {
92✔
1398
    lrr.qname = nt.first.makeRelative(domain);
92✔
1399
    lrr.ttl = 0;
92✔
1400
    lrr.auth = nt.second;
92✔
1401
    lrr.hasOrderName = lrr.auth && !narrow;
92!
1402
    ser = serializeToBuffer(lrr);
92✔
1403
    d_rwtxn->txn->put(d_rwtxn->db->dbi, co(domain_id, lrr.qname, QType::ENT), ser);
92✔
1404

1405
    if (lrr.hasOrderName) {
92✔
1406
      ordername = DNSName(toBase32Hex(hashQNameWithSalt(ns3prc, nt.first)));
80✔
1407
      writeNSEC3RecordPair(d_rwtxn, domain_id, lrr.qname, ordername);
80✔
1408
    }
80✔
1409
  }
92✔
1410
  return true;
18✔
1411
}
18✔
1412

1413
// might be called within a transaction, might also be called alone
1414
// NOLINTNEXTLINE(readability-identifier-length)
1415
bool LMDBBackend::replaceRRSet(domainid_t domain_id, const DNSName& qname, const QType& qt, const vector<DNSResourceRecord>& rrset)
1416
{
1,856✔
1417
  // zonk qname/qtype within domain_id (go through qname, check domain_id && qtype)
1418
  shared_ptr<RecordsRWTransaction> txn;
1,856✔
1419
  bool needCommit = false;
1,856✔
1420
  if (d_rwtxn && d_transactiondomainid == domain_id) {
1,856!
1421
    txn = d_rwtxn;
1,856✔
1422
    //    cout<<"Reusing open transaction"<<endl;
1423
  }
1,856✔
1424
  else {
×
1425
    //    cout<<"Making a new RW txn for replace rrset"<<endl;
1426
    txn = getRecordsRWTransaction(domain_id);
×
1427
    needCommit = true;
×
1428
  }
×
1429

1430
  DomainInfo info;
1,856✔
1431
  if (!findDomain(domain_id, info)) {
1,856!
1432
    return false;
×
1433
  }
×
1434

1435
  DNSName relative = qname.makeRelative(info.zone);
1,856✔
1436
  compoundOrdername co;
1,856✔
1437
  string match;
1,856✔
1438
  if (qt.getCode() == QType::ANY) {
1,856✔
1439
    // Check for an existing NSEC3 record. If one exists, we need to also
1440
    // remove the back chain record.
1441
    deleteNSEC3RecordPair(txn, domain_id, relative);
60✔
1442
    match = co(domain_id, relative);
60✔
1443
    deleteDomainRecords(*txn, match);
60✔
1444
    // Update key if insertions are to follow
1445
    if (!rrset.empty()) {
60!
1446
      match = co(domain_id, relative, rrset.front().qtype.getCode());
×
1447
    }
×
1448
  }
60✔
1449
  else {
1,796✔
1450
    if (qt.getCode() == QType::NSEC3) {
1,796!
1451
      deleteNSEC3RecordPair(txn, domain_id, relative);
×
1452
    }
×
1453
    else {
1,796✔
1454
      auto cursor = txn->txn->getCursor(txn->db->dbi);
1,796✔
1455
      MDBOutVal key{};
1,796✔
1456
      MDBOutVal val{};
1,796✔
1457
      match = co(domain_id, relative, qt.getCode());
1,796✔
1458
      // There should be at most one exact match here.
1459
      if (cursor.find(match, key, val) == 0) {
1,796✔
1460
        cursor.del(key);
1,588✔
1461
      }
1,588✔
1462
      // If we are not going to add any new records, check if there are any
1463
      // remaining records for this qname, ignoring NSEC3 chain records. If
1464
      // there aren't any, yet there is an NSEC3 record, delete the NSEC3 chain
1465
      // pair as well.
1466
      if (rrset.empty()) {
1,796✔
1467
        if (hasOrphanedNSEC3Record(cursor, domain_id, relative)) {
428✔
1468
          deleteNSEC3RecordPair(txn, domain_id, relative);
114✔
1469
        }
114✔
1470
      }
428✔
1471
    }
1,796✔
1472
  }
1,796✔
1473

1474
  if (!rrset.empty()) {
1,856✔
1475
    vector<LMDBResourceRecord> adjustedRRSet;
1,368✔
1476
    adjustedRRSet.reserve(rrset.size());
1,368✔
1477
    for (const auto& rr : rrset) {
1,449✔
1478
      LMDBResourceRecord lrr(rr);
1,449✔
1479
      lrr.content = serializeContent(lrr.qtype.getCode(), lrr.qname, lrr.content);
1,449✔
1480
      lrr.qname.makeUsRelative(info.zone);
1,449✔
1481

1482
      adjustedRRSet.emplace_back(lrr);
1,449✔
1483
    }
1,449✔
1484
    txn->txn->put(txn->db->dbi, match, serializeToBuffer(adjustedRRSet));
1,368✔
1485
  }
1,368✔
1486

1487
  if (needCommit)
1,856!
1488
    txn->txn->commit();
×
1489

1490
  return true;
1,856✔
1491
}
1,856✔
1492

1493
// NOLINTNEXTLINE(readability-identifier-length)
1494
bool LMDBBackend::replaceComments([[maybe_unused]] domainid_t domain_id, [[maybe_unused]] const DNSName& qname, [[maybe_unused]] const QType& qt, const vector<Comment>& comments)
1495
{
2✔
1496
  // if the vector is empty, good, that's what we do here (LMDB does not store comments)
1497
  // if it's not, report failure
1498
  return comments.empty();
2✔
1499
}
2✔
1500

1501
bool LMDBBackend::searchRecords(const string& pattern, size_t maxResults, vector<DNSResourceRecord>& result)
1502
{
5✔
1503
  SimpleMatch simpleMatch(pattern, true);
5✔
1504
  std::vector<DomainInfo> domains;
5✔
1505
  getAllDomains(&domains, false, true);
5✔
1506
  for (const auto& info : domains) {
286✔
1507
    if (!list(info.zone, info.id, true)) {
286!
1508
      return false;
×
1509
    }
×
1510
    DNSResourceRecord rec;
286✔
1511
    while (get(rec)) {
102,239✔
1512
      if (maxResults == 0) {
101,953!
1513
        continue;
×
1514
      }
×
1515
      if (simpleMatch.match(rec.qname.toStringNoDot()) || simpleMatch.match(rec.content)) {
101,953!
1516
        result.emplace_back(rec);
17✔
1517
        --maxResults;
17✔
1518
      }
17✔
1519
    }
101,953✔
1520
    if (maxResults == 0) {
286!
1521
      break;
×
1522
    }
×
1523
  }
286✔
1524
  return true;
5✔
1525
}
5✔
1526

1527
// FIXME: this is not very efficient
1528
static DNSName keyUnconv(std::string& instr)
1529
{
182✔
1530
  // instr is now com0example0
1531
  vector<string> labels;
182✔
1532
  boost::split(labels, instr, [](char chr) { return chr == '\0'; });
2,549✔
1533

1534
  // we get a spurious empty label at the end, drop it
1535
  labels.resize(labels.size() - 1);
182✔
1536

1537
  if (labels.size() == 1 && labels[0].empty()) {
182!
1538
    // this is the root
1539
    return g_rootdnsname;
5✔
1540
  }
5✔
1541

1542
  DNSName tmp;
177✔
1543

1544
  while (!labels.empty()) {
566✔
1545
    tmp.appendRawLabel(labels.back());
389✔
1546
    labels.pop_back();
389✔
1547
  }
389✔
1548
  return tmp;
177✔
1549
}
182✔
1550

1551
static std::string makeBadDataExceptionMessage(const std::string& where, std::exception& exc, MDBOutVal& key, MDBOutVal& val)
1552
{
×
1553
  ostringstream msg;
×
1554
  msg << "during " << where << ", got exception (" << exc.what() << "), ";
×
1555
  msg << "key: " << makeHexDump(key.getNoStripHeader<string>()) << ", ";
×
1556
  msg << "value: " << makeHexDump(val.get<string>());
×
1557

1558
  return msg.str();
×
1559
}
×
1560

1561
void LMDBBackend::viewList(vector<string>& result)
1562
{
43✔
1563
  auto txn = d_tdomains->getEnv()->getROTransaction();
43✔
1564

1565
  auto cursor = txn->getROCursor(d_tviews);
43✔
1566

1567
  MDBOutVal key{}; // <view, dnsname>
43✔
1568
  MDBOutVal val{}; // <variant>
43✔
1569

1570
  auto ret = cursor.first(key, val);
43✔
1571

1572
  if (ret == MDB_NOTFOUND) {
43✔
1573
    return;
11✔
1574
  }
11✔
1575

1576
  do {
82✔
1577
    string view;
82✔
1578
    string zone;
82✔
1579
    try {
82✔
1580
      std::tie(view, zone) = splitField(key.getNoStripHeader<string>(), '\x0');
82✔
1581
      auto variant = val.get<string>();
82✔
1582
      result.push_back(view);
82✔
1583
    }
82✔
1584
    catch (std::exception& e) {
82✔
1585
      throw PDNSException(makeBadDataExceptionMessage("viewList", e, key, val));
×
1586
    }
×
1587

1588
    string inkey{view + string(1, (char)1)};
82✔
1589
    MDBInVal bound{inkey};
82✔
1590
    ret = cursor.lower_bound(bound, key, val); // this should use some lower bound thing to skip to the next view, also avoiding duplicates in `result`
82✔
1591
  } while (ret != MDB_NOTFOUND);
82✔
1592
}
32✔
1593

1594
void LMDBBackend::viewListZones(const string& inview, vector<ZoneName>& result)
1595
{
84✔
1596
  result.clear();
84✔
1597

1598
  auto txn = d_tdomains->getEnv()->getROTransaction();
84✔
1599

1600
  auto cursor = txn->getROCursor(d_tviews);
84✔
1601

1602
  string inkey{inview + string(1, (char)0)};
84✔
1603
  MDBInVal prefix{inkey};
84✔
1604
  MDBOutVal key{}; // <view, dnsname>
84✔
1605
  MDBOutVal val{}; // <variant>
84✔
1606

1607
  auto ret = cursor.prefix(prefix, key, val);
84✔
1608

1609
  if (ret == MDB_NOTFOUND) {
84✔
1610
    return;
2✔
1611
  }
2✔
1612

1613
  do {
182✔
1614
    try {
182✔
1615
      auto [view, _zone] = splitField(key.getNoStripHeader<string>(), '\x0');
182✔
1616
      auto variant = val.get<string>();
182✔
1617
      auto zone = keyUnconv(_zone);
182✔
1618
      result.emplace_back(ZoneName(zone, variant));
182✔
1619
    }
182✔
1620
    catch (std::exception& e) {
182✔
1621
      throw PDNSException(makeBadDataExceptionMessage("viewListZones", e, key, val));
×
1622
    }
×
1623

1624
    ret = cursor.next(key, val);
182✔
1625
  } while (ret != MDB_NOTFOUND);
182✔
1626
}
82✔
1627

1628
// TODO: make this add-or-del to reduce code duplication?
1629
bool LMDBBackend::viewAddZone(const string& view, const ZoneName& zone)
1630
{
193✔
1631
  auto txn = d_tdomains->getEnv()->getRWTransaction();
193✔
1632

1633
  string key = view + string(1, (char)0) + keyConv(zone.operator const DNSName&());
193✔
1634
  string val = zone.getVariant(); // variant goes here
193✔
1635

1636
  txn->put(d_tviews, key, val);
193✔
1637
  txn->commit();
193✔
1638

1639
  return true;
193✔
1640
}
193✔
1641

1642
bool LMDBBackend::viewDelZone(const string& view, const ZoneName& zone)
1643
{
12✔
1644
  auto txn = d_tdomains->getEnv()->getRWTransaction();
12✔
1645

1646
  string key = view + string(1, (char)0) + keyConv(zone.operator const DNSName&());
12✔
1647
  // string val = "foo"; // variant goes here
1648

1649
  txn->del(d_tviews, key);
12✔
1650
  txn->commit();
12✔
1651

1652
  return true;
12✔
1653
}
12✔
1654

1655
bool LMDBBackend::networkSet(const Netmask& net, std::string& view)
1656
{
88✔
1657
  auto txn = d_tdomains->getEnv()->getRWTransaction();
88✔
1658

1659
  if (view.empty()) {
88✔
1660
    txn->del(d_tnetworks, net.toByteString());
1✔
1661
  }
1✔
1662
  else {
87✔
1663
    txn->put(d_tnetworks, net.toByteString(), view);
87✔
1664
  }
87✔
1665
  txn->commit();
88✔
1666

1667
  return true;
88✔
1668
}
88✔
1669

1670
bool LMDBBackend::networkList(vector<pair<Netmask, string>>& networks)
1671
{
33✔
1672
  networks.clear();
33✔
1673

1674
  auto txn = d_tdomains->getEnv()->getROTransaction();
33✔
1675

1676
  auto cursor = txn->getROCursor(d_tnetworks);
33✔
1677

1678
  MDBOutVal netval{};
33✔
1679
  MDBOutVal viewval{};
33✔
1680

1681
  auto ret = cursor.first(netval, viewval);
33✔
1682

1683
  if (ret == MDB_NOTFOUND) {
33✔
1684
    return true;
11✔
1685
  }
11✔
1686

1687
  do {
52✔
1688
    try {
52✔
1689
      auto net = Netmask(netval.getNoStripHeader<string>(), Netmask::byteString);
52✔
1690
      auto view = viewval.get<string>();
52✔
1691
      networks.emplace_back(std::make_pair(net, view));
52✔
1692
    }
52✔
1693
    catch (std::exception& e) {
52✔
1694
      throw PDNSException(makeBadDataExceptionMessage("networkList", e, netval, viewval));
×
1695
    }
×
1696

1697
    ret = cursor.next(netval, viewval);
52✔
1698
  } while (ret != MDB_NOTFOUND);
52✔
1699

1700
  return true;
22✔
1701
}
22✔
1702

1703
// tempting to templatize these two functions but the pain is not worth it
1704
// NOLINTNEXTLINE(readability-identifier-length)
1705
std::shared_ptr<LMDBBackend::RecordsRWTransaction> LMDBBackend::getRecordsRWTransaction(domainid_t id)
1706
{
2,583✔
1707
  auto& shard = d_trecords[id % s_shards];
2,583✔
1708
  if (!shard.env) {
2,583✔
1709
    shard.env = getMDBEnv((getArg("filename") + "-" + std::to_string(id % s_shards)).c_str(),
129✔
1710
                          MDB_NOSUBDIR | MDB_NORDAHEAD | d_asyncFlag, 0600, d_mapsize);
129✔
1711
    shard.dbi = shard.env->openDB("records_v5", MDB_CREATE);
129✔
1712
  }
129✔
1713
  auto ret = std::make_shared<RecordsRWTransaction>(shard.env->getRWTransaction());
2,583✔
1714
  ret->db = std::make_shared<RecordsDB>(shard);
2,583✔
1715

1716
  return ret;
2,583✔
1717
}
2,583✔
1718

1719
// NOLINTNEXTLINE(readability-identifier-length)
1720
std::shared_ptr<LMDBBackend::RecordsROTransaction> LMDBBackend::getRecordsROTransaction(domainid_t id, const std::shared_ptr<LMDBBackend::RecordsRWTransaction>& rwtxn)
1721
{
42,137✔
1722
  auto& shard = d_trecords[id % s_shards];
42,137✔
1723
  if (!shard.env) {
42,137✔
1724
    if (rwtxn) {
5,259!
1725
      throw DBException("attempting to start nested transaction without open parent env");
×
1726
    }
×
1727
    shard.env = getMDBEnv((getArg("filename") + "-" + std::to_string(id % s_shards)).c_str(),
5,259✔
1728
                          MDB_NOSUBDIR | MDB_NORDAHEAD | d_asyncFlag, 0600, d_mapsize);
5,259✔
1729
    shard.dbi = shard.env->openDB("records_v5", MDB_CREATE);
5,259✔
1730
  }
5,259✔
1731

1732
  if (rwtxn) {
42,137✔
1733
    auto ret = std::make_shared<RecordsROTransaction>(rwtxn->txn->getROTransaction());
9,017✔
1734
    ret->db = std::make_shared<RecordsDB>(shard);
9,017✔
1735
    return ret;
9,017✔
1736
  }
9,017✔
1737
  else {
33,120✔
1738
    auto ret = std::make_shared<RecordsROTransaction>(shard.env->getROTransaction());
33,120✔
1739
    ret->db = std::make_shared<RecordsDB>(shard);
33,120✔
1740
    return ret;
33,120✔
1741
  }
33,120✔
1742
}
42,137✔
1743

1744
bool LMDBBackend::deleteDomain(const ZoneName& domain)
1745
{
31✔
1746
  if (!d_rwtxn) {
31!
1747
    throw DBException(std::string(__PRETTY_FUNCTION__) + " called without a transaction");
×
1748
  }
×
1749

1750
  int transactionDomainId = d_transactiondomainid;
31✔
1751
  ZoneName transactionDomain = d_transactiondomain;
31✔
1752

1753
  abortTransaction();
31✔
1754

1755
  LmdbIdVec idvec;
31✔
1756

1757
  if (!d_handle_dups) {
31!
1758
    // get domain id
1759
    DomainInfo info;
31✔
1760
    if (findDomain(domain, info)) {
31!
1761
      idvec.push_back(info.id);
31✔
1762
    }
31✔
1763
  }
31✔
1764
  else {
×
1765
    // this transaction used to be RO.
1766
    // it is now RW to narrow a race window between PowerDNS and Lightning Stream
1767
    // FIXME: turn the entire delete, including this ID scan, into one RW transaction
1768
    // when doing that, first do a short RO check to see if we actually have anything to delete
1769
    auto txn = d_tdomains->getRWTransaction();
×
1770

1771
    txn.get_multi<0>(domain, idvec);
×
1772
  }
×
1773

1774
  for (auto id : idvec) {
31✔
1775

1776
    startTransaction(domain, id);
31✔
1777

1778
    { // Remove metadata
31✔
1779
      auto txn = d_tmeta->getRWTransaction();
31✔
1780
      LmdbIdVec ids;
31✔
1781

1782
      txn.get_multi<0>(domain, ids);
31✔
1783

1784
      for (auto& _id : ids) {
35✔
1785
        txn.del(_id);
35✔
1786
      }
35✔
1787

1788
      txn.commit();
31✔
1789
    }
31✔
1790

1791
    { // Remove cryptokeys
31✔
1792
      auto txn = d_tkdb->getRWTransaction();
31✔
1793
      LmdbIdVec ids;
31✔
1794
      txn.get_multi<0>(domain, ids);
31✔
1795

1796
      for (auto _id : ids) {
31!
1797
        txn.del(_id);
×
1798
      }
×
1799

1800
      txn.commit();
31✔
1801
    }
31✔
1802

1803
    // Remove records
1804
    commitTransaction();
31✔
1805

1806
    // Remove zone
1807
    {
31✔
1808
      auto container = s_notified_serial.write_lock();
31✔
1809
      container->remove(static_cast<domainid_t>(id));
31✔
1810
    }
31✔
1811
    auto txn = d_tdomains->getRWTransaction();
31✔
1812
    txn.del(id);
31✔
1813
    txn.commit();
31✔
1814
  }
31✔
1815

1816
  startTransaction(transactionDomain, transactionDomainId);
31✔
1817

1818
  return true;
31✔
1819
}
31✔
1820

1821
/*
1822
 * Domain lookup shared state, set by list/lookup, used by get:
1823
 *
1824
 * d_lookupdomain: current domain being processed (appended to the
1825
 *                 results' names)
1826
 * d_lookupsubmatch: relative name used for submatching (for listSubZone)
1827
 * d_currentrrset: temporary vector of results (records found at the same
1828
 *                 cursor, i.e. same qname but possibly different qtype)
1829
 * d_currentrrsetpos: position in the above when returning its elements one
1830
 *                    by one
1831
 * d_currentrrsettime: timestamp of d_currentrrset (can't be stored in
1832
 *                     DNSZoneRecord)
1833
 * d_currentKey: database key at cursor
1834
 * d_currentVal: database contents at cursor
1835
 * d_includedisabled: whether to include disabled records in the results
1836
 */
1837

1838
bool LMDBBackend::list(const ZoneName& target, domainid_t domain_id, bool include_disabled)
1839
{
1,527✔
1840
  d_lookupdomain = target;
1,527✔
1841
  d_lookupsubmatch.clear();
1,527✔
1842
  d_includedisabled = include_disabled;
1,527✔
1843

1844
  compoundOrdername order;
1,527✔
1845
  std::string match = order(domain_id);
1,527✔
1846

1847
  lookupStart(domain_id, match, false);
1,527✔
1848
  return true;
1,527✔
1849
}
1,527✔
1850

1851
bool LMDBBackend::listSubZone(const ZoneName& target, domainid_t domain_id)
1852
{
1,192✔
1853
  // 1. from domain_id get base domain name
1854
  DomainInfo info;
1,192✔
1855
  if (!findDomain(domain_id, info)) {
1,192!
1856
    return false;
×
1857
  }
×
1858

1859
  // 2. make target relative to it
1860
  DNSName relqname = target.operator const DNSName&().makeRelative(info.zone);
1,192✔
1861
  if (relqname.empty()) {
1,192!
1862
    return false;
×
1863
  }
×
1864

1865
  // 3. enumerate complete domain, but tell get() to ignore entries which are
1866
  //    not subsets of target
1867
  d_lookupdomain = std::move(info.zone);
1,192✔
1868
  d_lookupsubmatch = std::move(relqname);
1,192✔
1869
  d_includedisabled = true;
1,192✔
1870

1871
  compoundOrdername order;
1,192✔
1872
  std::string match = order(domain_id);
1,192✔
1873

1874
  lookupStart(domain_id, match, false);
1,192✔
1875
  return true;
1,192✔
1876
}
1,192✔
1877

1878
void LMDBBackend::lookupInternal(const QType& type, const DNSName& qdomain, domainid_t zoneId, DNSPacket* /* p */, bool include_disabled)
1879
{
16,178✔
1880
  if (d_dolog) {
16,178!
1881
    g_log << Logger::Warning << "Got lookup for " << qdomain << "|" << type.toString() << " in zone " << zoneId << endl;
×
1882
    d_dtime.set();
×
1883
  }
×
1884

1885
  DomainInfo info;
16,178✔
1886
  if (zoneId == UnknownDomainID) {
16,178✔
1887
    ZoneName hunt(qdomain);
864✔
1888
    do {
864✔
1889
      if (findDomain(hunt, info)) {
864!
1890
        break;
864✔
1891
      }
864✔
1892
    } while (type != QType::SOA && hunt.chopOff());
864!
1893
    if (info.id == 0) {
864!
1894
      //      cout << "Did not find zone for "<< qdomain<<endl;
1895
      d_getcursor.reset();
×
1896
      return;
×
1897
    }
×
1898
  }
864✔
1899
  else {
15,314✔
1900
    if (!findDomain(zoneId, info)) {
15,314✔
1901
      // cout<<"Could not find a zone with id "<<zoneId<<endl;
1902
      d_getcursor.reset();
8✔
1903
      return;
8✔
1904
    }
8✔
1905
  }
15,314✔
1906

1907
  DNSName relqname = qdomain.makeRelative(info.zone);
16,170✔
1908
  if (relqname.empty()) {
16,170!
1909
    return;
×
1910
  }
×
1911
  // cout<<"get will look for "<<relqname<< " in zone "<<info.zone<<" with id "<<info.id<<" and type "<<type.toString()<<endl;
1912

1913
  d_lookupdomain = std::move(info.zone);
16,170✔
1914
  d_lookupsubmatch.clear();
16,170✔
1915
  d_includedisabled = include_disabled;
16,170✔
1916

1917
  compoundOrdername order;
16,170✔
1918
  std::string match;
16,170✔
1919
  if (type.getCode() == QType::ANY) {
16,170✔
1920
    match = order(info.id, relqname);
9,852✔
1921
  }
9,852✔
1922
  else {
6,318✔
1923
    match = order(info.id, relqname, type.getCode());
6,318✔
1924
  }
6,318✔
1925

1926
  lookupStart(info.id, match, d_dolog);
16,170✔
1927
}
16,170✔
1928

1929
void LMDBBackend::lookupStart(domainid_t domain_id, const std::string& match, bool dolog)
1930
{
18,889✔
1931
  d_rotxn = getRecordsROTransaction(domain_id, d_rwtxn);
18,889✔
1932
  d_txnorder = true;
18,889✔
1933
  d_getcursor = std::make_shared<MDBROCursor>(d_rotxn->txn->getCursor(d_rotxn->db->dbi));
18,889✔
1934

1935
  // Make sure we start with fresh data
1936
  d_currentrrset.clear();
18,889✔
1937
  d_currentrrsetpos = 0;
18,889✔
1938

1939
  MDBOutVal key{};
18,889✔
1940
  MDBOutVal val{};
18,889✔
1941
  if (d_getcursor->prefix(match, key, val) != 0) {
18,889✔
1942
    d_getcursor.reset(); // will cause get() to fail
6,035✔
1943
    if (dolog) {
6,035!
1944
      g_log << Logger::Warning << "Query " << ((long)(void*)this) << ": " << d_dtime.udiffNoReset() << " us to execute (found nothing)" << endl;
×
1945
    }
×
1946
    return;
6,035✔
1947
  }
6,035✔
1948

1949
  if (dolog) {
12,854!
1950
    g_log << Logger::Warning << "Query " << ((long)(void*)this) << ": " << d_dtime.udiffNoReset() << " us to execute" << endl;
×
1951
  }
×
1952
}
12,854✔
1953

1954
bool LMDBBackend::get(DNSZoneRecord& zr)
1955
{
670,753✔
1956
  for (;;) {
807,854✔
1957
    // std::cerr<<"d_getcursor="<<d_getcursor<<std::endl;
1958
    if (!d_getcursor) {
807,854✔
1959
      d_rotxn.reset();
18,897✔
1960
      return false;
18,897✔
1961
    }
18,897✔
1962

1963
    string_view key;
788,957✔
1964

1965
    if (d_currentrrset.empty()) {
788,957✔
1966
      d_getcursor->current(d_currentKey, d_currentVal);
674,696✔
1967

1968
      key = d_currentKey.getNoStripHeader<string_view>();
674,696✔
1969
      zr.dr.d_type = compoundOrdername::getQType(key).getCode();
674,696✔
1970

1971
      if (zr.dr.d_type == QType::NSEC3) {
674,696✔
1972
        // Hit a magic NSEC3 skipping
1973
        if (d_getcursor->next(d_currentKey, d_currentVal) != 0) {
112,280✔
1974
          // cerr<<"resetting d_getcursor 1"<<endl;
1975
          d_getcursor.reset();
3,344✔
1976
        }
3,344✔
1977
        continue;
112,280✔
1978
      }
112,280✔
1979

1980
      deserializeFromBuffer(d_currentVal.get<string_view>(), d_currentrrset);
562,416✔
1981
      d_currentrrsettime = static_cast<time_t>(LMDBLS::LSgetTimestamp(d_currentVal.getNoStripHeader<string_view>()) / (1000UL * 1000UL * 1000UL));
562,416✔
1982
      d_currentrrsetpos = 0;
562,416✔
1983
    }
562,416✔
1984
    else {
114,261✔
1985
      key = d_currentKey.getNoStripHeader<string_view>();
114,261✔
1986
    }
114,261✔
1987
    try {
676,677✔
1988
      const auto& lrr = d_currentrrset.at(d_currentrrsetpos++);
676,677✔
1989
      DNSName basename;
676,677✔
1990
      bool validRecord = d_includedisabled || !lrr.disabled;
676,677✔
1991

1992
      if (validRecord) {
676,677✔
1993
        basename = compoundOrdername::getQName(key);
676,672✔
1994
        if (!d_lookupsubmatch.empty()) {
676,672✔
1995
          validRecord = basename.isPartOf(d_lookupsubmatch);
33,904✔
1996
        }
33,904✔
1997
      }
676,672✔
1998

1999
      if (validRecord) {
676,677✔
2000
        zr.dr.d_name = basename + d_lookupdomain.operator const DNSName&();
651,856✔
2001
        zr.domain_id = compoundOrdername::getDomainID(key);
651,856✔
2002
        zr.dr.d_type = compoundOrdername::getQType(key).getCode();
651,856✔
2003
        zr.dr.d_ttl = lrr.ttl;
651,856✔
2004
        zr.dr.setContent(deserializeContentZR(zr.dr.d_type, zr.dr.d_name, lrr.content));
651,856✔
2005
        zr.auth = lrr.auth;
651,856✔
2006
        zr.disabled = lrr.disabled;
651,856✔
2007
      }
651,856✔
2008

2009
      if (d_currentrrsetpos >= d_currentrrset.size()) {
676,677✔
2010
        d_currentrrset.clear(); // will invalidate lrr
562,416✔
2011
        if (d_getcursor->next(d_currentKey, d_currentVal) != 0) {
562,416✔
2012
          // cerr<<"resetting d_getcursor 2"<<endl;
2013
          d_getcursor.reset();
9,510✔
2014
        }
9,510✔
2015
      }
562,416✔
2016

2017
      if (!validRecord) {
676,677✔
2018
        continue;
24,821✔
2019
      }
24,821✔
2020
    }
676,677✔
2021
    catch (const std::exception& e) {
676,677✔
2022
      throw PDNSException(e.what());
×
2023
    }
×
2024

2025
    break;
651,856✔
2026
  }
676,677✔
2027

2028
  return true;
651,856✔
2029
}
670,753✔
2030

2031
bool LMDBBackend::get(DNSResourceRecord& rr)
2032
{
564,293✔
2033
  DNSZoneRecord zr;
564,293✔
2034
  if (!get(zr)) {
564,293✔
2035
    return false;
10,918✔
2036
  }
10,918✔
2037

2038
  rr.qname = zr.dr.d_name;
553,375✔
2039
  rr.ttl = zr.dr.d_ttl;
553,375✔
2040
  rr.qtype = zr.dr.d_type;
553,375✔
2041
  rr.content = zr.dr.getContent()->getZoneRepresentation(true);
553,375✔
2042
  rr.domain_id = zr.domain_id;
553,375✔
2043
  rr.auth = zr.auth;
553,375✔
2044
  rr.disabled = zr.disabled;
553,375✔
2045
  rr.last_modified = d_currentrrsettime;
553,375✔
2046

2047
  return true;
553,375✔
2048
}
564,293✔
2049

2050
bool LMDBBackend::getSerial(DomainInfo& di)
2051
{
5,516✔
2052
  auto txn = getRecordsROTransaction(di.id);
5,516✔
2053
  compoundOrdername co;
5,516✔
2054
  MDBOutVal val;
5,516✔
2055
  if (!txn->txn->get(txn->db->dbi, co(di.id, g_rootdnsname, QType::SOA), val)) {
5,516✔
2056
    LMDBResourceRecord lrr;
4,753✔
2057
    deserializeFromBuffer(val.get<string_view>(), lrr);
4,753✔
2058
    if (lrr.content.size() >= 5 * sizeof(uint32_t)) {
4,753!
2059
      uint32_t serial;
4,753✔
2060
      // a SOA has five 32 bit fields, the first of which is the serial
2061
      // there are two variable length names before the serial, so we calculate from the back
2062
      memcpy(&serial, &lrr.content[lrr.content.size() - (5 * sizeof(uint32_t))], sizeof(serial));
4,753✔
2063
      di.serial = ntohl(serial);
4,753✔
2064
    }
4,753✔
2065
    return !lrr.disabled;
4,753✔
2066
  }
4,753✔
2067
  return false;
763✔
2068
}
5,516✔
2069

2070
bool LMDBBackend::getDomainInfo(const ZoneName& domain, DomainInfo& info, bool getserial)
2071
{
4,712✔
2072
  // If caller asks about a zone with variant, but views are not enabled,
2073
  // punt.
2074
  if (domain.hasVariant() && !d_views) {
4,712!
2075
    return false;
×
2076
  }
×
2077

2078
  if (!findDomain(domain, info)) {
4,712✔
2079
    return false;
527✔
2080
  }
527✔
2081
  info.backend = this;
4,185✔
2082
  consolidateDomainInfo(info);
4,185✔
2083

2084
  if (getserial) {
4,185✔
2085
    getSerial(info);
3,458✔
2086
  }
3,458✔
2087

2088
  return true;
4,185✔
2089
}
4,712✔
2090

2091
int LMDBBackend::genChangeDomain(const ZoneName& domain, const std::function<void(DomainInfo&)>& func)
2092
{
710✔
2093
  DomainInfo info;
710✔
2094
  if (!findDomain(domain, info)) {
710!
2095
    return static_cast<int>(false);
×
2096
  }
×
2097
  consolidateDomainInfo(info);
710✔
2098
  func(info);
710✔
2099
  writeDomainInfo(info);
710✔
2100
  return true;
710✔
2101
}
710✔
2102

2103
// NOLINTNEXTLINE(readability-identifier-length)
2104
int LMDBBackend::genChangeDomain(domainid_t id, const std::function<void(DomainInfo&)>& func)
2105
{
91✔
2106
  DomainInfo info;
91✔
2107
  if (!findDomain(id, info)) {
91!
2108
    return false;
×
2109
  }
×
2110
  consolidateDomainInfo(info);
91✔
2111
  func(info);
91✔
2112
  writeDomainInfo(info);
91✔
2113
  return true;
91✔
2114
}
91✔
2115

2116
bool LMDBBackend::setKind(const ZoneName& domain, const DomainInfo::DomainKind kind)
2117
{
299✔
2118
  return genChangeDomain(domain, [kind](DomainInfo& di) {
299✔
2119
    di.kind = kind;
299✔
2120
  });
299✔
2121
}
299✔
2122

2123
bool LMDBBackend::setAccount(const ZoneName& domain, const std::string& account)
2124
{
1✔
2125
  return genChangeDomain(domain, [account](DomainInfo& di) {
1✔
2126
    di.account = account;
1✔
2127
  });
1✔
2128
}
1✔
2129

2130
bool LMDBBackend::setPrimaries(const ZoneName& domain, const vector<ComboAddress>& primaries)
2131
{
71✔
2132
  return genChangeDomain(domain, [&primaries](DomainInfo& di) {
71✔
2133
    di.primaries = primaries;
71✔
2134
  });
71✔
2135
}
71✔
2136

2137
bool LMDBBackend::createDomain(const ZoneName& domain, const DomainInfo::DomainKind kind, const vector<ComboAddress>& primaries, const string& account)
2138
{
502✔
2139
  DomainInfo info;
502✔
2140

2141
  if (findDomain(domain, info)) {
502!
2142
    throw DBException("Domain '" + domain.toLogString() + "' exists already");
×
2143
  }
×
2144
  {
502✔
2145
    auto txn = d_tdomains->getRWTransaction();
502✔
2146

2147
    info.zone = domain;
502✔
2148
    info.kind = kind;
502✔
2149
    info.primaries = primaries;
502✔
2150
    info.account = account;
502✔
2151

2152
    txn.put(info, 0, d_random_ids, domain.hash());
502✔
2153
    txn.commit();
502✔
2154
  }
502✔
2155

2156
  return true;
502✔
2157
}
502✔
2158

2159
void LMDBBackend::getAllDomainsFiltered(vector<DomainInfo>* domains, const std::function<bool(DomainInfo&)>& allow)
2160
{
155✔
2161
  auto txn = d_tdomains->getROTransaction();
155✔
2162
  if (d_handle_dups) {
155!
2163
    map<ZoneName, DomainInfo> zonemap;
×
2164
    set<ZoneName> dups;
×
2165

2166
    for (auto iter = txn.begin(); iter != txn.end(); ++iter) {
×
2167
      DomainInfo di = *iter;
×
2168
      di.id = iter.getID();
×
2169
      di.backend = this;
×
2170

2171
      if (!zonemap.emplace(di.zone, di).second) {
×
2172
        dups.insert(di.zone);
×
2173
      }
×
2174
    }
×
2175

2176
    for (const auto& zone : dups) {
×
2177
      DomainInfo info;
×
2178
      // this get grabs the oldest item if there are duplicates
2179
      if (!findDomain(zone, info)) {
×
2180
        continue;
×
2181
      }
×
2182
      info.backend = this;
×
2183
      zonemap[info.zone] = info;
×
2184
    }
×
2185

2186
    for (auto& [k, v] : zonemap) {
×
2187
      if (allow(v)) {
×
2188
        consolidateDomainInfo(v);
×
2189
        domains->push_back(std::move(v));
×
2190
      }
×
2191
    }
×
2192
  }
×
2193
  else {
155✔
2194
    for (auto iter = txn.begin(); iter != txn.end(); ++iter) {
2,600✔
2195
      DomainInfo di = *iter;
2,445✔
2196
      di.id = iter.getID();
2,445✔
2197
      di.backend = this;
2,445✔
2198

2199
      if (allow(di)) {
2,445✔
2200
        consolidateDomainInfo(di);
2,090✔
2201
        domains->push_back(di);
2,090✔
2202
      }
2,090✔
2203
    }
2,445✔
2204
  }
155✔
2205
}
155✔
2206

2207
void LMDBBackend::getAllDomains(vector<DomainInfo>* domains, bool /* doSerial */, bool include_disabled)
2208
{
128✔
2209
  getAllDomainsFiltered(domains, [this, include_disabled](DomainInfo& di) {
2,058✔
2210
    if (!getSerial(di) && !include_disabled) {
2,058!
2211
      return false;
×
2212
    }
×
2213

2214
    // Skip domains with variants if views are disabled.
2215
    if (di.zone.hasVariant() && !d_views) {
2,058!
2216
      return false;
×
2217
    }
×
2218

2219
    return true;
2,058✔
2220
  });
2,058✔
2221
}
128✔
2222

2223
void LMDBBackend::getUnfreshSecondaryInfos(vector<DomainInfo>* domains)
2224
{
11✔
2225
  uint32_t serial;
11✔
2226
  time_t now = time(0);
11✔
2227
  LMDBResourceRecord lrr;
11✔
2228
  soatimes st;
11✔
2229

2230
  getAllDomainsFiltered(domains, [this, &lrr, &st, &now, &serial](DomainInfo& di) {
83✔
2231
    if (!di.isSecondaryType()) {
83!
2232
      return false;
×
2233
    }
×
2234

2235
    auto txn2 = getRecordsROTransaction(di.id);
83✔
2236
    compoundOrdername co;
83✔
2237
    MDBOutVal val;
83✔
2238
    if (!txn2->txn->get(txn2->db->dbi, co(di.id, g_rootdnsname, QType::SOA), val)) {
83✔
2239
      deserializeFromBuffer(val.get<string_view>(), lrr);
51✔
2240
      memcpy(&st, &lrr.content[lrr.content.size() - sizeof(soatimes)], sizeof(soatimes));
51✔
2241
      if ((time_t)(di.last_check + ntohl(st.refresh)) > now) { // still fresh
51!
2242
        return false;
51✔
2243
      }
51✔
2244
      serial = ntohl(st.serial);
×
2245
    }
×
2246
    else {
32✔
2247
      serial = 0;
32✔
2248
    }
32✔
2249

2250
    return true;
32✔
2251
  });
83✔
2252
}
11✔
2253

2254
void LMDBBackend::setStale(domainid_t domain_id)
2255
{
2✔
2256
  genChangeDomain(domain_id, [](DomainInfo& di) {
2✔
2257
    di.last_check = 0;
2✔
2258
  });
2✔
2259
}
2✔
2260

2261
void LMDBBackend::setFresh(domainid_t domain_id)
2262
{
88✔
2263
  genChangeDomain(domain_id, [](DomainInfo& di) {
88✔
2264
    di.last_check = time(nullptr);
88✔
2265
  });
88✔
2266
}
88✔
2267

2268
void LMDBBackend::getUpdatedPrimaries(vector<DomainInfo>& updatedDomains, std::unordered_set<DNSName>& catalogs, CatalogHashMap& catalogHashes)
2269
{
×
2270
  CatalogInfo ci;
×
2271

2272
  getAllDomainsFiltered(&(updatedDomains), [this, &catalogs, &catalogHashes, &ci](DomainInfo& di) {
×
2273
    if (!di.isPrimaryType()) {
×
2274
      return false;
×
2275
    }
×
2276

2277
    if (di.kind == DomainInfo::Producer) {
×
2278
      catalogs.insert(di.zone.operator const DNSName&());
×
2279
      catalogHashes[di.zone].process("\0");
×
2280
      return false; // Producer fresness check is performed elsewhere
×
2281
    }
×
2282

2283
    if (!di.catalog.empty()) {
×
2284
      ci.fromJson(di.options, CatalogInfo::CatalogType::Producer);
×
2285
      ci.updateHash(catalogHashes, di);
×
2286
    }
×
2287

2288
    if (getSerial(di) && di.serial != di.notified_serial) {
×
2289
      di.backend = this;
×
2290
      return true;
×
2291
    }
×
2292

2293
    return false;
×
2294
  });
×
2295
}
×
2296

2297
void LMDBBackend::setNotified(domainid_t domain_id, uint32_t serial)
2298
{
1✔
2299
  if (d_write_notification_update) {
1!
2300
    genChangeDomain(domain_id, [serial](DomainInfo& info) {
1✔
2301
      info.notified_serial = serial;
1✔
2302
    });
1✔
2303
    return;
1✔
2304
  }
1✔
2305

2306
  DomainInfo info;
×
2307
  if (findDomain(domain_id, info)) {
×
2308
    auto container = s_notified_serial.write_lock();
×
2309
    container->update(info.id, serial);
×
2310
  }
×
2311
}
×
2312

2313
class getCatalogMembersReturnFalseException : std::runtime_error
2314
{
2315
public:
2316
  getCatalogMembersReturnFalseException() :
2317
    std::runtime_error("getCatalogMembers should return false") {}
×
2318
};
2319

2320
bool LMDBBackend::getCatalogMembers(const ZoneName& catalog, vector<CatalogInfo>& members, CatalogInfo::CatalogType type)
2321
{
16✔
2322
  vector<DomainInfo> scratch;
16✔
2323

2324
  try {
16✔
2325
    getAllDomainsFiltered(&scratch, [&catalog, &members, &type](DomainInfo& di) {
304✔
2326
      if ((type == CatalogInfo::CatalogType::Producer && di.kind != DomainInfo::Primary) || (type == CatalogInfo::CatalogType::Consumer && di.kind != DomainInfo::Secondary) || di.catalog != catalog) {
304✔
2327
        return false;
100✔
2328
      }
100✔
2329

2330
      CatalogInfo ci;
204✔
2331
      ci.d_id = di.id;
204✔
2332
      ci.d_zone = di.zone;
204✔
2333
      ci.d_primaries = di.primaries;
204✔
2334
      try {
204✔
2335
        ci.fromJson(di.options, type);
204✔
2336
      }
204✔
2337
      catch (const std::runtime_error& e) {
204✔
2338
        g_log << Logger::Warning << __PRETTY_FUNCTION__ << " options '" << di.options << "' for zone '" << di.zone << "' is no valid JSON: " << e.what() << endl;
×
2339
        members.clear();
×
2340
        throw getCatalogMembersReturnFalseException();
×
2341
      }
×
2342
      members.emplace_back(ci);
204✔
2343

2344
      return false;
204✔
2345
    });
204✔
2346
  }
16✔
2347
  catch (const getCatalogMembersReturnFalseException& e) {
16✔
2348
    return false;
×
2349
  }
×
2350
  return true;
16✔
2351
}
16✔
2352

2353
bool LMDBBackend::setOptions(const ZoneName& domain, const std::string& options)
2354
{
96✔
2355
  return genChangeDomain(domain, [options](DomainInfo& di) {
96✔
2356
    di.options = options;
96✔
2357
  });
96✔
2358
}
96✔
2359

2360
bool LMDBBackend::setCatalog(const ZoneName& domain, const ZoneName& catalog)
2361
{
243✔
2362
  return genChangeDomain(domain, [catalog](DomainInfo& di) {
243✔
2363
    di.catalog = catalog;
243✔
2364
  });
243✔
2365
}
243✔
2366

2367
bool LMDBBackend::getAllDomainMetadata(const ZoneName& name, std::map<std::string, std::vector<std::string>>& meta)
2368
{
8,767✔
2369
  meta.clear();
8,767✔
2370
  auto txn = d_tmeta->getROTransaction();
8,767✔
2371
  LmdbIdVec ids;
8,767✔
2372
  txn.get_multi<0>(name, ids);
8,767✔
2373

2374
  DomainMeta dm;
8,767✔
2375
  // cerr<<"getAllDomainMetadata start"<<endl;
2376
  for (auto id : ids) {
13,691✔
2377
    if (txn.get(id, dm)) {
13,691!
2378
      meta[dm.key].push_back(dm.value);
13,691✔
2379
    }
13,691✔
2380
  }
13,691✔
2381
  return true;
8,767✔
2382
}
8,767✔
2383

2384
bool LMDBBackend::setDomainMetadata(const ZoneName& name, const std::string& kind, const std::vector<std::string>& meta)
2385
{
991✔
2386
  auto txn = d_tmeta->getRWTransaction();
991✔
2387

2388
  LmdbIdVec ids;
991✔
2389
  txn.get_multi<0>(name, ids);
991✔
2390

2391
  DomainMeta dmeta;
991✔
2392
  for (auto id : ids) {
993✔
2393
    if (txn.get(id, dmeta)) {
835!
2394
      if (dmeta.key == kind) {
835✔
2395
        // cerr<<"delete"<<endl;
2396
        txn.del(id);
65✔
2397
      }
65✔
2398
    }
835✔
2399
  }
835✔
2400

2401
  for (const auto& m : meta) {
991✔
2402
    DomainMeta dm{name, kind, m};
812✔
2403
    txn.put(dm, 0, d_random_ids, burtleCI(kind, name.hash()));
812✔
2404
  }
812✔
2405
  txn.commit();
991✔
2406
  return true;
991✔
2407
}
991✔
2408

2409
bool LMDBBackend::getDomainKeys(const ZoneName& name, std::vector<KeyData>& keys)
2410
{
1,241✔
2411
  auto txn = d_tkdb->getROTransaction();
1,241✔
2412
  LmdbIdVec ids;
1,241✔
2413
  txn.get_multi<0>(name, ids);
1,241✔
2414

2415
  KeyDataDB key;
1,241✔
2416

2417
  for (auto id : ids) {
1,241✔
2418
    if (txn.get(id, key)) {
705!
2419
      KeyData kd{key.content, id, key.flags, key.active, key.published};
705✔
2420
      keys.emplace_back(std::move(kd));
705✔
2421
    }
705✔
2422
  }
705✔
2423

2424
  return true;
1,241✔
2425
}
1,241✔
2426

2427
bool LMDBBackend::removeDomainKey(const ZoneName& name, unsigned int keyId)
2428
{
29✔
2429
  auto txn = d_tkdb->getRWTransaction();
29✔
2430
  KeyDataDB kdb;
29✔
2431
  if (txn.get(keyId, kdb)) {
29✔
2432
    if (kdb.domain == name) {
20!
2433
      txn.del(keyId);
20✔
2434
      txn.commit();
20✔
2435
      return true;
20✔
2436
    }
20✔
2437
  }
20✔
2438
  // cout << "??? wanted to remove domain key for domain "<<name<<" with id "<<keyId<<", could not find it"<<endl;
2439
  return true;
9✔
2440
}
29✔
2441

2442
bool LMDBBackend::addDomainKey(const ZoneName& name, const KeyData& key, int64_t& keyId)
2443
{
211✔
2444
  auto txn = d_tkdb->getRWTransaction();
211✔
2445
  KeyDataDB kdb{name, key.content, key.flags, key.active, key.published};
211✔
2446

2447
  // all this just to get the tag - while most of our callers (except b2b-migrate) already have a dpk
2448
  DNSKEYRecordContent dkrc;
211✔
2449
  auto keyEngine = shared_ptr<DNSCryptoKeyEngine>(DNSCryptoKeyEngine::makeFromISCString(dkrc, key.content));
211✔
2450
  DNSSECPrivateKey dpk;
211✔
2451
  dpk.setKey(keyEngine, key.flags);
211✔
2452
  auto tag = dpk.getDNSKEY().getTag();
211✔
2453

2454
  keyId = txn.put(kdb, 0, d_random_ids, name.hash(tag));
211✔
2455
  txn.commit();
211✔
2456

2457
  return true;
211✔
2458
}
211✔
2459

2460
bool LMDBBackend::activateDomainKey(const ZoneName& name, unsigned int keyId)
2461
{
6✔
2462
  auto txn = d_tkdb->getRWTransaction();
6✔
2463
  KeyDataDB kdb;
6✔
2464
  if (txn.get(keyId, kdb)) {
6!
2465
    if (kdb.domain == name) {
6!
2466
      txn.modify(keyId, [](KeyDataDB& kdbarg) {
6✔
2467
        kdbarg.active = true;
6✔
2468
      });
6✔
2469
      txn.commit();
6✔
2470
      return true;
6✔
2471
    }
6✔
2472
  }
6✔
2473

2474
  // cout << "??? wanted to activate domain key for domain "<<name<<" with id "<<keyId<<", could not find it"<<endl;
2475
  return true;
×
2476
}
6✔
2477

2478
bool LMDBBackend::deactivateDomainKey(const ZoneName& name, unsigned int keyId)
2479
{
4✔
2480
  auto txn = d_tkdb->getRWTransaction();
4✔
2481
  KeyDataDB kdb;
4✔
2482
  if (txn.get(keyId, kdb)) {
4!
2483
    if (kdb.domain == name) {
4!
2484
      txn.modify(keyId, [](KeyDataDB& kdbarg) {
4✔
2485
        kdbarg.active = false;
4✔
2486
      });
4✔
2487
      txn.commit();
4✔
2488
      return true;
4✔
2489
    }
4✔
2490
  }
4✔
2491
  // cout << "??? wanted to deactivate domain key for domain "<<name<<" with id "<<keyId<<", could not find it"<<endl;
2492
  return true;
×
2493
}
4✔
2494

2495
bool LMDBBackend::publishDomainKey(const ZoneName& name, unsigned int keyId)
2496
{
6✔
2497
  auto txn = d_tkdb->getRWTransaction();
6✔
2498
  KeyDataDB kdb;
6✔
2499
  if (txn.get(keyId, kdb)) {
6!
2500
    if (kdb.domain == name) {
6!
2501
      txn.modify(keyId, [](KeyDataDB& kdbarg) {
6✔
2502
        kdbarg.published = true;
6✔
2503
      });
6✔
2504
      txn.commit();
6✔
2505
      return true;
6✔
2506
    }
6✔
2507
  }
6✔
2508

2509
  // cout << "??? wanted to hide domain key for domain "<<name<<" with id "<<keyId<<", could not find it"<<endl;
2510
  return true;
×
2511
}
6✔
2512

2513
bool LMDBBackend::unpublishDomainKey(const ZoneName& name, unsigned int keyId)
2514
{
10✔
2515
  auto txn = d_tkdb->getRWTransaction();
10✔
2516
  KeyDataDB kdb;
10✔
2517
  if (txn.get(keyId, kdb)) {
10!
2518
    if (kdb.domain == name) {
10!
2519
      txn.modify(keyId, [](KeyDataDB& kdbarg) {
10✔
2520
        kdbarg.published = false;
10✔
2521
      });
10✔
2522
      txn.commit();
10✔
2523
      return true;
10✔
2524
    }
10✔
2525
  }
10✔
2526
  // cout << "??? wanted to unhide domain key for domain "<<name<<" with id "<<keyId<<", could not find it"<<endl;
2527
  return true;
×
2528
}
10✔
2529

2530
// Return true if the key points to an NSEC3 back chain record (ttl == 0).
2531
// Updates lrr if this is an NSEC3 record (regardless of its kind).
2532
bool LMDBBackend::isNSEC3BackRecord(LMDBResourceRecord& lrr, const MDBOutVal& key, const MDBOutVal& val)
2533
{
48,961✔
2534
  if (compoundOrdername::getQType(key.getNoStripHeader<StringView>()) == QType::NSEC3) {
48,961✔
2535
    deserializeFromBuffer(val.get<StringView>(), lrr);
29,566✔
2536
    if (lrr.ttl == 0) {
29,566✔
2537
      return true;
18,593✔
2538
    }
18,593✔
2539
  }
29,566✔
2540
  return false;
30,368✔
2541
}
48,961✔
2542

2543
// Search for the next NSEC3 back record and return its qname as `after'.
2544
// Returns true if found, false if not (either end of database records, or
2545
// different domain).
2546
// NOLINTNEXTLINE(readability-identifier-length)
2547
bool LMDBBackend::getAfterForward(MDBROCursor& cursor, MDBOutVal& key, MDBOutVal& val, domainid_t id, DNSName& after)
2548
{
13,666✔
2549
  LMDBResourceRecord lrr;
13,666✔
2550

2551
  while (!isNSEC3BackRecord(lrr, key, val)) {
38,105✔
2552
    if (cursor.next(key, val) != 0 || compoundOrdername::getDomainID(key.getNoStripHeader<StringView>()) != id) {
25,723✔
2553
      // cout<<"hit end of zone or database when we shouldn't"<<endl;
2554
      return false;
1,284✔
2555
    }
1,284✔
2556
  }
25,723✔
2557
  after = compoundOrdername::getQName(key.getNoStripHeader<StringView>());
12,382✔
2558
  // cout<<"returning: before="<<before<<", after="<<after<<", unhashed: "<<unhashed<<endl;
2559
  return true;
12,382✔
2560
}
13,666✔
2561

2562
// Reset the cursor position and fall through getAfterForward.
2563
// NOLINTNEXTLINE(readability-identifier-length)
2564
bool LMDBBackend::getAfterForwardFromStart(MDBROCursor& cursor, MDBOutVal& key, MDBOutVal& val, domainid_t id, DNSName& after)
2565
{
1,945✔
2566
  compoundOrdername co; // NOLINT(readability-identifier-length)
1,945✔
2567

2568
  if (cursor.lower_bound(co(id), key, val) != 0) {
1,945!
2569
    // cout<<"hit end of zone find when we shouldn't"<<endl;
2570
    return false;
×
2571
  }
×
2572
  return getAfterForward(cursor, key, val, id, after);
1,945✔
2573
}
1,945✔
2574

2575
// NOLINTNEXTLINE(readability-identifier-length)
2576
bool LMDBBackend::getBeforeAndAfterNamesAbsolute(domainid_t id, const DNSName& qname, DNSName& unhashed, DNSName& before, DNSName& after)
2577
{
12,383✔
2578
  //  cout << __PRETTY_FUNCTION__<< ": "<<id <<", "<<qname << " " << unhashed<<endl;
2579

2580
  DomainInfo info;
12,383✔
2581
  if (!findDomain(id, info)) {
12,383!
2582
    // domain does not exist, tough luck
2583
    return false;
×
2584
  }
×
2585
  // cout <<"Zone: "<<info.zone<<endl;
2586

2587
  compoundOrdername co;
12,383✔
2588
  auto txn = getRecordsROTransaction(id);
12,383✔
2589

2590
  auto cursor = txn->txn->getCursor(txn->db->dbi);
12,383✔
2591
  MDBOutVal key, val;
12,383✔
2592

2593
  LMDBResourceRecord lrr;
12,383✔
2594

2595
  string matchkey = co(id, qname, QType::NSEC3);
12,383✔
2596
  if (cursor.lower_bound(matchkey, key, val)) {
12,383✔
2597
    // this is beyond the end of the database
2598
    // cout << "Beyond end of database!" << endl;
2599
    cursor.last(key, val);
174✔
2600

2601
    for (;;) {
672✔
2602
      if (co.getDomainID(key.getNoStripHeader<StringView>()) != id) {
672!
2603
        // cout<<"Last record also not part of this zone!"<<endl;
2604
        //  this implies something is wrong in the database, nothing we can do
2605
        return false;
×
2606
      }
×
2607

2608
      if (isNSEC3BackRecord(lrr, key, val)) {
672✔
2609
        break; // the kind of NSEC3 we need
174✔
2610
      }
174✔
2611
      if (cursor.prev(key, val)) {
498!
2612
        // hit beginning of database, again means something is wrong with it
2613
        return false;
×
2614
      }
×
2615
    }
498✔
2616
    before = co.getQName(key.getNoStripHeader<StringView>());
174✔
2617
    unhashed = DNSName(lrr.content.c_str(), lrr.content.size(), 0, false) + info.zone.operator const DNSName&();
174✔
2618

2619
    // now to find after .. at the beginning of the zone
2620
    return getAfterForwardFromStart(cursor, key, val, id, after);
174✔
2621
  }
174✔
2622

2623
  // cout<<"Ended up at "<<co.getQName(key.get<StringView>()) <<endl;
2624

2625
  before = co.getQName(key.getNoStripHeader<StringView>());
12,209✔
2626
  if (before == qname) {
12,209✔
2627
    // cout << "Ended up on exact right node" << endl;
2628
    // unhashed should be correct now, maybe check?
2629
    if (cursor.next(key, val)) {
6,173✔
2630
      // xxx should find first hash now
2631

2632
      return getAfterForwardFromStart(cursor, key, val, id, after);
190✔
2633
    }
190✔
2634
  }
6,173✔
2635
  else {
6,036✔
2636
    // cout <<"Going backwards to find 'before'"<<endl;
2637
    int count = 0;
6,036✔
2638
    for (;;) {
14,739✔
2639
      if (compoundOrdername::getQName(key.getNoStripHeader<StringView>()).canonCompare(qname)) {
14,739✔
2640
        if (isNSEC3BackRecord(lrr, key, val)) {
8,703✔
2641
          break;
5,739✔
2642
        }
5,739✔
2643
      }
8,703✔
2644

2645
      if (cursor.prev(key, val) || co.getDomainID(key.getNoStripHeader<StringView>()) != id) {
9,000✔
2646
        // cout <<"XXX Hit *beginning* of zone or database"<<endl;
2647
        // this can happen, must deal with it
2648
        // should now find the last hash of the zone
2649

2650
        if (cursor.lower_bound(co(id + 1), key, val)) {
297!
2651
          // cout << "Could not find the next higher zone, going to the end of the database then"<<endl;
2652
          cursor.last(key, val);
297✔
2653
        }
297✔
2654
        else
×
2655
          cursor.prev(key, val);
×
2656

2657
        for (;;) {
1,480✔
2658
          if (co.getDomainID(key.getNoStripHeader<StringView>()) != id) {
1,480!
2659
            // cout<<"Last record also not part of this zone!"<<endl;
2660
            //  this implies something is wrong in the database, nothing we can do
2661
            return false;
×
2662
          }
×
2663

2664
          if (isNSEC3BackRecord(lrr, key, val)) {
1,480✔
2665
            break;
297✔
2666
          }
297✔
2667
          if (cursor.prev(key, val)) {
1,183!
2668
            // hit beginning of database, again means something is wrong with it
2669
            return false;
×
2670
          }
×
2671
        }
1,183✔
2672
        before = co.getQName(key.getNoStripHeader<StringView>());
297✔
2673
        unhashed = DNSName(lrr.content.c_str(), lrr.content.size(), 0, false) + info.zone.operator const DNSName&();
297✔
2674
        // cout <<"Should still find 'after'!"<<endl;
2675
        // for 'after', we need to find the first hash of this zone
2676

2677
        return getAfterForwardFromStart(cursor, key, val, id, after);
297✔
2678
      }
297✔
2679
      ++count;
8,703✔
2680
    }
8,703✔
2681
    before = co.getQName(key.getNoStripHeader<StringView>());
5,739✔
2682
    unhashed = DNSName(lrr.content.c_str(), lrr.content.size(), 0, false) + info.zone.operator const DNSName&();
5,739✔
2683
    // cout<<"Went backwards, found "<<before<<endl;
2684
    // return us to starting point
2685
    while (count--)
13,314✔
2686
      cursor.next(key, val);
7,575✔
2687
  }
5,739✔
2688
  //  cout<<"Now going forward"<<endl;
2689
  if (getAfterForward(cursor, key, val, id, after)) {
11,722✔
2690
    return true;
10,438✔
2691
  }
10,438✔
2692
  // cout <<"Hit end of database or zone, finding first hash then in zone "<<id<<endl;
2693
  // Reset cursor position and retry
2694
  return getAfterForwardFromStart(cursor, key, val, id, after);
1,284✔
2695
}
11,722✔
2696

2697
// Return whether the given entry is an authoritative record, ignoring empty
2698
// non terminal records.
2699
bool LMDBBackend::isValidAuthRecord(const MDBOutVal& key, const MDBOutVal& val)
2700
{
10,270✔
2701
  LMDBResourceRecord lrr;
10,270✔
2702

2703
  deserializeFromBuffer(val.get<StringView>(), lrr);
10,270✔
2704
  QType qtype = compoundOrdername::getQType(key.getNoStripHeader<string_view>()).getCode();
10,270✔
2705
  return qtype != QType::ENT && (lrr.auth || qtype == QType::NS);
10,270✔
2706
}
10,270✔
2707

2708
bool LMDBBackend::getBeforeAndAfterNames(domainid_t domainId, const ZoneName& zonenameU, const DNSName& qname, DNSName& before, DNSName& after)
2709
{
5,267✔
2710
  ZoneName zonename = zonenameU.makeLowerCase();
5,267✔
2711
  //  cout << __PRETTY_FUNCTION__<< ": "<<domainId <<", "<<zonename << ", '"<<qname<<"'"<<endl;
2712

2713
  compoundOrdername co;
5,267✔
2714
  auto txn = getRecordsROTransaction(domainId);
5,267✔
2715

2716
  auto cursor = txn->txn->getCursor(txn->db->dbi);
5,267✔
2717
  MDBOutVal key, val;
5,267✔
2718

2719
  DNSName qname2 = qname.makeRelative(zonename);
5,267✔
2720
  string matchkey = co(domainId, qname2);
5,267✔
2721
  // cout<<"Lower_bound for "<<qname2<<endl;
2722
  if (cursor.lower_bound(matchkey, key, val)) {
5,267✔
2723
    // cout << "Hit end of database, bummer"<<endl;
2724
    cursor.last(key, val);
365✔
2725
    if (compoundOrdername::getDomainID(key.getNoStripHeader<string_view>()) == domainId) {
365!
2726
      before = compoundOrdername::getQName(key.getNoStripHeader<string_view>()) + zonename.operator const DNSName&();
365✔
2727
      after = zonename.operator const DNSName&();
365✔
2728
    }
365✔
2729
    // else
2730
    // cout << "We were at end of database, but this zone is not there?!"<<endl;
2731
    return true;
365✔
2732
  }
365✔
2733
  // cout<<"Cursor is at "<<co.getQName(key.get<string_view>()) <<", in zone id "<<compoundOrdername::getDomainID(key.get<string_view>())<< endl;
2734

2735
  if (compoundOrdername::getQType(key.getNoStripHeader<string_view>()).getCode() != 0 && compoundOrdername::getDomainID(key.getNoStripHeader<string_view>()) == domainId && compoundOrdername::getQName(key.getNoStripHeader<string_view>()) == qname2) { // don't match ENTs
4,902✔
2736
    // cout << "Had an exact match!"<<endl;
2737
    before = qname; // i.e. qname2 + zonename.operator const DNSName&();
1,912✔
2738
    int rc;
1,912✔
2739
    for (;;) {
5,164✔
2740
      rc = cursor.next(key, val);
5,164✔
2741
      if (rc)
5,164✔
2742
        break;
39✔
2743

2744
      if (compoundOrdername::getDomainID(key.getNoStripHeader<string_view>()) == domainId && key.getNoStripHeader<StringView>().rfind(matchkey, 0) == 0) {
5,125!
2745
        continue;
2,515✔
2746
      }
2,515✔
2747
      if (isValidAuthRecord(key, val)) {
2,610✔
2748
        break;
1,873✔
2749
      }
1,873✔
2750
    }
2,610✔
2751
    if (rc != 0 || compoundOrdername::getDomainID(key.getNoStripHeader<string_view>()) != domainId) {
1,912!
2752
      // cout << "We hit the end of the zone or database. 'after' is apex" << endl;
2753
      after = zonename.operator const DNSName&();
39✔
2754
      return false;
39✔
2755
    }
39✔
2756
    after = compoundOrdername::getQName(key.getNoStripHeader<string_view>()) + zonename.operator const DNSName&();
1,873✔
2757
    return true;
1,873✔
2758
  }
1,912✔
2759

2760
  if (compoundOrdername::getDomainID(key.getNoStripHeader<string_view>()) != domainId) {
2,990✔
2761
    // cout << "Ended up in next zone, 'after' is zonename" <<endl;
2762
    after = zonename.operator const DNSName&();
12✔
2763
    // cout << "Now hunting for previous" << endl;
2764
    int rc;
12✔
2765
    for (;;) {
12✔
2766
      rc = cursor.prev(key, val);
12✔
2767
      if (rc) {
12!
2768
        // cout<<"Reversed into zone, but got not found from lmdb" <<endl;
2769
        return false;
×
2770
      }
×
2771

2772
      if (compoundOrdername::getDomainID(key.getNoStripHeader<string_view>()) != domainId) {
12!
2773
        // cout<<"Reversed into zone, but found wrong zone id " << compoundOrdername::getDomainID(key.getNoStripHeader<string_view>()) << " != "<<domainId<<endl;
2774
        // "this can't happen"
2775
        return false;
×
2776
      }
×
2777
      if (isValidAuthRecord(key, val)) {
12!
2778
        break;
12✔
2779
      }
12✔
2780
    }
12✔
2781

2782
    before = compoundOrdername::getQName(key.getNoStripHeader<string_view>()) + zonename.operator const DNSName&();
12✔
2783
    // cout<<"Found: "<< before<<endl;
2784
    return true;
12✔
2785
  }
12✔
2786

2787
  // cout <<"We ended up after "<<qname<<", on "<<co.getQName(key.getNoStripHeader<string_view>())<<endl;
2788

2789
  int skips = 0;
2,978✔
2790
  for (;;) {
3,894✔
2791
    if (isValidAuthRecord(key, val)) {
3,894✔
2792
      after = compoundOrdername::getQName(key.getNoStripHeader<string_view>()) + zonename.operator const DNSName&();
2,978✔
2793
      // Note: change isValidAuthRecord to also return the LMDBResourceRecord if
2794
      // uncommenting these debug messages...
2795
      // cout <<"Found auth ("<<lrr.auth<<") or an NS record "<<after<<", type: "<<co.getQType(key.getNoStripHeader<string_view>()).toString()<<", ttl = "<<lrr.ttl<<endl;
2796
      // cout << makeHexDump(val.get<string>()) << endl;
2797
      break;
2,978✔
2798
    }
2,978✔
2799
    // cout <<"  oops, " << co.getQName(key.getNoStripHeader<string_view>()) << " was not auth "<<lrr.auth<< " type=" << lrr.qtype.toString()<<" or NS, so need to skip ahead a bit more" << endl;
2800
    int rc = cursor.next(key, val);
916✔
2801
    if (!rc)
916!
2802
      ++skips;
916✔
2803
    if (rc != 0 || compoundOrdername::getDomainID(key.getNoStripHeader<string_view>()) != domainId) {
916!
2804
      // cout << "  oops, hit end of database or zone. This means after is apex" <<endl;
2805
      after = zonename.operator const DNSName&();
×
2806
      break;
×
2807
    }
×
2808
  }
916✔
2809
  // go back to where we were
2810
  while (skips--)
3,894✔
2811
    cursor.prev(key, val);
916✔
2812

2813
  for (;;) {
3,754✔
2814
    int rc = cursor.prev(key, val);
3,754✔
2815
    if (rc != 0 || compoundOrdername::getDomainID(key.getNoStripHeader<string_view>()) != domainId) {
3,754!
2816
      // XX I don't think this case can happen
2817
      // cout << "We hit the beginning of the zone or database.. now what" << endl;
2818
      return false;
×
2819
    }
×
2820
    before = compoundOrdername::getQName(key.getNoStripHeader<string_view>()) + zonename.operator const DNSName&();
3,754✔
2821
    // cout<<"And before to "<<before<<", auth = "<<rr.auth<<endl;
2822
    if (isValidAuthRecord(key, val)) {
3,754✔
2823
      break;
2,978✔
2824
    }
2,978✔
2825
    // cout << "Oops, that was wrong, go back one more"<<endl;
2826
  }
3,754✔
2827

2828
  return true;
2,978✔
2829
}
2,978✔
2830

2831
bool LMDBBackend::updateDNSSECOrderNameAndAuth(domainid_t domain_id, const DNSName& qname, const DNSName& ordername, bool auth, const uint16_t qtype, bool isNsec3)
2832
{
126,489✔
2833
  //  cout << __PRETTY_FUNCTION__<< ": "<< domain_id <<", '"<<qname <<"', '"<<ordername<<"', "<<auth<< ", " << qtype << endl;
2834
  shared_ptr<RecordsRWTransaction> txn;
126,489✔
2835
  bool needCommit = false;
126,489✔
2836
  if (d_rwtxn && d_transactiondomainid == domain_id) {
126,489!
2837
    txn = d_rwtxn;
126,489✔
2838
    //    cout<<"Reusing open transaction"<<endl;
2839
  }
126,489✔
2840
  else {
×
2841
    //    cout<<"Making a new RW txn for " << __PRETTY_FUNCTION__ <<endl;
2842
    txn = getRecordsRWTransaction(domain_id);
×
2843
    needCommit = true;
×
2844
  }
×
2845

2846
  DomainInfo info;
126,489✔
2847
  if (!findDomain(domain_id, info)) {
126,489!
2848
    //    cout<<"Could not find domain_id "<<domain_id <<endl;
2849
    return false;
×
2850
  }
×
2851

2852
  DNSName rel = qname.makeRelative(info.zone);
126,489✔
2853

2854
  compoundOrdername co;
126,489✔
2855
  string matchkey = co(domain_id, rel);
126,489✔
2856

2857
  auto cursor = txn->txn->getCursor(txn->db->dbi);
126,489✔
2858
  MDBOutVal key, val;
126,489✔
2859
  if (cursor.prefix(matchkey, key, val) != 0) {
126,489!
2860
    // cout << "Could not find anything"<<endl;
2861
    return false;
×
2862
  }
×
2863

2864
  bool hasOrderName = !ordername.empty() && isNsec3;
126,489✔
2865
  bool keepNSEC3 = hasOrderName;
126,489✔
2866

2867
  do {
131,189✔
2868
    if (compoundOrdername::getQType(key.getNoStripHeader<StringView>()) == QType::NSEC3) {
131,189✔
2869
      continue;
1,545✔
2870
    }
1,545✔
2871

2872
    vector<LMDBResourceRecord> lrrs;
129,644✔
2873
    deserializeFromBuffer(val.get<StringView>(), lrrs);
129,644✔
2874
    bool changed = false;
129,644✔
2875
    vector<LMDBResourceRecord> newRRs;
129,644✔
2876
    newRRs.reserve(lrrs.size());
129,644✔
2877
    for (auto& lrr : lrrs) {
181,551✔
2878
      lrr.qtype = compoundOrdername::getQType(key.getNoStripHeader<StringView>());
181,551✔
2879
      bool isDifferentQType = qtype != QType::ANY && QType(qtype) != lrr.qtype;
181,551✔
2880
      // If there is at least one entry for that qname, with a different qtype
2881
      // than the one we are working for, known to be associated to an NSEC3
2882
      // record, then we should NOT delete it.
2883
      if (!keepNSEC3) {
181,551✔
2884
        keepNSEC3 = lrr.hasOrderName && isDifferentQType;
77,258✔
2885
      }
77,258✔
2886

2887
      if (!isDifferentQType && (lrr.hasOrderName != hasOrderName || lrr.auth != auth)) {
181,551✔
2888
        lrr.auth = auth;
83,902✔
2889
        lrr.hasOrderName = hasOrderName;
83,902✔
2890
        changed = true;
83,902✔
2891
      }
83,902✔
2892
      newRRs.push_back(std::move(lrr));
181,551✔
2893
    }
181,551✔
2894
    if (changed) {
129,644✔
2895
      cursor.put(key, serializeToBuffer(newRRs));
83,240✔
2896
    }
83,240✔
2897
  } while (cursor.next(key, val) == 0);
131,189✔
2898

2899
  if (!keepNSEC3) {
126,489✔
2900
    // NSEC3 link to be removed: need to remove an existing pair, if any
2901
    deleteNSEC3RecordPair(txn, domain_id, rel);
44,010✔
2902
  }
44,010✔
2903
  else if (hasOrderName) {
82,479✔
2904
    // NSEC3 link to be added or updated
2905
    writeNSEC3RecordPair(txn, domain_id, rel, ordername);
82,269✔
2906
  }
82,269✔
2907

2908
  if (needCommit)
126,489!
2909
    txn->txn->commit();
×
2910
  return false;
126,489✔
2911
}
126,489✔
2912

2913
bool LMDBBackend::updateEmptyNonTerminals(domainid_t domain_id, set<DNSName>& insert, set<DNSName>& erase, bool remove)
2914
{
1,732✔
2915
  // cout << __PRETTY_FUNCTION__<< ": "<< domain_id << ", insert.size() "<<insert.size()<<", "<<erase.size()<<", " <<remove<<endl;
2916

2917
  bool needCommit = false;
1,732✔
2918
  shared_ptr<RecordsRWTransaction> txn;
1,732✔
2919
  if (d_rwtxn && d_transactiondomainid == domain_id) {
1,732!
2920
    txn = d_rwtxn;
1,732✔
2921
    //    cout<<"Reusing open transaction"<<endl;
2922
  }
1,732✔
2923
  else {
×
2924
    //    cout<<"Making a new RW txn for delete domain"<<endl;
2925
    txn = getRecordsRWTransaction(domain_id);
×
2926
    needCommit = true;
×
2927
  }
×
2928

2929
  DomainInfo info;
1,732✔
2930
  if (!findDomain(domain_id, info)) {
1,732!
2931
    // cout <<"No such domain with id "<<domain_id<<endl;
2932
    return false;
×
2933
  }
×
2934

2935
  // if remove is set, all ENTs should be removed
2936
  compoundOrdername order;
1,732✔
2937
  if (remove) {
1,732!
2938
    string match = order(domain_id);
×
2939
    // We can not simply blindly delete all ENT records the way
2940
    // deleteDomainRecords() would do, as we also need to remove
2941
    // NSEC3 records for these ENT, if any.
2942
    {
×
2943
      auto cursor = txn->txn->getCursor(txn->db->dbi);
×
2944
      MDBOutVal key{};
×
2945
      MDBOutVal val{};
×
2946
      std::vector<DNSName> names;
×
2947

2948
      if (cursor.prefix(match, key, val) == 0) {
×
2949
        do {
×
2950
          if (compoundOrdername::getQType(key.getNoStripHeader<StringView>()) == QType::ENT) {
×
2951
            // We need to remember the name of the records we're deleting, so
2952
            // as to remove the matching NSEC3 records, if any.
2953
            // (we can't invoke deleteNSEC3RecordPair here as doing this
2954
            // could make our cursor invalid)
2955
            DNSName qname = compoundOrdername::getQName(key.getNoStripHeader<StringView>());
×
2956
            names.emplace_back(qname);
×
2957
            cursor.del(key);
×
2958
          }
×
2959
        } while (cursor.next(key, val) == 0);
×
2960
      }
×
2961
      for (const auto& qname : names) {
×
2962
        deleteNSEC3RecordPair(txn, domain_id, qname);
×
2963
      }
×
2964
    }
×
2965
  }
×
2966
  else {
1,732✔
2967
    for (auto name : erase) {
1,732✔
2968
      // cout <<" -"<<name<<endl;
2969
      name.makeUsRelative(info.zone);
1,667✔
2970
      std::string match = order(domain_id, name, QType::ENT);
1,667✔
2971
      MDBOutVal val{};
1,667✔
2972
      if (txn->txn->get(txn->db->dbi, match, val) == 0) {
1,667✔
2973
        txn->txn->del(txn->db->dbi, match);
161✔
2974
        deleteNSEC3RecordPair(txn, domain_id, name);
161✔
2975
      }
161✔
2976
    }
1,667✔
2977
  }
1,732✔
2978
  for (const auto& name : insert) {
1,741✔
2979
    LMDBResourceRecord lrr;
515✔
2980
    lrr.qname = name.makeRelative(info.zone);
515✔
2981
    lrr.ttl = 0;
515✔
2982
    lrr.auth = true;
515✔
2983
    std::string ser = serializeToBuffer(lrr);
515✔
2984
    txn->txn->put(txn->db->dbi, order(domain_id, lrr.qname, QType::ENT), ser);
515✔
2985
    // cout <<" +"<<name<<endl;
2986
  }
515✔
2987
  if (needCommit) {
1,732!
2988
    txn->txn->commit();
×
2989
  }
×
2990
  return false;
1,732✔
2991
}
1,732✔
2992

2993
/* TSIG */
2994
bool LMDBBackend::getTSIGKey(const DNSName& name, DNSName& algorithm, string& content)
2995
{
103✔
2996
  auto txn = d_ttsig->getROTransaction();
103✔
2997
  LmdbIdVec ids;
103✔
2998
  txn.get_multi<0>(name, ids);
103✔
2999

3000
  TSIGKey key;
103✔
3001
  for (auto id : ids) {
103✔
3002
    if (txn.get(id, key)) {
87!
3003
      if (algorithm.empty() || algorithm == DNSName(key.algorithm)) {
87!
3004
        algorithm = DNSName(key.algorithm);
87✔
3005
        content = key.key;
87✔
3006
      }
87✔
3007
    }
87✔
3008
  }
87✔
3009

3010
  return true;
103✔
3011
}
103✔
3012

3013
// this deletes an old key if it has the same algorithm
3014
bool LMDBBackend::setTSIGKey(const DNSName& name, const DNSName& algorithm, const string& content)
3015
{
27✔
3016
  auto txn = d_ttsig->getRWTransaction();
27✔
3017

3018
  LmdbIdVec ids;
27✔
3019
  txn.get_multi<0>(name, ids);
27✔
3020

3021
  TSIGKey key;
27✔
3022
  for (auto id : ids) {
27✔
3023
    if (txn.get(id, key)) {
2!
3024
      if (key.algorithm == algorithm) {
2✔
3025
        txn.del(id);
1✔
3026
      }
1✔
3027
    }
2✔
3028
  }
2✔
3029

3030
  TSIGKey tk;
27✔
3031
  tk.name = name;
27✔
3032
  tk.algorithm = algorithm;
27✔
3033
  tk.key = content;
27✔
3034

3035
  txn.put(tk, 0, d_random_ids, name.hash());
27✔
3036
  txn.commit();
27✔
3037

3038
  return true;
27✔
3039
}
27✔
3040
bool LMDBBackend::deleteTSIGKey(const DNSName& name)
3041
{
2✔
3042
  auto txn = d_ttsig->getRWTransaction();
2✔
3043

3044
  LmdbIdVec ids;
2✔
3045
  txn.get_multi<0>(name, ids);
2✔
3046

3047
  TSIGKey key;
2✔
3048

3049
  for (auto id : ids) {
2✔
3050
    if (txn.get(id, key)) {
2!
3051
      txn.del(id);
2✔
3052
    }
2✔
3053
  }
2✔
3054
  txn.commit();
2✔
3055
  return true;
2✔
3056
}
2✔
3057
bool LMDBBackend::getTSIGKeys(std::vector<struct TSIGKey>& keys)
3058
{
1✔
3059
  auto txn = d_ttsig->getROTransaction();
1✔
3060

3061
  keys.clear();
1✔
3062
  // In a perfect world, we would simply iterate over txn and add every
3063
  // item to the returned vector:
3064
  //   for (auto iter = txn.begin(); iter != txn.end(); ++iter) {
3065
  //     keys.push_back(*iter);
3066
  //   }
3067
  // But databases converted from older (< 5) schemas _may_ have multiple
3068
  // entries for the same TSIG key name and algorithm, something which is not
3069
  // allowed in the v5 database schema. These extra entries will not be found
3070
  // by get_multi<> during regular operations, and would only appear in the
3071
  // results of this method.
3072
  // In order to prevent this, we first only gather the list of key names, and
3073
  // in a second step, query for them using a similar logic as getTSIGKey().
3074
  // Unfortunately, there does not seem to be a way to know if the database had
3075
  // been created using the v5 schema (not converted), in which case we could
3076
  // use the above, simpler logic.
3077
  std::unordered_set<DNSName> keynames;
1✔
3078
  for (const auto& iter : txn) {
10✔
3079
    keynames.insert(iter.name);
10✔
3080
  }
10✔
3081
  for (const auto& iter : keynames) {
9✔
3082
    LmdbIdVec ids;
9✔
3083
    txn.get_multi<0>(iter, ids);
9✔
3084
    for (auto key_id : ids) {
10✔
3085
      TSIGKey key;
10✔
3086
      if (txn.get(key_id, key)) {
10!
3087
        keys.push_back(key);
10✔
3088
      }
10✔
3089
    }
10✔
3090
  }
9✔
3091
  return true;
1✔
3092
}
1✔
3093

3094
string LMDBBackend::directBackendCmd(const string& query)
3095
{
×
3096
  ostringstream ret, usage;
×
3097

3098
  usage << "info                               show some information about the database" << endl;
×
3099
  usage << "index check domains                check zone<>ID indexes" << endl;
×
3100
  usage << "index refresh domains <ID>         refresh index for zone with this ID" << endl;
×
3101
  usage << "index refresh-all domains          refresh index for all zones with disconnected indexes" << endl;
×
3102
  vector<string> argv;
×
3103
  stringtok(argv, query);
×
3104

3105
  if (argv.empty()) {
×
3106
    return usage.str();
×
3107
  }
×
3108

3109
  string& cmd = argv[0];
×
3110

3111
  if (cmd == "help") {
×
3112
    return usage.str();
×
3113
  }
×
3114

3115
  if (cmd == "info") {
×
3116
    ret << "shards: " << s_shards << endl;
×
3117
    ret << "schemaversion: " << SCHEMAVERSION << endl;
×
3118

3119
    return ret.str();
×
3120
  }
×
3121

3122
  if (cmd == "index") {
×
3123
    if (argv.size() < 2) {
×
3124
      return "need an index subcommand\n";
×
3125
    }
×
3126

3127
    string& subcmd = argv[1];
×
3128

3129
    if (subcmd == "check" || subcmd == "refresh-all") {
×
3130
      bool refresh = false;
×
3131

3132
      if (subcmd == "refresh-all") {
×
3133
        refresh = true;
×
3134
      }
×
3135

3136
      if (argv.size() < 3) {
×
3137
        return "need an index name\n";
×
3138
      }
×
3139

3140
      if (argv[2] != "domains") {
×
3141
        return "can only check the domains index\n";
×
3142
      }
×
3143

3144
      vector<uint32_t> refreshQueue;
×
3145

3146
      {
×
3147
        auto txn = d_tdomains->getROTransaction();
×
3148

3149
        for (auto iter = txn.begin(); iter != txn.end(); ++iter) {
×
3150
          DomainInfo di = *iter;
×
3151

3152
          auto id = iter.getID();
×
3153

3154
          LmdbIdVec ids;
×
3155
          txn.get_multi<0>(di.zone, ids);
×
3156

3157
          if (ids.size() != 1) {
×
3158
            ret << "ID->zone index has " << id << "->" << di.zone << ", ";
×
3159

3160
            if (ids.empty()) {
×
3161
              ret << "zone->ID index has no entry for " << di.zone << endl;
×
3162
              if (refresh) {
×
3163
                refreshQueue.push_back(id);
×
3164
              }
×
3165
              else {
×
3166
                ret << "  suggested remedy: index refresh domains " << id << endl;
×
3167
              }
×
3168
            }
×
3169
            else {
×
3170
              // ids.size() > 1
3171
              ret << "zone->ID index has multiple entries for " << di.zone << ": ";
×
3172
              for (auto id_ : ids) {
×
3173
                ret << id_ << " ";
×
3174
              }
×
3175
              ret << endl;
×
3176
            }
×
3177
          }
×
3178
        }
×
3179
      }
×
3180

3181
      if (refresh) {
×
3182
        for (const auto& id : refreshQueue) {
×
3183
          if (genChangeDomain(id, [](DomainInfo& /* di */) {})) {
×
3184
            ret << "refreshed " << id << endl;
×
3185
          }
×
3186
          else {
×
3187
            ret << "failed to refresh " << id << endl;
×
3188
          }
×
3189
        }
×
3190
      }
×
3191
      return ret.str();
×
3192
    }
×
3193
    if (subcmd == "refresh") {
×
3194
      // index refresh domains 12345
3195
      if (argv.size() < 4) {
×
3196
        return "usage: index refresh domains <ID>\n";
×
3197
      }
×
3198

3199
      if (argv[2] != "domains") {
×
3200
        return "can only refresh in the domains index\n";
×
3201
      }
×
3202

3203
      domainid_t id = 0; // NOLINT(readability-identifier-length)
×
3204

3205
      try {
×
3206
        pdns::checked_stoi_into(id, argv[3]);
×
3207
      }
×
3208
      catch (const std::out_of_range& e) {
×
3209
        return "ID out of range\n";
×
3210
      }
×
3211

3212
      if (genChangeDomain(id, [](DomainInfo& /* di */) {})) {
×
3213
        ret << "refreshed" << endl;
×
3214
      }
×
3215
      else {
×
3216
        ret << "failed" << endl;
×
3217
      }
×
3218
      return ret.str();
×
3219
    }
×
3220
  }
×
3221

3222
  return "unknown lmdbbackend command\n";
×
3223
}
×
3224

3225
bool LMDBBackend::hasCreatedLocalFiles() const
3226
{
2,070✔
3227
  // Since the lmdb file creation counter is global, if multiple LMDB backends
3228
  // are used, they may end up all reporting having created files even if
3229
  // not all of them did.
3230
  // But since this information is for the sake of pdnsutil, this is not
3231
  // really a problem.
3232
  return MDBDbi::d_creationCount != 0;
2,070✔
3233
}
2,070✔
3234

3235
class LMDBFactory : public BackendFactory
3236
{
3237
public:
3238
  LMDBFactory() :
3239
    BackendFactory("lmdb") {}
5,861✔
3240
  void declareArguments(const string& suffix = "") override
3241
  {
2,481✔
3242
    declare(suffix, "filename", "Filename for lmdb", "./pdns.lmdb");
2,481✔
3243
    declare(suffix, "sync-mode", "Synchronisation mode: nosync, nometasync, sync", "sync");
2,481✔
3244
    // there just is no room for more on 32 bit
3245
    declare(suffix, "shards", "Records database will be split into this number of shards", (sizeof(void*) == 4) ? "2" : "64");
2,481✔
3246
    declare(suffix, "schema-version", "Maximum allowed schema version to run on this DB. If a lower version is found, auto update is performed", std::to_string(SCHEMAVERSION));
2,481✔
3247
    declare(suffix, "random-ids", "Numeric IDs inside the database are generated randomly instead of sequentially", "no");
2,481✔
3248
    declare(suffix, "map-size", "LMDB map size in megabytes", (sizeof(void*) == 4) ? "100" : "16000");
2,481✔
3249
    declare(suffix, "flag-deleted", "Flag entries on deletion instead of deleting them", "no");
2,481✔
3250
    declare(suffix, "write-notification-update", "Do not update domain table upon notification", "yes");
2,481✔
3251
    declare(suffix, "lightning-stream", "Run in Lightning Stream compatible mode", "no");
2,481✔
3252
  }
2,481✔
3253
  DNSBackend* make(const string& suffix = "") override
3254
  {
4,428✔
3255
    return new LMDBBackend(suffix);
4,428✔
3256
  }
4,428✔
3257
};
3258

3259
/* THIRD PART */
3260

3261
class LMDBLoader
3262
{
3263
public:
3264
  LMDBLoader()
3265
  {
5,861✔
3266
    BackendMakers().report(std::make_unique<LMDBFactory>());
5,861✔
3267
    g_log << Logger::Info << "[lmdbbackend] This is the lmdb backend version " VERSION
5,861✔
3268
#ifndef REPRODUCIBLE
5,861✔
3269
          << " (" __DATE__ " " __TIME__ ")"
5,861✔
3270
#endif
5,861✔
3271
          << " reporting" << endl;
5,861✔
3272
  }
5,861✔
3273
};
3274

3275
static LMDBLoader randomLoader;
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc