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

PowerDNS / pdns / 15971963506

30 Jun 2025 11:43AM UTC coverage: 65.603% (+0.007%) from 65.596%
15971963506

Pull #15748

github

web-flow
Merge b42f175a8 into f296440f4
Pull Request #15748: rec meson: check python version to be at least 3.8

41548 of 91916 branches covered (45.2%)

Branch coverage included in aggregate %.

126860 of 164791 relevant lines covered (76.98%)

4939129.26 hits per line

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

74.64
/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,995✔
70
  // cerr << "getting schema version for path " << filename << endl;
71

72
  uint32_t schemaversion = 0;
1,995✔
73

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

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

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

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

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

90
  {
1,995✔
91
    int retCode = mdb_env_open(tmpEnv, filename.c_str(), MDB_NOSUBDIR | MDB_RDONLY, 0600);
1,995✔
92
    if (retCode != 0) {
1,995✔
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: " + MDBError(retCode));
×
98
    }
52✔
99
  }
1,995✔
100

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

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

107
  MDB_dbi dbi;
1,943✔
108

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

123
  MDB_val key, data;
1,943✔
124

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

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

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

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

159
  uint32_t shards = 0;
1,943✔
160

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

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

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

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

192
  mdb_txn_abort(txn);
1,943✔
193

194
  return {schemaversion, shards};
1,943✔
195
}
1,943✔
196

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

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

207
  MDB_cursor* cur;
4✔
208

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

213
  MDB_val key, data;
4✔
214

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

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

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

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

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

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

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

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

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

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

254
  MDB_cursor* cur;
8✔
255

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

260
  MDB_val key, data;
8✔
261

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

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

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

270
    uint32_t id;
32✔
271

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

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

278
    id = htonl(id);
32✔
279

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

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

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

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

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

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

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

311
  MDB_cursor* cur;
8✔
312

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

317
  MDB_val key, data;
8✔
318

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

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

325
    uint32_t id;
32✔
326

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

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

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

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

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

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

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

357
}
358

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

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

369
  MDB_env* env = nullptr;
2✔
370

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

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

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

385
  MDB_txn* txn = nullptr;
2✔
386

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

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

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

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

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

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

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

427
    MDB_txn* shtxn = nullptr;
4✔
428

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

434
    MDB_dbi shdbi = 0;
4✔
435

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

448
    MDB_dbi shdbi2 = 0;
4✔
449

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

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

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

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

477
  int index = 0;
2✔
478

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

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

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

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

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

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

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

523
  index = 0;
2✔
524

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

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

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

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

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

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

567
  MDB_dbi dbi = 0;
2✔
568

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

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

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

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

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

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

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

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

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

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

607
    MDB_val tdata;
4✔
608

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

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

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

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

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

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

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

631
    MDB_val tdata;
2✔
632

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

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

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

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

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

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

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

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

678
  d_views = ::arg().mustDo("views"); // This is a global setting
3,903✔
679

680
  setArgPrefix("lmdb" + suffix);
3,903✔
681

682
  string syncMode = toLower(getArg("sync-mode"));
3,903✔
683

684
  if (syncMode == "nosync")
3,903!
685
    d_asyncFlag = MDB_NOSYNC;
×
686
  else if (syncMode == "nometasync")
3,903!
687
    d_asyncFlag = MDB_NOMETASYNC;
×
688
  else if (syncMode.empty() || syncMode == "sync")
3,903!
689
    d_asyncFlag = 0;
3,902✔
690
  else
1✔
691
    throw std::runtime_error("Unknown sync mode " + syncMode + " requested for LMDB backend");
1✔
692

693
  d_mapsize = 0;
3,902✔
694
  try {
3,902✔
695
    d_mapsize = std::stoll(getArg("map-size"));
3,902✔
696
  }
3,902✔
697
  catch (const std::exception& e) {
3,902✔
698
    throw std::runtime_error(std::string("Unable to parse the 'map-size' LMDB value: ") + e.what());
×
699
  }
×
700

701
  if (mustDo("lightning-stream")) {
3,903!
702
    d_random_ids = true;
×
703
    d_handle_dups = true;
×
704
    LMDBLS::s_flag_deleted = true;
×
705

706
    if (atoi(getArg("shards").c_str()) != 1) {
×
707
      throw std::runtime_error(std::string("running with Lightning Stream support requires shards=1"));
×
708
    }
×
709
  }
×
710
  else {
3,903✔
711
    d_random_ids = mustDo("random-ids");
3,903✔
712
    d_handle_dups = false;
3,903✔
713
    LMDBLS::s_flag_deleted = mustDo("flag-deleted");
3,903✔
714
  }
3,903✔
715

716
  bool opened = false;
3,903✔
717

718
  if (s_first) {
3,903✔
719
    std::lock_guard<std::mutex> l(s_lmdbStartupLock);
1,993✔
720
    if (s_first) {
1,993!
721
      auto filename = getArg("filename");
1,993✔
722

723
      auto currentSchemaVersionAndShards = getSchemaVersionAndShards(filename);
1,993✔
724
      uint32_t currentSchemaVersion = currentSchemaVersionAndShards.first;
1,993✔
725
      // std::cerr<<"current schema version: "<<currentSchemaVersion<<", shards="<<currentSchemaVersionAndShards.second<<std::endl;
726

727
      if (getArgAsNum("schema-version") != SCHEMAVERSION) {
1,993!
728
        throw std::runtime_error("This version of the lmdbbackend only supports schema version 6. Configuration demands a lower version. Not starting up.");
×
729
      }
×
730

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

735
      if (currentSchemaVersion == 0) {
1,993✔
736
        // no database is present yet, we can just create them
737
        currentSchemaVersion = 6;
52✔
738
      }
52✔
739

740
      if (currentSchemaVersion == 3 || currentSchemaVersion == 4) {
1,993✔
741
        if (!upgradeToSchemav5(filename)) {
2!
742
          throw std::runtime_error("Failed to perform LMDB schema version upgrade from v4 to v5");
×
743
        }
×
744
        currentSchemaVersion = 5;
2✔
745
      }
2✔
746

747
      if (currentSchemaVersion == 5) {
1,993✔
748
        if (!upgradeToSchemav6(filename)) {
3!
749
          throw std::runtime_error("Failed to perform LMDB schema version upgrade from v5 to v6");
×
750
        }
×
751
        currentSchemaVersion = 6;
3✔
752
      }
3✔
753

754
      if (currentSchemaVersion != 6) {
1,993!
755
        throw std::runtime_error("Somehow, we are not at schema version 6. Giving up");
×
756
      }
×
757

758
      openAllTheDatabases();
1,993✔
759
      opened = true;
1,993✔
760
      auto pdnsdbi = d_tdomains->getEnv()->openDB("pdns", MDB_CREATE);
1,993✔
761

762
      auto txn = d_tdomains->getEnv()->getRWTransaction();
1,993✔
763

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

773
      MDBOutVal shards{};
1,993✔
774
      if (txn->get(pdnsdbi, "shards", shards) == 0) {
1,993✔
775
        s_shards = shards.get<uint32_t>();
1,941✔
776

777
        if (mustDo("lightning-stream") && s_shards != 1) {
1,941!
778
          throw std::runtime_error("running with Lightning Stream support enabled requires a database with exactly 1 shard");
×
779
        }
×
780

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

796
      MDBOutVal gotuuid{};
1,993✔
797
      if (txn->get(pdnsdbi, "uuid", gotuuid) != 0) {
1,993✔
798
        const auto uuid = getUniqueID();
52✔
799
        const string uuids(uuid.begin(), uuid.end());
52✔
800
        txn->put(pdnsdbi, "uuid", uuids);
52✔
801
      }
52✔
802

803
      MDBOutVal _schemaversion{};
1,993✔
804
      if (txn->get(pdnsdbi, "schemaversion", _schemaversion) != 0 || _schemaversion.get<uint32_t>() != currentSchemaVersion) {
1,993✔
805
        txn->put(pdnsdbi, "schemaversion", currentSchemaVersion);
55✔
806
      }
55✔
807
      txn->commit();
1,993✔
808

809
      s_first = false;
1,993✔
810
    }
1,993✔
811
  }
1,993✔
812

813
  if (!opened) {
3,903✔
814
    openAllTheDatabases();
1,910✔
815
  }
1,910✔
816
  d_trecords.resize(s_shards);
3,903✔
817
  d_dolog = ::arg().mustDo("query-logging");
3,903✔
818
}
3,903✔
819

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

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

847
unsigned int LMDBBackend::getCapabilities()
848
{
12,100✔
849
  unsigned int caps = CAP_DNSSEC | CAP_DIRECT | CAP_LIST | CAP_CREATE;
12,100✔
850
  if (d_views) {
12,100✔
851
    caps |= CAP_VIEWS;
10,625✔
852
  }
10,625✔
853
  return caps;
12,100✔
854
}
12,100✔
855

856
namespace boost
857
{
858
namespace serialization
859
{
860

861
  template <class Archive>
862
  void save(Archive& ar, const DNSName& g, const unsigned int /* version */)
863
  {
3,314✔
864
    if (g.empty()) {
3,314✔
865
      ar& std::string();
922✔
866
    }
922✔
867
    else {
2,392✔
868
      ar & g.toDNSStringLC();
2,392✔
869
    }
2,392✔
870
  }
3,314✔
871

872
  template <class Archive>
873
  void load(Archive& ar, DNSName& g, const unsigned int /* version */)
874
  {
288,691✔
875
    string tmp;
288,691✔
876
    ar & tmp;
288,691✔
877
    if (tmp.empty()) {
288,691✔
878
      g = DNSName();
130,694✔
879
    }
130,694✔
880
    else {
157,997✔
881
      g = DNSName(tmp.c_str(), tmp.size(), 0, false);
157,997✔
882
    }
157,997✔
883
  }
288,691✔
884

885
  template <class Archive>
886
  void save(Archive& arc, const ZoneName& zone, const unsigned int /* version */)
887
  {
3,260✔
888
    arc & zone.operator const DNSName&();
3,260✔
889
    arc & zone.getVariant();
3,260✔
890
  }
3,260✔
891

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

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

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

969
  template <class Archive>
970
  void serialize(Archive& ar, LMDBBackend::DomainMeta& g, const unsigned int /* version */)
971
  {
4,769✔
972
    ar & g.domain & g.key & g.value;
4,769✔
973
  }
4,769✔
974

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

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

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

1001
} // namespace serialization
1002
} // namespace boost
1003

1004
BOOST_SERIALIZATION_SPLIT_FREE(DNSName);
1005
BOOST_SERIALIZATION_SPLIT_FREE(ZoneName);
1006
BOOST_SERIALIZATION_SPLIT_FREE(LMDBBackend::KeyDataDB);
1007
BOOST_SERIALIZATION_SPLIT_FREE(DomainInfo);
1008
BOOST_IS_BITWISE_SERIALIZABLE(ComboAddress);
1009

1010
template <>
1011
std::string serializeToBuffer(const LMDBBackend::LMDBResourceRecord& value)
1012
{
874,585✔
1013
  std::string buffer;
874,585✔
1014

1015
  // Data size of the resource record.
1016
  uint16_t len = value.content.length();
874,585✔
1017

1018
  // Reserve space to store the size of the resource record + the content of the resource
1019
  // record + a few other things.
1020
  buffer.reserve(sizeof(len) + len + sizeof(value.ttl) + sizeof(value.auth) + sizeof(value.disabled) + sizeof(value.ordername));
874,585✔
1021

1022
  // Store the size of the resource record.
1023
  // NOLINTNEXTLINE.
1024
  buffer.assign((const char*)&len, sizeof(len));
874,585✔
1025

1026
  // Store the contents of the resource record.
1027
  buffer += value.content;
874,585✔
1028

1029
  // The few other things.
1030
  // NOLINTNEXTLINE.
1031
  buffer.append((const char*)&value.ttl, sizeof(value.ttl));
874,585✔
1032
  buffer.append(1, (char)value.auth);
874,585✔
1033
  buffer.append(1, (char)value.disabled);
874,585✔
1034
  buffer.append(1, (char)value.ordername);
874,585✔
1035

1036
  return buffer;
874,585✔
1037
}
874,585✔
1038

1039
template <>
1040
std::string serializeToBuffer(const vector<LMDBBackend::LMDBResourceRecord>& value)
1041
{
122,153✔
1042
  std::string ret;
122,153✔
1043
  for (const auto& lrr : value) {
122,904✔
1044
    ret += serializeToBuffer(lrr);
122,904✔
1045
  }
122,904✔
1046
  return ret;
122,153✔
1047
}
122,153✔
1048

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

1060
  return 2 + len + 7;
545,446✔
1061
}
545,446✔
1062

1063
template <>
1064
void deserializeFromBuffer(const string_view& buffer, LMDBBackend::LMDBResourceRecord& value)
1065
{
20,329✔
1066
  deserializeRRFromBuffer(buffer, value);
20,329✔
1067
}
20,329✔
1068

1069
template <>
1070
void deserializeFromBuffer(const string_view& buffer, vector<LMDBBackend::LMDBResourceRecord>& value)
1071
{
518,558✔
1072
  auto str_copy = buffer;
518,558✔
1073
  while (str_copy.size() >= 9) { // minimum length for a record is 10
1,043,675✔
1074
    LMDBBackend::LMDBResourceRecord lrr;
525,117✔
1075
    auto rrLength = deserializeRRFromBuffer(str_copy, lrr);
525,117✔
1076
    value.emplace_back(lrr);
525,117✔
1077
    str_copy.remove_prefix(rrLength);
525,117✔
1078
  }
525,117✔
1079
}
518,558✔
1080

