1 /* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
3 #include "cli/clicommand.hpp"
4 #include "config/configcompilercontext.hpp"
5 #include "config/configcompiler.hpp"
6 #include "config/configitembuilder.hpp"
7 #include "config/expression.hpp"
8 #include "base/application.hpp"
9 #include "base/configuration.hpp"
10 #include "base/logger.hpp"
11 #include "base/timer.hpp"
12 #include "base/utility.hpp"
13 #include "base/loader.hpp"
14 #include "base/exception.hpp"
15 #include "base/convert.hpp"
16 #include "base/scriptglobal.hpp"
17 #include "base/context.hpp"
18 #include "base/console.hpp"
19 #include "base/process.hpp"
21 #include <boost/program_options.hpp>
22 #include <boost/algorithm/string/split.hpp>
26 # include <sys/types.h>
32 # include <Shellapi.h>
36 using namespace icinga;
37 namespace po = boost::program_options;
40 static SERVICE_STATUS l_SvcStatus;
41 static SERVICE_STATUS_HANDLE l_SvcStatusHandle;
45 static std::vector<String> GetLogLevelCompletionSuggestions(const String& arg)
47 std::vector<String> result;
49 String debugLevel = "debug";
50 if (debugLevel.Find(arg) == 0)
51 result.push_back(debugLevel);
53 String noticeLevel = "notice";
54 if (noticeLevel.Find(arg) == 0)
55 result.push_back(noticeLevel);
57 String informationLevel = "information";
58 if (informationLevel.Find(arg) == 0)
59 result.push_back(informationLevel);
61 String warningLevel = "warning";
62 if (warningLevel.Find(arg) == 0)
63 result.push_back(warningLevel);
65 String criticalLevel = "critical";
66 if (criticalLevel.Find(arg) == 0)
67 result.push_back(criticalLevel);
72 static std::vector<String> GlobalArgumentCompletion(const String& argument, const String& word)
74 if (argument == "include")
75 return GetBashCompletionSuggestions("directory", word);
76 else if (argument == "log-level")
77 return GetLogLevelCompletionSuggestions(word);
79 return std::vector<String>();
82 static void HandleLegacyDefines()
85 String dataPrefix = Utility::GetIcingaDataPath();
88 Value localStateDir = Configuration::LocalStateDir;
90 if (!localStateDir.IsEmpty()) {
91 Log(LogWarning, "icinga-app")
92 << "Please do not set the deprecated 'LocalStateDir' constant,"
93 << " use the 'DataDir', 'LogDir', 'CacheDir' and 'SpoolDir' constants instead!"
94 << " For compatibility reasons, these are now set based on the 'LocalStateDir' constant.";
97 Configuration::DataDir = localStateDir + "\\lib\\icinga2";
98 Configuration::LogDir = localStateDir + "\\log\\icinga2";
99 Configuration::CacheDir = localStateDir + "\\cache\\icinga2";
100 Configuration::SpoolDir = localStateDir + "\\spool\\icinga2";
102 Configuration::LocalStateDir = dataPrefix + "\\var";
104 Configuration::DataDir = localStateDir + "/lib/icinga2";
105 Configuration::LogDir = localStateDir + "/log/icinga2";
106 Configuration::CacheDir = localStateDir + "/cache/icinga2";
107 Configuration::SpoolDir = localStateDir + "/spool/icinga2";
109 Configuration::LocalStateDir = ICINGA_LOCALSTATEDIR;
113 Value sysconfDir = Configuration::SysconfDir;
114 if (!sysconfDir.IsEmpty()) {
115 Log(LogWarning, "icinga-app")
116 << "Please do not set the deprecated 'Sysconfdir' constant, use the 'ConfigDir' constant instead! For compatibility reasons, their value is set based on the 'SysconfDir' constant.";
119 Configuration::ConfigDir = sysconfDir + "\\icinga2";
121 Configuration::SysconfDir = dataPrefix + "\\etc";
123 Configuration::ConfigDir = sysconfDir + "/icinga2";
125 Configuration::SysconfDir = ICINGA_SYSCONFDIR;
129 Value runDir = Configuration::RunDir;
130 if (!runDir.IsEmpty()) {
131 Log(LogWarning, "icinga-app")
132 << "Please do not set the deprecated 'RunDir' constant, use the 'InitRunDir' constant instead! For compatibility reasons, their value is set based on the 'RunDir' constant.";
135 Configuration::InitRunDir = runDir + "\\icinga2";
137 Configuration::RunDir = dataPrefix + "\\var\\run";
139 Configuration::InitRunDir = runDir + "/icinga2";
141 Configuration::RunDir = ICINGA_RUNDIR;
148 int argc = Application::GetArgC();
149 char **argv = Application::GetArgV();
151 bool autocomplete = false;
154 if (argc >= 4 && strcmp(argv[1], "--autocomplete") == 0) {
158 autoindex = Convert::ToLong(argv[2]);
159 } catch (const std::invalid_argument&) {
160 Log(LogCritical, "icinga-app")
161 << "Invalid index for --autocomplete: " << argv[2];
169 Application::SetStartTime(Utility::GetTime());
171 /* Set thread title. */
172 Utility::SetThreadName("Main Thread", false);
174 /* Install exception handlers to make debugging easier. */
175 Application::InstallExceptionHandlers();
178 bool builtinPaths = true;
180 /* Programm install location, C:/Program Files/Icinga2 */
181 String binaryPrefix = Utility::GetIcingaInstallPath();
182 /* Returns the datapath for daemons, %PROGRAMDATA%/icinga2 */
183 String dataPrefix = Utility::GetIcingaDataPath();
185 if (!binaryPrefix.IsEmpty() && !dataPrefix.IsEmpty()) {
186 Configuration::ProgramData = dataPrefix;
188 Configuration::ConfigDir = dataPrefix + "\\etc\\icinga2";
190 Configuration::DataDir = dataPrefix + "\\var\\lib\\icinga2";
191 Configuration::LogDir = dataPrefix + "\\var\\log\\icinga2";
192 Configuration::CacheDir = dataPrefix + "\\var\\cache\\icinga2";
193 Configuration::SpoolDir = dataPrefix + "\\var\\spool\\icinga2";
195 Configuration::PrefixDir = binaryPrefix;
197 /* Internal constants. */
198 Configuration::PkgDataDir = binaryPrefix + "\\share\\icinga2";
199 Configuration::IncludeConfDir = binaryPrefix + "\\share\\icinga2\\include";
201 Configuration::InitRunDir = dataPrefix + "\\var\\run\\icinga2";
203 Log(LogWarning, "icinga-app", "Registry key could not be read. Falling back to built-in paths.");
206 Configuration::ConfigDir = ICINGA_CONFIGDIR;
208 Configuration::DataDir = ICINGA_DATADIR;
209 Configuration::LogDir = ICINGA_LOGDIR;
210 Configuration::CacheDir = ICINGA_CACHEDIR;
211 Configuration::SpoolDir = ICINGA_SPOOLDIR;
213 Configuration::PrefixDir = ICINGA_PREFIX;
215 /* Internal constants. */
216 Configuration::PkgDataDir = ICINGA_PKGDATADIR;
217 Configuration::IncludeConfDir = ICINGA_INCLUDECONFDIR;
219 Configuration::InitRunDir = ICINGA_INITRUNDIR;
225 Configuration::ZonesDir = Configuration::ConfigDir + "/zones.d";
227 String icingaUser = Utility::GetFromEnvironment("ICINGA2_USER");
228 if (icingaUser.IsEmpty())
229 icingaUser = ICINGA_USER;
231 String icingaGroup = Utility::GetFromEnvironment("ICINGA2_GROUP");
232 if (icingaGroup.IsEmpty())
233 icingaGroup = ICINGA_GROUP;
235 Configuration::RunAsUser = icingaUser;
236 Configuration::RunAsGroup = icingaGroup;
240 String rLimitFiles = Utility::GetFromEnvironment("ICINGA2_RLIMIT_FILES");
241 if (rLimitFiles.IsEmpty())
242 Configuration::RLimitFiles = Application::GetDefaultRLimitFiles();
245 Configuration::RLimitFiles = Convert::ToLong(rLimitFiles);
246 } catch (const std::invalid_argument& ex) {
248 << "Error setting \"ICINGA2_RLIMIT_FILES\": " << ex.what() << '\n';
252 #endif /* RLIMIT_NOFILE */
255 String rLimitProcesses = Utility::GetFromEnvironment("ICINGA2_RLIMIT_PROCESSES");
256 if (rLimitProcesses.IsEmpty())
257 Configuration::RLimitProcesses = Application::GetDefaultRLimitProcesses();
260 Configuration::RLimitProcesses = Convert::ToLong(rLimitProcesses);
261 } catch (const std::invalid_argument& ex) {
263 << "Error setting \"ICINGA2_RLIMIT_PROCESSES\": " << ex.what() << '\n';
267 #endif /* RLIMIT_NPROC */
270 String rLimitStack = Utility::GetFromEnvironment("ICINGA2_RLIMIT_STACK");
271 if (rLimitStack.IsEmpty())
272 Configuration::RLimitStack = Application::GetDefaultRLimitStack();
275 Configuration::RLimitStack = Convert::ToLong(rLimitStack);
276 } catch (const std::invalid_argument& ex) {
278 << "Error setting \"ICINGA2_RLIMIT_STACK\": " << ex.what() << '\n';
282 #endif /* RLIMIT_STACK */
285 /* Calculate additional global constants. */
286 ScriptGlobal::Set("System.PlatformKernel", Utility::GetPlatformKernel(), true);
287 ScriptGlobal::Set("System.PlatformKernelVersion", Utility::GetPlatformKernelVersion(), true);
288 ScriptGlobal::Set("System.PlatformName", Utility::GetPlatformName(), true);
289 ScriptGlobal::Set("System.PlatformVersion", Utility::GetPlatformVersion(), true);
290 ScriptGlobal::Set("System.PlatformArchitecture", Utility::GetPlatformArchitecture(), true);
292 ScriptGlobal::Set("System.BuildHostName", ICINGA_BUILD_HOST_NAME, true);
293 ScriptGlobal::Set("System.BuildCompilerName", ICINGA_BUILD_COMPILER_NAME, true);
294 ScriptGlobal::Set("System.BuildCompilerVersion", ICINGA_BUILD_COMPILER_VERSION, true);
297 Application::SetResourceLimits();
299 LogSeverity logLevel = Logger::GetConsoleLogSeverity();
300 Logger::SetConsoleLogSeverity(LogWarning);
302 po::options_description visibleDesc("Global options");
304 visibleDesc.add_options()
305 ("help,h", "show this help message")
306 ("version,V", "show version information")
308 ("color", "use VT100 color codes even when stdout is not a terminal")
310 ("define,D", po::value<std::vector<std::string> >(), "define a constant")
311 ("include,I", po::value<std::vector<std::string> >(), "add include search directory")
312 ("log-level,x", po::value<std::string>(), "specify the log level for the console log.\n"
313 "The valid value is either debug, notice, information (default), warning, or critical")
314 ("script-debugger,X", "whether to enable the script debugger");
316 po::options_description hiddenDesc("Hidden options");
318 hiddenDesc.add_options()
319 ("no-stack-rlimit", "used internally, do not specify manually")
320 ("arg", po::value<std::vector<std::string> >(), "positional argument");
322 po::positional_options_description positionalDesc;
323 positionalDesc.add("arg", -1);
326 CLICommand::Ptr command;
327 po::variables_map vm;
330 CLICommand::ParseCommand(argc, argv, visibleDesc, hiddenDesc, positionalDesc,
331 vm, cmdname, command, autocomplete);
332 } catch (const std::exception& ex) {
333 Log(LogCritical, "icinga-app")
334 << "Error while parsing command-line options: " << ex.what();
339 char username[UNLEN + 1];
340 DWORD usernameLen = UNLEN + 1;
341 GetUserName(username, &usernameLen);
343 std::ifstream userFile;
345 /* The implicit string assignment is needed for Windows builds. */
346 String configDir = Configuration::ConfigDir;
347 userFile.open(configDir + "/user");
349 if (userFile && command && !Application::IsProcessElevated()) {
350 std::string userLine;
351 if (std::getline(userFile, userLine)) {
354 std::vector<std::string> strs;
355 boost::split(strs, userLine, boost::is_any_of("\\"));
357 if (username != strs[1] && command->GetImpersonationLevel() == ImpersonationLevel::ImpersonateIcinga
358 || command->GetImpersonationLevel() == ImpersonationLevel::ImpersonateRoot) {
359 TCHAR szPath[MAX_PATH];
361 if (GetModuleFileName(nullptr, szPath, ARRAYSIZE(szPath))) {
362 SHELLEXECUTEINFO sei = { sizeof(sei) };
363 sei.lpVerb = _T("runas");
364 sei.lpFile = "cmd.exe";
365 sei.fMask = SEE_MASK_NOCLOSEPROCESS | SEE_MASK_NOASYNC | SEE_MASK_FLAG_NO_UI;
368 std::stringstream parameters;
370 parameters << "/C " << "\"" << szPath << "\"" << " ";
372 for (int i = 1; i < argc; i++) {
375 parameters << argv[i];
378 parameters << " & SET exitcode=%errorlevel%";
379 parameters << " & pause";
380 parameters << " & EXIT /B %exitcode%";
382 std::string str = parameters.str();
383 LPCSTR cstr = str.c_str();
385 sei.lpParameters = cstr;
387 if (!ShellExecuteEx(&sei)) {
388 DWORD dwError = GetLastError();
389 if (dwError == ERROR_CANCELLED)
390 Application::Exit(0);
392 WaitForSingleObject(sei.hProcess, INFINITE);
395 GetExitCodeProcess(sei.hProcess, &exitCode);
397 CloseHandle(sei.hProcess);
399 Application::Exit(exitCode);
410 if (vm.count("color")) {
411 Console::SetType(std::cout, Console_VT100);
412 Console::SetType(std::cerr, Console_VT100);
416 if (vm.count("define")) {
417 for (const String& define : vm["define"].as<std::vector<std::string> >()) {
419 size_t pos = define.FindFirstOf('=');
420 if (pos != String::NPos) {
421 key = define.SubStr(0, pos);
422 value = define.SubStr(pos + 1);
428 std::vector<String> keyTokens = key.Split(".");
430 std::unique_ptr<Expression> expr;
431 std::unique_ptr<VariableExpression> varExpr{new VariableExpression(keyTokens[0], {}, DebugInfo())};
432 expr = std::move(varExpr);
434 for (size_t i = 1; i < keyTokens.size(); i++) {
435 std::unique_ptr<IndexerExpression> indexerExpr{new IndexerExpression(std::move(expr), MakeLiteral(keyTokens[i]))};
436 indexerExpr->SetOverrideFrozen();
437 expr = std::move(indexerExpr);
440 std::unique_ptr<SetExpression> setExpr{new SetExpression(std::move(expr), OpSetLiteral, MakeLiteral(value))};
441 setExpr->SetOverrideFrozen();
443 ScriptFrame frame(true);
444 setExpr->Evaluate(frame);
448 Configuration::SetReadOnly(true);
450 /* Ensure that all defined constants work in the way we expect them. */
451 HandleLegacyDefines();
453 if (vm.count("script-debugger"))
454 Application::SetScriptDebuggerEnabled(true);
456 Configuration::StatePath = Configuration::DataDir + "/icinga2.state";
457 Configuration::ModAttrPath = Configuration::DataDir + "/modified-attributes.conf";
458 Configuration::ObjectsPath = Configuration::CacheDir + "/icinga2.debug";
459 Configuration::VarsPath = Configuration::CacheDir + "/icinga2.vars";
460 Configuration::PidPath = Configuration::InitRunDir + "/icinga2.pid";
462 ConfigCompiler::AddIncludeSearchDir(Configuration::IncludeConfDir);
464 if (!autocomplete && vm.count("include")) {
465 for (const String& includePath : vm["include"].as<std::vector<std::string> >()) {
466 ConfigCompiler::AddIncludeSearchDir(includePath);
471 Logger::SetConsoleLogSeverity(logLevel);
473 if (vm.count("log-level")) {
474 String severity = vm["log-level"].as<std::string>();
476 LogSeverity logLevel = LogInformation;
478 logLevel = Logger::StringToSeverity(severity);
479 } catch (std::exception&) {
480 /* Inform user and exit */
481 Log(LogCritical, "icinga-app", "Invalid log level set. Default is 'information'.");
485 Logger::SetConsoleLogSeverity(logLevel);
488 if (!command || vm.count("help") || vm.count("version")) {
492 appName = Utility::BaseName(Application::GetArgV()[0]);
493 } catch (const std::bad_alloc&) {
494 Log(LogCritical, "icinga-app", "Allocation failed.");
498 if (appName.GetLength() > 3 && appName.SubStr(0, 3) == "lt-")
499 appName = appName.SubStr(3, appName.GetLength() - 3);
501 std::cout << appName << " " << "- The Icinga 2 network monitoring daemon (version: "
502 << ConsoleColorTag(vm.count("version") ? Console_ForegroundRed : Console_Normal)
503 << Application::GetAppVersion()
506 #endif /* I2_DEBUG */
507 << ConsoleColorTag(Console_Normal)
508 << ")" << std::endl << std::endl;
510 if ((!command || vm.count("help")) && !vm.count("version")) {
511 std::cout << "Usage:" << std::endl
512 << " " << Utility::BaseName(argv[0]) << " ";
514 if (cmdname.IsEmpty())
515 std::cout << "<command>";
517 std::cout << cmdname;
519 std::cout << " [<arguments>]" << std::endl;
522 std::cout << std::endl
523 << command->GetDescription() << std::endl;
527 if (vm.count("version")) {
528 std::cout << "Copyright (c) 2012-" << Utility::FormatDateTime("%Y", Utility::GetTime())
529 << " Icinga GmbH (https://icinga.com/)" << std::endl
530 << "License GPLv2+: GNU GPL version 2 or later <http://gnu.org/licenses/gpl2.html>" << std::endl
531 << "This is free software: you are free to change and redistribute it." << std::endl
532 << "There is NO WARRANTY, to the extent permitted by law.";
535 std::cout << std::endl;
537 if (vm.count("version")) {
538 std::cout << std::endl;
540 Application::DisplayInfoMessage(std::cout, true);
546 if (!command || vm.count("help")) {
548 CLICommand::ShowCommands(argc, argv, nullptr);
550 std::cout << visibleDesc << std::endl
551 << "Report bugs at <https://github.com/Icinga/icinga2>" << std::endl
552 << "Get support: <https://icinga.com/support/>" << std::endl
553 << "Documentation: <https://icinga.com/docs/>" << std::endl
554 << "Icinga home page: <https://icinga.com/>" << std::endl;
562 CLICommand::ShowCommands(argc, argv, &visibleDesc, &hiddenDesc,
563 &GlobalArgumentCompletion, true, autoindex);
565 } else if (command) {
566 Logger::DisableTimestamp();
568 if (command->GetImpersonationLevel() == ImpersonateRoot) {
570 Log(LogCritical, "cli", "This command must be run as root.");
573 } else if (command && command->GetImpersonationLevel() == ImpersonateIcinga) {
574 String group = Configuration::RunAsGroup;
575 String user = Configuration::RunAsUser;
578 struct group *gr = getgrnam(group.CStr());
582 Log(LogCritical, "cli")
583 << "Invalid group specified: " << group;
586 Log(LogCritical, "cli")
587 << "getgrnam() failed with error code " << errno << ", \"" << Utility::FormatErrorNumber(errno) << "\"";
592 if (getgid() != gr->gr_gid) {
593 if (!vm.count("reload-internal") && setgroups(0, nullptr) < 0) {
594 Log(LogCritical, "cli")
595 << "setgroups() failed with error code " << errno << ", \"" << Utility::FormatErrorNumber(errno) << "\"";
596 Log(LogCritical, "cli")
597 << "Please re-run this command as a privileged user or using the \"" << user << "\" account.";
601 if (setgid(gr->gr_gid) < 0) {
602 Log(LogCritical, "cli")
603 << "setgid() failed with error code " << errno << ", \"" << Utility::FormatErrorNumber(errno) << "\"";
609 struct passwd *pw = getpwnam(user.CStr());
613 Log(LogCritical, "cli")
614 << "Invalid user specified: " << user;
617 Log(LogCritical, "cli")
618 << "getpwnam() failed with error code " << errno << ", \"" << Utility::FormatErrorNumber(errno) << "\"";
623 // also activate the additional groups the configured user is member of
624 if (getuid() != pw->pw_uid) {
625 if (!vm.count("reload-internal") && initgroups(user.CStr(), pw->pw_gid) < 0) {
626 Log(LogCritical, "cli")
627 << "initgroups() failed with error code " << errno << ", \"" << Utility::FormatErrorNumber(errno) << "\"";
628 Log(LogCritical, "cli")
629 << "Please re-run this command as a privileged user or using the \"" << user << "\" account.";
633 if (setuid(pw->pw_uid) < 0) {
634 Log(LogCritical, "cli")
635 << "setuid() failed with error code " << errno << ", \"" << Utility::FormatErrorNumber(errno) << "\"";
636 Log(LogCritical, "cli")
637 << "Please re-run this command as a privileged user or using the \"" << user << "\" account.";
643 Process::InitializeSpawnHelper();
646 std::vector<std::string> args;
648 args = vm["arg"].as<std::vector<std::string> >();
650 if (static_cast<int>(args.size()) < command->GetMinArguments()) {
651 Log(LogCritical, "cli")
652 << "Too few arguments. Command needs at least " << command->GetMinArguments()
653 << " argument" << (command->GetMinArguments() != 1 ? "s" : "") << ".";
657 if (command->GetMaxArguments() >= 0 && static_cast<int>(args.size()) > command->GetMaxArguments()) {
658 Log(LogCritical, "cli")
659 << "Too many arguments. At most " << command->GetMaxArguments()
660 << " argument" << (command->GetMaxArguments() != 1 ? "s" : "") << " may be specified.";
664 rc = command->Run(vm, args);
671 static int SetupService(bool install, int argc, char **argv)
673 SC_HANDLE schSCManager = OpenSCManager(nullptr, nullptr, SC_MANAGER_ALL_ACCESS);
676 printf("OpenSCManager failed (%d)\n", GetLastError());
680 TCHAR szPath[MAX_PATH];
682 if (!GetModuleFileName(nullptr, szPath, MAX_PATH)) {
683 printf("Cannot install service (%d)\n", GetLastError());
688 szArgs = Utility::EscapeShellArg(szPath) + " --scm";
690 std::string scmUser = "NT AUTHORITY\\NetworkService";
691 std::ifstream initf(Utility::GetIcingaDataPath() + "\\etc\\icinga2\\user");
693 std::getline(initf, scmUser);
697 for (int i = 0; i < argc; i++) {
698 if (!strcmp(argv[i], "--scm-user") && i + 1 < argc) {
699 scmUser = argv[i + 1];
702 szArgs += " " + Utility::EscapeShellArg(argv[i]);
705 SC_HANDLE schService = OpenService(schSCManager, "icinga2", SERVICE_ALL_ACCESS);
708 SERVICE_STATUS status;
709 ControlService(schService, SERVICE_CONTROL_STOP, &status);
711 double start = Utility::GetTime();
712 while (status.dwCurrentState != SERVICE_STOPPED) {
713 double end = Utility::GetTime();
715 if (end - start > 30) {
716 printf("Could not stop the service.\n");
722 if (!QueryServiceStatus(schService, &status)) {
723 printf("QueryServiceStatus failed (%d)\n", GetLastError());
727 } else if (install) {
728 schService = CreateService(
733 SERVICE_WIN32_OWN_PROCESS,
734 SERVICE_DEMAND_START,
735 SERVICE_ERROR_NORMAL,
744 printf("CreateService failed (%d)\n", GetLastError());
745 CloseServiceHandle(schSCManager);
749 printf("Service isn't installed.\n");
750 CloseServiceHandle(schSCManager);
755 if (!DeleteService(schService)) {
756 printf("DeleteService failed (%d)\n", GetLastError());
757 CloseServiceHandle(schService);
758 CloseServiceHandle(schSCManager);
762 printf("Service uninstalled successfully\n");
764 if (!ChangeServiceConfig(schService, SERVICE_NO_CHANGE, SERVICE_AUTO_START,
765 SERVICE_ERROR_NORMAL, szArgs.CStr(), nullptr, nullptr, nullptr, scmUser.c_str(), nullptr, nullptr)) {
766 printf("ChangeServiceConfig failed (%d)\n", GetLastError());
767 CloseServiceHandle(schService);
768 CloseServiceHandle(schSCManager);
772 SERVICE_DESCRIPTION sdDescription = { "The Icinga 2 monitoring application" };
773 if(!ChangeServiceConfig2(schService, SERVICE_CONFIG_DESCRIPTION, &sdDescription)) {
774 printf("ChangeServiceConfig2 failed (%d)\n", GetLastError());
775 CloseServiceHandle(schService);
776 CloseServiceHandle(schSCManager);
780 if (!StartService(schService, 0, nullptr)) {
781 printf("StartService failed (%d)\n", GetLastError());
782 CloseServiceHandle(schService);
783 CloseServiceHandle(schSCManager);
787 std::cout << "Service successfully installed for user '" << scmUser << "'\n";
789 String userFilePath = Utility::GetIcingaDataPath() + "\\etc\\icinga2\\user";
791 std::ofstream fuser(userFilePath.CStr(), std::ios::out | std::ios::trunc);
795 std::cout << "Could not write user to " << userFilePath << "\n";
798 CloseServiceHandle(schService);
799 CloseServiceHandle(schSCManager);
804 static VOID ReportSvcStatus(DWORD dwCurrentState,
805 DWORD dwWin32ExitCode,
808 static DWORD dwCheckPoint = 1;
810 l_SvcStatus.dwCurrentState = dwCurrentState;
811 l_SvcStatus.dwWin32ExitCode = dwWin32ExitCode;
812 l_SvcStatus.dwWaitHint = dwWaitHint;
814 if (dwCurrentState == SERVICE_START_PENDING)
815 l_SvcStatus.dwControlsAccepted = 0;
817 l_SvcStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP;
819 if ((dwCurrentState == SERVICE_RUNNING) ||
820 (dwCurrentState == SERVICE_STOPPED))
821 l_SvcStatus.dwCheckPoint = 0;
823 l_SvcStatus.dwCheckPoint = dwCheckPoint++;
825 SetServiceStatus(l_SvcStatusHandle, &l_SvcStatus);
828 static VOID WINAPI ServiceControlHandler(DWORD dwCtrl)
830 if (dwCtrl == SERVICE_CONTROL_STOP) {
831 ReportSvcStatus(SERVICE_STOP_PENDING, NO_ERROR, 0);
832 TerminateJobObject(l_Job, 0);
836 static VOID WINAPI ServiceMain(DWORD argc, LPSTR *argv)
838 l_SvcStatusHandle = RegisterServiceCtrlHandler(
840 ServiceControlHandler);
842 l_SvcStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
843 l_SvcStatus.dwServiceSpecificExitCode = 0;
845 ReportSvcStatus(SERVICE_RUNNING, NO_ERROR, 0);
846 l_Job = CreateJobObject(nullptr, nullptr);
851 int uargc = Application::GetArgC();
852 char **uargv = Application::GetArgV();
854 args += Utility::EscapeShellArg(Application::GetExePath(uargv[0]));
856 for (int i = 2; i < uargc && uargv[i]; i++) {
860 args += Utility::EscapeShellArg(uargv[i]);
863 STARTUPINFO si = { sizeof(si) };
864 PROCESS_INFORMATION pi;
866 char *uargs = strdup(args.CStr());
868 BOOL res = CreateProcess(nullptr, uargs, nullptr, nullptr, FALSE, 0, nullptr, nullptr, &si, &pi);
875 CloseHandle(pi.hThread);
877 AssignProcessToJobObject(l_Job, pi.hProcess);
879 if (WaitForSingleObject(pi.hProcess, INFINITE) != WAIT_OBJECT_0)
884 if (!GetExitCodeProcess(pi.hProcess, &exitStatus))
891 TerminateJobObject(l_Job, 0);
895 ReportSvcStatus(SERVICE_STOPPED, NO_ERROR, 0);
897 Application::Exit(0);
902 * Entry point for the Icinga application.
904 * @params argc Number of command line arguments.
905 * @params argv Command line arguments.
906 * @returns The application's exit status.
908 int main(int argc, char **argv)
911 String keepFDs = Utility::GetFromEnvironment("ICINGA2_KEEP_FDS");
912 if (keepFDs.IsEmpty()) {
914 if (getrlimit(RLIMIT_NOFILE, &rl) >= 0) {
915 rlim_t maxfds = rl.rlim_max;
917 if (maxfds == RLIM_INFINITY)
920 for (rlim_t i = 3; i < maxfds; i++) {
925 std::cerr << "Closed FD " << i << " which we inherited from our parent process." << std::endl;
928 #endif /* I2_DEBUG */
934 /* must be called before using any other libbase functions */
935 Application::InitializeBase();
937 /* Set command-line arguments. */
938 Application::SetArgC(argc);
939 Application::SetArgV(argv);
942 if (argc > 1 && strcmp(argv[1], "--scm-install") == 0) {
943 return SetupService(true, argc - 2, &argv[2]);
946 if (argc > 1 && strcmp(argv[1], "--scm-uninstall") == 0) {
947 return SetupService(false, argc - 2, &argv[2]);
950 if (argc > 1 && strcmp(argv[1], "--scm") == 0) {
951 SERVICE_TABLE_ENTRY dispatchTable[] = {
952 { "icinga2", ServiceMain },
956 StartServiceCtrlDispatcher(dispatchTable);
957 Application::Exit(EXIT_FAILURE);
963 Application::Exit(rc);