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

PowerDNS / pdns / 18743945403

23 Oct 2025 09:29AM UTC coverage: 65.845% (+0.02%) from 65.829%
18743945403

Pull #16356

github

web-flow
Merge 8a2027ef1 into efa3637e8
Pull Request #16356: auth 5.0: backport "pdnsutil: fix b2b-migrate to from sql to non-sql"

42073 of 92452 branches covered (45.51%)

Branch coverage included in aggregate %.

128008 of 165855 relevant lines covered (77.18%)

6379935.17 hits per line

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

52.12
/pdns/arguments.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
#ifdef HAVE_CONFIG_H
23
#include "config.h"
24
#endif
25
#include "arguments.hh"
26
#include <boost/algorithm/string.hpp>
27
#include <boost/algorithm/string/compare.hpp>
28
#include <boost/algorithm/string/predicate.hpp>
29

30
#include "namespaces.hh"
31
#include "logger.hh"
32
#include <sys/types.h>
33
#include <dirent.h>
34
#include <sys/stat.h>
35
#include <unistd.h>
36
#include <climits>
37

38
ArgvMap::param_t::const_iterator ArgvMap::begin()
39
{
×
40
  return d_params.begin();
×
41
}
×
42

43
ArgvMap::param_t::const_iterator ArgvMap::end()
44
{
×
45
  return d_params.end();
×
46
}
×
47

48
string& ArgvMap::set(const string& var)
49
{
585,235✔
50
  return d_params[var];
585,235✔
51
}
585,235✔
52

53
void ArgvMap::setDefault(const string& var, const string& value)
54
{
248,694✔
55
  if (defaultmap.count(var) == 0) {
248,694!
56
    defaultmap.insert(pair<string, string>(var, value));
248,694✔
57
  }
248,694✔
58
}
248,694✔
59

60
void ArgvMap::setDefaults()
61
{
461✔
62
  for (const auto& param : d_params) {
88,872✔
63
    if (defaultmap.count(param.first) == 0) {
88,872!
64
      defaultmap.insert(param);
88,872✔
65
    }
88,872✔
66
  }
88,872✔
67
}
461✔
68

69
bool ArgvMap::mustDo(const string& var)
70
{
180,196✔
71
  return ((*this)[var] != "no") && ((*this)[var] != "off");
180,196✔
72
}
180,196✔
73

74
vector<string> ArgvMap::list()
75
{
6✔
76
  vector<string> ret;
6✔
77
  ret.reserve(d_params.size());
6✔
78
  for (const auto& param : d_params) {
1,399✔
79
    ret.push_back(param.first);
1,399✔
80
  }
1,399✔
81
  return ret;
6✔
82
}
6✔
83

84
string& ArgvMap::set(const string& var, const string& help)
85
{
525,010✔
86
  helpmap[var] = help;
525,010✔
87
  d_typeMap[var] = "Parameter";
525,010✔
88
  return set(var);
525,010✔
89
}
525,010✔
90

91
void ArgvMap::setCmd(const string& var, const string& help)
92
{
9,497✔
93
  helpmap[var] = help;
9,497✔
94
  d_typeMap[var] = "Command";
9,497✔
95
  set(var) = "no";
9,497✔
96
}
9,497✔
97

98
string& ArgvMap::setSwitch(const string& var, const string& help)
99
{
44,800✔
100
  helpmap[var] = help;
44,800✔
101
  d_typeMap[var] = "Switch";
44,800✔
102
  return set(var);
44,800✔
103
}
44,800✔
104

105
bool ArgvMap::contains(const string& var, const string& val)
106
{
×
107
  const auto& param = d_params.find(var);
×
108
  if (param == d_params.end() || param->second.empty()) {
×
109
    return false;
×
110
  }
×
111
  vector<string> parts;
×
112

113
  stringtok(parts, param->second, ", \t");
×
114
  return std::find(parts.begin(), parts.end(), val) != parts.end();
×
115
}
×
116