1081
static std::string serializeContent(uint16_t qtype, const DNSName& domain, const std::string& content)
1082
{
428,320✔
1083
  auto drc = DNSRecordContent::make(qtype, QClass::IN, content);
428,320✔
1084
  return drc->serialize(domain, false);
428,320✔
1085
}
428,320✔
1086

1087
static std::shared_ptr<DNSRecordContent> deserializeContentZR(uint16_t qtype, const DNSName& qname, const std::string& content)
1088
{
400,692✔
1089
  if (qtype == QType::A && content.size() == 4) {
400,692!
1090
    return std::make_shared<ARecordContent>(*((uint32_t*)content.c_str()));
386,229✔
1091
  }
386,229✔
1092
  return DNSRecordContent::deserialize(qname, qtype, content, QClass::IN, true);
14,463✔
1093
}
400,692✔
1094

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

1098
   The index we use is "zoneid,canonical relative name". This index is also used
1099
   for AXFR.
1100

1101
   Note - domain_id, name and type are ONLY present on the index!
1102
*/
1103

1104
#if BOOST_VERSION >= 106100
1105
#define StringView string_view
1106
#else
1107
#define StringView string
1108
#endif
1109

1110
void LMDBBackend::deleteDomainRecords(RecordsRWTransaction& txn, uint16_t qtype, const std::string& match)
1111
{
567✔
1112
  auto cursor = txn.txn->getCursor(txn.db->dbi);
567✔
1113
  MDBOutVal key{};
567✔
1114
  MDBOutVal val{};
567✔
1115

1116
  if (cursor.prefix(match, key, val) == 0) {
567✔
1117
    do {
738✔
1118
      if (qtype == QType::ANY || compoundOrdername::getQType(key.getNoStripHeader<StringView>()) == qtype) {
738!
1119
        cursor.del(key);
738✔
1120
      }
738✔
1121
    } while (cursor.next(key, val) == 0);
738✔
1122
  }
38✔
1123
}
567✔
1124

1125
/* Here's the complicated story. Other backends have just one transaction, which is either
1126
   on or not.
1127

1128
   You can't call feedRecord without a transaction started with startTransaction.
1129

1130
   However, other functions can be called after startTransaction() or without startTransaction()
1131
     (like updateDNSSECOrderNameAndAuth)
1132

1133

1134

1135
*/
1136

1137
bool LMDBBackend::startTransaction(const ZoneName& domain, domainid_t domain_id)
1138
{
1,163✔
1139
  // cout <<"startTransaction("<<domain<<", "<<domain_id<<")"<<endl;
1140
  domainid_t real_id = domain_id;
1,163✔
1141
  if (real_id == UnknownDomainID) {
1,163✔
1142
    auto rotxn = d_tdomains->getROTransaction();
606✔
1143
    DomainInfo di;
606✔
1144
    real_id = rotxn.get<0>(domain, di);
606✔
1145
    // cout<<"real_id = "<<real_id << endl;
1146
    if (!real_id)
606!
1147
      return false;
×
1148
  }
606✔
1149
  if (d_rwtxn) {
1,163!
1150
    throw DBException("Attempt to start a transaction while one was open already");
×
1151
  }
×
1152
  d_rwtxn = getRecordsRWTransaction(real_id);
1,163✔
1153
  d_txnorder = false;
1,163✔
1154

1155
  d_transactiondomain = domain;
1,163✔
1156
  d_transactiondomainid = real_id;
1,163✔
1157
  if (domain_id != UnknownDomainID) {
1,163✔
1158
    compoundOrdername order;
557✔
1159
    string match = order(domain_id);
557✔
1160
    LMDBBackend::deleteDomainRecords(*d_rwtxn, QType::ANY, match);
557✔
1161
  }
557✔
1162

1163
  return true;
1,163✔
1164
}
1,163✔
1165

1166
bool LMDBBackend::commitTransaction()
1167
{
1,091✔
1168
  // cout<<"Commit transaction" <<endl;
1169
  if (!d_rwtxn) {
1,091!
1170
    throw DBException("Attempt to commit a transaction while there isn't one open");
×
1171
  }
×
1172

1173
  d_rwtxn->txn->commit();
1,091✔
1174
  d_rwtxn.reset();
1,091✔
1175
  return true;
1,091✔
1176
}
1,091✔
1177

1178
bool LMDBBackend::abortTransaction()
1179
{
49✔
1180
  // cout<<"Abort transaction"<<endl;
1181
  if (!d_rwtxn) {
49!
1182
    throw DBException("Attempt to abort a transaction while there isn't one open");
×
1183
  }
×
1184

1185
  d_rwtxn->txn->abort();
49✔
1186
  d_rwtxn.reset();
49✔
1187

1188
  return true;
49✔
1189
}
49✔
1190

1191
// d_rwtxn must be set here
1192
bool LMDBBackend::feedRecord(const DNSResourceRecord& r, const DNSName& ordername, bool ordernameIsNSEC3)
1193
{
427,959✔
1194
  LMDBResourceRecord lrr(r);
427,959✔
1195
  lrr.qname.makeUsRelative(d_transactiondomain);
427,959✔
1196
  lrr.content = serializeContent(lrr.qtype.getCode(), r.qname, lrr.content);
427,959✔
1197

1198
  compoundOrdername co;
427,959✔
1199
  string matchName = co(lrr.domain_id, lrr.qname, lrr.qtype.getCode());
427,959✔
1200

1201
  string rrs;
427,959✔
1202
  MDBOutVal _rrs;
427,959✔
1203
  if (!d_rwtxn->txn->get(d_rwtxn->db->dbi, matchName, _rrs)) {
427,959✔
1204
    rrs = _rrs.get<string>();
22,426✔
1205
  }
22,426✔
1206

1207
  rrs += serializeToBuffer(lrr);
427,959✔
1208

1209
  d_rwtxn->txn->put(d_rwtxn->db->dbi, matchName, rrs);
427,959✔
1210

1211
  if (ordernameIsNSEC3 && !ordername.empty()) {
427,959!
1212
    MDBOutVal val;
40,787✔
1213
    if (d_rwtxn->txn->get(d_rwtxn->db->dbi, co(lrr.domain_id, lrr.qname, QType::NSEC3), val)) {
40,787✔
1214
      lrr.ttl = 0;
40,391✔
1215
      lrr.content = lrr.qname.toDNSStringLC();
40,391✔
1216
      lrr.auth = 0;
40,391✔
1217
      string ser = serializeToBuffer(lrr);
40,391✔
1218
      d_rwtxn->txn->put(d_rwtxn->db->dbi, co(lrr.domain_id, ordername, QType::NSEC3), ser);
40,391✔
1219

1220
      lrr.ttl = 1;
40,391✔
1221
      lrr.content = ordername.toDNSString();
40,391✔
1222
      ser = serializeToBuffer(lrr);
40,391✔
1223
      d_rwtxn->txn->put(d_rwtxn->db->dbi, co(lrr.domain_id, lrr.qname, QType::NSEC3), ser);
40,391✔
1224
    }
40,391✔
1225
  }
40,787✔
1226
  return true;
427,959✔
1227
}
427,959✔
1228

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

1239
    std::string ser = serializeToBuffer(lrr);
96✔
1240
    d_rwtxn->txn->put(d_rwtxn->db->dbi, co(domain_id, lrr.qname, QType::ENT), ser);
96✔
1241
  }
96✔
1242
  return true;
22✔
1243
}
22✔
1244

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

1259
    if (!narrow && lrr.auth) {
92!
1260
      lrr.content = lrr.qname.toDNSString();
80✔
1261
      lrr.auth = false;
80✔
1262
      lrr.ordername = false;
80✔
1263
      ser = serializeToBuffer(lrr);
80✔
1264

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

1268
      lrr.ttl = 1;
80✔
1269
      lrr.content = ordername.toDNSString();
80✔
1270
      ser = serializeToBuffer(lrr);
80✔
1271
      d_rwtxn->txn->put(d_rwtxn->db->dbi, co(domain_id, lrr.qname, QType::NSEC3), ser);
80✔
1272
    }
80✔
1273
  }
92✔
1274
  return true;
18✔
1275
}
18✔
1276

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

1294
  DomainInfo di;
362✔
1295
  if (!d_tdomains->getROTransaction().get(domain_id, di)) {
362!
1296
    return false;
×
1297
  }
×
1298

1299
  DNSName relative = qname.makeRelative(di.zone);
362✔
1300
  compoundOrdername co;
362✔
1301
  string match;
362✔
1302
  if (qt.getCode() == QType::ANY) {
362✔
1303
    match = co(domain_id, relative);
10✔
1304
    deleteDomainRecords(*txn, QType::ANY, match);
10✔
1305
    // Update key if insertions are to follow
1306
    if (!rrset.empty()) {
10!
1307
      match = co(domain_id, relative, rrset.front().qtype.getCode());
×
1308
    }
×
1309
  }
10✔
1310
  else {
352✔
1311
    auto cursor = txn->txn->getCursor(txn->db->dbi);
352✔
1312
    MDBOutVal key{};
352✔
1313
    MDBOutVal val{};
352✔
1314
    match = co(domain_id, relative, qt.getCode());
352✔
1315
    // There should be at most one exact match here.
1316
    if (cursor.find(match, key, val) == 0) {
352✔
1317
      cursor.del(key);
144✔
1318
    }
144✔
1319
  }
352✔
1320

1321
  if (!rrset.empty()) {
362✔
1322
    vector<LMDBResourceRecord> adjustedRRSet;
350✔
1323
    adjustedRRSet.reserve(rrset.size());
350✔
1324
    for (const auto& rr : rrset) {
361✔
1325
      LMDBResourceRecord lrr(rr);
361✔
1326
      lrr.content = serializeContent(lrr.qtype.getCode(), lrr.qname, lrr.content);
361✔
1327
      lrr.qname.makeUsRelative(di.zone);
361✔
1328

1329
      adjustedRRSet.emplace_back(lrr);
361✔
1330
    }
361✔
1331
    txn->txn->put(txn->db->dbi, match, serializeToBuffer(adjustedRRSet));
350✔
1332
  }
350✔
1333

1334
  if (needCommit)
362!
1335
    txn->txn->commit();
×
1336

1337
  return true;
362✔
1338
}
362✔
1339

1340
// NOLINTNEXTLINE(readability-identifier-length)
1341
bool LMDBBackend::replaceComments([[maybe_unused]] domainid_t domain_id, [[maybe_unused]] const DNSName& qname, [[maybe_unused]] const QType& qt, const vector<Comment>& comments)
1342
{
2✔
1343
  // if the vector is empty, good, that's what we do here (LMDB does not store comments)
1344
  // if it's not, report failure
1345
  return comments.empty();
2✔
1346
}
2✔
1347

1348
// FIXME: this is not very efficient
1349
static DNSName keyUnconv(std::string& instr)
1350
{
182✔
1351
  // instr is now com0example0
1352
  vector<string> labels;
182✔
1353
  boost::split(labels, instr, [](char chr) { return chr == '\0'; });
2,549✔
1354

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

1358
  if (labels.size() == 1 && labels[0].empty()) {
182!
1359
    // this is the root
1360
    return g_rootdnsname;
5✔
1361
  }
5✔
1362

1363
  DNSName tmp;
177✔
1364

1365
  for (auto const& label : labels) {
389✔
1366
    tmp.appendRawLabel(label);
389✔
1367
  }
389✔
1368
  return tmp.labelReverse();
177✔
1369
}
182✔
1370

1371
static std::string makeBadDataExceptionMessage(const std::string& where, std::exception& exc, MDBOutVal& key, MDBOutVal& val)
1372
{
×
1373
  ostringstream msg;
×
1374
  msg << "during " << where << ", got exception (" << exc.what() << "), ";
×
1375
  msg << "key: " << makeHexDump(key.getNoStripHeader<string>()) << ", ";
×
1376
  msg << "value: " << makeHexDump(val.get<string>());
×
1377

1378
  return msg.str();
×
1379
}
×
1380

1381
void LMDBBackend::viewList(vector<string>& result)
1382
{
43✔
1383
  auto txn = d_tdomains->getEnv()->getROTransaction();
43✔
1384

1385
  auto cursor = txn->getROCursor(d_tviews);
43✔
1386

1387
  MDBOutVal key{}; // <view, dnsname>
43✔
1388
  MDBOutVal val{}; // <variant>
43✔
1389

1390
  auto ret = cursor.first(key, val);
43✔
1391

1392
  if (ret == MDB_NOTFOUND) {
43✔
1393
    return;
11✔
1394
  }
11✔
1395

1396
  do {
82✔
1397
    string view;
82✔
1398
    string zone;
82✔
1399
    try {
82✔
1400
      std::tie(view, zone) = splitField(key.getNoStripHeader<string>(), '\x0');
82✔
1401
      auto variant = val.get<string>();
82✔
1402
      result.push_back(view);
82✔
1403
    }
82✔
1404
    catch (std::exception& e) {
82✔
1405
      throw PDNSException(makeBadDataExceptionMessage("viewList", e, key, val));
×
1406
    }
×
1407

1408
    string inkey{view + string(1, (char)1)};
82✔
1409
    MDBInVal bound{inkey};
82✔
1410
    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✔
1411
  } while (ret != MDB_NOTFOUND);
82✔
1412
}
32✔
1413

