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

PowerDNS / pdns / 15468206343

05 Jun 2025 01:23PM UTC coverage: 63.692% (-0.008%) from 63.7%
15468206343

push

github

web-flow
Merge pull request #15607 from miodvallat/too_much_sugar

Try harder matching command names in pdnsutil

42373 of 101390 branches covered (41.79%)

Branch coverage included in aggregate %.

130649 of 170266 relevant lines covered (76.73%)

4341228.82 hits per line

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

74.52
/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
{
1,987✔
70
  // cerr << "getting schema version for path " << filename << endl;
71

72
  uint32_t schemaversion = 0;
1,987✔
73

74
  MDB_env* tmpEnv = nullptr;
1,987✔
75

76
  if (mdb_env_create(&tmpEnv) != 0) {
1,987!
77
    throw std::runtime_error("mdb_env_create failed");
×
78
  }
×
79

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

82
  if (mdb_env_set_mapsize(tmpEnv, 0) != 0) {
1,987!
83
    throw std::runtime_error("mdb_env_set_mapsize failed");
×
84
  }
×
85

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

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

101
  MDB_txn* txn = nullptr;
1,935✔
102

103
  if (mdb_txn_begin(tmpEnv, nullptr, MDB_RDONLY, &txn) != 0) {
1,935!
104
    throw std::runtime_error("mdb_txn_begin failed");
×
105
  }
×
106

107
  MDB_dbi dbi;
1,935✔
108

109
  {
1,935✔
110
    int retCode = MDBDbi::mdb_dbi_open(txn, "pdns", 0, &dbi);
1,935✔
111
    if (retCode != 0) {
1,935!
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");
×
120
    }
×
121
  }
1,935✔
122

123
  MDB_val key, data;
1,935✔
124

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

128
  {
1,935✔
129
    int retCode = mdb_get(txn, dbi, &key, &data);
1,935✔
130
    if (retCode != 0) {
1,935!
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");
×
139
    }
×
140
  }
1,935✔
141

142
  if (data.mv_size == 4) {
1,935✔
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)) {
1,931!
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));
1,931✔
153
    schemaversion = ntohl(schemaversion);
1,931✔
154
  }
1,931✔
155
  else {
×
156
    throw std::runtime_error("pdns.schemaversion had unexpected size");
×
157
  }
×
158

159
  uint32_t shards = 0;
1,935✔
160

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

164
  {
1,935✔
165
    int retCode = mdb_get(txn, dbi, &key, &data);
1,935✔
166
    if (retCode != 0) {
1,935!
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");
×
175
    }
×
176
  }
1,935✔
177

178
  if (data.mv_size == 4) {
1,935✔
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)) {
1,931!
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));
1,931✔
186
    shards = ntohl(shards);
1,931✔
187
  }
1,931✔
188
  else {
×
189
    throw std::runtime_error("pdns.shards had unexpected size");
×
190
  }
×
191

192
  mdb_txn_abort(txn);
1,935✔
193

194
  return {schemaversion, shards};
1,935✔
195
}
1,935✔
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_cursur_open failed");
×
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");
×
235
    }
×
236

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

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

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

255
  MDB_cursor* cur;
8✔
256

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

261
  MDB_val key, data;
8✔
262

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

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

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

271
    uint32_t id;
32✔
272

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

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

279
    id = htonl(id);
32✔
280

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

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

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

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

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

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

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

313
  MDB_cursor* cur;
8✔
314

315
  if ((rc = mdb_cursor_open(txn, sdbi, &cur)) != 0) {
8!
316
    throw std::runtime_error("mdb_cursur_open failed");
×
317
  }
×
318

319
  MDB_val key, data;
8✔
320

321
  rc = mdb_cursor_get(cur, &key, &data, MDB_FIRST);
8✔
322

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

327
    uint32_t id;
32✔
328

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

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

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

340
    MDB_val tkey;
32✔
341
    MDB_val tdata;
32✔
342

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

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

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

359
}
360

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

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

371
  MDB_env* env = nullptr;
2✔
372

373
  if (mdb_env_create(&env) != 0) {
2!
374
    throw std::runtime_error("mdb_env_create failed");
×
375
  }
×
376

377
  if (mdb_env_set_maxdbs(env, 20) != 0) {
2!
378
    mdb_env_close(env);
×
379
    throw std::runtime_error("mdb_env_set_maxdbs failed");
×
380
  }
×
381

382
  if (mdb_env_open(env, filename.c_str(), MDB_NOSUBDIR, 0600) != 0) {
2!
383
    mdb_env_close(env);
×
384
    throw std::runtime_error("mdb_env_open failed");
×
385
  }
×
386

387
  MDB_txn* txn = nullptr;
2✔
388

389
  if (mdb_txn_begin(env, nullptr, 0, &txn) != 0) {
2!
390
    mdb_env_close(env);
×
391
    throw std::runtime_error("mdb_txn_begin failed");
×
392
  }
×
393

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

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

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

415
    if (mdb_env_create(&shenv) != 0) {
4!
416
      throw std::runtime_error("mdb_env_create failed");
×
417
    }
×
418

419
    if (mdb_env_set_maxdbs(shenv, 8) != 0) {
4!
420
      mdb_env_close(env);
×
421
      throw std::runtime_error("mdb_env_set_maxdbs failed");
×
422
    }
×
423

424
    if (mdb_env_open(shenv, shardfile.c_str(), MDB_NOSUBDIR, 0600) != 0) {
4!
425
      mdb_env_close(env);
×
426
      throw std::runtime_error("mdb_env_open failed");
×
427
    }
×
428

429
    MDB_txn* shtxn = nullptr;
4✔
430

431
    if (mdb_txn_begin(shenv, nullptr, 0, &shtxn) != 0) {
4!
432
      mdb_env_close(env);
×
433
      throw std::runtime_error("mdb_txn_begin failed");
×
434
    }
×
435

436
    MDB_dbi shdbi = 0;
4✔
437

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

450
    MDB_dbi shdbi2 = 0;
4✔
451

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

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

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

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

479
  int index = 0;
2✔
480

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

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

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

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

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

519
    index++;
8✔
520
  }
8✔
521

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

525
  index = 0;
2✔
526

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

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

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

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

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

566
    index++;
8✔
567
  }
8✔
568

569
  MDB_dbi dbi = 0;
2✔
570

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

578
  MDB_val key;
2✔
579
  MDB_val data;
2✔
580

581
  std::string header(LMDBLS::LS_MIN_HEADER_SIZE, '\0');
2✔
582

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

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

589
    if (mdb_get(txn, dbi, &key, &data) != 0) {
4!
590
      throw std::runtime_error("mdb_get pdns.shards failed");
×
591
    }
×
592

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

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

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

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

609
    MDB_val tdata;
4✔
610

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

614
    if (mdb_put(txn, dbi, &key, &tdata, 0) != 0) {
4!
615
      throw std::runtime_error("mdb_put failed");
×
616
    }
×
617
  }
4✔
618

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

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

625
    if (mdb_get(txn, dbi, &key, &data) != 0) {
2!
626
      throw std::runtime_error("mdb_get pdns.shards failed");
×
627
    }
×
628

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

631
    std::string stdata = header + sdata;
2✔
632

633
    MDB_val tdata;
2✔
634

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

638
    if (mdb_put(txn, dbi, &key, &tdata, 0) != 0) {
2!
639
      throw std::runtime_error("mdb_put failed");
×
640
    }
×
641
  }
2✔
642

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

650
  cerr << "txn commit=" << mdb_txn_commit(txn) << endl;
2✔
651

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

660
  // throw std::runtime_error("migration done");
661
  cerr << "migration done" << endl;
2✔
662
  // exit(1);
663
  return true;
2✔
664
}
2✔
665

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

675
LMDBBackend::LMDBBackend(const std::string& suffix)
676
{
3,868✔
677
  // overlapping domain ids in combination with relative names are a recipe for disaster
678
  if (!suffix.empty()) {
3,868!
679
    throw std::runtime_error("LMDB backend does not support multiple instances");
×
680
  }
×
681

682
  d_views = ::arg().mustDo("views"); // This is a global setting
3,868✔
683

684
  setArgPrefix("lmdb" + suffix);
3,868✔
685

686
  string syncMode = toLower(getArg("sync-mode"));
3,868✔
687

688
  d_random_ids = mustDo("random-ids");
3,868✔
689

690
  if (syncMode == "nosync")
3,868!
691
    d_asyncFlag = MDB_NOSYNC;
×
692
  else if (syncMode == "nometasync")
3,868!
693
    d_asyncFlag = MDB_NOMETASYNC;
×
694
  else if (syncMode.empty() || syncMode == "sync")
3,868!
695
    d_asyncFlag = 0;
3,868✔
696
  else
×
697
    throw std::runtime_error("Unknown sync mode " + syncMode + " requested for LMDB backend");
×
698

699
  uint64_t mapSize = 0;
3,868✔
700
  try {
3,868✔
701
    mapSize = std::stoll(getArg("map-size"));
3,868✔
702
  }
3,868✔
703
  catch (const std::exception& e) {
3,868✔
704
    throw std::runtime_error(std::string("Unable to parse the 'map-size' LMDB value: ") + e.what());
×
705
  }
×
706

707
  LMDBLS::s_flag_deleted = mustDo("flag-deleted");
3,868✔
708
  d_handle_dups = false;
3,868✔
709

710
  if (mustDo("lightning-stream")) {
3,868!
711
    d_random_ids = true;
×
712
    d_handle_dups = true;
×
713
    LMDBLS::s_flag_deleted = true;
×
714

715
    if (atoi(getArg("shards").c_str()) != 1) {
×
716
      throw std::runtime_error(std::string("running with Lightning Stream support requires shards=1"));
×
717
    }
×
718
  }
×
719

720
  bool opened = false;
3,868✔
721

722
  if (s_first) {
3,868✔
723
    std::lock_guard<std::mutex> l(s_lmdbStartupLock);
1,985✔
724
    if (s_first) {
1,985!
725
      auto filename = getArg("filename");
1,985✔
726

727
      auto currentSchemaVersionAndShards = getSchemaVersionAndShards(filename);
1,985✔
728
      uint32_t currentSchemaVersion = currentSchemaVersionAndShards.first;
1,985✔
729
      // std::cerr<<"current schema version: "<<currentSchemaVersion<<", shards="<<currentSchemaVersionAndShards.second<<std::endl;
730

731
      if (getArgAsNum("schema-version") != SCHEMAVERSION) {
1,985!
732
        throw std::runtime_error("This version of the lmdbbackend only supports schema version 6. Configuration demands a lower version. Not starting up.");
×
733
      }
×
734

735
      if (currentSchemaVersion > 0 && currentSchemaVersion < 3) {
1,985!
736
        throw std::runtime_error("this version of the lmdbbackend can only upgrade from schema v3 and up. Upgrading from older schemas is not supported.");
×
737
      }
×
738

739
      if (currentSchemaVersion == 0) {
1,985✔
740
        // no database is present yet, we can just create them
741
        currentSchemaVersion = 6;
52✔
742
      }
52✔
743

744
      if (currentSchemaVersion == 3 || currentSchemaVersion == 4) {
1,985✔
745
        if (!upgradeToSchemav5(filename)) {
2!
746
          throw std::runtime_error("Failed to perform LMDB schema version upgrade from v4 to v5");
×
747
        }
×
748
        currentSchemaVersion = 5;
2✔
749
      }
2✔
750

751
      if (currentSchemaVersion == 5) {
1,985✔
752
        if (!upgradeToSchemav6(filename)) {
3!
753
          throw std::runtime_error("Failed to perform LMDB schema version upgrade from v5 to v6");
×
754
        }
×
755
        currentSchemaVersion = 6;
3✔
756
      }
3✔
757

758
      if (currentSchemaVersion != 6) {
1,985!
759
        throw std::runtime_error("Somehow, we are not at schema version 6. Giving up");
×
760
      }
×
761

762
      openAllTheDatabases(mapSize);
1,985✔
763
      opened = true;
1,985✔
764
      auto pdnsdbi = d_tdomains->getEnv()->openDB("pdns", MDB_CREATE);
1,985✔
765

766
      auto txn = d_tdomains->getEnv()->getRWTransaction();
1,985✔
767

768
      const auto configShardsTemp = atoi(getArg("shards").c_str());
1,985✔
769
      if (configShardsTemp < 0) {
1,985!
770
        throw std::runtime_error("a negative shards value is not supported");
×
771
      }
×
772
      if (configShardsTemp == 0) {
1,985!
773
        throw std::runtime_error("a shards value of 0 is not supported");
×
774
      }
×
775
      const auto configShards = static_cast<uint32_t>(configShardsTemp);
1,985✔
776

777
      MDBOutVal shards{};
1,985✔
778
      if (txn->get(pdnsdbi, "shards", shards) == 0) {
1,985✔
779
        s_shards = shards.get<uint32_t>();
1,933✔
780

781
        if (mustDo("lightning-stream") && s_shards != 1) {
1,933!
782
          throw std::runtime_error("running with Lightning Stream support enabled requires a database with exactly 1 shard");
×
783
        }
×
784

785
        if (s_shards != configShards) {
1,933!
786
          g_log << Logger::Warning
×
787
                << "Note: configured number of lmdb shards ("
×
788
                << atoi(getArg("shards").c_str())
×
789
                << ") is different from on-disk ("
×
790
                << s_shards
×
791
                << "). Using on-disk shard number"
×
792
                << endl;
×
793
        }
×
794
      }
1,933✔
795
      else {
52✔
796
        s_shards = configShards;
52✔
797
        txn->put(pdnsdbi, "shards", s_shards);
52✔
798
      }
52✔
799

800
      MDBOutVal gotuuid{};
1,985✔
801
      if (txn->get(pdnsdbi, "uuid", gotuuid) != 0) {
1,985✔
802
        const auto uuid = getUniqueID();
52✔
803
        const string uuids(uuid.begin(), uuid.end());
52✔
804
        txn->put(pdnsdbi, "uuid", uuids);
52✔
805
      }
52✔
806

807
      MDBOutVal _schemaversion{};
1,985✔
808
      if (txn->get(pdnsdbi, "schemaversion", _schemaversion) != 0 || _schemaversion.get<uint32_t>() != currentSchemaVersion) {
1,985✔
809
        txn->put(pdnsdbi, "schemaversion", currentSchemaVersion);
55✔
810
      }
55✔
811
      txn->commit();
1,985✔
812

813
      s_first = false;
1,985✔
814
    }
1,985✔
815
  }
1,985✔
816

817
  if (!opened) {
3,868✔
818
    openAllTheDatabases(mapSize);
1,883✔
819
  }
1,883✔
820
  d_trecords.resize(s_shards);
3,868✔
821
  d_dolog = ::arg().mustDo("query-logging");
3,868✔
822
}
3,868✔
823

824
LMDBBackend::~LMDBBackend()
825
{
3,744✔
826
  // LMDB internals require that, if we have multiple transactions active,
827
  // we destroy them in the reverse order of their creation, thus we can't
828
  // let the default destructor take care of d_rotxn and d_rwtxn.
829
  if (d_txnorder) {
3,744✔
830
    // RO transaction more recent than RW transaction
831
    d_rotxn.reset();
798✔
832
    d_rwtxn.reset();
798✔
833
  }
798✔
834
  else {
2,946✔
835
    // RW transaction more recent than RO transaction
836
    d_rwtxn.reset();
2,946✔
837
    d_rotxn.reset();
2,946✔
838
  }
2,946✔
839
}
3,744✔
840