117
string ArgvMap::helpstring(string prefix)
118
{
×
119
  if (prefix == "no") {
×
120
    prefix = "";
×
121
  }
×
122

123
  string help;
×
124

125
  for (const auto& helpitem : helpmap) {
×
126
    if (!prefix.empty() && helpitem.first.find(prefix) != 0) { // only print items with prefix
×
127
      continue;
×
128
    }
×
129

130
    help += "  --";
×
131
    help += helpitem.first;
×
132

133
    string type = d_typeMap[helpitem.first];
×
134

135
    if (type == "Parameter") {
×
136
      help += "=...";
×
137
    }
×
138
    else if (type == "Switch") {
×
139
      help += " | --" + helpitem.first + "=yes";
×
140
      help += " | --" + helpitem.first + "=no";
×
141
    }
×
142

143
    help += "\n\t";
×
144
    help += helpitem.second;
×
145
    help += "\n";
×
146
  }
×
147
  return help;
×
148
}
×
149

150
string ArgvMap::formatOne(bool running, bool full, const string& var, const string& help, const string& theDefault, const string& current)
151
{
×
152
  string out;
×
153

154
  if (!running || full) {
×
155
    out += "#################################\n";
×
156
    out += "# ";
×
157
    out += var;
×
158
    out += "\t";
×
159
    out += help;
×
160
    out += "\n#\n";
×
161
  }
×
162
  else {
×
163
    if (theDefault == current) {
×
164
      return "";
×
165
    }
×
166
  }
×
167

168
  if (!running || theDefault == current) {
×
169
    out += "# ";
×
170
  }
×
171

172
  if (running) {
×
173
    out += var + "=" + current + "\n";
×
174
    if (full) {
×
175
      out += "\n";
×
176
    }
×
177
  }
×
178
  else {
×
179
    out += var + "=" + theDefault + "\n\n";
×
180
  }
×
181

182
  return out;
×
183
}
×
184

185
// If running and full, only changed settings are returned.
186
string ArgvMap::configstring(bool running, bool full)
187
{
×
188
  string help;
×
189

190
  if (running) {
×
191
    help = "# Autogenerated configuration file based on running instance (" + nowTime() + ")\n\n";
×
192
  }
×
193
  else {
×
194
    help = "# Autogenerated configuration file template\n\n";
×
195
  }
×
196

197
  // Affects parsing, should come first.
198
  help += formatOne(running, full, "ignore-unknown-settings", helpmap["ignore-unknown-settings"], defaultmap["ignore-unknown-settings"], d_params["ignore-unknown-settings"]);
×
199

200
  for (const auto& helpitem : helpmap) {
×
201
    if (d_typeMap[helpitem.first] == "Command") {
×
202
      continue;
×
203
    }
×
204
    if (helpitem.first == "ignore-unknown-settings") {
×
205
      continue;
×
206
    }
×
207

208
    if (defaultmap.count(helpitem.first) == 0) {
×
209
      throw ArgException(string("Default for setting '") + helpitem.first + "' not set");
×
210
    }
×
211

212
    help += formatOne(running, full, helpitem.first, helpitem.second, defaultmap[helpitem.first], d_params[helpitem.first]);
×
213
  }
×
214

215
  if (running) {
×
216
    for (const auto& unknown : d_unknownParams) {
×
217
      help += formatOne(running, full, unknown.first, "unknown setting", "", unknown.second);
×
218
    }
×
219
  }
×
220

221
  return help;
×
222
}
×
223

224
const string& ArgvMap::operator[](const string& arg)
225
{
1,054,701✔
226
  if (!parmIsset(arg)) {
1,054,701!
227
    throw ArgException(string("Undefined but needed argument: '") + arg + "'");
×
228
  }
×
229

230
  return d_params[arg];
1,054,701✔
231
}
1,054,701✔
232

233
mode_t ArgvMap::asMode(const string& arg)
234
{
×
235
  if (!parmIsset(arg)) {
×
236
    throw ArgException(string("Undefined but needed argument: '") + arg + "'");
×
237
  }
×
238

239
  const auto* const cptr_orig = d_params[arg].c_str();
×
240
  char* cptr_ret = nullptr;
×
241

242
  auto mode = static_cast<mode_t>(strtol(cptr_orig, &cptr_ret, 8));
×
243
  if (mode == 0 && cptr_ret == cptr_orig) {
×
244
    throw ArgException("'" + arg + string("' contains invalid octal mode"));
×
245
  }
×
246
  return mode;
×
247
}
×
248