1414
void LMDBBackend::viewListZones(const string& inview, vector<ZoneName>& result)
1415
{
84✔
1416
  result.clear();
84✔
1417

1418
  auto txn = d_tdomains->getEnv()->getROTransaction();
84✔
1419

1420
  auto cursor = txn->getROCursor(d_tviews);
84✔
1421

1422
  string inkey{inview + string(1, (char)0)};
84✔
1423
  MDBInVal prefix{inkey};
84✔
1424
  MDBOutVal key{}; // <view, dnsname>
84✔
1425
  MDBOutVal val{}; // <variant>
84✔
1426

1427
  auto ret = cursor.prefix(prefix, key, val);
84✔
1428

1429
  if (ret == MDB_NOTFOUND) {
84✔
1430
    return;
2✔
1431
  }
2✔
1432

1433
  do {
182✔
1434
    try {
182✔
1435
      auto [view, _zone] = splitField(key.getNoStripHeader<string>(), '\x0');
182✔
1436
      auto variant = val.get<string>();
182✔
1437
      auto zone = keyUnconv(_zone);
182✔
1438
      result.emplace_back(ZoneName(zone, variant));
182✔
1439
    }
182✔
1440
    catch (std::exception& e) {
182✔
1441
      throw PDNSException(makeBadDataExceptionMessage("viewListZones", e, key, val));
×
1442
    }
×
1443

1444
    ret = cursor.next(key, val);
182✔
1445
  } while (ret != MDB_NOTFOUND);
182✔
1446
}
82✔
1447

1448
// TODO: make this add-or-del to reduce code duplication?
1449
bool LMDBBackend::viewAddZone(const string& view, const ZoneName& zone)
1450
{
192✔
1451
  auto txn = d_tdomains->getEnv()->getRWTransaction();
192✔
1452

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

1456
  txn->put(d_tviews, key, val);
192✔
1457
  txn->commit();
192✔
1458

1459
  return true;
192✔
1460
}
192✔
1461

1462
bool LMDBBackend::viewDelZone(const string& view, const ZoneName& zone)
1463
{
12✔
1464
  auto txn = d_tdomains->getEnv()->getRWTransaction();
12✔
1465

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

1469
  txn->del(d_tviews, key);
12✔
1470
  txn->commit();
12✔
1471

1472
  return true;
12✔
1473
}
12✔
1474

1475
bool LMDBBackend::networkSet(const Netmask& net, std::string& view)
1476
{
86✔
1477
  auto txn = d_tdomains->getEnv()->getRWTransaction();
86✔
1478

1479
  if (view.empty()) {
86✔
1480
    txn->del(d_tnetworks, net.toByteString());
1✔
1481
  }
1✔
1482
  else {
85✔
1483
    txn->put(d_tnetworks, net.toByteString(), view);
85✔
1484
  }
85✔
1485
  txn->commit();
86✔
1486

1487
  return true;
86✔
1488
}
86✔
1489

1490
bool LMDBBackend::networkList(vector<pair<Netmask, string>>& networks)
1491
{
33✔
1492
  networks.clear();
33✔
1493

1494
  auto txn = d_tdomains->getEnv()->getROTransaction();
33✔
1495

1496
  auto cursor = txn->getROCursor(d_tnetworks);
33✔
1497

1498
  MDBOutVal netval{};
33✔
1499
  MDBOutVal viewval{};
33✔
1500

1501
  auto ret = cursor.first(netval, viewval);
33✔
1502

1503
  if (ret == MDB_NOTFOUND) {
33✔
1504
    return true;
11✔
1505
  }
11✔
1506

1507
  do {
52✔
1508
    try {
52✔
1509
      auto net = Netmask(netval.getNoStripHeader<string>(), Netmask::byteString);
52✔
1510
      auto view = viewval.get<string>();
52✔
1511
      networks.emplace_back(std::make_pair(net, view));
52✔
1512
    }
52✔
1513
    catch (std::exception& e) {
52✔
1514
      throw PDNSException(makeBadDataExceptionMessage("networkList", e, netval, viewval));
×
1515
    }
×
1516

1517
    ret = cursor.next(netval, viewval);
52✔
1518
  } while (ret != MDB_NOTFOUND);
52✔
1519

1520
  return true;
22✔
1521
}
22✔
1522

1523
// tempting to templatize these two functions but the pain is not worth it
1524
// NOLINTNEXTLINE(readability-identifier-length)
1525
std::shared_ptr<LMDBBackend::RecordsRWTransaction> LMDBBackend::getRecordsRWTransaction(domainid_t id)
1526
{
1,163✔
1527
  auto& shard = d_trecords[id % s_shards];
1,163✔
1528
  if (!shard.env) {
1,163✔
1529
    shard.env = getMDBEnv((getArg("filename") + "-" + std::to_string(id % s_shards)).c_str(),
129✔
1530
                          MDB_NOSUBDIR | MDB_NORDAHEAD | d_asyncFlag, 0600, d_mapsize);
129✔
1531
    shard.dbi = shard.env->openDB("records_v5", MDB_CREATE);
129✔
1532
  }
129✔
1533
  auto ret = std::make_shared<RecordsRWTransaction>(shard.env->getRWTransaction());
1,163✔
1534
  ret->db = std::make_shared<RecordsDB>(shard);
1,163✔
1535

1536
  return ret;
1,163✔
1537
}
1,163✔
1538

1539
// NOLINTNEXTLINE(readability-identifier-length)
1540
std::shared_ptr<LMDBBackend::RecordsROTransaction> LMDBBackend::getRecordsROTransaction(domainid_t id, const std::shared_ptr<LMDBBackend::RecordsRWTransaction>& rwtxn)
1541
{
18,799✔
1542
  auto& shard = d_trecords[id % s_shards];
18,799✔
1543
  if (!shard.env) {
18,799✔
1544
    if (rwtxn) {
4,366!
1545
      throw DBException("attempting to start nested transaction without open parent env");
×
1546
    }
×
1547
    shard.env = getMDBEnv((getArg("filename") + "-" + std::to_string(id % s_shards)).c_str(),
4,366✔
1548
                          MDB_NOSUBDIR | MDB_NORDAHEAD | d_asyncFlag, 0600, d_mapsize);
4,366✔
1549
    shard.dbi = shard.env->openDB("records_v5", MDB_CREATE);
4,366✔
1550
  }
4,366✔
1551

1552
  if (rwtxn) {
18,799✔
1553
    auto ret = std::make_shared<RecordsROTransaction>(rwtxn->txn->getROTransaction());
1,040✔
1554
    ret->db = std::make_shared<RecordsDB>(shard);
1,040✔
1555
    return ret;
1,040✔
1556
  }
1,040✔
1557
  else {
17,759✔
1558
    auto ret = std::make_shared<RecordsROTransaction>(shard.env->getROTransaction());
17,759✔
1559
    ret->db = std::make_shared<RecordsDB>(shard);
17,759✔
1560
    return ret;
17,759✔
1561
  }
17,759✔
1562
}
18,799✔
1563

1564
bool LMDBBackend::deleteDomain(const ZoneName& domain)
1565
{
31✔
1566
  if (!d_rwtxn) {
31!
1567
    throw DBException(std::string(__PRETTY_FUNCTION__) + " called without a transaction");
×
1568
  }
×
1569

1570
  int transactionDomainId = d_transactiondomainid;
31✔
1571
  ZoneName transactionDomain = d_transactiondomain;
31✔
1572

1573
  abortTransaction();
31✔
1574

1575
  LmdbIdVec idvec;
31✔
1576

1577
  if (!d_handle_dups) {
31!
1578
    // get domain id
1579
    auto txn = d_tdomains->getROTransaction();
31✔
1580

1581
    DomainInfo di;
31✔
1582
    idvec.push_back(txn.get<0>(domain, di));
31✔
1583
  }
31✔
1584
  else {
×
1585
    // this transaction used to be RO.
1586
    // it is now RW to narrow a race window between PowerDNS and Lightning Stream
1587
    // FIXME: turn the entire delete, including this ID scan, into one RW transaction
1588
    // when doing that, first do a short RO check to see if we actually have anything to delete
1589
    auto txn = d_tdomains->getRWTransaction();
×
1590

1591
    txn.get_multi<0>(domain, idvec);
×
1592
  }
×
1593

1594
  for (auto id : idvec) {
31✔
1595

1596
    startTransaction(domain, id);
31✔
1597

1598
    { // Remove metadata
31✔
1599
      auto txn = d_tmeta->getRWTransaction();
31✔
1600
      LmdbIdVec ids;
31✔
1601

1602
      txn.get_multi<0>(domain, ids);
31✔
1603

1604
      for (auto& _id : ids) {
33✔
1605
        txn.del(_id);
11✔
1606
      }
11✔
1607

1608
      txn.commit();
31✔
1609
    }
31✔
1610

1611
    { // Remove cryptokeys
31✔
1612
      auto txn = d_tkdb->getRWTransaction();
31✔
1613
      LmdbIdVec ids;
31✔
1614
      txn.get_multi<0>(domain, ids);
31✔
1615

1616
      for (auto _id : ids) {
31!
1617
        txn.del(_id);
×
1618
      }
×
1619

1620
      txn.commit();
31✔
1621
    }
31✔
1622

1623
    // Remove records
1624
    commitTransaction();
31✔
1625

1626
    // Remove zone
1627
    auto txn = d_tdomains->getRWTransaction();
31✔
1628
    txn.del(id);
31✔
1629
    txn.commit();
31✔
1630
  }
31✔
1631

1632
  startTransaction(transactionDomain, transactionDomainId);
31✔
1633

1634
  return true;
31✔
1635
}
31✔
1636

1637
bool LMDBBackend::list(const ZoneName& target, domainid_t /* id */, bool include_disabled)
1638
{
803✔
1639
  d_includedisabled = include_disabled;
803✔
1640

1641
  DomainInfo di;
803✔
1642
  {
803✔
1643
    auto dtxn = d_tdomains->getROTransaction();
803✔
1644
    if ((di.id = dtxn.get<0>(target, di))) {
803!
1645
      // cerr << "Found domain " << target << " on domain_id " << di.id << ", list requested " << id << endl;
1646
    }
803✔
1647
    else {
×
1648
      // cerr << "Did not find " << target << endl;
1649
      return false;
×
1650
    }
×
1651
  }
803✔
1652

1653
  d_rotxn = getRecordsROTransaction(di.id, d_rwtxn);
803✔
1654
  d_txnorder = true;
803✔
1655
  d_getcursor = std::make_shared<MDBROCursor>(d_rotxn->txn->getCursor(d_rotxn->db->dbi));
803✔
1656

1657
  compoundOrdername co;
803✔
1658
  d_matchkey = co(di.id);
803✔
1659

1660
  MDBOutVal key, val;
803✔
1661
  if (d_getcursor->prefix(d_matchkey, key, val) != 0) {
803✔
1662
    d_getcursor.reset();
11✔
1663
  }
11✔
1664

1665
  d_lookupdomain = target;
803✔
1666

1667
  // Make sure we start with fresh data
1668
  d_currentrrset.clear();
803✔
1669
  d_currentrrsetpos = 0;
803✔
1670

1671
  return true;
803✔
1672
}
803✔
1673

1674
void LMDBBackend::lookupInternal(const QType& type, const DNSName& qdomain, domainid_t zoneId, DNSPacket* /* p */, bool include_disabled)
1675
{
6,287✔
1676
  if (d_dolog) {
6,287!
1677
    g_log << Logger::Warning << "Got lookup for " << qdomain << "|" << type.toString() << " in zone " << zoneId << endl;
×
1678
    d_dtime.set();
×
1679
  }
×
1680

1681
  d_includedisabled = include_disabled;
6,287✔
1682

1683
  ZoneName hunt(qdomain);
6,287✔
1684
  DomainInfo di;
6,287✔
1685
  if (zoneId == UnknownDomainID) {
6,287✔
1686
    auto rotxn = d_tdomains->getROTransaction();
826✔
1687

1688
    do {
826✔
1689
      zoneId = rotxn.get<0>(hunt, di);
826✔
1690
    } while (zoneId == 0 && type != QType::SOA && hunt.chopOff());
826!
1691
    if (zoneId == 0) {
826!
1692
      //      cout << "Did not find zone for "<< qdomain<<endl;
1693
      d_getcursor.reset();
×
1694
      return;
×
1695
    }
×
1696
  }
826✔
1697
  else {
5,461✔
1698
    if (!d_tdomains->getROTransaction().get(zoneId, di)) {
5,461✔
1699
      // cout<<"Could not find a zone with id "<<zoneId<<endl;
1700
      d_getcursor.reset();
8✔
1701
      return;
8✔
1702
    }
8✔
1703
    hunt = di.zone;
5,453✔
1704
  }
5,453✔
1705

1706
  DNSName relqname = qdomain.makeRelative(hunt);
6,279✔
1707
  if (relqname.empty()) {
6,279!
1708
    return;
×
1709
  }
×
1710
  // cout<<"get will look for "<<relqname<< " in zone "<<hunt<<" with id "<<zoneId<<" and type "<<type.toString()<<endl;
1711
  d_rotxn = getRecordsROTransaction(zoneId, d_rwtxn);
6,279✔
1712
  d_txnorder = true;
6,279✔
1713

1714
  compoundOrdername co;
6,279✔
1715
  d_getcursor = std::make_shared<MDBROCursor>(d_rotxn->txn->getCursor(d_rotxn->db->dbi));
6,279✔
1716
  MDBOutVal key, val;
6,279✔
1717
  if (type.getCode() == QType::ANY) {
6,279✔
1718
    d_matchkey = co(zoneId, relqname);
4,665✔
1719
  }
4,665✔
1720
  else {
1,614✔
1721
    d_matchkey = co(zoneId, relqname, type.getCode());
1,614✔
1722
  }
1,614✔
1723

1724
  if (d_getcursor->prefix(d_matchkey, key, val) != 0) {
6,279✔
1725
    d_getcursor.reset();
2,383✔
1726
    if (d_dolog) {
2,383!
1727
      g_log << Logger::Warning << "Query " << ((long)(void*)this) << ": " << d_dtime.udiffNoReset() << " us to execute (found nothing)" << endl;
×
1728
    }
×
1729
    return;
2,383✔
1730
  }
2,383✔
1731

1732
  if (d_dolog) {
3,896!
1733
    g_log << Logger::Warning << "Query " << ((long)(void*)this) << ": " << d_dtime.udiffNoReset() << " us to execute" << endl;
×
1734
  }
×
1735

1736
  d_lookupdomain = std::move(hunt);
3,896✔
1737

1738
  // Make sure we start with fresh data
1739
  d_currentrrset.clear();
3,896✔
1740
  d_currentrrsetpos = 0;
3,896✔
1741
}
3,896✔
1742

