icinga2/lib/base/application.cpp
Julian Brost 15a16b23d9 Pass fallback stacktrace to DiagnosticInformation in terminate handler
By default, DiagnosticInformation uses the stack trace saved when the
exception was thrown, but this mechanism is not in use on Windows.
Gathering a stacktrace in the terminate handler serves as a fallback.
2021-03-01 14:06:47 +01:00

1226 lines
30 KiB
C++

/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
#include "base/application.hpp"
#include "base/application-ti.cpp"
#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;
double Application::m_LastReloadFailed;
#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();
#ifdef I2_DEBUG
exit(rc);
#else /* I2_DEBUG */
_exit(rc); // Yay, our static destructors are pretty much beyond repair at this point.
#endif /* I2_DEBUG */
}
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(std::bind(&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";
DisplayInfoMessage(ofs);
ofs << "\nStacktrace:\n" << 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" << 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;
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;
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()
{
return m_LastReloadFailed;
}
void Application::SetLastReloadFailed(double ts)
{
m_LastReloadFailed = ts;
}
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'."));
}