249
gid_t ArgvMap::asGid(const string& arg)
250
{
×
251
  if (!parmIsset(arg)) {
×
252
    throw ArgException(string("Undefined but needed argument: '") + arg + "'");
×
253
  }
×
254

255
  const auto* cptr_orig = d_params[arg].c_str();
×
256
  char* cptr_ret = nullptr;
×
257
  auto gid = static_cast<gid_t>(strtol(cptr_orig, &cptr_ret, 0));
×
258
  if (gid == 0 && cptr_ret == cptr_orig) {
×
259
    // try to resolve
260

261
    struct group* group = getgrnam(d_params[arg].c_str()); // NOLINT: called before going multi-threaded
×
262
    if (group == nullptr) {
×
263
      throw ArgException("'" + arg + string("' contains invalid group"));
×
264
    }
×
265
    gid = group->gr_gid;
×
266
  }
×
267
  return gid;
×
268
}
×
269

270
uid_t ArgvMap::asUid(const string& arg)
271
{
×
272
  if (!parmIsset(arg)) {
×
273
    throw ArgException(string("Undefined but needed argument: '") + arg + "'");
×
274
  }
×
275

276
  const auto* cptr_orig = d_params[arg].c_str();
×
277
  char* cptr_ret = nullptr;
×
278

279
  auto uid = static_cast<uid_t>(strtol(cptr_orig, &cptr_ret, 0));
×
280
  if (uid == 0 && cptr_ret == cptr_orig) {
×
281
    // try to resolve
282
    struct passwd* pwent = getpwnam(d_params[arg].c_str()); // NOLINT: called before going multi-threaded
×
283
    if (pwent == nullptr) {
×
284
      throw ArgException("'" + arg + string("' contains invalid group"));
×
285
    }
×
286
    uid = pwent->pw_uid;
×
287
  }
×
288
  return uid;
×
289
}
×
290

291
int ArgvMap::asNum(const string& arg, int def)
292
{
108,156✔
293
  if (!parmIsset(arg)) {
108,156!
294
    throw ArgException(string("Undefined but needed argument: '") + arg + "'");
×
295
  }
×
296

297
  // use default for empty values
298
  if (d_params[arg].empty()) {
108,156✔
299
    return def;
83✔
300
  }
83✔
301

302
  const auto* cptr_orig = d_params[arg].c_str();
108,073✔
303
  char* cptr_ret = nullptr;
108,073✔
304
  auto retval = static_cast<int>(strtol(cptr_orig, &cptr_ret, 0));
108,073✔
305
  if (retval == 0 && cptr_ret == cptr_orig) {
108,073!
306
    throw ArgException("'" + arg + "' value '" + string(cptr_orig) + string("' is not a valid number"));
×
307
  }
×
308

309
  return retval;
108,073✔
310
}
108,073✔
311

312
bool ArgvMap::isEmpty(const string& arg)
313
{
1,222✔
314
  if (!parmIsset(arg)) {
1,222!
315
    return true;
×
316
  }
×
317
  return d_params[arg].empty();
1,222✔
318
}
1,222✔
319

320
double ArgvMap::asDouble(const string& arg)
321
{
175✔
322
  if (!parmIsset(arg)) {
175!
323
    throw ArgException(string("Undefined but needed argument: '") + arg + "'");
×
324
  }
×
325

326
  if (d_params[arg].empty()) {
175!
327
    return 0.0;
×
328
  }
×
329

330
  const auto* cptr_orig = d_params[arg].c_str();
175✔
331
  char* cptr_ret = nullptr;
175✔
332
  auto retval = strtod(cptr_orig, &cptr_ret);
175✔
333

334
  if (retval == 0 && cptr_ret == cptr_orig) {
175!
335
    throw ArgException("'" + arg + string("' is not valid double"));
×
336
  }
×
337

338
  return retval;
175✔
339
}
175✔
340

341
ArgvMap::ArgvMap()
342
{
7,222✔
343
  set("ignore-unknown-settings", "Configuration settings to ignore if they are unknown") = "";
7,222✔
344
}
7,222✔
345

346
bool ArgvMap::parmIsset(const string& var)
347
{
1,304,052✔
348
  return d_params.find(var) != d_params.end();
1,304,052✔
349
}
1,304,052✔
350