841
void LMDBBackend::openAllTheDatabases(uint64_t mapSize)
842
{
3,868✔
843
  d_tdomains = std::make_shared<tdomains_t>(getMDBEnv(getArg("filename").c_str(), MDB_NOSUBDIR | MDB_NORDAHEAD | d_asyncFlag, 0600, mapSize), "domains_v5");
3,868✔
844
  d_tmeta = std::make_shared<tmeta_t>(d_tdomains->getEnv(), "metadata_v5");
3,868✔
845
  d_tkdb = std::make_shared<tkdb_t>(d_tdomains->getEnv(), "keydata_v5");
3,868✔
846
  d_ttsig = std::make_shared<ttsig_t>(d_tdomains->getEnv(), "tsig_v5");
3,868✔
847
  d_tnetworks = d_tdomains->getEnv()->openDB("networks_v6", MDB_CREATE);
3,868✔
848
  d_tviews = d_tdomains->getEnv()->openDB("views_v6", MDB_CREATE);
3,868✔
849
}
3,868✔
850

851
unsigned int LMDBBackend::getCapabilities()
852
{
12,102✔
853
  unsigned int caps = CAP_DNSSEC | CAP_DIRECT | CAP_LIST | CAP_CREATE;
12,102✔
854
  if (d_views) {
12,102✔
855
    caps |= CAP_VIEWS;
10,619✔
856
  }
10,619✔
857
  return caps;
12,102✔
858
}
12,102✔
859

860
namespace boost
861
{
862
namespace serialization
863
{
864

865
  template <class Archive>
866
  void save(Archive& ar, const DNSName& g, const unsigned int /* version */)
867
  {
3,313✔
868
    if (g.empty()) {
3,313✔
869
      ar& std::string();
924✔
870
    }
924✔
871
    else {
2,389✔
872
      ar & g.toDNSStringLC();
2,389✔
873
    }
2,389✔
874
  }
3,313✔
875

876
  template <class Archive>
877
  void load(Archive& ar, DNSName& g, const unsigned int /* version */)
878
  {
288,474✔
879
    string tmp;
288,474✔
880
    ar & tmp;
288,474✔
881
    if (tmp.empty()) {
288,474✔
882
      g = DNSName();
130,635✔
883
    }
130,635✔
884
    else {
157,839✔
885
      g = DNSName(tmp.c_str(), tmp.size(), 0, false);
157,839✔
886
    }
157,839✔
887
  }
288,474✔
888

889
  template <class Archive>
890
  void save(Archive& arc, const ZoneName& zone, const unsigned int /* version */)
891
  {
3,259✔
892
    arc & zone.operator const DNSName&();
3,259✔
893
    arc & zone.getVariant();
3,259✔
894
  }
3,259✔
895

896
  template <class Archive>
897
  void load(Archive& arc, ZoneName& zone, const unsigned int version)
898
  {
288,147✔
899
    if (version == 0) { // for schemas up to 5, ZoneName serialized as DNSName
288,147!
900
      std::string tmp{};
×
901
      arc & tmp;
×
902
      if (tmp.empty()) {
×
903
        zone = ZoneName();
×
904
      }
×
905
      else {
×
906
        zone = ZoneName(DNSName(tmp.c_str(), tmp.size(), 0, false));
×
907
      }
×
908
      return;
×
909
    }
×
910
    DNSName tmp;
288,147✔
911
    std::string variant{};
288,147✔
912
    arc & tmp;
288,147✔
913
    arc & variant;
288,147✔
914
    zone = ZoneName(tmp, variant);
288,147✔
915
  }
288,147✔
916

917
  template <class Archive>
918
  void save(Archive& ar, const DomainInfo& g, const unsigned int /* version */)
919
  {
1,292✔
920
    ar & g.zone;
1,292✔
921
    ar & g.last_check;
1,292✔
922
    ar & g.account;
1,292✔
923
    ar & g.primaries;
1,292✔
924
    ar& static_cast<uint32_t>(g.id);
1,292✔
925
    ar & g.notified_serial;
1,292✔
926
    ar & g.kind;
1,292✔
927
    ar & g.options;
1,292✔
928
    ar & g.catalog;
1,292✔
929
  }
1,292✔
930

931
  template <class Archive>
932
  void load(Archive& ar, DomainInfo& g, const unsigned int version)
933
  {
141,642✔
934
    if (version >= 2) {
141,642✔
935
      ar & g.zone;
141,546✔
936
    }
141,546✔
937
    else {
96✔
938
      DNSName tmp;
96✔
939
      ar & tmp;
96✔
940
      new (&g.zone) ZoneName(tmp);
96✔
941
    }
96✔
942
    ar & g.last_check;
141,642✔
943
    ar & g.account;
141,642✔
944
    ar & g.primaries;
141,642✔
945
    uint32_t domainId{0};
141,642✔
946
    ar & domainId;
141,642✔
947
    g.id = static_cast<domainid_t>(domainId);
141,642✔
948
    ar & g.notified_serial;
141,642✔
949
    ar & g.kind;
141,642✔
950
    switch (version) {
141,642✔
951
    case 0:
94✔
952
      // These fields did not exist.
953
      g.options.clear();
94✔
954
      g.catalog.clear();
94✔
955
      break;
94✔
956
    case 1:
2✔
957
      // These fields did exist, but catalog as DNSName only.
958
      ar & g.options;
2✔
959
      {
2✔
960
        DNSName tmp;
2✔
961
        ar & tmp;
2✔
962
        g.catalog = ZoneName(tmp);
2✔
963
      }
2✔
964
      break;
2✔
965
    default:
141,546✔
966
      // These fields exist, with catalog as ZoneName.
967
      ar & g.options;
141,546✔
968
      ar & g.catalog;
141,546✔
969
      break;
141,546✔
970
    }
141,642✔
971
  }
141,642✔
972

973
  template <class Archive>
974
  void serialize(Archive& ar, LMDBBackend::DomainMeta& g, const unsigned int /* version */)
975
  {
4,673✔
976
    ar & g.domain & g.key & g.value;
4,673✔
977
  }
4,673✔
978

979
  template <class Archive>
980
  void save(Archive& ar, const LMDBBackend::KeyDataDB& g, const unsigned int /* version */)
981
  {
236✔
982
    ar & g.domain & g.content & g.flags & g.active & g.published;
236✔
983
  }
236✔
984

985
  template <class Archive>
986
  void load(Archive& ar, LMDBBackend::KeyDataDB& g, const unsigned int version)
987
  {
819✔
988
    ar & g.domain & g.content & g.flags & g.active;
819✔
989
    if (version >= 1) {
819!
990
      ar & g.published;
819✔
991
    }
819✔
992
    else {
×
993
      g.published = true;
×
994
    }
×
995
  }
819✔
996

997
  template <class Archive>
998
  void serialize(Archive& ar, TSIGKey& g, const unsigned int /* version */)
999
  {
141✔
1000
    ar & g.name;
141✔
1001
    ar & g.algorithm; // this is the ordername
141✔
1002
    ar & g.key;
141✔
1003
  }
141✔
1004

1005
} // namespace serialization
1006
} // namespace boost
1007

1008
BOOST_SERIALIZATION_SPLIT_FREE(DNSName);
1009
BOOST_SERIALIZATION_SPLIT_FREE(ZoneName);
1010
BOOST_SERIALIZATION_SPLIT_FREE(LMDBBackend::KeyDataDB);
1011
BOOST_SERIALIZATION_SPLIT_FREE(DomainInfo);
1012
BOOST_IS_BITWISE_SERIALIZABLE(ComboAddress);
1013

1014
template <>
1015
std::string serializeToBuffer(const LMDBBackend::LMDBResourceRecord& value)
1016
{
874,571✔
1017
  std::string buffer;
874,571✔
1018

1019
  // Data size of the resource record.
1020
  uint16_t len = value.content.length();
874,571✔
1021

1022
  // Reserve space to store the size of the resource record + the content of the resource
1023
  // record + a few other things.
1024
  buffer.reserve(sizeof(len) + len + sizeof(value.ttl) + sizeof(value.auth) + sizeof(value.disabled) + sizeof(value.ordername));
874,571✔
1025

1026
  // Store the size of the resource record.
1027
  // NOLINTNEXTLINE.
1028
  buffer.assign((const char*)&len, sizeof(len));
874,571✔
1029

1030
  // Store the contents of the resource record.
1031
  buffer += value.content;
874,571✔
1032

1033
  // The few other things.
1034
  // NOLINTNEXTLINE.
1035
  buffer.append((const char*)&value.ttl, sizeof(value.ttl));
874,571✔
1036
  buffer.append(1, (char)value.auth);
874,571✔
1037
  buffer.append(1, (char)value.disabled);
874,571✔
1038
  buffer.append(1, (char)value.ordername);
874,571✔
1039

1040
  return buffer;
874,571✔
1041
}
874,571✔
1042

1043
template <>
1044
std::string serializeToBuffer(const vector<LMDBBackend::LMDBResourceRecord>& value)
1045
{
122,134✔
1046
  std::string ret;
122,134✔
1047
  for (const auto& lrr : value) {
122,889✔
1048
    ret += serializeToBuffer(lrr);
122,889✔
1049
  }
122,889✔
1050
  return ret;
122,134✔
1051
}
122,134✔
1052

1053
static inline size_t deserializeRRFromBuffer(const string_view& str, LMDBBackend::LMDBResourceRecord& lrr)
1054
{
545,493✔
1055
  uint16_t len;
545,493✔
1056
  memcpy(&len, &str[0], 2);
545,493✔
1057
  lrr.content.assign(&str[2], len); // len bytes
545,493✔
1058
  memcpy(&lrr.ttl, &str[2] + len, 4);
545,493✔
1059
  lrr.auth = str[2 + len + 4];
545,493✔
1060
  lrr.disabled = str[2 + len + 4 + 1];
545,493✔
1061
  lrr.ordername = str[2 + len + 4 + 2];
545,493✔
1062
  lrr.wildcardname.clear();
545,493✔
1063

1064
  return 2 + len + 7;
545,493✔
1065
}
545,493✔
1066

1067
template <>
1068
void deserializeFromBuffer(const string_view& buffer, LMDBBackend::LMDBResourceRecord& value)
1069
{
20,368✔
1070
  deserializeRRFromBuffer(buffer, value);
20,368✔
1071
}
20,368✔
1072

1073
template <>
1074
void deserializeFromBuffer(const string_view& buffer, vector<LMDBBackend::LMDBResourceRecord>& value)
1075
{
518,561✔
1076
  auto str_copy = buffer;
518,561✔
1077
  while (str_copy.size() >= 9) { // minimum length for a record is 10
1,043,685✔
1078
    LMDBBackend::LMDBResourceRecord lrr;
525,124✔
1079
    auto rrLength = deserializeRRFromBuffer(str_copy, lrr);
525,124✔
1080
    value.emplace_back(lrr);
525,124✔
1081
    str_copy.remove_prefix(rrLength);
525,124✔
1082
  }
525,124✔
1083
}
518,561✔
1084

1085
static std::string serializeContent(uint16_t qtype, const DNSName& domain, const std::string& content)
1086
{
428,306✔
1087
  auto drc = DNSRecordContent::make(qtype, QClass::IN, content);
428,306✔
1088
  return drc->serialize(domain, false);
428,306✔
1089
}
428,306✔
1090

1091
static std::shared_ptr<DNSRecordContent> deserializeContentZR(uint16_t qtype, const DNSName& qname, const std::string& content)
1092
{
400,702✔
1093
  if (qtype == QType::A && content.size() == 4) {
400,702!
1094
    return std::make_shared<ARecordContent>(*((uint32_t*)content.c_str()));
386,255✔
1095
  }
386,255✔
1096
  return DNSRecordContent::deserialize(qname, qtype, content, QClass::IN, true);
14,447✔
1097
}
400,702✔
1098

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

1102
   The index we use is "zoneid,canonical relative name". This index is also used
1103
   for AXFR.
1104

1105
   Note - domain_id, name and type are ONLY present on the index!
1106
*/
1107

1108
#if BOOST_VERSION >= 106100
1109
#define StringView string_view
1110
#else
1111
#define StringView string
1112
#endif
1113

1114
void LMDBBackend::deleteDomainRecords(RecordsRWTransaction& txn, domainid_t domain_id, uint16_t qtype)
1115
{
566✔
1116
  compoundOrdername co;
566✔
1117
  string match = co(domain_id);
566✔
1118

1119
  auto cursor = txn.txn->getCursor(txn.db->dbi);
566✔
1120
  MDBOutVal key, val;
566✔
1121
  //  cout<<"Match: "<<makeHexDump(match);
1122
  if (!cursor.lower_bound(match, key, val)) {
566✔
1123
    while (key.getNoStripHeader<StringView>().rfind(match, 0) == 0) {
756✔
1124
      if (qtype == QType::ANY || co.getQType(key.getNoStripHeader<StringView>()) == qtype)
732!
1125
        cursor.del();
732✔
1126
      if (cursor.next(key, val))
732✔
1127
        break;
28✔
1128
    }
732✔
1129
  }
52✔
1130
}
566✔
1131

1132
/* Here's the complicated story. Other backends have just one transaction, which is either
1133
   on or not.
1134

1135
   You can't call feedRecord without a transaction started with startTransaction.
1136

1137
   However, other functions can be called after startTransaction() or without startTransaction()
1138
     (like updateDNSSECOrderNameAndAuth)
1139

1140

1141

1142
*/
1143

1144
bool LMDBBackend::startTransaction(const ZoneName& domain, domainid_t domain_id)
1145
{
1,155✔
1146
  // cout <<"startTransaction("<<domain<<", "<<domain_id<<")"<<endl;
1147
  domainid_t real_id = domain_id;
1,155✔
1148
  if (real_id == UnknownDomainID) {
1,155✔
1149
    auto rotxn = d_tdomains->getROTransaction();
589✔
1150
    DomainInfo di;
589✔
1151
    real_id = rotxn.get<0>(domain, di);
589✔
1152
    // cout<<"real_id = "<<real_id << endl;
1153
    if (!real_id)
589!
1154
      return false;
×
1155
  }
589✔
1156
  if (d_rwtxn) {
1,155!
1157
    throw DBException("Attempt to start a transaction while one was open already");
×
1158
  }
×
1159
  d_rwtxn = getRecordsRWTransaction(real_id);
1,155✔
1160
  d_txnorder = false;
1,155✔
1161

1162
  d_transactiondomain = domain;
1,155✔
1163
  d_transactiondomainid = real_id;
1,155✔
1164
  if (domain_id != UnknownDomainID) {
1,155✔
1165
    LMDBBackend::deleteDomainRecords(*d_rwtxn, domain_id);
566✔
1166
  }
566✔
1167

1168
  return true;
1,155✔
1169
}
1,155✔
1170

