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, 2026
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
46#ifndef DOXYGEN_SHOULD_SKIP_THIS
47
48#include "log/Logger.h"
49
50#include <cerrno>
51#include <cstdlib>
52#include <cstring>
53#include <fcntl.h>
54#include <filesystem>
55#include <fstream>
56#include <functional>
57#include <grp.h>
58#include <iostream>
59#include <map>
60#include <memory>
61#include <pwd.h>
62#include <sstream>
63#include <string_view>
64#include <sys/types.h>
65#include <unistd.h>
66#include <unordered_set>
67#include <vector>
68
69#endif /* DOXYGEN_SHOULD_SKIP_THIS */
70
71#define XSTR(s) STR(s)
72#define STR(s) #s
73
74namespace utils::CallForCommandline {
76}
77
78namespace utils {
79
80 // Escape characters with special meaning in Bash (except whitespace).
81 static std::string bash_backslash_escape_no_whitespace(std::string_view s) {
82 static const std::unordered_set<char> special{
83 '\\', '\'', // quoting/escape
84 '`', '$', // substitution
85 '|', '&', ';', '<', '>', '(', ')', '{', '}', // operators
86 '*', '?', '[', ']', '~', '!', '#', '=' // globbing/history/others
87 };
88
89 std::string out;
90 out.reserve(s.size() * 3);
91
92 for (const char c : s) {
93 if (special.contains(c)) {
94 out.push_back('\\');
95 }
96 out.push_back(c);
97 }
98 return out;
99 }
100
101 static void createCommandLineOptions(std::stringstream& out, CLI::App* app, utils::CallForCommandline::Mode mode) {
102 const CLI::Option* disabledOpt = app->get_option_no_throw("--disabled");
103 const bool disabled = disabledOpt != nullptr ? disabledOpt->as<bool>() : false;
104 if (!disabled || mode == utils::CallForCommandline::Mode::COMPLETE) {
105 for (const CLI::Option* option : app->get_options()) {
106 if (option->get_configurable()) {
107 std::string value;
108
109 switch (mode) {
111 if (option->count() > 0) {
112 try {
113 for (const auto& result : option->reduced_results()) {
114 value += (!result.empty() ? result : "\"\"") + " ";
115 }
116 value.pop_back();
117 } catch (CLI::ParseError& e) {
118 value = std::string{"<["} + Color::Code::FG_RED + e.get_name() + Color::Code::FG_DEFAULT + "] " +
119 e.what() + ">";
120 }
121 } else if (option->get_required()) {
122 value = "<REQUIRED>";
123 }
124 break;
126 if (option->get_required()) {
127 if (option->count() > 0) {
128 try {
129 for (const auto& result : option->reduced_results()) {
130 value += (!result.empty() ? result : "\"\"") + " ";
131 }
132 value.pop_back();
133 } catch (CLI::ParseError& e) {
134 value = std::string{"<["} + Color::Code::FG_RED + e.get_name() + Color::Code::FG_DEFAULT + "] " +
135 e.what() + ">";
136 }
137 } else {
138 value = "<REQUIRED>";
139 }
140 }
141 break;
144 if (option->count() > 0) {
145 try {
146 for (const auto& result : option->reduced_results()) {
147 value += (!result.empty() ? result : "\"\"") + " ";
148 }
149 value.pop_back();
150 } catch (CLI::ParseError& e) {
151 value = std::string{"<["} + Color::Code::FG_RED + e.get_name() + Color::Code::FG_DEFAULT + "] " +
152 e.what() + ">";
153 }
154 } else if (!option->get_default_str().empty()) {
155 value = option->get_default_str();
156 } else if (!option->get_required()) {
157 value = "\"\"";
158 } else {
159 value = "<REQUIRED>";
160 }
161 break;
162 }
163
164 if (!value.empty()) {
165 if (value.starts_with(std::string{"["}) && value.ends_with("]")) {
166 value = value.substr(1, value.size() - 2);
167 }
168
169 if (value != "<REQUIRED>" && value != "\"\"" && !value.starts_with("<[")) {
171 }
172 out << "--" << option->get_single_name() << ((option->get_items_expected_max() == 0) ? "=" : " ") << value << " ";
173 }
174 }
175 }
176 } else if (disabledOpt->get_default_str() == "false") {
177 out << "--disabled=true ";
178 }
179 }
180
181 static std::string createCommandLineOptions(CLI::App* app, utils::CallForCommandline::Mode mode) {
182 std::stringstream out;
183
185
186 std::string optionString = out.str();
187 if (!optionString.empty() && optionString.back() == ' ') {
188 optionString.pop_back();
189 }
190
191 return optionString;
192 }
193
194 static void createCommandLineTemplate(std::stringstream& out, CLI::App* app, utils::CallForCommandline::Mode mode);
195
196 static std::string createCommandLineSubcommands(CLI::App* app, utils::CallForCommandline::Mode mode) {
197 std::stringstream out;
198
199 const CLI::Option* disabledOpt = app->get_option_no_throw("--disabled");
200 if (disabledOpt == nullptr || !disabledOpt->as<bool>() || mode == utils::CallForCommandline::Mode::COMPLETE) {
201 for (CLI::App* subcommand : app->get_subcommands({})) {
202 if (!subcommand->get_name().empty()) {
203 createCommandLineTemplate(out, subcommand, mode);
204 }
205 }
206 }
207
208 return out.str();
209 }
210
211 static void createCommandLineTemplate(std::stringstream& out, CLI::App* app, utils::CallForCommandline::Mode mode) {
212 std::string outString;
213
214 outString = createCommandLineOptions(app, mode);
215 if (!outString.empty()) {
216 outString += " ";
217 }
218
219 outString += createCommandLineSubcommands(app, mode);
220
221 if (!outString.empty()) {
222 out << app->get_name() << " " << outString;
223 }
224 }
225
226 static std::string createCommandLineTemplate(CLI::App* app, utils::CallForCommandline::Mode mode) {
227 std::stringstream out;
228
230
231 std::string outString = out.str();
232 while (app->get_parent() != nullptr) {
233 app = app->get_parent();
234 std::string parentOptions = createCommandLineOptions(app, mode);
235 outString =
236 std::string(app->get_name()).append(" ").append(!parentOptions.empty() ? parentOptions.append(" ") : "").append(outString);
237 }
238
239 if (outString.empty()) {
241 }
242
243 return outString;
244 }
245
246 static std::string getCommandLine(CLI::App* commandlineTriggerApp) {
247 const std::string modeString = commandlineTriggerApp->get_option("--command-line")->as<std::string>();
248
250 std::ostringstream out;
251
252 if (modeString == "standard") {
253 out << "Below is a command line viewing all non-default and required options:\n"
254 "* Options show their configured value\n"
255 "* Required but not yet configured options show <REQUIRED> as value\n"
256 "* Options marked as <REQUIRED> need to be configured for a successful bootstrap"
257 << std::endl;
259
260 } else if (modeString == "active") {
261 out << "Below is a command line viewing the active set of options with their default or configured values:\n"
262 "* Options show either their configured or default value\n"
263 "* Required but not yet configured options show <REQUIRED> as value\n"
264 "* Options marked as <REQUIRED> need to be configured for a successful bootstrap"
265 << std::endl;
267 } else if (modeString == "complete") {
268 out << "Below is a command line viewing the complete set of options with their default values\n"
269 "* Options show their default value\n"
270 "* Required but not yet configured options show <REQUIRED> as value\n"
271 "* Options marked as <REQUIRED> need to be configured for a successful bootstrap"
272 << std::endl;
274 } else if (modeString == "required") {
275 out << "Below is a command line viewing required options only:\n"
276 "* Options show either their configured or default value\n"
277 "* Required but not yet configured options show <REQUIRED> as value\n"
278 "* Options marked as <REQUIRED> need to be configured for a successful bootstrap"
279 << std::endl;
281 }
282
283 out << std::endl
284 << Color::Code::FG_GREEN << "command@line" << Color::Code::FG_DEFAULT << ":" << Color::Code::FG_BLUE << "~/> "
285 << Color::Code::FG_DEFAULT << createCommandLineTemplate(commandlineTriggerApp, mode) << std::endl
286 << std::endl;
287
288 return out.str();
289 }
290
291 static std::string getConfig(CLI::App* configTriggeredApp) {
292 std::stringstream out;
293
294 try {
295 out << configTriggeredApp->config_to_str(true, true);
296 } catch (const CLI::ParseError& e) {
297 out << std::string{"["} << Color::Code::FG_RED << "Error" << Color::Code::FG_DEFAULT
298 << "] Showing current config: " << configTriggeredApp->get_name() << " " << e.get_name() << " " << e.what();
299 }
300
301 return out.str();
302 }
303
304 static std::string doWriteConfig(utils::SubCommand* subCommand) {
305 std::stringstream out;
306
307 std::ofstream confFile(subCommand->getOption("--write-config")->as<std::string>());
308 if (confFile.is_open()) {
309 try {
310 confFile << subCommand->configToStr();
311 confFile.close();
312 out << std::string{"["} << Color::Code::FG_GREEN << "SUCCESS" << Color::Code::FG_DEFAULT
313 << "] Writing config file: " + subCommand->getOption("--write-config")->as<std::string>() << std::endl
314 << std::endl;
315 } catch (const CLI::ParseError& e) {
316 confFile.close();
317 out << std::string{"["} << Color::Code::FG_RED << "Error" << Color::Code::FG_DEFAULT
318 << "] Writing config file: " << e.get_name() << " " << e.what() << std::endl;
319 }
320 confFile.close();
321 } else {
322 out << std::string{"["} << Color::Code::FG_RED << "Error" << Color::Code::FG_DEFAULT
323 << "] Writing config file: " << std::strerror(errno) << std::endl;
324 }
325
326 return out.str();
327 }
328
329 static std::string getHelp(utils::SubCommand* subCommand, CLI::App* helpTriggerApp) {
330 std::stringstream out;
331
332 const std::string helpMode =
333 (helpTriggerApp != nullptr ? helpTriggerApp->get_option("--help") : subCommand->getOption("--help"))->as<std::string>();
334
335 const CLI::App* helpApp = nullptr;
336 CLI::AppFormatMode mode = CLI::AppFormatMode::Normal;
337 if (helpMode == "exact") {
338 helpApp = helpTriggerApp;
339 } else if (helpMode == "expanded") {
340 helpApp = helpTriggerApp;
341 mode = CLI::AppFormatMode::All;
342 }
343 try {
344 out << subCommand->help(helpApp, mode);
345 } catch (CLI::ParseError& e) {
346 out << std::string{"["} << Color::Code::FG_RED << "Error" << Color::Code::FG_DEFAULT << "] Show help: " << e.get_name() << " "
347 << e.what();
348 }
349
350 return out.str();
351 }
352
354 : utils::SubCommand(nullptr, std::make_shared<utils::AppWithPtr>("Root of config", "", this), "", false) {
356 }
357
360
361 ConfigRoot* ConfigRoot::addRootOptions(const std::string& applicationName,
362 const std::string& userName,
363 const std::string& groupName,
364 const std::string& configDirectory,
365 const std::string& logDirectory,
366 const std::string& pidDirectory) {
367 this->applicationName = applicationName;
368 this->pidDirectory = pidDirectory;
369
370 description("Configuration for Application '" + applicationName + "'");
371
372 footer("Application '" + applicationName +
373 "' powered by SNode.C\n"
374 "(C) 2020-2026 Volker Christian <me@vchrist.at>\n"
375 "https://github.com/SNodeC/snode.c");
376
377 setConfig(configDirectory + "/" + applicationName + ".conf");
378
380 "-w,--write-config",
381 "Write config file and exit",
382 "configfile",
383 configDirectory + "/" + applicationName + ".conf",
384 !CLI::ExistingDirectory),
385 false)
386 ->expected(0, 1);
387
388 killOpt = setConfigurable(addFlag("-k,--kill", "Kill running daemon", "", CLI::Validator()), false);
389
390 logLevelOpt = setConfigurable(addOption("--log-level", "Log level", "level", 4, CLI::Range(0, 6)), true);
391
392 verboseLevelOpt = setConfigurable(addOption("--verbose-level", "Verbose level", "level", 2, CLI::Range(0, 10)), true);
393
394 logFileOpt = setConfigurable(setLogFile(logDirectory + "/" + applicationName + ".log"), true);
395
397 "-m{true},--monochrom-logmonochromLogOption{true}",
398 [&monochromLogOpt = this->monochromLogOpt]() {
399 if (monochromLogOpt->as<bool>()) {
401 } else {
403 }
404 },
405 "Monochrom log output",
406 "bool",
407 logger::Logger::getDisableColor() ? "true" : "false",
408 CLI::IsMember({"true", "false"})),
409 true)
410 ->trigger_on_parse();
411
413 "-q{true},--quiet{true}",
414 "Quiet mode",
415 "bool",
416 "false",
417 CLI::IsMember({"true", "false"})),
418 true);
419
421 "-e{true},--enforce-log-file{true}",
422 "Enforce writing of logs to file for foreground applications",
423 "bool",
424 "false",
425 CLI::IsMember({"true", "false"})),
426 true);
427
429 "-d{true},--daemonize{true}",
430 "Start application as daemon",
431 "bool",
432 "false",
433 CLI::IsMember({"true", "false"})),
434 true);
435
437 "-u,--user-name",
438 "Run daemon under specific user permissions",
439 "username",
440 userName,
441 CLI::TypeValidator<std::string>()),
442 true);
443
445 "-g,--group-name",
446 "Run daemon under specific group permissions",
447 "groupname",
448 groupName,
449 CLI::TypeValidator<std::string>()),
450 true);
451
453 "Make an instance also known as an alias in configuration files",
454 "instance=instance_alias [instance=instance_alias [...]]",
455 CLI::TypeValidator<std::string>()),
456 false);
457
458 versionOpt = setVersionFlag("1.0-rc1");
459
460 return this;
461 }
462
463 bool ConfigRoot::parse1(int argc, char* argv[]) {
464 allowExtras(true);
465
466 bool proceed = parse2(argc, argv, true);
467
468 if (proceed) {
469 if (killOpt->count() > 0) {
470 try {
471 const pid_t daemonPid =
472 utils::Daemon::stopDaemon(pidDirectory + "/" + std::string(std::filesystem::path(argv[0]).filename()) + ".pid");
473 std::cout << "Daemon terminated: Pid = " << daemonPid << std::endl;
474 } catch (const DaemonError& e) {
475 std::cout << "DaemonError: " << e.what() << std::endl;
476 } catch (const DaemonFailure& e) {
477 std::cout << "DaemonFailure: " << e.what() << std::endl;
478 }
479
480 proceed = false;
481 } else {
482 if (helpTriggerApp == nullptr && showConfigTriggerApp == nullptr && commandlineTriggerApp == nullptr &&
483 versionOpt->count() == 0 && writeConfigOpt->count() == 0) {
486 }
487
489
490 allowExtras(false);
491 }
492 }
493
494 return proceed;
495 }
496
497 bool ConfigRoot::bootstrap(int argc, char* argv[]) {
498 finalCallback([this]() {
499 if (daemonizeOpt->as<bool>() && helpTriggerApp == nullptr && showConfigTriggerApp == nullptr && writeConfigOpt->count() == 0 &&
500 commandlineTriggerApp == nullptr) {
501 std::cout << "Running as daemon (double fork)" << std::endl;
502
504 pidDirectory + "/" + applicationName + ".pid", userNameOpt->as<std::string>(), groupNameOpt->as<std::string>());
505
507
508 const std::string logFile = logFileOpt->as<std::string>();
509 if (!logFile.empty()) {
511 }
512 } else if (enforceLogFileOpt->as<bool>()) {
513 const std::string logFile = logFileOpt->as<std::string>();
514 if (!logFile.empty()) {
515 std::cout << "Writing logs to file " << logFile << std::endl;
516
518 }
519 }
520 });
521
522 return parse2(argc, argv);
523 }
524
526 if (daemonizeOpt->as<bool>()) {
527 std::ifstream pidFile(pidDirectory + "/" + applicationName + ".pid", std::ifstream::in);
528
529 if (pidFile.good()) {
530 pid_t pid = 0;
531 pidFile >> pid;
532
533 if (getpid() == pid) {
535 }
536 }
537 } else if (fcntl(STDIN_FILENO, F_SETFL, O_NONBLOCK) >= 0) {
538 char buf[1024];
539 while (read(STDIN_FILENO, buf, 1024) > 0) {
540 }
541 }
542
544 }
545
546 bool ConfigRoot::parse2(int argc, char* argv[], bool parse1) {
547 bool proceed = false;
548
549 try {
550 try {
551 try {
552 aliases.clear();
553
554 helpTriggerApp = nullptr;
555 showConfigTriggerApp = nullptr;
556 commandlineTriggerApp = nullptr;
557
558 parse(argc, argv);
559
560 if (!parse1) {
561 if (showConfigTriggerApp != nullptr) {
563 } else if (commandlineTriggerApp != nullptr) {
565 } else if (getOption("--write-config")->count() > 0) {
566 std::cout << doWriteConfig(this);
567 } else {
568 proceed = true;
569 }
570 } else {
571 proceed = true;
572 }
573 } catch (const CLI::Success&) {
574 if (!parse1) {
575 throw;
576 }
577
578 proceed = true;
579 } catch (const CLI::ParseError& e) {
580 if (helpTriggerApp != nullptr || showConfigTriggerApp != nullptr || commandlineTriggerApp != nullptr ||
581 versionOpt->count() > 0) {
582 std::cout << std::string{"["} << Color::Code::FG_RED << e.get_name() << Color::Code::FG_DEFAULT << "] " << e.what()
583 << std::endl
584 << std::endl;
585
586 if (versionOpt->count() > 0) {
587 throw CLI::CallForVersion();
588 }
589 if (helpTriggerApp != nullptr) {
590 throw CLI::CallForHelp();
591 }
592 if (showConfigTriggerApp != nullptr) {
594 } else if (commandlineTriggerApp != nullptr) {
596 }
597 } else {
599 throw;
600 }
601 }
602 } catch (const DaemonError& e) {
603 std::cout << std::string{"["} << Color::Code::FG_RED << "Error" << Color::Code::FG_DEFAULT << "] Daemonize: " << e.what()
604 << " ... exiting" << std::endl;
605 } catch (const DaemonFailure& e) {
606 std::cout << std::string{"["} << Color::Code::FG_RED << "Failure" << Color::Code::FG_DEFAULT << "] Daemonize: " << e.what()
607 << " ... exiting" << std::endl;
608 } catch (const DaemonSignaled& e) {
609 std::cout << "Pid: " << getpid() << ", child pid: " << e.getPid() << ": " << e.what() << std::endl;
610 } catch (const CLI::CallForHelp&) {
611 std::cout << getHelp(this, helpTriggerApp);
612 } catch (const CLI::CallForVersion&) {
613 std::cout << version() << std::endl << std::endl;
614 } catch (const CLI::ConversionError& e) {
615 std::cout << std::string{"["} << Color::Code::FG_RED << e.get_name() << Color::Code::FG_DEFAULT << "] " << e.what()
616 << std::endl;
617 throw;
618 } catch (const CLI::ArgumentMismatch& e) {
619 std::cout << std::string{"["} << Color::Code::FG_RED << e.get_name() << Color::Code::FG_DEFAULT << "] " << e.what()
620 << std::endl;
621 throw;
622 } catch (const CLI::ConfigError& e) {
623 std::cout << std::string{"["} << Color::Code::FG_RED << e.get_name() << Color::Code::FG_DEFAULT << "] " << e.what()
624 << std::endl;
625 std::cout << " Adding '-w' on the command line may solve this problem" << std::endl;
626 throw;
627 } catch (const CLI::ParseError& e) {
628 const std::string what = e.what();
629 if (what.find("[Option Group: ") != std::string::npos) { // If CLI11 throws that error it means for us there are
630 // unconfigured anonymous instances
631 std::cout << Color::Code::FG_RED << e.get_name() << Color::Code::FG_DEFAULT
632 << " Anonymous instance(s) not configured in source code " << std::endl;
633 } else {
634 std::cout << std::string{"["} << Color::Code::FG_RED << e.get_name() << Color::Code::FG_DEFAULT << "] " << what
635 << std::endl;
636 }
637 throw;
638 }
639 } catch ([[maybe_unused]] const CLI::ParseError& e) {
640 std::cout << std::endl << "Append -h or --help to your command line for more information." << std::endl;
641 } catch (const CLI::Error& e) {
642 std::cout << std::string{"["} << Color::Code::FG_RED << e.get_name() << Color::Code::FG_DEFAULT << "] " << e.what()
643 << std::endl;
644
645 std::cout << std::endl << "Append -h or --help to your command line for more information." << std::endl;
646 }
647
648 return proceed;
649 }
650
651 bool Config::init(int argc, char* argv[]) {
652 bool proceed = true;
653
654 Config::argc = argc;
655 Config::argv = argv;
656
657 applicationName = std::filesystem::path(argv[0]).filename();
658
659 uid_t euid = 0;
660 const struct passwd* pw = nullptr;
661 const struct group* gr = nullptr;
662
663 if ((pw = getpwuid(getuid())) == nullptr) {
664 proceed = false;
665 } else if ((gr = getgrgid(pw->pw_gid)) == nullptr) {
666 proceed = false;
667 } else if ((euid = geteuid()) == 0) {
668 configDirectory = "/etc/snode.c";
669 logDirectory = "/var/log/snode.c";
670 pidDirectory = "/var/run/snode.c";
671 } else {
672 const char* homedir = nullptr;
673 if ((homedir = std::getenv("XDG_CONFIG_HOME")) == nullptr) {
674 if ((homedir = std::getenv("HOME")) == nullptr) {
675 homedir = pw->pw_dir;
676 }
677 }
678
679 if (homedir != nullptr) {
680 configDirectory = std::string(homedir) + "/.config/snode.c";
681 logDirectory = std::string(homedir) + "/.local/log/snode.c";
682 pidDirectory = std::string(homedir) + "/.local/run/snode.c";
683 } else {
684 proceed = false;
685 }
686 }
687
688 if (proceed && !std::filesystem::exists(configDirectory)) {
689 if (std::filesystem::create_directories(configDirectory)) {
690 std::filesystem::permissions(
692 (std::filesystem::perms::owner_all | std::filesystem::perms::group_read | std::filesystem::perms::group_exec) &
693 ~std::filesystem::perms::others_all);
694 if (geteuid() == 0) {
695 const struct group* gr = nullptr;
696 if ((gr = getgrnam(XSTR(GROUP_NAME))) != nullptr) {
697 if (chown(configDirectory.c_str(), euid, gr->gr_gid) < 0) {
698 std::cout << "Warning: Can not set group ownership of '" << configDirectory
699 << "' to 'snodec':" << strerror(errno) << std::endl;
700 }
701 } else {
702 std::cout << "Error: Can not find group 'snodec'. Add it using groupadd or addgroup" << std::endl;
703 std::cout << " and add the current user to this group." << std::endl;
704 std::filesystem::remove(configDirectory);
705 proceed = false;
706 }
707 }
708 } else {
709 std::cout << "Error: Can not create directory '" << configDirectory << "'" << std::endl;
710 proceed = false;
711 }
712 }
713
714 if (proceed && !std::filesystem::exists(logDirectory)) {
715 if (std::filesystem::create_directories(logDirectory)) {
716 std::filesystem::permissions(logDirectory,
717 (std::filesystem::perms::owner_all | std::filesystem::perms::group_all) &
718 ~std::filesystem::perms::others_all);
719 if (geteuid() == 0) {
720 const struct group* gr = nullptr;
721 if ((gr = getgrnam(XSTR(GROUP_NAME))) != nullptr) {
722 if (chown(logDirectory.c_str(), euid, gr->gr_gid) < 0) {
723 std::cout << "Warning: Can not set group ownership of '" << logDirectory << "' to 'snodec':" << strerror(errno)
724 << std::endl;
725 }
726 } else {
727 std::cout << "Error: Can not find group 'snodec'. Add it using groupadd or addgroup" << std::endl;
728 std::cout << " and add the current user to this group." << std::endl;
729 std::filesystem::remove(configDirectory);
730 proceed = false;
731 }
732 }
733 } else {
734 std::cout << "Error: Can not create directory '" << logDirectory << "'" << std::endl;
735 proceed = false;
736 }
737 }
738
739 if (proceed && !std::filesystem::exists(pidDirectory)) {
740 if (std::filesystem::create_directories(pidDirectory)) {
741 std::filesystem::permissions(pidDirectory,
742 (std::filesystem::perms::owner_all | std::filesystem::perms::group_all) &
743 ~std::filesystem::perms::others_all);
744 if (geteuid() == 0) {
745 const struct group* gr = nullptr;
746 if ((gr = getgrnam(XSTR(GROUP_NAME))) != nullptr) {
747 if (chown(pidDirectory.c_str(), euid, gr->gr_gid) < 0) {
748 std::cout << "Warning: Can not set group ownership of '" << pidDirectory << "' to 'snodec':" << strerror(errno)
749 << std::endl;
750 }
751 } else {
752 std::cout << "Error: Can not find group 'snodec'. Add it using groupadd or addgroup." << std::endl;
753 std::cout << " and add the current user to this group." << std::endl;
754 std::filesystem::remove(configDirectory);
755 proceed = false;
756 }
757 }
758 } else {
759 std::cout << "Error: Can not create directory '" << pidDirectory << "'" << std::endl;
760 proceed = false;
761 }
762 }
763
764 if (proceed) {
766
767 proceed = configRoot.parse1(argc, argv);
768 }
769
770 return proceed;
771 }
772
776
780
784
785 const std::string& Config::getApplicationName() {
786 return applicationName;
787 }
788
790 return configRoot.logLevelOpt->as<int>();
791 }
792
794 return configRoot.verboseLevelOpt->as<int>();
795 }
796
797 int Config::argc = 0;
798 char** Config::argv = nullptr;
799
801
802 std::string Config::applicationName;
803
804 std::string Config::configDirectory;
805 std::string Config::logDirectory;
806 std::string Config::pidDirectory;
807
808} // namespace utils
#define XSTR(s)
static void setDisableColor(bool disableColorLog=true)
Definition Logger.cpp:240
static void logToFile(const std::string &logFile)
Definition Logger.cpp:221
static void setVerboseLevel(int level)
Definition Logger.cpp:217
static void setLogLevel(int level)
Definition Logger.cpp:213
static void init()
Definition Logger.cpp:193
static void setQuiet(bool quiet=true)
Definition Logger.cpp:236
static bool getDisableColor()
Definition Logger.cpp:244
CLI::Option * enforceLogFileOpt
Definition Config.h:87
std::string pidDirectory
Definition Config.h:80
bool parse1(int argc, char *argv[])
Definition Config.cpp:463
CLI::Option * logFileOpt
Definition Config.h:83
CLI::Option * versionOpt
Definition Config.h:91
bool parse2(int argc, char *argv[], bool parse1=false)
Definition Config.cpp:546
CLI::Option * verboseLevelOpt
Definition Config.h:89
CLI::Option * killOpt
Definition Config.h:93
CLI::Option * aliasOpt
Definition Config.h:94
bool bootstrap(int argc, char *argv[])
Definition Config.cpp:497
std::string applicationName
Definition Config.h:79
CLI::Option * userNameOpt
Definition Config.h:85
CLI::Option * daemonizeOpt
Definition Config.h:82
CLI::Option * monochromLogOpt
Definition Config.h:84
ConfigRoot * addRootOptions(const std::string &applicationName, const std::string &userName, const std::string &groupName, const std::string &configDirectory, const std::string &logDirectory, const std::string &pidDirectory)
Definition Config.cpp:361
CLI::Option * writeConfigOpt
Definition Config.h:92
CLI::Option * groupNameOpt
Definition Config.h:86
CLI::Option * quietOpt
Definition Config.h:90
CLI::Option * logLevelOpt
Definition Config.h:88
~ConfigRoot() override
Definition Config.cpp:358
static std::string applicationName
Definition Config.h:122
static const std::string & getApplicationName()
Definition Config.cpp:785
static std::string configDirectory
Definition Config.h:124
static std::string pidDirectory
Definition Config.h:126
static void terminate()
Definition Config.cpp:781
static void parse()
Definition Config.cpp:777
static int argc
Definition Config.h:119
static std::string logDirectory
Definition Config.h:125
static bool init(int argc, char *argv[])
Definition Config.cpp:651
static ConfigRoot configRoot
Definition Config.h:116
static char ** argv
Definition Config.h:120
static bool bootstrap()
Definition Config.cpp:773
static int getLogLevel()
Definition Config.cpp:789
static int getVerboseLevel()
Definition Config.cpp:793
pid_t getPid() const
Definition Daemon.cpp:220
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:160
static void erasePidFile(const std::string &pidFileName)
Definition Daemon.cpp:202
std::string version() const
SubCommand * description(const std::string &description)
SubCommand * allowExtras(bool allow=true)
CLI::Option * addFlagFunction(const std::string &name, const std::function< void()> &callback, const std::string &description, const std::string &typeName, const std::string &defaultValue, const CLI::Validator &validator) const
SubCommand * footer(const std::string &footer)
void parse(int argc, char *argv[]) const
SubCommand(SubCommand *parent, std::shared_ptr< utils::AppWithPtr > appWithPtr, const std::string &group, bool final)
static CLI::App * showConfigTriggerApp
Definition SubCommand.h:243
CLI::Option * addFlag(const std::string &name, const std::string &description, const std::string &typeName, ValueTypeT defaultValue, const CLI::Validator &validator) const
Definition SubCommand.h:338
CLI::Option * addOption(const std::string &name, const std::string &description, const std::string &typeName, ValueTypeT defaultValue, const CLI::Validator &validator) const
Definition SubCommand.h:299
CLI::Option * setVersionFlag(const std::string &version) const
SubCommand * finalCallback(const std::function< void()> &finalCallback)
static CLI::App * helpTriggerApp
Definition SubCommand.h:242
CLI::Option * addOption(const std::string &name, const std::string &description, const std::string &typeName, const CLI::Validator &validator) const
CLI::Option * getOption(const std::string &name) const
CLI::Option * setConfig(const std::string &defaultConfigFile) const
static std::map< std::string, std::string > aliases
Definition SubCommand.h:240
std::string help(const CLI::App *helpApp, const CLI::AppFormatMode &mode) const
std::string configToStr() const
static CLI::App * commandlineTriggerApp
Definition SubCommand.h:244
CLI::Option * setLogFile(const std::string &defaultLogFile) const
CLI::Option * setConfigurable(CLI::Option *option, bool configurable) const
CLI::Option * addFlag(const std::string &name, const std::string &description, const std::string &typeName, const CLI::Validator &validator) const
#define STR(a)
Definition clients.h:48
Definition Logger.h:55
Code
Definition Logger.h:57
@ FG_DEFAULT
Definition Logger.h:58
std::string operator+(const std::string &string, const Code &code)
Definition Logger.cpp:304
static std::string createCommandLineTemplate(CLI::App *app, utils::CallForCommandline::Mode mode)
Definition Config.cpp:226
static std::string createCommandLineOptions(CLI::App *app, utils::CallForCommandline::Mode mode)
Definition Config.cpp:181
static std::string doWriteConfig(utils::SubCommand *subCommand)
Definition Config.cpp:304
static std::string createCommandLineSubcommands(CLI::App *app, utils::CallForCommandline::Mode mode)
Definition Config.cpp:196
static std::string getHelp(utils::SubCommand *subCommand, CLI::App *helpTriggerApp)
Definition Config.cpp:329
static std::string getConfig(CLI::App *configTriggeredApp)
Definition Config.cpp:291
static void createCommandLineTemplate(std::stringstream &out, CLI::App *app, utils::CallForCommandline::Mode mode)
Definition Config.cpp:211
static void createCommandLineOptions(std::stringstream &out, CLI::App *app, utils::CallForCommandline::Mode mode)
Definition Config.cpp:101
static std::string bash_backslash_escape_no_whitespace(std::string_view s)
Definition Config.cpp:81
static std::string getCommandLine(CLI::App *commandlineTriggerApp)
Definition Config.cpp:246