1743
bool LMDBBackend::get(DNSZoneRecord& zr)
1744
{
407,782✔
1745
  for (;;) {
513,840✔
1746
    // std::cerr<<"d_getcursor="<<d_getcursor<<std::endl;
1747
    if (!d_getcursor) {
513,840✔
1748
      d_rotxn.reset();
7,090✔
1749
      return false;
7,090✔
1750
    }
7,090✔
1751

1752
    string_view key;
506,750✔
1753

1754
    if (d_currentrrset.empty()) {
506,750✔
1755
      d_getcursor->current(d_currentKey, d_currentVal);
501,319✔
1756

1757
      key = d_currentKey.getNoStripHeader<string_view>();
501,319✔
1758
      zr.dr.d_type = compoundOrdername::getQType(key).getCode();
501,319✔
1759

1760
      if (zr.dr.d_type == QType::NSEC3) {
501,319✔
1761
        // Hit a magic NSEC3 skipping
1762
        if (d_getcursor->next(d_currentKey, d_currentVal) != 0) {
106,053✔
1763
          // cerr<<"resetting d_getcursor 1"<<endl;
1764
          d_getcursor.reset();
1,566✔
1765
        }
1,566✔
1766
        continue;
106,053✔
1767
      }
106,053✔
1768

1769
      deserializeFromBuffer(d_currentVal.get<string_view>(), d_currentrrset);
395,266✔
1770
      d_currentrrsetpos = 0;
395,266✔
1771
    }
395,266✔
1772
    else {
5,431✔
1773
      key = d_currentKey.getNoStripHeader<string_view>();
5,431✔
1774
    }
5,431✔
1775
    try {
400,697✔
1776
      const auto& lrr = d_currentrrset.at(d_currentrrsetpos++);
400,697✔
1777

1778
      zr.disabled = lrr.disabled;
400,697✔
1779
      if (!zr.disabled || d_includedisabled) {
400,697✔
1780
        zr.dr.d_name = compoundOrdername::getQName(key) + d_lookupdomain.operator const DNSName&();
400,692✔
1781
        zr.domain_id = compoundOrdername::getDomainID(key);
400,692✔
1782
        zr.dr.d_type = compoundOrdername::getQType(key).getCode();
400,692✔
1783
        zr.dr.d_ttl = lrr.ttl;
400,692✔
1784
        zr.dr.setContent(deserializeContentZR(zr.dr.d_type, zr.dr.d_name, lrr.content));
400,692✔
1785
        zr.auth = lrr.auth;
400,692✔
1786
      }
400,692✔
1787

1788
      if (d_currentrrsetpos >= d_currentrrset.size()) {
400,697✔
1789
        d_currentrrset.clear(); // will invalidate lrr
395,266✔
1790
        if (d_getcursor->next(d_currentKey, d_currentVal) != 0) {
395,266✔
1791
          // cerr<<"resetting d_getcursor 2"<<endl;
1792
          d_getcursor.reset();
3,122✔
1793
        }
3,122✔
1794
      }
395,266✔
1795

1796
      if (zr.disabled && !d_includedisabled) {
400,697✔
1797
        continue;
5✔
1798
      }
5✔
1799
    }
400,697✔
1800
    catch (const std::exception& e) {
400,697✔
1801
      throw PDNSException(e.what());
×
1802
    }
×
1803

1804
    break;
400,692✔
1805
  }
400,697✔
1806

1807
  return true;
400,692✔
1808
}
407,782✔
1809

1810
bool LMDBBackend::get(DNSResourceRecord& rr)
1811
{
309,253✔
1812
  DNSZoneRecord zr;
309,253✔
1813
  if (!get(zr)) {
309,253✔
1814
    return false;
2,233✔
1815
  }
2,233✔
1816

1817
  rr.qname = zr.dr.d_name;
307,020✔
1818
  rr.ttl = zr.dr.d_ttl;
307,020✔
1819
  rr.qtype = zr.dr.d_type;
307,020✔
1820
  rr.content = zr.dr.getContent()->getZoneRepresentation(true);
307,020✔
1821
  rr.domain_id = zr.domain_id;
307,020✔
1822
  rr.auth = zr.auth;
307,020✔
1823
  rr.disabled = zr.disabled;
307,020✔
1824

1825
  return true;
307,020✔
1826
}
309,253✔
1827

1828
bool LMDBBackend::getSerial(DomainInfo& di)
1829
{
3,351✔
1830
  auto txn = getRecordsROTransaction(di.id);
3,351✔
1831
  compoundOrdername co;
3,351✔
1832
  MDBOutVal val;
3,351✔
1833
  if (!txn->txn->get(txn->db->dbi, co(di.id, g_rootdnsname, QType::SOA), val)) {
3,351✔
1834
    LMDBResourceRecord lrr;
2,639✔
1835
    deserializeFromBuffer(val.get<string_view>(), lrr);
2,639✔
1836
    if (lrr.content.size() >= 5 * sizeof(uint32_t)) {
2,639!
1837
      uint32_t serial;
2,639✔
1838
      // a SOA has five 32 bit fields, the first of which is the serial
1839
      // there are two variable length names before the serial, so we calculate from the back
1840
      memcpy(&serial, &lrr.content[lrr.content.size() - (5 * sizeof(uint32_t))], sizeof(serial));
2,639✔
1841
      di.serial = ntohl(serial);
2,639✔
1842
    }
2,639✔
1843
    return !lrr.disabled;
2,639✔
1844
  }
2,639✔
1845
  return false;
712✔
1846
}
3,351✔
1847

1848
bool LMDBBackend::getDomainInfo(const ZoneName& domain, DomainInfo& info, bool getserial)
1849
{
3,136✔
1850
  // If caller asks about a zone with variant, but views are not enabled,
1851
  // punt.
1852
  if (domain.hasVariant() && !d_views) {
3,136!
1853
    return false;
×
1854
  }
×
1855

1856
  {
3,136✔
1857
    auto txn = d_tdomains->getROTransaction();
3,136✔
1858
    // auto range = txn.prefix_range<0>(domain);
1859

1860
    // bool found = false;
1861

1862
    // for (auto& iter = range.first ; iter != range.second; ++iter) {
1863
    //   found = true;
1864
    //   info.id = iter.getID();
1865
    //   info.backend = this;
1866
    // }
1867

1868
    // if (!found) {
1869
    //   return false;
1870
    // }
1871
    if ((info.id = txn.get<0>(domain, info)) == 0) {
3,136✔
1872
      return false;
510✔
1873
    }
510✔
1874

1875
    info.backend = this;
2,626✔
1876
  }
2,626✔
1877

1878
  if (getserial) {
2,626✔
1879
    getSerial(info);
1,945✔
1880
  }
1,945✔
1881

1882
  return true;
2,626✔
1883
}
3,136✔
1884

1885
int LMDBBackend::genChangeDomain(const ZoneName& domain, const std::function<void(DomainInfo&)>& func)
1886
{
704✔
1887
  auto txn = d_tdomains->getRWTransaction();
704✔
1888

1889
  DomainInfo di;
704✔
1890

1891
  auto id = txn.get<0>(domain, di);
704✔
1892
  func(di);
704✔
1893
  txn.put(di, id);
704✔
1894

1895
  txn.commit();
704✔
1896
  return true;
704✔
1897
}
704✔
1898

1899
// NOLINTNEXTLINE(readability-identifier-length)
1900
int LMDBBackend::genChangeDomain(domainid_t id, const std::function<void(DomainInfo&)>& func)
1901
{
91✔
1902
  DomainInfo di;
91✔
1903

1904
  auto txn = d_tdomains->getRWTransaction();
91✔
1905

1906
  if (!txn.get(id, di))
91!
1907
    return false;
×
1908

1909
  func(di);
91✔
1910

1911
  txn.put(di, id);
91✔
1912

1913
  txn.commit();
91✔
1914
  return true;
91✔
1915
}
91✔
1916

1917
bool LMDBBackend::setKind(const ZoneName& domain, const DomainInfo::DomainKind kind)
1918
{
293✔
1919
  return genChangeDomain(domain, [kind](DomainInfo& di) {
293✔
1920
    di.kind = kind;
293✔
1921
  });
293✔
1922
}
293✔
1923

1924
bool LMDBBackend::setAccount(const ZoneName& domain, const std::string& account)
1925
{
1✔
1926
  return genChangeDomain(domain, [account](DomainInfo& di) {
1✔
1927
    di.account = account;
1✔
1928
  });
1✔
1929
}
1✔
1930

1931
bool LMDBBackend::setPrimaries(const ZoneName& domain, const vector<ComboAddress>& primaries)
1932
{
71✔
1933
  return genChangeDomain(domain, [&primaries](DomainInfo& di) {
71✔
1934
    di.primaries = primaries;
71✔
1935
  });
71✔
1936
}
71✔
1937

1938
bool LMDBBackend::createDomain(const ZoneName& domain, const DomainInfo::DomainKind kind, const vector<ComboAddress>& primaries, const string& account)
1939
{
495✔
1940
  DomainInfo di;
495✔
1941

1942
  {
495✔
1943
    auto txn = d_tdomains->getRWTransaction();
495✔
1944
    if (txn.get<0>(domain, di)) {
495!
1945
      throw DBException("Domain '" + domain.toLogString() + "' exists already");
×
1946
    }
×
1947

1948
    di.zone = domain;
495✔
1949
    di.kind = kind;
495✔
1950
    di.primaries = primaries;
495✔
1951
    di.account = account;
495✔
1952

1953
    txn.put(di, 0, d_random_ids, domain.hash());
495✔
1954
    txn.commit();
495✔
1955
  }
495✔
1956

1957
  return true;
×
1958
}
495✔
1959

1960
void LMDBBackend::getAllDomainsFiltered(vector<DomainInfo>* domains, const std::function<bool(DomainInfo&)>& allow)
1961
{
143✔
1962
  auto txn = d_tdomains->getROTransaction();
143✔
1963
  if (d_handle_dups) {
143!
1964
    map<ZoneName, DomainInfo> zonemap;
×
1965
    set<ZoneName> dups;
×
1966

1967
    for (auto iter = txn.begin(); iter != txn.end(); ++iter) {
×
1968
      DomainInfo di = *iter;
×
1969
      di.id = iter.getID();
×
1970
      di.backend = this;
×
1971

1972
      if (!zonemap.emplace(di.zone, di).second) {
×
1973
        dups.insert(di.zone);
×
1974
      }
×
1975
    }
×
1976

1977
    for (const auto& zone : dups) {
×
1978
      DomainInfo di;
×
1979

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

1983
      if (di.id == 0) {
×
1984
        // .get actually found nothing for us
1985
        continue;
×
1986
      }
×
1987

1988
      di.backend = this;
×
1989
      zonemap[di.zone] = di;
×
1990
    }
×
1991

1992
    for (auto& [k, v] : zonemap) {
×
1993
      if (allow(v)) {
×
1994
        domains->push_back(std::move(v));
×
1995
      }
×
1996
    }
×
1997
  }
×
1998
  else {
143✔
1999
    for (auto iter = txn.begin(); iter != txn.end(); ++iter) {
1,936✔
2000
      DomainInfo di = *iter;
1,793✔
2001
      di.id = iter.getID();
1,793✔
2002
      di.backend = this;
1,793✔
2003

2004
      if (allow(di)) {
1,793✔
2005
        domains->push_back(di);
1,438✔
2006
      }
1,438✔
2007
    }
1,793✔
2008
  }
143✔
2009
}
143✔
2010

