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

PowerDNS / pdns / 13012068652

28 Jan 2025 01:59PM UTC coverage: 64.71% (+0.01%) from 64.699%
13012068652

Pull #14724

github

web-flow
Merge b15562560 into db18c3a17
Pull Request #14724: dnsdist: Add meson support

38328 of 90334 branches covered (42.43%)

Branch coverage included in aggregate %.

361 of 513 new or added lines in 35 files covered. (70.37%)

42 existing lines in 13 files now uncovered.

128150 of 166934 relevant lines covered (76.77%)

4540890.91 hits per line

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

72.72
/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/dnspacket.hh"
32
#include "pdns/dnssecinfra.hh"
33
#include "pdns/logger.hh"
34
#include "pdns/pdnsexception.hh"
35
#include "pdns/uuid-utils.hh"
36
#include <boost/archive/binary_iarchive.hpp>
37
#include <boost/archive/binary_oarchive.hpp>
38
#include <boost/iostreams/device/back_inserter.hpp>
39
#include <boost/serialization/string.hpp>
40
#include <boost/serialization/utility.hpp>
41
#include <boost/serialization/vector.hpp>
42
#include <boost/uuid/uuid_serialize.hpp>
43
#include <cstdio>
44
#include <cstring>
45
#include <lmdb.h>
46
#include <memory>
47
#include <stdexcept>
48
#include <unistd.h>
49
#include <utility>
50

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

55
#define SCHEMAVERSION 5
1,359✔
56

57
// List the class version here. Default is 0
58
BOOST_CLASS_VERSION(LMDBBackend::KeyDataDB, 1)
59
BOOST_CLASS_VERSION(DomainInfo, 1)
60

61
static bool s_first = true;
62
static uint32_t s_shards = 0;
63
static std::mutex s_lmdbStartupLock;
64

65
std::pair<uint32_t, uint32_t> LMDBBackend::getSchemaVersionAndShards(std::string& filename)
66
{
681✔
67
  // cerr << "getting schema version for path " << filename << endl;
68

69
  uint32_t schemaversion = 0;
681✔
70

71
  MDB_env* tmpEnv = nullptr;
681✔
72

73
  if (mdb_env_create(&tmpEnv) != 0) {
681!
74
    throw std::runtime_error("mdb_env_create failed");
×
75
  }
×
76

77
  std::unique_ptr<MDB_env, decltype(&mdb_env_close)> env{tmpEnv, mdb_env_close};
681✔
78

79
  if (mdb_env_set_mapsize(tmpEnv, 0) != 0) {
681!
80
    throw std::runtime_error("mdb_env_set_mapsize failed");
×
81
  }
×
82

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

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

98
  MDB_txn* txn = nullptr;
657✔
99

100
  if (mdb_txn_begin(tmpEnv, nullptr, MDB_RDONLY, &txn) != 0) {
657!
101
    throw std::runtime_error("mdb_txn_begin failed");
×
102
  }
×
103

104
  MDB_dbi dbi;
657✔
105

106
  {
657✔
107
    int retCode = mdb_dbi_open(txn, "pdns", 0, &dbi);
657✔
108
    if (retCode != 0) {
657!
109
      if (retCode == MDB_NOTFOUND) {
×
110
        // this means nothing has been inited yet
111
        // we pretend this means 5
112
        mdb_txn_abort(txn);
×
113
        return {5U, 0U};
×
114
      }
×
115
      mdb_txn_abort(txn);
×
116
      throw std::runtime_error("mdb_dbi_open failed");
×
117
    }
×
118
  }
657✔
119

120
  MDB_val key, data;
657✔
121

122
  key.mv_data = (char*)"schemaversion";
657✔
123
  key.mv_size = strlen((char*)key.mv_data);
657✔
124

125
  {
657✔
126
    int retCode = mdb_get(txn, dbi, &key, &data);
657✔
127
    if (retCode != 0) {
657!
128
      if (retCode == MDB_NOTFOUND) {
×
129
        // this means nothing has been inited yet
130
        // we pretend this means 5
131
        mdb_txn_abort(txn);
×
132
        return {5U, 0U};
×
133
      }
×
134

135
      throw std::runtime_error("mdb_get pdns.schemaversion failed");
×
136
    }
×
137
  }
657✔
138

139
  if (data.mv_size == 4) {
657✔
140
    // schemaversion is < 5 and is stored in 32 bits, in host order
141

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

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

156
  uint32_t shards = 0;
657✔
157

158
  key.mv_data = (char*)"shards";
657✔
159
  key.mv_size = strlen((char*)key.mv_data);
657✔
160

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

171
      throw std::runtime_error("mdb_get pdns.shards failed");
×
172
    }
×
173
  }
657✔
174

175
  if (data.mv_size == 4) {
657✔
176
    // 'shards' is stored in 32 bits, in host order
177

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

189
  mdb_txn_abort(txn);
657✔
190

191
  return {schemaversion, shards};
657✔
192
}
657✔
193

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

201
  std::string header(LMDBLS::LS_MIN_HEADER_SIZE, '\0');
4✔
202
  int rc;
4✔
203

204
  MDB_cursor* cur;
4✔
205

206
  if ((rc = mdb_cursor_open(txn, sdbi, &cur)) != 0) {
4!
207
    throw std::runtime_error("mdb_cursur_open failed");
×
208
  }
×
209

210
  MDB_val key, data;
4✔
211

212
  rc = mdb_cursor_get(cur, &key, &data, MDB_FIRST);
4✔
213

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

218
    std::string stdata = header + sdata;
40,444✔
219

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

222
    MDB_val tkey;
40,444✔
223
    MDB_val tdata;
40,444✔
224

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

230
    if ((rc = mdb_put(txn, tdbi, &tkey, &tdata, 0)) != 0) {
40,444!
231
      throw std::runtime_error("mdb_put failed");
×
232
    }
×
233

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

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

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

252
  MDB_cursor* cur;
8✔
253

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

258
  MDB_val key, data;
8✔
259

260
  rc = mdb_cursor_get(cur, &key, &data, MDB_FIRST);
8✔
261

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

266
    std::string stdata = header + sdata;
32✔
267

268
    uint32_t id;
32✔
269

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

274
    memcpy(&id, key.mv_data, sizeof(uint32_t));
32✔
275

276
    id = htonl(id);
32✔
277

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

280
    MDB_val tkey;
32✔
281
    MDB_val tdata;
32✔
282

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

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

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

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

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

310
  MDB_cursor* cur;
8✔
311

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

316
  MDB_val key, data;
8✔
317

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

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

324
    uint32_t id;
32✔
325

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

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

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

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

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

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

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

356
}
357

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

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

368
  MDB_env* env = nullptr;
2✔
369

370
  if (mdb_env_create(&env) != 0) {
2!
371
    throw std::runtime_error("mdb_env_create failed");
×
372
  }
×
373

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

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

384
  MDB_txn* txn = nullptr;
2✔
385

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

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

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

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

412
    if (mdb_env_create(&shenv) != 0) {
4!
413
      throw std::runtime_error("mdb_env_create failed");
×
414
    }
×
415

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

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

426
    MDB_txn* shtxn = nullptr;
4✔
427

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

433
    MDB_dbi shdbi = 0;
4✔
434

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

447
    MDB_dbi shdbi2 = 0;
4✔
448

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

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

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

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

476
  int index = 0;
2✔
477

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

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

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

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

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

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

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

522
  index = 0;
2✔
523

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

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

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

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

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

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

566
  MDB_dbi dbi = 0;
2✔
567

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

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

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

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

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

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

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

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

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

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

606
    MDB_val tdata;
4✔
607

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

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

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

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

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

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

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

630
    MDB_val tdata;
2✔
631

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

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

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

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

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

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

663
LMDBBackend::LMDBBackend(const std::string& suffix)
664
{
1,993✔
665
  // overlapping domain ids in combination with relative names are a recipe for disaster
666
  if (!suffix.empty()) {
1,993!
667
    throw std::runtime_error("LMDB backend does not support multiple instances");
×
668
  }
×
669

670
  setArgPrefix("lmdb" + suffix);
1,993✔
671

672
  string syncMode = toLower(getArg("sync-mode"));
1,993✔
673

674
  d_random_ids = mustDo("random-ids");
1,993✔
675

676
  if (syncMode == "nosync")
1,993!
677
    d_asyncFlag = MDB_NOSYNC;
×
678
  else if (syncMode == "nometasync")
1,993!
679
    d_asyncFlag = MDB_NOMETASYNC;
×
680
  else if (syncMode.empty() || syncMode == "sync")
1,993!
681
    d_asyncFlag = 0;
1,993✔
682
  else
×
683
    throw std::runtime_error("Unknown sync mode " + syncMode + " requested for LMDB backend");
×
684

685
  uint64_t mapSize = 0;
1,993✔
686
  try {
1,993✔
687
    mapSize = std::stoll(getArg("map-size"));
1,993✔
688
  }
1,993✔
689
  catch (const std::exception& e) {
1,993✔
690
    throw std::runtime_error(std::string("Unable to parse the 'map-size' LMDB value: ") + e.what());
×
691
  }
×
692

693
  LMDBLS::s_flag_deleted = mustDo("flag-deleted");
1,993✔
694
  d_handle_dups = false;
1,993✔
695

696
  if (mustDo("lightning-stream")) {
1,993!
697
    d_random_ids = true;
×
698
    d_handle_dups = true;
×
699
    LMDBLS::s_flag_deleted = true;
×
700

701
    if (atoi(getArg("shards").c_str()) != 1) {
×
702
      throw std::runtime_error(std::string("running with Lightning Stream support requires shards=1"));
×
703
    }
×
704
  }
×
705

706
  bool opened = false;
1,993✔
707

708
  if (s_first) {
1,993✔
709
    std::lock_guard<std::mutex> l(s_lmdbStartupLock);
679✔
710
    if (s_first) {
679!
711
      auto filename = getArg("filename");
679✔
712

713
      auto currentSchemaVersionAndShards = getSchemaVersionAndShards(filename);
679✔
714
      uint32_t currentSchemaVersion = currentSchemaVersionAndShards.first;
679✔
715
      // std::cerr<<"current schema version: "<<currentSchemaVersion<<", shards="<<currentSchemaVersionAndShards.second<<std::endl;
716

717
      if (getArgAsNum("schema-version") != SCHEMAVERSION) {
679!
718
        throw std::runtime_error("This version of the lmdbbackend only supports schema version 5. Configuration demands a lower version. Not starting up.");
×
719
      }
×
720

721
      if (currentSchemaVersion > 0 && currentSchemaVersion < 3) {
679!
722
        throw std::runtime_error("this version of the lmdbbackend can only upgrade from schema v3/v4 to v5. Upgrading from older schemas is not yet supported.");
×
723
      }
×
724

725
      if (currentSchemaVersion == 0) {
679✔
726
        // no database is present yet, we can just create them
727
        currentSchemaVersion = 5;
24✔
728
      }
24✔
729

730
      if (currentSchemaVersion == 3 || currentSchemaVersion == 4) {
679✔
731
        if (!upgradeToSchemav5(filename)) {
2!
732
          throw std::runtime_error("Failed to perform LMDB schema version upgrade from v4 to v5");
×
733
        }
×
734
        currentSchemaVersion = 5;
2✔
735
      }
2✔
736

737
      if (currentSchemaVersion != 5) {
679!
738
        throw std::runtime_error("Somehow, we are not at schema version 5. Giving up");
×
739
      }
×
740

741
      d_tdomains = std::make_shared<tdomains_t>(getMDBEnv(getArg("filename").c_str(), MDB_NOSUBDIR | d_asyncFlag, 0600, mapSize), "domains_v5");
679✔
742
      d_tmeta = std::make_shared<tmeta_t>(d_tdomains->getEnv(), "metadata_v5");
679✔
743
      d_tkdb = std::make_shared<tkdb_t>(d_tdomains->getEnv(), "keydata_v5");
679✔
744
      d_ttsig = std::make_shared<ttsig_t>(d_tdomains->getEnv(), "tsig_v5");
679✔
745

746
      auto pdnsdbi = d_tdomains->getEnv()->openDB("pdns", MDB_CREATE);
679✔
747

748
      opened = true;
679✔
749

750
      auto txn = d_tdomains->getEnv()->getRWTransaction();
679✔
751

752
      const auto configShardsTemp = atoi(getArg("shards").c_str());
679✔
753
      if (configShardsTemp < 0) {
679!
754
        throw std::runtime_error("a negative shards value is not supported");
×
755
      }
×
756
      if (configShardsTemp == 0) {
679!
757
        throw std::runtime_error("a shards value of 0 is not supported");
×
758
      }
×
759
      const auto configShards = static_cast<uint32_t>(configShardsTemp);
679✔
760

761
      MDBOutVal shards{};
679✔
762
      if (txn->get(pdnsdbi, "shards", shards) == 0) {
679✔
763
        s_shards = shards.get<uint32_t>();
655✔
764

765
        if (mustDo("lightning-stream") && s_shards != 1) {
655!
766
          throw std::runtime_error("running with Lightning Stream support enabled requires a database with exactly 1 shard");
×
767
        }
×
768

769
        if (s_shards != configShards) {
655!
770
          g_log << Logger::Warning
×
771
                << "Note: configured number of lmdb shards ("
×
772
                << atoi(getArg("shards").c_str())
×
773
                << ") is different from on-disk ("
×
774
                << s_shards
×
775
                << "). Using on-disk shard number"
×
776
                << endl;
×
777
        }
×
778
      }
655✔
779
      else {
24✔
780
        s_shards = configShards;
24✔
781
        txn->put(pdnsdbi, "shards", s_shards);
24✔
782
      }
24✔
783

784
      MDBOutVal gotuuid{};
679✔
785
      if (txn->get(pdnsdbi, "uuid", gotuuid) != 0) {
679✔
786
        const auto uuid = getUniqueID();
24✔
787
        const string uuids(uuid.begin(), uuid.end());
24✔
788
        txn->put(pdnsdbi, "uuid", uuids);
24✔
789
      }
24✔
790

791
      MDBOutVal _schemaversion{};
679✔
792
      if (txn->get(pdnsdbi, "schemaversion", _schemaversion) != 0) {
679✔
793
        // our DB is entirely new, so we need to write the schemaversion
794
        txn->put(pdnsdbi, "schemaversion", currentSchemaVersion);
24✔
795
      }
24✔
796
      txn->commit();
679✔
797

798
      s_first = false;
679✔
799
    }
679✔
800
  }
679✔
801

802
  if (!opened) {
1,993✔
803
    d_tdomains = std::make_shared<tdomains_t>(getMDBEnv(getArg("filename").c_str(), MDB_NOSUBDIR | d_asyncFlag, 0600, mapSize), "domains_v5");
1,314✔
804
    d_tmeta = std::make_shared<tmeta_t>(d_tdomains->getEnv(), "metadata_v5");
1,314✔
805
    d_tkdb = std::make_shared<tkdb_t>(d_tdomains->getEnv(), "keydata_v5");
1,314✔
806
    d_ttsig = std::make_shared<ttsig_t>(d_tdomains->getEnv(), "tsig_v5");
1,314✔
807
  }
1,314✔
808
  d_trecords.resize(s_shards);
1,993✔
809
  d_dolog = ::arg().mustDo("query-logging");
1,993✔
810
}
1,993✔
811

