/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ #include "base/application.hpp" #include "base/application-ti.cpp" #include "base/stacktrace.hpp" #include "base/timer.hpp" #include "base/logger.hpp" #include "base/exception.hpp" #include "base/objectlock.hpp" #include "base/utility.hpp" #include "base/loader.hpp" #include "base/debug.hpp" #include "base/type.hpp" #include "base/convert.hpp" #include "base/scriptglobal.hpp" #include "base/process.hpp" #include "base/tlsutility.hpp" #include #include #include #include #include #include #include #include #include #ifdef __linux__ #include #endif /* __linux__ */ #ifdef _WIN32 #include #else /* _WIN32 */ #include #endif /* _WIN32 */ using namespace icinga; #ifdef _WIN32 /* MSVC throws unhandled C++ exceptions as SEH exceptions with this specific error code. * There seems to be no system header that actually defines this constant. * See also https://devblogs.microsoft.com/oldnewthing/20160915-00/?p=94316 */ #define EXCEPTION_CODE_CXX_EXCEPTION 0xe06d7363 #endif /* _WIN32 */ REGISTER_TYPE(Application); boost::signals2::signal Application::OnReopenLogs; Application::Ptr Application::m_Instance = nullptr; bool Application::m_ShuttingDown = false; bool Application::m_RequestRestart = false; bool Application::m_RequestReopenLogs = false; pid_t Application::m_ReloadProcess = 0; #ifndef _WIN32 pid_t Application::m_UmbrellaProcess = 0; #endif /* _WIN32 */ static bool l_Restarting = false; static bool l_InExceptionHandler = false; int Application::m_ArgC; char **Application::m_ArgV; double Application::m_StartTime; bool Application::m_ScriptDebuggerEnabled = false; #ifdef _WIN32 double Application::m_LastReloadFailed = 0; #else /* _WIN32 */ SharedMemory Application::m_LastReloadFailed (0); #endif /* _WIN32 */ #ifdef _WIN32 static LPTOP_LEVEL_EXCEPTION_FILTER l_DefaultUnhandledExceptionFilter = nullptr; #endif /* _WIN32 */ /** * Constructor for the Application class. */ void Application::OnConfigLoaded() { m_PidFile = nullptr; ASSERT(m_Instance == nullptr); m_Instance = this; } /** * Destructor for the application class. */ void Application::Stop(bool runtimeRemoved) { m_ShuttingDown = true; #ifdef _WIN32 WSACleanup(); #endif /* _WIN32 */ #ifdef _WIN32 ClosePidFile(true); #endif /* _WIN32 */ ObjectImpl::Stop(runtimeRemoved); } Application::~Application() { m_Instance = nullptr; } void Application::Exit(int rc) { std::cout.flush(); std::cerr.flush(); for (const Logger::Ptr& logger : Logger::GetLoggers()) { logger->Flush(); } UninitializeBase(); _exit(rc); // Yay, our static destructors are pretty much beyond repair at this point. } void Application::InitializeBase() { #ifdef _WIN32 /* disable GUI-based error messages for LoadLibrary() */ SetErrorMode(SEM_FAILCRITICALERRORS); WSADATA wsaData; if (WSAStartup(MAKEWORD(1, 1), &wsaData) != 0) { BOOST_THROW_EXCEPTION(win32_error() << boost::errinfo_api_function("WSAStartup") << errinfo_win32_error(WSAGetLastError())); } #else /* _WIN32 */ struct sigaction sa; memset(&sa, 0, sizeof(sa)); sa.sa_handler = SIG_IGN; sigaction(SIGPIPE, &sa, nullptr); #endif /* _WIN32 */ Loader::ExecuteDeferredInitializers(); /* Make sure the thread pool gets initialized. */ GetTP().Start(); /* Make sure the timer thread gets initialized. */ Timer::Initialize(); } void Application::UninitializeBase() { Timer::Uninitialize(); GetTP().Stop(); } /** * Retrieves a pointer to the application singleton object. * * @returns The application object. */ Application::Ptr Application::GetInstance() { return m_Instance; } void Application::SetResourceLimits() { #ifdef __linux__ rlimit rl; # ifdef RLIMIT_NOFILE rlim_t fileLimit = Configuration::RLimitFiles; if (fileLimit != 0) { if (fileLimit < (rlim_t)GetDefaultRLimitFiles()) { Log(LogWarning, "Application") << "The user-specified value for RLimitFiles cannot be smaller than the default value (" << GetDefaultRLimitFiles() << "). Using the default value instead."; fileLimit = GetDefaultRLimitFiles(); } rl.rlim_cur = fileLimit; rl.rlim_max = rl.rlim_cur; if (setrlimit(RLIMIT_NOFILE, &rl) < 0) Log(LogWarning, "Application") << "Failed to adjust resource limit for open file handles (RLIMIT_NOFILE) with error \"" << strerror(errno) << "\""; # else /* RLIMIT_NOFILE */ Log(LogNotice, "Application", "System does not support adjusting the resource limit for open file handles (RLIMIT_NOFILE)"); # endif /* RLIMIT_NOFILE */ } # ifdef RLIMIT_NPROC rlim_t processLimit = Configuration::RLimitProcesses; if (processLimit != 0) { if (processLimit < (rlim_t)GetDefaultRLimitProcesses()) { Log(LogWarning, "Application") << "The user-specified value for RLimitProcesses cannot be smaller than the default value (" << GetDefaultRLimitProcesses() << "). Using the default value instead."; processLimit = GetDefaultRLimitProcesses(); } rl.rlim_cur = processLimit; rl.rlim_max = rl.rlim_cur; if (setrlimit(RLIMIT_NPROC, &rl) < 0) Log(LogWarning, "Application") << "Failed adjust resource limit for number of processes (RLIMIT_NPROC) with error \"" << strerror(errno) << "\""; # else /* RLIMIT_NPROC */ Log(LogNotice, "Application", "System does not support adjusting the resource limit for number of processes (RLIMIT_NPROC)"); # endif /* RLIMIT_NPROC */ } # ifdef RLIMIT_STACK int argc = Application::GetArgC(); char **argv = Application::GetArgV(); bool set_stack_rlimit = true; for (int i = 0; i < argc; i++) { if (strcmp(argv[i], "--no-stack-rlimit") == 0) { set_stack_rlimit = false; break; } } if (getrlimit(RLIMIT_STACK, &rl) < 0) { Log(LogWarning, "Application", "Could not determine resource limit for stack size (RLIMIT_STACK)"); rl.rlim_max = RLIM_INFINITY; } rlim_t stackLimit; stackLimit = Configuration::RLimitStack; if (stackLimit != 0) { if (stackLimit < (rlim_t)GetDefaultRLimitStack()) { Log(LogWarning, "Application") << "The user-specified value for RLimitStack cannot be smaller than the default value (" << GetDefaultRLimitStack() << "). Using the default value instead."; stackLimit = GetDefaultRLimitStack(); } if (set_stack_rlimit) rl.rlim_cur = stackLimit; else rl.rlim_cur = rl.rlim_max; if (setrlimit(RLIMIT_STACK, &rl) < 0) Log(LogWarning, "Application") << "Failed adjust resource limit for stack size (RLIMIT_STACK) with error \"" << strerror(errno) << "\""; else if (set_stack_rlimit) { char **new_argv = static_cast(malloc(sizeof(char *) * (argc + 2))); if (!new_argv) { perror("malloc"); Exit(EXIT_FAILURE); } new_argv[0] = argv[0]; new_argv[1] = strdup("--no-stack-rlimit"); if (!new_argv[1]) { perror("strdup"); exit(1); } for (int i = 1; i < argc; i++) new_argv[i + 1] = argv[i]; new_argv[argc + 1] = nullptr; (void) execvp(new_argv[0], new_argv); perror("execvp"); _exit(EXIT_FAILURE); } # else /* RLIMIT_STACK */ Log(LogNotice, "Application", "System does not support adjusting the resource limit for stack size (RLIMIT_STACK)"); # endif /* RLIMIT_STACK */ } #endif /* __linux__ */ } int Application::GetArgC() { return m_ArgC; } void Application::SetArgC(int argc) { m_ArgC = argc; } char **Application::GetArgV() { return m_ArgV; } void Application::SetArgV(char **argv) { m_ArgV = argv; } /** * Processes events for registered sockets and timers and calls whatever * handlers have been set up for these events. */ void Application::RunEventLoop() { double lastLoop = Utility::GetTime(); while (!m_ShuttingDown) { if (m_RequestRestart) { m_RequestRestart = false; // we are now handling the request, once is enough #ifdef _WIN32 // are we already restarting? ignore request if we already are if (!l_Restarting) { l_Restarting = true; m_ReloadProcess = StartReloadProcess(); } #else /* _WIN32 */ Log(LogNotice, "Application") << "Got reload command, forwarding to umbrella process (PID " << m_UmbrellaProcess << ")"; (void)kill(m_UmbrellaProcess, SIGHUP); #endif /* _WIN32 */ } else { /* Watches for changes to the system time. Adjusts timers if necessary. */ Utility::Sleep(2.5); if (m_RequestReopenLogs) { Log(LogNotice, "Application", "Reopening log files"); m_RequestReopenLogs = false; OnReopenLogs(); } double now = Utility::GetTime(); double timeDiff = lastLoop - now; if (std::fabs(timeDiff) > 15) { /* We made a significant jump in time. */ Log(LogInformation, "Application") << "We jumped " << (timeDiff < 0 ? "forward" : "backward") << " in time: " << std::fabs(timeDiff) << " seconds"; Timer::AdjustTimers(-timeDiff); } lastLoop = now; } } Log(LogInformation, "Application", "Shutting down..."); ConfigObject::StopObjects(); Application::GetInstance()->OnShutdown(); #ifdef I2_DEBUG UninitializeBase(); // Inspired from Exit() #endif /* I2_DEBUG */ } bool Application::IsShuttingDown() { return m_ShuttingDown; } bool Application::IsRestarting() { return l_Restarting; } void Application::OnShutdown() { /* Nothing to do here. */ } static void ReloadProcessCallbackInternal(const ProcessResult& pr) { if (pr.ExitStatus != 0) { Application::SetLastReloadFailed(Utility::GetTime()); Log(LogCritical, "Application", "Found error in config: reloading aborted"); } #ifdef _WIN32 else Application::Exit(7); /* keep this exit code in sync with icinga-app */ #endif /* _WIN32 */ } static void ReloadProcessCallback(const ProcessResult& pr) { l_Restarting = false; std::thread t([pr]() { ReloadProcessCallbackInternal(pr); }); t.detach(); } pid_t Application::StartReloadProcess() { // prepare arguments ArrayData args; args.push_back(GetExePath(m_ArgV[0])); for (int i=1; i < Application::GetArgC(); i++) { if (std::string(Application::GetArgV()[i]) != "--reload-internal") args.push_back(Application::GetArgV()[i]); else i++; // the next parameter after --reload-internal is the pid, remove that too } #ifndef _WIN32 args.push_back("--reload-internal"); args.push_back(Convert::ToString(Utility::GetPid())); #else /* _WIN32 */ args.push_back("--validate"); #endif /* _WIN32 */ double reloadTimeout = Application::GetReloadTimeout(); Process::Ptr process = new Process(Process::PrepareCommand(new Array(std::move(args)))); process->SetTimeout(reloadTimeout); process->Run(&ReloadProcessCallback); Log(LogInformation, "Application") << "Got reload command: Started new instance with PID '" << (unsigned long)(process->GetPID()) << "' (timeout is " << reloadTimeout << "s)."; return process->GetPID(); } /** * Signals the application to shut down during the next * execution of the event loop. */ void Application::RequestShutdown() { Log(LogInformation, "Application", "Received request to shut down."); m_ShuttingDown = true; } /** * Signals the application to restart during the next * execution of the event loop. */ void Application::RequestRestart() { m_RequestRestart = true; } /** * Signals the application to reopen log files during the * next execution of the event loop. */ void Application::RequestReopenLogs() { m_RequestReopenLogs = true; } #ifndef _WIN32 /** * Sets the PID of the Icinga umbrella process. * * @param pid The PID of the Icinga umbrella process. */ void Application::SetUmbrellaProcess(pid_t pid) { m_UmbrellaProcess = pid; } #endif /* _WIN32 */ /** * Retrieves the full path of the executable. * * @param argv0 The first command-line argument. * @returns The path. */ String Application::GetExePath(const String& argv0) { String executablePath; #ifndef _WIN32 char buffer[MAXPATHLEN]; if (!getcwd(buffer, sizeof(buffer))) { BOOST_THROW_EXCEPTION(posix_error() << boost::errinfo_api_function("getcwd") << boost::errinfo_errno(errno)); } String workingDirectory = buffer; if (argv0[0] != '/') executablePath = workingDirectory + "/" + argv0; else executablePath = argv0; bool foundSlash = false; for (size_t i = 0; i < argv0.GetLength(); i++) { if (argv0[i] == '/') { foundSlash = true; break; } } if (!foundSlash) { String pathEnv = Utility::GetFromEnvironment("PATH"); if (!pathEnv.IsEmpty()) { std::vector paths = String(pathEnv).Split(":"); bool foundPath = false; for (const String& path : paths) { String pathTest = path + "/" + argv0; if (access(pathTest.CStr(), X_OK) == 0) { executablePath = pathTest; foundPath = true; break; } } if (!foundPath) { executablePath.Clear(); BOOST_THROW_EXCEPTION(std::runtime_error("Could not determine executable path.")); } } } if (!realpath(executablePath.CStr(), buffer)) { BOOST_THROW_EXCEPTION(posix_error() << boost::errinfo_api_function("realpath") << boost::errinfo_errno(errno) << boost::errinfo_file_name(executablePath)); } return buffer; #else /* _WIN32 */ char FullExePath[MAXPATHLEN]; if (!GetModuleFileName(nullptr, FullExePath, sizeof(FullExePath))) BOOST_THROW_EXCEPTION(win32_error() << boost::errinfo_api_function("GetModuleFileName") << errinfo_win32_error(GetLastError())); return FullExePath; #endif /* _WIN32 */ } /** * Display version and path information. */ void Application::DisplayInfoMessage(std::ostream& os, bool skipVersion) { /* icinga-app prints its own version information, stack traces need it here. */ if (!skipVersion) os << " Application version: " << GetAppVersion() << "\n\n"; os << "System information:\n" << " Platform: " << Utility::GetPlatformName() << "\n" << " Platform version: " << Utility::GetPlatformVersion() << "\n" << " Kernel: " << Utility::GetPlatformKernel() << "\n" << " Kernel version: " << Utility::GetPlatformKernelVersion() << "\n" << " Architecture: " << Utility::GetPlatformArchitecture() << "\n"; Namespace::Ptr systemNS = ScriptGlobal::Get("System"); os << "\nBuild information:\n" << " Compiler: " << systemNS->Get("BuildCompilerName") << " " << systemNS->Get("BuildCompilerVersion") << "\n" << " Build host: " << systemNS->Get("BuildHostName") << "\n" << " OpenSSL version: " << GetOpenSSLVersion() << "\n"; os << "\nApplication information:\n" << "\nGeneral paths:\n" << " Config directory: " << Configuration::ConfigDir << "\n" << " Data directory: " << Configuration::DataDir << "\n" << " Log directory: " << Configuration::LogDir << "\n" << " Cache directory: " << Configuration::CacheDir << "\n" << " Spool directory: " << Configuration::SpoolDir << "\n" << " Run directory: " << Configuration::InitRunDir << "\n" << "\nOld paths (deprecated):\n" << " Installation root: " << Configuration::PrefixDir << "\n" << " Sysconf directory: " << Configuration::SysconfDir << "\n" << " Run directory (base): " << Configuration::RunDir << "\n" << " Local state directory: " << Configuration::LocalStateDir << "\n" << "\nInternal paths:\n" << " Package data directory: " << Configuration::PkgDataDir << "\n" << " State path: " << Configuration::StatePath << "\n" << " Modified attributes path: " << Configuration::ModAttrPath << "\n" << " Objects path: " << Configuration::ObjectsPath << "\n" << " Vars path: " << Configuration::VarsPath << "\n" << " PID path: " << Configuration::PidPath << "\n"; } /** * Displays a message that tells users what to do when they encounter a bug. */ void Application::DisplayBugMessage(std::ostream& os) { os << "***" << "\n" << "* This would indicate a runtime problem or configuration error. If you believe this is a bug in Icinga 2" << "\n" << "* please submit a bug report at https://github.com/Icinga/icinga2 and include this stack trace as well as any other" << "\n" << "* information that might be useful in order to reproduce this problem." << "\n" << "***" << "\n"; } String Application::GetCrashReportFilename() { return Configuration::LogDir + "/crash/report." + Convert::ToString(Utility::GetTime()); } void Application::AttachDebugger(const String& filename, bool interactive) { #ifndef _WIN32 #ifdef __linux__ prctl(PR_SET_DUMPABLE, 1); #endif /* __linux __ */ String my_pid = Convert::ToString(Utility::GetPid()); pid_t pid = fork(); if (pid < 0) { BOOST_THROW_EXCEPTION(posix_error() << boost::errinfo_api_function("fork") << boost::errinfo_errno(errno)); } if (pid == 0) { if (!interactive) { int fd = open(filename.CStr(), O_CREAT | O_RDWR | O_APPEND, 0600); if (fd < 0) { BOOST_THROW_EXCEPTION(posix_error() << boost::errinfo_api_function("open") << boost::errinfo_errno(errno) << boost::errinfo_file_name(filename)); } if (fd != 1) { /* redirect stdout to the file */ dup2(fd, 1); close(fd); } /* redirect stderr to stdout */ if (fd != 2) close(2); dup2(1, 2); } char **argv; char *my_pid_str = strdup(my_pid.CStr()); if (interactive) { const char *uargv[] = { "gdb", "-p", my_pid_str, nullptr }; argv = const_cast(uargv); (void) execvp(argv[0], argv); } else { const char *uargv[] = { "gdb", "--batch", "-p", my_pid_str, "-ex", "thread apply all bt full", "-ex", "detach", "-ex", "quit", nullptr }; argv = const_cast(uargv); (void) execvp(argv[0], argv); } perror("Failed to launch GDB"); free(my_pid_str); _exit(0); } int status; if (waitpid(pid, &status, 0) < 0) { BOOST_THROW_EXCEPTION(posix_error() << boost::errinfo_api_function("waitpid") << boost::errinfo_errno(errno)); } #ifdef __linux__ prctl(PR_SET_DUMPABLE, 0); #endif /* __linux __ */ #else /* _WIN32 */ DebugBreak(); #endif /* _WIN32 */ } /** * Signal handler for SIGUSR1. This signal causes Icinga to re-open * its log files and is mainly for use by logrotate. * * @param - The signal number. */ void Application::SigUsr1Handler(int) { Log(LogInformation, "Application") << "Received USR1 signal, reopening application logs."; RequestReopenLogs(); } /** * Signal handler for SIGABRT. Helps with debugging ASSERT()s. * * @param - The signal number. */ void Application::SigAbrtHandler(int) { #ifndef _WIN32 struct sigaction sa; memset(&sa, 0, sizeof(sa)); sa.sa_handler = SIG_DFL; sigaction(SIGABRT, &sa, nullptr); #endif /* _WIN32 */ std::cerr << "Caught SIGABRT." << std::endl << "Current time: " << Utility::FormatDateTime("%Y-%m-%d %H:%M:%S %z", Utility::GetTime()) << std::endl << std::endl; String fname = GetCrashReportFilename(); String dirName = Utility::DirName(fname); if (!Utility::PathExists(dirName)) { #ifndef _WIN32 if (mkdir(dirName.CStr(), 0700) < 0 && errno != EEXIST) { #else /*_ WIN32 */ if (mkdir(dirName.CStr()) < 0 && errno != EEXIST) { #endif /* _WIN32 */ std::cerr << "Could not create directory '" << dirName << "': Error " << errno << ", " << strerror(errno) << "\n"; } } bool interactive_debugger = Configuration::AttachDebugger; if (!interactive_debugger) { std::ofstream ofs; ofs.open(fname.CStr()); Log(LogCritical, "Application") << "Icinga 2 has terminated unexpectedly. Additional information can be found in '" << fname << "'" << "\n"; ofs << "Caught SIGABRT.\n" << "Current time: " << Utility::FormatDateTime("%Y-%m-%d %H:%M:%S %z", Utility::GetTime()) << "\n\n"; DisplayInfoMessage(ofs); ofs << "\nStacktrace:\n" << StackTraceFormatter(boost::stacktrace::stacktrace()) << "\n"; DisplayBugMessage(ofs); ofs << "\n"; ofs.close(); } else { Log(LogCritical, "Application", "Icinga 2 has terminated unexpectedly. Attaching debugger..."); } AttachDebugger(fname, interactive_debugger); } #ifdef _WIN32 /** * Console control handler. Prepares the application for cleanly * shutting down during the next execution of the event loop. */ BOOL WINAPI Application::CtrlHandler(DWORD type) { Application::Ptr instance = Application::GetInstance(); if (!instance) return TRUE; instance->RequestShutdown(); SetConsoleCtrlHandler(nullptr, FALSE); return TRUE; } bool Application::IsProcessElevated() { BOOL fIsElevated = FALSE; DWORD dwError = ERROR_SUCCESS; HANDLE hToken = nullptr; if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hToken)) dwError = GetLastError(); else { TOKEN_ELEVATION elevation; DWORD dwSize; if (!GetTokenInformation(hToken, TokenElevation, &elevation, sizeof(elevation), &dwSize)) dwError = GetLastError(); else fIsElevated = elevation.TokenIsElevated; } if (hToken) { CloseHandle(hToken); hToken = nullptr; } if (ERROR_SUCCESS != dwError) { LPSTR mBuf = nullptr; if (!FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, nullptr, dwError, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), mBuf, 0, nullptr)) BOOST_THROW_EXCEPTION(std::runtime_error("Failed to format error message, last error was: " + dwError)); else BOOST_THROW_EXCEPTION(std::runtime_error(mBuf)); } return fIsElevated; } #endif /* _WIN32 */ /** * Handler for unhandled exceptions. */ void Application::ExceptionHandler() { if (l_InExceptionHandler) for (;;) Utility::Sleep(5); l_InExceptionHandler = true; #ifndef _WIN32 struct sigaction sa; memset(&sa, 0, sizeof(sa)); sa.sa_handler = SIG_DFL; sigaction(SIGABRT, &sa, nullptr); #endif /* _WIN32 */ String fname = GetCrashReportFilename(); String dirName = Utility::DirName(fname); if (!Utility::PathExists(dirName)) { #ifndef _WIN32 if (mkdir(dirName.CStr(), 0700) < 0 && errno != EEXIST) { #else /*_ WIN32 */ if (mkdir(dirName.CStr()) < 0 && errno != EEXIST) { #endif /* _WIN32 */ std::cerr << "Could not create directory '" << dirName << "': Error " << errno << ", " << strerror(errno) << "\n"; } } bool interactive_debugger = Configuration::AttachDebugger; if (!interactive_debugger) { std::ofstream ofs; ofs.open(fname.CStr()); ofs << "Caught unhandled exception.\n" << "Current time: " << Utility::FormatDateTime("%Y-%m-%d %H:%M:%S %z", Utility::GetTime()) << "\n\n"; DisplayInfoMessage(ofs); try { RethrowUncaughtException(); } catch (const std::exception& ex) { Log(LogCritical, "Application") << DiagnosticInformation(ex, false) << "\n" << "\n" << "Additional information is available in '" << fname << "'" << "\n"; /* On platforms where HAVE_CXXABI_H is defined, we prefer to print the stack trace that was saved * when the last exception was thrown. Everywhere else, we do not have this information so we * collect a stack trace here, which might lack some information, for example when an exception * is rethrown, but this is still better than nothing. */ boost::stacktrace::stacktrace *stack = nullptr; #ifndef HAVE_CXXABI_H boost::stacktrace::stacktrace local_stack; stack = &local_stack; #endif /* HAVE_CXXABI_H */ ofs << "\n" << DiagnosticInformation(ex, true, stack) << "\n"; } DisplayBugMessage(ofs); ofs.close(); } AttachDebugger(fname, interactive_debugger); abort(); } #ifdef _WIN32 LONG CALLBACK Application::SEHUnhandledExceptionFilter(PEXCEPTION_POINTERS exi) { /* If an unhandled C++ exception occurs with both a termination handler (std::set_terminate()) and an unhandled * SEH filter (SetUnhandledExceptionFilter()) set, the latter one is called. However, our termination handler is * better suited for dealing with C++ exceptions. In this case, the SEH exception will have a specific code and * we can just call the default filter function which will take care of calling the termination handler. */ if (exi->ExceptionRecord->ExceptionCode == EXCEPTION_CODE_CXX_EXCEPTION) { return l_DefaultUnhandledExceptionFilter(exi); } if (l_InExceptionHandler) return EXCEPTION_CONTINUE_SEARCH; l_InExceptionHandler = true; String fname = GetCrashReportFilename(); String dirName = Utility::DirName(fname); if (!Utility::PathExists(dirName)) { #ifndef _WIN32 if (mkdir(dirName.CStr(), 0700) < 0 && errno != EEXIST) { #else /*_ WIN32 */ if (mkdir(dirName.CStr()) < 0 && errno != EEXIST) { #endif /* _WIN32 */ std::cerr << "Could not create directory '" << dirName << "': Error " << errno << ", " << strerror(errno) << "\n"; } } std::ofstream ofs; ofs.open(fname.CStr()); Log(LogCritical, "Application") << "Icinga 2 has terminated unexpectedly. Additional information can be found in '" << fname << "'"; ofs << "Caught unhandled SEH exception.\n" << "Current time: " << Utility::FormatDateTime("%Y-%m-%d %H:%M:%S %z", Utility::GetTime()) << "\n\n"; DisplayInfoMessage(ofs); std::ios::fmtflags savedflags(ofs.flags()); ofs << std::showbase << std::hex << "\nSEH exception:\n" << " Code: " << exi->ExceptionRecord->ExceptionCode << "\n" << " Address: " << exi->ExceptionRecord->ExceptionAddress << "\n" << " Flags: " << exi->ExceptionRecord->ExceptionFlags << "\n"; ofs.flags(savedflags); ofs << "\nStacktrace:\n" << StackTraceFormatter(boost::stacktrace::stacktrace()) << "\n"; DisplayBugMessage(ofs); return EXCEPTION_CONTINUE_SEARCH; } #endif /* _WIN32 */ /** * Installs the exception handlers. */ void Application::InstallExceptionHandlers() { std::set_terminate(&Application::ExceptionHandler); #ifndef _WIN32 struct sigaction sa; memset(&sa, 0, sizeof(sa)); sa.sa_handler = &Application::SigAbrtHandler; sa.sa_flags = SA_RESTART; sigaction(SIGABRT, &sa, nullptr); #else /* _WIN32 */ l_DefaultUnhandledExceptionFilter = SetUnhandledExceptionFilter(&Application::SEHUnhandledExceptionFilter); #endif /* _WIN32 */ } /** * Runs the application. * * @returns The application's exit code. */ int Application::Run() { #ifndef _WIN32 struct sigaction sa; memset(&sa, 0, sizeof(sa)); sa.sa_handler = &Application::SigUsr1Handler; sa.sa_flags = SA_RESTART; sigaction(SIGUSR1, &sa, nullptr); #else /* _WIN32 */ SetConsoleCtrlHandler(&Application::CtrlHandler, TRUE); #endif /* _WIN32 */ #ifdef _WIN32 try { UpdatePidFile(Configuration::PidPath); } catch (const std::exception&) { Log(LogCritical, "Application") << "Cannot update PID file '" << Configuration::PidPath << "'. Aborting."; return EXIT_FAILURE; } #endif /* _WIN32 */ SetStartTime(Utility::GetTime()); return Main(); } void Application::UpdatePidFile(const String& filename) { UpdatePidFile(filename, Utility::GetPid()); } /** * Grabs the PID file lock and updates the PID. Terminates the application * if the PID file is already locked by another instance of the application. * * @param filename The name of the PID file. * @param pid The PID to write; default is the current PID */ void Application::UpdatePidFile(const String& filename, pid_t pid) { ObjectLock olock(this); if (m_PidFile) fclose(m_PidFile); /* There's just no sane way of getting a file descriptor for a * C++ ofstream which is why we're using FILEs here. */ m_PidFile = fopen(filename.CStr(), "r+"); if (!m_PidFile) m_PidFile = fopen(filename.CStr(), "w"); if (!m_PidFile) { Log(LogCritical, "Application") << "Could not open PID file '" << filename << "'."; BOOST_THROW_EXCEPTION(std::runtime_error("Could not open PID file '" + filename + "'")); } #ifndef _WIN32 int fd = fileno(m_PidFile); Utility::SetCloExec(fd); struct flock lock; lock.l_start = 0; lock.l_len = 0; lock.l_type = F_WRLCK; lock.l_whence = SEEK_SET; if (fcntl(fd, F_SETLK, &lock) < 0) { Log(LogCritical, "Application", "Could not lock PID file. Make sure that only one instance of the application is running."); Application::Exit(EXIT_FAILURE); } if (ftruncate(fd, 0) < 0) { Log(LogCritical, "Application") << "ftruncate() failed with error code " << errno << ", \"" << Utility::FormatErrorNumber(errno) << "\""; BOOST_THROW_EXCEPTION(posix_error() << boost::errinfo_api_function("ftruncate") << boost::errinfo_errno(errno)); } #endif /* _WIN32 */ fprintf(m_PidFile, "%lu\n", (unsigned long)pid); fflush(m_PidFile); } /** * Closes the PID file. Does nothing if the PID file is not currently open. */ void Application::ClosePidFile(bool unlink) { ObjectLock olock(this); if (m_PidFile) { if (unlink) { String pidpath = Configuration::PidPath; ::unlink(pidpath.CStr()); } fclose(m_PidFile); } m_PidFile = nullptr; } /** * Checks if another process currently owns the pidfile and read it * * @param filename The name of the PID file. * @returns 0: no process owning the pidfile, pid of the process otherwise */ pid_t Application::ReadPidFile(const String& filename) { FILE *pidfile = fopen(filename.CStr(), "r"); if (!pidfile) return 0; #ifndef _WIN32 int fd = fileno(pidfile); struct flock lock; lock.l_start = 0; lock.l_len = 0; lock.l_type = F_WRLCK; lock.l_whence = SEEK_SET; if (fcntl(fd, F_GETLK, &lock) < 0) { int error = errno; fclose(pidfile); BOOST_THROW_EXCEPTION(posix_error() << boost::errinfo_api_function("fcntl") << boost::errinfo_errno(error)); } if (lock.l_type == F_UNLCK) { // nobody has locked the file: no icinga running fclose(pidfile); return -1; } #endif /* _WIN32 */ pid_t runningpid; int res = fscanf(pidfile, "%d", &runningpid); fclose(pidfile); // bogus result? if (res != 1) return 0; #ifdef _WIN32 HANDLE hProcess = OpenProcess(0, FALSE, runningpid); if (!hProcess) return 0; CloseHandle(hProcess); #endif /* _WIN32 */ return runningpid; } int Application::GetDefaultRLimitFiles() { return 16 * 1024; } int Application::GetDefaultRLimitProcesses() { return 16 * 1024; } int Application::GetDefaultRLimitStack() { return 256 * 1024; } double Application::GetReloadTimeout() { return ScriptGlobal::Get("ReloadTimeout"); } /** * Returns the global thread pool. * * @returns The global thread pool. */ ThreadPool& Application::GetTP() { static ThreadPool tp; return tp; } double Application::GetStartTime() { return m_StartTime; } void Application::SetStartTime(double ts) { m_StartTime = ts; } double Application::GetUptime() { return Utility::GetTime() - m_StartTime; } bool Application::GetScriptDebuggerEnabled() { return m_ScriptDebuggerEnabled; } void Application::SetScriptDebuggerEnabled(bool enabled) { m_ScriptDebuggerEnabled = enabled; } double Application::GetLastReloadFailed() { #ifdef _WIN32 return m_LastReloadFailed; #else /* _WIN32 */ return m_LastReloadFailed.Get().load(); #endif /* _WIN32 */ } void Application::SetLastReloadFailed(double ts) { #ifdef _WIN32 m_LastReloadFailed = ts; #else /* _WIN32 */ m_LastReloadFailed.Get().store(ts); #endif /* _WIN32 */ } void Application::ValidateName(const Lazy& lvalue, const ValidationUtils& utils) { ObjectImpl::ValidateName(lvalue, utils); if (lvalue() != "app") BOOST_THROW_EXCEPTION(ValidationError(this, { "name" }, "Application object must be named 'app'.")); }