2011
void LMDBBackend::getAllDomains(vector<DomainInfo>* domains, bool /* doSerial */, bool include_disabled)
2012
{
116✔
2013
  getAllDomainsFiltered(domains, [this, include_disabled](DomainInfo& di) {
1,406✔
2014
    if (!getSerial(di) && !include_disabled) {
1,406!
2015
      return false;
×
2016
    }
×
2017

2018
    // Skip domains with variants if views are disabled.
2019
    if (di.zone.hasVariant() && !d_views) {
1,406!
2020
      return false;
×
2021
    }
×
2022

2023
    return true;
1,406✔
2024
  });
1,406✔
2025
}
116✔
2026

2027
void LMDBBackend::getUnfreshSecondaryInfos(vector<DomainInfo>* domains)
2028
{
11✔
2029
  uint32_t serial;
11✔
2030
  time_t now = time(0);
11✔
2031
  LMDBResourceRecord lrr;
11✔
2032
  soatimes st;
11✔
2033

2034
  getAllDomainsFiltered(domains, [this, &lrr, &st, &now, &serial](DomainInfo& di) {
83✔
2035
    if (!di.isSecondaryType()) {
83!
2036
      return false;
×
2037
    }
×
2038

2039
    auto txn2 = getRecordsROTransaction(di.id);
83✔
2040
    compoundOrdername co;
83✔
2041
    MDBOutVal val;
83✔
2042
    if (!txn2->txn->get(txn2->db->dbi, co(di.id, g_rootdnsname, QType::SOA), val)) {
83✔
2043
      deserializeFromBuffer(val.get<string_view>(), lrr);
51✔
2044
      memcpy(&st, &lrr.content[lrr.content.size() - sizeof(soatimes)], sizeof(soatimes));
51✔
2045
      if ((time_t)(di.last_check + ntohl(st.refresh)) > now) { // still fresh
51!
2046
        return false;
51✔
2047
      }
51✔
2048
      serial = ntohl(st.serial);
×
2049
    }
×
2050
    else {
32✔
2051
      serial = 0;
32✔
2052
    }
32✔
2053

2054
    return true;
32✔
2055
  });
83✔
2056
}
11✔
2057

2058
void LMDBBackend::setStale(domainid_t domain_id)
2059
{
2✔
2060
  genChangeDomain(domain_id, [](DomainInfo& di) {
2✔
2061
    di.last_check = 0;
2✔
2062
  });
2✔
2063
}
2✔
2064

2065
void LMDBBackend::setFresh(domainid_t domain_id)
2066
{
88✔
2067
  genChangeDomain(domain_id, [](DomainInfo& di) {
88✔
2068
    di.last_check = time(nullptr);
88✔
2069
  });
88✔
2070
}
88✔
2071

2072
void LMDBBackend::getUpdatedPrimaries(vector<DomainInfo>& updatedDomains, std::unordered_set<DNSName>& catalogs, CatalogHashMap& catalogHashes)
2073
{
×
2074
  CatalogInfo ci;
×
2075

2076
  getAllDomainsFiltered(&(updatedDomains), [this, &catalogs, &catalogHashes, &ci](DomainInfo& di) {
×
2077
    if (!di.isPrimaryType()) {
×
2078
      return false;
×
2079
    }
×
2080

2081
    if (di.kind == DomainInfo::Producer) {
×
2082
      catalogs.insert(di.zone.operator const DNSName&());
×
2083
      catalogHashes[di.zone].process("\0");
×
2084
      return false; // Producer fresness check is performed elsewhere
×
2085
    }
×
2086

2087
    if (!di.catalog.empty()) {
×
2088
      ci.fromJson(di.options, CatalogInfo::CatalogType::Producer);
×
2089
      ci.updateHash(catalogHashes, di);
×
2090
    }
×
2091

2092
    if (getSerial(di) && di.serial != di.notified_serial) {
×
2093
      di.backend = this;
×
2094
      return true;
×
2095
    }
×
2096

2097
    return false;
×
2098
  });
×
2099
}
×
2100

2101
void LMDBBackend::setNotified(domainid_t domain_id, uint32_t serial)
2102
{
1✔
2103
  genChangeDomain(domain_id, [serial](DomainInfo& di) {
1✔
2104
    di.notified_serial = serial;
1✔
2105
  });
1✔
2106
}
1✔
2107

2108
class getCatalogMembersReturnFalseException : std::runtime_error
2109
{
2110
public:
2111
  getCatalogMembersReturnFalseException() :
2112
    std::runtime_error("getCatalogMembers should return false") {}
×
2113
};
2114

2115
bool LMDBBackend::getCatalogMembers(const ZoneName& catalog, vector<CatalogInfo>& members, CatalogInfo::CatalogType type)
2116
{
16✔
2117
  vector<DomainInfo> scratch;
16✔
2118

2119
  try {
16✔
2120
    getAllDomainsFiltered(&scratch, [&catalog, &members, &type](DomainInfo& di) {
304✔
2121
      if ((type == CatalogInfo::CatalogType::Producer && di.kind != DomainInfo::Primary) || (type == CatalogInfo::CatalogType::Consumer && di.kind != DomainInfo::Secondary) || di.catalog != catalog) {
304✔
2122
        return false;
100✔
2123
      }
100✔
2124

2125
      CatalogInfo ci;
204✔
2126
      ci.d_id = di.id;
204✔
2127
      ci.d_zone = di.zone;
204✔
2128
      ci.d_primaries = di.primaries;
204✔
2129
      try {
204✔
2130
        ci.fromJson(di.options, type);
204✔
2131
      }
204✔
2132
      catch (const std::runtime_error& e) {
204✔
2133
        g_log << Logger::Warning << __PRETTY_FUNCTION__ << " options '" << di.options << "' for zone '" << di.zone << "' is no valid JSON: " << e.what() << endl;
×
2134
        members.clear();
×
2135
        throw getCatalogMembersReturnFalseException();
×
2136
      }
×
2137
      members.emplace_back(ci);
204✔
2138

2139
      return false;
204✔
2140
    });
204✔
2141
  }
16✔
2142
  catch (const getCatalogMembersReturnFalseException& e) {
16✔
2143
    return false;
×
2144
  }
×
2145
  return true;
16✔
2146
}
16✔
2147

2148
bool LMDBBackend::setOptions(const ZoneName& domain, const std::string& options)
2149
{
96✔
2150
  return genChangeDomain(domain, [options](DomainInfo& di) {
96✔
2151
    di.options = options;
96✔
2152
  });
96✔
2153
}
96✔
2154

2155
bool LMDBBackend::setCatalog(const ZoneName& domain, const ZoneName& catalog)
2156
{
243✔
2157
  return genChangeDomain(domain, [catalog](DomainInfo& di) {
243✔
2158
    di.catalog = catalog;
243✔
2159
  });
243✔
2160
}
243✔
2161

2162
bool LMDBBackend::getAllDomainMetadata(const ZoneName& name, std::map<std::string, std::vector<std::string>>& meta)
2163
{
3,992✔
2164
  meta.clear();
3,992✔
2165
  auto txn = d_tmeta->getROTransaction();
3,992✔
2166
  LmdbIdVec ids;
3,992✔
2167
  txn.get_multi<0>(name, ids);
3,992✔
2168

2169
  DomainMeta dm;
3,992✔
2170
  // cerr<<"getAllDomainMetadata start"<<endl;
2171
  for (auto id : ids) {
4,340✔
2172
    if (txn.get(id, dm)) {
3,899!
2173
      meta[dm.key].push_back(dm.value);
3,899✔
2174
    }
3,899✔
2175
  }
3,899✔
2176
  return true;
3,992✔
2177
}
3,992✔
2178

2179
bool LMDBBackend::setDomainMetadata(const ZoneName& name, const std::string& kind, const std::vector<std::string>& meta)
2180
{
569✔
2181
  auto txn = d_tmeta->getRWTransaction();
569✔
2182

2183
  LmdbIdVec ids;
569✔
2184
  txn.get_multi<0>(name, ids);
569✔
2185

2186
  DomainMeta dmeta;
569✔
2187
  for (auto id : ids) {
569✔
2188
    if (txn.get(id, dmeta)) {
383!
2189
      if (dmeta.key == kind) {
383✔
2190
        // cerr<<"delete"<<endl;
2191
        txn.del(id);
32✔
2192
      }
32✔
2193
    }
383✔
2194
  }
383✔
2195

2196
  for (const auto& m : meta) {
569✔
2197
    DomainMeta dm{name, kind, m};
444✔
2198
    txn.put(dm, 0, d_random_ids, burtleCI(kind, name.hash()));
444✔
2199
  }
444✔
2200
  txn.commit();
569✔
2201
  return true;
569✔
2202
}
569✔
2203

2204
bool LMDBBackend::getDomainKeys(const ZoneName& name, std::vector<KeyData>& keys)
2205
{
1,229✔
2206
  auto txn = d_tkdb->getROTransaction();
1,229✔
2207
  LmdbIdVec ids;
1,229✔
2208
  txn.get_multi<0>(name, ids);
1,229✔
2209

2210
  KeyDataDB key;
1,229✔
2211

2212
  for (auto id : ids) {
1,229✔
2213
    if (txn.get(id, key)) {
701!
2214
      KeyData kd{key.content, id, key.flags, key.active, key.published};
701✔
2215
      keys.emplace_back(std::move(kd));
701✔
2216
    }
701✔
2217
  }
701✔
2218

2219
  return true;
1,229✔
2220
}
1,229✔
2221

2222
bool LMDBBackend::removeDomainKey(const ZoneName& name, unsigned int keyId)
2223
{
29✔
2224
  auto txn = d_tkdb->getRWTransaction();
29✔
2225
  KeyDataDB kdb;
29✔
2226
  if (txn.get(keyId, kdb)) {
29✔
2227
    if (kdb.domain == name) {
20!
2228
      txn.del(keyId);
20✔
2229
      txn.commit();
20✔
2230
      return true;
20✔
2231
    }
20✔
2232
  }
20✔
2233
  // cout << "??? wanted to remove domain key for domain "<<name<<" with id "<<keyId<<", could not find it"<<endl;
2234
  return true;
9✔
2235
}
29✔
2236

2237
bool LMDBBackend::addDomainKey(const ZoneName& name, const KeyData& key, int64_t& keyId)
2238
{
210✔
2239
  auto txn = d_tkdb->getRWTransaction();
210✔
2240
  KeyDataDB kdb{name, key.content, key.flags, key.active, key.published};
210✔
2241

2242
  // all this just to get the tag - while most of our callers (except b2b-migrate) already have a dpk
2243
  DNSKEYRecordContent dkrc;
210✔
2244
  auto keyEngine = shared_ptr<DNSCryptoKeyEngine>(DNSCryptoKeyEngine::makeFromISCString(dkrc, key.content));
210✔
2245
  DNSSECPrivateKey dpk;
210✔
2246
  dpk.setKey(keyEngine, key.flags);
210✔
2247
  auto tag = dpk.getDNSKEY().getTag();
210✔
2248

2249
  keyId = txn.put(kdb, 0, d_random_ids, name.hash(tag));
210✔
2250
  txn.commit();
210✔
2251

2252
  return true;
210✔
2253
}
210✔
2254

2255
bool LMDBBackend::activateDomainKey(const ZoneName& name, unsigned int keyId)
2256
{
6✔
2257
  auto txn = d_tkdb->getRWTransaction();
6✔
2258
  KeyDataDB kdb;
6✔
2259
  if (txn.get(keyId, kdb)) {
6!
2260
    if (kdb.domain == name) {
6!
2261
      txn.modify(keyId, [](KeyDataDB& kdbarg) {
6✔
2262
        kdbarg.active = true;
6✔
2263
      });
6✔
2264
      txn.commit();
6✔
2265
      return true;
6✔
2266
    }
6✔
2267
  }
6✔
2268

2269
  // cout << "??? wanted to activate domain key for domain "<<name<<" with id "<<keyId<<", could not find it"<<endl;
2270
  return true;
×
2271
}
6✔
2272

2273
bool LMDBBackend::deactivateDomainKey(const ZoneName& name, unsigned int keyId)
2274
{
4✔
2275
  auto txn = d_tkdb->getRWTransaction();
4✔
2276
  KeyDataDB kdb;
4✔
2277
  if (txn.get(keyId, kdb)) {
4!
2278
    if (kdb.domain == name) {
4!
2279
      txn.modify(keyId, [](KeyDataDB& kdbarg) {
4✔
2280
        kdbarg.active = false;
4✔
2281
      });
4✔
2282
      txn.commit();
4✔
2283
      return true;
4✔
2284
    }
4✔
2285
  }
4✔
2286
  // cout << "??? wanted to deactivate domain key for domain "<<name<<" with id "<<keyId<<", could not find it"<<endl;
2287
  return true;
×
2288
}
4✔
2289

2290
bool LMDBBackend::publishDomainKey(const ZoneName& name, unsigned int keyId)
2291
{
6✔
2292
  auto txn = d_tkdb->getRWTransaction();
6✔
2293
  KeyDataDB kdb;
6✔
2294
  if (txn.get(keyId, kdb)) {
6!
2295
    if (kdb.domain == name) {
6!
2296
      txn.modify(keyId, [](KeyDataDB& kdbarg) {
6✔
2297
        kdbarg.published = true;
6✔
2298
      });
6✔
2299
      txn.commit();
6✔
2300
      return true;
6✔
2301
    }
6✔
2302
  }
6✔
2303

2304
  // cout << "??? wanted to hide domain key for domain "<<name<<" with id "<<keyId<<", could not find it"<<endl;
2305
  return true;
×
2306
}
6✔
2307