812
namespace boost
813
{
814
namespace serialization
815
{
816

817
  template <class Archive>
818
  void save(Archive& ar, const DNSName& g, const unsigned int /* version */)
819
  {
2,346✔
820
    if (g.empty()) {
2,346✔
821
      ar& std::string();
633✔
822
    }
633✔
823
    else {
1,713✔
824
      ar& g.toDNSStringLC();
1,713✔
825
    }
1,713✔
826
  }
2,346✔
827

828
  template <class Archive>
829
  void load(Archive& ar, DNSName& g, const unsigned int /* version */)
830
  {
149,080✔
831
    string tmp;
149,080✔
832
    ar& tmp;
149,080✔
833
    if (tmp.empty()) {
149,080✔
834
      g = DNSName();
66,038✔
835
    }
66,038✔
836
    else {
83,042✔
837
      g = DNSName(tmp.c_str(), tmp.size(), 0, false);
83,042✔
838
    }
83,042✔
839
  }
149,080✔
840

841
  template <class Archive>
842
  void save(Archive& ar, const QType& g, const unsigned int /* version */)
843
  {
844
    ar& g.getCode();
845
  }
846

847
  template <class Archive>
848
  void load(Archive& ar, QType& g, const unsigned int /* version */)
849
  {
850
    uint16_t tmp;
851
    ar& tmp;
852
    g = QType(tmp);
853
  }
854

855
  template <class Archive>
856
  void save(Archive& ar, const DomainInfo& g, const unsigned int /* version */)
857
  {
911✔
858
    ar& g.zone;
911✔
859
    ar& g.last_check;
911✔
860
    ar& g.account;
911✔
861
    ar& g.primaries;
911✔
862
    ar& g.id;
911✔
863
    ar& g.notified_serial;
911✔
864
    ar& g.kind;
911✔
865
    ar& g.options;
911✔
866
    ar& g.catalog;
911✔
867
  }
911✔
868

869
  template <class Archive>
870
  void load(Archive& ar, DomainInfo& g, const unsigned int version)
871
  {
72,349✔
872
    ar& g.zone;
72,349✔
873
    ar& g.last_check;
72,349✔
874
    ar& g.account;
72,349✔
875
    ar& g.primaries;
72,349✔
876
    ar& g.id;
72,349✔
877
    ar& g.notified_serial;
72,349✔
878
    ar& g.kind;
72,349✔
879
    if (version >= 1) {
72,349✔
880
      ar& g.options;
72,285✔
881
      ar& g.catalog;
72,285✔
882
    }
72,285✔
883
    else {
64✔
884
      g.options.clear();
64✔
885
      g.catalog.clear();
64✔
886
    }
64✔
887
  }
72,349✔
888

889
  template <class Archive>
890
  void serialize(Archive& ar, LMDBBackend::DomainMeta& g, const unsigned int /* version */)
891
  {
4,112✔
892
    ar& g.domain& g.key& g.value;
4,112✔
893
  }
4,112✔
894

895
  template <class Archive>
896
  void save(Archive& ar, const LMDBBackend::KeyDataDB& g, const unsigned int /* version */)
897
  {
136✔
898
    ar& g.domain& g.content& g.flags& g.active& g.published;
136✔
899
  }
136✔
900

901
  template <class Archive>
902
  void load(Archive& ar, LMDBBackend::KeyDataDB& g, const unsigned int version)
903
  {
512✔
904
    ar& g.domain& g.content& g.flags& g.active;
512✔
905
    if (version >= 1) {
512!
906
      ar& g.published;
512✔
907
    }
512✔
908
    else {
×
909
      g.published = true;
×
910
    }
×
911
  }
512✔
912

913
  template <class Archive>
914
  void serialize(Archive& ar, TSIGKey& g, const unsigned int /* version */)
915
  {
104✔
916
    ar& g.name;
104✔
917
    ar& g.algorithm; // this is the ordername
104✔
918
    ar& g.key;
104✔
919
  }
104✔
920

921
} // namespace serialization
922
} // namespace boost
923

924
BOOST_SERIALIZATION_SPLIT_FREE(DNSName);
925
BOOST_SERIALIZATION_SPLIT_FREE(QType);
926
BOOST_SERIALIZATION_SPLIT_FREE(LMDBBackend::KeyDataDB);
927
BOOST_SERIALIZATION_SPLIT_FREE(DomainInfo);
928
BOOST_IS_BITWISE_SERIALIZABLE(ComboAddress);
929

930
template <>
931
std::string serializeToBuffer(const LMDBBackend::LMDBResourceRecord& value)
932
{
589,355✔
933
  std::string buffer;
589,355✔
934

935
  // Data size of the resource record.
936
  uint16_t len = value.content.length();
589,355✔
937

938
  // Reserve space to store the size of the resource record + the content of the resource
939
  // record + a few other things.
940
  buffer.reserve(sizeof(len) + len + sizeof(value.ttl) + sizeof(value.auth) + sizeof(value.disabled) + sizeof(value.ordername));
589,355✔
941

942
  // Store the size of the resource record.
943
  // NOLINTNEXTLINE.
944
  buffer.assign((const char*)&len, sizeof(len));
589,355✔
945

946
  // Store the contents of the resource record.
947
  buffer += value.content;
589,355✔
948

949
  // The few other things.
950
  // NOLINTNEXTLINE.
951
  buffer.append((const char*)&value.ttl, sizeof(value.ttl));
589,355✔
952
  buffer.append(1, (char)value.auth);
589,355✔
953
  buffer.append(1, (char)value.disabled);
589,355✔
954
  buffer.append(1, (char)value.ordername);
589,355✔
955

956
  return buffer;
589,355✔
957
}
589,355✔
958

959
template <>
960
std::string serializeToBuffer(const vector<LMDBBackend::LMDBResourceRecord>& value)
961
{
61,040✔
962
  std::string ret;
61,040✔
963
  for (const auto& lrr : value) {
61,410✔
964
    ret += serializeToBuffer(lrr);
61,410✔
965
  }
61,410✔
966
  return ret;
61,040✔
967
}
61,040✔
968

969
static inline size_t deserializeRRFromBuffer(const string_view& str, LMDBBackend::LMDBResourceRecord& lrr)
970
{
328,291✔
971
  uint16_t len;
328,291✔
972
  memcpy(&len, &str[0], 2);
328,291✔
973
  lrr.content.assign(&str[2], len); // len bytes
328,291✔
974
  memcpy(&lrr.ttl, &str[2] + len, 4);
328,291✔
975
  lrr.auth = str[2 + len + 4];
328,291✔
976
  lrr.disabled = str[2 + len + 4 + 1];
328,291✔
977
  lrr.ordername = str[2 + len + 4 + 2];
328,291✔
978
  lrr.wildcardname.clear();
328,291✔
979

980
  return 2 + len + 7;
328,291✔
981
}
328,291✔
982

983
template <>
984
void deserializeFromBuffer(const string_view& buffer, LMDBBackend::LMDBResourceRecord& value)
985
{
11,487✔
986
  deserializeRRFromBuffer(buffer, value);
11,487✔
987
}
11,487✔
988

989
template <>
990
void deserializeFromBuffer(const string_view& buffer, vector<LMDBBackend::LMDBResourceRecord>& value)
991
{
312,390✔
992
  auto str_copy = buffer;
312,390✔
993
  while (str_copy.size() >= 9) { // minimum length for a record is 10
629,194✔
994
    LMDBBackend::LMDBResourceRecord lrr;
316,804✔
995
    auto rrLength = deserializeRRFromBuffer(str_copy, lrr);
316,804✔
996
    value.emplace_back(lrr);
316,804✔
997
    str_copy.remove_prefix(rrLength);
316,804✔
998
  }
316,804✔
999
}
312,390✔
1000

1001
static std::string serializeContent(uint16_t qtype, const DNSName& domain, const std::string& content)
1002
{
325,668✔
1003
  auto drc = DNSRecordContent::make(qtype, QClass::IN, content);
325,668✔
1004
  return drc->serialize(domain, false);
325,668✔
1005
}
325,668✔
1006

1007
static std::shared_ptr<DNSRecordContent> deserializeContentZR(uint16_t qtype, const DNSName& qname, const std::string& content)
1008
{
254,417✔
1009
  if (qtype == QType::A && content.size() == 4) {
254,417!
1010
    return std::make_shared<ARecordContent>(*((uint32_t*)content.c_str()));
243,987✔
1011
  }
243,987✔
1012
  return DNSRecordContent::deserialize(qname, qtype, content, QClass::IN, true);
10,430✔
1013
}
254,417✔
1014

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

1018
   The index we use is "zoneid,canonical relative name". This index is also used
1019
   for AXFR.
1020

1021
   Note - domain_id, name and type are ONLY present on the index!
1022
*/
1023

1024
#if BOOST_VERSION >= 106100
1025
#define StringView string_view
1026
#else
1027
#define StringView string
1028
#endif
1029

1030
void LMDBBackend::deleteDomainRecords(RecordsRWTransaction& txn, uint32_t domain_id, uint16_t qtype)
1031
{
332✔
1032
  compoundOrdername co;
332✔
1033
  string match = co(domain_id);
332✔
1034

1035
  auto cursor = txn.txn->getCursor(txn.db->dbi);
332✔
1036
  MDBOutVal key, val;
332✔
1037
  //  cout<<"Match: "<<makeHexDump(match);
1038
  if (!cursor.lower_bound(match, key, val)) {
332✔
1039
    while (key.getNoStripHeader<StringView>().rfind(match, 0) == 0) {
637✔
1040
      if (qtype == QType::ANY || co.getQType(key.getNoStripHeader<StringView>()) == qtype)
633!
1041
        cursor.del();
633✔
1042
      if (cursor.next(key, val))
633✔
1043
        break;
15✔
1044
    }
633✔
1045
  }
19✔
1046
}
332✔
1047

1048
/* Here's the complicated story. Other backends have just one transaction, which is either
1049
   on or not.
1050

1051
   You can't call feedRecord without a transaction started with startTransaction.
1052

1053
   However, other functions can be called after startTransaction() or without startTransaction()
1054
     (like updateDNSSECOrderNameAndAuth)
1055

1056

1057

1058
*/
1059

1060
bool LMDBBackend::startTransaction(const DNSName& domain, int domain_id)
1061
{
534✔
1062
  // cout <<"startTransaction("<<domain<<", "<<domain_id<<")"<<endl;
1063
  int real_id = domain_id;
534✔
1064
  if (real_id < 0) {
534✔
1065
    auto rotxn = d_tdomains->getROTransaction();
202✔
1066
    DomainInfo di;
202✔
1067
    real_id = rotxn.get<0>(domain, di);
202✔
1068
    // cout<<"real_id = "<<real_id << endl;
1069
    if (!real_id)
202!
1070
      return false;
×
1071
  }
202✔
1072
  if (d_rwtxn) {
534!
1073
    throw DBException("Attempt to start a transaction while one was open already");
×
1074
  }
×
1075
  d_rwtxn = getRecordsRWTransaction(real_id);
534✔
1076

1077
  d_transactiondomain = domain;
534✔
1078
  d_transactiondomainid = real_id;
534✔
1079
  if (domain_id >= 0) {
534✔
1080
    deleteDomainRecords(*d_rwtxn, domain_id);
332✔
1081
  }
332✔
1082

1083
  return true;
534✔
1084
}
534✔
1085

1086
bool LMDBBackend::commitTransaction()
1087
{
495✔
1088
  // cout<<"Commit transaction" <<endl;
1089
  if (!d_rwtxn) {
495!
1090
    throw DBException("Attempt to commit a transaction while there isn't one open");
×
1091
  }
×
1092

1093
  d_rwtxn->txn->commit();
495✔
1094
  d_rwtxn.reset();
495✔
1095
  return true;
495✔
1096
}
495✔
1097

1098
bool LMDBBackend::abortTransaction()
1099
{
35✔
1100
  // cout<<"Abort transaction"<<endl;
1101
  if (!d_rwtxn) {
35!
1102
    throw DBException("Attempt to abort a transaction while there isn't one open");
×
1103
  }
×
1104

1105
  d_rwtxn->txn->abort();
35✔
1106
  d_rwtxn.reset();
35✔
1107

1108
  return true;
35✔
1109
}
35✔
1110