1171
bool LMDBBackend::commitTransaction()
1172
{
1,072✔
1173
  // cout<<"Commit transaction" <<endl;
1174
  if (!d_rwtxn) {
1,072!
1175
    throw DBException("Attempt to commit a transaction while there isn't one open");
×
1176
  }
×
1177

1178
  d_rwtxn->txn->commit();
1,072✔
1179
  d_rwtxn.reset();
1,072✔
1180
  return true;
1,072✔
1181
}
1,072✔
1182

1183
bool LMDBBackend::abortTransaction()
1184
{
51✔
1185
  // cout<<"Abort transaction"<<endl;
1186
  if (!d_rwtxn) {
51!
1187
    throw DBException("Attempt to abort a transaction while there isn't one open");
×
1188
  }
×
1189

1190
  d_rwtxn->txn->abort();
51✔
1191
  d_rwtxn.reset();
51✔
1192

1193
  return true;
51✔
1194
}
51✔
1195

1196
// d_rwtxn must be set here
1197
bool LMDBBackend::feedRecord(const DNSResourceRecord& r, const DNSName& ordername, bool ordernameIsNSEC3)
1198
{
427,960✔
1199
  LMDBResourceRecord lrr(r);
427,960✔
1200
  lrr.qname.makeUsRelative(d_transactiondomain);
427,960✔
1201
  lrr.content = serializeContent(lrr.qtype.getCode(), r.qname, lrr.content);
427,960✔
1202

1203
  compoundOrdername co;
427,960✔
1204
  string matchName = co(lrr.domain_id, lrr.qname, lrr.qtype.getCode());
427,960✔
1205

1206
  string rrs;
427,960✔
1207
  MDBOutVal _rrs;
427,960✔
1208
  if (!d_rwtxn->txn->get(d_rwtxn->db->dbi, matchName, _rrs)) {
427,960✔
1209
    rrs = _rrs.get<string>();
22,425✔
1210
  }
22,425✔
1211

1212
  rrs += serializeToBuffer(lrr);
427,960✔
1213

1214
  d_rwtxn->txn->put(d_rwtxn->db->dbi, matchName, rrs);
427,960✔
1215

1216
  if (ordernameIsNSEC3 && !ordername.empty()) {
427,960!
1217
    MDBOutVal val;
40,787✔
1218
    if (d_rwtxn->txn->get(d_rwtxn->db->dbi, co(lrr.domain_id, lrr.qname, QType::NSEC3), val)) {
40,787✔
1219
      lrr.ttl = 0;
40,391✔
1220
      lrr.content = lrr.qname.toDNSStringLC();
40,391✔
1221
      lrr.auth = 0;
40,391✔
1222
      string ser = serializeToBuffer(lrr);
40,391✔
1223
      d_rwtxn->txn->put(d_rwtxn->db->dbi, co(lrr.domain_id, ordername, QType::NSEC3), ser);
40,391✔
1224

1225
      lrr.ttl = 1;
40,391✔
1226
      lrr.content = ordername.toDNSString();
40,391✔
1227
      ser = serializeToBuffer(lrr);
40,391✔
1228
      d_rwtxn->txn->put(d_rwtxn->db->dbi, co(lrr.domain_id, lrr.qname, QType::NSEC3), ser);
40,391✔
1229
    }
40,391✔
1230
  }
40,787✔
1231
  return true;
427,960✔
1232
}
427,960✔
1233

1234
bool LMDBBackend::feedEnts(domainid_t domain_id, map<DNSName, bool>& nonterm)
1235
{
22✔
1236
  LMDBResourceRecord lrr;
22✔
1237
  lrr.ttl = 0;
22✔
1238
  compoundOrdername co;
22✔
1239
  for (const auto& nt : nonterm) {
96✔
1240
    lrr.qname = nt.first.makeRelative(d_transactiondomain);
96✔
1241
    lrr.auth = nt.second;
96✔
1242
    lrr.ordername = true;
96✔
1243

1244
    std::string ser = serializeToBuffer(lrr);
96✔
1245
    d_rwtxn->txn->put(d_rwtxn->db->dbi, co(domain_id, lrr.qname, QType::ENT), ser);
96✔
1246
  }
96✔
1247
  return true;
22✔
1248
}
22✔
1249

1250
bool LMDBBackend::feedEnts3(domainid_t domain_id, const DNSName& domain, map<DNSName, bool>& nonterm, const NSEC3PARAMRecordContent& ns3prc, bool narrow)
1251
{
18✔
1252
  string ser;
18✔
1253
  DNSName ordername;
18✔
1254
  LMDBResourceRecord lrr;
18✔
1255
  compoundOrdername co;
18✔
1256
  for (const auto& nt : nonterm) {
92✔
1257
    lrr.qname = nt.first.makeRelative(domain);
92✔
1258
    lrr.ttl = 0;
92✔
1259
    lrr.auth = nt.second;
92✔
1260
    lrr.ordername = nt.second;
92✔
1261
    ser = serializeToBuffer(lrr);
92✔
1262
    d_rwtxn->txn->put(d_rwtxn->db->dbi, co(domain_id, lrr.qname, QType::ENT), ser);
92✔
1263

1264
    if (!narrow && lrr.auth) {
92!
1265
      lrr.content = lrr.qname.toDNSString();
80✔
1266
      lrr.auth = false;
80✔
1267
      lrr.ordername = false;
80✔
1268
      ser = serializeToBuffer(lrr);
80✔
1269

1270
      ordername = DNSName(toBase32Hex(hashQNameWithSalt(ns3prc, nt.first)));
80✔
1271
      d_rwtxn->txn->put(d_rwtxn->db->dbi, co(domain_id, ordername, QType::NSEC3), ser);
80✔
1272

1273
      lrr.ttl = 1;
80✔
1274
      lrr.content = ordername.toDNSString();
80✔
1275
      ser = serializeToBuffer(lrr);
80✔
1276
      d_rwtxn->txn->put(d_rwtxn->db->dbi, co(domain_id, lrr.qname, QType::NSEC3), ser);
80✔
1277
    }
80✔
1278
  }
92✔
1279
  return true;
18✔
1280
}
18✔
1281

1282
// might be called within a transaction, might also be called alone
1283
// NOLINTNEXTLINE(readability-identifier-length)
1284
bool LMDBBackend::replaceRRSet(domainid_t domain_id, const DNSName& qname, const QType& qt, const vector<DNSResourceRecord>& rrset)
1285
{
333✔
1286
  // zonk qname/qtype within domain_id (go through qname, check domain_id && qtype)
1287
  shared_ptr<RecordsRWTransaction> txn;
333✔
1288
  bool needCommit = false;
333✔
1289
  if (d_rwtxn && d_transactiondomainid == domain_id) {
333!
1290
    txn = d_rwtxn;
333✔
1291
    //    cout<<"Reusing open transaction"<<endl;
1292
  }
333✔
1293
  else {
×
1294
    //    cout<<"Making a new RW txn for replace rrset"<<endl;
1295
    txn = getRecordsRWTransaction(domain_id);
×
1296
    needCommit = true;
×
1297
  }
×
1298

1299
  DomainInfo di;
333✔
1300
  if (!d_tdomains->getROTransaction().get(domain_id, di)) {
333!
1301
    return false;
×
1302
  }
×
1303

1304
  compoundOrdername co;
333✔
1305
  auto cursor = txn->txn->getCursor(txn->db->dbi);
333✔
1306
  MDBOutVal key, val;
333✔
1307
  string match = co(domain_id, qname.makeRelative(di.zone), qt.getCode());
333✔
1308
  if (!cursor.find(match, key, val)) {
333✔
1309
    cursor.del();
133✔
1310
  }
133✔
1311

1312
  if (!rrset.empty()) {
333✔
1313
    vector<LMDBResourceRecord> adjustedRRSet;
331✔
1314
    adjustedRRSet.reserve(rrset.size());
331✔
1315
    for (const auto& rr : rrset) {
346✔
1316
      LMDBResourceRecord lrr(rr);
346✔
1317
      lrr.content = serializeContent(lrr.qtype.getCode(), lrr.qname, lrr.content);
346✔
1318
      lrr.qname.makeUsRelative(di.zone);
346✔
1319

1320
      adjustedRRSet.emplace_back(lrr);
346✔
1321
    }
346✔
1322
    txn->txn->put(txn->db->dbi, match, serializeToBuffer(adjustedRRSet));
331✔
1323
  }
331✔
1324

1325
  if (needCommit)
333!
1326
    txn->txn->commit();
×
1327

1328
  return true;
333✔
1329
}
333✔
1330

1331
// NOLINTNEXTLINE(readability-identifier-length)
1332
bool LMDBBackend::replaceComments([[maybe_unused]] domainid_t domain_id, [[maybe_unused]] const DNSName& qname, [[maybe_unused]] const QType& qt, const vector<Comment>& comments)
1333
{
2✔
1334
  // if the vector is empty, good, that's what we do here (LMDB does not store comments)
1335
  // if it's not, report failure
1336
  return comments.empty();
2✔
1337
}
2✔
1338

1339
// FIXME: this is not very efficient
1340
static DNSName keyUnconv(std::string& instr)
1341
{
182✔
1342
  // instr is now com0example0
1343
  vector<string> labels;
182✔
1344
  boost::split(labels, instr, [](char chr) { return chr == '\0'; });
2,549✔
1345

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

1349
  if (labels.size() == 1 && labels[0].empty()) {
182!
1350
    // this is the root
1351
    return g_rootdnsname;
5✔
1352
  }
5✔
1353

1354
  DNSName tmp;
177✔
1355

1356
  for (auto const& label : labels) {
389✔
1357
    tmp.appendRawLabel(label);
389✔
1358
  }
389✔
1359
  return tmp.labelReverse();
177✔
1360
}
182✔
1361

1362
static std::string makeBadDataExceptionMessage(const std::string& where, std::exception& exc, MDBOutVal& key, MDBOutVal& val)
1363
{
×
1364
  ostringstream msg;
×
1365
  msg << "during " << where << ", got exception (" << exc.what() << "), ";
×
1366
  msg << "key: " << makeHexDump(key.getNoStripHeader<string>()) << ", ";
×
1367
  msg << "value: " << makeHexDump(val.get<string>());
×
1368

1369
  return msg.str();
×
1370
}
×
1371

1372
void LMDBBackend::viewList(vector<string>& result)
1373
{
43✔
1374
  auto txn = d_tdomains->getEnv()->getROTransaction();
43✔
1375

1376
  auto cursor = txn->getROCursor(d_tviews);
43✔
1377

1378
  MDBOutVal key{}; // <view, dnsname>
43✔
1379
  MDBOutVal val{}; // <variant>
43✔
1380

1381
  auto ret = cursor.first(key, val);
43✔
1382

1383
  if (ret == MDB_NOTFOUND) {
43✔
1384
    return;
11✔
1385
  }
11✔
1386

1387
  do {
82✔
1388
    string view;
82✔
1389
    string zone;
82✔
1390
    try {
82✔
1391
      std::tie(view, zone) = splitField(key.getNoStripHeader<string>(), '\x0');
82✔
1392
      auto variant = val.get<string>();
82✔
1393
      result.push_back(view);
82✔
1394
    }
82✔
1395
    catch (std::exception& e) {
82✔
1396
      throw PDNSException(makeBadDataExceptionMessage("viewList", e, key, val));
×
1397
    }
×
1398

1399
    string inkey{view + string(1, (char)1)};
82✔
1400
    MDBInVal bound{inkey};
82✔
1401
    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✔
1402
  } while (ret != MDB_NOTFOUND);
82✔
1403
}
32✔
1404

1405
void LMDBBackend::viewListZones(const string& inview, vector<ZoneName>& result)
1406
{
84✔
1407
  result.clear();
84✔
1408

1409
  auto txn = d_tdomains->getEnv()->getROTransaction();
84✔
1410

1411
  auto cursor = txn->getROCursor(d_tviews);
84✔
1412

1413
  string inkey{inview + string(1, (char)0)};
84✔
1414
  MDBInVal prefix{inkey};
84✔
1415
  MDBOutVal key{}; // <view, dnsname>
84✔
1416
  MDBOutVal val{}; // <variant>
84✔
1417

1418
  auto ret = cursor.prefix(prefix, key, val);
84✔
1419

1420
  if (ret == MDB_NOTFOUND) {
84✔
1421
    return;
2✔
1422
  }
2✔
1423

1424
  do {
182✔
1425
    try {
182✔
1426
      auto [view, _zone] = splitField(key.getNoStripHeader<string>(), '\x0');
182✔
1427
      auto variant = val.get<string>();
182✔
1428
      auto zone = keyUnconv(_zone);
182✔
1429
      result.emplace_back(ZoneName(zone, variant));
182✔
1430
    }
182✔
1431
    catch (std::exception& e) {
182✔
1432
      throw PDNSException(makeBadDataExceptionMessage("viewListZones", e, key, val));
×
1433
    }
×
1434

1435
    ret = cursor.next(key, val);
182✔
1436
  } while (ret != MDB_NOTFOUND);
182✔
1437
}
82✔
1438

1439
// TODO: make this add-or-del to reduce code duplication?
1440
bool LMDBBackend::viewAddZone(const string& view, const ZoneName& zone)
1441
{
192✔
1442
  auto txn = d_tdomains->getEnv()->getRWTransaction();
192✔
1443

1444
  string key = view + string(1, (char)0) + keyConv(zone.operator const DNSName&());
192✔
1445
  string val = zone.getVariant(); // variant goes here
192✔
1446

1447
  txn->put(d_tviews, key, val);
192✔
1448
  txn->commit();
192✔
1449

1450
  return true;
192✔
1451
}
192✔
1452

1453
bool LMDBBackend::viewDelZone(const string& view, const ZoneName& zone)
1454
{
12✔
1455
  auto txn = d_tdomains->getEnv()->getRWTransaction();
12✔
1456

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

1460
  txn->del(d_tviews, key);
12✔
1461
  txn->commit();
12✔
1462

1463
  return true;
12✔
1464
}
12✔
1465

1466
bool LMDBBackend::networkSet(const Netmask& net, std::string& view)
1467
{
86✔
1468
  auto txn = d_tdomains->getEnv()->getRWTransaction();
86✔
1469

1470
  if (view.empty()) {
86✔
1471
    txn->del(d_tnetworks, net.toByteString());
1✔
1472
  }
1✔
1473
  else {
85✔
1474
    txn->put(d_tnetworks, net.toByteString(), view);
85✔
1475
  }
85✔
1476
  txn->commit();
86✔
1477

1478
  return true;
86✔
1479
}
86✔
1480