2308
bool LMDBBackend::unpublishDomainKey(const ZoneName& name, unsigned int keyId)
2309
{
10✔
2310
  auto txn = d_tkdb->getRWTransaction();
10✔
2311
  KeyDataDB kdb;
10✔
2312
  if (txn.get(keyId, kdb)) {
10!
2313
    if (kdb.domain == name) {
10!
2314
      txn.modify(keyId, [](KeyDataDB& kdbarg) {
10✔
2315
        kdbarg.published = false;
10✔
2316
      });
10✔
2317
      txn.commit();
10✔
2318
      return true;
10✔
2319
    }
10✔
2320
  }
10✔
2321
  // cout << "??? wanted to unhide domain key for domain "<<name<<" with id "<<keyId<<", could not find it"<<endl;
2322
  return true;
×
2323
}
10✔
2324

2325
// NOLINTNEXTLINE(readability-function-cognitive-complexity,readability-identifier-length)
2326
bool LMDBBackend::getBeforeAndAfterNamesAbsolute(domainid_t id, const DNSName& qname, DNSName& unhashed, DNSName& before, DNSName& after)
2327
{
5,860✔
2328
  //  cout << __PRETTY_FUNCTION__<< ": "<<id <<", "<<qname << " " << unhashed<<endl;
2329

2330
  DomainInfo di;
5,860✔
2331
  if (!d_tdomains->getROTransaction().get(id, di)) {
5,860!
2332
    // domain does not exist, tough luck
2333
    return false;
×
2334
  }
×
2335
  // cout <<"Zone: "<<di.zone<<endl;
2336

2337
  compoundOrdername co;
5,860✔
2338
  auto txn = getRecordsROTransaction(id);
5,860✔
2339

2340
  auto cursor = txn->txn->getCursor(txn->db->dbi);
5,860✔
2341
  MDBOutVal key, val;
5,860✔
2342

2343
  LMDBResourceRecord lrr;
5,860✔
2344

2345
  string matchkey = co(id, qname, QType::NSEC3);
5,860✔
2346
  if (cursor.lower_bound(matchkey, key, val)) {
5,860✔
2347
    // this is beyond the end of the database
2348
    // cout << "Beyond end of database!" << endl;
2349
    cursor.last(key, val);
181✔
2350

2351
    for (;;) {
743✔
2352
      if (co.getDomainID(key.getNoStripHeader<StringView>()) != id) {
743!
2353
        // cout<<"Last record also not part of this zone!"<<endl;
2354
        //  this implies something is wrong in the database, nothing we can do
2355
        return false;
×
2356
      }
×
2357

2358
      if (co.getQType(key.getNoStripHeader<StringView>()) == QType::NSEC3) {
743✔
2359
        deserializeFromBuffer(val.get<StringView>(), lrr);
306✔
2360
        if (!lrr.ttl) // the kind of NSEC3 we need
306✔
2361
          break;
181✔
2362
      }
306✔
2363
      if (cursor.prev(key, val)) {
562!
2364
        // hit beginning of database, again means something is wrong with it
2365
        return false;
×
2366
      }
×
2367
    }
562✔
2368
    before = co.getQName(key.getNoStripHeader<StringView>());
181✔
2369
    unhashed = DNSName(lrr.content.c_str(), lrr.content.size(), 0, false) + di.zone.operator const DNSName&();
181✔
2370

2371
    // now to find after .. at the beginning of the zone
2372
    if (cursor.lower_bound(co(id), key, val)) {
181!
2373
      // cout<<"hit end of zone find when we shouldn't"<<endl;
2374
      return false;
×
2375
    }
×
2376
    for (;;) {
991✔
2377
      if (co.getQType(key.getNoStripHeader<StringView>()) == QType::NSEC3) {
991✔
2378
        deserializeFromBuffer(val.get<StringView>(), lrr);
390✔
2379
        if (!lrr.ttl)
390✔
2380
          break;
181✔
2381
      }
390✔
2382

2383
      if (cursor.next(key, val) || co.getDomainID(key.getNoStripHeader<StringView>()) != id) {
810!
2384
        // cout<<"hit end of zone or database when we shouldn't"<<endl;
2385
        return false;
×
2386
      }
×
2387
    }
810✔
2388
    after = co.getQName(key.getNoStripHeader<StringView>());
181✔
2389
    // cout<<"returning: before="<<before<<", after="<<after<<", unhashed: "<<unhashed<<endl;
2390
    return true;
181✔
2391
  }
181✔
2392

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

2395
  before = co.getQName(key.getNoStripHeader<StringView>());
5,679✔
2396
  if (before == qname) {
5,679✔
2397
    // cout << "Ended up on exact right node" << endl;
2398
    before = co.getQName(key.getNoStripHeader<StringView>());
3,204✔
2399
    // unhashed should be correct now, maybe check?
2400
    if (cursor.next(key, val)) {
3,204✔
2401
      // xxx should find first hash now
2402

2403
      if (cursor.lower_bound(co(id), key, val)) {
130!
2404
        // cout<<"hit end of zone find when we shouldn't for id "<<id<< __LINE__<<endl;
2405
        return false;
×
2406
      }
×
2407
      for (;;) {
650✔
2408
        if (co.getQType(key.getNoStripHeader<StringView>()) == QType::NSEC3) {
650✔
2409
          deserializeFromBuffer(val.get<StringView>(), lrr);
260✔
2410
          if (!lrr.ttl)
260✔
2411
            break;
130✔
2412
        }
260✔
2413

2414
        if (cursor.next(key, val) || co.getDomainID(key.getNoStripHeader<StringView>()) != id) {
520!
2415
          // cout<<"hit end of zone or database when we shouldn't" << __LINE__<<endl;
2416
          return false;
×
2417
        }
×
2418
      }
520✔
2419
      after = co.getQName(key.getNoStripHeader<StringView>());
130✔
2420
      // cout<<"returning: before="<<before<<", after="<<after<<", unhashed: "<<unhashed<<endl;
2421
      return true;
130✔
2422
    }
130✔
2423
  }
3,204✔
2424
  else {
2,475✔
2425
    // cout <<"Going backwards to find 'before'"<<endl;
2426
    int count = 0;
2,475✔
2427
    for (;;) {
6,412✔
2428
      if (co.getQName(key.getNoStripHeader<StringView>()).canonCompare(qname) && co.getQType(key.getNoStripHeader<StringView>()) == QType::NSEC3) {
6,412✔
2429
        // cout<<"Potentially stopping traverse at "<< co.getQName(key.get<StringView>()) <<", " << (co.getQName(key.get<StringView>()).canonCompare(qname))<<endl;
2430
        // cout<<"qname = "<<qname<<endl;
2431
        // cout<<"here  = "<<co.getQName(key.get<StringView>())<<endl;
2432
        deserializeFromBuffer(val.get<StringView>(), lrr);
2,906✔
2433
        if (!lrr.ttl)
2,906✔
2434
          break;
2,358✔
2435
      }
2,906✔
2436

2437
      if (cursor.prev(key, val) || co.getDomainID(key.getNoStripHeader<StringView>()) != id) {
4,054!
2438
        // cout <<"XXX Hit *beginning* of zone or database"<<endl;
2439
        // this can happen, must deal with it
2440
        // should now find the last hash of the zone
2441

2442
        if (cursor.lower_bound(co(id + 1), key, val)) {
117!
2443
          // cout << "Could not find the next higher zone, going to the end of the database then"<<endl;
2444
          cursor.last(key, val);
117✔
2445
        }
117✔
2446
        else
×
2447
          cursor.prev(key, val);
×
2448

2449
        for (;;) {
1,063✔
2450
          if (co.getDomainID(key.getNoStripHeader<StringView>()) != id) {
1,063!
2451
            // cout<<"Last record also not part of this zone!"<<endl;
2452
            //  this implies something is wrong in the database, nothing we can do
2453
            return false;
×
2454
          }
×
2455

2456
          if (co.getQType(key.getNoStripHeader<StringView>()) == QType::NSEC3) {
1,063✔
2457
            deserializeFromBuffer(val.get<StringView>(), lrr);
249✔
2458
            if (!lrr.ttl) // the kind of NSEC3 we need
249✔
2459
              break;
117✔
2460
          }
249✔
2461
          if (cursor.prev(key, val)) {
946!
2462
            // hit beginning of database, again means something is wrong with it
2463
            return false;
×
2464
          }
×
2465
        }
946✔
2466
        before = co.getQName(key.getNoStripHeader<StringView>());
117✔
2467
        unhashed = DNSName(lrr.content.c_str(), lrr.content.size(), 0, false) + di.zone.operator const DNSName&();
117✔
2468
        // cout <<"Should still find 'after'!"<<endl;
2469
        // for 'after', we need to find the first hash of this zone
2470

2471
        if (cursor.lower_bound(co(id), key, val)) {
117!
2472
          // cout<<"hit end of zone find when we shouldn't"<<endl;
2473
          // means database is wrong, nothing we can do
2474
          return false;
×
2475
        }
×
2476
        for (;;) {
657✔
2477
          if (co.getQType(key.getNoStripHeader<StringView>()) == QType::NSEC3) {
657✔
2478
            deserializeFromBuffer(val.get<StringView>(), lrr);
264✔
2479
            if (!lrr.ttl)
264✔
2480
              break;
117✔
2481
          }
264✔
2482

2483
          if (cursor.next(key, val)) {
540!
2484
            // means database is wrong, nothing we can do
2485
            // cout<<"hit end of zone when we shouldn't 2"<<endl;
2486
            return false;
×
2487
          }
×
2488
        }
540✔
2489
        after = co.getQName(key.getNoStripHeader<StringView>());
117✔
2490

2491
        // cout<<"returning: before="<<before<<", after="<<after<<", unhashed: "<<unhashed<<endl;
2492
        return true;
117✔
2493
      }
117✔
2494
      ++count;
3,937✔
2495
    }
3,937✔
2496
    before = co.getQName(key.getNoStripHeader<StringView>());
2,358✔
2497
    unhashed = DNSName(lrr.content.c_str(), lrr.content.size(), 0, false) + di.zone.operator const DNSName&();
2,358✔
2498
    // cout<<"Went backwards, found "<<before<<endl;
2499
    // return us to starting point
2500
    while (count--)
5,778✔
2501
      cursor.next(key, val);
3,420✔
2502
  }
2,358✔
2503
  //  cout<<"Now going forward"<<endl;
2504
  for (int count = 0;; ++count) {
11,206✔
2505
    if ((count && cursor.next(key, val)) || co.getDomainID(key.getNoStripHeader<StringView>()) != id) {
11,206✔
2506
      // cout <<"Hit end of database or zone, finding first hash then in zone "<<id<<endl;
2507
      if (cursor.lower_bound(co(id), key, val)) {
163!
2508
        // cout<<"hit end of zone find when we shouldn't"<<endl;
2509
        // means database is wrong, nothing we can do
2510
        return false;
×
2511
      }
×
2512
      for (;;) {
1,186✔
2513
        if (co.getQType(key.getNoStripHeader<StringView>()) == QType::NSEC3) {
1,186✔
2514
          deserializeFromBuffer(val.get<StringView>(), lrr);
410✔
2515
          if (!lrr.ttl)
410✔
2516
            break;
163✔
2517
        }
410✔
2518

2519
        if (cursor.next(key, val)) {
1,023!
2520
          // means database is wrong, nothing we can do
2521
          // cout<<"hit end of zone when we shouldn't 2"<<endl;
2522
          return false;
×
2523
        }
×
2524
        // cout << "Next.. "<<endl;
2525
      }
1,023✔
2526
      after = co.getQName(key.getNoStripHeader<StringView>());
163✔
2527

2528
      // cout<<"returning: before="<<before<<", after="<<after<<", unhashed: "<<unhashed<<endl;
2529
      return true;
163✔
2530
    }
163✔
2531

2532
    // cout<<"After "<<co.getQName(key.get<StringView>()) <<endl;
2533
    if (co.getQType(key.getNoStripHeader<StringView>()) == QType::NSEC3) {
11,043✔
2534
      deserializeFromBuffer(val.get<StringView>(), lrr);
7,196✔
2535
      if (!lrr.ttl) {
7,196✔
2536
        break;
5,269✔
2537
      }
5,269✔
2538
    }
7,196✔
2539
  }
11,043✔
2540
  after = co.getQName(key.getNoStripHeader<StringView>());
5,269✔
2541
  // cout<<"returning: before="<<before<<", after="<<after<<", unhashed: "<<unhashed<<endl;
2542
  return true;
5,269✔
2543
}
5,432✔
2544