351
// ATM Shared between Recursor and Auth, is that a good idea?
352
static const map<string, string> deprecateList = {
353
  {"stats-api-blacklist", "stats-api-disabled-list"},
354
  {"stats-carbon-blacklist", "stats-carbon-disabled-list"},
355
  {"stats-rec-control-blacklist", "stats-rec-control-disabled-list"},
356
  {"stats-snmp-blacklist", "stats-snmp-disabled-list"},
357
  {"edns-subnet-whitelist", "edns-subnet-allow-list"},
358
  {"new-domain-whitelist", "new-domain-ignore-list"},
359
  {"snmp-master-socket", "snmp-daemon-socket"},
360
  {"xpf-allow-from", "Proxy Protocol"},
361
  {"xpf-rr-code", "Proxy Protocol"},
362
  {"domain-metadata-cache-ttl", "zone-metadata-cache-ttl"},
363
};
364

365
// NOLINTNEXTLINE(readability-convert-member-functions-to-static): accesses d_log (compiled out in auth, hence clang-tidy message)
366
void ArgvMap::warnIfDeprecated(const string& var) const
367
{
7,521✔
368
  const auto msg = deprecateList.find(var);
7,521✔
369
  if (msg != deprecateList.end()) {
7,521✔
370
    SLOG(g_log << Logger::Warning << "'" << var << "' is deprecated and will be removed in a future release, use '" << msg->second << "' instead" << endl,
1✔
371
         d_log->info(Logr::Warning, "Option is deprecated and will be removed in a future release", "deprecatedName", Logging::Loggable(var), "alternative", Logging::Loggable(msg->second)));
1✔
372
  }
1✔
373
}
7,521✔
374

375
string ArgvMap::isDeprecated(const string& var)
376
{
2,329✔
377
  const auto msg = deprecateList.find(var);
2,329✔
378
  return msg != deprecateList.end() ? msg->second : "";
2,329✔
379
}
2,329✔
380

381
void ArgvMap::parseOne(const string& arg, const string& parseOnly, bool lax)
382
{
137,960✔
383
  string var;
137,960✔
384
  string val;
137,960✔
385
  string::size_type pos = 0;
137,960✔
386
  bool incremental = false;
137,960✔
387

388
  if (arg.find("--") == 0 && (pos = arg.find("+=")) != string::npos) // this is a --port+=25 case
137,960✔
389
  {
480✔
390
    var = arg.substr(2, pos - 2);
480✔
391
    val = arg.substr(pos + 2);
480✔
392
    incremental = true;
480✔
393
  }
480✔
394
  else if (arg.find("--") == 0 && (pos = arg.find('=')) != string::npos) // this is a --port=25 case
137,480✔
395
  {
114,372✔
396
    var = arg.substr(2, pos - 2);
114,372✔
397
    val = arg.substr(pos + 1);
114,372✔
398
  }
114,372✔
399
  else if (arg.find("--") == 0 && (arg.find('=') == string::npos)) // this is a --daemon case
23,108!
400
  {
20,822✔
401
    var = arg.substr(2);
20,822✔
402
    val = "";
20,822✔
403
  }
20,822✔
404
  else if (arg[0] == '-' && arg.length() > 1) {
2,286!
405
    var = arg.substr(1);
×
406
    val = "";
×
407
  }
×
408
  else { // command
2,286✔
409
    d_cmds.push_back(arg);
2,286✔
410
  }
2,286✔
411

412
  boost::trim(var);
137,960✔
413

414
  if (!var.empty() && (parseOnly.empty() || var == parseOnly)) {
137,960✔
415
    if (!lax) {
126,846✔
416
      warnIfDeprecated(var);
7,521✔
417
    }
7,521✔
418
    pos = val.find_first_not_of(" \t"); // strip leading whitespace
126,846✔
419
    if (pos != 0 && pos != string::npos) {
126,846!
420
      val = val.substr(pos);
×
421
    }
×
422
    if (parmIsset(var)) {
126,846✔
423
      if (incremental) {
68,632✔
424
        if (d_params[var].empty()) {
476!
425
          if (d_cleared.count(var) == 0) {
×
426
            throw ArgException("Incremental setting '" + var + "' without a parent");
×
427
          }
×
428
          d_params[var] = val;
×
429
        }
×
430
        else {
476✔
431
          d_params[var] += ", " + val;
476✔
432
        }
476✔
433
      }
476✔
434
      else {
68,156✔
435
        d_params[var] = val;
68,156✔
436
        d_cleared.insert(std::move(var));
68,156✔
437
      }
68,156✔
438
    }
68,632✔
439
    else {
58,214✔
440
      // unknown setting encountered. see if its on the ignore list before throwing.
441
      vector<string> parts;
58,214✔
442
      stringtok(parts, d_params["ignore-unknown-settings"], " ,\t\n\r");
58,214✔
443
      if (find(parts.begin(), parts.end(), var) != parts.end()) {
58,214!
444
        d_unknownParams[var] = std::move(val);
×
445
        SLOG(g_log << Logger::Warning << "Ignoring unknown setting '" << var << "' as requested" << endl,
×
446
             d_log->info(Logr::Warning, "Ignoring unknown setting as requested", "name", Logging::Loggable(var)));
×
447
        return;
×
448
      }
×
449

450
      if (!lax) {
58,214!
451
        throw ArgException("Trying to set unknown setting '" + var + "'");
×
452
      }
×
453
    }
58,214✔
454
  }
126,846✔
455
}
137,960✔
456