1111
// d_rwtxn must be set here
1112
bool LMDBBackend::feedRecord(const DNSResourceRecord& r, const DNSName& ordername, bool ordernameIsNSEC3)
1113
{
325,540✔
1114
  LMDBResourceRecord lrr(r);
325,540✔
1115
  lrr.qname.makeUsRelative(d_transactiondomain);
325,540✔
1116
  lrr.content = serializeContent(lrr.qtype.getCode(), r.qname, lrr.content);
325,540✔
1117

1118
  compoundOrdername co;
325,540✔
1119
  string matchName = co(lrr.domain_id, lrr.qname, lrr.qtype.getCode());
325,540✔
1120

1121
  string rrs;
325,540✔
1122
  MDBOutVal _rrs;
325,540✔
1123
  if (!d_rwtxn->txn->get(d_rwtxn->db->dbi, matchName, _rrs)) {
325,540✔
1124
    rrs = _rrs.get<string>();
21,779✔
1125
  }
21,779✔
1126

1127
  rrs += serializeToBuffer(lrr);
325,540✔
1128

1129
  d_rwtxn->txn->put(d_rwtxn->db->dbi, matchName, rrs);
325,540✔
1130

1131
  if (ordernameIsNSEC3 && !ordername.empty()) {
325,540!
1132
    MDBOutVal val;
40,767✔
1133
    if (d_rwtxn->txn->get(d_rwtxn->db->dbi, co(lrr.domain_id, lrr.qname, QType::NSEC3), val)) {
40,767✔
1134
      lrr.ttl = 0;
40,379✔
1135
      lrr.content = lrr.qname.toDNSStringLC();
40,379✔
1136
      lrr.auth = 0;
40,379✔
1137
      string ser = serializeToBuffer(lrr);
40,379✔
1138
      d_rwtxn->txn->put(d_rwtxn->db->dbi, co(lrr.domain_id, ordername, QType::NSEC3), ser);
40,379✔
1139

1140
      lrr.ttl = 1;
40,379✔
1141
      lrr.content = ordername.toDNSString();
40,379✔
1142
      ser = serializeToBuffer(lrr);
40,379✔
1143
      d_rwtxn->txn->put(d_rwtxn->db->dbi, co(lrr.domain_id, lrr.qname, QType::NSEC3), ser);
40,379✔
1144
    }
40,379✔
1145
  }
40,767✔
1146
  return true;
325,540✔
1147
}
325,540✔
1148

1149
bool LMDBBackend::feedEnts(int domain_id, map<DNSName, bool>& nonterm)
1150
{
22✔
1151
  LMDBResourceRecord lrr;
22✔
1152
  lrr.ttl = 0;
22✔
1153
  compoundOrdername co;
22✔
1154
  for (const auto& nt : nonterm) {
96✔
1155
    lrr.qname = nt.first.makeRelative(d_transactiondomain);
96✔
1156
    lrr.auth = nt.second;
96✔
1157
    lrr.ordername = true;
96✔
1158

1159
    std::string ser = serializeToBuffer(lrr);
96✔
1160
    d_rwtxn->txn->put(d_rwtxn->db->dbi, co(domain_id, lrr.qname, QType::ENT), ser);
96✔
1161
  }
96✔
1162
  return true;
22✔
1163
}
22✔
1164

1165
bool LMDBBackend::feedEnts3(int domain_id, const DNSName& domain, map<DNSName, bool>& nonterm, const NSEC3PARAMRecordContent& ns3prc, bool narrow)
1166
{
18✔
1167
  string ser;
18✔
1168
  DNSName ordername;
18✔
1169
  LMDBResourceRecord lrr;
18✔
1170
  compoundOrdername co;
18✔
1171
  for (const auto& nt : nonterm) {
92✔
1172
    lrr.qname = nt.first.makeRelative(domain);
92✔
1173
    lrr.ttl = 0;
92✔
1174
    lrr.auth = nt.second;
92✔
1175
    lrr.ordername = nt.second;
92✔
1176
    ser = serializeToBuffer(lrr);
92✔
1177
    d_rwtxn->txn->put(d_rwtxn->db->dbi, co(domain_id, lrr.qname, QType::ENT), ser);
92✔
1178

1179
    if (!narrow && lrr.auth) {
92!
1180
      lrr.content = lrr.qname.toDNSString();
80✔
1181
      lrr.auth = false;
80✔
1182
      lrr.ordername = false;
80✔
1183
      ser = serializeToBuffer(lrr);
80✔
1184

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

1188
      lrr.ttl = 1;
80✔
1189
      lrr.content = ordername.toDNSString();
80✔
1190
      ser = serializeToBuffer(lrr);
80✔
1191
      d_rwtxn->txn->put(d_rwtxn->db->dbi, co(domain_id, lrr.qname, QType::NSEC3), ser);
80✔
1192
    }
80✔
1193
  }
92✔
1194
  return true;
18✔
1195
}
18✔
1196

1197
// might be called within a transaction, might also be called alone
1198
bool LMDBBackend::replaceRRSet(uint32_t domain_id, const DNSName& qname, const QType& qt, const vector<DNSResourceRecord>& rrset)
1199
{
129✔
1200
  // zonk qname/qtype within domain_id (go through qname, check domain_id && qtype)
1201
  shared_ptr<RecordsRWTransaction> txn;
129✔
1202
  bool needCommit = false;
129✔
1203
  if (d_rwtxn && d_transactiondomainid == domain_id) {
129!
1204
    txn = d_rwtxn;
129✔
1205
    //    cout<<"Reusing open transaction"<<endl;
1206
  }
129✔
1207
  else {
×
1208
    //    cout<<"Making a new RW txn for replace rrset"<<endl;
1209
    txn = getRecordsRWTransaction(domain_id);
×
1210
    needCommit = true;
×
1211
  }
×
1212

1213
  DomainInfo di;
129✔
1214
  if (!d_tdomains->getROTransaction().get(domain_id, di)) {
129!
1215
    return false;
×
1216
  }
×
1217

1218
  compoundOrdername co;
129✔
1219
  auto cursor = txn->txn->getCursor(txn->db->dbi);
129✔
1220
  MDBOutVal key, val;
129✔
1221
  string match = co(domain_id, qname.makeRelative(di.zone), qt.getCode());
129✔
1222
  if (!cursor.find(match, key, val)) {
129✔
1223
    cursor.del();
116✔
1224
  }
116✔
1225

1226
  if (!rrset.empty()) {
129✔
1227
    vector<LMDBResourceRecord> adjustedRRSet;
127✔
1228
    for (const auto& rr : rrset) {
128✔
1229
      LMDBResourceRecord lrr(rr);
128✔
1230
      lrr.content = serializeContent(lrr.qtype.getCode(), lrr.qname, lrr.content);
128✔
1231
      lrr.qname.makeUsRelative(di.zone);
128✔
1232

1233
      adjustedRRSet.emplace_back(lrr);
128✔
1234
    }
128✔
1235
    txn->txn->put(txn->db->dbi, match, serializeToBuffer(adjustedRRSet));
127✔
1236
  }
127✔
1237

1238
  if (needCommit)
129!
1239
    txn->txn->commit();
×
1240

1241
  return true;
129✔
1242
}
129✔
1243

1244
bool LMDBBackend::replaceComments([[maybe_unused]] const uint32_t domain_id, [[maybe_unused]] const DNSName& qname, [[maybe_unused]] const QType& qt, const vector<Comment>& comments)
1245
{
2✔
1246
  // if the vector is empty, good, that's what we do here (LMDB does not store comments)
1247
  // if it's not, report failure
1248
  return comments.empty();
2✔
1249
}
2✔
1250

1251
// tempting to templatize these two functions but the pain is not worth it
1252
std::shared_ptr<LMDBBackend::RecordsRWTransaction> LMDBBackend::getRecordsRWTransaction(uint32_t id)
1253
{
534✔
1254
  auto& shard = d_trecords[id % s_shards];
534✔
1255
  if (!shard.env) {
534✔
1256
    shard.env = getMDBEnv((getArg("filename") + "-" + std::to_string(id % s_shards)).c_str(),
69✔
1257
                          MDB_NOSUBDIR | d_asyncFlag, 0600);
69✔
1258
    shard.dbi = shard.env->openDB("records_v5", MDB_CREATE);
69✔
1259
  }
69✔
1260
  auto ret = std::make_shared<RecordsRWTransaction>(shard.env->getRWTransaction());
534✔
1261
  ret->db = std::make_shared<RecordsDB>(shard);
534✔
1262

1263
  return ret;
534✔
1264
}
534✔
1265

1266
std::shared_ptr<LMDBBackend::RecordsROTransaction> LMDBBackend::getRecordsROTransaction(uint32_t id, const std::shared_ptr<LMDBBackend::RecordsRWTransaction>& rwtxn)
1267
{
10,935✔
1268
  auto& shard = d_trecords[id % s_shards];
10,935✔
1269
  if (!shard.env) {
10,935✔
1270
    if (rwtxn) {
2,268!
1271
      throw DBException("attempting to start nested transaction without open parent env");
×
1272
    }
×
1273
    shard.env = getMDBEnv((getArg("filename") + "-" + std::to_string(id % s_shards)).c_str(),
2,268✔
1274
                          MDB_NOSUBDIR | d_asyncFlag, 0600);
2,268✔
1275
    shard.dbi = shard.env->openDB("records_v5", MDB_CREATE);
2,268✔
1276
  }
2,268✔
1277

1278
  if (rwtxn) {
10,935✔
1279
    auto ret = std::make_shared<RecordsROTransaction>(rwtxn->txn->getROTransaction());
392✔
1280
    ret->db = std::make_shared<RecordsDB>(shard);
392✔
1281
    return ret;
392✔
1282
  }
392✔
1283
  else {
10,543✔
1284
    auto ret = std::make_shared<RecordsROTransaction>(shard.env->getROTransaction());
10,543✔
1285
    ret->db = std::make_shared<RecordsDB>(shard);
10,543✔
1286
    return ret;
10,543✔
1287
  }
10,543✔
1288
}
10,935✔
1289

1290
#if 0
1291
// FIXME reinstate soon
1292
bool LMDBBackend::upgradeToSchemav3()
1293
{
1294
  g_log << Logger::Warning << "Upgrading LMDB schema" << endl;
1295

1296
  for (auto i = 0; i < s_shards; i++) {
1297
    string filename = getArg("filename") + "-" + std::to_string(i);
1298
    if (rename(filename.c_str(), (filename + "-old").c_str()) < 0) {
1299
      if (errno == ENOENT) {
1300
        // apparently this shard doesn't exist yet, moving on
1301
        continue;
1302
      }
1303
      unixDie("Rename failed during LMDB upgrade");
1304
    }
1305

1306
    LMDBBackend::RecordsDB oldShard, newShard;
1307

1308
    oldShard.env = getMDBEnv((filename + "-old").c_str(),
1309
                             MDB_NOSUBDIR | d_asyncFlag, 0600);
1310
    oldShard.dbi = oldShard.env->openDB("records", MDB_CREATE | MDB_DUPSORT);
1311
    auto txn = oldShard.env->getROTransaction();
1312
    auto cursor = txn->getROCursor(oldShard.dbi);
1313

1314
    newShard.env = getMDBEnv((filename).c_str(),
1315
                             MDB_NOSUBDIR | d_asyncFlag, 0600);
1316
    newShard.dbi = newShard.env->openDB("records", MDB_CREATE);
1317
    auto newTxn = newShard.env->getRWTransaction();
1318

1319
    MDBOutVal key, val;
1320
    if (cursor.first(key, val) != 0) {
1321
      cursor.close();
1322
      txn->abort();
1323
      newTxn->abort();
1324
      continue;
1325
    }
1326
    string_view currentKey;
1327
    string value;
1328
    for (;;) {
1329
      auto newKey = key.getNoStripHeader<string_view>();
1330
      if (currentKey.compare(newKey) != 0) {
1331
        if (value.size() > 0) {
1332
          newTxn->put(newShard.dbi, currentKey, value);
1333
        }
1334
        currentKey = newKey;
1335
        value = "";
1336
      }
1337
      value += val.get<string>();
1338
      if (cursor.next(key, val) != 0) {
1339
        if (value.size() > 0) {
1340
          newTxn->put(newShard.dbi, currentKey, value);
1341
        }
1342
        break;
1343
      }
1344
    }
1345

1346
    cursor.close();
1347
    txn->commit();
1348
    newTxn->commit();
1349
  }
1350

1351
  return true;
1352
}
1353
#endif
1354

1355
bool LMDBBackend::deleteDomain(const DNSName& domain)
1356
{
18✔
1357
  if (!d_rwtxn) {
18!
1358
    throw DBException(std::string(__PRETTY_FUNCTION__) + " called without a transaction");
×
1359
  }
×
1360

1361
  int transactionDomainId = d_transactiondomainid;
18✔
1362
  DNSName transactionDomain = d_transactiondomain;
18✔
1363

1364
  abortTransaction();
18✔
1365

1366
  LmdbIdVec idvec;
18✔
1367

1368
  if (!d_handle_dups) {
18!
1369
    // get domain id
1370
    auto txn = d_tdomains->getROTransaction();
18✔
1371

1372
    DomainInfo di;
18✔
1373
    idvec.push_back(txn.get<0>(domain, di));
18✔
1374
  }
18✔
1375
  else {
×
1376
    // this transaction used to be RO.
1377
    // it is now RW to narrow a race window between PowerDNS and Lightning Stream
1378
    // FIXME: turn the entire delete, including this ID scan, into one RW transaction
1379
    // when doing that, first do a short RO check to see if we actually have anything to delete
1380
    auto txn = d_tdomains->getRWTransaction();
×
1381

1382
    txn.get_multi<0>(domain, idvec);
×
1383
  }
×
1384

1385
  for (auto id : idvec) {
18✔
1386

1387
    startTransaction(domain, id);
18✔
1388

1389
    { // Remove metadata
18✔
1390
      auto txn = d_tmeta->getRWTransaction();
18✔
1391
      LmdbIdVec ids;
18✔
1392

1393
      txn.get_multi<0>(domain, ids);
18✔
1394

1395
      for (auto& _id : ids) {
20✔
1396
        txn.del(_id);
8✔
1397
      }
8✔
1398

1399
      txn.commit();
18✔
1400
    }
18✔
1401

1402
    { // Remove cryptokeys
18✔
1403
      auto txn = d_tkdb->getRWTransaction();
18✔
1404
      LmdbIdVec ids;
18✔
1405
      txn.get_multi<0>(domain, ids);
18✔
1406

1407
      for (auto _id : ids) {
18!
1408
        txn.del(_id);
×
1409
      }
×
1410

1411
      txn.commit();
18✔
1412
    }
18✔
1413

1414
    // Remove records
1415
    commitTransaction();
18✔
1416

1417
    // Remove zone
1418
    auto txn = d_tdomains->getRWTransaction();
18✔
1419
    txn.del(id);
18✔
1420
    txn.commit();
18✔
1421
  }
18✔
1422

1423
  startTransaction(transactionDomain, transactionDomainId);
18✔
1424

1425
  return true;
18✔
1426
}
18✔
1427