1481
bool LMDBBackend::networkList(vector<pair<Netmask, string>>& networks)
1482
{
33✔
1483
  networks.clear();
33✔
1484

1485
  auto txn = d_tdomains->getEnv()->getROTransaction();
33✔
1486

1487
  auto cursor = txn->getROCursor(d_tnetworks);
33✔
1488

1489
  MDBOutVal netval{};
33✔
1490
  MDBOutVal viewval{};
33✔
1491

1492
  auto ret = cursor.first(netval, viewval);
33✔
1493

1494
  if (ret == MDB_NOTFOUND) {
33✔
1495
    return true;
11✔
1496
  }
11✔
1497

1498
  do {
52✔
1499
    try {
52✔
1500
      auto net = Netmask(netval.getNoStripHeader<string>(), Netmask::byteString);
52✔
1501
      auto view = viewval.get<string>();
52✔
1502
      networks.emplace_back(std::make_pair(net, view));
52✔
1503
    }
52✔
1504
    catch (std::exception& e) {
52✔
1505
      throw PDNSException(makeBadDataExceptionMessage("networkList", e, netval, viewval));
×
1506
    }
×
1507

1508
    ret = cursor.next(netval, viewval);
52✔
1509
  } while (ret != MDB_NOTFOUND);
52✔
1510

1511
  return true;
22✔
1512
}
22✔
1513

1514
// tempting to templatize these two functions but the pain is not worth it
1515
// NOLINTNEXTLINE(readability-identifier-length)
1516
std::shared_ptr<LMDBBackend::RecordsRWTransaction> LMDBBackend::getRecordsRWTransaction(domainid_t id)
1517
{
1,155✔
1518
  auto& shard = d_trecords[id % s_shards];
1,155✔
1519
  if (!shard.env) {
1,155✔
1520
    shard.env = getMDBEnv((getArg("filename") + "-" + std::to_string(id % s_shards)).c_str(),
129✔
1521
                          MDB_NOSUBDIR | MDB_NORDAHEAD | d_asyncFlag, 0600);
129✔
1522
    shard.dbi = shard.env->openDB("records_v5", MDB_CREATE);
129✔
1523
  }
129✔
1524
  auto ret = std::make_shared<RecordsRWTransaction>(shard.env->getRWTransaction());
1,155✔
1525
  ret->db = std::make_shared<RecordsDB>(shard);
1,155✔
1526

1527
  return ret;
1,155✔
1528
}
1,155✔
1529

1530
// NOLINTNEXTLINE(readability-identifier-length)
1531
std::shared_ptr<LMDBBackend::RecordsROTransaction> LMDBBackend::getRecordsROTransaction(domainid_t id, const std::shared_ptr<LMDBBackend::RecordsRWTransaction>& rwtxn)
1532
{
18,780✔
1533
  auto& shard = d_trecords[id % s_shards];
18,780✔
1534
  if (!shard.env) {
18,780✔
1535
    if (rwtxn) {
4,268!
1536
      throw DBException("attempting to start nested transaction without open parent env");
×
1537
    }
×
1538
    shard.env = getMDBEnv((getArg("filename") + "-" + std::to_string(id % s_shards)).c_str(),
4,268✔
1539
                          MDB_NOSUBDIR | MDB_NORDAHEAD | d_asyncFlag, 0600);
4,268✔
1540
    shard.dbi = shard.env->openDB("records_v5", MDB_CREATE);
4,268✔
1541
  }
4,268✔
1542

1543
  if (rwtxn) {
18,780✔
1544
    auto ret = std::make_shared<RecordsROTransaction>(rwtxn->txn->getROTransaction());
1,033✔
1545
    ret->db = std::make_shared<RecordsDB>(shard);
1,033✔
1546
    return ret;
1,033✔
1547
  }
1,033✔
1548
  else {
17,747✔
1549
    auto ret = std::make_shared<RecordsROTransaction>(shard.env->getROTransaction());
17,747✔
1550
    ret->db = std::make_shared<RecordsDB>(shard);
17,747✔
1551
    return ret;
17,747✔
1552
  }
17,747✔
1553
}
18,780✔
1554

1555
bool LMDBBackend::deleteDomain(const ZoneName& domain)
1556
{
34✔
1557
  if (!d_rwtxn) {
34!
1558
    throw DBException(std::string(__PRETTY_FUNCTION__) + " called without a transaction");
×
1559
  }
×
1560

1561
  int transactionDomainId = d_transactiondomainid;
34✔
1562
  ZoneName transactionDomain = d_transactiondomain;
34✔
1563

1564
  abortTransaction();
34✔
1565

1566
  LmdbIdVec idvec;
34✔
1567

1568
  if (!d_handle_dups) {
34!
1569
    // get domain id
1570
    auto txn = d_tdomains->getROTransaction();
34✔
1571

1572
    DomainInfo di;
34✔
1573
    idvec.push_back(txn.get<0>(domain, di));
34✔
1574
  }
34✔
1575
  else {
×
1576
    // this transaction used to be RO.
1577
    // it is now RW to narrow a race window between PowerDNS and Lightning Stream
1578
    // FIXME: turn the entire delete, including this ID scan, into one RW transaction
1579
    // when doing that, first do a short RO check to see if we actually have anything to delete
1580
    auto txn = d_tdomains->getRWTransaction();
×
1581

1582
    txn.get_multi<0>(domain, idvec);
×
1583
  }
×
1584

1585
  for (auto id : idvec) {
34✔
1586

1587
    startTransaction(domain, id);
34✔
1588

1589
    { // Remove metadata
34✔
1590
      auto txn = d_tmeta->getRWTransaction();
34✔
1591
      LmdbIdVec ids;
34✔
1592

1593
      txn.get_multi<0>(domain, ids);
34✔
1594

1595
      for (auto& _id : ids) {
36✔
1596
        txn.del(_id);
10✔
1597
      }
10✔
1598

1599
      txn.commit();
34✔
1600
    }
34✔
1601

1602
    { // Remove cryptokeys
34✔
1603
      auto txn = d_tkdb->getRWTransaction();
34✔
1604
      LmdbIdVec ids;
34✔
1605
      txn.get_multi<0>(domain, ids);
34✔
1606

1607
      for (auto _id : ids) {
34!
1608
        txn.del(_id);
×
1609
      }
×
1610

1611
      txn.commit();
34✔
1612
    }
34✔
1613

1614
    // Remove records
1615
    commitTransaction();
34✔
1616

1617
    // Remove zone
1618
    auto txn = d_tdomains->getRWTransaction();
34✔
1619
    txn.del(id);
34✔
1620
    txn.commit();
34✔
1621
  }
34✔
1622

1623
  startTransaction(transactionDomain, transactionDomainId);
34✔
1624

1625
  return true;
34✔
1626
}
34✔
1627

1628
bool LMDBBackend::list(const ZoneName& target, domainid_t /* id */, bool include_disabled)
1629
{
805✔
1630
  d_includedisabled = include_disabled;
805✔
1631

1632
  DomainInfo di;
805✔
1633
  {
805✔
1634
    auto dtxn = d_tdomains->getROTransaction();
805✔
1635
    if ((di.id = dtxn.get<0>(target, di))) {
805!
1636
      // cerr << "Found domain " << target << " on domain_id " << di.id << ", list requested " << id << endl;
1637
    }
805✔
1638
    else {
×
1639
      // cerr << "Did not find " << target << endl;
1640
      return false;
×
1641
    }
×
1642
  }
805✔
1643

1644
  d_rotxn = getRecordsROTransaction(di.id, d_rwtxn);
805✔
1645
  d_txnorder = true;
805✔
1646
  d_getcursor = std::make_shared<MDBROCursor>(d_rotxn->txn->getCursor(d_rotxn->db->dbi));
805✔
1647

1648
  compoundOrdername co;
805✔
1649
  d_matchkey = co(di.id);
805✔
1650

1651
  MDBOutVal key, val;
805✔
1652
  if (d_getcursor->prefix(d_matchkey, key, val) != 0) {
805✔
1653
    d_getcursor.reset();
11✔
1654
  }
11✔
1655

1656
  d_lookupdomain = target;
805✔
1657

1658
  // Make sure we start with fresh data
1659
  d_currentrrset.clear();
805✔
1660
  d_currentrrsetpos = 0;
805✔
1661

1662
  return true;
805✔
1663
}
805✔
1664

1665
void LMDBBackend::lookupInternal(const QType& type, const DNSName& qdomain, domainid_t zoneId, DNSPacket* /* p */, bool include_disabled)
1666
{
6,275✔
1667
  if (d_dolog) {
6,275!
1668
    g_log << Logger::Warning << "Got lookup for " << qdomain << "|" << type.toString() << " in zone " << zoneId << endl;
×
1669
    d_dtime.set();
×
1670
  }
×
1671

1672
  d_includedisabled = include_disabled;
6,275✔
1673

1674
  ZoneName hunt(qdomain);
6,275✔
1675
  DomainInfo di;
6,275✔
1676
  if (zoneId == UnknownDomainID) {
6,275✔
1677
    auto rotxn = d_tdomains->getROTransaction();
812✔
1678

1679
    do {
812✔
1680
      zoneId = rotxn.get<0>(hunt, di);
812✔
1681
    } while (zoneId == 0 && type != QType::SOA && hunt.chopOff());
812!
1682
    if (zoneId == 0) {
812!
1683
      //      cout << "Did not find zone for "<< qdomain<<endl;
1684
      d_getcursor.reset();
×
1685
      return;
×
1686
    }
×
1687
  }
812✔
1688
  else {
5,463✔
1689
    if (!d_tdomains->getROTransaction().get(zoneId, di)) {
5,463✔
1690
      // cout<<"Could not find a zone with id "<<zoneId<<endl;
1691
      d_getcursor.reset();
8✔
1692
      return;
8✔
1693
    }
8✔
1694
    hunt = di.zone;
5,455✔
1695
  }
5,455✔
1696

1697
  DNSName relqname = qdomain.makeRelative(hunt);
6,267✔
1698
  if (relqname.empty()) {
6,267!
1699
    return;
×
1700
  }
×
1701
  // cout<<"get will look for "<<relqname<< " in zone "<<hunt<<" with id "<<zoneId<<" and type "<<type.toString()<<endl;
1702
  d_rotxn = getRecordsROTransaction(zoneId, d_rwtxn);
6,267✔
1703
  d_txnorder = true;
6,267✔
1704

1705
  compoundOrdername co;
6,267✔
1706
  d_getcursor = std::make_shared<MDBROCursor>(d_rotxn->txn->getCursor(d_rotxn->db->dbi));
6,267✔
1707
  MDBOutVal key, val;
6,267✔
1708
  if (type.getCode() == QType::ANY) {
6,267✔
1709
    d_matchkey = co(zoneId, relqname);
4,681✔
1710
  }
4,681✔
1711
  else {
1,586✔
1712
    d_matchkey = co(zoneId, relqname, type.getCode());
1,586✔
1713
  }
1,586✔
1714

1715
  if (d_getcursor->prefix(d_matchkey, key, val) != 0) {
6,267✔
1716
    d_getcursor.reset();
2,378✔
1717
    if (d_dolog) {
2,378!
1718
      g_log << Logger::Warning << "Query " << ((long)(void*)this) << ": " << d_dtime.udiffNoReset() << " us to execute (found nothing)" << endl;
×
1719
    }
×
1720
    return;
2,378✔
1721
  }
2,378✔
1722

1723
  if (d_dolog) {
3,889!
1724
    g_log << Logger::Warning << "Query " << ((long)(void*)this) << ": " << d_dtime.udiffNoReset() << " us to execute" << endl;
×
1725
  }
×
1726

1727
  d_lookupdomain = std::move(hunt);
3,889✔
1728

1729
  // Make sure we start with fresh data
1730
  d_currentrrset.clear();
3,889✔
1731
  d_currentrrsetpos = 0;
3,889✔
1732
}
3,889✔
1733

1734
bool LMDBBackend::get(DNSZoneRecord& zr)
1735
{
407,781✔
1736
  for (;;) {
513,840✔
1737
    // std::cerr<<"d_getcursor="<<d_getcursor<<std::endl;
1738
    if (!d_getcursor) {
513,840✔
1739
      d_rotxn.reset();
7,078✔
1740
      return false;
7,078✔
1741
    }
7,078✔
1742

1743
    string_view key;
506,762✔
1744

1745
    if (d_currentrrset.empty()) {
506,762✔
1746
      d_getcursor->current(d_currentKey, d_currentVal);
501,323✔
1747

1748
      key = d_currentKey.getNoStripHeader<string_view>();
501,323✔
1749
      zr.dr.d_type = compoundOrdername::getQType(key).getCode();
501,323✔
1750

1751
      if (zr.dr.d_type == QType::NSEC3) {
501,323✔
1752
        // Hit a magic NSEC3 skipping
1753
        if (d_getcursor->next(d_currentKey, d_currentVal) != 0) {
106,054✔
1754
          // cerr<<"resetting d_getcursor 1"<<endl;
1755
          d_getcursor.reset();
1,566✔
1756
        }
1,566✔
1757
        continue;
106,054✔
1758
      }
106,054✔
1759

1760
      deserializeFromBuffer(d_currentVal.get<string_view>(), d_currentrrset);
395,269✔
1761
      d_currentrrsetpos = 0;
395,269✔
1762
    }
395,269✔
1763
    else {
5,439✔
1764
      key = d_currentKey.getNoStripHeader<string_view>();
5,439✔
1765
    }
5,439✔
1766
    try {
400,708✔
1767
      const auto& lrr = d_currentrrset.at(d_currentrrsetpos++);
400,708✔
1768

1769
      zr.disabled = lrr.disabled;
400,708✔
1770
      if (!zr.disabled || d_includedisabled) {
400,708✔
1771
        zr.dr.d_name = compoundOrdername::getQName(key) + d_lookupdomain.operator const DNSName&();
400,702✔
1772
        zr.domain_id = compoundOrdername::getDomainID(key);
400,702✔
1773
        zr.dr.d_type = compoundOrdername::getQType(key).getCode();
400,702✔
1774
        zr.dr.d_ttl = lrr.ttl;
400,702✔
1775
        zr.dr.setContent(deserializeContentZR(zr.dr.d_type, zr.dr.d_name, lrr.content));
400,702✔
1776
        zr.auth = lrr.auth;
400,702✔
1777
      }
400,702✔
1778

1779
      if (d_currentrrsetpos >= d_currentrrset.size()) {
400,708✔
1780
        d_currentrrset.clear(); // will invalidate lrr
395,271✔
1781
        if (d_getcursor->next(d_currentKey, d_currentVal) != 0) {
395,271✔
1782
          // cerr<<"resetting d_getcursor 2"<<endl;
1783
          d_getcursor.reset();
3,116✔
1784
        }
3,116✔
1785
      }
395,271✔
1786

1787
      if (zr.disabled && !d_includedisabled) {
400,708✔
1788
        continue;
5✔
1789
      }
5✔
1790
    }
400,708✔
1791
    catch (const std::exception& e) {
400,708✔
1792
      throw PDNSException(e.what());
×
1793
    }
×
1794

1795
    break;
400,702✔
1796
  }
400,708✔
1797

1798
  return true;
400,702✔
1799
}
407,781✔
1800

1801
bool LMDBBackend::get(DNSResourceRecord& rr)
1802
{
309,245✔
1803
  DNSZoneRecord zr;
309,245✔
1804
  if (!get(zr)) {
309,245✔
1805
    return false;
2,215✔
1806
  }
2,215✔
1807

1808
  rr.qname = zr.dr.d_name;
307,030✔
1809
  rr.ttl = zr.dr.d_ttl;
307,030✔
1810
  rr.qtype = zr.dr.d_type;
307,030✔
1811
  rr.content = zr.dr.getContent()->getZoneRepresentation(true);
307,030✔
1812
  rr.domain_id = zr.domain_id;
307,030✔
1813
  rr.auth = zr.auth;
307,030✔
1814
  rr.disabled = zr.disabled;
307,030✔
1815

1816
  return true;
307,030✔
1817
}
309,245✔
1818