457
const vector<string>& ArgvMap::getCommands()
458
{
1,736✔
459
  return d_cmds;
1,736✔
460
}
1,736✔
461

462
void ArgvMap::parse(int& argc, char** argv, bool lax)
463
{
2,443✔
464
  d_cmds.clear();
2,443✔
465
  d_cleared.clear();
2,443✔
466
  for (int i = 1; i < argc; i++) {
16,301✔
467
    parseOne(argv[i], "", lax); // NOLINT: Posix argument parsing
13,858✔
468
  }
13,858✔
469
}
2,443✔
470

471
void ArgvMap::preParse(int& argc, char** argv, const string& arg)
472
{
1,147✔
473
  for (int i = 1; i < argc; i++) {
6,617✔
474
    string varval = argv[i]; // NOLINT: Posix argument parsing
5,470✔
475
    if (varval.find("--" + arg) == 0) {
5,470!
476
      parseOne(argv[i]); // NOLINT:  Posix argument parsing
×
477
    }
×
478
  }
5,470✔
479
}
1,147✔
480

481
bool ArgvMap::parseFile(const string& fname, const string& arg, bool lax)
482
{
13,012✔
483
  string line;
13,012✔
484
  string pline;
13,012✔
485

486
  std::ifstream configFileStream(fname);
13,012✔
487
  if (!configFileStream) {
13,012✔
488
    return false;
186✔
489
  }
186✔
490

491
  while (getline(configFileStream, pline)) {
136,931✔
492
    boost::trim_right(pline);
124,105✔
493

494
    if (!pline.empty() && pline[pline.size() - 1] == '\\') {
124,105✔
495
      line += pline.substr(0, pline.length() - 1);
3✔
496
      continue;
3✔
497
    }
3✔
498

499
    line += pline;
124,102✔
500

501
    // strip everything after a #
502
    string::size_type pos = line.find('#');
124,102✔
503
    if (pos != string::npos) {
124,102✔
504
      // make sure it's either first char or has whitespace before
505
      // fixes issue #354
506
      if (pos == 0 || (std::isspace(line[pos - 1]) != 0)) {
1,429!
507
        line = line.substr(0, pos);
1,429✔
508
      }
1,429✔
509
    }
1,429✔
510

511
    // strip trailing spaces
512
    boost::trim_right(line);
124,102✔
513

514
    // strip leading spaces
515
    pos = line.find_first_not_of(" \t\r\n");
124,102✔
516
    if (pos != string::npos) {
124,102✔
517
      line = line.substr(pos);
115,398✔
518
    }
115,398✔
519

520
    // gpgsql-basic-query=sdfsdfs dfsdfsdf sdfsdfsfd
521

522
    parseOne(string("--") + line, arg, lax);
124,102✔
523
    line = "";
124,102✔
524
  }
124,102✔
525

526
  return true;
12,826✔
527
}
13,012✔
528