2545
bool LMDBBackend::getBeforeAndAfterNames(domainid_t domainId, const ZoneName& zonenameU, const DNSName& qname, DNSName& before, DNSName& after)
2546
{
2,423✔
2547
  ZoneName zonename = zonenameU.makeLowerCase();
2,423✔
2548
  //  cout << __PRETTY_FUNCTION__<< ": "<<domainId <<", "<<zonename << ", '"<<qname<<"'"<<endl;
2549

2550
  auto txn = getRecordsROTransaction(domainId);
2,423✔
2551
  compoundOrdername co;
2,423✔
2552
  DNSName qname2 = qname.makeRelative(zonename);
2,423✔
2553
  string matchkey = co(domainId, qname2);
2,423✔
2554
  auto cursor = txn->txn->getCursor(txn->db->dbi);
2,423✔
2555
  MDBOutVal key, val;
2,423✔
2556
  // cout<<"Lower_bound for "<<qname2<<endl;
2557
  if (cursor.lower_bound(matchkey, key, val)) {
2,423✔
2558
    // cout << "Hit end of database, bummer"<<endl;
2559
    cursor.last(key, val);
295✔
2560
    if (compoundOrdername::getDomainID(key.getNoStripHeader<string_view>()) == domainId) {
295!
2561
      before = compoundOrdername::getQName(key.getNoStripHeader<string_view>()) + zonename.operator const DNSName&();
295✔
2562
      after = zonename.operator const DNSName&();
295✔
2563
    }
295✔
2564
    // else
2565
    // cout << "We were at end of database, but this zone is not there?!"<<endl;
2566
    return true;
295✔
2567
  }
295✔
2568
  // cout<<"Cursor is at "<<co.getQName(key.get<string_view>()) <<", in zone id "<<compoundOrdername::getDomainID(key.get<string_view>())<< endl;
2569

2570
  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,128✔
2571
    // cout << "Had an exact match!"<<endl;
2572
    before = qname2 + zonename.operator const DNSName&();
1,046✔
2573
    int rc;
1,046✔
2574
    for (;;) {
3,954✔
2575
      rc = cursor.next(key, val);
3,954✔
2576
      if (rc)
3,954✔
2577
        break;
37✔
2578

2579
      if (compoundOrdername::getDomainID(key.getNoStripHeader<string_view>()) == domainId && key.getNoStripHeader<StringView>().rfind(matchkey, 0) == 0) {
3,917!
2580
        continue;
2,269✔
2581
      }
2,269✔
2582
      LMDBResourceRecord lrr;
1,648✔
2583
      deserializeFromBuffer(val.get<StringView>(), lrr);
1,648✔
2584
      if (co.getQType(key.getNoStripHeader<string_view>()).getCode() && (lrr.auth || co.getQType(key.getNoStripHeader<string_view>()).getCode() == QType::NS))
1,648✔
2585
        break;
1,009✔
2586
    }
1,648✔
2587
    if (rc != 0 || compoundOrdername::getDomainID(key.getNoStripHeader<string_view>()) != domainId) {
1,046!
2588
      // cout << "We hit the end of the zone or database. 'after' is apex" << endl;
2589
      after = zonename.operator const DNSName&();
37✔
2590
      return false;
37✔
2591
    }
37✔
2592
    after = compoundOrdername::getQName(key.getNoStripHeader<string_view>()) + zonename.operator const DNSName&();
1,009✔
2593
    return true;
1,009✔
2594
  }
1,046✔
2595

2596
  if (compoundOrdername::getDomainID(key.getNoStripHeader<string_view>()) != domainId) {
1,082✔
2597
    // cout << "Ended up in next zone, 'after' is zonename" <<endl;
2598
    after = zonename.operator const DNSName&();
12✔
2599
    // cout << "Now hunting for previous" << endl;
2600
    int rc;
12✔
2601
    for (;;) {
24✔
2602
      rc = cursor.prev(key, val);
24✔
2603
      if (rc) {
24!
2604
        // cout<<"Reversed into zone, but got not found from lmdb" <<endl;
2605
        return false;
×
2606
      }
×
2607

2608
      if (compoundOrdername::getDomainID(key.getNoStripHeader<string_view>()) != domainId) {
24!
2609
        // cout<<"Reversed into zone, but found wrong zone id " << compoundOrdername::getDomainID(key.getNoStripHeader<string_view>()) << " != "<<domainId<<endl;
2610
        // "this can't happen"
2611
        return false;
×
2612
      }
×
2613
      LMDBResourceRecord lrr;
24✔
2614
      deserializeFromBuffer(val.get<StringView>(), lrr);
24✔
2615
      if (co.getQType(key.getNoStripHeader<string_view>()).getCode() && (lrr.auth || co.getQType(key.getNoStripHeader<string_view>()).getCode() == QType::NS))
24!
2616
        break;
12✔
2617
    }
24✔
2618

2619
    before = compoundOrdername::getQName(key.getNoStripHeader<string_view>()) + zonename.operator const DNSName&();
12✔
2620
    // cout<<"Found: "<< before<<endl;
2621
    return true;
12✔
2622
  }
12✔
2623

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

2626
  int skips = 0;
1,070✔
2627
  for (;;) {
1,696✔
2628
    LMDBResourceRecord lrr;
1,696✔
2629
    deserializeFromBuffer(val.get<StringView>(), lrr);
1,696✔
2630
    if (co.getQType(key.getNoStripHeader<string_view>()).getCode() && (lrr.auth || co.getQType(key.getNoStripHeader<string_view>()).getCode() == QType::NS)) {
1,696!
2631
      after = compoundOrdername::getQName(key.getNoStripHeader<string_view>()) + zonename.operator const DNSName&();
1,070✔
2632
      // cout <<"Found auth ("<<lrr.auth<<") or an NS record "<<after<<", type: "<<co.getQType(key.getNoStripHeader<string_view>()).toString()<<", ttl = "<<lrr.ttl<<endl;
2633
      // cout << makeHexDump(val.get<string>()) << endl;
2634
      break;
1,070✔
2635
    }
1,070✔
2636
    // 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;
2637
    int rc = cursor.next(key, val);
626✔
2638
    if (!rc)
626!
2639
      ++skips;
626✔
2640
    if (rc != 0 || compoundOrdername::getDomainID(key.getNoStripHeader<string_view>()) != domainId) {
626!
2641
      // cout << "  oops, hit end of database or zone. This means after is apex" <<endl;
2642
      after = zonename.operator const DNSName&();
×
2643
      break;
×
2644
    }
×
2645
  }
626✔
2646
  // go back to where we were
2647
  while (skips--)
1,696✔
2648
    cursor.prev(key, val);
626✔
2649

2650
  for (;;) {
2,080✔
2651
    int rc = cursor.prev(key, val);
2,080✔
2652
    if (rc != 0 || compoundOrdername::getDomainID(key.getNoStripHeader<string_view>()) != domainId) {
2,080!
2653
      // XX I don't think this case can happen
2654
      // cout << "We hit the beginning of the zone or database.. now what" << endl;
2655
      return false;
×
2656
    }
×
2657
    before = compoundOrdername::getQName(key.getNoStripHeader<string_view>()) + zonename.operator const DNSName&();
2,080✔
2658
    LMDBResourceRecord lrr;
2,080✔
2659
    deserializeFromBuffer(val.get<string_view>(), lrr);
2,080✔
2660
    // cout<<"And before to "<<before<<", auth = "<<rr.auth<<endl;
2661
    if (co.getQType(key.getNoStripHeader<string_view>()).getCode() && (lrr.auth || co.getQType(key.getNoStripHeader<string_view>()) == QType::NS))
2,080✔
2662
      break;
1,070✔
2663
    // cout << "Oops, that was wrong, go back one more"<<endl;
2664
  }
2,080✔
2665

2666
  return true;
1,070✔
2667
}
1,070✔
2668

2669
bool LMDBBackend::updateDNSSECOrderNameAndAuth(domainid_t domain_id, const DNSName& qname, const DNSName& ordername, bool auth, const uint16_t qtype)
2670
{
122,446✔
2671
  //  cout << __PRETTY_FUNCTION__<< ": "<< domain_id <<", '"<<qname <<"', '"<<ordername<<"', "<<auth<< ", " << qtype << endl;
2672
  shared_ptr<RecordsRWTransaction> txn;
122,446✔
2673
  bool needCommit = false;
122,446✔
2674
  if (d_rwtxn && d_transactiondomainid == domain_id) {
122,446!
2675
    txn = d_rwtxn;
122,446✔
2676
    //    cout<<"Reusing open transaction"<<endl;
2677
  }
122,446✔
2678
  else {
×
2679
    //    cout<<"Making a new RW txn for " << __PRETTY_FUNCTION__ <<endl;
2680
    txn = getRecordsRWTransaction(domain_id);
×
2681
    needCommit = true;
×
2682
  }
×
2683

2684
  DomainInfo di;
122,446✔
2685
  if (!d_tdomains->getROTransaction().get(domain_id, di)) {
122,446!
2686
    //    cout<<"Could not find domain_id "<<domain_id <<endl;
2687
    return false;
×
2688
  }
×
2689

2690
  DNSName rel = qname.makeRelative(di.zone);
122,446✔
2691

2692
  compoundOrdername co;
122,446✔
2693
  string matchkey = co(domain_id, rel);
122,446✔
2694

2695
  auto cursor = txn->txn->getCursor(txn->db->dbi);
122,446✔
2696
  MDBOutVal key, val;
122,446✔
2697
  if (cursor.prefix(matchkey, key, val) != 0) {
122,446!
2698
    // cout << "Could not find anything"<<endl;
2699
    return false;
×
2700
  }
×
2701

2702
  bool hasOrderName = !ordername.empty();
122,446✔
2703
  bool needNSEC3 = hasOrderName;
122,446✔
2704

2705
  do {
123,502✔
2706
    vector<LMDBResourceRecord> lrrs;
123,502✔
2707

2708
    if (co.getQType(key.getNoStripHeader<StringView>()) != QType::NSEC3) {
123,502✔
2709
      deserializeFromBuffer(val.get<StringView>(), lrrs);
123,292✔
2710
      bool changed = false;
123,292✔
2711
      vector<LMDBResourceRecord> newRRs;
123,292✔
2712
      newRRs.reserve(lrrs.size());
123,292✔
2713
      for (auto& lrr : lrrs) {
124,420✔
2714
        lrr.qtype = co.getQType(key.getNoStripHeader<StringView>());
124,420✔
2715
        if (!needNSEC3 && qtype != QType::ANY) {
124,420✔
2716
          needNSEC3 = (lrr.ordername && QType(qtype) != lrr.qtype);
955✔
2717
        }
955✔
2718

2719
        if ((qtype == QType::ANY || QType(qtype) == lrr.qtype) && (lrr.ordername != hasOrderName || lrr.auth != auth)) {
124,420✔
2720
          lrr.auth = auth;
122,543✔
2721
          lrr.ordername = hasOrderName;
122,543✔
2722
          changed = true;
122,543✔
2723
        }
122,543✔
2724
        newRRs.push_back(std::move(lrr));
124,420✔
2725
      }
124,420✔
2726
      if (changed) {
123,292✔
2727
        cursor.put(key, serializeToBuffer(newRRs));
121,803✔
2728
      }
121,803✔
2729
    }
123,292✔
2730

2731
  } while (cursor.next(key, val) == 0);
123,502✔
2732

2733
  bool del = false;
122,446✔
2734
  LMDBResourceRecord lrr;
122,446✔
2735
  matchkey = co(domain_id, rel, QType::NSEC3);
122,446✔
2736
  // cerr<<"here qname="<<qname<<" ordername="<<ordername<<" qtype="<<qtype<<" matchkey="<<makeHexDump(matchkey)<<endl;
2737
  int txngetrc;
122,446✔
2738
  if (!(txngetrc = txn->txn->get(txn->db->dbi, matchkey, val))) {
122,446✔
2739
    deserializeFromBuffer(val.get<string_view>(), lrr);
210✔
2740

2741
    if (needNSEC3) {
210✔
2742
      if (hasOrderName && lrr.content != ordername.toDNSStringLC()) {
180✔
2743
        del = true;
1✔
2744
      }
1✔
2745
    }
180✔
2746
    else {
30✔
2747
      del = true;
30✔
2748
    }
30✔
2749
    if (del) {
210✔
2750
      txn->txn->del(txn->db->dbi, co(domain_id, DNSName(lrr.content.c_str(), lrr.content.size(), 0, false), QType::NSEC3));
31✔
2751
      txn->txn->del(txn->db->dbi, matchkey);
31✔
2752
    }
31✔
2753
  }
210✔
2754
  else {
122,236✔
2755
    del = true;
122,236✔
2756
  }
122,236✔
2757

2758
  if (hasOrderName && del) {
122,446✔
2759
    matchkey = co(domain_id, rel, QType::NSEC3);
121,119✔
2760

2761
    lrr.ttl = 0;
121,119✔
2762
    lrr.auth = 0;
121,119✔
2763
    lrr.content = rel.toDNSStringLC();
121,119✔
2764

2765
    string str = serializeToBuffer(lrr);
121,119✔
2766
    txn->txn->put(txn->db->dbi, co(domain_id, ordername, QType::NSEC3), str);
121,119✔
2767
    lrr.ttl = 1;
121,119✔
2768
    lrr.content = ordername.toDNSStringLC();
121,119✔
2769
    str = serializeToBuffer(lrr);
121,119✔
2770
    txn->txn->put(txn->db->dbi, matchkey, str); // 2
121,119✔
2771
  }
121,119✔
2772

2773
  if (needCommit)
122,446!
2774
    txn->txn->commit();
×
2775
  return false;
122,446✔
2776
}
122,446✔
2777

