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 <sys/types.h>
66#include <unistd.h>
67#include <vector>
68
69#ifdef __GNUC__
70#pragma GCC diagnostic push
71#pragma GCC diagnostic ignored "-Wfloat-equal"
72#ifdef __has_warning
73#if __has_warning("-Wweak-vtables")
74#pragma GCC diagnostic ignored "-Wweak-vtables"
75#endif
76#if __has_warning("-Wcovered-switch-default")
77#pragma GCC diagnostic ignored "-Wcovered-switch-default"
78#endif
79#endif
80#endif
81#include "utils/CLI11.hpp"
82#ifdef __GNUC__
83#pragma GCC diagnostic pop
84#endif
85
86#endif /* DOXYGEN_SHOULD_SKIP_THIS */
87
88#define XSTR(s) STR(s)
89#define STR(s) #s
90
91namespace utils {
92
93 static const std::shared_ptr<CLI::App> makeApp() { // NO_LINT
94 const std::shared_ptr<CLI::App> app = std::make_shared<CLI::App>();
95
96 app->configurable(false);
97 app->allow_extras();
98 app->allow_config_extras();
99
100 const std::shared_ptr<CLI::HelpFormatter> helpFormatter = std::make_shared<CLI::HelpFormatter>();
101
102 helpFormatter->label("SUBCOMMAND", "INSTANCE");
103 helpFormatter->label("SUBCOMMANDS", "INSTANCES");
104 helpFormatter->label("PERSISTENT", "");
105 helpFormatter->label("Persistent Options", "Options (persistent)");
106 helpFormatter->label("Nonpersistent Options", "Options (nonpersistent)");
107 helpFormatter->label("Usage", "\nUsage");
108 helpFormatter->label("bool:{true,false}", "{true,false}");
109 helpFormatter->label(":{standard,required,full,default}", "{standard,required,full,default}");
110 helpFormatter->label(":{standard,expanded}", "{standard,expanded}");
111 helpFormatter->column_width(7);
112
113 app->formatter(helpFormatter);
114
115 app->config_formatter(std::make_shared<CLI::ConfigFormatter>());
116 app->get_config_formatter_base()->arrayDelimiter(' ');
117
118 app->option_defaults()->take_last();
119 app->option_defaults()->group(app->get_formatter()->get_label("Nonpersistent Options"));
120
121 logger::Logger::init();
122
123 return app;
124 }
125
126 std::shared_ptr<CLI::App> Config::app = makeApp();
127
128 bool Config::init(int argc, char* argv[]) {
129 bool proceed = true;
130
131 Config::argc = argc;
132 Config::argv = argv;
133
134 applicationName = std::filesystem::path(argv[0]).filename();
135
136 uid_t euid = 0;
137 struct passwd* pw = nullptr;
138 struct group* gr = nullptr;
139
140 if ((pw = getpwuid(getuid())) == nullptr) {
141 proceed = false;
142 } else if ((gr = getgrgid(pw->pw_gid)) == nullptr) {
143 proceed = false;
144 } else if ((euid = geteuid()) == 0) {
145 configDirectory = "/etc/snode.c";
146 logDirectory = "/var/log/snode.c";
147 pidDirectory = "/var/run/snode.c";
148 } else {
149 const char* homedir = nullptr;
150 if ((homedir = std::getenv("XDG_CONFIG_HOME")) == nullptr) {
151 if ((homedir = std::getenv("HOME")) == nullptr) {
152 homedir = pw->pw_dir;
153 }
154 }
155
156 if (homedir != nullptr) {
157 configDirectory = std::string(homedir) + "/.config/snode.c";
158 logDirectory = std::string(homedir) + "/.local/log/snode.c";
159 pidDirectory = std::string(homedir) + "/.local/run/snode.c";
160 } else {
161 proceed = false;
162 }
163 }
164
165 if (proceed && !std::filesystem::exists(configDirectory)) {
166 if (std::filesystem::create_directories(configDirectory)) {
167 std::filesystem::permissions(
168 configDirectory,
169 (std::filesystem::perms::owner_all | std::filesystem::perms::group_read | std::filesystem::perms::group_exec) &
170 ~std::filesystem::perms::others_all);
171 if (geteuid() == 0) {
172 struct group* gr = nullptr;
173 if ((gr = getgrnam(XSTR(GROUP_NAME))) != nullptr) {
174 if (chown(configDirectory.c_str(), euid, gr->gr_gid) < 0) {
175 std::cout << "Warning: Can not set group ownership of '" << configDirectory
176 << "' to 'snodec':" << strerror(errno) << std::endl;
177 }
178 } else {
179 std::cout << "Error: Can not find group 'snodec'. Add it using groupadd or addgroup" << std::endl;
180 std::cout << " and add the current user to this group." << std::endl;
181 std::filesystem::remove(configDirectory);
182 proceed = false;
183 }
184 }
185 } else {
186 std::cout << "Error: Can not create directory '" << configDirectory << "'" << std::endl;
187 proceed = false;
188 }
189 }
190
191 if (proceed && !std::filesystem::exists(logDirectory)) {
192 if (std::filesystem::create_directories(logDirectory)) {
193 std::filesystem::permissions(logDirectory,
194 (std::filesystem::perms::owner_all | std::filesystem::perms::group_all) &
195 ~std::filesystem::perms::others_all);
196 if (geteuid() == 0) {
197 struct group* gr = nullptr;
198 if ((gr = getgrnam(XSTR(GROUP_NAME))) != nullptr) {
199 if (chown(logDirectory.c_str(), euid, gr->gr_gid) < 0) {
200 std::cout << "Warning: Can not set group ownership of '" << logDirectory << "' to 'snodec':" << strerror(errno)
201 << std::endl;
202 }
203 } else {
204 std::cout << "Error: Can not find group 'snodec'. Add it using groupadd or addgroup" << std::endl;
205 std::cout << " and add the current user to this group." << std::endl;
206 std::filesystem::remove(configDirectory);
207 proceed = false;
208 }
209 }
210 } else {
211 std::cout << "Error: Can not create directory '" << logDirectory << "'" << std::endl;
212 proceed = false;
213 }
214 }
215
216 if (proceed && !std::filesystem::exists(pidDirectory)) {
217 if (std::filesystem::create_directories(pidDirectory)) {
218 std::filesystem::permissions(pidDirectory,
219 (std::filesystem::perms::owner_all | std::filesystem::perms::group_all) &
220 ~std::filesystem::perms::others_all);
221 if (geteuid() == 0) {
222 struct group* gr = nullptr;
223 if ((gr = getgrnam(XSTR(GROUP_NAME))) != nullptr) {
224 if (chown(pidDirectory.c_str(), euid, gr->gr_gid) < 0) {
225 std::cout << "Warning: Can not set group ownership of '" << pidDirectory << "' to 'snodec':" << strerror(errno)
226 << std::endl;
227 }
228 } else {
229 std::cout << "Error: Can not find group 'snodec'. Add it using groupadd or addgroup." << std::endl;
230 std::cout << " and add the current user to this group." << std::endl;
231 std::filesystem::remove(configDirectory);
232 proceed = false;
233 }
234 }
235 } else {
236 std::cout << "Error: Can not create directory '" << pidDirectory << "'" << std::endl;
237 proceed = false;
238 }
239 }
240
241 if (proceed) {
242 app->description("Configuration for Application '" + applicationName + "'");
243
244 app->footer("Application '" + applicationName +
245 "' powered by SNode.C\n"
246 "(C) 2020-2025 Volker Christian <me@vchrist.at>\n"
247 "https://github.com/SNodeC/snode.c");
248
249 app->set_config( //
250 "-c,--config-file",
251 configDirectory + "/" + applicationName + ".conf",
252 "Read a config file",
253 false) //
254 ->take_all()
255 ->type_name("configfile")
256 ->check(!CLI::ExistingDirectory);
257
258 app->add_option_function<std::string>(
259 "-w,--write-config",
260 []([[maybe_unused]] const std::string& configFile) {
261 throw CLI::CallForWriteConfig(configFile);
262 },
263 "Write config file and exit")
264 ->configurable(false)
265 ->default_val(configDirectory + "/" + applicationName + ".conf")
266 ->type_name("[configfile]")
267 ->check(!CLI::ExistingDirectory)
268 ->expected(0, 1);
269
270 app->add_flag( //
271 "-k,--kill",
272 "Kill running daemon") //
273 ->configurable(false)
274 ->disable_flag_override();
275
276 app->add_option( //
277 "-i,--instance-alias",
278 "Make an instance also known as an alias in configuration files")
279 ->configurable(false)
280 ->type_name("instance=instance_alias [instance=instance_alias [...]]")
281 ->each([](const std::string& item) {
282 const auto it = item.find('=');
283 if (it != std::string::npos) {
284 aliases[item.substr(0, it)] = item.substr(it + 1);
285 } else {
286 throw CLI::ConversionError("Can not convert '" + item + "' to a 'instance=instance_alias' pair");
287 }
288 });
289
290 addStandardFlags(app.get());
291
292 logLevelOpt = app->add_option( //
293 "-l,--log-level",
294 "Log level") //
295 ->default_val(4)
296 ->type_name("level")
297 ->check(CLI::Range(0, 6))
298 ->group(app->get_formatter()->get_label("Persistent Options"));
299
300 verboseLevelOpt = app->add_option( //
301 "-v,--verbose-level",
302 "Verbose level") //
303 ->default_val(2)
304 ->type_name("level")
305 ->check(CLI::Range(0, 10))
306 ->group(app->get_formatter()->get_label("Persistent Options"));
307
308 quietOpt = app->add_flag( //
309 "-q{true},!-u,--quiet{true}",
310 "Quiet mode") //
311 ->default_val("false")
312 ->type_name("bool")
313 ->check(CLI::IsMember({"true", "false"}))
314 ->group(app->get_formatter()->get_label("Persistent Options"));
315
316 logFileOpt = app->add_option( //
317 "--log-file",
318 "Log file path") //
319 ->default_val(logDirectory + "/" + applicationName + ".log")
320 ->type_name("logfile")
321 ->check(!CLI::ExistingDirectory)
322 ->group(app->get_formatter()->get_label("Persistent Options"));
323
324 enforceLogFileOpt = app->add_flag( //
325 "-e{true},!-n,--enforce-log-file{true}",
326 "Enforce writing of logs to file for foreground applications") //
327 ->default_val("false")
328 ->type_name("bool")
329 ->check(CLI::IsMember({"true", "false"}))
330 ->group(app->get_formatter()->get_label("Persistent Options"));
331
332 daemonizeOpt = app->add_flag( //
333 "-d{true},!-f,--daemonize{true}",
334 "Start application as daemon") //
335 ->default_val("false")
336 ->type_name("bool")
337 ->check(CLI::IsMember({"true", "false"}))
338 ->group(app->get_formatter()->get_label("Persistent Options"));
339
340 userNameOpt = app->add_option( //
341 "--user-name",
342 "Run daemon under specific user permissions") //
343 ->default_val(pw->pw_name)
344 ->type_name("username")
345 ->needs(daemonizeOpt)
346 ->group(app->get_formatter()->get_label("Persistent Options"));
347
348 groupNameOpt = app->add_option( //
349 "--group-name",
350 "Run daemon under specific group permissions")
351 ->default_val(gr->gr_name)
352 ->type_name("groupname")
353 ->needs(daemonizeOpt)
354 ->group(app->get_formatter()->get_label("Persistent Options"));
355
356 proceed = parse1(); // for stopDaemon and pre init application options
357
358 app->set_version_flag("--version", "1.0-rc1", "Framework version");
359 addHelp(app.get());
360 }
361
362 return proceed;
363 }
364
365 bool Config::bootstrap() {
366 aliases.clear();
367
368 app->final_callback([]() {
369 if (daemonizeOpt->as<bool>() && (*app)["--show-config"]->count() == 0 && (*app)["--write-config"]->count() == 0 &&
370 (*app)["--command-line"]->count() == 0) {
371 std::cout << "Running as daemon (double fork)" << std::endl;
372
374 pidDirectory + "/" + applicationName + ".pid", userNameOpt->as<std::string>(), groupNameOpt->as<std::string>());
375
376 logger::Logger::setQuiet();
377
378 const std::string logFile = logFileOpt->as<std::string>();
379 if (!logFile.empty()) {
380 logger::Logger::logToFile(logFile);
381 }
382 } else if ((*app)["--enforce-log-file"]->as<bool>()) {
383 const std::string logFile = logFileOpt->as<std::string>();
384 if (!logFile.empty()) {
385 std::cout << "Writing logs to file " << logFile << std::endl;
386
387 logger::Logger::logToFile(logFile);
388 }
389 }
390 });
391
392 return parse2();
393 }
394
395 bool Config::parse1() {
396 bool proceed = true;
397
398 try {
399 app->parse(argc, argv);
400 } catch (const CLI::ParseError&) {
401 // Do not process ParseError here but on second parse pass
402 }
403
404 if ((*app)["--kill"]->count() > 0) {
405 try {
406 const pid_t daemonPid = utils::Daemon::stopDaemon(pidDirectory + "/" + applicationName + ".pid");
407 std::cout << "Daemon terminated: Pid = " << daemonPid << std::endl;
408 } catch (const DaemonError& e) {
409 std::cout << "DaemonError: " << e.what() << std::endl;
410 } catch (const DaemonFailure& e) {
411 std::cout << "DaemonFailure: " << e.what() << std::endl;
412 }
413
414 proceed = false;
415 } else {
416 if ((*app)["--show-config"]->count() == 0 && (*app)["--write-config"]->count() == 0 && (*app)["--command-line"]->count() == 0) {
417 app->allow_extras(false);
418 }
419
420 if (!quietOpt->as<bool>()) {
421 logger::Logger::setLogLevel(logLevelOpt->as<int>());
422 logger::Logger::setVerboseLevel(verboseLevelOpt->as<int>());
423 }
424
425 logger::Logger::setQuiet(quietOpt->as<bool>());
426 }
427
428 return proceed;
429 }
430
431 static void createCommandLineOptions(std::stringstream& out, CLI::App* app, CLI::CallForCommandline::Mode mode) {
432 CLI::Option* disabledOpt = app->get_option_no_throw("--disabled");
433 const bool disabled = disabledOpt != nullptr ? disabledOpt->as<bool>() : false;
434 if (!disabled || mode == CLI::CallForCommandline::Mode::DEFAULT) {
435 for (const CLI::Option* option : app->get_options()) {
436 if (option->get_configurable()) {
437 std::string value;
438
439 switch (mode) {
441 if (option->count() > 0) {
442 value = option->as<std::string>();
443 } else if (option->get_required()) {
444 value = "<REQUIRED>";
445 }
446 break;
448 if (option->get_required()) {
449 if (option->count() > 0) {
450 value = option->as<std::string>();
451 } else {
452 value = "<REQUIRED>";
453 }
454 }
455 break;
457 if (option->count() > 0) {
458 value = option->as<std::string>();
459 } else if (!option->get_default_str().empty()) {
460 value = option->get_default_str();
461 } else if (!option->get_required()) {
462 value = "\"\"";
463 } else {
464 value = "<REQUIRED>";
465 }
466 break;
468 if (!option->get_default_str().empty()) {
469 value = option->get_default_str();
470 } else if (!option->get_required()) {
471 value = "\"\"";
472 } else {
473 value = "<REQUIRED>";
474 }
475 break;
476 }
477 }
478
479 if (!value.empty()) {
480 if (value.starts_with("[") && value.ends_with("]")) {
481 value = value.substr(1, value.size() - 2);
482 }
483
484 out << "--" << option->get_single_name() << ((option->get_items_expected_max() == 0) ? "=" : " ") << value << " ";
485 }
486 }
487 }
488 } else if (disabledOpt->get_default_str() == "false") {
489 out << "--disabled=true ";
490 }
491 }
492
493 static std::string createCommandLineOptions(CLI::App* app, CLI::CallForCommandline::Mode mode) {
494 std::stringstream out;
495
497
498 std::string optionString = out.str();
499 if (optionString.back() == ' ') {
500 optionString.pop_back();
501 }
502
503 return optionString;
504 }
505
506 static void createCommandLineTemplate(std::stringstream& out, CLI::App* app, CLI::CallForCommandline::Mode mode);
507
508 static std::string createCommandLineSubcommands(CLI::App* app, CLI::CallForCommandline::Mode mode) {
509 std::stringstream out;
510
511 CLI::Option* disabledOpt = app->get_option_no_throw("--disabled");
512 if (disabledOpt == nullptr || !disabledOpt->as<bool>() || mode == CLI::CallForCommandline::Mode::DEFAULT) {
513 for (CLI::App* subcommand : app->get_subcommands({})) {
514 if (!subcommand->get_name().empty()) {
515 createCommandLineTemplate(out, subcommand, mode);
516 }
517 }
518 }
519
520 return out.str();
521 }
522
523 static void createCommandLineTemplate(std::stringstream& out, CLI::App* app, CLI::CallForCommandline::Mode mode) {
524 std::string outString;
525
526 outString = createCommandLineOptions(app, mode);
527 if (!outString.empty()) {
528 outString += " ";
529 }
530
531 outString += createCommandLineSubcommands(app, mode);
532
533 if (!outString.empty()) {
534 out << app->get_name() << " " << outString;
535 }
536 }
537
538 static std::string createCommandLineTemplate(CLI::App* app, CLI::CallForCommandline::Mode mode) {
539 std::stringstream out;
540
542
543 std::string outString = out.str();
544 while (app->get_parent() != nullptr) {
545 app = app->get_parent();
546 std::string parentOptions = createCommandLineOptions(app, mode);
547 outString =
548 std::string(app->get_name()).append(" ").append(!parentOptions.empty() ? parentOptions.append(" ") : "").append(outString);
549 }
550
551 if (outString.empty()) {
552 outString = Config::getApplicationName();
553 }
554
555 return outString;
556 }
557
558 bool Config::parse2() {
559 bool success = false;
560
561 try {
562 try {
563 app->parse(argc, argv);
564 success = true;
565 } catch (const DaemonError& e) {
566 std::cout << "Daemon error: " << e.what() << " ... exiting" << std::endl;
567 } catch (const DaemonFailure& e) {
568 std::cout << "Daemon failure: " << e.what() << " ... exiting" << std::endl;
569 } catch (const DaemonSignaled& e) {
570 std::cout << "Pid: " << getpid() << ", child pid: " << e.getPid() << ": " << e.what() << std::endl;
571 } catch (const CLI::CallForHelp&) {
572 std::cout << app->help() << std::endl;
573 } catch (const CLI::CallForAllHelp&) {
574 std::cout << app->help("", CLI::AppFormatMode::All) << std::endl;
575 } catch (const CLI::CallForVersion&) {
576 std::cout << app->version() << std::endl << std::endl;
577 } catch (const CLI::CallForCommandline& e) {
578 std::cout << e.what() << std::endl;
579 std::cout << std::endl
580 << Color::Code::FG_GREEN << "command@line" << Color::Code::FG_DEFAULT << ":" << Color::Code::FG_BLUE << "~/> "
581 << Color::Code::FG_DEFAULT << createCommandLineTemplate(e.getApp(), e.getMode()) << std::endl
582 << std::endl;
583 } catch (const CLI::CallForShowConfig& e) {
584 try {
585 std::cout << e.getApp()->config_to_str(true, true);
586 } catch (const CLI::ParseError& e1) {
587 std::cout << "Error showing config file: " << e.getApp() << " " << e1.get_name() << " " << e1.what() << std::endl;
588 throw;
589 }
590 } catch (const CLI::CallForWriteConfig& e) {
591 std::cout << e.what() << std::endl;
592 std::ofstream confFile(e.getConfigFile());
593 if (confFile.is_open()) {
594 try {
595 confFile << app->config_to_str(true, true);
596 confFile.close();
597 } catch (const CLI::ParseError& e1) {
598 confFile.close();
599 std::cout << "Error writing config file: " << e1.get_name() << " " << e1.what() << std::endl;
600 throw;
601 }
602 } else {
603 std::cout << "Error writing config file: " << std::strerror(errno) << std::endl;
604 }
605 } catch (const CLI::ConversionError& e) {
606 std::cout << "[" << Color::Code::FG_RED << e.get_name() << Color::Code::FG_DEFAULT << "] " << e.what() << std::endl;
607 throw;
608 } catch (const CLI::ArgumentMismatch& e) {
609 std::cout << "[" << Color::Code::FG_RED << e.get_name() << Color::Code::FG_DEFAULT << "] " << e.what() << std::endl;
610 throw;
611 } catch (const CLI::ConfigError& e) {
612 std::cout << "[" << Color::Code::FG_RED << e.get_name() << Color::Code::FG_DEFAULT << "] " << e.what() << std::endl;
613 std::cout << " Adding '-w' on the command line may solve this problem" << std::endl;
614 throw;
615 } catch (const CLI::ParseError& e) {
616 const std::string what = e.what();
617 if (what.find("[Option Group: ") != std::string::npos) { // If CLI11 throws that error it means for us there are
618 // unconfigured anonymous instances
619 std::cout << Color::Code::FG_RED << "[BootstrapError]" << Color::Code::FG_DEFAULT
620 << " Anonymous instance(s) not configured in source code " << std::endl;
621 } else {
622 std::cout << "[" << Color::Code::FG_RED << e.get_name() << Color::Code::FG_DEFAULT << "] " << what << std::endl;
623 }
624 throw;
625 }
626 } catch ([[maybe_unused]] const CLI::ParseError& e) {
627 std::cout << std::endl << "Append -h or --help to your command line for more information." << std::endl;
628 } catch (const CLI::Error& e) {
629 std::cout << "[" << Color::Code::FG_RED << e.get_name() << Color::Code::FG_DEFAULT << "] " << e.what() << std::endl;
630 std::cout << "Append -h or --help to your command line for more information." << std::endl;
631 }
632
633 if (!success) {
634 logger::Logger::setQuiet();
635 }
636
637 return success;
638 }
639
640 void Config::terminate() {
641 if ((*app)["--daemonize"]->as<bool>()) {
642 std::ifstream pidFile(pidDirectory + "/" + applicationName + ".pid", std::ifstream::in);
643
644 if (pidFile.good()) {
645 pid_t pid = 0;
646 pidFile >> pid;
647
648 if (getpid() == pid) {
649 Daemon::erasePidFile(pidDirectory + "/" + applicationName + ".pid");
650 }
651 }
652 } else if (fcntl(STDIN_FILENO, F_SETFL, O_NONBLOCK) >= 0) {
653 char buf[1024];
654 while (read(STDIN_FILENO, buf, 1024) > 0) {
655 }
656 }
657 }
658
659 static const std::shared_ptr<CLI::HelpFormatter> makeSectionFormatter() {
660 const std::shared_ptr<CLI::HelpFormatter> sectionFormatter = std::make_shared<CLI::HelpFormatter>();
661
662 sectionFormatter->label("SUBCOMMAND", "SECTION");
663 sectionFormatter->label("SUBCOMMANDS", "SECTIONS");
664 sectionFormatter->label("PERSISTENT", "");
665 sectionFormatter->label("Persistent Options", "Options (persistent)");
666 sectionFormatter->label("Nonpersistent Options", "Options (nonpersistent)");
667 sectionFormatter->label("Usage", "\nUsage");
668 sectionFormatter->label("bool:{true,false}", "{true,false}");
669 sectionFormatter->label(":{standard,required,full,default}", "{standard,required,full,default}");
670 sectionFormatter->label(":{standard,expanded}", "{standard,expanded}");
671 sectionFormatter->column_width(7);
672
673 return sectionFormatter;
674 }
675
676 std::shared_ptr<CLI::Formatter> Config::sectionFormatter = makeSectionFormatter();
677
678 CLI::App* Config::addInstance(const std::string& name, const std::string& description, const std::string& group) {
679 CLI::App* instance = app->add_subcommand(name, description) //
680 ->group(group)
681 ->fallthrough()
682 ->formatter(sectionFormatter)
683 ->configurable(false)
684 ->allow_extras(false)
685 ->disabled(name.empty());
686
687 instance //
688 ->option_defaults()
689 ->configurable(!instance->get_disabled());
690
691 if (!instance->get_disabled()) {
692 if (aliases.contains(name)) {
693 instance //
694 ->alias(aliases[name]);
695 }
696 }
697
698 return instance;
699 }
700
701 CLI::App* Config::addStandardFlags(CLI::App* app) {
702 app //
703 ->add_flag_callback(
704 "-s,--show-config",
705 [app]() {
706 throw CLI::CallForShowConfig(app);
707 },
708 "Show current configuration and exit") //
709 ->configurable(false)
710 ->disable_flag_override();
711
712 app //
713 ->add_flag(
714 "--command-line{standard}",
715 [app]([[maybe_unused]] std::int64_t count) {
716 const std::string& result = app->get_option("--command-line")->as<std::string>();
717 if (result == "standard") {
719 app,
720 "Below is a command line viewing all non-default and required options:\n"
721 "* Options show their configured value\n"
722 "* Required but not yet configured options show <REQUIRED> as value\n"
723 "* Options marked as <REQUIRED> need to be configured for a successful bootstrap",
725 }
726 if (result == "required") {
728 app,
729 "Below is a command line viewing required options only:\n"
730 "* Options show either their configured or default value\n"
731 "* Required but not yet configured options show <REQUIRED> as value\n"
732 "* Options marked as <REQUIRED> need to be configured for a successful bootstrap",
734 }
735 if (result == "full") {
737 app,
738 "Below is a command line viewing the full set of options with their default or configured values:\n"
739 "* Options show either their configured or default value\n"
740 "* Required but not yet configured options show <REQUIRED> as value\n"
741 "* Options marked as <REQUIRED> need to be configured for a successful bootstrap",
743 }
744 if (result == "default") {
746 app,
747 "Below is a command line viewing the full set of options with their default values\n"
748 "* Options show their default value\n"
749 "* Required but not yet configured options show <REQUIRED> as value\n"
750 "* Options marked as <REQUIRED> need to be configured for a successful bootstrap",
752 }
753 },
754 "Print a command line\n"
755 " standard (default): View all non-default and required options\n"
756 " required: View required options only\n"
757 " full: View the full set of options with their default or configured values\n"
758 " default: View the full set of options with their default values")
759 ->configurable(false)
760 ->check(CLI::IsMember({"standard", "required", "full", "default"}));
761
762 return app;
763 }
764
765 CLI::App* Config::addHelp(CLI::App* app) {
766 app //
767 ->set_help_flag();
768
769 app //
770 ->add_flag(
771 "-h{standard},--help{standard}",
772 [app]([[maybe_unused]] std::int64_t count) {
773 const std::size_t disabledCount =
774 app->get_subcommands([](CLI::App* app) -> bool {
775 return app->get_group() == "Instance" && app->get_option("--disabled")->as<bool>();
776 })
777 .size();
778 const std::size_t enabledCount =
779 app->get_subcommands([](CLI::App* app) -> bool {
780 return app->get_group() == "Instance" && !app->get_option("--disabled")->as<bool>();
781 })
782 .size();
783
784 for (auto* instance : app->get_subcommands({})) {
785 if (instance->get_group() == "Instance") {
786 if (instance->get_option("--disabled")->as<bool>()) {
787 instance->group(std::string("Instance").append((disabledCount > 1) ? "s" : "").append(" (disabled)"));
788 } else {
789 instance->group(std::string("Instance").append((enabledCount > 1) ? "s" : ""));
790 }
791 }
792 }
793
794 const std::string& result = app->get_option("--help")->as<std::string>();
795
796 if (result == "standard") {
797 throw CLI::CallForHelp();
798 }
799 if (result == "expanded") {
800 throw CLI::CallForAllHelp();
801 }
802 },
803 "Print help message")
804 ->configurable(false)
805 ->check(CLI::IsMember({"standard", "expanded"}));
806
807 return app;
808 }
809
810 CLI::App* Config::addSimpleHelp(CLI::App* app) {
811 app //
812 ->set_help_flag();
813
814 app //
815 ->add_flag(
816 "-h,--help",
817 []([[maybe_unused]] std::int64_t count) {
818 throw CLI::CallForHelp();
819 },
820 "Print help message")
821 ->configurable(false)
822 ->disable_flag_override();
823
824 return app;
825 }
826
827 void Config::required(CLI::App* instance, bool reqired) {
828 if (reqired) {
829 app->needs(instance);
830
831 for (const auto& sub : instance->get_subcommands([](const CLI::App* sc) -> bool {
832 return sc->get_required();
833 })) {
834 instance->needs(sub);
835 }
836 } else {
837 app->remove_needs(instance);
838
839 for (const auto& sub : instance->get_subcommands([](const CLI::App* sc) -> bool {
840 return sc->get_required();
841 })) {
842 instance->remove_needs(sub);
843 }
844 }
845
846 instance->required(reqired);
847 }
848
849 void Config::disabled(CLI::App* instance, bool disabled) {
850 if (disabled) {
851 app->remove_needs(instance);
852
853 for (const auto& sub : instance->get_subcommands([](const CLI::App* sc) -> bool {
854 return !sc->get_disabled();
855 })) {
856 sub->disabled();
857
858 if (sub->get_required()) {
859 instance->remove_needs(sub);
860 }
861 }
862 } else {
863 app->needs(instance);
864
865 for (const auto& sub : instance->get_subcommands([](const CLI::App* sc) -> bool {
866 return sc->get_disabled();
867 })) {
868 sub->disabled(false);
869
870 if (sub->get_required()) {
871 instance->needs(sub);
872 }
873 }
874 }
875
876 instance->required(!disabled);
877 }
878
879 bool Config::removeInstance(CLI::App* instance) {
880 Config::required(instance, false);
881
882 return app->remove_subcommand(instance);
883 }
884
885 CLI::Option* Config::addStringOption(const std::string& name, const std::string& description, const std::string& typeName) {
886 applicationOptions[name] = app //
887 ->add_option(name, description)
888 ->type_name(typeName)
889 ->configurable()
890 ->required()
891 ->group("Application Options");
892
893 app->needs(applicationOptions[name]);
894
895 return applicationOptions[name];
896 }
897
898 CLI::Option*
899 Config::addStringOption(const std::string& name, const std::string& description, const std::string& typeName, bool configurable) {
900 addStringOption(name, description, typeName);
901 return applicationOptions[name] //
902 ->configurable(configurable);
903 }
904
905 CLI::Option* Config::addStringOption(const std::string& name,
906 const std::string& description,
907 const std::string& typeName,
908 const std::string& defaultValue) {
909 addStringOption(name, description, typeName);
910
911 applicationOptions[name] //
912 ->required(false)
913 ->default_str(defaultValue);
914
915 app->remove_needs(applicationOptions[name]);
916
917 return applicationOptions[name];
918 }
919
920 CLI::Option* Config::addStringOption(const std::string& name,
921 const std::string& description,
922 const std::string& typeName,
923 const std::string& defaultValue,
924 bool configurable) {
925 addStringOption(name, description, typeName, defaultValue);
926 return applicationOptions[name] //
927 ->configurable(configurable);
928 }
929
930 CLI::Option* Config::addStringOption(const std::string& name,
931 const std::string& description,
932 const std::string& typeName,
933 const char* defaultValue) {
934 return addStringOption(name, description, typeName, std::string(defaultValue));
935 }
936
937 CLI::Option* Config::addStringOption(
938 const std::string& name, const std::string& description, const std::string& typeName, const char* defaultValue, bool configurable) {
939 return addStringOption(name, description, typeName, std::string(defaultValue), configurable);
940 }
941
942 std::string Config::getStringOptionValue(const std::string& name) {
943 if (app->get_option(name) == nullptr) {
944 throw CLI::OptionNotFound(name);
945 }
946
947 return (*app)[name]->as<std::string>();
948 }
949
950 void Config::addFlag(const std::string& name,
951 bool& variable,
952 const std::string& description,
953 bool required,
954 bool configurable,
955 const std::string& groupName) {
956 app->add_flag(name, variable, description) //
957 ->required(required) //
958 ->configurable(configurable) //
959 ->group(groupName);
960 }
961
962 std::string Config::getApplicationName() {
963 return applicationName;
964 }
965
966 int Config::getLogLevel() {
967 return logLevelOpt->as<int>();
968 }
969
970 int Config::getVerboseLevel() {
971 return verboseLevelOpt->as<int>();
972 }
973
974 int Config::argc = 0;
975 char** Config::argv = nullptr;
976
977 std::string Config::applicationName;
978
979 std::string Config::configDirectory;
980 std::string Config::logDirectory;
981 std::string Config::pidDirectory;
982
983 CLI::Option* Config::daemonizeOpt = nullptr;
984 CLI::Option* Config::logFileOpt = nullptr;
985 CLI::Option* Config::userNameOpt = nullptr;
986 CLI::Option* Config::groupNameOpt = nullptr;
987 CLI::Option* Config::enforceLogFileOpt = nullptr;
988 CLI::Option* Config::logLevelOpt = nullptr;
989 CLI::Option* Config::verboseLevelOpt = nullptr;
990 CLI::Option* Config::quietOpt = nullptr;
991
992 std::map<std::string, std::string> Config::aliases;
993 std::map<std::string, CLI::Option*> Config::applicationOptions;
994
995} // 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)
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
static const std::shared_ptr< CLI::App > makeApp()
Definition Config.cpp:93
static std::string createCommandLineTemplate(CLI::App *app, CLI::CallForCommandline::Mode mode)
Definition Config.cpp:538
static const std::shared_ptr< CLI::HelpFormatter > makeSectionFormatter()
Definition Config.cpp:659
static void createCommandLineTemplate(std::stringstream &out, CLI::App *app, CLI::CallForCommandline::Mode mode)
Definition Config.cpp:523
static void createCommandLineOptions(std::stringstream &out, CLI::App *app, CLI::CallForCommandline::Mode mode)
Definition Config.cpp:431
static std::string createCommandLineOptions(CLI::App *app, CLI::CallForCommandline::Mode mode)
Definition Config.cpp:493
static std::string createCommandLineSubcommands(CLI::App *app, CLI::CallForCommandline::Mode mode)
Definition Config.cpp:508