1428
bool LMDBBackend::list(const DNSName& target, int /* id */, bool include_disabled)
1429
{
613✔
1430
  d_includedisabled = include_disabled;
613✔
1431

1432
  DomainInfo di;
613✔
1433
  {
613✔
1434
    auto dtxn = d_tdomains->getROTransaction();
613✔
1435
    if ((di.id = dtxn.get<0>(target, di))) {
613!
1436
      // cerr << "Found domain " << target << " on domain_id " << di.id << ", list requested " << id << endl;
1437
    }
613✔
1438
    else {
×
1439
      // cerr << "Did not find " << target << endl;
1440
      return false;
×
1441
    }
×
1442
  }
613✔
1443

1444
  d_rotxn = getRecordsROTransaction(di.id, d_rwtxn);
613✔
1445
  d_getcursor = std::make_shared<MDBROCursor>(d_rotxn->txn->getCursor(d_rotxn->db->dbi));
613✔
1446

1447
  compoundOrdername co;
613✔
1448
  d_matchkey = co(di.id);
613✔
1449

1450
  MDBOutVal key, val;
613✔
1451
  auto a = d_getcursor->lower_bound(d_matchkey, key, val);
613✔
1452
  auto b0 = key.getNoStripHeader<StringView>();
613✔
1453
  auto b = b0.rfind(d_matchkey, 0);
613✔
1454
  if (a || b != 0) {
613!
1455
    d_getcursor.reset();
11✔
1456
  }
11✔
1457

1458
  d_lookupdomain = target;
613✔
1459

1460
  // Make sure we start with fresh data
1461
  d_currentrrset.clear();
613✔
1462
  d_currentrrsetpos = 0;
613✔
1463

1464
  return true;
613✔
1465
}
613✔
1466

1467
void LMDBBackend::lookup(const QType& type, const DNSName& qdomain, int zoneId, DNSPacket* /* p */)
1468
{
3,735✔
1469
  if (d_dolog) {
3,735!
1470
    g_log << Logger::Warning << "Got lookup for " << qdomain << "|" << type.toString() << " in zone " << zoneId << endl;
×
1471
    d_dtime.set();
×
1472
  }
×
1473

1474
  d_includedisabled = false;
3,735✔
1475

1476
  DNSName hunt(qdomain);
3,735✔
1477
  DomainInfo di;
3,735✔
1478
  if (zoneId < 0) {
3,735✔
1479
    auto rotxn = d_tdomains->getROTransaction();
810✔
1480

1481
    do {
810✔
1482
      zoneId = rotxn.get<0>(hunt, di);
810✔
1483
    } while (!zoneId && type != QType::SOA && hunt.chopOff());
810!
1484
    if (zoneId <= 0) {
810!
1485
      //      cout << "Did not find zone for "<< qdomain<<endl;
1486
      d_getcursor.reset();
×
1487
      return;
×
1488
    }
×
1489
  }
810✔
1490
  else {
2,925✔
1491
    if (!d_tdomains->getROTransaction().get(zoneId, di)) {
2,925✔
1492
      // cout<<"Could not find a zone with id "<<zoneId<<endl;
1493
      d_getcursor.reset();
8✔
1494
      return;
8✔
1495
    }
8✔
1496
    hunt = di.zone;
2,917✔
1497
  }
2,917✔
1498

1499
  DNSName relqname = qdomain.makeRelative(hunt);
3,727✔
1500
  if (relqname.empty()) {
3,727!
1501
    return;
×
1502
  }
×
1503
  // cout<<"get will look for "<<relqname<< " in zone "<<hunt<<" with id "<<zoneId<<" and type "<<type.toString()<<endl;
1504
  d_rotxn = getRecordsROTransaction(zoneId, d_rwtxn);
3,727✔
1505

1506
  compoundOrdername co;
3,727✔
1507
  d_getcursor = std::make_shared<MDBROCursor>(d_rotxn->txn->getCursor(d_rotxn->db->dbi));
3,727✔
1508
  MDBOutVal key, val;
3,727✔
1509
  if (type.getCode() == QType::ANY) {
3,727✔
1510
    d_matchkey = co(zoneId, relqname);
2,912✔
1511
  }
2,912✔
1512
  else {
815✔
1513
    d_matchkey = co(zoneId, relqname, type.getCode());
815✔
1514
  }
815✔
1515

1516
  if (d_getcursor->lower_bound(d_matchkey, key, val) || key.getNoStripHeader<StringView>().rfind(d_matchkey, 0) != 0) {
3,727✔
1517
    d_getcursor.reset();
1,252✔
1518
    if (d_dolog) {
1,252!
1519
      g_log << Logger::Warning << "Query " << ((long)(void*)this) << ": " << d_dtime.udiffNoReset() << " us to execute (found nothing)" << endl;
×
1520
    }
×
1521
    return;
1,252✔
1522
  }
1,252✔
1523

1524
  if (d_dolog) {
2,475!
1525
    g_log << Logger::Warning << "Query " << ((long)(void*)this) << ": " << d_dtime.udiffNoReset() << " us to execute" << endl;
×
1526
  }
×
1527

1528
  d_lookupdomain = hunt;
2,475✔
1529

1530
  // Make sure we start with fresh data
1531
  d_currentrrset.clear();
2,475✔
1532
  d_currentrrsetpos = 0;
2,475✔
1533
}
2,475✔
1534

1535
bool LMDBBackend::get(DNSZoneRecord& zr)
1536
{
258,765✔
1537
  for (;;) {
362,988✔
1538
    // std::cerr<<"d_getcursor="<<d_getcursor<<std::endl;
1539
    if (!d_getcursor) {
362,988✔
1540
      d_rotxn.reset();
4,348✔
1541
      return false;
4,348✔
1542
    }
4,348✔
1543

1544
    string_view key;
358,640✔
1545

1546
    if (d_currentrrset.empty()) {
358,640✔
1547
      d_getcursor->current(d_currentKey, d_currentVal);
354,841✔
1548

1549
      key = d_currentKey.getNoStripHeader<string_view>();
354,841✔
1550
      zr.dr.d_type = compoundOrdername::getQType(key).getCode();
354,841✔
1551

1552
      if (zr.dr.d_type == QType::NSEC3) {
354,841✔
1553
        // Hit a magic NSEC3 skipping
1554
        if (d_getcursor->next(d_currentKey, d_currentVal) || d_currentKey.getNoStripHeader<StringView>().rfind(d_matchkey, 0) != 0) {
104,221✔
1555
          // cerr<<"resetting d_getcursor 1"<<endl;
1556
          d_getcursor.reset();
1,070✔
1557
        }
1,070✔
1558
        continue;
104,221✔
1559
      }
104,221✔
1560

1561
      deserializeFromBuffer(d_currentVal.get<string_view>(), d_currentrrset);
250,620✔
1562
      d_currentrrsetpos = 0;
250,620✔
1563
    }
250,620✔
1564
    else {
3,799✔
1565
      key = d_currentKey.getNoStripHeader<string_view>();
3,799✔
1566
    }
3,799✔
1567
    try {
254,419✔
1568
      const auto& lrr = d_currentrrset.at(d_currentrrsetpos++);
254,419✔
1569

1570
      zr.disabled = lrr.disabled;
254,419✔
1571
      if (!zr.disabled || d_includedisabled) {
254,420✔
1572
        zr.dr.d_name = compoundOrdername::getQName(key) + d_lookupdomain;
254,417✔
1573
        zr.domain_id = compoundOrdername::getDomainID(key);
254,417✔
1574
        zr.dr.d_type = compoundOrdername::getQType(key).getCode();
254,417✔
1575
        zr.dr.d_ttl = lrr.ttl;
254,417✔
1576
        zr.dr.setContent(deserializeContentZR(zr.dr.d_type, zr.dr.d_name, lrr.content));
254,417✔
1577
        zr.auth = lrr.auth;
254,417✔
1578
      }
254,417✔
1579

1580
      if (d_currentrrsetpos >= d_currentrrset.size()) {
254,419✔
1581
        d_currentrrset.clear(); // will invalidate lrr
250,621✔
1582
        if (d_getcursor->next(d_currentKey, d_currentVal) || d_currentKey.getNoStripHeader<StringView>().rfind(d_matchkey, 0) != 0) {
250,621✔
1583
          // cerr<<"resetting d_getcursor 2"<<endl;
1584
          d_getcursor.reset();
2,006✔
1585
        }
2,006✔
1586
      }
250,621✔
1587

1588
      if (zr.disabled && !d_includedisabled) {
254,419✔
1589
        continue;
3✔
1590
      }
3✔
1591
    }
254,419✔
1592
    catch (const std::exception& e) {
254,419✔
1593
      throw PDNSException(e.what());
×
1594
    }
×
1595

1596
    break;
254,417✔
1597
  }
254,419✔
1598

1599
  return true;
254,418✔
1600
}
258,765✔
1601

1602
bool LMDBBackend::get(DNSResourceRecord& rr)
1603
{
165,425✔
1604
  DNSZoneRecord zr;
165,425✔
1605
  if (!get(zr)) {
165,425✔
1606
    return false;
1,216✔
1607
  }
1,216✔
1608

1609
  rr.qname = zr.dr.d_name;
164,209✔
1610
  rr.ttl = zr.dr.d_ttl;
164,209✔
1611
  rr.qtype = zr.dr.d_type;
164,209✔
1612
  rr.content = zr.dr.getContent()->getZoneRepresentation(true);
164,209✔
1613
  rr.domain_id = zr.domain_id;
164,209✔
1614
  rr.auth = zr.auth;
164,209✔
1615
  rr.disabled = zr.disabled;
164,209✔
1616

1617
  return true;
164,209✔
1618
}
165,425✔
1619

1620
bool LMDBBackend::getSerial(DomainInfo& di)
1621
{
1,654✔
1622
  auto txn = getRecordsROTransaction(di.id);
1,654✔
1623
  compoundOrdername co;
1,654✔
1624
  MDBOutVal val;
1,654✔
1625
  if (!txn->txn->get(txn->db->dbi, co(di.id, g_rootdnsname, QType::SOA), val)) {
1,654✔
1626
    LMDBResourceRecord lrr;
1,147✔
1627
    deserializeFromBuffer(val.get<string_view>(), lrr);
1,147✔
1628
    if (lrr.content.size() >= 5 * sizeof(uint32_t)) {
1,147!
1629
      uint32_t serial;
1,147✔
1630
      // a SOA has five 32 bit fields, the first of which is the serial
1631
      // there are two variable length names before the serial, so we calculate from the back
1632
      memcpy(&serial, &lrr.content[lrr.content.size() - (5 * sizeof(uint32_t))], sizeof(serial));
1,147✔
1633
      di.serial = ntohl(serial);
1,147✔
1634
    }
1,147✔
1635
    return !lrr.disabled;
1,147✔
1636
  }
1,147✔
1637
  return false;
507✔
1638
}
1,654✔
1639

1640
bool LMDBBackend::getDomainInfo(const DNSName& domain, DomainInfo& di, bool getserial)
1641
{
1,623✔
1642
  {
1,623✔
1643
    auto txn = d_tdomains->getROTransaction();
1,623✔
1644
    // auto range = txn.prefix_range<0>(domain);
1645

1646
    // bool found = false;
1647

1648
    // for (auto& iter = range.first ; iter != range.second; ++iter) {
1649
    //   found = true;
1650
    //   di.id = iter.getID();
1651
    //   di.backend = this;
1652
    // }
1653

1654
    // if (!found) {
1655
    //   return false;
1656
    // }
1657
    if (!(di.id = txn.get<0>(domain, di))) {
1,623✔
1658
      return false;
304✔
1659
    }
304✔
1660

1661
    di.backend = this;
1,319✔
1662
  }
1,319✔
1663

1664
  if (getserial) {
1,319✔
1665
    getSerial(di);
1,022✔
1666
  }
1,022✔
1667

1668
  return true;
1,319✔
1669
}
1,623✔
1670

1671
int LMDBBackend::genChangeDomain(const DNSName& domain, const std::function<void(DomainInfo&)>& func)
1672
{
526✔
1673
  auto txn = d_tdomains->getRWTransaction();
526✔
1674

1675
  DomainInfo di;
526✔
1676

1677
  auto id = txn.get<0>(domain, di);
526✔
1678
  func(di);
526✔
1679
  txn.put(di, id);
526✔
1680

1681
  txn.commit();
526✔
1682
  return true;
526✔
1683
}
526✔
1684

1685
int LMDBBackend::genChangeDomain(uint32_t id, const std::function<void(DomainInfo&)>& func)
1686
{
91✔
1687
  DomainInfo di;
91✔
1688

1689
  auto txn = d_tdomains->getRWTransaction();
91✔
1690

1691
  if (!txn.get(id, di))
91!
1692
    return false;
×
1693

1694
  func(di);
91✔
1695

1696
  txn.put(di, id);
91✔
1697

1698
  txn.commit();
91✔
1699
  return true;
91✔
1700
}
91✔
1701