1819
bool LMDBBackend::getSerial(DomainInfo& di)
1820
{
3,336✔
1821
  auto txn = getRecordsROTransaction(di.id);
3,336✔
1822
  compoundOrdername co;
3,336✔
1823
  MDBOutVal val;
3,336✔
1824
  if (!txn->txn->get(txn->db->dbi, co(di.id, g_rootdnsname, QType::SOA), val)) {
3,336✔
1825
    LMDBResourceRecord lrr;
2,625✔
1826
    deserializeFromBuffer(val.get<string_view>(), lrr);
2,625✔
1827
    if (lrr.content.size() >= 5 * sizeof(uint32_t)) {
2,625!
1828
      uint32_t serial;
2,625✔
1829
      // a SOA has five 32 bit fields, the first of which is the serial
1830
      // there are two variable length names before the serial, so we calculate from the back
1831
      memcpy(&serial, &lrr.content[lrr.content.size() - (5 * sizeof(uint32_t))], sizeof(serial));
2,625✔
1832
      di.serial = ntohl(serial);
2,625✔
1833
    }
2,625✔
1834
    return !lrr.disabled;
2,625✔
1835
  }
2,625✔
1836
  return false;
711✔
1837
}
3,336✔
1838

1839
bool LMDBBackend::getDomainInfo(const ZoneName& domain, DomainInfo& info, bool getserial)
1840
{
3,126✔
1841
  // If caller asks about a zone with variant, but views are not enabled,
1842
  // punt.
1843
  if (domain.hasVariant() && !d_views) {
3,126!
1844
    return false;
×
1845
  }
×
1846

1847
  {
3,126✔
1848
    auto txn = d_tdomains->getROTransaction();
3,126✔
1849
    // auto range = txn.prefix_range<0>(domain);
1850

1851
    // bool found = false;
1852

1853
    // for (auto& iter = range.first ; iter != range.second; ++iter) {
1854
    //   found = true;
1855
    //   info.id = iter.getID();
1856
    //   info.backend = this;
1857
    // }
1858

1859
    // if (!found) {
1860
    //   return false;
1861
    // }
1862
    if ((info.id = txn.get<0>(domain, info)) == 0) {
3,126✔
1863
      return false;
513✔
1864
    }
513✔
1865

1866
    info.backend = this;
2,613✔
1867
  }
2,613✔
1868

1869
  if (getserial) {
2,613✔
1870
    getSerial(info);
1,934✔
1871
  }
1,934✔
1872

1873
  return true;
2,613✔
1874
}
3,126✔
1875

1876
int LMDBBackend::genChangeDomain(const ZoneName& domain, const std::function<void(DomainInfo&)>& func)
1877
{
703✔
1878
  auto txn = d_tdomains->getRWTransaction();
703✔
1879

1880
  DomainInfo di;
703✔
1881

1882
  auto id = txn.get<0>(domain, di);
703✔
1883
  func(di);
703✔
1884
  txn.put(di, id);
703✔
1885

1886
  txn.commit();
703✔
1887
  return true;
703✔
1888
}
703✔
1889

1890
// NOLINTNEXTLINE(readability-identifier-length)
1891
int LMDBBackend::genChangeDomain(domainid_t id, const std::function<void(DomainInfo&)>& func)
1892
{
91✔
1893
  DomainInfo di;
91✔
1894

1895
  auto txn = d_tdomains->getRWTransaction();
91✔
1896

1897
  if (!txn.get(id, di))
91!
1898
    return false;
×
1899

1900
  func(di);
91✔
1901

1902
  txn.put(di, id);
91✔
1903

1904
  txn.commit();
91✔
1905
  return true;
91✔
1906
}
91✔
1907

1908
bool LMDBBackend::setKind(const ZoneName& domain, const DomainInfo::DomainKind kind)
1909
{
292✔
1910
  return genChangeDomain(domain, [kind](DomainInfo& di) {
292✔
1911
    di.kind = kind;
292✔
1912
  });
292✔
1913
}
292✔
1914

1915
bool LMDBBackend::setAccount(const ZoneName& domain, const std::string& account)
1916
{
1✔
1917
  return genChangeDomain(domain, [account](DomainInfo& di) {
1✔
1918
    di.account = account;
1✔
1919
  });
1✔
1920
}
1✔
1921

1922
bool LMDBBackend::setPrimaries(const ZoneName& domain, const vector<ComboAddress>& primaries)
1923
{
71✔
1924
  return genChangeDomain(domain, [&primaries](DomainInfo& di) {
71✔
1925
    di.primaries = primaries;
71✔
1926
  });
71✔
1927
}
71✔
1928

1929
bool LMDBBackend::createDomain(const ZoneName& domain, const DomainInfo::DomainKind kind, const vector<ComboAddress>& primaries, const string& account)
1930
{
498✔
1931
  DomainInfo di;
498✔
1932

1933
  {
498✔
1934
    auto txn = d_tdomains->getRWTransaction();
498✔
1935
    if (txn.get<0>(domain, di)) {
498!
1936
      throw DBException("Domain '" + domain.toLogString() + "' exists already");
×
1937
    }
×
1938

1939
    di.zone = domain;
498✔
1940
    di.kind = kind;
498✔
1941
    di.primaries = primaries;
498✔
1942
    di.account = account;
498✔
1943

1944
    txn.put(di, 0, d_random_ids);
498✔
1945
    txn.commit();
498✔
1946
  }
498✔
1947

1948
  return true;
×
1949
}
498✔
1950

1951
void LMDBBackend::getAllDomainsFiltered(vector<DomainInfo>* domains, const std::function<bool(DomainInfo&)>& allow)
1952
{
142✔
1953
  auto txn = d_tdomains->getROTransaction();
142✔
1954
  if (d_handle_dups) {
142!
1955
    map<ZoneName, DomainInfo> zonemap;
×
1956
    set<ZoneName> dups;
×
1957

1958
    for (auto iter = txn.begin(); iter != txn.end(); ++iter) {
×
1959
      DomainInfo di = *iter;
×
1960
      di.id = iter.getID();
×
1961
      di.backend = this;
×
1962

1963
      if (!zonemap.emplace(di.zone, di).second) {
×
1964
        dups.insert(di.zone);
×
1965
      }
×
1966
    }
×
1967

1968
    for (const auto& zone : dups) {
×
1969
      DomainInfo di;
×
1970

1971
      // this get grabs the oldest item if there are duplicates
1972
      di.id = txn.get<0>(zone, di);
×
1973

1974
      if (di.id == 0) {
×
1975
        // .get actually found nothing for us
1976
        continue;
×
1977
      }
×
1978

1979
      di.backend = this;
×
1980
      zonemap[di.zone] = di;
×
1981
    }
×
1982

1983
    for (auto& [k, v] : zonemap) {
×
1984
      if (allow(v)) {
×
1985
        domains->push_back(std::move(v));
×
1986
      }
×
1987
    }
×
1988
  }
×
1989
  else {
142✔
1990
    for (auto iter = txn.begin(); iter != txn.end(); ++iter) {
1,931✔
1991
      DomainInfo di = *iter;
1,789✔
1992
      di.id = iter.getID();
1,789✔
1993
      di.backend = this;
1,789✔
1994

1995
      if (allow(di)) {
1,789✔
1996
        domains->push_back(di);
1,434✔
1997
      }
1,434✔
1998
    }
1,789✔
1999
  }
142✔
2000
}
142✔
2001

2002
void LMDBBackend::getAllDomains(vector<DomainInfo>* domains, bool /* doSerial */, bool include_disabled)
2003
{
115✔
2004
  getAllDomainsFiltered(domains, [this, include_disabled](DomainInfo& di) {
1,402✔
2005
    if (!getSerial(di) && !include_disabled) {
1,402!
2006
      return false;
×
2007
    }
×
2008

2009
    // Skip domains with variants if views are disabled.
2010
    if (di.zone.hasVariant() && !d_views) {
1,402!
2011
      return false;
×
2012
    }
×
2013

2014
    return true;
1,402✔
2015
  });
1,402✔
2016
}
115✔
2017

2018
void LMDBBackend::getUnfreshSecondaryInfos(vector<DomainInfo>* domains)
2019
{
11✔
2020
  uint32_t serial;
11✔
2021
  time_t now = time(0);
11✔
2022
  LMDBResourceRecord lrr;
11✔
2023
  soatimes st;
11✔
2024

2025
  getAllDomainsFiltered(domains, [this, &lrr, &st, &now, &serial](DomainInfo& di) {
83✔
2026
    if (!di.isSecondaryType()) {
83!
2027
      return false;
×
2028
    }
×
2029

2030
    auto txn2 = getRecordsROTransaction(di.id);
83✔
2031
    compoundOrdername co;
83✔
2032
    MDBOutVal val;
83✔
2033
    if (!txn2->txn->get(txn2->db->dbi, co(di.id, g_rootdnsname, QType::SOA), val)) {
83✔
2034
      deserializeFromBuffer(val.get<string_view>(), lrr);
51✔
2035
      memcpy(&st, &lrr.content[lrr.content.size() - sizeof(soatimes)], sizeof(soatimes));
51✔
2036
      if ((time_t)(di.last_check + ntohl(st.refresh)) > now) { // still fresh
51!
2037
        return false;
51✔
2038
      }
51✔
2039
      serial = ntohl(st.serial);
×
2040
    }
×
2041
    else {
32✔
2042
      serial = 0;
32✔
2043
    }
32✔
2044

2045
    return true;
32✔
2046
  });
83✔
2047
}
11✔
2048

2049
void LMDBBackend::setStale(domainid_t domain_id)
2050
{
2✔
2051
  genChangeDomain(domain_id, [](DomainInfo& di) {
2✔
2052
    di.last_check = 0;
2✔
2053
  });
2✔
2054
}
2✔
2055

2056
void LMDBBackend::setFresh(domainid_t domain_id)
2057
{
88✔
2058
  genChangeDomain(domain_id, [](DomainInfo& di) {
88✔
2059
    di.last_check = time(nullptr);
88✔
2060
  });
88✔
2061
}
88✔
2062

2063
void LMDBBackend::getUpdatedPrimaries(vector<DomainInfo>& updatedDomains, std::unordered_set<DNSName>& catalogs, CatalogHashMap& catalogHashes)
2064
{
×
2065
  CatalogInfo ci;
×
2066

2067
  getAllDomainsFiltered(&(updatedDomains), [this, &catalogs, &catalogHashes, &ci](DomainInfo& di) {
×
2068
    if (!di.isPrimaryType()) {
×
2069
      return false;
×
2070
    }
×
2071

2072
    if (di.kind == DomainInfo::Producer) {
×
2073
      catalogs.insert(di.zone.operator const DNSName&());
×
2074
      catalogHashes[di.zone].process("\0");
×
2075
      return false; // Producer fresness check is performed elsewhere
×
2076
    }
×
2077

2078
    if (!di.catalog.empty()) {
×
2079
      ci.fromJson(di.options, CatalogInfo::CatalogType::Producer);
×
2080
      ci.updateHash(catalogHashes, di);
×
2081
    }
×
2082

2083
    if (getSerial(di) && di.serial != di.notified_serial) {
×
2084
      di.backend = this;
×
2085
      return true;
×
2086
    }
×
2087

2088
    return false;
×
2089
  });
×
2090
}
×
2091

2092
void LMDBBackend::setNotified(domainid_t domain_id, uint32_t serial)
2093
{
1✔
2094
  genChangeDomain(domain_id, [serial](DomainInfo& di) {
1✔
2095
    di.notified_serial = serial;
1✔
2096
  });
1✔
2097
}
1✔
2098

2099
class getCatalogMembersReturnFalseException : std::runtime_error
2100
{
2101
public:
2102
  getCatalogMembersReturnFalseException() :
2103
    std::runtime_error("getCatalogMembers should return false") {}
×
2104
};
2105

2106
bool LMDBBackend::getCatalogMembers(const ZoneName& catalog, vector<CatalogInfo>& members, CatalogInfo::CatalogType type)
2107
{
16✔
2108
  vector<DomainInfo> scratch;
16✔
2109

2110
  try {
16✔
2111
    getAllDomainsFiltered(&scratch, [&catalog, &members, &type](DomainInfo& di) {
304✔
2112
      if ((type == CatalogInfo::CatalogType::Producer && di.kind != DomainInfo::Primary) || (type == CatalogInfo::CatalogType::Consumer && di.kind != DomainInfo::Secondary) || di.catalog != catalog) {
304✔
2113
        return false;
100✔
2114
      }
100✔
2115

2116
      CatalogInfo ci;
204✔
2117
      ci.d_id = di.id;
204✔
2118
      ci.d_zone = di.zone;
204✔
2119
      ci.d_primaries = di.primaries;
204✔
2120
      try {
204✔
2121
        ci.fromJson(di.options, type);
204✔
2122
      }
204✔
2123
      catch (const std::runtime_error& e) {
204✔
2124
        g_log << Logger::Warning << __PRETTY_FUNCTION__ << " options '" << di.options << "' for zone '" << di.zone << "' is no valid JSON: " << e.what() << endl;
×
2125
        members.clear();
×
2126
        throw getCatalogMembersReturnFalseException();
×
2127
      }
×
2128
      members.emplace_back(ci);
204✔
2129

2130
      return false;
204✔
2131
    });
204✔
2132
  }
16✔
2133
  catch (const getCatalogMembersReturnFalseException& e) {
16✔
2134
    return false;
×
2135
  }
×
2136
  return true;
16✔
2137
}
16✔
2138

2139
bool LMDBBackend::setOptions(const ZoneName& domain, const std::string& options)
2140
{
96✔
2141
  return genChangeDomain(domain, [options](DomainInfo& di) {
96✔
2142
    di.options = options;
96✔
2143
  });
96✔
2144
}
96✔
2145

2146
bool LMDBBackend::setCatalog(const ZoneName& domain, const ZoneName& catalog)
2147
{
243✔
2148
  return genChangeDomain(domain, [catalog](DomainInfo& di) {
243✔
2149
    di.catalog = catalog;
243✔
2150
  });
243✔
2151
}
243✔
2152

2153
bool LMDBBackend::getAllDomainMetadata(const ZoneName& name, std::map<std::string, std::vector<std::string>>& meta)
2154
{
3,908✔
2155
  meta.clear();
3,908✔
2156
  auto txn = d_tmeta->getROTransaction();
3,908✔
2157
  LmdbIdVec ids;
3,908✔
2158
  txn.get_multi<0>(name, ids);
3,908✔
2159

2160
  DomainMeta dm;
3,908✔
2161
  // cerr<<"getAllDomainMetadata start"<<endl;
2162
  for (auto id : ids) {
4,259✔
2163
    if (txn.get(id, dm)) {
3,816!
2164
      meta[dm.key].push_back(dm.value);
3,816✔
2165
    }
3,816✔
2166
  }
3,816✔
2167
  return true;
3,908✔
2168
}
3,908✔
2169

