diff options
Diffstat (limited to 'lib/base/application.cpp')
-rw-r--r-- | lib/base/application.cpp | 1238 |
1 files changed, 1238 insertions, 0 deletions
diff --git a/lib/base/application.cpp b/lib/base/application.cpp new file mode 100644 index 0000000..89a0f55 --- /dev/null +++ b/lib/base/application.cpp @@ -0,0 +1,1238 @@ +/* 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 <boost/algorithm/string/trim.hpp> +#include <boost/exception/errinfo_api_function.hpp> +#include <boost/exception/errinfo_errno.hpp> +#include <boost/exception/errinfo_file_name.hpp> +#include <boost/stacktrace.hpp> +#include <sstream> +#include <iostream> +#include <fstream> +#include <thread> +#ifdef __linux__ +#include <sys/prctl.h> +#endif /* __linux__ */ +#ifdef _WIN32 +#include <windows.h> +#else /* _WIN32 */ +#include <signal.h> +#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<void ()> 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::AtomicTs> 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<Application>::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<char **>(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<String> 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<char **>(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<char **>(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<String>& lvalue, const ValidationUtils& utils) +{ + ObjectImpl<Application>::ValidateName(lvalue, utils); + + if (lvalue() != "app") + BOOST_THROW_EXCEPTION(ValidationError(this, { "name" }, "Application object must be named 'app'.")); +} |