1702
bool LMDBBackend::setKind(const DNSName& domain, const DomainInfo::DomainKind kind)
1703
{
205✔
1704
  return genChangeDomain(domain, [kind](DomainInfo& di) {
205✔
1705
    di.kind = kind;
205✔
1706
  });
205✔
1707
}
205✔
1708

1709
bool LMDBBackend::setAccount(const DNSName& domain, const std::string& account)
1710
{
1✔
1711
  return genChangeDomain(domain, [account](DomainInfo& di) {
1✔
1712
    di.account = account;
1✔
1713
  });
1✔
1714
}
1✔
1715

1716
bool LMDBBackend::setPrimaries(const DNSName& domain, const vector<ComboAddress>& primaries)
1717
{
71✔
1718
  return genChangeDomain(domain, [&primaries](DomainInfo& di) {
71✔
1719
    di.primaries = primaries;
71✔
1720
  });
71✔
1721
}
71✔
1722

1723
bool LMDBBackend::createDomain(const DNSName& domain, const DomainInfo::DomainKind kind, const vector<ComboAddress>& primaries, const string& account)
1724
{
294✔
1725
  DomainInfo di;
294✔
1726

1727
  {
294✔
1728
    auto txn = d_tdomains->getRWTransaction();
294✔
1729
    if (txn.get<0>(domain, di)) {
294!
1730
      throw DBException("Domain '" + domain.toLogString() + "' exists already");
×
1731
    }
×
1732

1733
    di.zone = domain;
294✔
1734
    di.kind = kind;
294✔
1735
    di.primaries = primaries;
294✔
1736
    di.account = account;
294✔
1737

1738
    txn.put(di, 0, d_random_ids);
294✔
1739
    txn.commit();
294✔
1740
  }
294✔
1741

1742
  return true;
×
1743
}
294✔
1744

1745
void LMDBBackend::getAllDomainsFiltered(vector<DomainInfo>* domains, const std::function<bool(DomainInfo&)>& allow)
1746
{
77✔
1747
  auto txn = d_tdomains->getROTransaction();
77✔
1748
  if (d_handle_dups) {
77!
1749
    map<DNSName, DomainInfo> zonemap;
×
1750
    set<DNSName> dups;
×
1751

1752
    for (auto iter = txn.begin(); iter != txn.end(); ++iter) {
×
1753
      DomainInfo di = *iter;
×
1754
      di.id = iter.getID();
×
1755
      di.backend = this;
×
1756

1757
      if (!zonemap.emplace(di.zone, di).second) {
×
1758
        dups.insert(di.zone);
×
1759
      }
×
1760
    }
×
1761

1762
    for (const auto& zone : dups) {
×
1763
      DomainInfo di;
×
1764

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

1768
      if (di.id == 0) {
×
1769
        // .get actually found nothing for us
1770
        continue;
×
1771
      }
×
1772

1773
      di.backend = this;
×
1774
      zonemap[di.zone] = di;
×
1775
    }
×
1776

1777
    for (auto& [k, v] : zonemap) {
×
1778
      if (allow(v)) {
×
1779
        domains->push_back(std::move(v));
×
1780
      }
×
1781
    }
×
1782
  }
×
1783
  else {
77✔
1784
    for (auto iter = txn.begin(); iter != txn.end(); ++iter) {
1,024✔
1785
      DomainInfo di = *iter;
947✔
1786
      di.id = iter.getID();
947✔
1787
      di.backend = this;
947✔
1788

1789
      if (allow(di)) {
947✔
1790
        domains->push_back(di);
664✔
1791
      }
664✔
1792
    }
947✔
1793
  }
77✔
1794
}
77✔
1795

1796
void LMDBBackend::getAllDomains(vector<DomainInfo>* domains, bool /* doSerial */, bool include_disabled)
1797
{
50✔
1798
  domains->clear();
50✔
1799

1800
  getAllDomainsFiltered(domains, [this, include_disabled](DomainInfo& di) {
632✔
1801
    if (!getSerial(di) && !include_disabled) {
632!
1802
      return false;
×
1803
    }
×
1804

1805
    return true;
632✔
1806
  });
632✔
1807
}
50✔
1808

1809
void LMDBBackend::getUnfreshSecondaryInfos(vector<DomainInfo>* domains)
1810
{
11✔
1811
  uint32_t serial;
11✔
1812
  time_t now = time(0);
11✔
1813
  LMDBResourceRecord lrr;
11✔
1814
  soatimes st;
11✔
1815

1816
  getAllDomainsFiltered(domains, [this, &lrr, &st, &now, &serial](DomainInfo& di) {
83✔
1817
    if (!di.isSecondaryType()) {
83!
1818
      return false;
×
1819
    }
×
1820

1821
    auto txn2 = getRecordsROTransaction(di.id);
83✔
1822
    compoundOrdername co;
83✔
1823
    MDBOutVal val;
83✔
1824
    if (!txn2->txn->get(txn2->db->dbi, co(di.id, g_rootdnsname, QType::SOA), val)) {
83✔
1825
      deserializeFromBuffer(val.get<string_view>(), lrr);
51✔
1826
      memcpy(&st, &lrr.content[lrr.content.size() - sizeof(soatimes)], sizeof(soatimes));
51✔
1827
      if ((time_t)(di.last_check + ntohl(st.refresh)) > now) { // still fresh
51!
1828
        return false;
51✔
1829
      }
51✔
1830
      serial = ntohl(st.serial);
×
1831
    }
×
1832
    else {
32✔
1833
      serial = 0;
32✔
1834
    }
32✔
1835

1836
    return true;
32✔
1837
  });
83✔
1838
}
11✔
1839

1840
void LMDBBackend::setStale(uint32_t domain_id)
1841
{
2✔
1842
  genChangeDomain(domain_id, [](DomainInfo& di) {
2✔
1843
    di.last_check = 0;
2✔
1844
  });
2✔
1845
}
2✔
1846

1847
void LMDBBackend::setFresh(uint32_t domain_id)
1848
{
88✔
1849
  genChangeDomain(domain_id, [](DomainInfo& di) {
88✔
1850
    di.last_check = time(nullptr);
88✔
1851
  });
88✔
1852
}
88✔
1853

1854
void LMDBBackend::getUpdatedPrimaries(vector<DomainInfo>& updatedDomains, std::unordered_set<DNSName>& catalogs, CatalogHashMap& catalogHashes)
1855
{
×
1856
  CatalogInfo ci;
×
1857

1858
  getAllDomainsFiltered(&(updatedDomains), [this, &catalogs, &catalogHashes, &ci](DomainInfo& di) {
×
1859
    if (!di.isPrimaryType()) {
×
1860
      return false;
×
1861
    }
×
1862

1863
    if (di.kind == DomainInfo::Producer) {
×
1864
      catalogs.insert(di.zone);
×
1865
      catalogHashes[di.zone].process("\0");
×
1866
      return false; // Producer fresness check is performed elsewhere
×
1867
    }
×
1868

1869
    if (!di.catalog.empty()) {
×
1870
      ci.fromJson(di.options, CatalogInfo::CatalogType::Producer);
×
1871
      ci.updateHash(catalogHashes, di);
×
1872
    }
×
1873

1874
    if (getSerial(di) && di.serial != di.notified_serial) {
×
1875
      di.backend = this;
×
1876
      return true;
×
1877
    }
×
1878

1879
    return false;
×
1880
  });
×
1881
}
×
1882

1883
void LMDBBackend::setNotified(uint32_t domain_id, uint32_t serial)
1884
{
1✔
1885
  genChangeDomain(domain_id, [serial](DomainInfo& di) {
1✔
1886
    di.notified_serial = serial;
1✔
1887
  });
1✔
1888
}
1✔
1889

1890
class getCatalogMembersReturnFalseException : std::runtime_error
1891
{
1892
public:
1893
  getCatalogMembersReturnFalseException() :
1894
    std::runtime_error("getCatalogMembers should return false") {}
×
1895
};
1896

1897
bool LMDBBackend::getCatalogMembers(const DNSName& catalog, vector<CatalogInfo>& members, CatalogInfo::CatalogType type)
1898
{
16✔
1899
  vector<DomainInfo> scratch;
16✔
1900

1901
  try {
16✔
1902
    getAllDomainsFiltered(&scratch, [&catalog, &members, &type](DomainInfo& di) {
232✔
1903
      if ((type == CatalogInfo::CatalogType::Producer && di.kind != DomainInfo::Primary) || (type == CatalogInfo::CatalogType::Consumer && di.kind != DomainInfo::Secondary) || di.catalog != catalog) {
232✔
1904
        return false;
28✔
1905
      }
28✔
1906

1907
      CatalogInfo ci;
204✔
1908
      ci.d_id = di.id;
204✔
1909
      ci.d_zone = di.zone;
204✔
1910
      ci.d_primaries = di.primaries;
204✔
1911
      try {
204✔
1912
        ci.fromJson(di.options, type);
204✔
1913
      }
204✔
1914
      catch (const std::runtime_error& e) {
204✔
1915
        g_log << Logger::Warning << __PRETTY_FUNCTION__ << " options '" << di.options << "' for zone '" << di.zone << "' is no valid JSON: " << e.what() << endl;
×
1916
        members.clear();
×
1917
        throw getCatalogMembersReturnFalseException();
×
1918
      }
×
1919
      members.emplace_back(ci);
204✔
1920

1921
      return false;
204✔
1922
    });
204✔
1923
  }
16✔
1924
  catch (const getCatalogMembersReturnFalseException& e) {
16✔
1925
    return false;
×
1926
  }
×
1927
  return true;
16✔
1928
}
16✔
1929

1930
bool LMDBBackend::setOptions(const DNSName& domain, const std::string& options)
1931
{
86✔
1932
  return genChangeDomain(domain, [options](DomainInfo& di) {
86✔
1933
    di.options = options;
86✔
1934
  });
86✔
1935
}
86✔
1936

1937
bool LMDBBackend::setCatalog(const DNSName& domain, const DNSName& catalog)
1938
{
163✔
1939
  return genChangeDomain(domain, [catalog](DomainInfo& di) {
163✔
1940
    di.catalog = catalog;
163✔
1941
  });
163✔
1942
}
163✔
1943

1944
bool LMDBBackend::getAllDomainMetadata(const DNSName& name, std::map<std::string, std::vector<std::string>>& meta)
1945
{
3,308✔
1946
  meta.clear();
3,308✔
1947
  auto txn = d_tmeta->getROTransaction();
3,308✔
1948
  LmdbIdVec ids;
3,308✔
1949
  txn.get_multi<0>(name, ids);
3,308✔
1950

1951
  DomainMeta dm;
3,308✔
1952
  // cerr<<"getAllDomainMetadata start"<<endl;
1953
  for (auto id : ids) {
3,659✔
1954
    if (txn.get(id, dm)) {
3,440!
1955
      meta[dm.key].push_back(dm.value);
3,440✔
1956
    }
3,440✔
1957
  }
3,440✔
1958
  return true;
3,308✔
1959
}
3,308✔
1960

1961
bool LMDBBackend::setDomainMetadata(const DNSName& name, const std::string& kind, const std::vector<std::string>& meta)
1962
{
437✔
1963
  auto txn = d_tmeta->getRWTransaction();
437✔
1964

1965
  LmdbIdVec ids;
437✔
1966
  txn.get_multi<0>(name, ids);
437✔
1967

1968
  DomainMeta dmeta;
437✔
1969
  for (auto id : ids) {
437✔
1970
    if (txn.get(id, dmeta)) {
288!
1971
      if (dmeta.key == kind) {
288✔
1972
        // cerr<<"delete"<<endl;
1973
        txn.del(id);
32✔
1974
      }
32✔
1975
    }
288✔
1976
  }
288✔
1977

1978
  for (const auto& m : meta) {
437✔
1979
    DomainMeta dm{name, kind, m};
344✔
1980
    txn.put(dm, 0, d_random_ids);
344✔
1981
  }
344✔
1982
  txn.commit();
437✔
1983
  return true;
437✔
1984
}
437✔
1985

1986
bool LMDBBackend::getDomainKeys(const DNSName& name, std::vector<KeyData>& keys)
1987
{
763✔
1988
  auto txn = d_tkdb->getROTransaction();
763✔
1989
  LmdbIdVec ids;
763✔
1990
  txn.get_multi<0>(name, ids);
763✔
1991

1992
  KeyDataDB key;
763✔
1993

1994
  for (auto id : ids) {
763✔
1995
    if (txn.get(id, key)) {
406!
1996
      KeyData kd{key.content, id, key.flags, key.active, key.published};
406✔
1997
      keys.push_back(kd);
406✔
1998
    }
406✔
1999
  }
406✔
2000

2001
  return true;
763✔
2002
}
763✔
2003

2004
bool LMDBBackend::removeDomainKey(const DNSName& name, unsigned int id)
2005
{
29✔
2006
  auto txn = d_tkdb->getRWTransaction();
29✔
2007
  KeyDataDB kdb;
29✔
2008
  if (txn.get(id, kdb)) {
29✔
2009
    if (kdb.domain == name) {
20!
2010
      txn.del(id);
20✔
2011
      txn.commit();
20✔
2012
      return true;
20✔
2013
    }
20✔
2014
  }
20✔
2015
  // cout << "??? wanted to remove domain key for domain "<<name<<" with id "<<id<<", could not find it"<<endl;
2016
  return true;
9✔
2017
}
29✔
2018

2019
bool LMDBBackend::addDomainKey(const DNSName& name, const KeyData& key, int64_t& id)
2020
{
114✔
2021
  auto txn = d_tkdb->getRWTransaction();
114✔
2022
  KeyDataDB kdb{name, key.content, key.flags, key.active, key.published};
114✔
2023
  id = txn.put(kdb, 0, d_random_ids);
114✔
2024
  txn.commit();
114✔
2025

2026
  return true;
114✔
2027
}
114✔
2028