2170
bool LMDBBackend::setDomainMetadata(const ZoneName& name, const std::string& kind, const std::vector<std::string>& meta)
2171
{
564✔
2172
  auto txn = d_tmeta->getRWTransaction();
564✔
2173

2174
  LmdbIdVec ids;
564✔
2175
  txn.get_multi<0>(name, ids);
564✔
2176

2177
  DomainMeta dmeta;
564✔
2178
  for (auto id : ids) {
564✔
2179
    if (txn.get(id, dmeta)) {
376!
2180
      if (dmeta.key == kind) {
376✔
2181
        // cerr<<"delete"<<endl;
2182
        txn.del(id);
32✔
2183
      }
32✔
2184
    }
376✔
2185
  }
376✔
2186

2187
  for (const auto& m : meta) {
564✔
2188
    DomainMeta dm{name, kind, m};
439✔
2189
    txn.put(dm, 0, d_random_ids);
439✔
2190
  }
439✔
2191
  txn.commit();
564✔
2192
  return true;
564✔
2193
}
564✔
2194

2195
bool LMDBBackend::getDomainKeys(const ZoneName& name, std::vector<KeyData>& keys)
2196
{
1,218✔
2197
  auto txn = d_tkdb->getROTransaction();
1,218✔
2198
  LmdbIdVec ids;
1,218✔
2199
  txn.get_multi<0>(name, ids);
1,218✔
2200

2201
  KeyDataDB key;
1,218✔
2202

2203
  for (auto id : ids) {
1,218✔
2204
    if (txn.get(id, key)) {
701!
2205
      KeyData kd{key.content, id, key.flags, key.active, key.published};
701✔
2206
      keys.emplace_back(std::move(kd));
701✔
2207
    }
701✔
2208
  }
701✔
2209

2210
  return true;
1,218✔
2211
}
1,218✔
2212

2213
bool LMDBBackend::removeDomainKey(const ZoneName& name, unsigned int keyId)
2214
{
29✔
2215
  auto txn = d_tkdb->getRWTransaction();
29✔
2216
  KeyDataDB kdb;
29✔
2217
  if (txn.get(keyId, kdb)) {
29✔
2218
    if (kdb.domain == name) {
20!
2219
      txn.del(keyId);
20✔
2220
      txn.commit();
20✔
2221
      return true;
20✔
2222
    }
20✔
2223
  }
20✔
2224
  // cout << "??? wanted to remove domain key for domain "<<name<<" with id "<<keyId<<", could not find it"<<endl;
2225
  return true;
9✔
2226
}
29✔
2227

2228
bool LMDBBackend::addDomainKey(const ZoneName& name, const KeyData& key, int64_t& keyId)
2229
{
210✔
2230
  auto txn = d_tkdb->getRWTransaction();
210✔
2231
  KeyDataDB kdb{name, key.content, key.flags, key.active, key.published};
210✔
2232
  keyId = txn.put(kdb, 0, d_random_ids);
210✔
2233
  txn.commit();
210✔
2234

2235
  return true;
210✔
2236
}
210✔
2237

2238
bool LMDBBackend::activateDomainKey(const ZoneName& name, unsigned int keyId)
2239
{
6✔
2240
  auto txn = d_tkdb->getRWTransaction();
6✔
2241
  KeyDataDB kdb;
6✔
2242
  if (txn.get(keyId, kdb)) {
6!
2243
    if (kdb.domain == name) {
6!
2244
      txn.modify(keyId, [](KeyDataDB& kdbarg) {
6✔
2245
        kdbarg.active = true;
6✔
2246
      });
6✔
2247
      txn.commit();
6✔
2248
      return true;
6✔
2249
    }
6✔
2250
  }
6✔
2251

2252
  // cout << "??? wanted to activate domain key for domain "<<name<<" with id "<<keyId<<", could not find it"<<endl;
2253
  return true;
×
2254
}
6✔
2255

2256
bool LMDBBackend::deactivateDomainKey(const ZoneName& name, unsigned int keyId)
2257
{
4✔
2258
  auto txn = d_tkdb->getRWTransaction();
4✔
2259
  KeyDataDB kdb;
4✔
2260
  if (txn.get(keyId, kdb)) {
4!
2261
    if (kdb.domain == name) {
4!
2262
      txn.modify(keyId, [](KeyDataDB& kdbarg) {
4✔
2263
        kdbarg.active = false;
4✔
2264
      });
4✔
2265
      txn.commit();
4✔
2266
      return true;
4✔
2267
    }
4✔
2268
  }
4✔
2269
  // cout << "??? wanted to deactivate domain key for domain "<<name<<" with id "<<keyId<<", could not find it"<<endl;
2270
  return true;
×
2271
}
4✔
2272

2273
bool LMDBBackend::publishDomainKey(const ZoneName& name, unsigned int keyId)
2274
{
6✔
2275
  auto txn = d_tkdb->getRWTransaction();
6✔
2276
  KeyDataDB kdb;
6✔
2277
  if (txn.get(keyId, kdb)) {
6!
2278
    if (kdb.domain == name) {
6!
2279
      txn.modify(keyId, [](KeyDataDB& kdbarg) {
6✔
2280
        kdbarg.published = true;
6✔
2281
      });
6✔
2282
      txn.commit();
6✔
2283
      return true;
6✔
2284
    }
6✔
2285
  }
6✔
2286

2287
  // cout << "??? wanted to hide domain key for domain "<<name<<" with id "<<keyId<<", could not find it"<<endl;
2288
  return true;
×
2289
}
6✔
2290

2291
bool LMDBBackend::unpublishDomainKey(const ZoneName& name, unsigned int keyId)
2292
{
10✔
2293
  auto txn = d_tkdb->getRWTransaction();
10✔
2294
  KeyDataDB kdb;
10✔
2295
  if (txn.get(keyId, kdb)) {
10!
2296
    if (kdb.domain == name) {
10!
2297
      txn.modify(keyId, [](KeyDataDB& kdbarg) {
10✔
2298
        kdbarg.published = false;
10✔
2299
      });
10✔
2300
      txn.commit();
10✔
2301
      return true;
10✔
2302
    }
10✔
2303
  }
10✔
2304
  // cout << "??? wanted to unhide domain key for domain "<<name<<" with id "<<keyId<<", could not find it"<<endl;
2305
  return true;
×
2306
}
10✔
2307

2308
// NOLINTNEXTLINE(readability-function-cognitive-complexity,readability-identifier-length)
2309
bool LMDBBackend::getBeforeAndAfterNamesAbsolute(domainid_t id, const DNSName& qname, DNSName& unhashed, DNSName& before, DNSName& after)
2310
{
5,866✔
2311
  //  cout << __PRETTY_FUNCTION__<< ": "<<id <<", "<<qname << " " << unhashed<<endl;
2312

2313
  DomainInfo di;
5,866✔
2314
  if (!d_tdomains->getROTransaction().get(id, di)) {
5,866!
2315
    // domain does not exist, tough luck
2316
    return false;
×
2317
  }
×
2318
  // cout <<"Zone: "<<di.zone<<endl;
2319

2320
  compoundOrdername co;
5,866✔
2321
  auto txn = getRecordsROTransaction(id);
5,866✔
2322

2323
  auto cursor = txn->txn->getCursor(txn->db->dbi);
5,866✔
2324
  MDBOutVal key, val;
5,866✔
2325

2326
  LMDBResourceRecord lrr;
5,866✔
2327

2328
  string matchkey = co(id, qname, QType::NSEC3);
5,866✔
2329
  if (cursor.lower_bound(matchkey, key, val)) {
5,866✔
2330
    // this is beyond the end of the database
2331
    // cout << "Beyond end of database!" << endl;
2332
    cursor.last(key, val);
175✔
2333

2334
    for (;;) {
755✔
2335
      if (co.getDomainID(key.getNoStripHeader<StringView>()) != id) {
755!
2336
        // cout<<"Last record also not part of this zone!"<<endl;
2337
        //  this implies something is wrong in the database, nothing we can do
2338
        return false;
×
2339
      }
×
2340

2341
      if (co.getQType(key.getNoStripHeader<StringView>()) == QType::NSEC3) {
755✔
2342
        deserializeFromBuffer(val.get<StringView>(), lrr);
302✔
2343
        if (!lrr.ttl) // the kind of NSEC3 we need
302✔
2344
          break;
175✔
2345
      }
302✔
2346
      if (cursor.prev(key, val)) {
580!
2347
        // hit beginning of database, again means something is wrong with it
2348
        return false;
×
2349
      }
×
2350
    }
580✔
2351
    before = co.getQName(key.getNoStripHeader<StringView>());
175✔
2352
    unhashed = DNSName(lrr.content.c_str(), lrr.content.size(), 0, false) + di.zone.operator const DNSName&();
175✔
2353

2354
    // now to find after .. at the beginning of the zone
2355
    if (cursor.lower_bound(co(id), key, val)) {
175!
2356
      // cout<<"hit end of zone find when we shouldn't"<<endl;
2357
      return false;
×
2358
    }
×
2359
    for (;;) {
967✔
2360
      if (co.getQType(key.getNoStripHeader<StringView>()) == QType::NSEC3) {
967✔
2361
        deserializeFromBuffer(val.get<StringView>(), lrr);
378✔
2362
        if (!lrr.ttl)
378✔
2363
          break;
175✔
2364
      }
378✔
2365

2366
      if (cursor.next(key, val) || co.getDomainID(key.getNoStripHeader<StringView>()) != id) {
792!
2367
        // cout<<"hit end of zone or database when we shouldn't"<<endl;
2368
        return false;
×
2369
      }
×
2370
    }
792✔
2371
    after = co.getQName(key.getNoStripHeader<StringView>());
175✔
2372
    // cout<<"returning: before="<<before<<", after="<<after<<", unhashed: "<<unhashed<<endl;
2373
    return true;
175✔
2374
  }
175✔
2375

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

2378
  before = co.getQName(key.getNoStripHeader<StringView>());
5,691✔
2379
  if (before == qname) {
5,691✔
2380
    // cout << "Ended up on exact right node" << endl;
2381
    before = co.getQName(key.getNoStripHeader<StringView>());
3,206✔
2382
    // unhashed should be correct now, maybe check?
2383
    if (cursor.next(key, val)) {
3,206✔
2384
      // xxx should find first hash now
2385

2386
      if (cursor.lower_bound(co(id), key, val)) {
125!
2387
        // cout<<"hit end of zone find when we shouldn't for id "<<id<< __LINE__<<endl;
2388
        return false;
×
2389
      }
×
2390
      for (;;) {
630✔
2391
        if (co.getQType(key.getNoStripHeader<StringView>()) == QType::NSEC3) {
630✔
2392
          deserializeFromBuffer(val.get<StringView>(), lrr);
250✔
2393
          if (!lrr.ttl)
250✔
2394
            break;
125✔
2395
        }
250✔
2396

2397
        if (cursor.next(key, val) || co.getDomainID(key.getNoStripHeader<StringView>()) != id) {
505!
2398
          // cout<<"hit end of zone or database when we shouldn't" << __LINE__<<endl;
2399
          return false;
×
2400
        }
×
2401
      }
505✔
2402
      after = co.getQName(key.getNoStripHeader<StringView>());
125✔
2403
      // cout<<"returning: before="<<before<<", after="<<after<<", unhashed: "<<unhashed<<endl;
2404
      return true;
125✔
2405
    }
125✔
2406
  }
3,206✔
2407
  else {
2,485✔
2408
    // cout <<"Going backwards to find 'before'"<<endl;
2409
    int count = 0;
2,485✔
2410
    for (;;) {
6,527✔
2411
      if (co.getQName(key.getNoStripHeader<StringView>()).canonCompare(qname) && co.getQType(key.getNoStripHeader<StringView>()) == QType::NSEC3) {
6,527✔
2412
        // cout<<"Potentially stopping traverse at "<< co.getQName(key.get<StringView>()) <<", " << (co.getQName(key.get<StringView>()).canonCompare(qname))<<endl;
2413
        // cout<<"qname = "<<qname<<endl;
2414
        // cout<<"here  = "<<co.getQName(key.get<StringView>())<<endl;
2415
        deserializeFromBuffer(val.get<StringView>(), lrr);
2,930✔
2416
        if (!lrr.ttl)
2,930✔
2417
          break;
2,359✔
2418
      }
2,930✔
2419

2420
      if (cursor.prev(key, val) || co.getDomainID(key.getNoStripHeader<StringView>()) != id) {
4,168✔
2421
        // cout <<"XXX Hit *beginning* of zone or database"<<endl;
2422
        // this can happen, must deal with it
2423
        // should now find the last hash of the zone
2424

2425
        if (cursor.lower_bound(co(id + 1), key, val)) {
126!
2426
          // cout << "Could not find the next higher zone, going to the end of the database then"<<endl;
2427
          cursor.last(key, val);
126✔
2428
        }
126✔
2429
        else
×
2430
          cursor.prev(key, val);
×
2431

2432
        for (;;) {
1,153✔
2433
          if (co.getDomainID(key.getNoStripHeader<StringView>()) != id) {
1,153!
2434
            // cout<<"Last record also not part of this zone!"<<endl;
2435
            //  this implies something is wrong in the database, nothing we can do
2436
            return false;
×
2437
          }
×
2438

2439
          if (co.getQType(key.getNoStripHeader<StringView>()) == QType::NSEC3) {
1,153✔
2440
            deserializeFromBuffer(val.get<StringView>(), lrr);
267✔
2441
            if (!lrr.ttl) // the kind of NSEC3 we need
267✔
2442
              break;
126✔
2443
          }
267✔
2444
          if (cursor.prev(key, val)) {
1,027!
2445
            // hit beginning of database, again means something is wrong with it
2446
            return false;
×
2447
          }
×
2448
        }
1,027✔
2449
        before = co.getQName(key.getNoStripHeader<StringView>());
126✔
2450
        unhashed = DNSName(lrr.content.c_str(), lrr.content.size(), 0, false) + di.zone.operator const DNSName&();
126✔
2451
        // cout <<"Should still find 'after'!"<<endl;
2452
        // for 'after', we need to find the first hash of this zone
2453

2454
        if (cursor.lower_bound(co(id), key, val)) {
126!
2455
          // cout<<"hit end of zone find when we shouldn't"<<endl;
2456
          // means database is wrong, nothing we can do
2457
          return false;
×
2458
        }
×
2459
        for (;;) {
693✔
2460
          if (co.getQType(key.getNoStripHeader<StringView>()) == QType::NSEC3) {
693✔
2461
            deserializeFromBuffer(val.get<StringView>(), lrr);
282✔
2462
            if (!lrr.ttl)
282✔
2463
              break;
126✔
2464
          }
282✔
2465

2466
          if (cursor.next(key, val)) {
567!
2467
            // means database is wrong, nothing we can do
2468
            // cout<<"hit end of zone when we shouldn't 2"<<endl;
2469
            return false;
×
2470
          }
×
2471
        }
567✔
2472
        after = co.getQName(key.getNoStripHeader<StringView>());
126✔
2473

2474
        // cout<<"returning: before="<<before<<", after="<<after<<", unhashed: "<<unhashed<<endl;
2475
        return true;
126✔
2476
      }
126✔
2477
      ++count;
4,042✔
2478
    }
4,042✔
2479
    before = co.getQName(key.getNoStripHeader<StringView>());
2,359✔
2480
    unhashed = DNSName(lrr.content.c_str(), lrr.content.size(), 0, false) + di.zone.operator const DNSName&();
2,359✔
2481
    // cout<<"Went backwards, found "<<before<<endl;
2482
    // return us to starting point
2483
    while (count--)
5,857✔
2484
      cursor.next(key, val);
3,498✔
2485
  }
2,359✔
2486
  //  cout<<"Now going forward"<<endl;
2487
  for (int count = 0;; ++count) {
11,151✔
2488
    if ((count && cursor.next(key, val)) || co.getDomainID(key.getNoStripHeader<StringView>()) != id) {
11,151✔
2489
      // cout <<"Hit end of database or zone, finding first hash then in zone "<<id<<endl;
2490
      if (cursor.lower_bound(co(id), key, val)) {
169!
2491
        // cout<<"hit end of zone find when we shouldn't"<<endl;
2492
        // means database is wrong, nothing we can do
2493
        return false;
×
2494
      }
×
2495
      for (;;) {
1,210✔
2496
        if (co.getQType(key.getNoStripHeader<StringView>()) == QType::NSEC3) {
1,210✔
2497
          deserializeFromBuffer(val.get<StringView>(), lrr);
422✔
2498
          if (!lrr.ttl)
422✔
2499
            break;
169✔
2500
        }
422✔
2501

2502
        if (cursor.next(key, val)) {
1,041!
2503
          // means database is wrong, nothing we can do
2504
          // cout<<"hit end of zone when we shouldn't 2"<<endl;
2505
          return false;
×
2506
        }
×
2507
        // cout << "Next.. "<<endl;
2508
      }
1,041✔
2509
      after = co.getQName(key.getNoStripHeader<StringView>());
169✔
2510

2511
      // cout<<"returning: before="<<before<<", after="<<after<<", unhashed: "<<unhashed<<endl;
2512
      return true;
169✔
2513
    }
169✔
2514

2515
    // cout<<"After "<<co.getQName(key.get<StringView>()) <<endl;
2516
    if (co.getQType(key.getNoStripHeader<StringView>()) == QType::NSEC3) {
10,982✔
2517
      deserializeFromBuffer(val.get<StringView>(), lrr);
7,191✔
2518
      if (!lrr.ttl) {
7,191✔
2519
        break;
5,271✔
2520
      }
5,271✔
2521
    }
7,191✔
2522
  }
10,982✔
2523
  after = co.getQName(key.getNoStripHeader<StringView>());
5,271✔
2524
  // cout<<"returning: before="<<before<<", after="<<after<<", unhashed: "<<unhashed<<endl;
2525
  return true;
5,271✔
2526
}
5,440✔
2527

