]> git.ipfire.org Git - thirdparty/pdns.git/blame - pdns/arguments.cc
Merge pull request #13387 from omoerbeek/rec-b-root-servers
[thirdparty/pdns.git] / pdns / arguments.cc
CommitLineData
12c86877 1/*
12471842
PL
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 */
870a0fe4
AT
22#ifdef HAVE_CONFIG_H
23#include "config.h"
24#endif
12c86877 25#include "arguments.hh"
5b2cb3be 26#include <boost/algorithm/string.hpp>
2dfec518 27#include <boost/algorithm/string/compare.hpp>
4f6ef75f 28#include <boost/algorithm/string/predicate.hpp>
fa8fd4d2 29
61b26744 30#include "namespaces.hh"
4f6ef75f 31#include "logger.hh"
8a16d778
AT
32#include <sys/types.h>
33#include <dirent.h>
34#include <sys/stat.h>
35#include <unistd.h>
e64bde34 36#include <climits>
12c86877 37
bace7695 38ArgvMap::param_t::const_iterator ArgvMap::begin()
12c86877 39{
9d6e970b 40 return d_params.begin();
12c86877
BH
41}
42
bace7695 43ArgvMap::param_t::const_iterator ArgvMap::end()
12c86877 44{
9d6e970b 45 return d_params.end();
12c86877
BH
46}
47
725bf803 48string& ArgvMap::set(const string& var)
12c86877 49{
9d6e970b 50 return d_params[var];
12c86877
BH
51}
52
725bf803 53void ArgvMap::setDefault(const string& var, const string& value)
8864bdf6 54{
bace7695 55 if (defaultmap.count(var) == 0) {
8864bdf6 56 defaultmap.insert(pair<string, string>(var, value));
bace7695 57 }
8864bdf6
KM
58}
59
60void ArgvMap::setDefaults()
61{
bace7695
OM
62 for (const auto& param : d_params) {
63 if (defaultmap.count(param.first) == 0) {
64 defaultmap.insert(param);
65 }
66 }
8864bdf6
KM
67}
68
725bf803 69bool ArgvMap::mustDo(const string& var)
bd11bd1d 70{
725bf803 71 return ((*this)[var] != "no") && ((*this)[var] != "off");
12c86877
BH
72}
73
725bf803 74vector<string> ArgvMap::list()
12c86877
BH
75{
76 vector<string> ret;
bace7695
OM
77 ret.reserve(d_params.size());
78 for (const auto& param : d_params) {
79 ret.push_back(param.first);
80 }
12c86877
BH
81 return ret;
82}
83
725bf803 84string& ArgvMap::set(const string& var, const string& help)
12c86877 85{
725bf803
OM
86 helpmap[var] = help;
87 d_typeMap[var] = "Parameter";
12c86877
BH
88 return set(var);
89}
90
725bf803 91void ArgvMap::setCmd(const string& var, const string& help)
12c86877 92{
725bf803
OM
93 helpmap[var] = help;
94 d_typeMap[var] = "Command";
95 set(var) = "no";
12c86877
BH
96}
97
725bf803 98string& ArgvMap::setSwitch(const string& var, const string& help)
12c86877 99{
725bf803
OM
100 helpmap[var] = help;
101 d_typeMap[var] = "Switch";
12c86877
BH
102 return set(var);
103}
104
725bf803 105bool ArgvMap::contains(const string& var, const string& val)
12c86877 106{
9d6e970b 107 const auto& param = d_params.find(var);
725bf803 108 if (param == d_params.end() || param->second.empty()) {
102eb646
BH
109 return false;
110 }
562588a3 111 vector<string> parts;
3ea9dcbc 112
9d6e970b 113 stringtok(parts, param->second, ", \t");
bace7695 114 return std::any_of(parts.begin(), parts.end(), [&](const std::string& str) { return str == val; });
12c86877
BH
115}
116
12c86877
BH
117string ArgvMap::helpstring(string prefix)
118{
bace7695 119 if (prefix == "no") {
725bf803 120 prefix = "";
bace7695 121 }
3ea9dcbc 122
12c86877 123 string help;
93a471a3 124
bace7695
OM
125 for (const auto& helpitem : helpmap) {
126 if (!prefix.empty() && helpitem.first.find(prefix) != 0) { // only print items with prefix
725bf803 127 continue;
bace7695 128 }
3ea9dcbc 129
725bf803 130 help += " --";
bace7695 131 help += helpitem.first;
12c86877 132
bace7695 133 string type = d_typeMap[helpitem.first];
12c86877 134
bace7695 135 if (type == "Parameter") {
725bf803 136 help += "=...";
bace7695 137 }
725bf803 138 else if (type == "Switch") {
bace7695
OM
139 help += " | --" + helpitem.first + "=yes";
140 help += " | --" + helpitem.first + "=no";
12c86877 141 }
725bf803
OM
142
143 help += "\n\t";
bace7695 144 help += helpitem.second;
725bf803
OM
145 help += "\n";
146 }
12c86877
BH
147 return help;
148}
149
bace7695 150string ArgvMap::formatOne(bool running, bool full, const string& var, const string& help, const string& theDefault, const string& current)
9d6e970b
CH
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";
725bf803
OM
161 }
162 else {
9d6e970b
CH
163 if (theDefault == current) {
164 return "";
165 }
166 }
167
725bf803 168 if (!running || theDefault == current) {
9d6e970b
CH
169 out += "# ";
170 }
171
172 if (running) {
173 out += var + "=" + current + "\n";
174 if (full) {
175 out += "\n";
176 }
725bf803
OM
177 }
178 else {
9d6e970b
CH
179 out += var + "=" + theDefault + "\n\n";
180 }
181
182 return out;
183}
184
185// If running and full, only changed settings are returned.
8864bdf6 186string ArgvMap::configstring(bool running, bool full)
12c86877
BH
187{
188 string help;
6bef3d91 189
bace7695 190 if (running) {
725bf803 191 help = "# Autogenerated configuration file based on running instance (" + nowTime() + ")\n\n";
bace7695
OM
192 }
193 else {
725bf803 194 help = "# Autogenerated configuration file template\n\n";
bace7695 195 }
8864bdf6 196
9d6e970b
CH
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
cdb00dca
OM
200 for (const auto& helpitem : helpmap) {
201 if (d_typeMap[helpitem.first] == "Command") {
9d6e970b 202 continue;
bace7695 203 }
cdb00dca 204 if (helpitem.first == "ignore-unknown-settings") {
c7efa8ff 205 continue;
bace7695 206 }
c7efa8ff 207
cdb00dca
OM
208 if (defaultmap.count(helpitem.first) == 0) {
209 throw ArgException(string("Default for setting '") + helpitem.first + "' not set");
8864bdf6
KM
210 }
211
cdb00dca 212 help += formatOne(running, full, helpitem.first, helpitem.second, defaultmap[helpitem.first], d_params[helpitem.first]);
9d6e970b 213 }
8864bdf6 214
9d6e970b 215 if (running) {
bace7695
OM
216 for (const auto& unknown : d_unknownParams) {
217 help += formatOne(running, full, unknown.first, "unknown setting", "", unknown.second);
12c86877 218 }
c7efa8ff 219 }
9d6e970b 220
12c86877
BH
221 return help;
222}
223
725bf803 224const string& ArgvMap::operator[](const string& arg)
12c86877 225{
bace7695 226 if (!parmIsset(arg)) {
725bf803 227 throw ArgException(string("Undefined but needed argument: '") + arg + "'");
bace7695 228 }
12c86877 229
9d6e970b 230 return d_params[arg];
12c86877
BH
231}
232
725bf803 233mode_t ArgvMap::asMode(const string& arg)
cc2978dc 234{
bace7695 235 if (!parmIsset(arg)) {
725bf803 236 throw ArgException(string("Undefined but needed argument: '") + arg + "'");
bace7695 237 }
cc2978dc 238
bace7695
OM
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) {
cc2978dc 244 throw ArgException("'" + arg + string("' contains invalid octal mode"));
bace7695 245 }
725bf803 246 return mode;
cc2978dc
BH
247}
248
725bf803 249gid_t ArgvMap::asGid(const string& arg)
cc2978dc 250{
bace7695 251 if (!parmIsset(arg)) {
725bf803 252 throw ArgException(string("Undefined but needed argument: '") + arg + "'");
bace7695 253 }
cc2978dc 254
bace7695
OM
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));
cc2978dc
BH
258 if (gid == 0 && cptr_ret == cptr_orig) {
259 // try to resolve
bace7695
OM
260
261 struct group* group = getgrnam(d_params[arg].c_str()); // NOLINT: called before going multi-threaded
262 if (group == nullptr) {
725bf803 263 throw ArgException("'" + arg + string("' contains invalid group"));
bace7695 264 }
cc2978dc 265 gid = group->gr_gid;
725bf803
OM
266 }
267 return gid;
cc2978dc
BH
268}
269
725bf803 270uid_t ArgvMap::asUid(const string& arg)
cc2978dc 271{
bace7695 272 if (!parmIsset(arg)) {
725bf803 273 throw ArgException(string("Undefined but needed argument: '") + arg + "'");
bace7695
OM
274 }
275
276 const auto* cptr_orig = d_params[arg].c_str();
277 char* cptr_ret = nullptr;
cc2978dc 278
bace7695 279 auto uid = static_cast<uid_t>(strtol(cptr_orig, &cptr_ret, 0));
cc2978dc
BH
280 if (uid == 0 && cptr_ret == cptr_orig) {
281 // try to resolve
bace7695
OM
282 struct passwd* pwent = getpwnam(d_params[arg].c_str()); // NOLINT: called before going multi-threaded
283 if (pwent == nullptr) {
725bf803 284 throw ArgException("'" + arg + string("' contains invalid group"));
bace7695 285 }
cc2978dc 286 uid = pwent->pw_uid;
725bf803
OM
287 }
288 return uid;
cc2978dc 289}
cc2978dc 290
725bf803 291int ArgvMap::asNum(const string& arg, int def)
12c86877 292{
bace7695 293 if (!parmIsset(arg)) {
725bf803 294 throw ArgException(string("Undefined but needed argument: '") + arg + "'");
bace7695 295 }
12c86877 296
ff99a74b 297 // use default for empty values
bace7695 298 if (d_params[arg].empty()) {
725bf803 299 return def;
bace7695 300 }
cc2978dc 301
bace7695
OM
302 const auto* cptr_orig = d_params[arg].c_str();
303 char* cptr_ret = nullptr;
304 auto retval = static_cast<int>(strtol(cptr_orig, &cptr_ret, 0));
305 if (retval == 0 && cptr_ret == cptr_orig) {
725bf803 306 throw ArgException("'" + arg + "' value '" + string(cptr_orig) + string("' is not a valid number"));
bace7695 307 }
cc2978dc
BH
308
309 return retval;
310}
311
725bf803 312bool ArgvMap::isEmpty(const string& arg)
cc2978dc 313{
bace7695 314 if (!parmIsset(arg)) {
cc2978dc 315 return true;
bace7695 316 }
725bf803 317 return d_params[arg].empty();
12c86877
BH
318}
319
725bf803 320double ArgvMap::asDouble(const string& arg)
12c86877 321{
bace7695 322 if (!parmIsset(arg)) {
725bf803 323 throw ArgException(string("Undefined but needed argument: '") + arg + "'");
bace7695 324 }
12c86877 325
bace7695 326 if (d_params[arg].empty()) {
725bf803 327 return 0.0;
bace7695 328 }
cc2978dc 329
bace7695
OM
330 const auto* cptr_orig = d_params[arg].c_str();
331 char* cptr_ret = nullptr;
332 auto retval = strtod(cptr_orig, &cptr_ret);
3ea9dcbc 333
bace7695 334 if (retval == 0 && cptr_ret == cptr_orig) {
725bf803 335 throw ArgException("'" + arg + string("' is not valid double"));
bace7695 336 }
cc2978dc
BH
337
338 return retval;
12c86877
BH
339}
340
341ArgvMap::ArgvMap()
342{
725bf803 343 set("ignore-unknown-settings", "Configuration settings to ignore if they are unknown") = "";
12c86877
BH
344}
345
725bf803 346bool ArgvMap::parmIsset(const string& var)
12c86877 347{
9d6e970b 348 return d_params.find(var) != d_params.end();
12c86877
BH
349}
350
3d324e00 351// ATM Shared between Recursor and Auth, is that a good idea?
725bf803
OM
352static 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"},
d0fbd333 362 {"domain-metadata-cache-ttl", "zone-metadata-cache-ttl"},
3d324e00
O
363};
364
9883d3f9
OM
365// NOLINTNEXTLINE(readability-convert-member-functions-to-static): accesses d_log (compiled out in auth, hence clang-tidy message)
366void ArgvMap::warnIfDeprecated(const string& var) const
3d324e00
O
367{
368 const auto msg = deprecateList.find(var);
369 if (msg != deprecateList.end()) {
ebaffe64 370 SLOG(g_log << Logger::Warning << "'" << var << "' is deprecated and will be removed in a future release, use '" << msg->second << "' instead" << endl,
7e32a0b9 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)));
3d324e00
O
372 }
373}
374
9883d3f9
OM
375string ArgvMap::isDeprecated(const string& var)
376{
377 const auto msg = deprecateList.find(var);
378 return msg != deprecateList.end() ? msg->second : "";
379}
380
725bf803 381void ArgvMap::parseOne(const string& arg, const string& parseOnly, bool lax)
12c86877 382{
bace7695
OM
383 string var;
384 string val;
385 string::size_type pos = 0;
5a177409 386 bool incremental = false;
4d752b05 387
725bf803 388 if (arg.find("--") == 0 && (pos = arg.find("+=")) != string::npos) // this is a --port+=25 case
4d752b05 389 {
725bf803
OM
390 var = arg.substr(2, pos - 2);
391 val = arg.substr(pos + 2);
4d752b05
KM
392 incremental = true;
393 }
725bf803 394 else if (arg.find("--") == 0 && (pos = arg.find('=')) != string::npos) // this is a --port=25 case
4d752b05 395 {
725bf803
OM
396 var = arg.substr(2, pos - 2);
397 val = arg.substr(pos + 1);
4d752b05 398 }
725bf803 399 else if (arg.find("--") == 0 && (arg.find('=') == string::npos)) // this is a --daemon case
4d752b05 400 {
725bf803
OM
401 var = arg.substr(2);
402 val = "";
4d752b05 403 }
725bf803
OM
404 else if (arg[0] == '-' && arg.length() > 1) {
405 var = arg.substr(1);
406 val = "";
12c86877 407 }
bace7695 408 else { // command
4d752b05 409 d_cmds.push_back(arg);
bace7695 410 }
3ea9dcbc 411
c63f0951 412 boost::trim(var);
12c86877 413
bace7695 414 if (!var.empty() && (parseOnly.empty() || var == parseOnly)) {
ebaffe64
OM
415 if (!lax) {
416 warnIfDeprecated(var);
417 }
725bf803 418 pos = val.find_first_not_of(" \t"); // strip leading whitespace
bace7695 419 if (pos != 0 && pos != string::npos) {
725bf803 420 val = val.substr(pos);
bace7695 421 }
725bf803
OM
422 if (parmIsset(var)) {
423 if (incremental) {
424 if (d_params[var].empty()) {
bace7695 425 if (d_cleared.count(var) == 0) {
725bf803 426 throw ArgException("Incremental setting '" + var + "' without a parent");
bace7695 427 }
9d6e970b 428 d_params[var] = val;
4d752b05 429 }
bace7695 430 else {
9d6e970b 431 d_params[var] += ", " + val;
bace7695 432 }
4d752b05 433 }
725bf803 434 else {
9d6e970b 435 d_params[var] = val;
4d752b05 436 d_cleared.insert(var);
5a177409 437 }
3702b428 438 }
725bf803 439 else {
9d6e970b 440 // unknown setting encountered. see if its on the ignore list before throwing.
6e01be56 441 vector<string> parts;
9d6e970b 442 stringtok(parts, d_params["ignore-unknown-settings"], " ,\t\n\r");
6e01be56 443 if (find(parts.begin(), parts.end(), var) != parts.end()) {
9d6e970b 444 d_unknownParams[var] = val;
725bf803 445 SLOG(g_log << Logger::Warning << "Ignoring unknown setting '" << var << "' as requested" << endl,
ebaffe64 446 d_log->info(Logr::Warning, "Ignoring unknown setting as requested", "name", Logging::Loggable(var)));
9d6e970b
CH
447 return;
448 }
449
450 if (!lax) {
725bf803 451 throw ArgException("Trying to set unknown setting '" + var + "'");
9d6e970b
CH
452 }
453 }
12c86877
BH
454 }
455}
456
725bf803 457const vector<string>& ArgvMap::getCommands()
12c86877
BH
458{
459 return d_cmds;
460}
461
725bf803 462void ArgvMap::parse(int& argc, char** argv, bool lax)
12c86877 463{
116cda91 464 d_cmds.clear();
58a57699 465 d_cleared.clear();
bace7695
OM
466 for (int i = 1; i < argc; i++) {
467 parseOne(argv[i], "", lax); // NOLINT: Posix argument parsing
12c86877
BH
468 }
469}
470
725bf803 471void ArgvMap::preParse(int& argc, char** argv, const string& arg)
12c86877 472{
bace7695
OM
473 for (int i = 1; i < argc; i++) {
474 string varval = argv[i]; // NOLINT: Posix argument parsing
475 if (varval.find("--" + arg) == 0) {
476 parseOne(argv[i]); // NOLINT: Posix argument parsing
477 }
12c86877
BH
478 }
479}
480
9883d3f9 481bool ArgvMap::parseFile(const string& fname, const string& arg, bool lax)
5a249fb1 482{
d3002a04 483 string line;
07b7e0b5 484 string pline;
07b7e0b5 485
564db8c5 486 std::ifstream configFileStream(fname);
e64bde34 487 if (!configFileStream) {
d3002a04 488 return false;
5a249fb1 489 }
07b7e0b5 490
e64bde34 491 while (getline(configFileStream, pline)) {
dc593046 492 boost::trim_right(pline);
3ea9dcbc 493
5a249fb1
FM
494 if (!pline.empty() && pline[pline.size() - 1] == '\\') {
495 line += pline.substr(0, pline.length() - 1);
d3002a04
AT
496 continue;
497 }
e64bde34
FM
498
499 line += pline;
d3002a04
AT
500
501 // strip everything after a #
e64bde34
FM
502 string::size_type pos = line.find('#');
503 if (pos != string::npos) {
d3002a04
AT
504 // make sure it's either first char or has whitespace before
505 // fixes issue #354
e64bde34 506 if (pos == 0 || (std::isspace(line[pos - 1]) != 0)) {
5a249fb1
FM
507 line = line.substr(0, pos);
508 }
d3002a04
AT
509 }
510
511 // strip trailing spaces
dc593046 512 boost::trim_right(line);
07b7e0b5 513
d3002a04 514 // strip leading spaces
e64bde34
FM
515 pos = line.find_first_not_of(" \t\r\n");
516 if (pos != string::npos) {
5a249fb1
FM
517 line = line.substr(pos);
518 }
07b7e0b5 519
d3002a04
AT
520 // gpgsql-basic-query=sdfsdfs dfsdfsdf sdfsdfsfd
521
5a249fb1
FM
522 parseOne(string("--") + line, arg, lax);
523 line = "";
d3002a04 524 }
07b7e0b5 525
07b7e0b5
AT
526 return true;
527}
528
9883d3f9 529bool ArgvMap::preParseFile(const string& fname, const string& arg, const string& theDefault)
12c86877 530{
9d6e970b 531 d_params[arg] = theDefault;
b56ac6fd 532
d3002a04 533 return parseFile(fname, arg, false);
12c86877
BH
534}
535
9883d3f9 536bool ArgvMap::file(const string& fname, bool lax)
4f6ef75f 537{
fe6861c2 538 return file(fname, lax, false);
4f6ef75f
AT
539}
540
9883d3f9 541bool ArgvMap::file(const string& fname, bool lax, bool included)
12c86877 542{
5a249fb1
FM
543 if (!parmIsset("include-dir")) { // inject include-dir
544 set("include-dir", "Directory to include configuration files from");
545 }
4f6ef75f 546
5a249fb1 547 if (!parseFile(fname, "", lax)) {
ebaffe64 548 SLOG(g_log << Logger::Warning << "Unable to open " << fname << std::endl,
ed3ec74d 549 d_log->error(Logr::Warning, "Unable to open file", "name", Logging::Loggable(fname)));
d3002a04
AT
550 return false;
551 }
eefd15f9 552
4f6ef75f 553 // handle include here (avoid re-include)
9d6e970b 554 if (!included && !d_params["include-dir"].empty()) {
93d7e233 555 std::vector<std::string> extraConfigs;
9883d3f9 556 gatherIncludes(d_params["include-dir"], ".conf", extraConfigs);
5a249fb1
FM
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");
4f6ef75f 562 }
93d7e233 563 }
93d7e233 564 }
8a16d778 565
93d7e233
AT
566 return true;
567}
8a16d778 568
9883d3f9
OM
569// NOLINTNEXTLINE(readability-convert-member-functions-to-static): accesses d_log (compiled out in auth, hence clang-tidy message)
570void ArgvMap::gatherIncludes(const std::string& directory, const std::string& suffix, std::vector<std::string>& extraConfigs)
725bf803 571{
9883d3f9 572 if (directory.empty()) {
a73da04b 573 return; // nothing to do
bace7695 574 }
a73da04b 575
9883d3f9
OM
576 auto dir = std::unique_ptr<DIR, decltype(&closedir)>(opendir(directory.c_str()), closedir);
577 if (dir == nullptr) {
a73da04b 578 int err = errno;
9883d3f9 579 string msg = directory + " is not accessible: " + stringerror(err);
ebaffe64 580 SLOG(g_log << Logger::Error << msg << std::endl,
9883d3f9 581 d_log->error(Logr::Error, err, "Directory is not accessible", "name", Logging::Loggable(directory)));
a73da04b
OM
582 throw ArgException(msg);
583 }
93d7e233 584
9883d3f9 585 std::vector<std::string> vec;
bace7695 586 struct dirent* ent = nullptr;
9883d3f9 587 while ((ent = readdir(dir.get())) != nullptr) { // NOLINT(concurrency-mt-unsafe): see Linux man page
bace7695 588 if (ent->d_name[0] == '.') {
a73da04b 589 continue; // skip any dots
bace7695 590 }
9883d3f9 591 if (boost::ends_with(ent->d_name, suffix)) {
a73da04b 592 // build name
9883d3f9 593 string name = directory + "/" + ent->d_name; // NOLINT: Posix API
a73da04b 594 // ensure it's readable file
bace7695
OM
595 struct stat statInfo
596 {
597 };
598 if (stat(name.c_str(), &statInfo) != 0 || !S_ISREG(statInfo.st_mode)) {
a73da04b 599 string msg = name + " is not a regular file";
ebaffe64 600 SLOG(g_log << Logger::Error << msg << std::endl,
ed3ec74d 601 d_log->info(Logr::Error, "Unable to open non-regular file", "name", Logging::Loggable(name)));
a73da04b 602 throw ArgException(msg);
93d7e233 603 }
9883d3f9 604 vec.emplace_back(name);
93d7e233 605 }
a73da04b 606 }
9883d3f9
OM
607 std::sort(vec.begin(), vec.end(), CIStringComparePOSIX());
608 extraConfigs.insert(extraConfigs.end(), vec.begin(), vec.end());
12c86877 609}