2029
bool LMDBBackend::activateDomainKey(const DNSName& name, unsigned int id)
2030
{
6✔
2031
  auto txn = d_tkdb->getRWTransaction();
6✔
2032
  KeyDataDB kdb;
6✔
2033
  if (txn.get(id, kdb)) {
6!
2034
    if (kdb.domain == name) {
6!
2035
      txn.modify(id, [](KeyDataDB& kdbarg) {
6✔
2036
        kdbarg.active = true;
6✔
2037
      });
6✔
2038
      txn.commit();
6✔
2039
      return true;
6✔
2040
    }
6✔
2041
  }
6✔
2042

2043
  // cout << "??? wanted to activate domain key for domain "<<name<<" with id "<<id<<", could not find it"<<endl;
2044
  return true;
×
2045
}
6✔
2046

2047
bool LMDBBackend::deactivateDomainKey(const DNSName& name, unsigned int id)
2048
{
4✔
2049
  auto txn = d_tkdb->getRWTransaction();
4✔
2050
  KeyDataDB kdb;
4✔
2051
  if (txn.get(id, kdb)) {
4!
2052
    if (kdb.domain == name) {
4!
2053
      txn.modify(id, [](KeyDataDB& kdbarg) {
4✔
2054
        kdbarg.active = false;
4✔
2055
      });
4✔
2056
      txn.commit();
4✔
2057
      return true;
4✔
2058
    }
4✔
2059
  }
4✔
2060
  // cout << "??? wanted to deactivate domain key for domain "<<name<<" with id "<<id<<", could not find it"<<endl;
2061
  return true;
×
2062
}
4✔
2063

2064
bool LMDBBackend::publishDomainKey(const DNSName& name, unsigned int id)
2065
{
6✔
2066
  auto txn = d_tkdb->getRWTransaction();
6✔
2067
  KeyDataDB kdb;
6✔
2068
  if (txn.get(id, kdb)) {
6!
2069
    if (kdb.domain == name) {
6!
2070
      txn.modify(id, [](KeyDataDB& kdbarg) {
6✔
2071
        kdbarg.published = true;
6✔
2072
      });
6✔
2073
      txn.commit();
6✔
2074
      return true;
6✔
2075
    }
6✔
2076
  }
6✔
2077

2078
  // cout << "??? wanted to hide domain key for domain "<<name<<" with id "<<id<<", could not find it"<<endl;
2079
  return true;
×
2080
}
6✔
2081

2082
bool LMDBBackend::unpublishDomainKey(const DNSName& name, unsigned int id)
2083
{
6✔
2084
  auto txn = d_tkdb->getRWTransaction();
6✔
2085
  KeyDataDB kdb;
6✔
2086
  if (txn.get(id, kdb)) {
6!
2087
    if (kdb.domain == name) {
6!
2088
      txn.modify(id, [](KeyDataDB& kdbarg) {
6✔
2089
        kdbarg.published = false;
6✔
2090
      });
6✔
2091
      txn.commit();
6✔
2092
      return true;
6✔
2093
    }
6✔
2094
  }
6✔
2095
  // cout << "??? wanted to unhide domain key for domain "<<name<<" with id "<<id<<", could not find it"<<endl;
2096
  return true;
×
2097
}
6✔
2098

2099
bool LMDBBackend::getBeforeAndAfterNamesAbsolute(uint32_t id, const DNSName& qname, DNSName& unhashed, DNSName& before, DNSName& after)
2100
{
3,441✔
2101
  //  cout << __PRETTY_FUNCTION__<< ": "<<id <<", "<<qname << " " << unhashed<<endl;
2102

2103
  DomainInfo di;
3,441✔
2104
  if (!d_tdomains->getROTransaction().get(id, di)) {
3,441!
2105
    // domain does not exist, tough luck
2106
    return false;
×
2107
  }
×
2108
  // cout <<"Zone: "<<di.zone<<endl;
2109

2110
  compoundOrdername co;
3,441✔
2111
  auto txn = getRecordsROTransaction(id);
3,441✔
2112

2113
  auto cursor = txn->txn->getCursor(txn->db->dbi);
3,441✔
2114
  MDBOutVal key, val;
3,441✔
2115

2116
  LMDBResourceRecord lrr;
3,441✔
2117

2118
  string matchkey = co(id, qname, QType::NSEC3);
3,441✔
2119
  if (cursor.lower_bound(matchkey, key, val)) {
3,441✔
2120
    // this is beyond the end of the database
2121
    // cout << "Beyond end of database!" << endl;
2122
    cursor.last(key, val);
109✔
2123

2124
    for (;;) {
485✔
2125
      if (co.getDomainID(key.getNoStripHeader<StringView>()) != id) {
485!
2126
        // cout<<"Last record also not part of this zone!"<<endl;
2127
        //  this implies something is wrong in the database, nothing we can do
2128
        return false;
×
2129
      }
×
2130

2131
      if (co.getQType(key.getNoStripHeader<StringView>()) == QType::NSEC3) {
485✔
2132
        deserializeFromBuffer(val.get<StringView>(), lrr);
190✔
2133
        if (!lrr.ttl) // the kind of NSEC3 we need
190✔
2134
          break;
109✔
2135
      }
190✔
2136
      if (cursor.prev(key, val)) {
376!
2137
        // hit beginning of database, again means something is wrong with it
2138
        return false;
×
2139
      }
×
2140
    }
376✔
2141
    before = co.getQName(key.getNoStripHeader<StringView>());
109✔
2142
    unhashed = DNSName(lrr.content.c_str(), lrr.content.size(), 0, false) + di.zone;
109✔
2143

2144
    // now to find after .. at the beginning of the zone
2145
    if (cursor.lower_bound(co(id), key, val)) {
109!
2146
      // cout<<"hit end of zone find when we shouldn't"<<endl;
2147
      return false;
×
2148
    }
×
2149
    for (;;) {
663✔
2150
      if (co.getQType(key.getNoStripHeader<StringView>()) == QType::NSEC3) {
663✔
2151
        deserializeFromBuffer(val.get<StringView>(), lrr);
236✔
2152
        if (!lrr.ttl)
236✔
2153
          break;
109✔
2154
      }
236✔
2155

2156
      if (cursor.next(key, val) || co.getDomainID(key.getNoStripHeader<StringView>()) != id) {
554!
2157
        // cout<<"hit end of zone or database when we shouldn't"<<endl;
2158
        return false;
×
2159
      }
×
2160
    }
554✔
2161
    after = co.getQName(key.getNoStripHeader<StringView>());
109✔
2162
    // cout<<"returning: before="<<before<<", after="<<after<<", unhashed: "<<unhashed<<endl;
2163
    return true;
109✔
2164
  }
109✔
2165

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

2168
  before = co.getQName(key.getNoStripHeader<StringView>());
3,332✔
2169
  if (before == qname) {
3,332✔
2170
    // cout << "Ended up on exact right node" << endl;
2171
    before = co.getQName(key.getNoStripHeader<StringView>());
1,853✔
2172
    // unhashed should be correct now, maybe check?
2173
    if (cursor.next(key, val)) {
1,853✔
2174
      // xxx should find first hash now
2175

2176
      if (cursor.lower_bound(co(id), key, val)) {
79!
2177
        // cout<<"hit end of zone find when we shouldn't for id "<<id<< __LINE__<<endl;
2178
        return false;
×
2179
      }
×
2180
      for (;;) {
438✔
2181
        if (co.getQType(key.getNoStripHeader<StringView>()) == QType::NSEC3) {
438✔
2182
          deserializeFromBuffer(val.get<StringView>(), lrr);
158✔
2183
          if (!lrr.ttl)
158✔
2184
            break;
79✔
2185
        }
158✔
2186

2187
        if (cursor.next(key, val) || co.getDomainID(key.getNoStripHeader<StringView>()) != id) {
359!
2188
          // cout<<"hit end of zone or database when we shouldn't" << __LINE__<<endl;
2189
          return false;
×
2190
        }
×
2191
      }
359✔
2192
      after = co.getQName(key.getNoStripHeader<StringView>());
79✔
2193
      // cout<<"returning: before="<<before<<", after="<<after<<", unhashed: "<<unhashed<<endl;
2194
      return true;
79✔
2195
    }
79✔
2196
  }
1,853✔
2197
  else {
1,479✔
2198
    // cout <<"Going backwards to find 'before'"<<endl;
2199
    int count = 0;
1,479✔
2200
    for (;;) {
3,806✔
2201
      if (co.getQName(key.getNoStripHeader<StringView>()).canonCompare(qname) && co.getQType(key.getNoStripHeader<StringView>()) == QType::NSEC3) {
3,806✔
2202
        // cout<<"Potentially stopping traverse at "<< co.getQName(key.get<StringView>()) <<", " << (co.getQName(key.get<StringView>()).canonCompare(qname))<<endl;
2203
        // cout<<"qname = "<<qname<<endl;
2204
        // cout<<"here  = "<<co.getQName(key.get<StringView>())<<endl;
2205
        deserializeFromBuffer(val.get<StringView>(), lrr);
1,704✔
2206
        if (!lrr.ttl)
1,704✔
2207
          break;
1,416✔
2208
      }
1,704✔
2209

2210
      if (cursor.prev(key, val) || co.getDomainID(key.getNoStripHeader<StringView>()) != id) {
2,390✔
2211
        // cout <<"XXX Hit *beginning* of zone or database"<<endl;
2212
        // this can happen, must deal with it
2213
        // should now find the last hash of the zone
2214

2215
        if (cursor.lower_bound(co(id + 1), key, val)) {
63!
2216
          // cout << "Could not find the next higher zone, going to the end of the database then"<<endl;
2217
          cursor.last(key, val);
63✔
2218
        }
63✔
2219
        else
×
2220
          cursor.prev(key, val);
×
2221

2222
        for (;;) {
566✔
2223
          if (co.getDomainID(key.getNoStripHeader<StringView>()) != id) {
566!
2224
            // cout<<"Last record also not part of this zone!"<<endl;
2225
            //  this implies something is wrong in the database, nothing we can do
2226
            return false;
×
2227
          }
×
2228

2229
          if (co.getQType(key.getNoStripHeader<StringView>()) == QType::NSEC3) {
566✔
2230
            deserializeFromBuffer(val.get<StringView>(), lrr);
136✔
2231
            if (!lrr.ttl) // the kind of NSEC3 we need
136✔
2232
              break;
63✔
2233
          }
136✔
2234
          if (cursor.prev(key, val)) {
503!
2235
            // hit beginning of database, again means something is wrong with it
2236
            return false;
×
2237
          }
×
2238
        }
503✔
2239
        before = co.getQName(key.getNoStripHeader<StringView>());
63✔
2240
        unhashed = DNSName(lrr.content.c_str(), lrr.content.size(), 0, false) + di.zone;
63✔
2241
        // cout <<"Should still find 'after'!"<<endl;
2242
        // for 'after', we need to find the first hash of this zone
2243

2244
        if (cursor.lower_bound(co(id), key, val)) {
63!
2245
          // cout<<"hit end of zone find when we shouldn't"<<endl;
2246
          // means database is wrong, nothing we can do
2247
          return false;
×
2248
        }
×
2249
        for (;;) {
405✔
2250
          if (co.getQType(key.getNoStripHeader<StringView>()) == QType::NSEC3) {
405✔
2251
            deserializeFromBuffer(val.get<StringView>(), lrr);
146✔
2252
            if (!lrr.ttl)
146✔
2253
              break;
63✔
2254
          }
146✔
2255

2256
          if (cursor.next(key, val)) {
342!
2257
            // means database is wrong, nothing we can do
2258
            // cout<<"hit end of zone when we shouldn't 2"<<endl;
2259
            return false;
×
2260
          }
×
2261
        }
342✔
2262
        after = co.getQName(key.getNoStripHeader<StringView>());
63✔
2263

2264
        // cout<<"returning: before="<<before<<", after="<<after<<", unhashed: "<<unhashed<<endl;
2265
        return true;
63✔
2266
      }
63✔
2267
      ++count;
2,327✔
2268
    }
2,327✔
2269
    before = co.getQName(key.getNoStripHeader<StringView>());
1,416✔
2270
    unhashed = DNSName(lrr.content.c_str(), lrr.content.size(), 0, false) + di.zone;
1,416✔
2271
    // cout<<"Went backwards, found "<<before<<endl;
2272
    // return us to starting point
2273
    while (count--)
3,422✔
2274
      cursor.next(key, val);
2,006✔
2275
  }
1,416✔
2276
  //  cout<<"Now going forward"<<endl;
2277
  for (int count = 0;; ++count) {
7,510✔
2278
    if ((count && cursor.next(key, val)) || co.getDomainID(key.getNoStripHeader<StringView>()) != id) {
7,510✔
2279
      // cout <<"Hit end of database or zone, finding first hash then in zone "<<id<<endl;
2280
      if (cursor.lower_bound(co(id), key, val)) {
106!
2281
        // cout<<"hit end of zone find when we shouldn't"<<endl;
2282
        // means database is wrong, nothing we can do
2283
        return false;
×
2284
      }
×
2285
      for (;;) {
793✔
2286
        if (co.getQType(key.getNoStripHeader<StringView>()) == QType::NSEC3) {
793✔
2287
          deserializeFromBuffer(val.get<StringView>(), lrr);
259✔
2288
          if (!lrr.ttl)
259✔
2289
            break;
106✔
2290
        }
259✔
2291

2292
        if (cursor.next(key, val)) {
687!
2293
          // means database is wrong, nothing we can do
2294
          // cout<<"hit end of zone when we shouldn't 2"<<endl;
2295
          return false;
×
2296
        }
×
2297
        // cout << "Next.. "<<endl;
2298
      }
687✔
2299
      after = co.getQName(key.getNoStripHeader<StringView>());
106✔
2300

2301
      // cout<<"returning: before="<<before<<", after="<<after<<", unhashed: "<<unhashed<<endl;
2302
      return true;
106✔
2303
    }
106✔
2304

2305
    // cout<<"After "<<co.getQName(key.get<StringView>()) <<endl;
2306
    if (co.getQType(key.getNoStripHeader<StringView>()) == QType::NSEC3) {
7,404✔
2307
      deserializeFromBuffer(val.get<StringView>(), lrr);
4,308✔
2308
      if (!lrr.ttl) {
4,308✔
2309
        break;
3,084✔
2310
      }
3,084✔
2311
    }
4,308✔
2312
  }
7,404✔
2313
  after = co.getQName(key.getNoStripHeader<StringView>());
3,084✔
2314
  // cout<<"returning: before="<<before<<", after="<<after<<", unhashed: "<<unhashed<<endl;
2315
  return true;
3,084✔
2316
}
3,190✔
2317