2528
bool LMDBBackend::getBeforeAndAfterNames(domainid_t domainId, const ZoneName& zonenameU, const DNSName& qname, DNSName& before, DNSName& after)
2529
{
2,423✔
2530
  ZoneName zonename = zonenameU.makeLowerCase();
2,423✔
2531
  //  cout << __PRETTY_FUNCTION__<< ": "<<domainId <<", "<<zonename << ", '"<<qname<<"'"<<endl;
2532

2533
  auto txn = getRecordsROTransaction(domainId);
2,423✔
2534
  compoundOrdername co;
2,423✔
2535
  DNSName qname2 = qname.makeRelative(zonename);
2,423✔
2536
  string matchkey = co(domainId, qname2);
2,423✔
2537
  auto cursor = txn->txn->getCursor(txn->db->dbi);
2,423✔
2538
  MDBOutVal key, val;
2,423✔
2539
  // cout<<"Lower_bound for "<<qname2<<endl;
2540
  if (cursor.lower_bound(matchkey, key, val)) {
2,423✔
2541
    // cout << "Hit end of database, bummer"<<endl;
2542
    cursor.last(key, val);
289✔
2543
    if (compoundOrdername::getDomainID(key.getNoStripHeader<string_view>()) == domainId) {
289!
2544
      before = compoundOrdername::getQName(key.getNoStripHeader<string_view>()) + zonename.operator const DNSName&();
289✔
2545
      after = zonename.operator const DNSName&();
289✔
2546
    }
289✔
2547
    // else
2548
    // cout << "We were at end of database, but this zone is not there?!"<<endl;
2549
    return true;
289✔
2550
  }
289✔
2551
  // cout<<"Cursor is at "<<co.getQName(key.get<string_view>()) <<", in zone id "<<compoundOrdername::getDomainID(key.get<string_view>())<< endl;
2552

2553
  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
2,134✔
2554
    // cout << "Had an exact match!"<<endl;
2555
    before = qname2 + zonename.operator const DNSName&();
1,046✔
2556
    int rc;
1,046✔
2557
    for (;;) {
3,954✔
2558
      rc = cursor.next(key, val);
3,954✔
2559
      if (rc)
3,954✔
2560
        break;
37✔
2561

2562
      if (compoundOrdername::getDomainID(key.getNoStripHeader<string_view>()) == domainId && key.getNoStripHeader<StringView>().rfind(matchkey, 0) == 0) {
3,917!
2563
        continue;
2,269✔
2564
      }
2,269✔
2565
      LMDBResourceRecord lrr;
1,648✔
2566
      deserializeFromBuffer(val.get<StringView>(), lrr);
1,648✔
2567
      if (co.getQType(key.getNoStripHeader<string_view>()).getCode() && (lrr.auth || co.getQType(key.getNoStripHeader<string_view>()).getCode() == QType::NS))
1,648✔
2568
        break;
1,009✔
2569
    }
1,648✔
2570
    if (rc != 0 || compoundOrdername::getDomainID(key.getNoStripHeader<string_view>()) != domainId) {
1,046!
2571
      // cout << "We hit the end of the zone or database. 'after' is apex" << endl;
2572
      after = zonename.operator const DNSName&();
37✔
2573
      return false;
37✔
2574
    }
37✔
2575
    after = compoundOrdername::getQName(key.getNoStripHeader<string_view>()) + zonename.operator const DNSName&();
1,009✔
2576
    return true;
1,009✔
2577
  }
1,046✔
2578

2579
  if (compoundOrdername::getDomainID(key.getNoStripHeader<string_view>()) != domainId) {
1,088✔
2580
    // cout << "Ended up in next zone, 'after' is zonename" <<endl;
2581
    after = zonename.operator const DNSName&();
18✔
2582
    // cout << "Now hunting for previous" << endl;
2583
    int rc;
18✔
2584
    for (;;) {
36✔
2585
      rc = cursor.prev(key, val);
36✔
2586
      if (rc) {
36!
2587
        // cout<<"Reversed into zone, but got not found from lmdb" <<endl;
2588
        return false;
×
2589
      }
×
2590

2591
      if (compoundOrdername::getDomainID(key.getNoStripHeader<string_view>()) != domainId) {
36!
2592
        // cout<<"Reversed into zone, but found wrong zone id " << compoundOrdername::getDomainID(key.getNoStripHeader<string_view>()) << " != "<<domainId<<endl;
2593
        // "this can't happen"
2594
        return false;
×
2595
      }
×
2596
      LMDBResourceRecord lrr;
36✔
2597
      deserializeFromBuffer(val.get<StringView>(), lrr);
36✔
2598
      if (co.getQType(key.getNoStripHeader<string_view>()).getCode() && (lrr.auth || co.getQType(key.getNoStripHeader<string_view>()).getCode() == QType::NS))
36!
2599
        break;
18✔
2600
    }
36✔
2601

2602
    before = compoundOrdername::getQName(key.getNoStripHeader<string_view>()) + zonename.operator const DNSName&();
18✔
2603
    // cout<<"Found: "<< before<<endl;
2604
    return true;
18✔
2605
  }
18✔
2606

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

2609
  int skips = 0;
1,070✔
2610
  for (;;) {
1,696✔
2611
    LMDBResourceRecord lrr;
1,696✔
2612
    deserializeFromBuffer(val.get<StringView>(), lrr);
1,696✔
2613
    if (co.getQType(key.getNoStripHeader<string_view>()).getCode() && (lrr.auth || co.getQType(key.getNoStripHeader<string_view>()).getCode() == QType::NS)) {
1,696!
2614
      after = compoundOrdername::getQName(key.getNoStripHeader<string_view>()) + zonename.operator const DNSName&();
1,070✔
2615
      // cout <<"Found auth ("<<lrr.auth<<") or an NS record "<<after<<", type: "<<co.getQType(key.getNoStripHeader<string_view>()).toString()<<", ttl = "<<lrr.ttl<<endl;
2616
      // cout << makeHexDump(val.get<string>()) << endl;
2617
      break;
1,070✔
2618
    }
1,070✔
2619
    // 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;
2620
    int rc = cursor.next(key, val);
626✔
2621
    if (!rc)
626!
2622
      ++skips;
626✔
2623
    if (rc != 0 || compoundOrdername::getDomainID(key.getNoStripHeader<string_view>()) != domainId) {
626!
2624
      // cout << "  oops, hit end of database or zone. This means after is apex" <<endl;
2625
      after = zonename.operator const DNSName&();
×
2626
      break;
×
2627
    }
×
2628
  }
626✔
2629
  // go back to where we were
2630
  while (skips--)
1,696✔
2631
    cursor.prev(key, val);
626✔
2632

2633
  for (;;) {
2,080✔
2634
    int rc = cursor.prev(key, val);
2,080✔
2635
    if (rc != 0 || compoundOrdername::getDomainID(key.getNoStripHeader<string_view>()) != domainId) {
2,080!
2636
      // XX I don't think this case can happen
2637
      // cout << "We hit the beginning of the zone or database.. now what" << endl;
2638
      return false;
×
2639
    }
×
2640
    before = compoundOrdername::getQName(key.getNoStripHeader<string_view>()) + zonename.operator const DNSName&();
2,080✔
2641
    LMDBResourceRecord lrr;
2,080✔
2642
    deserializeFromBuffer(val.get<string_view>(), lrr);
2,080✔
2643
    // cout<<"And before to "<<before<<", auth = "<<rr.auth<<endl;
2644
    if (co.getQType(key.getNoStripHeader<string_view>()).getCode() && (lrr.auth || co.getQType(key.getNoStripHeader<string_view>()) == QType::NS))
2,080✔
2645
      break;
1,070✔
2646
    // cout << "Oops, that was wrong, go back one more"<<endl;
2647
  }
2,080✔
2648

2649
  return true;
1,070✔
2650
}
1,070✔
2651

2652
bool LMDBBackend::updateDNSSECOrderNameAndAuth(domainid_t domain_id, const DNSName& qname, const DNSName& ordername, bool auth, const uint16_t qtype)
2653
{
122,445✔
2654
  //  cout << __PRETTY_FUNCTION__<< ": "<< domain_id <<", '"<<qname <<"', '"<<ordername<<"', "<<auth<< ", " << qtype << endl;
2655
  shared_ptr<RecordsRWTransaction> txn;
122,445✔
2656
  bool needCommit = false;
122,445✔
2657
  if (d_rwtxn && d_transactiondomainid == domain_id) {
122,445!
2658
    txn = d_rwtxn;
122,445✔
2659
    //    cout<<"Reusing open transaction"<<endl;
2660
  }
122,445✔
2661
  else {
×
2662
    //    cout<<"Making a new RW txn for " << __PRETTY_FUNCTION__ <<endl;
2663
    txn = getRecordsRWTransaction(domain_id);
×
2664
    needCommit = true;
×
2665
  }
×
2666

2667
  DomainInfo di;
122,445✔
2668
  if (!d_tdomains->getROTransaction().get(domain_id, di)) {
122,445!
2669
    //    cout<<"Could not find domain_id "<<domain_id <<endl;
2670
    return false;
×
2671
  }
×
2672

2673
  DNSName rel = qname.makeRelative(di.zone);
122,445✔
2674

2675
  compoundOrdername co;
122,445✔
2676
  string matchkey = co(domain_id, rel);
122,445✔
2677

2678
  auto cursor = txn->txn->getCursor(txn->db->dbi);
122,445✔
2679
  MDBOutVal key, val;
122,445✔
2680
  if (cursor.prefix(matchkey, key, val) != 0) {
122,445!
2681
    // cout << "Could not find anything"<<endl;
2682
    return false;
×
2683
  }
×
2684

2685
  bool hasOrderName = !ordername.empty();
122,445✔
2686
  bool needNSEC3 = hasOrderName;
122,445✔
2687

2688
  do {
123,500✔
2689
    vector<LMDBResourceRecord> lrrs;
123,500✔
2690

2691
    if (co.getQType(key.getNoStripHeader<StringView>()) != QType::NSEC3) {
123,500✔
2692
      deserializeFromBuffer(val.get<StringView>(), lrrs);
123,290✔
2693
      bool changed = false;
123,290✔
2694
      vector<LMDBResourceRecord> newRRs;
123,290✔
2695
      newRRs.reserve(lrrs.size());
123,290✔
2696
      for (auto& lrr : lrrs) {
124,417✔
2697
        lrr.qtype = co.getQType(key.getNoStripHeader<StringView>());
124,417✔
2698
        if (!needNSEC3 && qtype != QType::ANY) {
124,417✔
2699
          needNSEC3 = (lrr.ordername && QType(qtype) != lrr.qtype);
955✔
2700
        }
955✔
2701

2702
        if ((qtype == QType::ANY || QType(qtype) == lrr.qtype) && (lrr.ordername != hasOrderName || lrr.auth != auth)) {
124,417✔
2703
          lrr.auth = auth;
122,543✔
2704
          lrr.ordername = hasOrderName;
122,543✔
2705
          changed = true;
122,543✔
2706
        }
122,543✔
2707
        newRRs.push_back(std::move(lrr));
124,417✔
2708
      }
124,417✔
2709
      if (changed) {
123,290✔
2710
        cursor.put(key, serializeToBuffer(newRRs));
121,803✔
2711
      }
121,803✔
2712
    }
123,290✔
2713

2714
  } while (cursor.next(key, val) == 0);
123,500✔
2715

2716
  bool del = false;
122,445✔
2717
  LMDBResourceRecord lrr;
122,445✔
2718
  matchkey = co(domain_id, rel, QType::NSEC3);
122,445✔
2719
  // cerr<<"here qname="<<qname<<" ordername="<<ordername<<" qtype="<<qtype<<" matchkey="<<makeHexDump(matchkey)<<endl;
2720
  int txngetrc;
122,445✔
2721
  if (!(txngetrc = txn->txn->get(txn->db->dbi, matchkey, val))) {
122,445✔
2722
    deserializeFromBuffer(val.get<string_view>(), lrr);
210✔
2723

2724
    if (needNSEC3) {
210✔
2725
      if (hasOrderName && lrr.content != ordername.toDNSStringLC()) {
180✔
2726
        del = true;
1✔
2727
      }
1✔
2728
    }
180✔
2729
    else {
30✔
2730
      del = true;
30✔
2731
    }
30✔
2732
    if (del) {
210✔
2733
      txn->txn->del(txn->db->dbi, co(domain_id, DNSName(lrr.content.c_str(), lrr.content.size(), 0, false), QType::NSEC3));
31✔
2734
      txn->txn->del(txn->db->dbi, matchkey);
31✔
2735
    }
31✔
2736
  }
210✔
2737
  else {
122,235✔
2738
    del = true;
122,235✔
2739
  }
122,235✔
2740

2741
  if (hasOrderName && del) {
122,445✔
2742
    matchkey = co(domain_id, rel, QType::NSEC3);
121,119✔
2743

2744
    lrr.ttl = 0;
121,119✔
2745
    lrr.auth = 0;
121,119✔
2746
    lrr.content = rel.toDNSStringLC();
121,119✔
2747

2748
    string str = serializeToBuffer(lrr);
121,119✔
2749
    txn->txn->put(txn->db->dbi, co(domain_id, ordername, QType::NSEC3), str);
121,119✔
2750
    lrr.ttl = 1;
121,119✔
2751
    lrr.content = ordername.toDNSStringLC();
121,119✔
2752
    str = serializeToBuffer(lrr);
121,119✔
2753
    txn->txn->put(txn->db->dbi, matchkey, str); // 2
121,119✔
2754
  }
121,119✔
2755

2756
  if (needCommit)
122,445!
2757
    txn->txn->commit();
×
2758
  return false;
122,445✔
2759
}
122,445✔
2760