2778
bool LMDBBackend::updateEmptyNonTerminals(domainid_t domain_id, set<DNSName>& insert, set<DNSName>& erase, bool remove)
2779
{
75✔
2780
  // cout << __PRETTY_FUNCTION__<< ": "<< domain_id << ", insert.size() "<<insert.size()<<", "<<erase.size()<<", " <<remove<<endl;
2781

2782
  bool needCommit = false;
75✔
2783
  shared_ptr<RecordsRWTransaction> txn;
75✔
2784
  if (d_rwtxn && d_transactiondomainid == domain_id) {
75!
2785
    txn = d_rwtxn;
75✔
2786
    //    cout<<"Reusing open transaction"<<endl;
2787
  }
75✔
2788
  else {
×
2789
    //    cout<<"Making a new RW txn for delete domain"<<endl;
2790
    txn = getRecordsRWTransaction(domain_id);
×
2791
    needCommit = true;
×
2792
  }
×
2793

2794
  // if remove is set, all ENTs should be removed & nothing else should be done
2795
  if (remove) {
75!
2796
    compoundOrdername order;
×
2797
    string match = order(domain_id);
×
2798
    LMDBBackend::deleteDomainRecords(*txn, QType::ENT, match);
×
2799
  }
×
2800
  else {
75✔
2801
    DomainInfo di;
75✔
2802
    auto rotxn = d_tdomains->getROTransaction();
75✔
2803
    if (!rotxn.get(domain_id, di)) {
75!
2804
      // cout <<"No such domain with id "<<domain_id<<endl;
2805
      return false;
×
2806
    }
×
2807
    compoundOrdername co;
75✔
2808
    for (const auto& n : insert) {
354✔
2809
      LMDBResourceRecord lrr;
354✔
2810
      lrr.qname = n.makeRelative(di.zone);
354✔
2811
      lrr.ttl = 0;
354✔
2812
      lrr.auth = true;
354✔
2813

2814
      std::string ser = serializeToBuffer(lrr);
354✔
2815

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

2818
      // cout <<" +"<<n<<endl;
2819
    }
354✔
2820
    for (auto n : erase) {
75✔
2821
      // cout <<" -"<<n<<endl;
2822
      n.makeUsRelative(di.zone);
1✔
2823
      txn->txn->del(txn->db->dbi, co(domain_id, n, 0));
1✔
2824
    }
1✔
2825
  }
75✔
2826
  if (needCommit)
75!
2827
    txn->txn->commit();
×
2828
  return false;
75✔
2829
}
75✔
2830

2831
/* TSIG */
2832
bool LMDBBackend::getTSIGKey(const DNSName& name, DNSName& algorithm, string& content)
2833
{
103✔
2834
  auto txn = d_ttsig->getROTransaction();
103✔
2835
  LmdbIdVec ids;
103✔
2836
  txn.get_multi<0>(name, ids);
103✔
2837

2838
  TSIGKey key;
103✔
2839
  for (auto id : ids) {
103✔
2840
    if (txn.get(id, key)) {
87!
2841
      if (algorithm.empty() || algorithm == DNSName(key.algorithm)) {
87!
2842
        algorithm = DNSName(key.algorithm);
87✔
2843
        content = key.key;
87✔
2844
      }
87✔
2845
    }
87✔
2846
  }
87✔
2847

2848
  return true;
103✔
2849
}
103✔
2850

2851
// this deletes an old key if it has the same algorithm
2852
bool LMDBBackend::setTSIGKey(const DNSName& name, const DNSName& algorithm, const string& content)
2853
{
27✔
2854
  auto txn = d_ttsig->getRWTransaction();
27✔
2855

2856
  LmdbIdVec ids;
27✔
2857
  txn.get_multi<0>(name, ids);
27✔
2858

2859
  TSIGKey key;
27✔
2860
  for (auto id : ids) {
27✔
2861
    if (txn.get(id, key)) {
2!
2862
      if (key.algorithm == algorithm) {
2✔
2863
        txn.del(id);
1✔
2864
      }
1✔
2865
    }
2✔
2866
  }
2✔
2867

2868
  TSIGKey tk;
27✔
2869
  tk.name = name;
27✔
2870
  tk.algorithm = algorithm;
27✔
2871
  tk.key = content;
27✔
2872

2873
  txn.put(tk, 0, d_random_ids, name.hash());
27✔
2874
  txn.commit();
27✔
2875

2876
  return true;
27✔
2877
}
27✔
2878
bool LMDBBackend::deleteTSIGKey(const DNSName& name)
2879
{
2✔
2880
  auto txn = d_ttsig->getRWTransaction();
2✔
2881

2882
  LmdbIdVec ids;
2✔
2883
  txn.get_multi<0>(name, ids);
2✔
2884

2885
  TSIGKey key;
2✔
2886

2887
  for (auto id : ids) {
2✔
2888
    if (txn.get(id, key)) {
2!
2889
      txn.del(id);
2✔
2890
    }
2✔
2891
  }
2✔
2892
  txn.commit();
2✔
2893
  return true;
2✔
2894
}
2✔
2895
bool LMDBBackend::getTSIGKeys(std::vector<struct TSIGKey>& keys)
2896
{
1✔
2897
  auto txn = d_ttsig->getROTransaction();
1✔
2898

2899
  keys.clear();
1✔
2900
  // In a perfect world, we would simply iterate over txn and add every
2901
  // item to the returned vector:
2902
  //   for (auto iter = txn.begin(); iter != txn.end(); ++iter) {
2903
  //     keys.push_back(*iter);
2904
  //   }
2905
  // But databases converted from older (< 5) schemas _may_ have multiple
2906
  // entries for the same TSIG key name and algorithm, something which is not
2907
  // allowed in the v5 database schema. These extra entries will not be found
2908
  // by get_multi<> during regular operations, and would only appear in the
2909
  // results of this method.
2910
  // In order to prevent this, we first only gather the list of key names, and
2911
  // in a second step, query for them using a similar logic as getTSIGKey().
2912
  // Unfortunately, there does not seem to be a way to know if the database had
2913
  // been created using the v5 schema (not converted), in which case we could
2914
  // use the above, simpler logic.
2915
  std::unordered_set<DNSName> keynames;
1✔
2916
  for (const auto& iter : txn) {
10✔
2917
    keynames.insert(iter.name);
10✔
2918
  }
10✔
2919
  for (const auto& iter : keynames) {
9✔
2920
    LmdbIdVec ids;
9✔
2921
    txn.get_multi<0>(iter, ids);
9✔
2922
    for (auto key_id : ids) {
10✔
2923
      TSIGKey key;
10✔
2924
      if (txn.get(key_id, key)) {
10!
2925
        keys.push_back(key);
10✔
2926
      }
10✔
2927
    }
10✔
2928
  }
9✔
2929
  return true;
1✔
2930
}
1✔
2931

2932
string LMDBBackend::directBackendCmd(const string& query)
2933
{
×
2934
  ostringstream ret, usage;
×
2935

2936
  usage << "info                               show some information about the database" << endl;
×
2937
  usage << "index check domains                check zone<>ID indexes" << endl;
×
2938
  usage << "index refresh domains <ID>         refresh index for zone with this ID" << endl;
×
2939
  usage << "index refresh-all domains          refresh index for all zones with disconnected indexes" << endl;
×
2940
  vector<string> argv;
×
2941
  stringtok(argv, query);
×
2942

2943
  if (argv.empty()) {
×
2944
    return usage.str();
×
2945
  }
×
2946

2947
  string& cmd = argv[0];
×
2948

2949
  if (cmd == "help") {
×
2950
    return usage.str();
×
2951
  }
×
2952

2953
  if (cmd == "info") {
×
2954
    ret << "shards: " << s_shards << endl;
×
2955
    ret << "schemaversion: " << SCHEMAVERSION << endl;
×
2956

2957
    return ret.str();
×
2958
  }
×
2959

2960
  if (cmd == "index") {
×
2961
    if (argv.size() < 2) {
×
2962
      return "need an index subcommand\n";
×
2963
    }
×
2964

2965
    string& subcmd = argv[1];
×
2966

2967
    if (subcmd == "check" || subcmd == "refresh-all") {
×
2968
      bool refresh = false;
×
2969

2970
      if (subcmd == "refresh-all") {
×
2971
        refresh = true;
×
2972
      }
×
2973

2974
      if (argv.size() < 3) {
×
2975
        return "need an index name\n";
×
2976
      }
×
2977

2978
      if (argv[2] != "domains") {
×
2979
        return "can only check the domains index\n";
×
2980
      }
×
2981

2982
      vector<uint32_t> refreshQueue;
×
2983

2984
      {
×
2985
        auto txn = d_tdomains->getROTransaction();
×
2986

2987
        for (auto iter = txn.begin(); iter != txn.end(); ++iter) {
×
2988
          DomainInfo di = *iter;
×
2989

2990
          auto id = iter.getID();
×
2991

2992
          LmdbIdVec ids;
×
2993
          txn.get_multi<0>(di.zone, ids);
×
2994

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

2998
            if (ids.empty()) {
×
2999
              ret << "zone->ID index has no entry for " << di.zone << endl;
×
3000
              if (refresh) {
×
3001
                refreshQueue.push_back(id);
×
3002
              }
×
3003
              else {
×
3004
                ret << "  suggested remedy: index refresh domains " << id << endl;
×
3005
              }
×
3006
            }
×
3007
            else {
×
3008
              // ids.size() > 1
3009
              ret << "zone->ID index has multiple entries for " << di.zone << ": ";
×
3010
              for (auto id_ : ids) {
×
3011
                ret << id_ << " ";
×
3012
              }
×
3013
              ret << endl;
×
3014
            }
×
3015
          }
×
3016
        }
×
3017
      }
×
3018

3019
      if (refresh) {
×
3020
        for (const auto& id : refreshQueue) {
×
3021
          if (genChangeDomain(id, [](DomainInfo& /* di */) {})) {
×
3022
            ret << "refreshed " << id << endl;
×
3023
          }
×
3024
          else {
×
3025
            ret << "failed to refresh " << id << endl;
×
3026
          }
×
3027
        }
×
3028
      }
×
3029
      return ret.str();
×
3030
    }
×
3031
    if (subcmd == "refresh") {
×
3032
      // index refresh domains 12345
3033
      if (argv.size() < 4) {
×
3034
        return "usage: index refresh domains <ID>\n";
×
3035
      }
×
3036

3037
      if (argv[2] != "domains") {
×
3038
        return "can only refresh in the domains index\n";
×
3039
      }
×
3040

3041
      domainid_t id = 0; // NOLINT(readability-identifier-length)
×
3042

3043
      try {
×
3044
        pdns::checked_stoi_into(id, argv[3]);
×
3045
      }
×
3046
      catch (const std::out_of_range& e) {
×
3047
        return "ID out of range\n";
×
3048
      }
×
3049

3050
      if (genChangeDomain(id, [](DomainInfo& /* di */) {})) {
×
3051
        ret << "refreshed" << endl;
×
3052
      }
×
3053
      else {
×
3054
        ret << "failed" << endl;
×
3055
      }
×
3056
      return ret.str();
×
3057
    }
×
3058
  }
×
3059

3060
  return "unknown lmdbbackend command\n";
×
3061
}
×
3062

3063
bool LMDBBackend::hasCreatedLocalFiles() const
3064
{
1,584✔
3065
  // Since the lmdb file creation counter is global, if multiple LMDB backends
3066
  // are used, they may end up all reporting having created files even if
3067
  // not all of them did.
3068
  // But since this information is for the sake of pdnsutil, this is not
3069
  // really a problem.
3070
  return MDBDbi::d_creationCount != 0;
1,584✔
3071
}
1,584✔
3072

3073
class LMDBFactory : public BackendFactory
3074
{
3075
public:
3076
  LMDBFactory() :
3077
    BackendFactory("lmdb") {}
5,295✔
3078
  void declareArguments(const string& suffix = "") override
3079
  {
1,993✔
3080
    declare(suffix, "filename", "Filename for lmdb", "./pdns.lmdb");
1,993✔
3081
    declare(suffix, "sync-mode", "Synchronisation mode: nosync, nometasync, sync", "sync");
1,993✔
3082
    // there just is no room for more on 32 bit
3083
    declare(suffix, "shards", "Records database will be split into this number of shards", (sizeof(void*) == 4) ? "2" : "64");
1,993✔
3084
    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,993✔
3085
    declare(suffix, "random-ids", "Numeric IDs inside the database are generated randomly instead of sequentially", "no");
1,993✔
3086
    declare(suffix, "map-size", "LMDB map size in megabytes", (sizeof(void*) == 4) ? "100" : "16000");
1,993✔
3087
    declare(suffix, "flag-deleted", "Flag entries on deletion instead of deleting them", "no");
1,993✔
3088
    declare(suffix, "lightning-stream", "Run in Lightning Stream compatible mode", "no");
1,993✔
3089
  }
1,993✔
3090
  DNSBackend* make(const string& suffix = "") override
3091
  {
3,903✔
3092
    return new LMDBBackend(suffix);
3,903✔
3093
  }
3,903✔
3094
};
3095

3096
/* THIRD PART */
3097

3098
class LMDBLoader
3099
{
3100
public:
3101
  LMDBLoader()
3102
  {
5,295✔
3103
    BackendMakers().report(std::make_unique<LMDBFactory>());
5,295✔
3104
    g_log << Logger::Info << "[lmdbbackend] This is the lmdb backend version " VERSION
5,295✔
3105
#ifndef REPRODUCIBLE
5,295✔
3106
          << " (" __DATE__ " " __TIME__ ")"
5,295✔
3107
#endif
5,295✔
3108
          << " reporting" << endl;
5,295✔
3109
  }
5,295✔
3110
};
3111

3112
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