2318
bool LMDBBackend::getBeforeAndAfterNames(uint32_t id, const DNSName& zonenameU, const DNSName& qname, DNSName& before, DNSName& after)
2319
{
1,417✔
2320
  DNSName zonename = zonenameU.makeLowerCase();
1,417✔
2321
  //  cout << __PRETTY_FUNCTION__<< ": "<<id <<", "<<zonename << ", '"<<qname<<"'"<<endl;
2322

2323
  auto txn = getRecordsROTransaction(id);
1,417✔
2324
  compoundOrdername co;
1,417✔
2325
  DNSName qname2 = qname.makeRelative(zonename);
1,417✔
2326
  string matchkey = co(id, qname2);
1,417✔
2327
  auto cursor = txn->txn->getCursor(txn->db->dbi);
1,417✔
2328
  MDBOutVal key, val;
1,417✔
2329
  // cout<<"Lower_bound for "<<qname2<<endl;
2330
  if (cursor.lower_bound(matchkey, key, val)) {
1,417✔
2331
    // cout << "Hit end of database, bummer"<<endl;
2332
    cursor.last(key, val);
195✔
2333
    if (co.getDomainID(key.getNoStripHeader<string_view>()) == id) {
195!
2334
      before = co.getQName(key.getNoStripHeader<string_view>()) + zonename;
195✔
2335
      after = zonename;
195✔
2336
    }
195✔
2337
    // else
2338
    // cout << "We were at end of database, but this zone is not there?!"<<endl;
2339
    return true;
195✔
2340
  }
195✔
2341
  // cout<<"Cursor is at "<<co.getQName(key.get<string_view>()) <<", in zone id "<<co.getDomainID(key.get<string_view>())<< endl;
2342

2343
  if (co.getQType(key.getNoStripHeader<string_view>()).getCode() && co.getDomainID(key.getNoStripHeader<string_view>()) == id && co.getQName(key.getNoStripHeader<string_view>()) == qname2) { // don't match ENTs
1,222!
2344
    // cout << "Had an exact match!"<<endl;
2345
    before = qname2 + zonename;
600✔
2346
    int rc;
600✔
2347
    for (;;) {
2,238✔
2348
      rc = cursor.next(key, val);
2,238✔
2349
      if (rc)
2,238✔
2350
        break;
24✔
2351

2352
      if (co.getDomainID(key.getNoStripHeader<string_view>()) == id && key.getNoStripHeader<StringView>().rfind(matchkey, 0) == 0)
2,214!
2353
        continue;
1,280✔
2354
      LMDBResourceRecord lrr;
934✔
2355
      deserializeFromBuffer(val.get<StringView>(), lrr);
934✔
2356
      if (co.getQType(key.getNoStripHeader<string_view>()).getCode() && (lrr.auth || co.getQType(key.getNoStripHeader<string_view>()).getCode() == QType::NS))
934✔
2357
        break;
576✔
2358
    }
934✔
2359
    if (rc || co.getDomainID(key.getNoStripHeader<string_view>()) != id) {
600!
2360
      // cout << "We hit the end of the zone or database. 'after' is apex" << endl;
2361
      after = zonename;
24✔
2362
      return false;
24✔
2363
    }
24✔
2364
    after = co.getQName(key.getNoStripHeader<string_view>()) + zonename;
576✔
2365
    return true;
576✔
2366
  }
600✔
2367

2368
  if (co.getDomainID(key.getNoStripHeader<string_view>()) != id) {
622!
2369
    // cout << "Ended up in next zone, 'after' is zonename" <<endl;
UNCOV
2370
    after = zonename;
×
2371
    // cout << "Now hunting for previous" << endl;
UNCOV
2372
    int rc;
×
UNCOV
2373
    for (;;) {
×
UNCOV
2374
      rc = cursor.prev(key, val);
×
UNCOV
2375
      if (rc) {
×
2376
        // cout<<"Reversed into zone, but got not found from lmdb" <<endl;
2377
        return false;
×
2378
      }
×
2379

UNCOV
2380
      if (co.getDomainID(key.getNoStripHeader<string_view>()) != id) {
×
2381
        // cout<<"Reversed into zone, but found wrong zone id " << co.getDomainID(key.getNoStripHeader<string_view>()) << " != "<<id<<endl;
2382
        // "this can't happen"
2383
        return false;
×
2384
      }
×
UNCOV
2385
      LMDBResourceRecord lrr;
×
UNCOV
2386
      deserializeFromBuffer(val.get<StringView>(), lrr);
×
UNCOV
2387
      if (co.getQType(key.getNoStripHeader<string_view>()).getCode() && (lrr.auth || co.getQType(key.getNoStripHeader<string_view>()).getCode() == QType::NS))
×
UNCOV
2388
        break;
×
UNCOV
2389
    }
×
2390

UNCOV
2391
    before = co.getQName(key.getNoStripHeader<string_view>()) + zonename;
×
2392
    // cout<<"Found: "<< before<<endl;
UNCOV
2393
    return true;
×
UNCOV
2394
  }
×
2395

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

2398
  int skips = 0;
622✔
2399
  for (;;) {
992✔
2400
    LMDBResourceRecord lrr;
992✔
2401
    deserializeFromBuffer(val.get<StringView>(), lrr);
992✔
2402
    if (co.getQType(key.getNoStripHeader<string_view>()).getCode() && (lrr.auth || co.getQType(key.getNoStripHeader<string_view>()).getCode() == QType::NS)) {
992!
2403
      after = co.getQName(key.getNoStripHeader<string_view>()) + zonename;
622✔
2404
      // cout <<"Found auth ("<<lrr.auth<<") or an NS record "<<after<<", type: "<<co.getQType(key.getNoStripHeader<string_view>()).toString()<<", ttl = "<<lrr.ttl<<endl;
2405
      // cout << makeHexDump(val.get<string>()) << endl;
2406
      break;
622✔
2407
    }
622✔
2408
    // 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;
2409
    int rc = cursor.next(key, val);
370✔
2410
    if (!rc)
370!
2411
      ++skips;
370✔
2412
    if (rc || co.getDomainID(key.getNoStripHeader<string_view>()) != id) {
370!
2413
      // cout << "  oops, hit end of database or zone. This means after is apex" <<endl;
2414
      after = zonename;
×
2415
      break;
×
2416
    }
×
2417
  }
370✔
2418
  // go back to where we were
2419
  while (skips--)
992✔
2420
    cursor.prev(key, val);
370✔
2421

2422
  for (;;) {
1,116✔
2423
    int rc = cursor.prev(key, val);
1,116✔
2424
    if (rc || co.getDomainID(key.getNoStripHeader<string_view>()) != id) {
1,116!
2425
      // XX I don't think this case can happen
2426
      // cout << "We hit the beginning of the zone or database.. now what" << endl;
2427
      return false;
×
2428
    }
×
2429
    before = co.getQName(key.getNoStripHeader<string_view>()) + zonename;
1,116✔
2430
    LMDBResourceRecord lrr;
1,116✔
2431
    deserializeFromBuffer(val.get<string_view>(), lrr);
1,116✔
2432
    // cout<<"And before to "<<before<<", auth = "<<rr.auth<<endl;
2433
    if (co.getQType(key.getNoStripHeader<string_view>()).getCode() && (lrr.auth || co.getQType(key.getNoStripHeader<string_view>()) == QType::NS))
1,116✔
2434
      break;
622✔
2435
    // cout << "Oops, that was wrong, go back one more"<<endl;
2436
  }
1,116✔
2437

2438
  return true;
622✔
2439
}
622✔
2440

2441
bool LMDBBackend::updateDNSSECOrderNameAndAuth(uint32_t domain_id, const DNSName& qname, const DNSName& ordername, bool auth, const uint16_t qtype)
2442
{
61,280✔
2443
  //  cout << __PRETTY_FUNCTION__<< ": "<< domain_id <<", '"<<qname <<"', '"<<ordername<<"', "<<auth<< ", " << qtype << endl;
2444
  shared_ptr<RecordsRWTransaction> txn;
61,280✔
2445
  bool needCommit = false;
61,280✔
2446
  if (d_rwtxn && d_transactiondomainid == domain_id) {
61,280!
2447
    txn = d_rwtxn;
61,280✔
2448
    //    cout<<"Reusing open transaction"<<endl;
2449
  }
61,280✔
2450
  else {
×
2451
    //    cout<<"Making a new RW txn for " << __PRETTY_FUNCTION__ <<endl;
2452
    txn = getRecordsRWTransaction(domain_id);
×
2453
    needCommit = true;
×
2454
  }
×
2455

2456
  DomainInfo di;
61,280✔
2457
  if (!d_tdomains->getROTransaction().get(domain_id, di)) {
61,280!
2458
    //    cout<<"Could not find domain_id "<<domain_id <<endl;
2459
    return false;
×
2460
  }
×
2461

2462
  DNSName rel = qname.makeRelative(di.zone);
61,280✔
2463

2464
  compoundOrdername co;
61,280✔
2465
  string matchkey = co(domain_id, rel);
61,280✔
2466

2467
  auto cursor = txn->txn->getCursor(txn->db->dbi);
61,280✔
2468
  MDBOutVal key, val;
61,280✔
2469
  if (cursor.lower_bound(matchkey, key, val)) {
61,280!
2470
    // cout << "Could not find anything"<<endl;
2471
    return false;
×
2472
  }
×
2473

2474
  bool hasOrderName = !ordername.empty();
61,280✔
2475
  bool needNSEC3 = hasOrderName;
61,280✔
2476

2477
  for (; key.getNoStripHeader<StringView>().rfind(matchkey, 0) == 0;) {
122,964✔
2478
    vector<LMDBResourceRecord> lrrs;
61,879✔
2479

2480
    if (co.getQType(key.getNoStripHeader<StringView>()) != QType::NSEC3) {
61,879✔
2481
      deserializeFromBuffer(val.get<StringView>(), lrrs);
61,769✔
2482
      bool changed = false;
61,769✔
2483
      vector<LMDBResourceRecord> newRRs;
61,769✔
2484
      for (auto& lrr : lrrs) {
62,384✔
2485
        lrr.qtype = co.getQType(key.getNoStripHeader<StringView>());
62,384✔
2486
        if (!needNSEC3 && qtype != QType::ANY) {
62,384✔
2487
          needNSEC3 = (lrr.ordername && QType(qtype) != lrr.qtype);
474✔
2488
        }
474✔
2489

2490
        if ((qtype == QType::ANY || QType(qtype) == lrr.qtype) && (lrr.ordername != hasOrderName || lrr.auth != auth)) {
62,384✔
2491
          lrr.auth = auth;
61,282✔
2492
          lrr.ordername = hasOrderName;
61,282✔
2493
          changed = true;
61,282✔
2494
        }
61,282✔
2495
        newRRs.push_back(std::move(lrr));
62,384✔
2496
      }
62,384✔
2497
      if (changed) {
61,769✔
2498
        cursor.put(key, serializeToBuffer(newRRs));
60,913✔
2499
      }
60,913✔
2500
    }
61,769✔
2501

2502
    if (cursor.next(key, val))
61,879✔
2503
      break;
195✔
2504
  }
61,879✔
2505

2506
  bool del = false;
61,280✔
2507
  LMDBResourceRecord lrr;
61,280✔
2508
  matchkey = co(domain_id, rel, QType::NSEC3);
61,280✔
2509
  // cerr<<"here qname="<<qname<<" ordername="<<ordername<<" qtype="<<qtype<<" matchkey="<<makeHexDump(matchkey)<<endl;
2510
  int txngetrc;
61,280✔
2511
  if (!(txngetrc = txn->txn->get(txn->db->dbi, matchkey, val))) {
61,280✔
2512
    deserializeFromBuffer(val.get<string_view>(), lrr);
110✔
2513

2514
    if (needNSEC3) {
110✔
2515
      if (hasOrderName && lrr.content != ordername.toDNSStringLC()) {
93✔
2516
        del = true;
1✔
2517
      }
1✔
2518
    }
93✔
2519
    else {
17✔
2520
      del = true;
17✔
2521
    }
17✔
2522
    if (del) {
110✔
2523
      txn->txn->del(txn->db->dbi, co(domain_id, DNSName(lrr.content.c_str(), lrr.content.size(), 0, false), QType::NSEC3));
18✔
2524
      txn->txn->del(txn->db->dbi, matchkey);
18✔
2525
    }
18✔
2526
  }
110✔
2527
  else {
61,170✔
2528
    del = true;
61,170✔
2529
  }
61,170✔
2530

2531
  if (hasOrderName && del) {
61,280✔
2532
    matchkey = co(domain_id, rel, QType::NSEC3);
60,563✔
2533

2534
    lrr.ttl = 0;
60,563✔
2535
    lrr.auth = 0;
60,563✔
2536
    lrr.content = rel.toDNSStringLC();
60,563✔
2537

2538
    string str = serializeToBuffer(lrr);
60,563✔
2539
    txn->txn->put(txn->db->dbi, co(domain_id, ordername, QType::NSEC3), str);
60,563✔
2540
    lrr.ttl = 1;
60,563✔
2541
    lrr.content = ordername.toDNSStringLC();
60,563✔
2542
    str = serializeToBuffer(lrr);
60,563✔
2543
    txn->txn->put(txn->db->dbi, matchkey, str); // 2
60,563✔
2544
  }
60,563✔
2545

2546
  if (needCommit)
61,280!
2547
    txn->txn->commit();
×
2548
  return false;
61,280✔
2549
}
61,280✔
2550