529
bool ArgvMap::preParseFile(const string& fname, const string& arg, const string& theDefault)
530
{
7✔
531
  d_params[arg] = theDefault;
7✔
532

533
  return parseFile(fname, arg, false);
7✔
534
}
7✔
535

536
bool ArgvMap::file(const string& fname, bool lax)
537
{
13,005✔
538
  return file(fname, lax, false);
13,005✔
539
}
13,005✔
540

541
bool ArgvMap::file(const string& fname, bool lax, bool included)
542
{
13,005✔
543
  if (!parmIsset("include-dir")) { // inject include-dir
13,005✔
544
    set("include-dir", "Directory to include configuration files from");
6,603✔
545
  }
6,603✔
546

547
  if (!parseFile(fname, "", lax)) {
13,005✔
548
    SLOG(g_log << Logger::Warning << "Unable to open " << fname << std::endl,
186✔
549
         d_log->error(Logr::Warning, "Unable to open file", "name", Logging::Loggable(fname)));
186✔
550
    return false;
186✔
551
  }
186✔
552

553
  // handle include here (avoid re-include)
554
  if (!included && !d_params["include-dir"].empty()) {
12,819!
555
    std::vector<std::string> extraConfigs;
×
556
    gatherIncludes(d_params["include-dir"], ".conf", extraConfigs);
×
557
    for (const std::string& filename : extraConfigs) {
×
558
      if (!file(filename.c_str(), lax, true)) {
×
559
        SLOG(g_log << Logger::Error << filename << " could not be parsed" << std::endl,
×
560
             d_log->info(Logr::Error, "Unable to parse config file", "name", Logging::Loggable(filename)));
×
561
        throw ArgException(filename + " could not be parsed");
×
562
      }
×
563
    }
×
564
  }
×
565

566
  return true;
12,819✔
567
}
12,819✔
568

569
// NOLINTNEXTLINE(readability-convert-member-functions-to-static): accesses d_log (compiled out in auth, hence clang-tidy message)
570
void ArgvMap::gatherIncludes(const std::string& directory, const std::string& suffix, std::vector<std::string>& extraConfigs)
571
{
31✔
572
  if (directory.empty()) {
31✔
573
    return; // nothing to do
1✔
574
  }
1✔
575

576
  std::vector<std::string> vec;
30✔
577
  auto directoryError = pdns::visit_directory(directory, [this, &directory, &suffix, &vec]([[maybe_unused]] ino_t inodeNumber, const std::string_view& name) {
4,197✔
578
    (void)this;
4,197✔
579
    if (boost::starts_with(name, ".")) {
4,197✔
580
      return true; // skip any dots
60✔
581
    }
60✔
582
    if (boost::ends_with(name, suffix)) {
4,137✔
583
      // build name
584
      string fullName = directory + "/" + std::string(name);
1,409✔
585
      // ensure it's readable file
586
      struct stat statInfo{};
1,409✔
587
      if (stat(fullName.c_str(), &statInfo) != 0 || !S_ISREG(statInfo.st_mode)) {
1,409!
588
        string msg = fullName + " is not a regular file";
×
589
        SLOG(g_log << Logger::Error << msg << std::endl,
×
590
             d_log->info(Logr::Error, "Unable to open non-regular file", "name", Logging::Loggable(fullName)));
×
591
        throw ArgException(std::move(msg));
×
592
      }
×
593
      vec.emplace_back(fullName);
1,409✔
594
    }
1,409✔
595
    return true;
4,137✔
596
  });
4,137✔
597

598
  if (directoryError) {
30!
599
    int err = errno;
×
600
    string msg = directory + " is not accessible: " + stringerror(err);
×
601
    SLOG(g_log << Logger::Error << msg << std::endl,
×
602
         d_log->error(Logr::Error, err, "Directory is not accessible", "name", Logging::Loggable(directory)));
×
603
    throw ArgException(std::move(msg));
×
604
  }
×
605

606
  std::sort(vec.begin(), vec.end(), CIStringComparePOSIX());
30✔
607
  extraConfigs.insert(extraConfigs.end(), vec.begin(), vec.end());
30✔
608
}
30✔
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