SNode.C
Loading...
Searching...
No Matches
Config.cpp
Go to the documentation of this file.
1/*
2 * SNode.C - A Slim Toolkit for Network Communication
3 * Copyright (C) Volker Christian <me@vchrist.at>
4 * 2020, 2021, 2022, 2023, 2024, 2025
5 *
6 * This program is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU Lesser General Public License as published
8 * by the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU Lesser General Public License for more details.
15 *
16 * You should have received a copy of the GNU Lesser General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20/*
21 * MIT License
22 *
23 * Permission is hereby granted, free of charge, to any person obtaining a copy
24 * of this software and associated documentation files (the "Software"), to deal
25 * in the Software without restriction, including without limitation the rights
26 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
27 * copies of the Software, and to permit persons to whom the Software is
28 * furnished to do so, subject to the following conditions:
29 *
30 * The above copyright notice and this permission notice shall be included in
31 * all copies or substantial portions of the Software.
32 *
33 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
34 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
35 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
36 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
37 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
38 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
39 * THE SOFTWARE.
40 */
41
42#include "utils/Config.h"
43
44#include "utils/Daemon.h"
45#include "utils/Exceptions.h"
46#include "utils/Formatter.h"
47
48#ifndef DOXYGEN_SHOULD_SKIP_THIS
49
50#include "log/Logger.h"
51
52#include <cerrno>
53#include <cstdint>
54#include <cstdlib>
55#include <cstring>
56#include <fcntl.h>
57#include <filesystem>
58#include <fstream>
59#include <functional>
60#include <grp.h>
61#include <iostream>
62#include <memory>
63#include <pwd.h>
64#include <sstream>
65#include <string_view>
66#include <sys/types.h>
67#include <unistd.h>
68#include <unordered_set>
69#include <vector>
70
71#ifdef __GNUC__
72#pragma GCC diagnostic push
73#pragma GCC diagnostic ignored "-Wfloat-equal"
74#ifdef __has_warning
75#if __has_warning("-Wweak-vtables")
76#pragma GCC diagnostic ignored "-Wweak-vtables"
77#endif
78#if __has_warning("-Wcovered-switch-default")
79#pragma GCC diagnostic ignored "-Wcovered-switch-default"
80#endif
81#if __has_warning("-Wmissing-noreturn")
82#pragma GCC diagnostic ignored "-Wmissing-noreturn"
83#endif
84#if __has_warning("-Wnrvo")
85#pragma GCC diagnostic ignored "-Wnrvo"
86#endif
87#endif
88#endif
89#include "utils/CLI11.hpp"
90#ifdef __GNUC__
91#pragma GCC diagnostic pop
92#endif
93
94#endif /* DOXYGEN_SHOULD_SKIP_THIS */
95
96#define XSTR(s) STR(s)
97#define STR(s) #s
98
99namespace utils {
100
101 static std::shared_ptr<CLI::App> makeApp() { // NO_LINT
102 const std::shared_ptr<CLI::App> app = std::make_shared<CLI::App>();
103
104 app->configurable(false);
105 app->allow_extras();
106 app->allow_config_extras();
107
108 const std::shared_ptr<CLI::HelpFormatter> helpFormatter = std::make_shared<CLI::HelpFormatter>();
109
110 helpFormatter->label("SUBCOMMAND", "INSTANCE");
111 helpFormatter->label("SUBCOMMANDS", "INSTANCES");
112 helpFormatter->label("PERSISTENT", "");
113 helpFormatter->label("Persistent Options", "Options (persistent)");
114 helpFormatter->label("Nonpersistent Options", "Options (nonpersistent)");
115 helpFormatter->label("Usage", "\nUsage");
116 helpFormatter->label("bool:{true,false}", "{true,false}");
117 helpFormatter->label(":{standard,required,full,default}", "{standard,required,full,default}");
118 helpFormatter->label(":{standard,exact,expanded}", "{standard,exact,expanded}");
119 helpFormatter->column_width(7);
120
121 app->formatter(helpFormatter);
122
123 app->config_formatter(std::make_shared<CLI::ConfigFormatter>());
124 app->get_config_formatter_base()->arrayDelimiter(' ');
125
126 app->option_defaults()->take_last();
127 app->option_defaults()->group(app->get_formatter()->get_label("Nonpersistent Options"));
128
130
131 return app;
132 }
133
134 std::shared_ptr<CLI::App> Config::app = makeApp();
135
136 bool Config::init(int argc, char* argv[]) {
137 bool proceed = true;
138
139 Config::argc = argc;
140 Config::argv = argv;
141
142 applicationName = std::filesystem::path(argv[0]).filename();
143
144 uid_t euid = 0;
145 struct passwd* pw = nullptr;
146 struct group* gr = nullptr;
147
148 if ((pw = getpwuid(getuid())) == nullptr) {
149 proceed = false;
150 } else if ((gr = getgrgid(pw->pw_gid)) == nullptr) {
151 proceed = false;
152 } else if ((euid = geteuid()) == 0) {
153 configDirectory = "/etc/snode.c";
154 logDirectory = "/var/log/snode.c";
155 pidDirectory = "/var/run/snode.c";
156 } else {
157 const char* homedir = nullptr;
158 if ((homedir = std::getenv("XDG_CONFIG_HOME")) == nullptr) {
159 if ((homedir = std::getenv("HOME")) == nullptr) {
160 homedir = pw->pw_dir;
161 }
162 }
163
164 if (homedir != nullptr) {
165 configDirectory = std::string(homedir) + "/.config/snode.c";
166 logDirectory = std::string(homedir) + "/.local/log/snode.c";
167 pidDirectory = std::string(homedir) + "/.local/run/snode.c";
168 } else {
169 proceed = false;
170 }
171 }
172
173 if (proceed && !std::filesystem::exists(configDirectory)) {
174 if (std::filesystem::create_directories(configDirectory)) {
175 std::filesystem::permissions(
177 (std::filesystem::perms::owner_all | std::filesystem::perms::group_read | std::filesystem::perms::group_exec) &
178 ~std::filesystem::perms::others_all);
179 if (geteuid() == 0) {
180 struct group* gr = nullptr;
181 if ((gr = getgrnam(XSTR(GROUP_NAME))) != nullptr) {
182 if (chown(configDirectory.c_str(), euid, gr->gr_gid) < 0) {
183 std::cout << "Warning: Can not set group ownership of '" << configDirectory
184 << "' to 'snodec':" << strerror(errno) << std::endl;
185 }
186 } else {
187 std::cout << "Error: Can not find group 'snodec'. Add it using groupadd or addgroup" << std::endl;
188 std::cout << " and add the current user to this group." << std::endl;
189 std::filesystem::remove(configDirectory);
190 proceed = false;
191 }
192 }
193 } else {
194 std::cout << "Error: Can not create directory '" << configDirectory << "'" << std::endl;
195 proceed = false;
196 }
197 }
198
199 if (proceed && !std::filesystem::exists(logDirectory)) {
200 if (std::filesystem::create_directories(logDirectory)) {
201 std::filesystem::permissions(logDirectory,
202 (std::filesystem::perms::owner_all | std::filesystem::perms::group_all) &
203 ~std::filesystem::perms::others_all);
204 if (geteuid() == 0) {
205 struct group* gr = nullptr;
206 if ((gr = getgrnam(XSTR(GROUP_NAME))) != nullptr) {
207 if (chown(logDirectory.c_str(), euid, gr->gr_gid) < 0) {
208 std::cout << "Warning: Can not set group ownership of '" << logDirectory << "' to 'snodec':" << strerror(errno)
209 << std::endl;
210 }
211 } else {
212 std::cout << "Error: Can not find group 'snodec'. Add it using groupadd or addgroup" << std::endl;
213 std::cout << " and add the current user to this group." << std::endl;
214 std::filesystem::remove(configDirectory);
215 proceed = false;
216 }
217 }
218 } else {
219 std::cout << "Error: Can not create directory '" << logDirectory << "'" << std::endl;
220 proceed = false;
221 }
222 }
223
224 if (proceed && !std::filesystem::exists(pidDirectory)) {
225 if (std::filesystem::create_directories(pidDirectory)) {
226 std::filesystem::permissions(pidDirectory,
227 (std::filesystem::perms::owner_all | std::filesystem::perms::group_all) &
228 ~std::filesystem::perms::others_all);
229 if (geteuid() == 0) {
230 struct group* gr = nullptr;
231 if ((gr = getgrnam(XSTR(GROUP_NAME))) != nullptr) {
232 if (chown(pidDirectory.c_str(), euid, gr->gr_gid) < 0) {
233 std::cout << "Warning: Can not set group ownership of '" << pidDirectory << "' to 'snodec':" << strerror(errno)
234 << std::endl;
235 }
236 } else {
237 std::cout << "Error: Can not find group 'snodec'. Add it using groupadd or addgroup." << std::endl;
238 std::cout << " and add the current user to this group." << std::endl;
239 std::filesystem::remove(configDirectory);
240 proceed = false;
241 }
242 }
243 } else {
244 std::cout << "Error: Can not create directory '" << pidDirectory << "'" << std::endl;
245 proceed = false;
246 }
247 }
248
249 if (proceed) {
250 app->description("Configuration for Application '" + applicationName + "'");
251
252 app->footer("Application '" + applicationName +
253 "' powered by SNode.C\n"
254 "(C) 2020-2025 Volker Christian <me@vchrist.at>\n"
255 "https://github.com/SNodeC/snode.c");
256
257 app->set_config( //
258 "-c,--config-file",
259 configDirectory + "/" + applicationName + ".conf",
260 "Read a config file",
261 false) //
262 ->take_all()
263 ->type_name("configfile")
264 ->check(!CLI::ExistingDirectory);
265
266 app->add_option("-w,--write-config", "Write config file and exit")
267 ->configurable(false)
268 ->default_val(configDirectory + "/" + applicationName + ".conf")
269 ->type_name("configfile")
270 ->check(!CLI::ExistingDirectory)
271 ->expected(0, 1);
272
273 app->add_flag( //
274 "-k,--kill",
275 "Kill running daemon") //
276 ->configurable(false)
277 ->disable_flag_override();
278
279 app->add_option( //
280 "-i,--instance-alias",
281 "Make an instance also known as an alias in configuration files")
282 ->configurable(false)
283 ->type_name("instance=instance_alias [instance=instance_alias [...]]")
284 ->each([](const std::string& item) {
285 const auto it = item.find('=');
286 if (it != std::string::npos) {
287 aliases[item.substr(0, it)] = item.substr(it + 1);
288 } else {
289 throw CLI::ConversionError("Can not convert '" + item + "' to a 'instance=instance_alias' pair");
290 }
291 });
292
294
295 logLevelOpt = app->add_option( //
296 "-l,--log-level",
297 "Log level") //
298 ->default_val(4)
299 ->type_name("level")
300 ->check(CLI::Range(0, 6))
301 ->group(app->get_formatter()->get_label("Persistent Options"));
302
303 verboseLevelOpt = app->add_option( //
304 "-v,--verbose-level",
305 "Verbose level") //
306 ->default_val(2)
307 ->type_name("level")
308 ->check(CLI::Range(0, 10))
309 ->group(app->get_formatter()->get_label("Persistent Options"));
310
311 quietOpt = app->add_flag( //
312 "-q{true},!-u,--quiet{true}",
313 "Quiet mode") //
314 ->default_val("false")
315 ->type_name("bool")
316 ->check(CLI::IsMember({"true", "false"}))
317 ->group(app->get_formatter()->get_label("Persistent Options"));
318
319 logFileOpt = app->add_option( //
320 "--log-file",
321 "Log file path") //
322 ->default_val(logDirectory + "/" + applicationName + ".log")
323 ->type_name("logfile")
324 ->check(!CLI::ExistingDirectory)
325 ->group(app->get_formatter()->get_label("Persistent Options"));
326
327 enforceLogFileOpt = app->add_flag( //
328 "-e{true},!-n,--enforce-log-file{true}",
329 "Enforce writing of logs to file for foreground applications") //
330 ->default_val("false")
331 ->type_name("bool")
332 ->check(CLI::IsMember({"true", "false"}))
333 ->group(app->get_formatter()->get_label("Persistent Options"));
334
335 daemonizeOpt = app->add_flag( //
336 "-d{true},!-f,--daemonize{true}",
337 "Start application as daemon") //
338 ->default_val("false")
339 ->type_name("bool")
340 ->check(CLI::IsMember({"true", "false"}))
341 ->group(app->get_formatter()->get_label("Persistent Options"));
342
343 userNameOpt = app->add_option( //
344 "--user-name",
345 "Run daemon under specific user permissions") //
346 ->default_val(pw->pw_name)
347 ->type_name("username")
348 ->needs(daemonizeOpt)
349 ->group(app->get_formatter()->get_label("Persistent Options"));
350
351 groupNameOpt = app->add_option( //
352 "--group-name",
353 "Run daemon under specific group permissions")
354 ->default_val(gr->gr_name)
355 ->type_name("groupname")
356 ->needs(daemonizeOpt)
357 ->group(app->get_formatter()->get_label("Persistent Options"));
358
359 proceed = parse1(); // for stopDaemon and pre init application options
360
361 app->set_version_flag("--version", "1.0-rc1", "Framework version");
362 addHelp(app.get());
363 }
364
365 return proceed;
366 }
367
369 aliases.clear();
370
371 app->final_callback([]() {
372 if (daemonizeOpt->as<bool>() && helpTriggerApp == nullptr && showConfigTriggerApp == nullptr &&
373 (*app)["--write-config"]->count() == 0 && (*app)["--command-line"]->count() == 0) {
374 std::cout << "Running as daemon (double fork)" << std::endl;
375
377 pidDirectory + "/" + applicationName + ".pid", userNameOpt->as<std::string>(), groupNameOpt->as<std::string>());
378
380
381 const std::string logFile = logFileOpt->as<std::string>();
382 if (!logFile.empty()) {
383 logger::Logger::logToFile(logFile);
384 }
385 } else if ((*app)["--enforce-log-file"]->as<bool>()) {
386 const std::string logFile = logFileOpt->as<std::string>();
387 if (!logFile.empty()) {
388 std::cout << "Writing logs to file " << logFile << std::endl;
389
390 logger::Logger::logToFile(logFile);
391 }
392 }
393 });
394
395 return parse2();
396 }
397
398 bool Config::parse1() {
399 bool proceed = true;
400
401 try {
402 app->parse(argc, argv);
403 } catch (const CLI::ParseError&) {
404 // Do not process ParseError here but on second parse pass
405 }
406
407 if ((*app)["--kill"]->count() > 0) {
408 try {
409 const pid_t daemonPid = utils::Daemon::stopDaemon(pidDirectory + "/" + applicationName + ".pid");
410 std::cout << "Daemon terminated: Pid = " << daemonPid << std::endl;
411 } catch (const DaemonError& e) {
412 std::cout << "DaemonError: " << e.what() << std::endl;
413 } catch (const DaemonFailure& e) {
414 std::cout << "DaemonFailure: " << e.what() << std::endl;
415 }
416
417 proceed = false;
418 } else {
419 app->allow_extras((*app)["--show-config"]->count() != 0);
420
421 if (!quietOpt->as<bool>()) {
424 }
425
427 }
428
429 return proceed;
430 }
431
432 // Escape characters with special meaning in Bash (except whitespace).
433 static std::string bash_backslash_escape_no_whitespace(std::string_view s) {
434 static const std::unordered_set<char> special{
435 '\\', '\'', // quoting/escape
436 '`', '$', // substitution
437 '|', '&', ';', '<', '>', '(', ')', '{', '}', // operators
438 '*', '?', '[', ']', '~', '!', '#', '=' // globbing/history/others
439 };
440
441 std::string out;
442 out.reserve(s.size() * 3);
443
444 for (const char c : s) {
445 if (special.contains(c)) {
446 out.push_back('\\');
447 }
448 out.push_back(c);
449 }
450 return out;
451 }
452
453 static void createCommandLineOptions(std::stringstream& out, CLI::App* app, CLI::CallForCommandline::Mode mode) {
454 CLI::Option* disabledOpt = app->get_option_no_throw("--disabled");
455 const bool disabled = disabledOpt != nullptr ? disabledOpt->as<bool>() : false;
456 if (!disabled || mode == CLI::CallForCommandline::Mode::DEFAULT) {
457 for (const CLI::Option* option : app->get_options()) {
458 if (option->get_configurable()) {
459 std::string value;
460
461 switch (mode) {
463 if (option->count() > 0) {
464 value = option->as<std::string>();
465 } else if (option->get_required()) {
466 value = "<REQUIRED>";
467 }
468 break;
470 if (option->get_required()) {
471 if (option->count() > 0) {
472 value = option->as<std::string>();
473 } else {
474 value = "<REQUIRED>";
475 }
476 }
477 break;
479 if (option->count() > 0) {
480 value = option->as<std::string>();
481 } else if (!option->get_default_str().empty()) {
482 value = option->get_default_str();
483 } else if (!option->get_required()) {
484 value = "\"\"";
485 } else {
486 value = "<REQUIRED>";
487 }
488 break;
490 if (!option->get_default_str().empty()) {
491 value = option->get_default_str();
492 } else if (!option->get_required()) {
493 value = "\"\"";
494 } else {
495 value = "<REQUIRED>";
496 }
497 break;
498 }
499 }
500
501 if (!value.empty()) {
502 if (value.starts_with("[") && value.ends_with("]")) {
503 value = value.substr(1, value.size() - 2);
504 }
505
506 if (value != "<REQUIRED>" && value != "\"\"") {
508 }
509 out << "--" << option->get_single_name() << ((option->get_items_expected_max() == 0) ? "=" : " ") << value << " ";
510 }
511 }
512 }
513 } else if (disabledOpt->get_default_str() == "false") {
514 out << "--disabled=true ";
515 }
516 }
517
518 static std::string createCommandLineOptions(CLI::App* app, CLI::CallForCommandline::Mode mode) {
519 std::stringstream out;
520
522
523 std::string optionString = out.str();
524 if (!optionString.empty() && optionString.back() == ' ') {
525 optionString.pop_back();
526 }
527
528 return optionString;
529 }
530
531 static void createCommandLineTemplate(std::stringstream& out, CLI::App* app, CLI::CallForCommandline::Mode mode);
532
533 static std::string createCommandLineSubcommands(CLI::App* app, CLI::CallForCommandline::Mode mode) {
534 std::stringstream out;
535
536 CLI::Option* disabledOpt = app->get_option_no_throw("--disabled");
537 if (disabledOpt == nullptr || !disabledOpt->as<bool>() || mode == CLI::CallForCommandline::Mode::DEFAULT) {
538 for (CLI::App* subcommand : app->get_subcommands({})) {
539 if (!subcommand->get_name().empty()) {
540 createCommandLineTemplate(out, subcommand, mode);
541 }
542 }
543 }
544
545 return out.str();
546 }
547
548 static void createCommandLineTemplate(std::stringstream& out, CLI::App* app, CLI::CallForCommandline::Mode mode) {
549 std::string outString;
550
551 outString = createCommandLineOptions(app, mode);
552 if (!outString.empty()) {
553 outString += " ";
554 }
555
556 outString += createCommandLineSubcommands(app, mode);
557
558 if (!outString.empty()) {
559 out << app->get_name() << " " << outString;
560 }
561 }
562
563 static std::string createCommandLineTemplate(CLI::App* app, CLI::CallForCommandline::Mode mode) {
564 std::stringstream out;
565
567
568 std::string outString = out.str();
569 while (app->get_parent() != nullptr) {
570 app = app->get_parent();
571 std::string parentOptions = createCommandLineOptions(app, mode);
572 outString =
573 std::string(app->get_name()).append(" ").append(!parentOptions.empty() ? parentOptions.append(" ") : "").append(outString);
574 }
575
576 if (outString.empty()) {
578 }
579
580 return outString;
581 }
582
583 bool Config::parse2() {
584 bool success = false;
585
586 try {
587 try {
588 try {
589 app->parse(argc, argv);
590 success = true;
591 } catch (const CLI::ParseError&) {
592 if (helpTriggerApp == nullptr) {
593 if (showConfigTriggerApp != nullptr) {
594 success = false;
596 }
597 if ((*app)["--write-config"]->count() > 0) {
598 success = false;
599 throw CLI::CallForWriteConfig((*app)["--write-config"]->as<std::string>());
600 }
601 }
602
603 throw;
604 }
605 if (helpTriggerApp == nullptr) {
606 if (showConfigTriggerApp != nullptr) {
607 success = false;
609 }
610 if ((*app)["--write-config"]->count() > 0) {
611 success = false;
612 throw CLI::CallForWriteConfig((*app)["--write-config"]->as<std::string>());
613 }
614 }
615 } catch (const DaemonError& e) {
616 std::cout << "[" << Color::Code::FG_RED << "Error" << Color::Code::FG_DEFAULT << "] Daemonize: " << e.what()
617 << " ... exiting" << std::endl;
618 } catch (const DaemonFailure& e) {
619 std::cout << "[" << Color::Code::FG_RED << "Failure" << Color::Code::FG_DEFAULT << "] Daemonize: " << e.what()
620 << " ... exiting" << std::endl;
621 } catch (const DaemonSignaled& e) {
622 std::cout << "Pid: " << getpid() << ", child pid: " << e.getPid() << ": " << e.what() << std::endl;
623 } catch (const CLI::CallForHelp&) {
624 const std::string helpMode = helpTriggerApp->get_option("--help")->as<std::string>();
625 const CLI::App* helpApp = nullptr;
626 CLI::AppFormatMode mode = CLI::AppFormatMode::Normal;
627 if (helpMode == "exact") {
629 } else if (helpMode == "expanded") {
631 mode = CLI::AppFormatMode::All;
632 }
633 std::cout << app->help(helpApp, "", mode) << std::endl;
634 } catch (const CLI::CallForVersion&) {
635 std::cout << app->version() << std::endl << std::endl;
636 } catch (const CLI::CallForCommandline& e) {
637 std::cout << e.what() << std::endl;
638 std::cout << std::endl
639 << Color::Code::FG_GREEN << "command@line" << Color::Code::FG_DEFAULT << ":" << Color::Code::FG_BLUE << "~/> "
641 << std::endl;
642 } catch (const CLI::CallForShowConfig& e) {
643 try {
644 std::cout << e.getApp()->config_to_str(true, true);
645 } catch (const CLI::ParseError& e1) {
646 std::cout << "[" << Color::Code::FG_RED << "Error" << Color::Code::FG_DEFAULT << "] Showing config file: " << e.getApp()
647 << " " << e1.get_name() << " " << e1.what() << std::endl;
648 throw;
649 }
650 } catch (const CLI::CallForWriteConfig& e) {
651 std::cout << e.what() << std::endl;
652 std::ofstream confFile(e.getConfigFile());
653 if (confFile.is_open()) {
654 try {
655 confFile << app->config_to_str(true, true);
656 confFile.close();
657 } catch (const CLI::ParseError& e1) {
658 confFile.close();
659 std::cout << "Error writing config file: " << e1.get_name() << " " << e1.what() << std::endl;
660 throw;
661 }
662 } else {
663 std::cout << "[" << Color::Code::FG_RED << "Error" << Color::Code::FG_DEFAULT
664 << "] Writing config file: " << std::strerror(errno) << std::endl;
665 }
666 } catch (const CLI::ConversionError& e) {
667 std::cout << "[" << Color::Code::FG_RED << e.get_name() << Color::Code::FG_DEFAULT << "] " << e.what() << std::endl;
668 throw;
669 } catch (const CLI::ArgumentMismatch& e) {
670 std::cout << "[" << Color::Code::FG_RED << e.get_name() << Color::Code::FG_DEFAULT << "] " << e.what() << std::endl;
671 throw;
672 } catch (const CLI::ConfigError& e) {
673 std::cout << "[" << Color::Code::FG_RED << e.get_name() << Color::Code::FG_DEFAULT << "] " << e.what() << std::endl;
674 std::cout << " Adding '-w' on the command line may solve this problem" << std::endl;
675 throw;
676 } catch (const CLI::ParseError& e) {
677 const std::string what = e.what();
678 if (what.find("[Option Group: ") != std::string::npos) { // If CLI11 throws that error it means for us there are
679 // unconfigured anonymous instances
680 std::cout << Color::Code::FG_RED << e.get_name() << Color::Code::FG_DEFAULT
681 << " Anonymous instance(s) not configured in source code " << std::endl;
682 } else {
683 std::cout << "[" << Color::Code::FG_RED << e.get_name() << Color::Code::FG_DEFAULT << "] " << what << std::endl;
684 }
685 throw;
686 }
687 } catch ([[maybe_unused]] const CLI::ParseError& e) {
688 std::cout << std::endl << "Append -h or --help to your command line for more information." << std::endl;
689 } catch (const CLI::Error& e) {
690 std::cout << "[" << Color::Code::FG_RED << e.get_name() << Color::Code::FG_DEFAULT << "] " << e.what() << std::endl;
691
692 std::cout << std::endl << "Append -h or --help to your command line for more information." << std::endl;
693 }
694
695 return success;
696 }
697
699 if ((*app)["--daemonize"]->as<bool>()) {
700 std::ifstream pidFile(pidDirectory + "/" + applicationName + ".pid", std::ifstream::in);
701
702 if (pidFile.good()) {
703 pid_t pid = 0;
704 pidFile >> pid;
705
706 if (getpid() == pid) {
708 }
709 }
710 } else if (fcntl(STDIN_FILENO, F_SETFL, O_NONBLOCK) >= 0) {
711 char buf[1024];
712 while (read(STDIN_FILENO, buf, 1024) > 0) {
713 }
714 }
715 }
716
717 static std::shared_ptr<CLI::HelpFormatter> makeSectionFormatter() {
718 const std::shared_ptr<CLI::HelpFormatter> sectionFormatter = std::make_shared<CLI::HelpFormatter>();
719
720 sectionFormatter->label("SUBCOMMAND", "SECTION");
721 sectionFormatter->label("SUBCOMMANDS", "SECTIONS");
722 sectionFormatter->label("PERSISTENT", "");
723 sectionFormatter->label("Persistent Options", "Options (persistent)");
724 sectionFormatter->label("Nonpersistent Options", "Options (nonpersistent)");
725 sectionFormatter->label("Usage", "\nUsage");
726 sectionFormatter->label("bool:{true,false}", "{true,false}");
727 sectionFormatter->label(":{standard,required,full,default}", "{standard,required,full,default}");
728 sectionFormatter->label(":{standard,exact,expanded}", "{standard,exact,expanded}");
729 sectionFormatter->column_width(7);
730
731 return sectionFormatter;
732 }
733
734 std::shared_ptr<CLI::Formatter> Config::sectionFormatter = makeSectionFormatter();
735
736 CLI::App* Config::addInstance(const std::string& name, const std::string& description, const std::string& group, bool final) {
737 CLI::App* instanceSc = app->add_subcommand(name, description) //
738 ->group(group)
739 ->fallthrough()
740 ->formatter(sectionFormatter)
741 ->configurable(false)
742 ->allow_extras(false)
743 ->disabled(name.empty());
744
745 instanceSc //
746 ->option_defaults()
747 ->configurable(!instanceSc->get_disabled());
748
749 if (!instanceSc->get_disabled()) {
750 if (aliases.contains(name)) {
751 instanceSc //
752 ->alias(aliases[name]);
753 }
754 }
755
757
758 if (!final) {
759 utils::Config::addHelp(instanceSc);
760 } else {
762 }
763
764 return instanceSc;
765 }
766
767 CLI::App* Config::getInstance(const std::string& name) {
768 return app->get_subcommand(name);
769 }
770
771 CLI::App* Config::addStandardFlags(CLI::App* app) {
772 app //
773 ->add_flag_function(
774 "-s,--show-config",
775 [app](std::size_t) {
777 },
778 "Show current configuration and exit") //
779 ->configurable(false)
780 ->disable_flag_override();
781
782 app //
783 ->add_flag(
784 "--command-line{standard}",
785 [app]([[maybe_unused]] std::int64_t count) {
786 const std::string& result = app->get_option("--command-line")->as<std::string>();
787 if (result == "standard") {
789 app,
790 "Below is a command line viewing all non-default and required options:\n"
791 "* Options show their configured value\n"
792 "* Required but not yet configured options show <REQUIRED> as value\n"
793 "* Options marked as <REQUIRED> need to be configured for a successful bootstrap",
795 }
796 if (result == "active") {
798 app,
799 "Below is a command line viewing the active set of options with their default or configured values:\n"
800 "* Options show either their configured or default value\n"
801 "* Required but not yet configured options show <REQUIRED> as value\n"
802 "* Options marked as <REQUIRED> need to be configured for a successful bootstrap",
804 }
805 if (result == "complete") {
807 app,
808 "Below is a command line viewing the complete set of options with their default values\n"
809 "* Options show their default value\n"
810 "* Required but not yet configured options show <REQUIRED> as value\n"
811 "* Options marked as <REQUIRED> need to be configured for a successful bootstrap",
813 }
814 if (result == "required") {
816 app,
817 "Below is a command line viewing required options only:\n"
818 "* Options show either their configured or default value\n"
819 "* Required but not yet configured options show <REQUIRED> as value\n"
820 "* Options marked as <REQUIRED> need to be configured for a successful bootstrap",
822 }
823 },
824 "Print command-line\n"
825 "* standard (default): Show all non-default and required options\n"
826 "* active: Show all active options\n"
827 "* complete: Show the complete option set with default values\n"
828 "* required: Show only required options")
829 ->configurable(false)
830 ->check(CLI::IsMember({"standard", "active", "complete", "required"}));
831
832 return app;
833 }
834
835 CLI::App* Config::addHelp(CLI::App* app) {
836 app->set_help_flag(
837 "-h{exact},--help{exact}",
838 [app](std::size_t) {
839 helpTriggerApp = app;
840 },
841 "Print help message and exit\n"
842 "* standard: display help for the last command processed\n"
843 "* exact: display help for the command directly preceding --help\n"
844 "* expanded: print help including all descendant command options")
845 ->group(app->get_formatter()->get_label("Nonpersistent Options"))
846 ->check(CLI::IsMember({"standard", "exact", "expanded"}));
847
848 return app;
849 }
850
851 CLI::App* Config::addSimpleHelp(CLI::App* app) {
852 app->set_help_flag(
853 "--help{exact},-h{exact}",
854 [app](std::size_t) {
855 helpTriggerApp = app;
856 },
857 "Print help message and exit\n"
858 "* standard: display help for the last command processed\n"
859 "* exact: display help for the command directly preceding --help\n")
860 ->group(app->get_formatter()->get_label("Nonpersistent Options"))
861 ->check(CLI::IsMember({"standard", "exact"}));
862
863 return app;
864 }
865
866 void Config::required(CLI::App* instance, bool required) {
867 if (required) {
868 app->needs(instance);
869
870 for (const auto& sub : instance->get_subcommands([](const CLI::App* sc) -> bool {
871 return sc->get_required();
872 })) {
873 instance->needs(sub);
874 }
875 } else {
876 app->remove_needs(instance);
877
878 for (const auto& sub : instance->get_subcommands([](const CLI::App* sc) -> bool {
879 return sc->get_required();
880 })) {
881 instance->remove_needs(sub);
882 }
883 }
884
885 instance->required(required);
886 instance->ignore_case(required);
887 }
888
889 void Config::disabled(CLI::App* instance, bool disabled) {
890 if (disabled) {
891 if (instance->get_ignore_case()) {
892 app->remove_needs(instance);
893 }
894
895 for (const auto& sub : instance->get_subcommands({})) {
896 if (sub->get_ignore_case()) {
897 instance->remove_needs(sub);
898 sub->required(false); // ### must be stored in ConfigInstance
899 }
900 }
901 } else {
902 if (instance->get_ignore_case()) {
903 app->needs(instance);
904 }
905
906 for (const auto& sub : instance->get_subcommands({})) {
907 if (sub->get_ignore_case()) { // ### must be recalled from ConfigInstance
908 instance->needs(sub);
909 sub->required();
910 }
911 }
912 }
913
914 instance->required(disabled ? false : instance->get_ignore_case());
915 }
916
917 bool Config::removeInstance(CLI::App* instance) {
918 Config::required(instance, false);
919
920 return app->remove_subcommand(instance);
921 }
922
923 CLI::Option* Config::addStringOption(const std::string& name, const std::string& description, const std::string& typeName) {
924 applicationOptions[name] = app //
925 ->add_option(name, description)
926 ->type_name(typeName)
927 ->configurable()
928 ->required()
929 ->group("Application Options");
930
931 app->needs(applicationOptions[name]);
932
933 return applicationOptions[name];
934 }
935
936 CLI::Option*
937 Config::addStringOption(const std::string& name, const std::string& description, const std::string& typeName, bool configurable) {
938 addStringOption(name, description, typeName);
939 return applicationOptions[name] //
940 ->configurable(configurable);
941 }
942
943 CLI::Option* Config::addStringOption(const std::string& name,
944 const std::string& description,
945 const std::string& typeName,
946 const std::string& defaultValue) {
947 addStringOption(name, description, typeName);
948
949 applicationOptions[name] //
950 ->required(false)
951 ->default_str(defaultValue);
952
953 app->remove_needs(applicationOptions[name]);
954
955 return applicationOptions[name];
956 }
957
958 CLI::Option* Config::addStringOption(const std::string& name,
959 const std::string& description,
960 const std::string& typeName,
961 const std::string& defaultValue,
962 bool configurable) {
963 addStringOption(name, description, typeName, defaultValue);
964 return applicationOptions[name] //
965 ->configurable(configurable);
966 }
967
968 CLI::Option* Config::addStringOption(const std::string& name,
969 const std::string& description,
970 const std::string& typeName,
971 const char* defaultValue) {
972 return addStringOption(name, description, typeName, std::string(defaultValue));
973 }
974
976 const std::string& name, const std::string& description, const std::string& typeName, const char* defaultValue, bool configurable) {
977 return addStringOption(name, description, typeName, std::string(defaultValue), configurable);
978 }
979
980 std::string Config::getStringOptionValue(const std::string& name) {
981 if (app->get_option(name) == nullptr) {
982 throw CLI::OptionNotFound(name);
983 }
984
985 return (*app)[name]->as<std::string>();
986 }
987
988 void Config::addFlag(const std::string& name,
989 bool& variable,
990 const std::string& description,
991 bool required,
992 bool configurable,
993 const std::string& groupName) {
994 app->add_flag(name, variable, description) //
995 ->required(required) //
996 ->configurable(configurable) //
997 ->group(groupName);
998 }
999
1001 return applicationName;
1002 }
1003
1005 return logLevelOpt->as<int>();
1006 }
1007
1009 return verboseLevelOpt->as<int>();
1010 }
1011
1012 int Config::argc = 0;
1013 char** Config::argv = nullptr;
1014
1015 std::string Config::applicationName;
1016
1017 std::string Config::configDirectory;
1018 std::string Config::logDirectory;
1019 std::string Config::pidDirectory;
1020
1021 CLI::Option* Config::daemonizeOpt = nullptr;
1022 CLI::Option* Config::logFileOpt = nullptr;
1023 CLI::Option* Config::userNameOpt = nullptr;
1024 CLI::Option* Config::groupNameOpt = nullptr;
1025 CLI::Option* Config::enforceLogFileOpt = nullptr;
1026 CLI::Option* Config::logLevelOpt = nullptr;
1027 CLI::Option* Config::verboseLevelOpt = nullptr;
1028 CLI::Option* Config::quietOpt = nullptr;
1029
1030 CLI::App* Config::helpTriggerApp = nullptr;
1031 CLI::App* Config::showConfigTriggerApp = nullptr;
1032
1033 std::map<std::string, std::string> Config::aliases;
1034 std::map<std::string, CLI::Option*> Config::applicationOptions;
1035
1036} // namespace utils
#define XSTR(s)
CLI::App * getApp() const
CallForCommandline(CLI::App *app, const std::string &description, Mode mode)
CLI::App * getApp() const
CallForShowConfig(CLI::App *app)
std::string getConfigFile() const
CallForWriteConfig(const std::string &configFile)
static void setVerboseLevel(int level)
Definition Logger.cpp:121
static void setLogLevel(int level)
Definition Logger.cpp:84
static void init()
Definition Logger.cpp:54
static void setQuiet(bool quiet=true)
Definition Logger.cpp:136
static CLI::Option * enforceLogFileOpt
Definition Config.h:147
static CLI::Option * verboseLevelOpt
Definition Config.h:149
static bool parse1()
Definition Config.cpp:398
static CLI::Option * addStringOption(const std::string &name, const std::string &description, const std::string &typeName, const std::string &defaultValue)
Definition Config.cpp:943
static CLI::Option * addStringOption(const std::string &name, const std::string &description, const std::string &typeName, const std::string &defaultValue, bool configurable)
Definition Config.cpp:958
static CLI::Option * groupNameOpt
Definition Config.h:146
static bool removeInstance(CLI::App *instance)
Definition Config.cpp:917
static std::string applicationName
Definition Config.h:137
static std::map< std::string, std::string > aliases
Definition Config.h:157
static CLI::App * addHelp(CLI::App *app)
Definition Config.cpp:835
static void addFlag(const std::string &name, bool &variable, const std::string &description, bool required, bool configurable=true, const std::string &groupName="Application Options")
Definition Config.cpp:988
static CLI::App * helpTriggerApp
Definition Config.h:153
static CLI::Option * addStringOption(const std::string &name, const std::string &description, const std::string &typeName, bool configurable)
Definition Config.cpp:937
static CLI::Option * addStringOption(const std::string &name, const std::string &description, const std::string &typeName, const char *defaultValue)
Definition Config.cpp:968
static std::shared_ptr< CLI::Formatter > sectionFormatter
Definition Config.h:129
static CLI::Option * logFileOpt
Definition Config.h:144
static std::string configDirectory
Definition Config.h:139
static std::shared_ptr< CLI::App > app
Definition Config.h:123
static std::string pidDirectory
Definition Config.h:141
static CLI::App * addSimpleHelp(CLI::App *app)
Definition Config.cpp:851
static CLI::App * getInstance(const std::string &name)
Definition Config.cpp:767
static void terminate()
Definition Config.cpp:698
static std::string getApplicationName()
Definition Config.cpp:1000
static CLI::App * showConfigTriggerApp
Definition Config.h:154
static void disabled(CLI::App *instance, bool disabled=true)
Definition Config.cpp:889
static CLI::App * addInstance(const std::string &name, const std::string &description, const std::string &group, bool final=false)
Definition Config.cpp:736
static int argc
Definition Config.h:132
static CLI::App * addStandardFlags(CLI::App *app)
Definition Config.cpp:771
static CLI::Option * userNameOpt
Definition Config.h:145
static std::map< std::string, CLI::Option * > applicationOptions
Definition Config.h:158
static CLI::Option * addStringOption(const std::string &name, const std::string &description, const std::string &typeName, const char *defaultValue, bool configurable)
Definition Config.cpp:975
static std::string logDirectory
Definition Config.h:140
static CLI::Option * addStringOption(const std::string &name, const std::string &description, const std::string &typeName)
Definition Config.cpp:923
static bool init(int argc, char *argv[])
Definition Config.cpp:136
static void required(CLI::App *instance, bool required=true)
Definition Config.cpp:866
static CLI::Option * quietOpt
Definition Config.h:150
static std::string getStringOptionValue(const std::string &name)
Definition Config.cpp:980
static CLI::Option * daemonizeOpt
Definition Config.h:143
static bool parse2()
Definition Config.cpp:583
static char ** argv
Definition Config.h:133
static bool bootstrap()
Definition Config.cpp:368
static int getLogLevel()
Definition Config.cpp:1004
static CLI::Option * logLevelOpt
Definition Config.h:148
static int getVerboseLevel()
Definition Config.cpp:1008
pid_t getPid() const
Definition Daemon.cpp:214
static void startDaemon(const std::string &pidFileName, const std::string &userName, const std::string &groupName)
Definition Daemon.cpp:75
static pid_t stopDaemon(const std::string &pidFileName)
Definition Daemon.cpp:158
static void erasePidFile(const std::string &pidFileName)
Definition Daemon.cpp:200
#define STR(a)
Definition clients.h:48
Definition Logger.h:66
Code
Definition Logger.h:68
@ FG_DEFAULT
Definition Logger.h:69
Definition Config.h:59
static std::string createCommandLineTemplate(CLI::App *app, CLI::CallForCommandline::Mode mode)
Definition Config.cpp:563
static void createCommandLineTemplate(std::stringstream &out, CLI::App *app, CLI::CallForCommandline::Mode mode)
Definition Config.cpp:548
static void createCommandLineOptions(std::stringstream &out, CLI::App *app, CLI::CallForCommandline::Mode mode)
Definition Config.cpp:453
static std::shared_ptr< CLI::App > makeApp()
Definition Config.cpp:101
static std::string bash_backslash_escape_no_whitespace(std::string_view s)
Definition Config.cpp:433
static std::string createCommandLineOptions(CLI::App *app, CLI::CallForCommandline::Mode mode)
Definition Config.cpp:518
static std::shared_ptr< CLI::HelpFormatter > makeSectionFormatter()
Definition Config.cpp:717
static std::string createCommandLineSubcommands(CLI::App *app, CLI::CallForCommandline::Mode mode)
Definition Config.cpp:533