2551
bool LMDBBackend::updateEmptyNonTerminals(uint32_t domain_id, set<DNSName>& insert, set<DNSName>& erase, bool remove)
2552
{
39✔
2553
  // cout << __PRETTY_FUNCTION__<< ": "<< domain_id << ", insert.size() "<<insert.size()<<", "<<erase.size()<<", " <<remove<<endl;
2554

2555
  bool needCommit = false;
39✔
2556
  shared_ptr<RecordsRWTransaction> txn;
39✔
2557
  if (d_rwtxn && d_transactiondomainid == domain_id) {
39!
2558
    txn = d_rwtxn;
39✔
2559
    //    cout<<"Reusing open transaction"<<endl;
2560
  }
39✔
2561
  else {
×
2562
    //    cout<<"Making a new RW txn for delete domain"<<endl;
2563
    txn = getRecordsRWTransaction(domain_id);
×
2564
    needCommit = true;
×
2565
  }
×
2566

2567
  // if remove is set, all ENTs should be removed & nothing else should be done
2568
  if (remove) {
39!
2569
    deleteDomainRecords(*txn, domain_id, 0);
×
2570
  }
×
2571
  else {
39✔
2572
    DomainInfo di;
39✔
2573
    auto rotxn = d_tdomains->getROTransaction();
39✔
2574
    if (!rotxn.get(domain_id, di)) {
39!
2575
      // cout <<"No such domain with id "<<domain_id<<endl;
2576
      return false;
×
2577
    }
×
2578
    compoundOrdername co;
39✔
2579
    for (const auto& n : insert) {
174✔
2580
      LMDBResourceRecord lrr;
173✔
2581
      lrr.qname = n.makeRelative(di.zone);
173✔
2582
      lrr.ttl = 0;
173✔
2583
      lrr.auth = true;
173✔
2584

2585
      std::string ser = serializeToBuffer(lrr);
173✔
2586

2587
      txn->txn->put(txn->db->dbi, co(domain_id, lrr.qname, 0), ser);
173✔
2588

2589
      // cout <<" +"<<n<<endl;
2590
    }
173✔
2591
    for (auto n : erase) {
39✔
2592
      // cout <<" -"<<n<<endl;
2593
      n.makeUsRelative(di.zone);
1✔
2594
      txn->txn->del(txn->db->dbi, co(domain_id, n, 0));
1✔
2595
    }
1✔
2596
  }
39✔
2597
  if (needCommit)
39!
2598
    txn->txn->commit();
×
2599
  return false;
39✔
2600
}
39✔
2601

2602
/* TSIG */
2603
bool LMDBBackend::getTSIGKey(const DNSName& name, DNSName& algorithm, string& content)
2604
{
71✔
2605
  auto txn = d_ttsig->getROTransaction();
71✔
2606
  LmdbIdVec ids;
71✔
2607
  txn.get_multi<0>(name, ids);
71✔
2608

2609
  TSIGKey key;
71✔
2610
  for (auto id : ids) {
71✔
2611
    if (txn.get(id, key)) {
55!
2612
      if (algorithm.empty() || algorithm == DNSName(key.algorithm)) {
55!
2613
        algorithm = DNSName(key.algorithm);
55✔
2614
        content = key.key;
55✔
2615
      }
55✔
2616
    }
55✔
2617
  }
55✔
2618

2619
  return true;
71✔
2620
}
71✔
2621

2622
// this deletes an old key if it has the same algorithm
2623
bool LMDBBackend::setTSIGKey(const DNSName& name, const DNSName& algorithm, const string& content)
2624
{
22✔
2625
  auto txn = d_ttsig->getRWTransaction();
22✔
2626

2627
  LmdbIdVec ids;
22✔
2628
  txn.get_multi<0>(name, ids);
22✔
2629

2630
  TSIGKey key;
22✔
2631
  for (auto id : ids) {
22✔
2632
    if (txn.get(id, key)) {
2!
2633
      if (key.algorithm == algorithm) {
2✔
2634
        txn.del(id);
1✔
2635
      }
1✔
2636
    }
2✔
2637
  }
2✔
2638

2639
  TSIGKey tk;
22✔
2640
  tk.name = name;
22✔
2641
  tk.algorithm = algorithm;
22✔
2642
  tk.key = content;
22✔
2643

2644
  txn.put(tk, 0, d_random_ids);
22✔
2645
  txn.commit();
22✔
2646

2647
  return true;
22✔
2648
}
22✔
2649
bool LMDBBackend::deleteTSIGKey(const DNSName& name)
2650
{
2✔
2651
  auto txn = d_ttsig->getRWTransaction();
2✔
2652

2653
  LmdbIdVec ids;
2✔
2654
  txn.get_multi<0>(name, ids);
2✔
2655

2656
  TSIGKey key;
2✔
2657

2658
  for (auto id : ids) {
2✔
2659
    if (txn.get(id, key)) {
2!
2660
      txn.del(id);
2✔
2661
    }
2✔
2662
  }
2✔
2663
  txn.commit();
2✔
2664
  return true;
2✔
2665
}
2✔
2666
bool LMDBBackend::getTSIGKeys(std::vector<struct TSIGKey>& keys)
2667
{
1✔
2668
  auto txn = d_ttsig->getROTransaction();
1✔
2669

2670
  keys.clear();
1✔
2671
  // In a perfect world, we would simply iterate over txn and add every
2672
  // item to the returned vector:
2673
  //   for (auto iter = txn.begin(); iter != txn.end(); ++iter) {
2674
  //     keys.push_back(*iter);
2675
  //   }
2676
  // But databases converted from older (< 5) schemas _may_ have multiple
2677
  // entries for the same TSIG key name and algorithm, something which is not
2678
  // allowed in the v5 database schema. These extra entries will not be found
2679
  // by get_multi<> during regular operations, and would only appear in the
2680
  // results of this method.
2681
  // In order to prevent this, we first only gather the list of key names, and
2682
  // in a second step, query for them using a similar logic as getTSIGKey().
2683
  // Unfortunately, there does not seem to be a way to know if the database had
2684
  // been created using the v5 schema (not converted), in which case we could
2685
  // use the above, simpler logic.
2686
  std::unordered_set<DNSName> keynames;
1✔
2687
  for (const auto& iter : txn) {
10✔
2688
    keynames.insert(iter.name);
10✔
2689
  }
10✔
2690
  for (const auto& iter : keynames) {
9✔
2691
    LmdbIdVec ids;
9✔
2692
    txn.get_multi<0>(iter, ids);
9✔
2693
    for (auto key_id : ids) {
10✔
2694
      TSIGKey key;
10✔
2695
      if (txn.get(key_id, key)) {
10!
2696
        keys.push_back(key);
10✔
2697
      }
10✔
2698
    }
10✔
2699
  }
9✔
2700
  return true;
1✔
2701
}
1✔
2702

2703
string LMDBBackend::directBackendCmd(const string& query)
2704
{
×
2705
  ostringstream ret, usage;
×
2706

2707
  usage << "info                               show some information about the database" << endl;
×
2708
  usage << "index check domains                check zone<>ID indexes" << endl;
×
2709
  usage << "index refresh domains <ID>         refresh index for zone with this ID" << endl;
×
2710
  usage << "index refresh-all domains          refresh index for all zones with disconnected indexes" << endl;
×
2711
  vector<string> argv;
×
2712
  stringtok(argv, query);
×
2713

2714
  if (argv.empty()) {
×
2715
    return usage.str();
×
2716
  }
×
2717

2718
  string& cmd = argv[0];
×
2719

2720
  if (cmd == "help") {
×
2721
    return usage.str();
×
2722
  }
×
2723

2724
  if (cmd == "info") {
×
2725
    ret << "shards: " << s_shards << endl;
×
2726
    ret << "schemaversion: " << SCHEMAVERSION << endl;
×
2727

2728
    return ret.str();
×
2729
  }
×
2730

2731
  if (cmd == "index") {
×
2732
    if (argv.size() < 2) {
×
2733
      return "need an index subcommand\n";
×
2734
    }
×
2735

2736
    string& subcmd = argv[1];
×
2737

2738
    if (subcmd == "check" || subcmd == "refresh-all") {
×
2739
      bool refresh = false;
×
2740

2741
      if (subcmd == "refresh-all") {
×
2742
        refresh = true;
×
2743
      }
×
2744

2745
      if (argv.size() < 3) {
×
2746
        return "need an index name\n";
×
2747
      }
×
2748

2749
      if (argv[2] != "domains") {
×
2750
        return "can only check the domains index\n";
×
2751
      }
×
2752

2753
      vector<uint32_t> refreshQueue;
×
2754

2755
      {
×
2756
        auto txn = d_tdomains->getROTransaction();
×
2757

2758
        for (auto iter = txn.begin(); iter != txn.end(); ++iter) {
×
2759
          DomainInfo di = *iter;
×
2760

2761
          auto id = iter.getID();
×
2762

2763
          LmdbIdVec ids;
×
2764
          txn.get_multi<0>(di.zone, ids);
×
2765

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

2769
            if (ids.empty()) {
×
2770
              ret << "zone->ID index has no entry for " << di.zone << endl;
×
2771
              if (refresh) {
×
2772
                refreshQueue.push_back(id);
×
2773
              }
×
2774
              else {
×
2775
                ret << "  suggested remedy: index refresh domains " << id << endl;
×
2776
              }
×
2777
            }
×
2778
            else {
×
2779
              // ids.size() > 1
2780
              ret << "zone->ID index has multiple entries for " << di.zone << ": ";
×
2781
              for (auto id_ : ids) {
×
2782
                ret << id_ << " ";
×
2783
              }
×
2784
              ret << endl;
×
2785
            }
×
2786
          }
×
2787
        }
×
2788
      }
×
2789

2790
      if (refresh) {
×
2791
        for (const auto& id : refreshQueue) {
×
2792
          if (genChangeDomain(id, [](DomainInfo& /* di */) {})) {
×
2793
            ret << "refreshed " << id << endl;
×
2794
          }
×
2795
          else {
×
2796
            ret << "failed to refresh " << id << endl;
×
2797
          }
×
2798
        }
×
2799
      }
×
2800
      return ret.str();
×
2801
    }
×
2802
    if (subcmd == "refresh") {
×
2803
      // index refresh domains 12345
2804
      if (argv.size() < 4) {
×
2805
        return "usage: index refresh domains <ID>\n";
×
2806
      }
×
2807

2808
      if (argv[2] != "domains") {
×
2809
        return "can only refresh in the domains index\n";
×
2810
      }
×
2811

2812
      uint32_t id = 0;
×
2813

2814
      try {
×
2815
        id = pdns::checked_stoi<uint32_t>(argv[3]);
×
2816
      }
×
2817
      catch (const std::out_of_range& e) {
×
2818
        return "ID out of range\n";
×
2819
      }
×
2820

2821
      if (genChangeDomain(id, [](DomainInfo& /* di */) {})) {
×
2822
        ret << "refreshed" << endl;
×
2823
      }
×
2824
      else {
×
2825
        ret << "failed" << endl;
×
2826
      }
×
2827
      return ret.str();
×
2828
    }
×
2829
  }
×
2830

2831
  return "unknown lmdbbackend command\n";
×
2832
}
×
2833

2834
class LMDBFactory : public BackendFactory
2835
{
2836
public:
2837
  LMDBFactory() :
2838
    BackendFactory("lmdb") {}
3,484✔
2839
  void declareArguments(const string& suffix = "") override
2840
  {
680✔
2841
    declare(suffix, "filename", "Filename for lmdb", "./pdns.lmdb");
680✔
2842
    declare(suffix, "sync-mode", "Synchronisation mode: nosync, nometasync, sync", "sync");
680✔
2843
    // there just is no room for more on 32 bit
2844
    declare(suffix, "shards", "Records database will be split into this number of shards", (sizeof(void*) == 4) ? "2" : "64");
680✔
2845
    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));
680✔
2846
    declare(suffix, "random-ids", "Numeric IDs inside the database are generated randomly instead of sequentially", "no");
680✔
2847
    declare(suffix, "map-size", "LMDB map size in megabytes", (sizeof(void*) == 4) ? "100" : "16000");
680✔
2848
    declare(suffix, "flag-deleted", "Flag entries on deletion instead of deleting them", "no");
680✔
2849
    declare(suffix, "lightning-stream", "Run in Lightning Stream compatible mode", "no");
680✔
2850
  }
680✔
2851
  DNSBackend* make(const string& suffix = "") override
2852
  {
1,993✔
2853
    return new LMDBBackend(suffix);
1,993✔
2854
  }
1,993✔
2855
};
2856

2857
/* THIRD PART */
2858

2859
class LMDBLoader
2860
{
2861
public:
2862
  LMDBLoader()
2863
  {
3,484✔
2864
    BackendMakers().report(std::make_unique<LMDBFactory>());
3,484✔
2865
    g_log << Logger::Info << "[lmdbbackend] This is the lmdb backend version " VERSION
3,484✔
2866
#ifndef REPRODUCIBLE
3,484✔
2867
          << " (" __DATE__ " " __TIME__ ")"
3,484✔
2868
#endif
3,484✔
2869
          << " reporting" << endl;
3,484✔
2870
  }
3,484✔
2871
};
2872

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

© 2025 Coveralls, Inc