2761
bool LMDBBackend::updateEmptyNonTerminals(domainid_t domain_id, set<DNSName>& insert, set<DNSName>& erase, bool remove)
2762
{
75✔
2763
  // cout << __PRETTY_FUNCTION__<< ": "<< domain_id << ", insert.size() "<<insert.size()<<", "<<erase.size()<<", " <<remove<<endl;
2764

2765
  bool needCommit = false;
75✔
2766
  shared_ptr<RecordsRWTransaction> txn;
75✔
2767
  if (d_rwtxn && d_transactiondomainid == domain_id) {
75!
2768
    txn = d_rwtxn;
75✔
2769
    //    cout<<"Reusing open transaction"<<endl;
2770
  }
75✔
2771
  else {
×
2772
    //    cout<<"Making a new RW txn for delete domain"<<endl;
2773
    txn = getRecordsRWTransaction(domain_id);
×
2774
    needCommit = true;
×
2775
  }
×
2776

2777
  // if remove is set, all ENTs should be removed & nothing else should be done
2778
  if (remove) {
75!
2779
    LMDBBackend::deleteDomainRecords(*txn, domain_id, 0);
×
2780
  }
×
2781
  else {
75✔
2782
    DomainInfo di;
75✔
2783
    auto rotxn = d_tdomains->getROTransaction();
75✔
2784
    if (!rotxn.get(domain_id, di)) {
75!
2785
      // cout <<"No such domain with id "<<domain_id<<endl;
2786
      return false;
×
2787
    }
×
2788
    compoundOrdername co;
75✔
2789
    for (const auto& n : insert) {
354✔
2790
      LMDBResourceRecord lrr;
354✔
2791
      lrr.qname = n.makeRelative(di.zone);
354✔
2792
      lrr.ttl = 0;
354✔
2793
      lrr.auth = true;
354✔
2794

2795
      std::string ser = serializeToBuffer(lrr);
354✔
2796

2797
      txn->txn->put(txn->db->dbi, co(domain_id, lrr.qname, 0), ser);
354✔
2798

2799
      // cout <<" +"<<n<<endl;
2800
    }
354✔
2801
    for (auto n : erase) {
75✔
2802
      // cout <<" -"<<n<<endl;
2803
      n.makeUsRelative(di.zone);
1✔
2804
      txn->txn->del(txn->db->dbi, co(domain_id, n, 0));
1✔
2805
    }
1✔
2806
  }
75✔
2807
  if (needCommit)
75!
2808
    txn->txn->commit();
×
2809
  return false;
75✔
2810
}
75✔
2811

2812
/* TSIG */
2813
bool LMDBBackend::getTSIGKey(const DNSName& name, DNSName& algorithm, string& content)
2814
{
103✔
2815
  auto txn = d_ttsig->getROTransaction();
103✔
2816
  LmdbIdVec ids;
103✔
2817
  txn.get_multi<0>(name, ids);
103✔
2818

2819
  TSIGKey key;
103✔
2820
  for (auto id : ids) {
103✔
2821
    if (txn.get(id, key)) {
87!
2822
      if (algorithm.empty() || algorithm == DNSName(key.algorithm)) {
87!
2823
        algorithm = DNSName(key.algorithm);
87✔
2824
        content = key.key;
87✔
2825
      }
87✔
2826
    }
87✔
2827
  }
87✔
2828

2829
  return true;
103✔
2830
}
103✔
2831

2832
// this deletes an old key if it has the same algorithm
2833
bool LMDBBackend::setTSIGKey(const DNSName& name, const DNSName& algorithm, const string& content)
2834
{
27✔
2835
  auto txn = d_ttsig->getRWTransaction();
27✔
2836

2837
  LmdbIdVec ids;
27✔
2838
  txn.get_multi<0>(name, ids);
27✔
2839

2840
  TSIGKey key;
27✔
2841
  for (auto id : ids) {
27✔
2842
    if (txn.get(id, key)) {
2!
2843
      if (key.algorithm == algorithm) {
2✔
2844
        txn.del(id);
1✔
2845
      }
1✔
2846
    }
2✔
2847
  }
2✔
2848

2849
  TSIGKey tk;
27✔
2850
  tk.name = name;
27✔
2851
  tk.algorithm = algorithm;
27✔
2852
  tk.key = content;
27✔
2853

2854
  txn.put(tk, 0, d_random_ids);
27✔
2855
  txn.commit();
27✔
2856

2857
  return true;
27✔
2858
}
27✔
2859
bool LMDBBackend::deleteTSIGKey(const DNSName& name)
2860
{
2✔
2861
  auto txn = d_ttsig->getRWTransaction();
2✔
2862

2863
  LmdbIdVec ids;
2✔
2864
  txn.get_multi<0>(name, ids);
2✔
2865

2866
  TSIGKey key;
2✔
2867

2868
  for (auto id : ids) {
2✔
2869
    if (txn.get(id, key)) {
2!
2870
      txn.del(id);
2✔
2871
    }
2✔
2872
  }
2✔
2873
  txn.commit();
2✔
2874
  return true;
2✔
2875
}
2✔
2876
bool LMDBBackend::getTSIGKeys(std::vector<struct TSIGKey>& keys)
2877
{
1✔
2878
  auto txn = d_ttsig->getROTransaction();
1✔
2879

2880
  keys.clear();
1✔
2881
  // In a perfect world, we would simply iterate over txn and add every
2882
  // item to the returned vector:
2883
  //   for (auto iter = txn.begin(); iter != txn.end(); ++iter) {
2884
  //     keys.push_back(*iter);
2885
  //   }
2886
  // But databases converted from older (< 5) schemas _may_ have multiple
2887
  // entries for the same TSIG key name and algorithm, something which is not
2888
  // allowed in the v5 database schema. These extra entries will not be found
2889
  // by get_multi<> during regular operations, and would only appear in the
2890
  // results of this method.
2891
  // In order to prevent this, we first only gather the list of key names, and
2892
  // in a second step, query for them using a similar logic as getTSIGKey().
2893
  // Unfortunately, there does not seem to be a way to know if the database had
2894
  // been created using the v5 schema (not converted), in which case we could
2895
  // use the above, simpler logic.
2896
  std::unordered_set<DNSName> keynames;
1✔
2897
  for (const auto& iter : txn) {
10✔
2898
    keynames.insert(iter.name);
10✔
2899
  }
10✔
2900
  for (const auto& iter : keynames) {
9✔
2901
    LmdbIdVec ids;
9✔
2902
    txn.get_multi<0>(iter, ids);
9✔
2903
    for (auto key_id : ids) {
10✔
2904
      TSIGKey key;
10✔
2905
      if (txn.get(key_id, key)) {
10!
2906
        keys.push_back(key);
10✔
2907
      }
10✔
2908
    }
10✔
2909
  }
9✔
2910
  return true;
1✔
2911
}
1✔
2912

2913
string LMDBBackend::directBackendCmd(const string& query)
2914
{
×
2915
  ostringstream ret, usage;
×
2916

2917
  usage << "info                               show some information about the database" << endl;
×
2918
  usage << "index check domains                check zone<>ID indexes" << endl;
×
2919
  usage << "index refresh domains <ID>         refresh index for zone with this ID" << endl;
×
2920
  usage << "index refresh-all domains          refresh index for all zones with disconnected indexes" << endl;
×
2921
  vector<string> argv;
×
2922
  stringtok(argv, query);
×
2923

2924
  if (argv.empty()) {
×
2925
    return usage.str();
×
2926
  }
×
2927

2928
  string& cmd = argv[0];
×
2929

2930
  if (cmd == "help") {
×
2931
    return usage.str();
×
2932
  }
×
2933

2934
  if (cmd == "info") {
×
2935
    ret << "shards: " << s_shards << endl;
×
2936
    ret << "schemaversion: " << SCHEMAVERSION << endl;
×
2937

2938
    return ret.str();
×
2939
  }
×
2940

2941
  if (cmd == "index") {
×
2942
    if (argv.size() < 2) {
×
2943
      return "need an index subcommand\n";
×
2944
    }
×
2945

2946
    string& subcmd = argv[1];
×
2947

2948
    if (subcmd == "check" || subcmd == "refresh-all") {
×
2949
      bool refresh = false;
×
2950

2951
      if (subcmd == "refresh-all") {
×
2952
        refresh = true;
×
2953
      }
×
2954

2955
      if (argv.size() < 3) {
×
2956
        return "need an index name\n";
×
2957
      }
×
2958

2959
      if (argv[2] != "domains") {
×
2960
        return "can only check the domains index\n";
×
2961
      }
×
2962

2963
      vector<uint32_t> refreshQueue;
×
2964

2965
      {
×
2966
        auto txn = d_tdomains->getROTransaction();
×
2967

2968
        for (auto iter = txn.begin(); iter != txn.end(); ++iter) {
×
2969
          DomainInfo di = *iter;
×
2970

2971
          auto id = iter.getID();
×
2972

2973
          LmdbIdVec ids;
×
2974
          txn.get_multi<0>(di.zone, ids);
×
2975

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

2979
            if (ids.empty()) {
×
2980
              ret << "zone->ID index has no entry for " << di.zone << endl;
×
2981
              if (refresh) {
×
2982
                refreshQueue.push_back(id);
×
2983
              }
×
2984
              else {
×
2985
                ret << "  suggested remedy: index refresh domains " << id << endl;
×
2986
              }
×
2987
            }
×
2988
            else {
×
2989
              // ids.size() > 1
2990
              ret << "zone->ID index has multiple entries for " << di.zone << ": ";
×
2991
              for (auto id_ : ids) {
×
2992
                ret << id_ << " ";
×
2993
              }
×
2994
              ret << endl;
×
2995
            }
×
2996
          }
×
2997
        }
×
2998
      }
×
2999

3000
      if (refresh) {
×
3001
        for (const auto& id : refreshQueue) {
×
3002
          if (genChangeDomain(id, [](DomainInfo& /* di */) {})) {
×
3003
            ret << "refreshed " << id << endl;
×
3004
          }
×
3005
          else {
×
3006
            ret << "failed to refresh " << id << endl;
×
3007
          }
×
3008
        }
×
3009
      }
×
3010
      return ret.str();
×
3011
    }
×
3012
    if (subcmd == "refresh") {
×
3013
      // index refresh domains 12345
3014
      if (argv.size() < 4) {
×
3015
        return "usage: index refresh domains <ID>\n";
×
3016
      }
×
3017

3018
      if (argv[2] != "domains") {
×
3019
        return "can only refresh in the domains index\n";
×
3020
      }
×
3021

3022
      domainid_t id = 0; // NOLINT(readability-identifier-length)
×
3023

3024
      try {
×
3025
        pdns::checked_stoi_into(id, argv[3]);
×
3026
      }
×
3027
      catch (const std::out_of_range& e) {
×
3028
        return "ID out of range\n";
×
3029
      }
×
3030

3031
      if (genChangeDomain(id, [](DomainInfo& /* di */) {})) {
×
3032
        ret << "refreshed" << endl;
×
3033
      }
×
3034
      else {
×
3035
        ret << "failed" << endl;
×
3036
      }
×
3037
      return ret.str();
×
3038
    }
×
3039
  }
×
3040

3041
  return "unknown lmdbbackend command\n";
×
3042
}
×
3043

3044
bool LMDBBackend::hasCreatedLocalFiles() const
3045
{
1,576✔
3046
  // Since the lmdb file creation counter is global, if multiple LMDB backends
3047
  // are used, they may end up all reporting having created files even if
3048
  // not all of them did.
3049
  // But since this information is for the sake of pdnsutil, this is not
3050
  // really a problem.
3051
  return MDBDbi::d_creationCount != 0;
1,576✔
3052
}
1,576✔
3053

3054
class LMDBFactory : public BackendFactory
3055
{
3056
public:
3057
  LMDBFactory() :
3058
    BackendFactory("lmdb") {}
5,320✔
3059
  void declareArguments(const string& suffix = "") override
3060
  {
1,985✔
3061
    declare(suffix, "filename", "Filename for lmdb", "./pdns.lmdb");
1,985✔
3062
    declare(suffix, "sync-mode", "Synchronisation mode: nosync, nometasync, sync", "sync");
1,985✔
3063
    // there just is no room for more on 32 bit
3064
    declare(suffix, "shards", "Records database will be split into this number of shards", (sizeof(void*) == 4) ? "2" : "64");
1,985✔
3065
    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));
1,985✔
3066
    declare(suffix, "random-ids", "Numeric IDs inside the database are generated randomly instead of sequentially", "no");
1,985✔
3067
    declare(suffix, "map-size", "LMDB map size in megabytes", (sizeof(void*) == 4) ? "100" : "16000");
1,985✔
3068
    declare(suffix, "flag-deleted", "Flag entries on deletion instead of deleting them", "no");
1,985✔
3069
    declare(suffix, "lightning-stream", "Run in Lightning Stream compatible mode", "no");
1,985✔
3070
  }
1,985✔
3071
  DNSBackend* make(const string& suffix = "") override
3072
  {
3,868✔
3073
    return new LMDBBackend(suffix);
3,868✔
3074
  }
3,868✔
3075
};
3076

3077
/* THIRD PART */
3078

3079
class LMDBLoader
3080
{
3081
public:
3082
  LMDBLoader()
3083
  {
5,320✔
3084
    BackendMakers().report(std::make_unique<LMDBFactory>());
5,320✔
3085
    g_log << Logger::Info << "[lmdbbackend] This is the lmdb backend version " VERSION
5,320✔
3086
#ifndef REPRODUCIBLE
5,320✔
3087
          << " (" __DATE__ " " __TIME__ ")"
5,320✔
3088
#endif
5,320✔
3089
          << " reporting" << endl;
5,320✔
3090
  }
5,320✔
3091
};
3092

3093
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

© 2026 Coveralls, Inc