1 /* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
3 #include "base/application.hpp"
4 #include "base/application-ti.cpp"
5 #include "base/stacktrace.hpp"
6 #include "base/timer.hpp"
7 #include "base/logger.hpp"
8 #include "base/exception.hpp"
9 #include "base/objectlock.hpp"
10 #include "base/utility.hpp"
11 #include "base/loader.hpp"
12 #include "base/debug.hpp"
13 #include "base/type.hpp"
14 #include "base/convert.hpp"
15 #include "base/scriptglobal.hpp"
16 #include "base/process.hpp"
17 #include <boost/algorithm/string/trim.hpp>
18 #include <boost/exception/errinfo_api_function.hpp>
19 #include <boost/exception/errinfo_errno.hpp>
20 #include <boost/exception/errinfo_file_name.hpp>
26 #include <sys/prctl.h>
27 #endif /* __linux__ */
32 #include <systemd/sd-daemon.h>
33 #endif /* HAVE_SYSTEMD */
35 using namespace icinga;
37 REGISTER_TYPE(Application);
39 boost::signals2::signal<void ()> Application::OnReopenLogs;
40 Application::Ptr Application::m_Instance = nullptr;
41 bool Application::m_ShuttingDown = false;
42 bool Application::m_RequestRestart = false;
43 bool Application::m_RequestReopenLogs = false;
44 pid_t Application::m_ReloadProcess = 0;
45 static bool l_Restarting = false;
46 static bool l_InExceptionHandler = false;
47 int Application::m_ArgC;
48 char **Application::m_ArgV;
49 double Application::m_StartTime;
50 double Application::m_MainTime;
51 bool Application::m_ScriptDebuggerEnabled = false;
52 double Application::m_LastReloadFailed;
55 * Constructor for the Application class.
57 void Application::OnConfigLoaded()
61 ASSERT(m_Instance == nullptr);
66 * Destructor for the application class.
68 void Application::Stop(bool runtimeRemoved)
70 m_ShuttingDown = true;
78 ObjectImpl<Application>::Stop(runtimeRemoved);
81 Application::~Application()
86 void Application::Exit(int rc)
91 for (const Logger::Ptr& logger : Logger::GetLoggers()) {
99 _exit(rc); // Yay, our static destructors are pretty much beyond repair at this point.
100 #endif /* I2_DEBUG */
103 void Application::InitializeBase()
106 /* disable GUI-based error messages for LoadLibrary() */
107 SetErrorMode(SEM_FAILCRITICALERRORS);
110 if (WSAStartup(MAKEWORD(1, 1), &wsaData) != 0) {
111 BOOST_THROW_EXCEPTION(win32_error()
112 << boost::errinfo_api_function("WSAStartup")
113 << errinfo_win32_error(WSAGetLastError()));
117 memset(&sa, 0, sizeof(sa));
118 sa.sa_handler = SIG_IGN;
119 sigaction(SIGPIPE, &sa, nullptr);
122 Loader::ExecuteDeferredInitializers();
124 /* Make sure the thread pool gets initialized. */
127 /* Make sure the timer thread gets initialized. */
131 void Application::UninitializeBase()
133 Timer::Uninitialize();
139 * Retrieves a pointer to the application singleton object.
141 * @returns The application object.
143 Application::Ptr Application::GetInstance()
148 void Application::SetResourceLimits()
153 # ifdef RLIMIT_NOFILE
154 rlim_t fileLimit = Configuration::RLimitFiles;
156 if (fileLimit != 0) {
157 if (fileLimit < (rlim_t)GetDefaultRLimitFiles()) {
158 Log(LogWarning, "Application")
159 << "The user-specified value for RLimitFiles cannot be smaller than the default value (" << GetDefaultRLimitFiles() << "). Using the default value instead.";
160 fileLimit = GetDefaultRLimitFiles();
163 rl.rlim_cur = fileLimit;
164 rl.rlim_max = rl.rlim_cur;
166 if (setrlimit(RLIMIT_NOFILE, &rl) < 0)
167 Log(LogWarning, "Application")
168 << "Failed to adjust resource limit for open file handles (RLIMIT_NOFILE) with error \"" << strerror(errno) << "\"";
169 # else /* RLIMIT_NOFILE */
170 Log(LogNotice, "Application", "System does not support adjusting the resource limit for open file handles (RLIMIT_NOFILE)");
171 # endif /* RLIMIT_NOFILE */
175 rlim_t processLimit = Configuration::RLimitProcesses;
177 if (processLimit != 0) {
178 if (processLimit < (rlim_t)GetDefaultRLimitProcesses()) {
179 Log(LogWarning, "Application")
180 << "The user-specified value for RLimitProcesses cannot be smaller than the default value (" << GetDefaultRLimitProcesses() << "). Using the default value instead.";
181 processLimit = GetDefaultRLimitProcesses();
184 rl.rlim_cur = processLimit;
185 rl.rlim_max = rl.rlim_cur;
187 if (setrlimit(RLIMIT_NPROC, &rl) < 0)
188 Log(LogWarning, "Application")
189 << "Failed adjust resource limit for number of processes (RLIMIT_NPROC) with error \"" << strerror(errno) << "\"";
190 # else /* RLIMIT_NPROC */
191 Log(LogNotice, "Application", "System does not support adjusting the resource limit for number of processes (RLIMIT_NPROC)");
192 # endif /* RLIMIT_NPROC */
196 int argc = Application::GetArgC();
197 char **argv = Application::GetArgV();
198 bool set_stack_rlimit = true;
200 for (int i = 0; i < argc; i++) {
201 if (strcmp(argv[i], "--no-stack-rlimit") == 0) {
202 set_stack_rlimit = false;
207 if (getrlimit(RLIMIT_STACK, &rl) < 0) {
208 Log(LogWarning, "Application", "Could not determine resource limit for stack size (RLIMIT_STACK)");
209 rl.rlim_max = RLIM_INFINITY;
214 stackLimit = Configuration::RLimitStack;
216 if (stackLimit != 0) {
217 if (stackLimit < (rlim_t)GetDefaultRLimitStack()) {
218 Log(LogWarning, "Application")
219 << "The user-specified value for RLimitStack cannot be smaller than the default value (" << GetDefaultRLimitStack() << "). Using the default value instead.";
220 stackLimit = GetDefaultRLimitStack();
223 if (set_stack_rlimit)
224 rl.rlim_cur = stackLimit;
226 rl.rlim_cur = rl.rlim_max;
228 if (setrlimit(RLIMIT_STACK, &rl) < 0)
229 Log(LogWarning, "Application")
230 << "Failed adjust resource limit for stack size (RLIMIT_STACK) with error \"" << strerror(errno) << "\"";
231 else if (set_stack_rlimit) {
232 char **new_argv = static_cast<char **>(malloc(sizeof(char *) * (argc + 2)));
239 new_argv[0] = argv[0];
240 new_argv[1] = strdup("--no-stack-rlimit");
247 for (int i = 1; i < argc; i++)
248 new_argv[i + 1] = argv[i];
250 new_argv[argc + 1] = nullptr;
252 (void) execvp(new_argv[0], new_argv);
256 # else /* RLIMIT_STACK */
257 Log(LogNotice, "Application", "System does not support adjusting the resource limit for stack size (RLIMIT_STACK)");
258 # endif /* RLIMIT_STACK */
260 #endif /* __linux__ */
263 int Application::GetArgC()
268 void Application::SetArgC(int argc)
273 char **Application::GetArgV()
278 void Application::SetArgV(char **argv)
284 * Processes events for registered sockets and timers and calls whatever
285 * handlers have been set up for these events.
287 void Application::RunEventLoop()
290 sd_notify(0, "READY=1");
291 #endif /* HAVE_SYSTEMD */
293 double lastLoop = Utility::GetTime();
295 while (!m_ShuttingDown) {
296 if (m_RequestRestart) {
297 m_RequestRestart = false; // we are now handling the request, once is enough
300 sd_notify(0, "RELOADING=1");
301 #endif /* HAVE_SYSTEMD */
303 // are we already restarting? ignore request if we already are
306 m_ReloadProcess = StartReloadProcess();
309 /* Watches for changes to the system time. Adjusts timers if necessary. */
312 if (m_RequestReopenLogs) {
313 Log(LogNotice, "Application", "Reopening log files");
314 m_RequestReopenLogs = false;
318 double now = Utility::GetTime();
319 double timeDiff = lastLoop - now;
322 sd_notify(0, "WATCHDOG=1");
323 #endif /* HAVE_SYSTEMD */
325 if (std::fabs(timeDiff) > 15) {
326 /* We made a significant jump in time. */
327 Log(LogInformation, "Application")
329 << (timeDiff < 0 ? "forward" : "backward")
330 << " in time: " << std::fabs(timeDiff) << " seconds";
332 Timer::AdjustTimers(-timeDiff);
340 sd_notify(0, "STOPPING=1");
341 #endif /* HAVE_SYSTEMD */
343 Log(LogInformation, "Application", "Shutting down...");
345 ConfigObject::StopObjects();
346 Application::GetInstance()->OnShutdown();
351 bool Application::IsShuttingDown()
353 return m_ShuttingDown;
356 bool Application::IsRestarting()
361 void Application::OnShutdown()
363 /* Nothing to do here. */
366 static void ReloadProcessCallbackInternal(const ProcessResult& pr)
368 if (pr.ExitStatus != 0) {
369 Application::SetLastReloadFailed(Utility::GetTime());
370 Log(LogCritical, "Application", "Found error in config: reloading aborted");
374 Application::Exit(7); /* keep this exit code in sync with icinga-app */
378 static void ReloadProcessCallback(const ProcessResult& pr)
380 l_Restarting = false;
382 std::thread t(std::bind(&ReloadProcessCallbackInternal, pr));
386 pid_t Application::StartReloadProcess()
390 args.push_back(GetExePath(m_ArgV[0]));
392 for (int i=1; i < Application::GetArgC(); i++) {
393 if (std::string(Application::GetArgV()[i]) != "--reload-internal")
394 args.push_back(Application::GetArgV()[i]);
396 i++; // the next parameter after --reload-internal is the pid, remove that too
400 args.push_back("--reload-internal");
401 args.push_back(Convert::ToString(Utility::GetPid()));
403 args.push_back("--validate");
406 double reloadTimeout = Application::GetReloadTimeout();
408 Process::Ptr process = new Process(Process::PrepareCommand(new Array(std::move(args))));
409 process->SetTimeout(reloadTimeout);
410 process->Run(&ReloadProcessCallback);
412 Log(LogInformation, "Application")
413 << "Got reload command: Started new instance with PID '"
414 << (unsigned long)(process->GetPID()) << "' (timeout is "
415 << reloadTimeout << "s).";
417 return process->GetPID();
421 * Signals the application to shut down during the next
422 * execution of the event loop.
424 void Application::RequestShutdown()
426 Log(LogInformation, "Application", "Received request to shut down.");
428 m_ShuttingDown = true;
432 * Signals the application to restart during the next
433 * execution of the event loop.
435 void Application::RequestRestart()
437 m_RequestRestart = true;
441 * Signals the application to reopen log files during the
442 * next execution of the event loop.
444 void Application::RequestReopenLogs()
446 m_RequestReopenLogs = true;
450 * Retrieves the full path of the executable.
452 * @param argv0 The first command-line argument.
455 String Application::GetExePath(const String& argv0)
457 String executablePath;
460 char buffer[MAXPATHLEN];
461 if (!getcwd(buffer, sizeof(buffer))) {
462 BOOST_THROW_EXCEPTION(posix_error()
463 << boost::errinfo_api_function("getcwd")
464 << boost::errinfo_errno(errno));
467 String workingDirectory = buffer;
470 executablePath = workingDirectory + "/" + argv0;
472 executablePath = argv0;
474 bool foundSlash = false;
475 for (size_t i = 0; i < argv0.GetLength(); i++) {
476 if (argv0[i] == '/') {
483 String pathEnv = Utility::GetFromEnvironment("PATH");
484 if (!pathEnv.IsEmpty()) {
485 std::vector<String> paths = String(pathEnv).Split(":");
487 bool foundPath = false;
488 for (const String& path : paths) {
489 String pathTest = path + "/" + argv0;
491 if (access(pathTest.CStr(), X_OK) == 0) {
492 executablePath = pathTest;
499 executablePath.Clear();
500 BOOST_THROW_EXCEPTION(std::runtime_error("Could not determine executable path."));
505 if (!realpath(executablePath.CStr(), buffer)) {
506 BOOST_THROW_EXCEPTION(posix_error()
507 << boost::errinfo_api_function("realpath")
508 << boost::errinfo_errno(errno)
509 << boost::errinfo_file_name(executablePath));
514 char FullExePath[MAXPATHLEN];
516 if (!GetModuleFileName(nullptr, FullExePath, sizeof(FullExePath)))
517 BOOST_THROW_EXCEPTION(win32_error()
518 << boost::errinfo_api_function("GetModuleFileName")
519 << errinfo_win32_error(GetLastError()));
526 * Display version and path information.
528 void Application::DisplayInfoMessage(std::ostream& os, bool skipVersion)
530 /* icinga-app prints its own version information, stack traces need it here. */
532 os << " Application version: " << GetAppVersion() << "\n\n";
534 os << "System information:\n"
535 << " Platform: " << Utility::GetPlatformName() << "\n"
536 << " Platform version: " << Utility::GetPlatformVersion() << "\n"
537 << " Kernel: " << Utility::GetPlatformKernel() << "\n"
538 << " Kernel version: " << Utility::GetPlatformKernelVersion() << "\n"
539 << " Architecture: " << Utility::GetPlatformArchitecture() << "\n";
541 Namespace::Ptr systemNS = ScriptGlobal::Get("System");
543 os << "\nBuild information:\n"
544 << " Compiler: " << systemNS->Get("BuildCompilerName") << " " << systemNS->Get("BuildCompilerVersion") << "\n"
545 << " Build host: " << systemNS->Get("BuildHostName") << "\n";
547 os << "\nApplication information:\n"
548 << "\nGeneral paths:\n"
549 << " Config directory: " << Configuration::ConfigDir << "\n"
550 << " Data directory: " << Configuration::DataDir << "\n"
551 << " Log directory: " << Configuration::LogDir << "\n"
552 << " Cache directory: " << Configuration::CacheDir << "\n"
553 << " Spool directory: " << Configuration::SpoolDir << "\n"
554 << " Run directory: " << Configuration::InitRunDir << "\n"
555 << "\nOld paths (deprecated):\n"
556 << " Installation root: " << Configuration::PrefixDir << "\n"
557 << " Sysconf directory: " << Configuration::SysconfDir << "\n"
558 << " Run directory (base): " << Configuration::RunDir << "\n"
559 << " Local state directory: " << Configuration::LocalStateDir << "\n"
560 << "\nInternal paths:\n"
561 << " Package data directory: " << Configuration::PkgDataDir << "\n"
562 << " State path: " << Configuration::StatePath << "\n"
563 << " Modified attributes path: " << Configuration::ModAttrPath << "\n"
564 << " Objects path: " << Configuration::ObjectsPath << "\n"
565 << " Vars path: " << Configuration::VarsPath << "\n"
566 << " PID path: " << Configuration::PidPath << "\n";
571 * Displays a message that tells users what to do when they encounter a bug.
573 void Application::DisplayBugMessage(std::ostream& os)
576 << "* This would indicate a runtime problem or configuration error. If you believe this is a bug in Icinga 2" << "\n"
577 << "* please submit a bug report at https://github.com/Icinga/icinga2 and include this stack trace as well as any other" << "\n"
578 << "* information that might be useful in order to reproduce this problem." << "\n"
582 String Application::GetCrashReportFilename()
584 return Configuration::LogDir + "/crash/report." + Convert::ToString(Utility::GetTime());
588 void Application::AttachDebugger(const String& filename, bool interactive)
592 prctl(PR_SET_DUMPABLE, 1);
593 #endif /* __linux __ */
595 String my_pid = Convert::ToString(Utility::GetPid());
600 BOOST_THROW_EXCEPTION(posix_error()
601 << boost::errinfo_api_function("fork")
602 << boost::errinfo_errno(errno));
607 int fd = open(filename.CStr(), O_CREAT | O_RDWR | O_APPEND, 0600);
610 BOOST_THROW_EXCEPTION(posix_error()
611 << boost::errinfo_api_function("open")
612 << boost::errinfo_errno(errno)
613 << boost::errinfo_file_name(filename));
617 /* redirect stdout to the file */
622 /* redirect stderr to stdout */
630 char *my_pid_str = strdup(my_pid.CStr());
633 const char *uargv[] = {
640 argv = const_cast<char **>(uargv);
642 (void) execvp(argv[0], argv);
644 const char *uargv[] = {
650 "thread apply all bt full",
658 argv = const_cast<char **>(uargv);
660 (void) execvp(argv[0], argv);
663 perror("Failed to launch GDB");
669 if (waitpid(pid, &status, 0) < 0) {
670 BOOST_THROW_EXCEPTION(posix_error()
671 << boost::errinfo_api_function("waitpid")
672 << boost::errinfo_errno(errno));
676 prctl(PR_SET_DUMPABLE, 0);
677 #endif /* __linux __ */
685 * Signal handler for SIGINT and SIGTERM. Prepares the application for cleanly
686 * shutting down during the next execution of the event loop.
688 * @param - The signal number.
690 void Application::SigIntTermHandler(int signum)
693 memset(&sa, 0, sizeof(sa));
694 sa.sa_handler = SIG_DFL;
695 sigaction(signum, &sa, nullptr);
697 Application::Ptr instance = Application::GetInstance();
702 instance->RequestShutdown();
707 * Signal handler for SIGUSR1. This signal causes Icinga to re-open
708 * its log files and is mainly for use by logrotate.
710 * @param - The signal number.
712 void Application::SigUsr1Handler(int)
714 Log(LogInformation, "Application")
715 << "Received USR1 signal, reopening application logs.";
721 * Signal handler for SIGUSR2. Hands over PID to child and commits suicide
723 * @param - The signal number.
725 void Application::SigUsr2Handler(int)
727 Log(LogInformation, "Application", "Reload requested, letting new process take over.");
729 sd_notifyf(0, "MAINPID=%lu", (unsigned long) m_ReloadProcess);
730 #endif /* HAVE_SYSTEMD */
732 /* Write the PID of the new process to the pidfile before this
733 * process exits to keep systemd happy.
735 Application::Ptr instance = GetInstance();
737 instance->UpdatePidFile(Configuration::PidPath, m_ReloadProcess);
738 } catch (const std::exception&) {
740 Log(LogCritical, "Application", "Cannot update PID file. Aborting restart operation.");
744 instance->ClosePidFile(false);
746 /* Ensure to dump the program state on reload. */
747 ConfigObject::StopObjects();
748 instance->OnShutdown();
750 Log(LogInformation, "Application")
751 << "Reload done, parent process shutting down. Child process with PID '" << m_ReloadProcess << "' is taking over.";
757 * Signal handler for SIGABRT. Helps with debugging ASSERT()s.
759 * @param - The signal number.
761 void Application::SigAbrtHandler(int)
765 memset(&sa, 0, sizeof(sa));
766 sa.sa_handler = SIG_DFL;
767 sigaction(SIGABRT, &sa, nullptr);
770 std::cerr << "Caught SIGABRT." << std::endl
771 << "Current time: " << Utility::FormatDateTime("%Y-%m-%d %H:%M:%S %z", Utility::GetTime()) << std::endl
774 String fname = GetCrashReportFilename();
775 String dirName = Utility::DirName(fname);
777 if (!Utility::PathExists(dirName)) {
779 if (mkdir(dirName.CStr(), 0700) < 0 && errno != EEXIST) {
781 if (mkdir(dirName.CStr()) < 0 && errno != EEXIST) {
783 std::cerr << "Could not create directory '" << dirName << "': Error " << errno << ", " << strerror(errno) << "\n";
787 bool interactive_debugger = Configuration::AttachDebugger;
789 if (!interactive_debugger) {
791 ofs.open(fname.CStr());
793 Log(LogCritical, "Application")
794 << "Icinga 2 has terminated unexpectedly. Additional information can be found in '" << fname << "'" << "\n";
796 DisplayInfoMessage(ofs);
799 ofs << "Stacktrace:" << "\n";
802 DisplayBugMessage(ofs);
807 Log(LogCritical, "Application", "Icinga 2 has terminated unexpectedly. Attaching debugger...");
810 AttachDebugger(fname, interactive_debugger);
815 * Console control handler. Prepares the application for cleanly
816 * shutting down during the next execution of the event loop.
818 BOOL WINAPI Application::CtrlHandler(DWORD type)
820 Application::Ptr instance = Application::GetInstance();
825 instance->RequestShutdown();
827 SetConsoleCtrlHandler(nullptr, FALSE);
831 bool Application::IsProcessElevated() {
832 BOOL fIsElevated = FALSE;
833 DWORD dwError = ERROR_SUCCESS;
834 HANDLE hToken = nullptr;
836 if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hToken))
837 dwError = GetLastError();
839 TOKEN_ELEVATION elevation;
842 if (!GetTokenInformation(hToken, TokenElevation, &elevation, sizeof(elevation), &dwSize))
843 dwError = GetLastError();
845 fIsElevated = elevation.TokenIsElevated;
853 if (ERROR_SUCCESS != dwError) {
854 LPSTR mBuf = nullptr;
855 if (!FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
856 nullptr, dwError, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), mBuf, 0, nullptr))
857 BOOST_THROW_EXCEPTION(std::runtime_error("Failed to format error message, last error was: " + dwError));
859 BOOST_THROW_EXCEPTION(std::runtime_error(mBuf));
867 * Handler for unhandled exceptions.
869 void Application::ExceptionHandler()
871 if (l_InExceptionHandler)
875 l_InExceptionHandler = true;
879 memset(&sa, 0, sizeof(sa));
880 sa.sa_handler = SIG_DFL;
881 sigaction(SIGABRT, &sa, nullptr);
884 String fname = GetCrashReportFilename();
885 String dirName = Utility::DirName(fname);
887 if (!Utility::PathExists(dirName)) {
889 if (mkdir(dirName.CStr(), 0700) < 0 && errno != EEXIST) {
891 if (mkdir(dirName.CStr()) < 0 && errno != EEXIST) {
893 std::cerr << "Could not create directory '" << dirName << "': Error " << errno << ", " << strerror(errno) << "\n";
897 bool interactive_debugger = Configuration::AttachDebugger;
899 if (!interactive_debugger) {
901 ofs.open(fname.CStr());
903 ofs << "Caught unhandled exception." << "\n"
904 << "Current time: " << Utility::FormatDateTime("%Y-%m-%d %H:%M:%S %z", Utility::GetTime()) << "\n"
907 DisplayInfoMessage(ofs);
910 RethrowUncaughtException();
911 } catch (const std::exception& ex) {
912 Log(LogCritical, "Application")
913 << DiagnosticInformation(ex, false) << "\n"
915 << "Additional information is available in '" << fname << "'" << "\n";
918 << DiagnosticInformation(ex)
922 DisplayBugMessage(ofs);
927 AttachDebugger(fname, interactive_debugger);
933 LONG CALLBACK Application::SEHUnhandledExceptionFilter(PEXCEPTION_POINTERS exi)
935 if (l_InExceptionHandler)
936 return EXCEPTION_CONTINUE_SEARCH;
938 l_InExceptionHandler = true;
940 String fname = GetCrashReportFilename();
941 String dirName = Utility::DirName(fname);
943 if (!Utility::PathExists(dirName)) {
945 if (mkdir(dirName.CStr(), 0700) < 0 && errno != EEXIST) {
947 if (mkdir(dirName.CStr()) < 0 && errno != EEXIST) {
949 std::cerr << "Could not create directory '" << dirName << "': Error " << errno << ", " << strerror(errno) << "\n";
954 ofs.open(fname.CStr());
956 Log(LogCritical, "Application")
957 << "Icinga 2 has terminated unexpectedly. Additional information can be found in '" << fname << "'";
959 DisplayInfoMessage(ofs);
961 ofs << "Caught unhandled SEH exception." << "\n"
962 << "Current time: " << Utility::FormatDateTime("%Y-%m-%d %H:%M:%S %z", Utility::GetTime()) << "\n"
965 StackTrace trace(exi);
966 ofs << "Stacktrace:" << "\n";
969 DisplayBugMessage(ofs);
971 return EXCEPTION_CONTINUE_SEARCH;
976 * Installs the exception handlers.
978 void Application::InstallExceptionHandlers()
980 std::set_terminate(&Application::ExceptionHandler);
984 memset(&sa, 0, sizeof(sa));
985 sa.sa_handler = &Application::SigAbrtHandler;
986 sigaction(SIGABRT, &sa, nullptr);
988 SetUnhandledExceptionFilter(&Application::SEHUnhandledExceptionFilter);
993 * Runs the application.
995 * @returns The application's exit code.
997 int Application::Run()
1000 struct sigaction sa;
1001 memset(&sa, 0, sizeof(sa));
1002 sa.sa_handler = &Application::SigIntTermHandler;
1003 sigaction(SIGINT, &sa, nullptr);
1004 sigaction(SIGTERM, &sa, nullptr);
1006 sa.sa_handler = &Application::SigUsr1Handler;
1007 sigaction(SIGUSR1, &sa, nullptr);
1009 sa.sa_handler = &Application::SigUsr2Handler;
1010 sigaction(SIGUSR2, &sa, nullptr);
1012 SetConsoleCtrlHandler(&Application::CtrlHandler, TRUE);
1016 UpdatePidFile(Configuration::PidPath);
1017 } catch (const std::exception&) {
1018 Log(LogCritical, "Application")
1019 << "Cannot update PID file '" << Configuration::PidPath << "'. Aborting.";
1020 return EXIT_FAILURE;
1023 SetMainTime(Utility::GetTime());
1028 void Application::UpdatePidFile(const String& filename)
1030 UpdatePidFile(filename, Utility::GetPid());
1034 * Grabs the PID file lock and updates the PID. Terminates the application
1035 * if the PID file is already locked by another instance of the application.
1037 * @param filename The name of the PID file.
1038 * @param pid The PID to write; default is the current PID
1040 void Application::UpdatePidFile(const String& filename, pid_t pid)
1042 ObjectLock olock(this);
1047 /* There's just no sane way of getting a file descriptor for a
1048 * C++ ofstream which is why we're using FILEs here. */
1049 m_PidFile = fopen(filename.CStr(), "r+");
1052 m_PidFile = fopen(filename.CStr(), "w");
1055 Log(LogCritical, "Application")
1056 << "Could not open PID file '" << filename << "'.";
1057 BOOST_THROW_EXCEPTION(std::runtime_error("Could not open PID file '" + filename + "'"));
1061 int fd = fileno(m_PidFile);
1063 Utility::SetCloExec(fd);
1069 lock.l_type = F_WRLCK;
1070 lock.l_whence = SEEK_SET;
1072 if (fcntl(fd, F_SETLK, &lock) < 0) {
1073 Log(LogCritical, "Application", "Could not lock PID file. Make sure that only one instance of the application is running.");
1075 Application::Exit(EXIT_FAILURE);
1078 if (ftruncate(fd, 0) < 0) {
1079 Log(LogCritical, "Application")
1080 << "ftruncate() failed with error code " << errno << ", \"" << Utility::FormatErrorNumber(errno) << "\"";
1082 BOOST_THROW_EXCEPTION(posix_error()
1083 << boost::errinfo_api_function("ftruncate")
1084 << boost::errinfo_errno(errno));
1088 fprintf(m_PidFile, "%lu\n", (unsigned long)pid);
1093 * Closes the PID file. Does nothing if the PID file is not currently open.
1095 void Application::ClosePidFile(bool unlink)
1097 ObjectLock olock(this);
1101 String pidpath = Configuration::PidPath;
1102 ::unlink(pidpath.CStr());
1108 m_PidFile = nullptr;
1112 * Checks if another process currently owns the pidfile and read it
1114 * @param filename The name of the PID file.
1115 * @returns 0: no process owning the pidfile, pid of the process otherwise
1117 pid_t Application::ReadPidFile(const String& filename)
1119 FILE *pidfile = fopen(filename.CStr(), "r");
1125 int fd = fileno(pidfile);
1131 lock.l_type = F_WRLCK;
1132 lock.l_whence = SEEK_SET;
1134 if (fcntl(fd, F_GETLK, &lock) < 0) {
1137 BOOST_THROW_EXCEPTION(posix_error()
1138 << boost::errinfo_api_function("fcntl")
1139 << boost::errinfo_errno(error));
1142 if (lock.l_type == F_UNLCK) {
1143 // nobody has locked the file: no icinga running
1150 int res = fscanf(pidfile, "%d", &runningpid);
1158 HANDLE hProcess = OpenProcess(0, FALSE, runningpid);
1163 CloseHandle(hProcess);
1169 int Application::GetDefaultRLimitFiles()
1174 int Application::GetDefaultRLimitProcesses()
1179 int Application::GetDefaultRLimitStack()
1184 double Application::GetReloadTimeout()
1186 return ScriptGlobal::Get("ReloadTimeout");
1190 * Returns the global thread pool.
1192 * @returns The global thread pool.
1194 ThreadPool& Application::GetTP()
1196 static ThreadPool tp;
1200 double Application::GetStartTime()
1205 void Application::SetStartTime(double ts)
1210 double Application::GetMainTime()
1215 void Application::SetMainTime(double ts)
1220 bool Application::GetScriptDebuggerEnabled()
1222 return m_ScriptDebuggerEnabled;
1225 void Application::SetScriptDebuggerEnabled(bool enabled)
1227 m_ScriptDebuggerEnabled = enabled;
1230 double Application::GetLastReloadFailed()
1232 return m_LastReloadFailed;
1235 void Application::SetLastReloadFailed(double ts)
1237 m_LastReloadFailed = ts;
1240 void Application::ValidateName(const Lazy<String>& lvalue, const ValidationUtils& utils)
1242 ObjectImpl<Application>::ValidateName(lvalue, utils);
1244 if (lvalue() != "app")
1245 BOOST_THROW_EXCEPTION(ValidationError(this, { "name" }, "Application object must be named 'app'."));