diff options
Diffstat (limited to 'lib/base')
170 files changed, 24779 insertions, 0 deletions
diff --git a/lib/base/CMakeLists.txt b/lib/base/CMakeLists.txt new file mode 100644 index 0000000..e50e330 --- /dev/null +++ b/lib/base/CMakeLists.txt @@ -0,0 +1,160 @@ +# Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ + +mkclass_target(application.ti application-ti.cpp application-ti.hpp) +mkclass_target(configobject.ti configobject-ti.cpp configobject-ti.hpp) +mkclass_target(configuration.ti configuration-ti.cpp configuration-ti.hpp) +mkclass_target(datetime.ti datetime-ti.cpp datetime-ti.hpp) +mkclass_target(filelogger.ti filelogger-ti.cpp filelogger-ti.hpp) +mkclass_target(function.ti function-ti.cpp function-ti.hpp) +mkclass_target(journaldlogger.ti journaldlogger-ti.cpp journaldlogger-ti.hpp) +mkclass_target(logger.ti logger-ti.cpp logger-ti.hpp) +mkclass_target(perfdatavalue.ti perfdatavalue-ti.cpp perfdatavalue-ti.hpp) +mkclass_target(streamlogger.ti streamlogger-ti.cpp streamlogger-ti.hpp) +mkclass_target(sysloglogger.ti sysloglogger-ti.cpp sysloglogger-ti.hpp) + +set(base_SOURCES + i2-base.hpp + application.cpp application.hpp application-ti.hpp application-version.cpp application-environment.cpp + array.cpp array.hpp array-script.cpp + atomic.hpp + atomic-file.cpp atomic-file.hpp + base64.cpp base64.hpp + boolean.cpp boolean.hpp boolean-script.cpp + bulker.hpp + configobject.cpp configobject.hpp configobject-ti.hpp configobject-script.cpp + configtype.cpp configtype.hpp + configuration.cpp configuration.hpp configuration-ti.hpp + configwriter.cpp configwriter.hpp + console.cpp console.hpp + context.cpp context.hpp + convert.cpp convert.hpp + datetime.cpp datetime.hpp datetime-ti.hpp datetime-script.cpp + debug.hpp + debuginfo.cpp debuginfo.hpp + dependencygraph.cpp dependencygraph.hpp + dictionary.cpp dictionary.hpp dictionary-script.cpp + exception.cpp exception.hpp + fifo.cpp fifo.hpp + filelogger.cpp filelogger.hpp filelogger-ti.hpp + function.cpp function.hpp function-ti.hpp function-script.cpp functionwrapper.hpp + initialize.cpp initialize.hpp + io-engine.cpp io-engine.hpp + journaldlogger.cpp journaldlogger.hpp journaldlogger-ti.hpp + json.cpp json.hpp json-script.cpp + lazy-init.hpp + library.cpp library.hpp + loader.cpp loader.hpp + logger.cpp logger.hpp logger-ti.hpp + math-script.cpp + netstring.cpp netstring.hpp + networkstream.cpp networkstream.hpp + namespace.cpp namespace.hpp namespace-script.cpp + number.cpp number.hpp number-script.cpp + object.cpp object.hpp object-script.cpp + objectlock.cpp objectlock.hpp + object-packer.cpp object-packer.hpp + objecttype.cpp objecttype.hpp + perfdatavalue.cpp perfdatavalue.hpp perfdatavalue-ti.hpp + primitivetype.cpp primitivetype.hpp + process.cpp process.hpp + reference.cpp reference.hpp reference-script.cpp + registry.hpp + ringbuffer.cpp ringbuffer.hpp + scriptframe.cpp scriptframe.hpp + scriptglobal.cpp scriptglobal.hpp + scriptutils.cpp scriptutils.hpp + serializer.cpp serializer.hpp + shared.hpp + shared-memory.hpp + shared-object.hpp + singleton.hpp + socket.cpp socket.hpp + stacktrace.cpp stacktrace.hpp + statsfunction.hpp + stdiostream.cpp stdiostream.hpp + stream.cpp stream.hpp + streamlogger.cpp streamlogger.hpp streamlogger-ti.hpp + string.cpp string.hpp string-script.cpp + sysloglogger.cpp sysloglogger.hpp sysloglogger-ti.hpp + tcpsocket.cpp tcpsocket.hpp + threadpool.cpp threadpool.hpp + timer.cpp timer.hpp + tlsstream.cpp tlsstream.hpp + tlsutility.cpp tlsutility.hpp + type.cpp type.hpp typetype-script.cpp + unix.hpp + unixsocket.cpp unixsocket.hpp + utility.cpp utility.hpp + value.cpp value.hpp value-operators.cpp + win32.hpp + workqueue.cpp workqueue.hpp +) + +if(WIN32) + mkclass_target(windowseventloglogger.ti windowseventloglogger-ti.cpp windowseventloglogger-ti.hpp) + list(APPEND base_SOURCES windowseventloglogger.cpp windowseventloglogger.hpp windowseventloglogger-ti.hpp) + + # Generate a DLL containing message definitions for the Windows Event Viewer. + # See also: https://docs.microsoft.com/en-us/windows/win32/eventlog/reporting-an-event + add_custom_command( + OUTPUT windowseventloglogger-provider.rc windowseventloglogger-provider.h + COMMAND mc ARGS -U ${CMAKE_CURRENT_SOURCE_DIR}/windowseventloglogger-provider.mc + DEPENDS windowseventloglogger-provider.mc + ) + + list(APPEND base_SOURCES windowseventloglogger-provider.h) + + add_custom_command( + OUTPUT windowseventloglogger-provider.res + COMMAND rc ARGS windowseventloglogger-provider.rc + DEPENDS windowseventloglogger-provider.rc + ) + + add_library(eventprovider MODULE windowseventloglogger-provider.res windowseventloglogger-provider.rc) + set_target_properties(eventprovider PROPERTIES LINKER_LANGUAGE CXX) + target_link_libraries(eventprovider PRIVATE -noentry) + + install(TARGETS eventprovider LIBRARY DESTINATION ${CMAKE_INSTALL_SBINDIR}) +endif() + +set_property( + SOURCE ${CMAKE_CURRENT_SOURCE_DIR}/application-version.cpp ${CMAKE_CURRENT_SOURCE_DIR}/journaldlogger.cpp + PROPERTY EXCLUDE_UNITY_BUILD TRUE +) + +if(ICINGA2_UNITY_BUILD) + mkunity_target(base base base_SOURCES) +endif() + +if(HAVE_SYSTEMD) + find_path(SYSTEMD_INCLUDE_DIR + NAMES systemd/sd-daemon.h + HINTS ${SYSTEMD_ROOT_DIR}) + include_directories(${SYSTEMD_INCLUDE_DIR}) + set_property( + SOURCE ${CMAKE_CURRENT_SOURCE_DIR}/journaldlogger.cpp + APPEND PROPERTY COMPILE_DEFINITIONS + SD_JOURNAL_SUPPRESS_LOCATION + ) +endif() + +add_library(base OBJECT ${base_SOURCES}) + +include_directories(${icinga2_SOURCE_DIR}/third-party/execvpe) +link_directories(${icinga2_BINARY_DIR}/third-party/execvpe) + +include_directories(${icinga2_SOURCE_DIR}/third-party/mmatch) +link_directories(${icinga2_BINARY_DIR}/third-party/mmatch) + +include_directories(${icinga2_SOURCE_DIR}/third-party/socketpair) +link_directories(${icinga2_BINARY_DIR}/third-party/socketpair) + +set_target_properties ( + base PROPERTIES + FOLDER Lib +) + +install(CODE "file(MAKE_DIRECTORY \"\$ENV{DESTDIR}${ICINGA2_FULL_CACHEDIR}\")") +install(CODE "file(MAKE_DIRECTORY \"\$ENV{DESTDIR}${ICINGA2_FULL_LOGDIR}/crash\")") + +set(CPACK_NSIS_EXTRA_INSTALL_COMMANDS "${CPACK_NSIS_EXTRA_INSTALL_COMMANDS}" PARENT_SCOPE) diff --git a/lib/base/application-environment.cpp b/lib/base/application-environment.cpp new file mode 100644 index 0000000..819783f --- /dev/null +++ b/lib/base/application-environment.cpp @@ -0,0 +1,17 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#include "base/application.hpp" +#include "base/scriptglobal.hpp" + +using namespace icinga; + +String Application::GetAppEnvironment() +{ + Value defaultValue = Empty; + return ScriptGlobal::Get("Environment", &defaultValue); +} + +void Application::SetAppEnvironment(const String& name) +{ + ScriptGlobal::Set("Environment", name); +} diff --git a/lib/base/application-version.cpp b/lib/base/application-version.cpp new file mode 100644 index 0000000..d17775b --- /dev/null +++ b/lib/base/application-version.cpp @@ -0,0 +1,17 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#include "base/application.hpp" +#include "icinga-version.h" +#include "icinga-spec-version.h" + +using namespace icinga; + +String Application::GetAppVersion() +{ + return VERSION; +} + +String Application::GetAppSpecVersion() +{ + return SPEC_VERSION; +} 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'.")); +} diff --git a/lib/base/application.hpp b/lib/base/application.hpp new file mode 100644 index 0000000..f45c8bd --- /dev/null +++ b/lib/base/application.hpp @@ -0,0 +1,170 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#ifndef APPLICATION_H +#define APPLICATION_H + +#include "base/i2-base.hpp" +#include "base/atomic.hpp" +#include "base/application-ti.hpp" +#include "base/logger.hpp" +#include "base/configuration.hpp" +#include "base/shared-memory.hpp" +#include <cstdint> +#include <iosfwd> +#include <type_traits> + +namespace icinga +{ + +class ThreadPool; + +/** + * Abstract base class for applications. + * + * @ingroup base + */ +class Application : public ObjectImpl<Application> { +public: + DECLARE_OBJECT(Application); + + static boost::signals2::signal<void ()> OnReopenLogs; + + ~Application() override; + + static void InitializeBase(); + static void UninitializeBase(); + + static Application::Ptr GetInstance(); + + static void Exit(int rc); + + int Run(); + + /** + * Starts the application. + * + * @returns The exit code of the application. + */ + virtual int Main() = 0; + + static void SetResourceLimits(); + + static int GetArgC(); + static void SetArgC(int argc); + + static char **GetArgV(); + static void SetArgV(char **argv); + + static void InstallExceptionHandlers(); + + static void RequestShutdown(); + static void RequestRestart(); + static void RequestReopenLogs(); + +#ifndef _WIN32 + static void SetUmbrellaProcess(pid_t pid); +#endif /* _WIN32 */ + + static bool IsShuttingDown(); + static bool IsRestarting(); + + static void SetDebuggingSeverity(LogSeverity severity); + static LogSeverity GetDebuggingSeverity(); + + void UpdatePidFile(const String& filename); + void UpdatePidFile(const String& filename, pid_t pid); + void ClosePidFile(bool unlink); + static pid_t ReadPidFile(const String& filename); + + static String GetExePath(const String& argv0); + +#ifdef _WIN32 + static bool IsProcessElevated(); +#endif /* _WIN32 */ + + static int GetDefaultRLimitFiles(); + static int GetDefaultRLimitProcesses(); + static int GetDefaultRLimitStack(); + + static double GetReloadTimeout(); + + static ThreadPool& GetTP(); + + static String GetAppVersion(); + static String GetAppSpecVersion(); + + static String GetAppEnvironment(); + static void SetAppEnvironment(const String& name); + + static double GetStartTime(); + static void SetStartTime(double ts); + + static double GetUptime(); + + static bool GetScriptDebuggerEnabled(); + static void SetScriptDebuggerEnabled(bool enabled); + + static double GetLastReloadFailed(); + static void SetLastReloadFailed(double ts); + + static void DisplayInfoMessage(std::ostream& os, bool skipVersion = false); + +protected: + void OnConfigLoaded() override; + void Stop(bool runtimeRemoved) override; + + void RunEventLoop(); + + pid_t StartReloadProcess(); + + virtual void OnShutdown(); + + void ValidateName(const Lazy<String>& lvalue, const ValidationUtils& utils) final; + +private: + static Application::Ptr m_Instance; /**< The application instance. */ + + static bool m_ShuttingDown; /**< Whether the application is in the process of shutting down. */ + static bool m_RequestRestart; /**< A restart was requested through SIGHUP */ + static pid_t m_ReloadProcess; /**< The PID of a subprocess doing a reload, only valid when l_Restarting==true */ + static bool m_RequestReopenLogs; /**< Whether we should re-open log files. */ + +#ifndef _WIN32 + static pid_t m_UmbrellaProcess; /**< The PID of the Icinga umbrella process */ +#endif /* _WIN32 */ + + static int m_ArgC; /**< The number of command-line arguments. */ + static char **m_ArgV; /**< Command-line arguments. */ + FILE *m_PidFile = nullptr; /**< The PID file */ + static bool m_Debugging; /**< Whether debugging is enabled. */ + static LogSeverity m_DebuggingSeverity; /**< Whether debugging severity is set. */ + static double m_StartTime; + static double m_MainTime; + static bool m_ScriptDebuggerEnabled; +#ifdef _WIN32 + static double m_LastReloadFailed; +#else /* _WIN32 */ + typedef Atomic<std::conditional_t<Atomic<double>::is_always_lock_free, double, uint32_t>> AtomicTs; + static_assert(AtomicTs::is_always_lock_free); + static SharedMemory<AtomicTs> m_LastReloadFailed; +#endif /* _WIN32 */ + +#ifdef _WIN32 + static BOOL WINAPI CtrlHandler(DWORD type); + static LONG WINAPI SEHUnhandledExceptionFilter(PEXCEPTION_POINTERS exi); +#endif /* _WIN32 */ + + static void DisplayBugMessage(std::ostream& os); + + static void SigAbrtHandler(int signum); + static void SigUsr1Handler(int signum); + static void ExceptionHandler(); + + static String GetCrashReportFilename(); + + static void AttachDebugger(const String& filename, bool interactive); +}; + +} + +#endif /* APPLICATION_H */ diff --git a/lib/base/application.ti b/lib/base/application.ti new file mode 100644 index 0000000..3d5908a --- /dev/null +++ b/lib/base/application.ti @@ -0,0 +1,14 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#include "base/configobject.hpp" + +library base; + +namespace icinga +{ + +abstract class Application : ConfigObject +{ +}; + +} diff --git a/lib/base/array-script.cpp b/lib/base/array-script.cpp new file mode 100644 index 0000000..a976683 --- /dev/null +++ b/lib/base/array-script.cpp @@ -0,0 +1,260 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#include "base/array.hpp" +#include "base/function.hpp" +#include "base/functionwrapper.hpp" +#include "base/scriptframe.hpp" +#include "base/objectlock.hpp" +#include "base/exception.hpp" + +using namespace icinga; + +static double ArrayLen() +{ + ScriptFrame *vframe = ScriptFrame::GetCurrentFrame(); + Array::Ptr self = static_cast<Array::Ptr>(vframe->Self); + REQUIRE_NOT_NULL(self); + return self->GetLength(); +} + +static void ArraySet(int index, const Value& value) +{ + ScriptFrame *vframe = ScriptFrame::GetCurrentFrame(); + Array::Ptr self = static_cast<Array::Ptr>(vframe->Self); + REQUIRE_NOT_NULL(self); + self->Set(index, value); +} + +static Value ArrayGet(int index) +{ + ScriptFrame *vframe = ScriptFrame::GetCurrentFrame(); + Array::Ptr self = static_cast<Array::Ptr>(vframe->Self); + REQUIRE_NOT_NULL(self); + return self->Get(index); +} + +static void ArrayAdd(const Value& value) +{ + ScriptFrame *vframe = ScriptFrame::GetCurrentFrame(); + Array::Ptr self = static_cast<Array::Ptr>(vframe->Self); + REQUIRE_NOT_NULL(self); + self->Add(value); +} + +static void ArrayRemove(int index) +{ + ScriptFrame *vframe = ScriptFrame::GetCurrentFrame(); + Array::Ptr self = static_cast<Array::Ptr>(vframe->Self); + REQUIRE_NOT_NULL(self); + self->Remove(index); +} + +static bool ArrayContains(const Value& value) +{ + ScriptFrame *vframe = ScriptFrame::GetCurrentFrame(); + Array::Ptr self = static_cast<Array::Ptr>(vframe->Self); + REQUIRE_NOT_NULL(self); + return self->Contains(value); +} + +static void ArrayClear() +{ + ScriptFrame *vframe = ScriptFrame::GetCurrentFrame(); + Array::Ptr self = static_cast<Array::Ptr>(vframe->Self); + REQUIRE_NOT_NULL(self); + self->Clear(); +} + +static Array::Ptr ArraySort(const std::vector<Value>& args) +{ + ScriptFrame *vframe = ScriptFrame::GetCurrentFrame(); + Array::Ptr self = static_cast<Array::Ptr>(vframe->Self); + REQUIRE_NOT_NULL(self); + + Array::Ptr arr = self->ShallowClone(); + + if (args.empty()) { + ObjectLock olock(arr); + std::sort(arr->Begin(), arr->End()); + } else { + Function::Ptr function = args[0]; + REQUIRE_NOT_NULL(function); + + if (vframe->Sandboxed && !function->IsSideEffectFree()) + BOOST_THROW_EXCEPTION(ScriptError("Sort function must be side-effect free.")); + + ObjectLock olock(arr); + std::sort(arr->Begin(), arr->End(), [&args](const Value& a, const Value& b) -> bool { + Function::Ptr cmp = args[0]; + return cmp->Invoke({ a, b }); + }); + } + + return arr; +} + +static Array::Ptr ArrayShallowClone() +{ + ScriptFrame *vframe = ScriptFrame::GetCurrentFrame(); + Array::Ptr self = static_cast<Array::Ptr>(vframe->Self); + REQUIRE_NOT_NULL(self); + return self->ShallowClone(); +} + +static Value ArrayJoin(const Value& separator) +{ + ScriptFrame *vframe = ScriptFrame::GetCurrentFrame(); + Array::Ptr self = static_cast<Array::Ptr>(vframe->Self); + REQUIRE_NOT_NULL(self); + return self->Join(separator); +} + +static Array::Ptr ArrayReverse() +{ + ScriptFrame *vframe = ScriptFrame::GetCurrentFrame(); + Array::Ptr self = static_cast<Array::Ptr>(vframe->Self); + REQUIRE_NOT_NULL(self); + return self->Reverse(); +} + +static Array::Ptr ArrayMap(const Function::Ptr& function) +{ + ScriptFrame *vframe = ScriptFrame::GetCurrentFrame(); + Array::Ptr self = static_cast<Array::Ptr>(vframe->Self); + REQUIRE_NOT_NULL(self); + REQUIRE_NOT_NULL(function); + + if (vframe->Sandboxed && !function->IsSideEffectFree()) + BOOST_THROW_EXCEPTION(ScriptError("Map function must be side-effect free.")); + + ArrayData result; + + ObjectLock olock(self); + for (const Value& item : self) { + result.push_back(function->Invoke({ item })); + } + + return new Array(std::move(result)); +} + +static Value ArrayReduce(const Function::Ptr& function) +{ + ScriptFrame *vframe = ScriptFrame::GetCurrentFrame(); + Array::Ptr self = static_cast<Array::Ptr>(vframe->Self); + REQUIRE_NOT_NULL(self); + REQUIRE_NOT_NULL(function); + + if (vframe->Sandboxed && !function->IsSideEffectFree()) + BOOST_THROW_EXCEPTION(ScriptError("Reduce function must be side-effect free.")); + + if (self->GetLength() == 0) + return Empty; + + Value result = self->Get(0); + + ObjectLock olock(self); + for (size_t i = 1; i < self->GetLength(); i++) { + result = function->Invoke({ result, self->Get(i) }); + } + + return result; +} + +static Array::Ptr ArrayFilter(const Function::Ptr& function) +{ + ScriptFrame *vframe = ScriptFrame::GetCurrentFrame(); + Array::Ptr self = static_cast<Array::Ptr>(vframe->Self); + REQUIRE_NOT_NULL(self); + REQUIRE_NOT_NULL(function); + + if (vframe->Sandboxed && !function->IsSideEffectFree()) + BOOST_THROW_EXCEPTION(ScriptError("Filter function must be side-effect free.")); + + ArrayData result; + + ObjectLock olock(self); + for (const Value& item : self) { + if (function->Invoke({ item })) + result.push_back(item); + } + + return new Array(std::move(result)); +} + +static bool ArrayAny(const Function::Ptr& function) +{ + ScriptFrame *vframe = ScriptFrame::GetCurrentFrame(); + Array::Ptr self = static_cast<Array::Ptr>(vframe->Self); + REQUIRE_NOT_NULL(self); + REQUIRE_NOT_NULL(function); + + if (vframe->Sandboxed && !function->IsSideEffectFree()) + BOOST_THROW_EXCEPTION(ScriptError("Filter function must be side-effect free.")); + + ObjectLock olock(self); + for (const Value& item : self) { + if (function->Invoke({ item })) + return true; + } + + return false; +} + +static bool ArrayAll(const Function::Ptr& function) +{ + ScriptFrame *vframe = ScriptFrame::GetCurrentFrame(); + Array::Ptr self = static_cast<Array::Ptr>(vframe->Self); + REQUIRE_NOT_NULL(self); + REQUIRE_NOT_NULL(function); + + if (vframe->Sandboxed && !function->IsSideEffectFree()) + BOOST_THROW_EXCEPTION(ScriptError("Filter function must be side-effect free.")); + + ObjectLock olock(self); + for (const Value& item : self) { + if (!function->Invoke({ item })) + return false; + } + + return true; +} +static Array::Ptr ArrayUnique() +{ + ScriptFrame *vframe = ScriptFrame::GetCurrentFrame(); + Array::Ptr self = static_cast<Array::Ptr>(vframe->Self); + REQUIRE_NOT_NULL(self); + return self->Unique(); +} + +static void ArrayFreeze() +{ + ScriptFrame *vframe = ScriptFrame::GetCurrentFrame(); + Array::Ptr self = static_cast<Array::Ptr>(vframe->Self); + self->Freeze(); +} + +Object::Ptr Array::GetPrototype() +{ + static Dictionary::Ptr prototype = new Dictionary({ + { "len", new Function("Array#len", ArrayLen, {}, true) }, + { "set", new Function("Array#set", ArraySet, { "index", "value" }) }, + { "get", new Function("Array#get", ArrayGet, { "index" }) }, + { "add", new Function("Array#add", ArrayAdd, { "value" }) }, + { "remove", new Function("Array#remove", ArrayRemove, { "index" }) }, + { "contains", new Function("Array#contains", ArrayContains, { "value" }, true) }, + { "clear", new Function("Array#clear", ArrayClear) }, + { "sort", new Function("Array#sort", ArraySort, { "less_cmp" }, true) }, + { "shallow_clone", new Function("Array#shallow_clone", ArrayShallowClone, {}, true) }, + { "join", new Function("Array#join", ArrayJoin, { "separator" }, true) }, + { "reverse", new Function("Array#reverse", ArrayReverse, {}, true) }, + { "map", new Function("Array#map", ArrayMap, { "func" }, true) }, + { "reduce", new Function("Array#reduce", ArrayReduce, { "reduce" }, true) }, + { "filter", new Function("Array#filter", ArrayFilter, { "func" }, true) }, + { "any", new Function("Array#any", ArrayAny, { "func" }, true) }, + { "all", new Function("Array#all", ArrayAll, { "func" }, true) }, + { "unique", new Function("Array#unique", ArrayUnique, {}, true) }, + { "freeze", new Function("Array#freeze", ArrayFreeze, {}) } + }); + + return prototype; +} diff --git a/lib/base/array.cpp b/lib/base/array.cpp new file mode 100644 index 0000000..08e06fa --- /dev/null +++ b/lib/base/array.cpp @@ -0,0 +1,380 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#include "base/array.hpp" +#include "base/objectlock.hpp" +#include "base/debug.hpp" +#include "base/primitivetype.hpp" +#include "base/dictionary.hpp" +#include "base/configwriter.hpp" +#include "base/convert.hpp" +#include "base/exception.hpp" + +using namespace icinga; + +template class std::vector<Value>; + +REGISTER_PRIMITIVE_TYPE(Array, Object, Array::GetPrototype()); + +Array::Array(const ArrayData& other) + : m_Data(other) +{ } + +Array::Array(ArrayData&& other) + : m_Data(std::move(other)) +{ } + +Array::Array(std::initializer_list<Value> init) + : m_Data(init) +{ } + +/** + * Restrieves a value from an array. + * + * @param index The index. + * @returns The value. + */ +Value Array::Get(SizeType index) const +{ + ObjectLock olock(this); + + return m_Data.at(index); +} + +/** + * Sets a value in the array. + * + * @param index The index. + * @param value The value. + * @param overrideFrozen Whether to allow modifying frozen arrays. + */ +void Array::Set(SizeType index, const Value& value, bool overrideFrozen) +{ + ObjectLock olock(this); + + if (m_Frozen && !overrideFrozen) + BOOST_THROW_EXCEPTION(std::invalid_argument("Value in array must not be modified.")); + + m_Data.at(index) = value; +} + +/** + * Sets a value in the array. + * + * @param index The index. + * @param value The value. + * @param overrideFrozen Whether to allow modifying frozen arrays. + */ +void Array::Set(SizeType index, Value&& value, bool overrideFrozen) +{ + ObjectLock olock(this); + + if (m_Frozen && !overrideFrozen) + BOOST_THROW_EXCEPTION(std::invalid_argument("Array must not be modified.")); + + m_Data.at(index).Swap(value); +} + +/** + * Adds a value to the array. + * + * @param value The value. + * @param overrideFrozen Whether to allow modifying frozen arrays. + */ +void Array::Add(Value value, bool overrideFrozen) +{ + ObjectLock olock(this); + + if (m_Frozen && !overrideFrozen) + BOOST_THROW_EXCEPTION(std::invalid_argument("Array must not be modified.")); + + m_Data.push_back(std::move(value)); +} + +/** + * Returns an iterator to the beginning of the array. + * + * Note: Caller must hold the object lock while using the iterator. + * + * @returns An iterator. + */ +Array::Iterator Array::Begin() +{ + ASSERT(OwnsLock()); + + return m_Data.begin(); +} + +/** + * Returns an iterator to the end of the array. + * + * Note: Caller must hold the object lock while using the iterator. + * + * @returns An iterator. + */ +Array::Iterator Array::End() +{ + ASSERT(OwnsLock()); + + return m_Data.end(); +} + +/** + * Returns the number of elements in the array. + * + * @returns Number of elements. + */ +size_t Array::GetLength() const +{ + ObjectLock olock(this); + + return m_Data.size(); +} + +/** + * Checks whether the array contains the specified value. + * + * @param value The value. + * @returns true if the array contains the value, false otherwise. + */ +bool Array::Contains(const Value& value) const +{ + ObjectLock olock(this); + + return (std::find(m_Data.begin(), m_Data.end(), value) != m_Data.end()); +} + +/** + * Insert the given value at the specified index + * + * @param index The index + * @param value The value to add + * @param overrideFrozen Whether to allow modifying frozen arrays. + */ +void Array::Insert(SizeType index, Value value, bool overrideFrozen) +{ + ObjectLock olock(this); + + ASSERT(index <= m_Data.size()); + + if (m_Frozen && !overrideFrozen) + BOOST_THROW_EXCEPTION(std::invalid_argument("Array must not be modified.")); + + m_Data.insert(m_Data.begin() + index, std::move(value)); +} + +/** + * Removes the specified index from the array. + * + * @param index The index. + * @param overrideFrozen Whether to allow modifying frozen arrays. + */ +void Array::Remove(SizeType index, bool overrideFrozen) +{ + ObjectLock olock(this); + + if (m_Frozen && !overrideFrozen) + BOOST_THROW_EXCEPTION(std::invalid_argument("Array must not be modified.")); + + if (index >= m_Data.size()) + BOOST_THROW_EXCEPTION(std::invalid_argument("Index to remove must be within bounds.")); + + m_Data.erase(m_Data.begin() + index); +} + +/** + * Removes the item specified by the iterator from the array. + * + * @param it The iterator. + * @param overrideFrozen Whether to allow modifying frozen arrays. + */ +void Array::Remove(Array::Iterator it, bool overrideFrozen) +{ + ASSERT(OwnsLock()); + + if (m_Frozen && !overrideFrozen) + BOOST_THROW_EXCEPTION(std::invalid_argument("Array must not be modified.")); + + m_Data.erase(it); +} + +void Array::Resize(SizeType newSize, bool overrideFrozen) +{ + ObjectLock olock(this); + + if (m_Frozen && !overrideFrozen) + BOOST_THROW_EXCEPTION(std::invalid_argument("Array must not be modified.")); + + m_Data.resize(newSize); +} + +void Array::Clear(bool overrideFrozen) +{ + ObjectLock olock(this); + + if (m_Frozen && !overrideFrozen) + BOOST_THROW_EXCEPTION(std::invalid_argument("Array must not be modified.")); + + m_Data.clear(); +} + +void Array::Reserve(SizeType newSize, bool overrideFrozen) +{ + ObjectLock olock(this); + + if (m_Frozen && !overrideFrozen) + BOOST_THROW_EXCEPTION(std::invalid_argument("Array must not be modified.")); + + m_Data.reserve(newSize); +} + +void Array::CopyTo(const Array::Ptr& dest) const +{ + ObjectLock olock(this); + ObjectLock xlock(dest); + + if (dest->m_Frozen) + BOOST_THROW_EXCEPTION(std::invalid_argument("Array must not be modified.")); + + std::copy(m_Data.begin(), m_Data.end(), std::back_inserter(dest->m_Data)); +} + +/** + * Makes a shallow copy of an array. + * + * @returns a copy of the array. + */ +Array::Ptr Array::ShallowClone() const +{ + Array::Ptr clone = new Array(); + CopyTo(clone); + return clone; +} + +/** + * Makes a deep clone of an array + * and its elements. + * + * @returns a copy of the array. + */ +Object::Ptr Array::Clone() const +{ + ArrayData arr; + + ObjectLock olock(this); + for (const Value& val : m_Data) { + arr.push_back(val.Clone()); + } + + return new Array(std::move(arr)); +} + +Array::Ptr Array::Reverse() const +{ + Array::Ptr result = new Array(); + + ObjectLock olock(this); + ObjectLock xlock(result); + + std::copy(m_Data.rbegin(), m_Data.rend(), std::back_inserter(result->m_Data)); + + return result; +} + +void Array::Sort(bool overrideFrozen) +{ + ObjectLock olock(this); + + if (m_Frozen && !overrideFrozen) + BOOST_THROW_EXCEPTION(std::invalid_argument("Array must not be modified.")); + + std::sort(m_Data.begin(), m_Data.end()); +} + +String Array::ToString() const +{ + std::ostringstream msgbuf; + ConfigWriter::EmitArray(msgbuf, 1, const_cast<Array *>(this)); + return msgbuf.str(); +} + +Value Array::Join(const Value& separator) const +{ + Value result; + bool first = true; + + ObjectLock olock(this); + + for (const Value& item : m_Data) { + if (first) { + first = false; + } else { + result = result + separator; + } + + result = result + item; + } + + return result; +} + +Array::Ptr Array::Unique() const +{ + std::set<Value> result; + + ObjectLock olock(this); + + for (const Value& item : m_Data) { + result.insert(item); + } + + return Array::FromSet(result); +} + +void Array::Freeze() +{ + ObjectLock olock(this); + m_Frozen = true; +} + +Value Array::GetFieldByName(const String& field, bool sandboxed, const DebugInfo& debugInfo) const +{ + int index; + + try { + index = Convert::ToLong(field); + } catch (...) { + return Object::GetFieldByName(field, sandboxed, debugInfo); + } + + ObjectLock olock(this); + + if (index < 0 || static_cast<size_t>(index) >= GetLength()) + BOOST_THROW_EXCEPTION(ScriptError("Array index '" + Convert::ToString(index) + "' is out of bounds.", debugInfo)); + + return Get(index); +} + +void Array::SetFieldByName(const String& field, const Value& value, bool overrideFrozen, const DebugInfo& debugInfo) +{ + ObjectLock olock(this); + + int index = Convert::ToLong(field); + + if (index < 0) + BOOST_THROW_EXCEPTION(ScriptError("Array index '" + Convert::ToString(index) + "' is out of bounds.", debugInfo)); + + if (static_cast<size_t>(index) >= GetLength()) + Resize(index + 1, overrideFrozen); + + Set(index, value, overrideFrozen); +} + +Array::Iterator icinga::begin(const Array::Ptr& x) +{ + return x->Begin(); +} + +Array::Iterator icinga::end(const Array::Ptr& x) +{ + return x->End(); +} diff --git a/lib/base/array.hpp b/lib/base/array.hpp new file mode 100644 index 0000000..2c9a9dd --- /dev/null +++ b/lib/base/array.hpp @@ -0,0 +1,117 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#ifndef ARRAY_H +#define ARRAY_H + +#include "base/i2-base.hpp" +#include "base/objectlock.hpp" +#include "base/value.hpp" +#include <boost/range/iterator.hpp> +#include <vector> +#include <set> + +namespace icinga +{ + +typedef std::vector<Value> ArrayData; + +/** + * An array of Value items. + * + * @ingroup base + */ +class Array final : public Object +{ +public: + DECLARE_OBJECT(Array); + + /** + * An iterator that can be used to iterate over array elements. + */ + typedef std::vector<Value>::iterator Iterator; + + typedef std::vector<Value>::size_type SizeType; + + Array() = default; + Array(const ArrayData& other); + Array(ArrayData&& other); + Array(std::initializer_list<Value> init); + + Value Get(SizeType index) const; + void Set(SizeType index, const Value& value, bool overrideFrozen = false); + void Set(SizeType index, Value&& value, bool overrideFrozen = false); + void Add(Value value, bool overrideFrozen = false); + + Iterator Begin(); + Iterator End(); + + size_t GetLength() const; + bool Contains(const Value& value) const; + + void Insert(SizeType index, Value value, bool overrideFrozen = false); + void Remove(SizeType index, bool overrideFrozen = false); + void Remove(Iterator it, bool overrideFrozen = false); + + void Resize(SizeType newSize, bool overrideFrozen = false); + void Clear(bool overrideFrozen = false); + + void Reserve(SizeType newSize, bool overrideFrozen = false); + + void CopyTo(const Array::Ptr& dest) const; + Array::Ptr ShallowClone() const; + + static Object::Ptr GetPrototype(); + + template<typename T> + static Array::Ptr FromVector(const std::vector<T>& v) + { + Array::Ptr result = new Array(); + ObjectLock olock(result); + std::copy(v.begin(), v.end(), std::back_inserter(result->m_Data)); + return result; + } + + template<typename T> + std::set<T> ToSet() + { + ObjectLock olock(this); + return std::set<T>(Begin(), End()); + } + + template<typename T> + static Array::Ptr FromSet(const std::set<T>& v) + { + Array::Ptr result = new Array(); + ObjectLock olock(result); + std::copy(v.begin(), v.end(), std::back_inserter(result->m_Data)); + return result; + } + + Object::Ptr Clone() const override; + + Array::Ptr Reverse() const; + + void Sort(bool overrideFrozen = false); + + String ToString() const override; + Value Join(const Value& separator) const; + + Array::Ptr Unique() const; + void Freeze(); + + Value GetFieldByName(const String& field, bool sandboxed, const DebugInfo& debugInfo) const override; + void SetFieldByName(const String& field, const Value& value, bool overrideFrozen, const DebugInfo& debugInfo) override; + +private: + std::vector<Value> m_Data; /**< The data for the array. */ + bool m_Frozen{false}; +}; + +Array::Iterator begin(const Array::Ptr& x); +Array::Iterator end(const Array::Ptr& x); + +} + +extern template class std::vector<icinga::Value>; + +#endif /* ARRAY_H */ diff --git a/lib/base/atomic-file.cpp b/lib/base/atomic-file.cpp new file mode 100644 index 0000000..762f384 --- /dev/null +++ b/lib/base/atomic-file.cpp @@ -0,0 +1,123 @@ +/* Icinga 2 | (c) 2022 Icinga GmbH | GPLv2+ */ + +#include "base/atomic-file.hpp" +#include "base/exception.hpp" +#include "base/utility.hpp" +#include <utility> + +#ifdef _WIN32 +# include <io.h> +# include <windows.h> +#else /* _WIN32 */ +# include <errno.h> +# include <unistd.h> +#endif /* _WIN32 */ + +using namespace icinga; + +void AtomicFile::Write(String path, int mode, const String& content) +{ + AtomicFile af (path, mode); + af << content; + af.Commit(); +} + +AtomicFile::AtomicFile(String path, int mode) : m_Path(std::move(path)) +{ + m_TempFilename = m_Path + ".tmp.XXXXXX"; + +#ifdef _WIN32 + m_Fd = Utility::MksTemp(&m_TempFilename[0]); +#else /* _WIN32 */ + m_Fd = mkstemp(&m_TempFilename[0]); +#endif /* _WIN32 */ + + if (m_Fd < 0) { + auto error (errno); + + BOOST_THROW_EXCEPTION(posix_error() + << boost::errinfo_api_function("mkstemp") + << boost::errinfo_errno(error) + << boost::errinfo_file_name(m_TempFilename)); + } + + try { + exceptions(failbit | badbit); + + open(boost::iostreams::file_descriptor( + m_Fd, + // Rationale: https://github.com/boostorg/iostreams/issues/152 + boost::iostreams::never_close_handle + )); + + if (chmod(m_TempFilename.CStr(), mode) < 0) { + auto error (errno); + + BOOST_THROW_EXCEPTION(posix_error() + << boost::errinfo_api_function("chmod") + << boost::errinfo_errno(error) + << boost::errinfo_file_name(m_TempFilename)); + } + } catch (...) { + if (is_open()) { + close(); + } + + (void)::close(m_Fd); + (void)unlink(m_TempFilename.CStr()); + throw; + } +} + +AtomicFile::~AtomicFile() +{ + if (is_open()) { + try { + close(); + } catch (...) { + // Destructor must not throw + } + } + + if (m_Fd >= 0) { + (void)::close(m_Fd); + } + + if (!m_TempFilename.IsEmpty()) { + (void)unlink(m_TempFilename.CStr()); + } +} + +void AtomicFile::Commit() +{ + flush(); + + auto h ((*this)->handle()); + +#ifdef _WIN32 + if (!FlushFileBuffers(h)) { + auto err (GetLastError()); + + BOOST_THROW_EXCEPTION(win32_error() + << boost::errinfo_api_function("FlushFileBuffers") + << errinfo_win32_error(err) + << boost::errinfo_file_name(m_TempFilename)); + } +#else /* _WIN32 */ + if (fsync(h)) { + auto err (errno); + + BOOST_THROW_EXCEPTION(posix_error() + << boost::errinfo_api_function("fsync") + << boost::errinfo_errno(err) + << boost::errinfo_file_name(m_TempFilename)); + } +#endif /* _WIN32 */ + + close(); + (void)::close(m_Fd); + m_Fd = -1; + + Utility::RenameFile(m_TempFilename, m_Path); + m_TempFilename = ""; +} diff --git a/lib/base/atomic-file.hpp b/lib/base/atomic-file.hpp new file mode 100644 index 0000000..c5c7897 --- /dev/null +++ b/lib/base/atomic-file.hpp @@ -0,0 +1,41 @@ +/* Icinga 2 | (c) 2022 Icinga GmbH | GPLv2+ */ + +#ifndef ATOMIC_FILE_H +#define ATOMIC_FILE_H + +#include "base/string.hpp" +#include <boost/iostreams/device/file_descriptor.hpp> +#include <boost/iostreams/stream.hpp> + +namespace icinga +{ + +/** + * Atomically replaces a file's content. + * + * @ingroup base + */ +class AtomicFile : public boost::iostreams::stream<boost::iostreams::file_descriptor> +{ +public: + static void Write(String path, int mode, const String& content); + + AtomicFile(String path, int mode); + ~AtomicFile(); + + inline const String& GetTempFilename() const noexcept + { + return m_TempFilename; + } + + void Commit(); + +private: + String m_Path; + String m_TempFilename; + int m_Fd; +}; + +} + +#endif /* ATOMIC_FILE_H */ diff --git a/lib/base/atomic.hpp b/lib/base/atomic.hpp new file mode 100644 index 0000000..c8f169c --- /dev/null +++ b/lib/base/atomic.hpp @@ -0,0 +1,91 @@ +/* Icinga 2 | (c) 2019 Icinga GmbH | GPLv2+ */ + +#ifndef ATOMIC_H +#define ATOMIC_H + +#include <atomic> +#include <mutex> +#include <type_traits> +#include <utility> + +namespace icinga +{ + +/** + * Extends std::atomic with an atomic constructor. + * + * @ingroup base + */ +template<class T> +class Atomic : public std::atomic<T> { +public: + /** + * Like std::atomic#atomic, but operates atomically + * + * @param desired Initial value + */ + inline Atomic(T desired) + { + this->store(desired); + } + + /** + * Like std::atomic#atomic, but operates atomically + * + * @param desired Initial value + * @param order Initial store operation's memory order + */ + inline Atomic(T desired, std::memory_order order) + { + this->store(desired, order); + } +}; + +/** + * Wraps any T into a std::atomic<T>-like interface that locks using a mutex. + * + * In contrast to std::atomic<T>, Locked<T> is also valid for types that are not trivially copyable. + * In case T is trivially copyable, std::atomic<T> is almost certainly the better choice. + * + * @ingroup base + */ +template<typename T> +class Locked +{ +public: + inline T load() const + { + std::unique_lock<std::mutex> lock(m_Mutex); + + return m_Value; + } + + inline void store(T desired) + { + std::unique_lock<std::mutex> lock(m_Mutex); + + m_Value = std::move(desired); + } + +private: + mutable std::mutex m_Mutex; + T m_Value; +}; + +/** + * Type alias for std::atomic<T> if possible, otherwise Locked<T> is used as a fallback. + * + * @ingroup base + */ +template <typename T> +using AtomicOrLocked = +#if defined(__GNUC__) && __GNUC__ < 5 + // GCC does not implement std::is_trivially_copyable until version 5. + typename std::conditional<std::is_fundamental<T>::value || std::is_pointer<T>::value, std::atomic<T>, Locked<T>>::type; +#else /* defined(__GNUC__) && __GNUC__ < 5 */ + typename std::conditional<std::is_trivially_copyable<T>::value, std::atomic<T>, Locked<T>>::type; +#endif /* defined(__GNUC__) && __GNUC__ < 5 */ + +} + +#endif /* ATOMIC_H */ diff --git a/lib/base/base64.cpp b/lib/base/base64.cpp new file mode 100644 index 0000000..42999c3 --- /dev/null +++ b/lib/base/base64.cpp @@ -0,0 +1,53 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#include "base/base64.hpp" +#include <openssl/bio.h> +#include <openssl/evp.h> +#include <openssl/buffer.h> +#include <sstream> + +using namespace icinga; + +String Base64::Encode(const String& input) +{ + BIO *biomem = BIO_new(BIO_s_mem()); + BIO *bio64 = BIO_new(BIO_f_base64()); + BIO_push(bio64, biomem); + BIO_set_flags(bio64, BIO_FLAGS_BASE64_NO_NL); + BIO_write(bio64, input.CStr(), input.GetLength()); + (void) BIO_flush(bio64); + + char *outbuf; + long len = BIO_get_mem_data(biomem, &outbuf); + + String ret = String(outbuf, outbuf + len); + BIO_free_all(bio64); + + return ret; +} + +String Base64::Decode(const String& input) +{ + BIO *biomem = BIO_new_mem_buf( + const_cast<char*>(input.CStr()), input.GetLength()); + BIO *bio64 = BIO_new(BIO_f_base64()); + BIO_push(bio64, biomem); + BIO_set_flags(bio64, BIO_FLAGS_BASE64_NO_NL); + + auto *outbuf = new char[input.GetLength()]; + + size_t len = 0; + int rc; + + while ((rc = BIO_read(bio64, outbuf + len, input.GetLength() - len)) > 0) + len += rc; + + String ret = String(outbuf, outbuf + len); + BIO_free_all(bio64); + delete [] outbuf; + + if (ret.IsEmpty() && !input.IsEmpty()) + throw std::invalid_argument("Not a valid base64 string"); + + return ret; +} diff --git a/lib/base/base64.hpp b/lib/base/base64.hpp new file mode 100644 index 0000000..8abbdbf --- /dev/null +++ b/lib/base/base64.hpp @@ -0,0 +1,25 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#ifndef BASE64_H +#define BASE64_H + +#include "remote/i2-remote.hpp" +#include "base/string.hpp" + +namespace icinga +{ + +/** + * Base64 + * + * @ingroup remote + */ +struct Base64 +{ + static String Decode(const String& data); + static String Encode(const String& data); +}; + +} + +#endif /* BASE64_H */ diff --git a/lib/base/boolean-script.cpp b/lib/base/boolean-script.cpp new file mode 100644 index 0000000..a9167ca --- /dev/null +++ b/lib/base/boolean-script.cpp @@ -0,0 +1,26 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#include "base/boolean.hpp" +#include "base/convert.hpp" +#include "base/function.hpp" +#include "base/functionwrapper.hpp" +#include "base/scriptframe.hpp" + +using namespace icinga; + +static String BooleanToString() +{ + ScriptFrame *vframe = ScriptFrame::GetCurrentFrame(); + bool self = vframe->Self; + return self ? "true" : "false"; +} + +Object::Ptr Boolean::GetPrototype() +{ + static Dictionary::Ptr prototype = new Dictionary({ + { "to_string", new Function("Boolean#to_string", BooleanToString, {}, true) } + }); + + return prototype; +} + diff --git a/lib/base/boolean.cpp b/lib/base/boolean.cpp new file mode 100644 index 0000000..683a727 --- /dev/null +++ b/lib/base/boolean.cpp @@ -0,0 +1,9 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#include "base/boolean.hpp" +#include "base/primitivetype.hpp" + +using namespace icinga; + +REGISTER_BUILTIN_TYPE(Boolean, Boolean::GetPrototype()); + diff --git a/lib/base/boolean.hpp b/lib/base/boolean.hpp new file mode 100644 index 0000000..6533cb4 --- /dev/null +++ b/lib/base/boolean.hpp @@ -0,0 +1,27 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#ifndef BOOLEAN_H +#define BOOLEAN_H + +#include "base/i2-base.hpp" +#include "base/object.hpp" + +namespace icinga { + +class Value; + +/** + * Boolean class. + */ +class Boolean +{ +public: + static Object::Ptr GetPrototype(); + +private: + Boolean(); +}; + +} + +#endif /* BOOLEAN_H */ diff --git a/lib/base/bulker.hpp b/lib/base/bulker.hpp new file mode 100644 index 0000000..2c30dc3 --- /dev/null +++ b/lib/base/bulker.hpp @@ -0,0 +1,119 @@ +/* Icinga 2 | (c) 2022 Icinga GmbH | GPLv2+ */ + +#ifndef BULKER_H +#define BULKER_H + +#include <boost/config.hpp> +#include <chrono> +#include <condition_variable> +#include <mutex> +#include <queue> +#include <utility> +#include <vector> + +namespace icinga +{ + +/** + * A queue which outputs the input as bulks of a defined size + * or after a defined time, whichever is reached first + * + * @ingroup base + */ +template<class T> +class Bulker +{ +private: + typedef std::chrono::steady_clock Clock; + +public: + typedef std::vector<T> Container; + typedef typename Container::size_type SizeType; + typedef typename Clock::duration Duration; + + Bulker(SizeType bulkSize, Duration threshold) + : m_BulkSize(bulkSize), m_Threshold(threshold), m_NextConsumption(NullTimePoint()) { } + + void ProduceOne(T needle); + Container ConsumeMany(); + SizeType Size(); + + inline SizeType GetBulkSize() const noexcept + { + return m_BulkSize; + } + +private: + typedef std::chrono::time_point<Clock> TimePoint; + + static inline + TimePoint NullTimePoint() + { + return TimePoint::min(); + } + + inline void UpdateNextConsumption() + { + m_NextConsumption = Clock::now() + m_Threshold; + } + + const SizeType m_BulkSize; + const Duration m_Threshold; + + std::mutex m_Mutex; + std::condition_variable m_CV; + std::queue<Container> m_Bulks; + TimePoint m_NextConsumption; +}; + +template<class T> +void Bulker<T>::ProduceOne(T needle) +{ + std::unique_lock<std::mutex> lock (m_Mutex); + + if (m_Bulks.empty() || m_Bulks.back().size() == m_BulkSize) { + m_Bulks.emplace(); + } + + m_Bulks.back().emplace_back(std::move(needle)); + + if (m_Bulks.size() == 1u && m_Bulks.back().size() == m_BulkSize) { + m_CV.notify_one(); + } +} + +template<class T> +typename Bulker<T>::Container Bulker<T>::ConsumeMany() +{ + std::unique_lock<std::mutex> lock (m_Mutex); + + if (BOOST_UNLIKELY(m_NextConsumption == NullTimePoint())) { + UpdateNextConsumption(); + } + + auto deadline (m_NextConsumption); + + m_CV.wait_until(lock, deadline, [this]() { return !m_Bulks.empty() && m_Bulks.front().size() == m_BulkSize; }); + UpdateNextConsumption(); + + if (m_Bulks.empty()) { + return Container(); + } + + auto haystack (std::move(m_Bulks.front())); + + m_Bulks.pop(); + return haystack; +} + +template<class T> +typename Bulker<T>::SizeType Bulker<T>::Size() +{ + std::unique_lock<std::mutex> lock (m_Mutex); + + return m_Bulks.empty() ? 0 : (m_Bulks.size() - 1u) * m_BulkSize + m_Bulks.back().size(); +} + +} + +#endif /* BULKER_H */ diff --git a/lib/base/configobject-script.cpp b/lib/base/configobject-script.cpp new file mode 100644 index 0000000..46a9ca2 --- /dev/null +++ b/lib/base/configobject-script.cpp @@ -0,0 +1,36 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#include "base/configobject.hpp" +#include "base/dictionary.hpp" +#include "base/function.hpp" +#include "base/functionwrapper.hpp" +#include "base/scriptframe.hpp" + +using namespace icinga; + +static void ConfigObjectModifyAttribute(const String& attr, const Value& value) +{ + ScriptFrame *vframe = ScriptFrame::GetCurrentFrame(); + ConfigObject::Ptr self = vframe->Self; + REQUIRE_NOT_NULL(self); + return self->ModifyAttribute(attr, value); +} + +static void ConfigObjectRestoreAttribute(const String& attr) +{ + ScriptFrame *vframe = ScriptFrame::GetCurrentFrame(); + ConfigObject::Ptr self = vframe->Self; + REQUIRE_NOT_NULL(self); + return self->RestoreAttribute(attr); +} + +Object::Ptr ConfigObject::GetPrototype() +{ + static Dictionary::Ptr prototype = new Dictionary({ + { "modify_attribute", new Function("ConfigObject#modify_attribute", ConfigObjectModifyAttribute, { "attr", "value" }, false) }, + { "restore_attribute", new Function("ConfigObject#restore_attribute", ConfigObjectRestoreAttribute, { "attr", "value" }, false) } + }); + + return prototype; +} + diff --git a/lib/base/configobject.cpp b/lib/base/configobject.cpp new file mode 100644 index 0000000..4317771 --- /dev/null +++ b/lib/base/configobject.cpp @@ -0,0 +1,701 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#include "base/atomic-file.hpp" +#include "base/configobject.hpp" +#include "base/configobject-ti.cpp" +#include "base/configtype.hpp" +#include "base/serializer.hpp" +#include "base/netstring.hpp" +#include "base/json.hpp" +#include "base/stdiostream.hpp" +#include "base/debug.hpp" +#include "base/objectlock.hpp" +#include "base/logger.hpp" +#include "base/exception.hpp" +#include "base/function.hpp" +#include "base/initialize.hpp" +#include "base/workqueue.hpp" +#include "base/context.hpp" +#include "base/application.hpp" +#include <fstream> +#include <boost/exception/errinfo_api_function.hpp> +#include <boost/exception/errinfo_errno.hpp> +#include <boost/exception/errinfo_file_name.hpp> + +using namespace icinga; + +REGISTER_TYPE_WITH_PROTOTYPE(ConfigObject, ConfigObject::GetPrototype()); + +boost::signals2::signal<void (const ConfigObject::Ptr&)> ConfigObject::OnStateChanged; + +bool ConfigObject::IsActive() const +{ + return GetActive(); +} + +bool ConfigObject::IsPaused() const +{ + return GetPaused(); +} + +void ConfigObject::SetExtension(const String& key, const Value& value) +{ + Dictionary::Ptr extensions = GetExtensions(); + + if (!extensions) { + extensions = new Dictionary(); + SetExtensions(extensions); + } + + extensions->Set(key, value); +} + +Value ConfigObject::GetExtension(const String& key) +{ + Dictionary::Ptr extensions = GetExtensions(); + + if (!extensions) + return Empty; + + return extensions->Get(key); +} + +void ConfigObject::ClearExtension(const String& key) +{ + Dictionary::Ptr extensions = GetExtensions(); + + if (!extensions) + return; + + extensions->Remove(key); +} + +class ModAttrValidationUtils final : public ValidationUtils +{ +public: + bool ValidateName(const String& type, const String& name) const override + { + Type::Ptr ptype = Type::GetByName(type); + auto *dtype = dynamic_cast<ConfigType *>(ptype.get()); + + if (!dtype) + return false; + + if (!dtype->GetObject(name)) + return false; + + return true; + } +}; + +void ConfigObject::ModifyAttribute(const String& attr, const Value& value, bool updateVersion) +{ + Dictionary::Ptr original_attributes = GetOriginalAttributes(); + bool updated_original_attributes = false; + + Type::Ptr type = GetReflectionType(); + + std::vector<String> tokens = attr.Split("."); + + String fieldName = tokens[0]; + + int fid = type->GetFieldId(fieldName); + Field field = type->GetFieldInfo(fid); + + if (field.Attributes & FANoUserModify) + BOOST_THROW_EXCEPTION(std::invalid_argument("Attribute cannot be modified.")); + + if (field.Attributes & FAConfig) { + if (!original_attributes) { + original_attributes = new Dictionary(); + SetOriginalAttributes(original_attributes, true); + } + } + + Value oldValue = GetField(fid); + Value newValue; + + if (tokens.size() > 1) { + newValue = oldValue.Clone(); + Value current = newValue; + + if (current.IsEmpty()) { + current = new Dictionary(); + newValue = current; + } + + String prefix = tokens[0]; + + for (std::vector<String>::size_type i = 1; i < tokens.size() - 1; i++) { + if (!current.IsObjectType<Dictionary>()) + BOOST_THROW_EXCEPTION(std::invalid_argument("Value must be a dictionary.")); + + Dictionary::Ptr dict = current; + + const String& key = tokens[i]; + prefix += "." + key; + + if (!dict->Get(key, ¤t)) { + current = new Dictionary(); + dict->Set(key, current); + } + } + + if (!current.IsObjectType<Dictionary>()) + BOOST_THROW_EXCEPTION(std::invalid_argument("Value must be a dictionary.")); + + Dictionary::Ptr dict = current; + + const String& key = tokens[tokens.size() - 1]; + prefix += "." + key; + + /* clone it for original attributes */ + oldValue = dict->Get(key).Clone(); + + if (field.Attributes & FAConfig) { + updated_original_attributes = true; + + if (oldValue.IsObjectType<Dictionary>()) { + Dictionary::Ptr oldDict = oldValue; + ObjectLock olock(oldDict); + for (const auto& kv : oldDict) { + String key = prefix + "." + kv.first; + if (!original_attributes->Contains(key)) + original_attributes->Set(key, kv.second); + } + + /* store the new value as null */ + if (value.IsObjectType<Dictionary>()) { + Dictionary::Ptr valueDict = value; + ObjectLock olock(valueDict); + for (const auto& kv : valueDict) { + String key = attr + "." + kv.first; + if (!original_attributes->Contains(key)) + original_attributes->Set(key, Empty); + } + } + } else if (!original_attributes->Contains(attr)) + original_attributes->Set(attr, oldValue); + } + + dict->Set(key, value); + } else { + newValue = value; + + if (field.Attributes & FAConfig) { + if (!original_attributes->Contains(attr)) { + updated_original_attributes = true; + original_attributes->Set(attr, oldValue); + } + } + } + + ModAttrValidationUtils utils; + ValidateField(fid, Lazy<Value>{newValue}, utils); + + SetField(fid, newValue); + + if (updateVersion && (field.Attributes & FAConfig)) + SetVersion(Utility::GetTime()); + + if (updated_original_attributes) + NotifyOriginalAttributes(); +} + +void ConfigObject::RestoreAttribute(const String& attr, bool updateVersion) +{ + Type::Ptr type = GetReflectionType(); + + std::vector<String> tokens = attr.Split("."); + + String fieldName = tokens[0]; + + int fid = type->GetFieldId(fieldName); + + Value currentValue = GetField(fid); + + Dictionary::Ptr original_attributes = GetOriginalAttributes(); + + if (!original_attributes) + return; + + Value oldValue = original_attributes->Get(attr); + Value newValue; + + if (tokens.size() > 1) { + newValue = currentValue.Clone(); + Value current = newValue; + + if (current.IsEmpty()) + BOOST_THROW_EXCEPTION(std::invalid_argument("Cannot restore non-existent object attribute")); + + String prefix = tokens[0]; + + for (std::vector<String>::size_type i = 1; i < tokens.size() - 1; i++) { + if (!current.IsObjectType<Dictionary>()) + BOOST_THROW_EXCEPTION(std::invalid_argument("Value must be a dictionary.")); + + Dictionary::Ptr dict = current; + + const String& key = tokens[i]; + prefix += "." + key; + + if (!dict->Contains(key)) + BOOST_THROW_EXCEPTION(std::invalid_argument("Cannot restore non-existent object attribute")); + + current = dict->Get(key); + } + + if (!current.IsObjectType<Dictionary>()) + BOOST_THROW_EXCEPTION(std::invalid_argument("Value must be a dictionary.")); + + Dictionary::Ptr dict = current; + + const String& key = tokens[tokens.size() - 1]; + prefix += "." + key; + + std::vector<String> restoredAttrs; + + { + ObjectLock olock(original_attributes); + for (const auto& kv : original_attributes) { + std::vector<String> originalTokens = String(kv.first).Split("."); + + if (tokens.size() > originalTokens.size()) + continue; + + bool match = true; + for (std::vector<String>::size_type i = 0; i < tokens.size(); i++) { + if (tokens[i] != originalTokens[i]) { + match = false; + break; + } + } + + if (!match) + continue; + + Dictionary::Ptr dict; + + if (tokens.size() == originalTokens.size()) + dict = current; + else { + Value currentSub = current; + + for (std::vector<String>::size_type i = tokens.size() - 1; i < originalTokens.size() - 1; i++) { + dict = currentSub; + currentSub = dict->Get(originalTokens[i]); + + if (!currentSub.IsObjectType<Dictionary>()) { + currentSub = new Dictionary(); + dict->Set(originalTokens[i], currentSub); + } + } + + dict = currentSub; + } + + dict->Set(originalTokens[originalTokens.size() - 1], kv.second); + restoredAttrs.push_back(kv.first); + } + } + + for (const String& attr : restoredAttrs) + original_attributes->Remove(attr); + + + } else { + newValue = oldValue; + } + + original_attributes->Remove(attr); + SetField(fid, newValue); + + if (updateVersion) + SetVersion(Utility::GetTime()); +} + +bool ConfigObject::IsAttributeModified(const String& attr) const +{ + Dictionary::Ptr original_attributes = GetOriginalAttributes(); + + if (!original_attributes) + return false; + + return original_attributes->Contains(attr); +} + +void ConfigObject::Register() +{ + ASSERT(!OwnsLock()); + + TypeImpl<ConfigObject>::Ptr type = static_pointer_cast<TypeImpl<ConfigObject> >(GetReflectionType()); + type->RegisterObject(this); +} + +void ConfigObject::Unregister() +{ + ASSERT(!OwnsLock()); + + TypeImpl<ConfigObject>::Ptr type = static_pointer_cast<TypeImpl<ConfigObject> >(GetReflectionType()); + type->UnregisterObject(this); +} + +void ConfigObject::Start(bool runtimeCreated) +{ + ObjectImpl<ConfigObject>::Start(runtimeCreated); + + ObjectLock olock(this); + + SetStartCalled(true); +} + +void ConfigObject::PreActivate() +{ + CONTEXT("Setting 'active' to true for object '" << GetName() << "' of type '" << GetReflectionType()->GetName() << "'"); + + ASSERT(!IsActive()); + SetActive(true, true); +} + +void ConfigObject::Activate(bool runtimeCreated, const Value& cookie) +{ + CONTEXT("Activating object '" << GetName() << "' of type '" << GetReflectionType()->GetName() << "'"); + + { + ObjectLock olock(this); + + Start(runtimeCreated); + + ASSERT(GetStartCalled()); + + if (GetHAMode() == HARunEverywhere) + SetAuthority(true); + } + + NotifyActive(cookie); +} + +void ConfigObject::Stop(bool runtimeRemoved) +{ + ObjectImpl<ConfigObject>::Stop(runtimeRemoved); + + ObjectLock olock(this); + + SetStopCalled(true); +} + +void ConfigObject::Deactivate(bool runtimeRemoved, const Value& cookie) +{ + CONTEXT("Deactivating object '" << GetName() << "' of type '" << GetReflectionType()->GetName() << "'"); + + { + ObjectLock olock(this); + + if (!IsActive()) + return; + + SetActive(false, true); + + SetAuthority(false); + + Stop(runtimeRemoved); + } + + ASSERT(GetStopCalled()); + + NotifyActive(cookie); +} + +void ConfigObject::OnConfigLoaded() +{ + /* Nothing to do here. */ +} + +void ConfigObject::OnAllConfigLoaded() +{ + static ConfigType *ctype = dynamic_cast<ConfigType *>(Type::GetByName("Zone").get()); + String zoneName = GetZoneName(); + + if (!zoneName.IsEmpty()) + m_Zone = ctype->GetObject(zoneName); +} + +void ConfigObject::CreateChildObjects(const Type::Ptr& childType) +{ + /* Nothing to do here. */ +} + +void ConfigObject::OnStateLoaded() +{ + /* Nothing to do here. */ +} + +void ConfigObject::Pause() +{ + SetPauseCalled(true); +} + +void ConfigObject::Resume() +{ + SetResumeCalled(true); +} + +void ConfigObject::SetAuthority(bool authority) +{ + ObjectLock olock(this); + + if (authority && GetPaused()) { + SetResumeCalled(false); + Resume(); + ASSERT(GetResumeCalled()); + SetPaused(false); + } else if (!authority && !GetPaused()) { + SetPaused(true); + SetPauseCalled(false); + Pause(); + ASSERT(GetPauseCalled()); + } +} + +void ConfigObject::DumpObjects(const String& filename, int attributeTypes) +{ + Log(LogInformation, "ConfigObject") + << "Dumping program state to file '" << filename << "'"; + + try { + Utility::Glob(filename + ".tmp.*", &Utility::Remove, GlobFile); + } catch (const std::exception& ex) { + Log(LogWarning, "ConfigObject") << DiagnosticInformation(ex); + } + + AtomicFile fp (filename, 0600); + StdioStream::Ptr sfp = new StdioStream(&fp, false); + + for (const Type::Ptr& type : Type::GetAllTypes()) { + auto *dtype = dynamic_cast<ConfigType *>(type.get()); + + if (!dtype) + continue; + + for (const ConfigObject::Ptr& object : dtype->GetObjects()) { + Dictionary::Ptr update = Serialize(object, attributeTypes); + + if (!update) + continue; + + Dictionary::Ptr persistentObject = new Dictionary({ + { "type", type->GetName() }, + { "name", object->GetName() }, + { "update", update } + }); + + String json = JsonEncode(persistentObject); + + NetString::WriteStringToStream(sfp, json); + } + } + + sfp->Close(); + fp.Commit(); +} + +void ConfigObject::RestoreObject(const String& message, int attributeTypes) +{ + Dictionary::Ptr persistentObject = JsonDecode(message); + + String type = persistentObject->Get("type"); + String name = persistentObject->Get("name"); + + ConfigObject::Ptr object = GetObject(type, name); + + if (!object) + return; + +#ifdef I2_DEBUG + Log(LogDebug, "ConfigObject") + << "Restoring object '" << name << "' of type '" << type << "'."; +#endif /* I2_DEBUG */ + Dictionary::Ptr update = persistentObject->Get("update"); + Deserialize(object, update, false, attributeTypes); + object->OnStateLoaded(); + object->SetStateLoaded(true); +} + +void ConfigObject::RestoreObjects(const String& filename, int attributeTypes) +{ + if (!Utility::PathExists(filename)) + return; + + Log(LogInformation, "ConfigObject") + << "Restoring program state from file '" << filename << "'"; + + std::fstream fp; + fp.open(filename.CStr(), std::ios_base::in); + + StdioStream::Ptr sfp = new StdioStream (&fp, false); + + unsigned long restored = 0; + + WorkQueue upq(25000, Configuration::Concurrency); + upq.SetName("ConfigObject::RestoreObjects"); + + String message; + StreamReadContext src; + for (;;) { + StreamReadStatus srs = NetString::ReadStringFromStream(sfp, &message, src); + + if (srs == StatusEof) + break; + + if (srs != StatusNewItem) + continue; + + upq.Enqueue([message, attributeTypes]() { RestoreObject(message, attributeTypes); }); + restored++; + } + + sfp->Close(); + + upq.Join(); + + unsigned long no_state = 0; + + for (const Type::Ptr& type : Type::GetAllTypes()) { + auto *dtype = dynamic_cast<ConfigType *>(type.get()); + + if (!dtype) + continue; + + for (const ConfigObject::Ptr& object : dtype->GetObjects()) { + if (!object->GetStateLoaded()) { + object->OnStateLoaded(); + object->SetStateLoaded(true); + + no_state++; + } + } + } + + Log(LogInformation, "ConfigObject") + << "Restored " << restored << " objects. Loaded " << no_state << " new objects without state."; +} + +void ConfigObject::StopObjects() +{ + std::vector<Type::Ptr> types = Type::GetAllTypes(); + + std::sort(types.begin(), types.end(), [](const Type::Ptr& a, const Type::Ptr& b) { + if (a->GetActivationPriority() > b->GetActivationPriority()) + return true; + return false; + }); + + for (const Type::Ptr& type : types) { + auto *dtype = dynamic_cast<ConfigType *>(type.get()); + + if (!dtype) + continue; + + for (const ConfigObject::Ptr& object : dtype->GetObjects()) { +#ifdef I2_DEBUG + Log(LogDebug, "ConfigObject") + << "Deactivate() called for config object '" << object->GetName() << "' with type '" << type->GetName() << "'."; +#endif /* I2_DEBUG */ + object->Deactivate(); + } + } +} + +void ConfigObject::DumpModifiedAttributes(const std::function<void(const ConfigObject::Ptr&, const String&, const Value&)>& callback) +{ + for (const Type::Ptr& type : Type::GetAllTypes()) { + auto *dtype = dynamic_cast<ConfigType *>(type.get()); + + if (!dtype) + continue; + + for (const ConfigObject::Ptr& object : dtype->GetObjects()) { + Dictionary::Ptr originalAttributes = object->GetOriginalAttributes(); + + if (!originalAttributes) + continue; + + ObjectLock olock(originalAttributes); + for (const Dictionary::Pair& kv : originalAttributes) { + String key = kv.first; + + Type::Ptr type = object->GetReflectionType(); + + std::vector<String> tokens = key.Split("."); + + String fieldName = tokens[0]; + int fid = type->GetFieldId(fieldName); + + Value currentValue = object->GetField(fid); + Value modifiedValue; + + if (tokens.size() > 1) { + Value current = currentValue; + + for (std::vector<String>::size_type i = 1; i < tokens.size() - 1; i++) { + if (!current.IsObjectType<Dictionary>()) + BOOST_THROW_EXCEPTION(std::invalid_argument("Value must be a dictionary.")); + + Dictionary::Ptr dict = current; + const String& key = tokens[i]; + + if (!dict->Contains(key)) + break; + + current = dict->Get(key); + } + + if (!current.IsObjectType<Dictionary>()) + BOOST_THROW_EXCEPTION(std::invalid_argument("Value must be a dictionary.")); + + Dictionary::Ptr dict = current; + const String& key = tokens[tokens.size() - 1]; + + modifiedValue = dict->Get(key); + } else + modifiedValue = currentValue; + + callback(object, key, modifiedValue); + } + } + } + +} + +ConfigObject::Ptr ConfigObject::GetObject(const String& type, const String& name) +{ + Type::Ptr ptype = Type::GetByName(type); + auto *ctype = dynamic_cast<ConfigType *>(ptype.get()); + + if (!ctype) + return nullptr; + + return ctype->GetObject(name); +} + +ConfigObject::Ptr ConfigObject::GetZone() const +{ + return m_Zone; +} + +Dictionary::Ptr ConfigObject::GetSourceLocation() const +{ + DebugInfo di = GetDebugInfo(); + + return new Dictionary({ + { "path", di.Path }, + { "first_line", di.FirstLine }, + { "first_column", di.FirstColumn }, + { "last_line", di.LastLine }, + { "last_column", di.LastColumn } + }); +} + +NameComposer::~NameComposer() +{ } diff --git a/lib/base/configobject.hpp b/lib/base/configobject.hpp new file mode 100644 index 0000000..5596363 --- /dev/null +++ b/lib/base/configobject.hpp @@ -0,0 +1,101 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#ifndef CONFIGOBJECT_H +#define CONFIGOBJECT_H + +#include "base/i2-base.hpp" +#include "base/configobject-ti.hpp" +#include "base/object.hpp" +#include "base/type.hpp" +#include "base/dictionary.hpp" +#include <boost/signals2.hpp> + +namespace icinga +{ + +class ConfigType; + +/** + * A dynamic object that can be instantiated from the configuration file. + * + * @ingroup base + */ +class ConfigObject : public ObjectImpl<ConfigObject> +{ +public: + DECLARE_OBJECT(ConfigObject); + + static boost::signals2::signal<void (const ConfigObject::Ptr&)> OnStateChanged; + + bool IsActive() const; + bool IsPaused() const; + + void SetExtension(const String& key, const Value& value); + Value GetExtension(const String& key); + void ClearExtension(const String& key); + + ConfigObject::Ptr GetZone() const; + + void ModifyAttribute(const String& attr, const Value& value, bool updateVersion = true); + void RestoreAttribute(const String& attr, bool updateVersion = true); + bool IsAttributeModified(const String& attr) const; + + void Register(); + void Unregister(); + + void PreActivate(); + void Activate(bool runtimeCreated = false, const Value& cookie = Empty); + void Deactivate(bool runtimeRemoved = false, const Value& cookie = Empty); + void SetAuthority(bool authority); + + void Start(bool runtimeCreated = false) override; + void Stop(bool runtimeRemoved = false) override; + + virtual void Pause(); + virtual void Resume(); + + virtual void OnConfigLoaded(); + virtual void CreateChildObjects(const Type::Ptr& childType); + virtual void OnAllConfigLoaded(); + virtual void OnStateLoaded(); + + Dictionary::Ptr GetSourceLocation() const override; + + template<typename T> + static intrusive_ptr<T> GetObject(const String& name) + { + typedef TypeImpl<T> ObjType; + auto *ptype = static_cast<ObjType *>(T::TypeInstance.get()); + return static_pointer_cast<T>(ptype->GetObject(name)); + } + + static ConfigObject::Ptr GetObject(const String& type, const String& name); + + static void DumpObjects(const String& filename, int attributeTypes = FAState); + static void RestoreObjects(const String& filename, int attributeTypes = FAState); + static void StopObjects(); + + static void DumpModifiedAttributes(const std::function<void(const ConfigObject::Ptr&, const String&, const Value&)>& callback); + + static Object::Ptr GetPrototype(); + +private: + ConfigObject::Ptr m_Zone; + + static void RestoreObject(const String& message, int attributeTypes); +}; + +#define DECLARE_OBJECTNAME(klass) \ + inline static String GetTypeName() \ + { \ + return #klass; \ + } \ + \ + inline static intrusive_ptr<klass> GetByName(const String& name) \ + { \ + return ConfigObject::GetObject<klass>(name); \ + } + +} + +#endif /* CONFIGOBJECT_H */ diff --git a/lib/base/configobject.ti b/lib/base/configobject.ti new file mode 100644 index 0000000..ea67dfa --- /dev/null +++ b/lib/base/configobject.ti @@ -0,0 +1,94 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#include "base/debuginfo.hpp" +#include "base/configtype.hpp" + +library base; + +namespace icinga +{ + +code {{{ +enum HAMode +{ + HARunOnce, + HARunEverywhere +}; + +class NameComposer +{ +public: + virtual ~NameComposer(); + + virtual String MakeName(const String& shortName, const Object::Ptr& context) const = 0; + virtual Dictionary::Ptr ParseName(const String& name) const = 0; +}; +}}} + +abstract class ConfigObjectBase +{ }; + +code {{{ +class ConfigObjectBase : public ObjectImpl<ConfigObjectBase> +{ +public: + inline DebugInfo GetDebugInfo() const + { + return m_DebugInfo; + } + + void SetDebugInfo(const DebugInfo& di) + { + m_DebugInfo = di; + } + + inline virtual void Start(bool /* runtimeCreated */) + { } + + inline virtual void Stop(bool /* runtimeRemoved */) + { } + +private: + DebugInfo m_DebugInfo; +}; + +}}} + +abstract class ConfigObject : ConfigObjectBase < ConfigType +{ + [config, no_user_modify] String __name (Name); + [config, no_user_modify, required] String "name" (ShortName) { + get {{{ + String shortName = m_ShortName.load(); + if (shortName.IsEmpty()) + return GetName(); + else + return shortName; + }}} + }; + [config, no_user_modify] name(Zone) zone (ZoneName); + [config, no_user_modify] String package; + [config, get_protected, no_user_modify] Array::Ptr templates; + [config, no_storage, no_user_modify] Dictionary::Ptr source_location { + get; + }; + [get_protected, no_user_modify] bool active; + [get_protected, no_user_modify] bool paused { + default {{{ return true; }}} + }; + [get_protected, no_user_view, no_user_modify] bool start_called; + [get_protected, no_user_view, no_user_modify] bool stop_called; + [get_protected, no_user_view, no_user_modify] bool pause_called; + [get_protected, no_user_view, no_user_modify] bool resume_called; + [enum] HAMode ha_mode (HAMode); + [protected, no_user_view, no_user_modify] Dictionary::Ptr extensions; + + [protected, no_user_view, no_user_modify] bool state_loaded; + [no_user_modify] Dictionary::Ptr original_attributes; + [state, no_user_modify] double version { + default {{{ return 0; }}} + }; + [no_user_view, no_user_modify] String icingadb_identifier; +}; + +} diff --git a/lib/base/configtype.cpp b/lib/base/configtype.cpp new file mode 100644 index 0000000..b266cd2 --- /dev/null +++ b/lib/base/configtype.cpp @@ -0,0 +1,76 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#include "base/configobject.hpp" +#include "base/convert.hpp" +#include "base/exception.hpp" + +using namespace icinga; + +ConfigType::~ConfigType() +{ } + +ConfigObject::Ptr ConfigType::GetObject(const String& name) const +{ + std::shared_lock<decltype(m_Mutex)> lock (m_Mutex); + + auto nt = m_ObjectMap.find(name); + + if (nt == m_ObjectMap.end()) + return nullptr; + + return nt->second; +} + +void ConfigType::RegisterObject(const ConfigObject::Ptr& object) +{ + String name = object->GetName(); + + { + std::unique_lock<decltype(m_Mutex)> lock (m_Mutex); + + auto it = m_ObjectMap.find(name); + + if (it != m_ObjectMap.end()) { + if (it->second == object) + return; + + auto *type = dynamic_cast<Type *>(this); + + BOOST_THROW_EXCEPTION(ScriptError("An object with type '" + type->GetName() + "' and name '" + name + "' already exists (" + + Convert::ToString(it->second->GetDebugInfo()) + "), new declaration: " + Convert::ToString(object->GetDebugInfo()), + object->GetDebugInfo())); + } + + m_ObjectMap[name] = object; + m_ObjectVector.push_back(object); + } +} + +void ConfigType::UnregisterObject(const ConfigObject::Ptr& object) +{ + String name = object->GetName(); + + { + std::unique_lock<decltype(m_Mutex)> lock (m_Mutex); + + m_ObjectMap.erase(name); + m_ObjectVector.erase(std::remove(m_ObjectVector.begin(), m_ObjectVector.end(), object), m_ObjectVector.end()); + } +} + +std::vector<ConfigObject::Ptr> ConfigType::GetObjects() const +{ + std::shared_lock<decltype(m_Mutex)> lock (m_Mutex); + return m_ObjectVector; +} + +std::vector<ConfigObject::Ptr> ConfigType::GetObjectsHelper(Type *type) +{ + return static_cast<TypeImpl<ConfigObject> *>(type)->GetObjects(); +} + +int ConfigType::GetObjectCount() const +{ + std::shared_lock<decltype(m_Mutex)> lock (m_Mutex); + return m_ObjectVector.size(); +} diff --git a/lib/base/configtype.hpp b/lib/base/configtype.hpp new file mode 100644 index 0000000..c77fc5e --- /dev/null +++ b/lib/base/configtype.hpp @@ -0,0 +1,64 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#ifndef CONFIGTYPE_H +#define CONFIGTYPE_H + +#include "base/i2-base.hpp" +#include "base/object.hpp" +#include "base/type.hpp" +#include "base/dictionary.hpp" +#include <shared_mutex> +#include <unordered_map> + +namespace icinga +{ + +class ConfigObject; + +class ConfigType +{ +public: + virtual ~ConfigType(); + + intrusive_ptr<ConfigObject> GetObject(const String& name) const; + + void RegisterObject(const intrusive_ptr<ConfigObject>& object); + void UnregisterObject(const intrusive_ptr<ConfigObject>& object); + + std::vector<intrusive_ptr<ConfigObject> > GetObjects() const; + + template<typename T> + static TypeImpl<T> *Get() + { + typedef TypeImpl<T> ObjType; + return static_cast<ObjType *>(T::TypeInstance.get()); + } + + template<typename T> + static std::vector<intrusive_ptr<T> > GetObjectsByType() + { + std::vector<intrusive_ptr<ConfigObject> > objects = GetObjectsHelper(T::TypeInstance.get()); + std::vector<intrusive_ptr<T> > result; + result.reserve(objects.size()); +for (const auto& object : objects) { + result.push_back(static_pointer_cast<T>(object)); + } + return result; + } + + int GetObjectCount() const; + +private: + typedef std::unordered_map<String, intrusive_ptr<ConfigObject> > ObjectMap; + typedef std::vector<intrusive_ptr<ConfigObject> > ObjectVector; + + mutable std::shared_timed_mutex m_Mutex; + ObjectMap m_ObjectMap; + ObjectVector m_ObjectVector; + + static std::vector<intrusive_ptr<ConfigObject> > GetObjectsHelper(Type *type); +}; + +} + +#endif /* CONFIGTYPE_H */ diff --git a/lib/base/configuration.cpp b/lib/base/configuration.cpp new file mode 100644 index 0000000..908c161 --- /dev/null +++ b/lib/base/configuration.cpp @@ -0,0 +1,379 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#include "base/configuration.hpp" +#include "base/configuration-ti.cpp" +#include "base/exception.hpp" + +using namespace icinga; + +REGISTER_TYPE(Configuration); + +String Configuration::ApiBindHost = []() { +#ifndef _WIN32 + // Automatically fall back to an IPv4 default if socket() tells us that IPv6 is not supported. + int fd = socket(AF_INET6, SOCK_STREAM, 0); + if (fd < 0 && errno == EAFNOSUPPORT) { + return "0.0.0.0"; + } else if (fd >= 0) { + close(fd); + } +#endif /* _WIN32 */ + + return "::"; +}(); + +String Configuration::ApiBindPort{"5665"}; +bool Configuration::AttachDebugger{false}; +String Configuration::CacheDir; +int Configuration::Concurrency{1}; +bool Configuration::ConcurrencyWasModified{false}; +String Configuration::ConfigDir; +String Configuration::DataDir; +String Configuration::EventEngine; +String Configuration::IncludeConfDir; +String Configuration::InitRunDir; +String Configuration::LogDir; +String Configuration::ModAttrPath; +String Configuration::ObjectsPath; +String Configuration::PidPath; +String Configuration::PkgDataDir; +String Configuration::PrefixDir; +String Configuration::ProgramData; +int Configuration::RLimitFiles; +int Configuration::RLimitProcesses; +int Configuration::RLimitStack; +String Configuration::RunAsGroup; +String Configuration::RunAsUser; +String Configuration::SpoolDir; +String Configuration::StatePath; +double Configuration::TlsHandshakeTimeout{10}; +String Configuration::VarsPath; +String Configuration::ZonesDir; + +/* deprecated */ +String Configuration::LocalStateDir; +String Configuration::RunDir; +String Configuration::SysconfDir; + +/* internal */ +bool Configuration::m_ReadOnly{false}; + +template<typename T> +void HandleUserWrite(const String& name, T *target, const T& value, bool readOnly) +{ + if (readOnly) + BOOST_THROW_EXCEPTION(ScriptError("Configuration attribute '" + name + "' is read-only.")); + + *target = value; +} + +String Configuration::GetApiBindHost() const +{ + return Configuration::ApiBindHost; +} + +void Configuration::SetApiBindHost(const String& val, bool suppress_events, const Value& cookie) +{ + HandleUserWrite("ApiBindHost", &Configuration::ApiBindHost, val, m_ReadOnly); +} + +String Configuration::GetApiBindPort() const +{ + return Configuration::ApiBindPort; +} + +void Configuration::SetApiBindPort(const String& val, bool suppress_events, const Value& cookie) +{ + HandleUserWrite("ApiBindPort", &Configuration::ApiBindPort, val, m_ReadOnly); +} + +bool Configuration::GetAttachDebugger() const +{ + return Configuration::AttachDebugger; +} + +void Configuration::SetAttachDebugger(bool val, bool suppress_events, const Value& cookie) +{ + HandleUserWrite("AttachDebugger", &Configuration::AttachDebugger, val, m_ReadOnly); +} + +String Configuration::GetCacheDir() const +{ + return Configuration::CacheDir; +} + +void Configuration::SetCacheDir(const String& val, bool suppress_events, const Value& cookie) +{ + HandleUserWrite("CacheDir", &Configuration::CacheDir, val, m_ReadOnly); +} + +int Configuration::GetConcurrency() const +{ + return Configuration::Concurrency; +} + +void Configuration::SetConcurrency(int val, bool suppress_events, const Value& cookie) +{ + HandleUserWrite("Concurrency", &Configuration::Concurrency, val, m_ReadOnly); + Configuration::ConcurrencyWasModified = true; +} + +String Configuration::GetConfigDir() const +{ + return Configuration::ConfigDir; +} + +void Configuration::SetConfigDir(const String& val, bool suppress_events, const Value& cookie) +{ + HandleUserWrite("ConfigDir", &Configuration::ConfigDir, val, m_ReadOnly); +} + +String Configuration::GetDataDir() const +{ + return Configuration::DataDir; +} + +void Configuration::SetDataDir(const String& val, bool suppress_events, const Value& cookie) +{ + HandleUserWrite("DataDir", &Configuration::DataDir, val, m_ReadOnly); +} + +String Configuration::GetEventEngine() const +{ + return Configuration::EventEngine; +} + +void Configuration::SetEventEngine(const String& val, bool suppress_events, const Value& cookie) +{ + HandleUserWrite("EventEngine", &Configuration::EventEngine, val, m_ReadOnly); +} + +String Configuration::GetIncludeConfDir() const +{ + return Configuration::IncludeConfDir; +} + +void Configuration::SetIncludeConfDir(const String& val, bool suppress_events, const Value& cookie) +{ + HandleUserWrite("IncludeConfDir", &Configuration::IncludeConfDir, val, m_ReadOnly); +} + +String Configuration::GetInitRunDir() const +{ + return Configuration::InitRunDir; +} + +void Configuration::SetInitRunDir(const String& val, bool suppress_events, const Value& cookie) +{ + HandleUserWrite("InitRunDir", &Configuration::InitRunDir, val, m_ReadOnly); +} + +String Configuration::GetLogDir() const +{ + return Configuration::LogDir; +} + +void Configuration::SetLogDir(const String& val, bool suppress_events, const Value& cookie) +{ + HandleUserWrite("LogDir", &Configuration::LogDir, val, m_ReadOnly); +} + +String Configuration::GetModAttrPath() const +{ + return Configuration::ModAttrPath; +} + +void Configuration::SetModAttrPath(const String& val, bool suppress_events, const Value& cookie) +{ + HandleUserWrite("ModAttrPath", &Configuration::ModAttrPath, val, m_ReadOnly); +} + +String Configuration::GetObjectsPath() const +{ + return Configuration::ObjectsPath; +} + +void Configuration::SetObjectsPath(const String& val, bool suppress_events, const Value& cookie) +{ + HandleUserWrite("ObjectsPath", &Configuration::ObjectsPath, val, m_ReadOnly); +} + +String Configuration::GetPidPath() const +{ + return Configuration::PidPath; +} + +void Configuration::SetPidPath(const String& val, bool suppress_events, const Value& cookie) +{ + HandleUserWrite("PidPath", &Configuration::PidPath, val, m_ReadOnly); +} + +String Configuration::GetPkgDataDir() const +{ + return Configuration::PkgDataDir; +} + +void Configuration::SetPkgDataDir(const String& val, bool suppress_events, const Value& cookie) +{ + HandleUserWrite("PkgDataDir", &Configuration::PkgDataDir, val, m_ReadOnly); +} + +String Configuration::GetPrefixDir() const +{ + return Configuration::PrefixDir; +} + +void Configuration::SetPrefixDir(const String& val, bool suppress_events, const Value& cookie) +{ + HandleUserWrite("PrefixDir", &Configuration::PrefixDir, val, m_ReadOnly); +} + +String Configuration::GetProgramData() const +{ + return Configuration::ProgramData; +} + +void Configuration::SetProgramData(const String& val, bool suppress_events, const Value& cookie) +{ + HandleUserWrite("ProgramData", &Configuration::ProgramData, val, m_ReadOnly); +} + +int Configuration::GetRLimitFiles() const +{ + return Configuration::RLimitFiles; +} + +void Configuration::SetRLimitFiles(int val, bool suppress_events, const Value& cookie) +{ + HandleUserWrite("RLimitFiles", &Configuration::RLimitFiles, val, m_ReadOnly); +} + +int Configuration::GetRLimitProcesses() const +{ + return RLimitProcesses; +} + +void Configuration::SetRLimitProcesses(int val, bool suppress_events, const Value& cookie) +{ + HandleUserWrite("RLimitProcesses", &Configuration::RLimitProcesses, val, m_ReadOnly); +} + +int Configuration::GetRLimitStack() const +{ + return Configuration::RLimitStack; +} + +void Configuration::SetRLimitStack(int val, bool suppress_events, const Value& cookie) +{ + HandleUserWrite("RLimitStack", &Configuration::RLimitStack, val, m_ReadOnly); +} + +String Configuration::GetRunAsGroup() const +{ + return Configuration::RunAsGroup; +} + +void Configuration::SetRunAsGroup(const String& val, bool suppress_events, const Value& cookie) +{ + HandleUserWrite("RunAsGroup", &Configuration::RunAsGroup, val, m_ReadOnly); +} + +String Configuration::GetRunAsUser() const +{ + return Configuration::RunAsUser; +} + +void Configuration::SetRunAsUser(const String& val, bool suppress_events, const Value& cookie) +{ + HandleUserWrite("RunAsUser", &Configuration::RunAsUser, val, m_ReadOnly); +} + +String Configuration::GetSpoolDir() const +{ + return Configuration::SpoolDir; +} + +void Configuration::SetSpoolDir(const String& val, bool suppress_events, const Value& cookie) +{ + HandleUserWrite("SpoolDir", &Configuration::SpoolDir, val, m_ReadOnly); +} + +String Configuration::GetStatePath() const +{ + return Configuration::StatePath; +} + +void Configuration::SetStatePath(const String& val, bool suppress_events, const Value& cookie) +{ + HandleUserWrite("StatePath", &Configuration::StatePath, val, m_ReadOnly); +} + +double Configuration::GetTlsHandshakeTimeout() const +{ + return Configuration::TlsHandshakeTimeout; +} + +void Configuration::SetTlsHandshakeTimeout(double val, bool suppress_events, const Value& cookie) +{ + HandleUserWrite("TlsHandshakeTimeout", &Configuration::TlsHandshakeTimeout, val, m_ReadOnly); +} + +String Configuration::GetVarsPath() const +{ + return Configuration::VarsPath; +} + +void Configuration::SetVarsPath(const String& val, bool suppress_events, const Value& cookie) +{ + HandleUserWrite("VarsPath", &Configuration::VarsPath, val, m_ReadOnly); +} + +String Configuration::GetZonesDir() const +{ + return Configuration::ZonesDir; +} + +void Configuration::SetZonesDir(const String& val, bool suppress_events, const Value& cookie) +{ + HandleUserWrite("ZonesDir", &Configuration::ZonesDir, val, m_ReadOnly); +} + +String Configuration::GetLocalStateDir() const +{ + return Configuration::LocalStateDir; +} + +void Configuration::SetLocalStateDir(const String& val, bool suppress_events, const Value& cookie) +{ + HandleUserWrite("LocalStateDir", &Configuration::LocalStateDir, val, m_ReadOnly); +} + +String Configuration::GetSysconfDir() const +{ + return Configuration::SysconfDir; +} + +void Configuration::SetSysconfDir(const String& val, bool suppress_events, const Value& cookie) +{ + HandleUserWrite("SysconfDir", &Configuration::SysconfDir, val, m_ReadOnly); +} + +String Configuration::GetRunDir() const +{ + return Configuration::RunDir; +} + +void Configuration::SetRunDir(const String& val, bool suppress_events, const Value& cookie) +{ + HandleUserWrite("RunDir", &Configuration::RunDir, val, m_ReadOnly); +} + +bool Configuration::GetReadOnly() +{ + return m_ReadOnly; +} + +void Configuration::SetReadOnly(bool readOnly) +{ + m_ReadOnly = readOnly; +} diff --git a/lib/base/configuration.hpp b/lib/base/configuration.hpp new file mode 100644 index 0000000..a5aed01 --- /dev/null +++ b/lib/base/configuration.hpp @@ -0,0 +1,157 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#ifndef CONFIGURATION_H +#define CONFIGURATION_H + +#include "base/i2-base.hpp" +#include "base/configuration-ti.hpp" + +namespace icinga +{ + +/** + * Global configuration. + * + * @ingroup base + */ +class Configuration : public ObjectImpl<Configuration> +{ +public: + DECLARE_OBJECT(Configuration); + + String GetApiBindHost() const override; + void SetApiBindHost(const String& value, bool suppress_events = false, const Value& cookie = Empty) override; + + String GetApiBindPort() const override; + void SetApiBindPort(const String& value, bool suppress_events = false, const Value& cookie = Empty) override; + + bool GetAttachDebugger() const override; + void SetAttachDebugger(bool value, bool suppress_events = false, const Value& cookie = Empty) override; + + String GetCacheDir() const override; + void SetCacheDir(const String& value, bool suppress_events = false, const Value& cookie = Empty) override; + + int GetConcurrency() const override; + void SetConcurrency(int value, bool suppress_events = false, const Value& cookie = Empty) override; + + String GetConfigDir() const override; + void SetConfigDir(const String& value, bool suppress_events = false, const Value& cookie = Empty) override; + + String GetDataDir() const override; + void SetDataDir(const String& value, bool suppress_events = false, const Value& cookie = Empty) override; + + String GetEventEngine() const override; + void SetEventEngine(const String& value, bool suppress_events = false, const Value& cookie = Empty) override; + + String GetIncludeConfDir() const override; + void SetIncludeConfDir(const String& value, bool suppress_events = false, const Value& cookie = Empty) override; + + String GetInitRunDir() const override; + void SetInitRunDir(const String& value, bool suppress_events = false, const Value& cookie = Empty) override; + + String GetLogDir() const override; + void SetLogDir(const String& value, bool suppress_events = false, const Value& cookie = Empty) override; + + String GetModAttrPath() const override; + void SetModAttrPath(const String& value, bool suppress_events = false, const Value& cookie = Empty) override; + + String GetObjectsPath() const override; + void SetObjectsPath(const String& value, bool suppress_events = false, const Value& cookie = Empty) override; + + String GetPidPath() const override; + void SetPidPath(const String& value, bool suppress_events = false, const Value& cookie = Empty) override; + + String GetPkgDataDir() const override; + void SetPkgDataDir(const String& value, bool suppress_events = false, const Value& cookie = Empty) override; + + String GetPrefixDir() const override; + void SetPrefixDir(const String& value, bool suppress_events = false, const Value& cookie = Empty) override; + + String GetProgramData() const override; + void SetProgramData(const String& value, bool suppress_events = false, const Value& cookie = Empty) override; + + int GetRLimitFiles() const override; + void SetRLimitFiles(int value, bool suppress_events = false, const Value& cookie = Empty) override; + + int GetRLimitProcesses() const override; + void SetRLimitProcesses(int value, bool suppress_events = false, const Value& cookie = Empty) override; + + int GetRLimitStack() const override; + void SetRLimitStack(int value, bool suppress_events = false, const Value& cookie = Empty) override; + + String GetRunAsGroup() const override; + void SetRunAsGroup(const String& value, bool suppress_events = false, const Value& cookie = Empty) override; + + String GetRunAsUser() const override; + void SetRunAsUser(const String& value, bool suppress_events = false, const Value& cookie = Empty) override; + + String GetSpoolDir() const override; + void SetSpoolDir(const String& value, bool suppress_events = false, const Value& cookie = Empty) override; + + String GetStatePath() const override; + void SetStatePath(const String& value, bool suppress_events = false, const Value& cookie = Empty) override; + + double GetTlsHandshakeTimeout() const override; + void SetTlsHandshakeTimeout(double value, bool suppress_events = false, const Value& cookie = Empty) override; + + String GetVarsPath() const override; + void SetVarsPath(const String& value, bool suppress_events = false, const Value& cookie = Empty) override; + + String GetZonesDir() const override; + void SetZonesDir(const String& value, bool suppress_events = false, const Value& cookie = Empty) override; + + /* deprecated */ + String GetLocalStateDir() const override; + void SetLocalStateDir(const String& value, bool suppress_events = false, const Value& cookie = Empty) override; + + String GetSysconfDir() const override; + void SetSysconfDir(const String& value, bool suppress_events = false, const Value& cookie = Empty) override; + + String GetRunDir() const override; + void SetRunDir(const String& value, bool suppress_events = false, const Value& cookie = Empty) override; + + static bool GetReadOnly(); + static void SetReadOnly(bool readOnly); + + static String ApiBindHost; + static String ApiBindPort; + static bool AttachDebugger; + static String CacheDir; + static int Concurrency; + static bool ConcurrencyWasModified; + static String ConfigDir; + static String DataDir; + static String EventEngine; + static String IncludeConfDir; + static String InitRunDir; + static String LogDir; + static String ModAttrPath; + static String ObjectsPath; + static String PidPath; + static String PkgDataDir; + static String PrefixDir; + static String ProgramData; + static int RLimitFiles; + static int RLimitProcesses; + static int RLimitStack; + static String RunAsGroup; + static String RunAsUser; + static String SpoolDir; + static String StatePath; + static double TlsHandshakeTimeout; + static String VarsPath; + static String ZonesDir; + + /* deprecated */ + static String LocalStateDir; + static String RunDir; + static String SysconfDir; + +private: + static bool m_ReadOnly; + +}; + +} + +#endif /* CONFIGURATION_H */ diff --git a/lib/base/configuration.ti b/lib/base/configuration.ti new file mode 100644 index 0000000..72fa92d --- /dev/null +++ b/lib/base/configuration.ti @@ -0,0 +1,164 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#include "base/configobject.hpp" + +library base; + +namespace icinga +{ + +abstract class Configuration +{ + [config, no_storage, virtual] String ApiBindHost { + get; + set; + }; + + [config, no_storage, virtual] String ApiBindPort { + get; + set; + }; + + [config, no_storage, virtual] bool AttachDebugger { + get; + set; + }; + + [config, no_storage, virtual] String CacheDir { + get; + set; + }; + + [config, no_storage, virtual] int Concurrency { + get; + set; + }; + + [config, no_storage, virtual] String ConfigDir { + get; + set; + }; + + [config, no_storage, virtual] String DataDir { + get; + set; + }; + + [config, no_storage, virtual] String EventEngine { + get; + set; + }; + + [config, no_storage, virtual] String IncludeConfDir { + get; + set; + }; + + [config, no_storage, virtual] String InitRunDir { + get; + set; + }; + + [config, no_storage, virtual] String LogDir { + get; + set; + }; + + [config, no_storage, virtual] String ModAttrPath { + get; + set; + }; + + [config, no_storage, virtual] String ObjectsPath { + get; + set; + }; + + [config, no_storage, virtual] String PidPath { + get; + set; + }; + + [config, no_storage, virtual] String PkgDataDir { + get; + set; + }; + + [config, no_storage, virtual] String PrefixDir { + get; + set; + }; + + [config, no_storage, virtual] String ProgramData { + get; + set; + }; + + [config, no_storage, virtual] int RLimitFiles { + get; + set; + }; + + [config, no_storage, virtual] int RLimitProcesses { + get; + set; + }; + + [config, no_storage, virtual] int RLimitStack { + get; + set; + }; + + [config, no_storage, virtual] String RunAsGroup { + get; + set; + }; + + [config, no_storage, virtual] String RunAsUser { + get; + set; + }; + + [config, no_storage, virtual] String SpoolDir { + get; + set; + }; + + [config, no_storage, virtual] String StatePath { + get; + set; + }; + + [config, no_storage, virtual] double TlsHandshakeTimeout { + get; + set; + }; + + [config, no_storage, virtual] String VarsPath { + get; + set; + }; + + [config, no_storage, virtual] String ZonesDir { + get; + set; + }; + + /* deprecated */ + [config, no_storage, virtual] String LocalStateDir { + get; + set; + }; + + [config, no_storage, virtual] String RunDir { + get; + set; + }; + + [config, no_storage, virtual] String SysconfDir { + get; + set; + }; +}; + +} diff --git a/lib/base/configwriter.cpp b/lib/base/configwriter.cpp new file mode 100644 index 0000000..c9dd582 --- /dev/null +++ b/lib/base/configwriter.cpp @@ -0,0 +1,260 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#include "base/configwriter.hpp" +#include "base/exception.hpp" +#include <boost/regex.hpp> +#include <boost/algorithm/string/replace.hpp> +#include <set> +#include <iterator> + +using namespace icinga; + +void ConfigWriter::EmitBoolean(std::ostream& fp, bool val) +{ + fp << (val ? "true" : "false"); +} + +void ConfigWriter::EmitNumber(std::ostream& fp, double val) +{ + fp << std::fixed << val; +} + +void ConfigWriter::EmitString(std::ostream& fp, const String& val) +{ + fp << "\"" << EscapeIcingaString(val) << "\""; +} + +void ConfigWriter::EmitEmpty(std::ostream& fp) +{ + fp << "null"; +} + +void ConfigWriter::EmitArray(std::ostream& fp, int indentLevel, const Array::Ptr& val) +{ + fp << "[ "; + EmitArrayItems(fp, indentLevel, val); + if (val->GetLength() > 0) + fp << " "; + fp << "]"; +} + +void ConfigWriter::EmitArrayItems(std::ostream& fp, int indentLevel, const Array::Ptr& val) +{ + bool first = true; + + ObjectLock olock(val); + for (const Value& item : val) { + if (first) + first = false; + else + fp << ", "; + + EmitValue(fp, indentLevel, item); + } +} + +void ConfigWriter::EmitScope(std::ostream& fp, int indentLevel, const Dictionary::Ptr& val, + const Array::Ptr& imports, bool splitDot) +{ + fp << "{"; + + if (imports && imports->GetLength() > 0) { + ObjectLock xlock(imports); + for (const Value& import : imports) { + fp << "\n"; + EmitIndent(fp, indentLevel); + fp << "import \"" << import << "\""; + } + + fp << "\n"; + } + + if (val) { + ObjectLock olock(val); + for (const Dictionary::Pair& kv : val) { + fp << "\n"; + EmitIndent(fp, indentLevel); + + if (splitDot) { + std::vector<String> tokens = kv.first.Split("."); + + EmitIdentifier(fp, tokens[0], true); + + for (std::vector<String>::size_type i = 1; i < tokens.size(); i++) { + fp << "["; + EmitString(fp, tokens[i]); + fp << "]"; + } + } else + EmitIdentifier(fp, kv.first, true); + + fp << " = "; + EmitValue(fp, indentLevel + 1, kv.second); + } + } + + fp << "\n"; + EmitIndent(fp, indentLevel - 1); + fp << "}"; +} + +void ConfigWriter::EmitValue(std::ostream& fp, int indentLevel, const Value& val) +{ + if (val.IsObjectType<Array>()) + EmitArray(fp, indentLevel, val); + else if (val.IsObjectType<Dictionary>()) + EmitScope(fp, indentLevel, val); + else if (val.IsObjectType<ConfigIdentifier>()) + EmitIdentifier(fp, static_cast<ConfigIdentifier::Ptr>(val)->GetName(), false); + else if (val.IsString()) + EmitString(fp, val); + else if (val.IsNumber()) + EmitNumber(fp, val); + else if (val.IsBoolean()) + EmitBoolean(fp, val); + else if (val.IsEmpty()) + EmitEmpty(fp); +} + +void ConfigWriter::EmitRaw(std::ostream& fp, const String& val) +{ + fp << val; +} + +void ConfigWriter::EmitIndent(std::ostream& fp, int indentLevel) +{ + for (int i = 0; i < indentLevel; i++) + fp << "\t"; +} + +void ConfigWriter::EmitIdentifier(std::ostream& fp, const String& identifier, bool inAssignment) +{ + static std::set<String> keywords; + static std::mutex mutex; + + { + std::unique_lock<std::mutex> lock(mutex); + if (keywords.empty()) { + const std::vector<String>& vkeywords = GetKeywords(); + std::copy(vkeywords.begin(), vkeywords.end(), std::inserter(keywords, keywords.begin())); + } + } + + if (keywords.find(identifier) != keywords.end()) { + fp << "@" << identifier; + return; + } + + boost::regex expr("^[a-zA-Z_][a-zA-Z0-9\\_]*$"); + boost::smatch what; + if (boost::regex_search(identifier.GetData(), what, expr)) + fp << identifier; + else if (inAssignment) + EmitString(fp, identifier); + else + BOOST_THROW_EXCEPTION(std::invalid_argument("Invalid identifier")); +} + +void ConfigWriter::EmitConfigItem(std::ostream& fp, const String& type, const String& name, bool isTemplate, + bool ignoreOnError, const Array::Ptr& imports, const Dictionary::Ptr& attrs) +{ + if (isTemplate) + fp << "template "; + else + fp << "object "; + + EmitIdentifier(fp, type, false); + fp << " "; + EmitString(fp, name); + + if (ignoreOnError) + fp << " ignore_on_error"; + + fp << " "; + EmitScope(fp, 1, attrs, imports, true); +} + +void ConfigWriter::EmitComment(std::ostream& fp, const String& text) +{ + fp << "/* " << text << " */\n"; +} + +void ConfigWriter::EmitFunctionCall(std::ostream& fp, const String& name, const Array::Ptr& arguments) +{ + EmitIdentifier(fp, name, false); + fp << "("; + EmitArrayItems(fp, 0, arguments); + fp << ")"; +} + +String ConfigWriter::EscapeIcingaString(const String& str) +{ + String result = str; + boost::algorithm::replace_all(result, "\\", "\\\\"); + boost::algorithm::replace_all(result, "\n", "\\n"); + boost::algorithm::replace_all(result, "\t", "\\t"); + boost::algorithm::replace_all(result, "\r", "\\r"); + boost::algorithm::replace_all(result, "\b", "\\b"); + boost::algorithm::replace_all(result, "\f", "\\f"); + boost::algorithm::replace_all(result, "\"", "\\\""); + return result; +} + +const std::vector<String>& ConfigWriter::GetKeywords() +{ + static std::vector<String> keywords; + static std::mutex mutex; + std::unique_lock<std::mutex> lock(mutex); + + if (keywords.empty()) { + keywords.emplace_back("object"); + keywords.emplace_back("template"); + keywords.emplace_back("include"); + keywords.emplace_back("include_recursive"); + keywords.emplace_back("include_zones"); + keywords.emplace_back("library"); + keywords.emplace_back("null"); + keywords.emplace_back("true"); + keywords.emplace_back("false"); + keywords.emplace_back("const"); + keywords.emplace_back("var"); + keywords.emplace_back("this"); + keywords.emplace_back("globals"); + keywords.emplace_back("locals"); + keywords.emplace_back("use"); + keywords.emplace_back("using"); + keywords.emplace_back("namespace"); + keywords.emplace_back("default"); + keywords.emplace_back("ignore_on_error"); + keywords.emplace_back("current_filename"); + keywords.emplace_back("current_line"); + keywords.emplace_back("apply"); + keywords.emplace_back("to"); + keywords.emplace_back("where"); + keywords.emplace_back("import"); + keywords.emplace_back("assign"); + keywords.emplace_back("ignore"); + keywords.emplace_back("function"); + keywords.emplace_back("return"); + keywords.emplace_back("break"); + keywords.emplace_back("continue"); + keywords.emplace_back("for"); + keywords.emplace_back("if"); + keywords.emplace_back("else"); + keywords.emplace_back("while"); + keywords.emplace_back("throw"); + keywords.emplace_back("try"); + keywords.emplace_back("except"); + } + + return keywords; +} + +ConfigIdentifier::ConfigIdentifier(String identifier) + : m_Name(std::move(identifier)) +{ } + +String ConfigIdentifier::GetName() const +{ + return m_Name; +} diff --git a/lib/base/configwriter.hpp b/lib/base/configwriter.hpp new file mode 100644 index 0000000..a0c70f7 --- /dev/null +++ b/lib/base/configwriter.hpp @@ -0,0 +1,67 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#ifndef CONFIGWRITER_H +#define CONFIGWRITER_H + +#include "base/object.hpp" +#include "base/array.hpp" +#include "base/dictionary.hpp" +#include <iosfwd> + +namespace icinga +{ + +/** + * A config identifier. + * + * @ingroup base + */ +class ConfigIdentifier final : public Object +{ +public: + DECLARE_PTR_TYPEDEFS(ConfigIdentifier); + + ConfigIdentifier(String name); + + String GetName() const; + +private: + String m_Name; +}; + +/** + * A configuration writer. + * + * @ingroup base + */ +class ConfigWriter +{ +public: + static void EmitBoolean(std::ostream& fp, bool val); + static void EmitNumber(std::ostream& fp, double val); + static void EmitString(std::ostream& fp, const String& val); + static void EmitEmpty(std::ostream& fp); + static void EmitArray(std::ostream& fp, int indentLevel, const Array::Ptr& val); + static void EmitArrayItems(std::ostream& fp, int indentLevel, const Array::Ptr& val); + static void EmitScope(std::ostream& fp, int indentLevel, const Dictionary::Ptr& val, + const Array::Ptr& imports = nullptr, bool splitDot = false); + static void EmitValue(std::ostream& fp, int indentLevel, const Value& val); + static void EmitRaw(std::ostream& fp, const String& val); + static void EmitIndent(std::ostream& fp, int indentLevel); + + static void EmitIdentifier(std::ostream& fp, const String& identifier, bool inAssignment); + static void EmitConfigItem(std::ostream& fp, const String& type, const String& name, bool isTemplate, + bool ignoreOnError, const Array::Ptr& imports, const Dictionary::Ptr& attrs); + + static void EmitComment(std::ostream& fp, const String& text); + static void EmitFunctionCall(std::ostream& fp, const String& name, const Array::Ptr& arguments); + + static const std::vector<String>& GetKeywords(); +private: + static String EscapeIcingaString(const String& str); + ConfigWriter(); +}; + +} + +#endif /* CONFIGWRITER_H */ diff --git a/lib/base/console.cpp b/lib/base/console.cpp new file mode 100644 index 0000000..99a5fad --- /dev/null +++ b/lib/base/console.cpp @@ -0,0 +1,203 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#include "base/console.hpp" +#include "base/initialize.hpp" +#include <iostream> + +using namespace icinga; + +static ConsoleType l_ConsoleType = Console_Dumb; + +static void InitializeConsole() +{ + l_ConsoleType = Console_Dumb; + +#ifndef _WIN32 + if (isatty(1)) + l_ConsoleType = Console_VT100; +#else /* _WIN32 */ + l_ConsoleType = Console_Windows; +#endif /* _WIN32 */ +} + +INITIALIZE_ONCE(InitializeConsole); + +ConsoleColorTag::ConsoleColorTag(int color, ConsoleType consoleType) + : m_Color(color), m_ConsoleType(consoleType) +{ } + +std::ostream& icinga::operator<<(std::ostream& fp, const ConsoleColorTag& cct) +{ +#ifndef _WIN32 + if (cct.m_ConsoleType == Console_VT100 || Console::GetType(fp) == Console_VT100) + Console::PrintVT100ColorCode(fp, cct.m_Color); +#else /* _WIN32 */ + if (Console::GetType(fp) == Console_Windows) { + fp.flush(); + Console::SetWindowsConsoleColor(fp, cct.m_Color); + } +#endif /* _WIN32 */ + + return fp; +} + +void Console::SetType(std::ostream& fp, ConsoleType type) +{ + if (&fp == &std::cout || &fp == &std::cerr) + l_ConsoleType = type; +} + +ConsoleType Console::GetType(std::ostream& fp) +{ + if (&fp == &std::cout || &fp == &std::cerr) + return l_ConsoleType; + else + return Console_Dumb; +} + +#ifndef _WIN32 +void Console::PrintVT100ColorCode(std::ostream& fp, int color) +{ + if (color == Console_Normal) { + fp << "\33[0m"; + return; + } + + switch (color & 0xff) { + case Console_ForegroundBlack: + fp << "\33[30m"; + break; + case Console_ForegroundRed: + fp << "\33[31m"; + break; + case Console_ForegroundGreen: + fp << "\33[32m"; + break; + case Console_ForegroundYellow: + fp << "\33[33m"; + break; + case Console_ForegroundBlue: + fp << "\33[34m"; + break; + case Console_ForegroundMagenta: + fp << "\33[35m"; + break; + case Console_ForegroundCyan: + fp << "\33[36m"; + break; + case Console_ForegroundWhite: + fp << "\33[37m"; + break; + } + + switch (color & 0xff00) { + case Console_BackgroundBlack: + fp << "\33[40m"; + break; + case Console_BackgroundRed: + fp << "\33[41m"; + break; + case Console_BackgroundGreen: + fp << "\33[42m"; + break; + case Console_BackgroundYellow: + fp << "\33[43m"; + break; + case Console_BackgroundBlue: + fp << "\33[44m"; + break; + case Console_BackgroundMagenta: + fp << "\33[45m"; + break; + case Console_BackgroundCyan: + fp << "\33[46m"; + break; + case Console_BackgroundWhite: + fp << "\33[47m"; + break; + } + + if (color & Console_Bold) + fp << "\33[1m"; +} +#else /* _WIN32 */ +void Console::SetWindowsConsoleColor(std::ostream& fp, int color) +{ + CONSOLE_SCREEN_BUFFER_INFO consoleInfo; + HANDLE hConsole; + + if (&fp == &std::cout) + hConsole = GetStdHandle(STD_OUTPUT_HANDLE); + else if (&fp == &std::cerr) + hConsole = GetStdHandle(STD_ERROR_HANDLE); + else + return; + + if (!GetConsoleScreenBufferInfo(hConsole, &consoleInfo)) + return; + + WORD attrs = 0; + + if (color == Console_Normal) + attrs = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE; + + switch (color & 0xff) { + case Console_ForegroundBlack: + attrs |= 0; + break; + case Console_ForegroundRed: + attrs |= FOREGROUND_RED; + break; + case Console_ForegroundGreen: + attrs |= FOREGROUND_GREEN; + break; + case Console_ForegroundYellow: + attrs |= FOREGROUND_RED | FOREGROUND_GREEN; + break; + case Console_ForegroundBlue: + attrs |= FOREGROUND_BLUE; + break; + case Console_ForegroundMagenta: + attrs |= FOREGROUND_RED | FOREGROUND_BLUE; + break; + case Console_ForegroundCyan: + attrs |= FOREGROUND_GREEN | FOREGROUND_BLUE; + break; + case Console_ForegroundWhite: + attrs |= FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE; + break; + } + + switch (color & 0xff00) { + case Console_BackgroundBlack: + attrs |= 0; + break; + case Console_BackgroundRed: + attrs |= BACKGROUND_RED; + break; + case Console_BackgroundGreen: + attrs |= BACKGROUND_GREEN; + break; + case Console_BackgroundYellow: + attrs |= BACKGROUND_RED | BACKGROUND_GREEN; + break; + case Console_BackgroundBlue: + attrs |= BACKGROUND_BLUE; + break; + case Console_BackgroundMagenta: + attrs |= BACKGROUND_RED | BACKGROUND_BLUE; + break; + case Console_BackgroundCyan: + attrs |= BACKGROUND_GREEN | BACKGROUND_BLUE; + break; + case Console_BackgroundWhite: + attrs |= BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_BLUE; + break; + } + + if (color & Console_Bold) + attrs |= FOREGROUND_INTENSITY; + + SetConsoleTextAttribute(hConsole, attrs); +} +#endif /* _WIN32 */ diff --git a/lib/base/console.hpp b/lib/base/console.hpp new file mode 100644 index 0000000..f5b8c9a --- /dev/null +++ b/lib/base/console.hpp @@ -0,0 +1,91 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#ifndef CONSOLE_H +#define CONSOLE_H + +#include "base/i2-base.hpp" +#include <iosfwd> + +namespace icinga +{ + +enum ConsoleColor +{ + Console_Normal, + + // bit 0-7: foreground + Console_ForegroundBlack = 1, + Console_ForegroundRed = 2, + Console_ForegroundGreen = 3, + Console_ForegroundYellow = 4, + Console_ForegroundBlue = 5, + Console_ForegroundMagenta = 6, + Console_ForegroundCyan = 7, + Console_ForegroundWhite = 8, + + // bit 8-15: background + Console_BackgroundBlack = 256, + Console_BackgroundRed = 266, + Console_BackgroundGreen = 267, + Console_BackgroundYellow = 268, + Console_BackgroundBlue = 269, + Console_BackgroundMagenta = 270, + Console_BackgroundCyan = 271, + Console_BackgroundWhite = 272, + + // bit 16-23: flags + Console_Bold = 65536 +}; + +enum ConsoleType +{ + Console_Autodetect = -1, + + Console_Dumb, +#ifndef _WIN32 + Console_VT100, +#else /* _WIN32 */ + Console_Windows, +#endif /* _WIN32 */ +}; + +class ConsoleColorTag +{ +public: + ConsoleColorTag(int color, ConsoleType consoleType = Console_Autodetect); + + friend std::ostream& operator<<(std::ostream& fp, const ConsoleColorTag& cct); + +private: + int m_Color; + int m_ConsoleType; +}; + +std::ostream& operator<<(std::ostream& fp, const ConsoleColorTag& cct); + +/** + * Console utilities. + * + * @ingroup base + */ +class Console +{ +public: + static void DetectType(); + + static void SetType(std::ostream& fp, ConsoleType type); + static ConsoleType GetType(std::ostream& fp); + +#ifndef _WIN32 + static void PrintVT100ColorCode(std::ostream& fp, int color); +#else /* _WIN32 */ + static void SetWindowsConsoleColor(std::ostream& fp, int color); +#endif /* _WIN32 */ + +private: + Console(); +}; + +} + +#endif /* CONSOLE_H */ diff --git a/lib/base/context.cpp b/lib/base/context.cpp new file mode 100644 index 0000000..9c0a781 --- /dev/null +++ b/lib/base/context.cpp @@ -0,0 +1,64 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#include "base/context.hpp" +#include <boost/thread/tss.hpp> +#include <iostream> +#include <sstream> +#include <utility> + +using namespace icinga; + +static boost::thread_specific_ptr<std::vector<std::function<void(std::ostream&)>>> l_Frames; + +ContextFrame::ContextFrame(std::function<void(std::ostream&)> message) +{ + GetFrames().emplace_back(std::move(message)); +} + +ContextFrame::~ContextFrame() +{ + GetFrames().pop_back(); +} + +std::vector<std::function<void(std::ostream&)>>& ContextFrame::GetFrames() +{ + if (!l_Frames.get()) + l_Frames.reset(new std::vector<std::function<void(std::ostream&)>>()); + + return *l_Frames; +} + +ContextTrace::ContextTrace() +{ + for (auto frame (ContextFrame::GetFrames().rbegin()); frame != ContextFrame::GetFrames().rend(); ++frame) { + std::ostringstream oss; + + (*frame)(oss); + m_Frames.emplace_back(oss.str()); + } +} + +void ContextTrace::Print(std::ostream& fp) const +{ + if (m_Frames.empty()) + return; + + fp << "\n"; + + int i = 0; + for (const String& frame : m_Frames) { + fp << "\t(" << i << ") " << frame << "\n"; + i++; + } +} + +size_t ContextTrace::GetLength() const +{ + return m_Frames.size(); +} + +std::ostream& icinga::operator<<(std::ostream& stream, const ContextTrace& trace) +{ + trace.Print(stream); + return stream; +} diff --git a/lib/base/context.hpp b/lib/base/context.hpp new file mode 100644 index 0000000..d6fe733 --- /dev/null +++ b/lib/base/context.hpp @@ -0,0 +1,54 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#ifndef CONTEXT_H +#define CONTEXT_H + +#include "base/i2-base.hpp" +#include "base/string.hpp" +#include <functional> +#include <vector> + +namespace icinga +{ + +class ContextTrace +{ +public: + ContextTrace(); + + void Print(std::ostream& fp) const; + + size_t GetLength() const; + +private: + std::vector<String> m_Frames; +}; + +std::ostream& operator<<(std::ostream& stream, const ContextTrace& trace); + +/** + * A context frame. + * + * @ingroup base + */ +class ContextFrame +{ +public: + ContextFrame(std::function<void(std::ostream&)> message); + ~ContextFrame(); + +private: + static std::vector<std::function<void(std::ostream&)>>& GetFrames(); + + friend class ContextTrace; +}; + +/* The currentContextFrame variable has to be volatile in order to prevent + * the compiler from optimizing it away. */ +#define CONTEXT(message) volatile icinga::ContextFrame currentContextFrame ([&](std::ostream& _CONTEXT_stream) { \ +_CONTEXT_stream << message; \ +}) + +} + +#endif /* CONTEXT_H */ diff --git a/lib/base/convert.cpp b/lib/base/convert.cpp new file mode 100644 index 0000000..19d3e44 --- /dev/null +++ b/lib/base/convert.cpp @@ -0,0 +1,46 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#include "base/convert.hpp" +#include "base/datetime.hpp" +#include <boost/lexical_cast.hpp> +#include <iomanip> + +using namespace icinga; + +String Convert::ToString(const String& val) +{ + return val; +} + +String Convert::ToString(const Value& val) +{ + return val; +} + +String Convert::ToString(double val) +{ + double integral; + double fractional = std::modf(val, &integral); + + std::ostringstream msgbuf; + if (fractional == 0) { + msgbuf << std::setprecision(0); + } + msgbuf << std::fixed << val; + return msgbuf.str(); +} + +double Convert::ToDateTimeValue(double val) +{ + return val; +} + +double Convert::ToDateTimeValue(const Value& val) +{ + if (val.IsNumber()) + return val; + else if (val.IsObjectType<DateTime>()) + return static_cast<DateTime::Ptr>(val)->GetValue(); + else + BOOST_THROW_EXCEPTION(std::invalid_argument("Not a DateTime value.")); +} diff --git a/lib/base/convert.hpp b/lib/base/convert.hpp new file mode 100644 index 0000000..e0754b3 --- /dev/null +++ b/lib/base/convert.hpp @@ -0,0 +1,84 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#ifndef CONVERT_H +#define CONVERT_H + +#include "base/i2-base.hpp" +#include "base/value.hpp" +#include <boost/lexical_cast.hpp> + +namespace icinga +{ + +/** + * Utility class for converting types. + * + * @ingroup base + */ +class Convert +{ +public: + template<typename T> + static long ToLong(const T& val) + { + try { + return boost::lexical_cast<long>(val); + } catch (const std::exception&) { + std::ostringstream msgbuf; + msgbuf << "Can't convert '" << val << "' to an integer."; + BOOST_THROW_EXCEPTION(std::invalid_argument(msgbuf.str())); + } + } + + template<typename T> + static double ToDouble(const T& val) + { + try { + return boost::lexical_cast<double>(val); + } catch (const std::exception&) { + std::ostringstream msgbuf; + msgbuf << "Can't convert '" << val << "' to a floating point number."; + BOOST_THROW_EXCEPTION(std::invalid_argument(msgbuf.str())); + } + } + + static long ToLong(const Value& val) + { + return val; + } + + static long ToLong(double val) + { + return static_cast<long>(val); + } + + static double ToDouble(const Value& val) + { + return val; + } + + static bool ToBool(const Value& val) + { + return val.ToBool(); + } + + template<typename T> + static String ToString(const T& val) + { + return boost::lexical_cast<std::string>(val); + } + + static String ToString(const String& val); + static String ToString(const Value& val); + static String ToString(double val); + + static double ToDateTimeValue(double val); + static double ToDateTimeValue(const Value& val); + +private: + Convert(); +}; + +} + +#endif /* CONVERT_H */ diff --git a/lib/base/datetime-script.cpp b/lib/base/datetime-script.cpp new file mode 100644 index 0000000..6c18381 --- /dev/null +++ b/lib/base/datetime-script.cpp @@ -0,0 +1,28 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#include "base/datetime.hpp" +#include "base/function.hpp" +#include "base/functionwrapper.hpp" +#include "base/scriptframe.hpp" +#include "base/objectlock.hpp" + +using namespace icinga; + +static String DateTimeFormat(const String& format) +{ + ScriptFrame *vframe = ScriptFrame::GetCurrentFrame(); + DateTime::Ptr self = static_cast<DateTime::Ptr>(vframe->Self); + REQUIRE_NOT_NULL(self); + + return self->Format(format); +} + +Object::Ptr DateTime::GetPrototype() +{ + static Dictionary::Ptr prototype = new Dictionary({ + { "format", new Function("DateTime#format", DateTimeFormat, { "format" }) } + }); + + return prototype; +} + diff --git a/lib/base/datetime.cpp b/lib/base/datetime.cpp new file mode 100644 index 0000000..aa7b5e5 --- /dev/null +++ b/lib/base/datetime.cpp @@ -0,0 +1,58 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#include "base/datetime.hpp" +#include "base/datetime-ti.cpp" +#include "base/utility.hpp" +#include "base/primitivetype.hpp" + +using namespace icinga; + +REGISTER_TYPE_WITH_PROTOTYPE(DateTime, DateTime::GetPrototype()); + +DateTime::DateTime(double value) + : m_Value(value) +{ } + +DateTime::DateTime(const std::vector<Value>& args) +{ + if (args.empty()) + m_Value = Utility::GetTime(); + else if (args.size() == 3 || args.size() == 6) { + struct tm tms; + tms.tm_year = args[0] - 1900; + tms.tm_mon = args[1] - 1; + tms.tm_mday = args[2]; + + if (args.size() == 6) { + tms.tm_hour = args[3]; + tms.tm_min = args[4]; + tms.tm_sec = args[5]; + } else { + tms.tm_hour = 0; + tms.tm_min = 0; + tms.tm_sec = 0; + } + + tms.tm_isdst = -1; + + m_Value = mktime(&tms); + } else if (args.size() == 1) + m_Value = args[0]; + else + BOOST_THROW_EXCEPTION(std::invalid_argument("Invalid number of arguments for the DateTime constructor.")); +} + +double DateTime::GetValue() const +{ + return m_Value; +} + +String DateTime::Format(const String& format) const +{ + return Utility::FormatDateTime(format.CStr(), m_Value); +} + +String DateTime::ToString() const +{ + return Format("%Y-%m-%d %H:%M:%S %z"); +} diff --git a/lib/base/datetime.hpp b/lib/base/datetime.hpp new file mode 100644 index 0000000..e7b8a1f --- /dev/null +++ b/lib/base/datetime.hpp @@ -0,0 +1,40 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#ifndef DATETIME_H +#define DATETIME_H + +#include "base/i2-base.hpp" +#include "base/datetime-ti.hpp" +#include "base/value.hpp" +#include <vector> + +namespace icinga +{ + +/** + * A date/time value. + * + * @ingroup base + */ +class DateTime final : public ObjectImpl<DateTime> +{ +public: + DECLARE_OBJECT(DateTime); + + DateTime(double value); + DateTime(const std::vector<Value>& args); + + String Format(const String& format) const; + + double GetValue() const override; + String ToString() const override; + + static Object::Ptr GetPrototype(); + +private: + double m_Value; +}; + +} + +#endif /* DATETIME_H */ diff --git a/lib/base/datetime.ti b/lib/base/datetime.ti new file mode 100644 index 0000000..b9d7375 --- /dev/null +++ b/lib/base/datetime.ti @@ -0,0 +1,15 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +library base; + +namespace icinga +{ + +vararg_constructor class DateTime +{ + [state, no_storage] Timestamp value { + get; + }; +}; + +} diff --git a/lib/base/debug.hpp b/lib/base/debug.hpp new file mode 100644 index 0000000..54b424c --- /dev/null +++ b/lib/base/debug.hpp @@ -0,0 +1,49 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#ifndef DEBUG_H +#define DEBUG_H + +#include "i2-base.hpp" + +#ifndef I2_DEBUG +# define ASSERT(expr) ((void)0) +#else /* I2_DEBUG */ +# define ASSERT(expr) ((expr) ? 0 : icinga_assert_fail(#expr, __FILE__, __LINE__)) +#endif /* I2_DEBUG */ + +#define VERIFY(expr) ((expr) ? 0 : icinga_assert_fail(#expr, __FILE__, __LINE__)) + +#ifdef _MSC_VER +# define NORETURNPRE __declspec(noreturn) +#else /* _MSC_VER */ +# define NORETURNPRE +#endif /* _MSC_VER */ + +#ifdef __GNUC__ +# define NORETURNPOST __attribute__((noreturn)) +#else /* __GNUC__ */ +# define NORETURNPOST +#endif /* __GNUC__ */ + +NORETURNPRE int icinga_assert_fail(const char *expr, const char *file, int line) NORETURNPOST; + +#ifdef _MSC_VER +# pragma warning( push ) +# pragma warning( disable : 4646 ) /* function declared with __declspec(noreturn) has non-void return type */ +#endif /* _MSC_VER */ + +inline int icinga_assert_fail(const char *expr, const char *file, int line) +{ + fprintf(stderr, "%s:%d: assertion failed: %s\n", file, line, expr); + std::abort(); + +#if !defined(__GNUC__) && !defined(_MSC_VER) + return 0; +#endif /* !defined(__GNUC__) && !defined(_MSC_VER) */ +} + +#ifdef _MSC_VER +# pragma warning( pop ) +#endif /* _MSC_VER */ + +#endif /* DEBUG_H */ diff --git a/lib/base/debuginfo.cpp b/lib/base/debuginfo.cpp new file mode 100644 index 0000000..99006ac --- /dev/null +++ b/lib/base/debuginfo.cpp @@ -0,0 +1,98 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#include "base/debuginfo.hpp" +#include "base/convert.hpp" +#include <fstream> + +using namespace icinga; + +/** + * Outputs a DebugInfo struct to a stream. + * + * @param out The output stream. + * @param val The DebugInfo struct. + * @returns The output stream. + */ +std::ostream& icinga::operator<<(std::ostream& out, const DebugInfo& val) +{ + out << "in " << val.Path << ": " + << val.FirstLine << ":" << val.FirstColumn + << "-" + << val.LastLine << ":" << val.LastColumn; + + return out; +} + +DebugInfo icinga::DebugInfoRange(const DebugInfo& start, const DebugInfo& end) +{ + DebugInfo result; + result.Path = start.Path; + result.FirstLine = start.FirstLine; + result.FirstColumn = start.FirstColumn; + result.LastLine = end.LastLine; + result.LastColumn = end.LastColumn; + return result; +} + +#define EXTRA_LINES 2 + +void icinga::ShowCodeLocation(std::ostream& out, const DebugInfo& di, bool verbose) +{ + if (di.Path.IsEmpty()) + return; + + out << "Location: " << di; + + std::ifstream ifs; + ifs.open(di.Path.CStr(), std::ifstream::in); + + int lineno = 0; + char line[1024]; + + while (ifs.good() && lineno <= di.LastLine + EXTRA_LINES) { + if (lineno == 0) + out << "\n"; + + lineno++; + + ifs.getline(line, sizeof(line)); + + for (int i = 0; line[i]; i++) + if (line[i] == '\t') + line[i] = ' '; + + int extra_lines = verbose ? EXTRA_LINES : 0; + + if (lineno < di.FirstLine - extra_lines || lineno > di.LastLine + extra_lines) + continue; + + String pathInfo = di.Path + "(" + Convert::ToString(lineno) + "): "; + out << pathInfo; + out << line << "\n"; + + if (lineno >= di.FirstLine && lineno <= di.LastLine) { + int start, end; + + start = 0; + end = strlen(line); + + if (lineno == di.FirstLine) + start = di.FirstColumn - 1; + + if (lineno == di.LastLine) + end = di.LastColumn; + + if (start < 0) { + end -= start; + start = 0; + } + + out << String(pathInfo.GetLength(), ' '); + out << String(start, ' '); + out << String(end - start, '^'); + + out << "\n"; + } + } +} + diff --git a/lib/base/debuginfo.hpp b/lib/base/debuginfo.hpp new file mode 100644 index 0000000..d47db91 --- /dev/null +++ b/lib/base/debuginfo.hpp @@ -0,0 +1,36 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#ifndef DEBUGINFO_H +#define DEBUGINFO_H + +#include "base/i2-base.hpp" +#include "base/string.hpp" + +namespace icinga +{ + +/** + * Debug information for a configuration element. + * + * @ingroup config + */ +struct DebugInfo +{ + String Path; + + int FirstLine{0}; + int FirstColumn{0}; + + int LastLine{0}; + int LastColumn{0}; +}; + +std::ostream& operator<<(std::ostream& out, const DebugInfo& val); + +DebugInfo DebugInfoRange(const DebugInfo& start, const DebugInfo& end); + +void ShowCodeLocation(std::ostream& out, const DebugInfo& di, bool verbose = true); + +} + +#endif /* DEBUGINFO_H */ diff --git a/lib/base/defer.hpp b/lib/base/defer.hpp new file mode 100644 index 0000000..2a23261 --- /dev/null +++ b/lib/base/defer.hpp @@ -0,0 +1,54 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#ifndef DEFER +#define DEFER + +#include <functional> +#include <utility> + +namespace icinga +{ + +/** + * An action to be executed at end of scope. + * + * @ingroup base + */ +class Defer +{ +public: + inline + Defer(std::function<void()> func) : m_Func(std::move(func)) + { + } + + Defer(const Defer&) = delete; + Defer(Defer&&) = delete; + Defer& operator=(const Defer&) = delete; + Defer& operator=(Defer&&) = delete; + + inline + ~Defer() + { + if (m_Func) { + try { + m_Func(); + } catch (...) { + // https://stackoverflow.com/questions/130117/throwing-exceptions-out-of-a-destructor + } + } + } + + inline + void Cancel() + { + m_Func = nullptr; + } + +private: + std::function<void()> m_Func; +}; + +} + +#endif /* DEFER */ diff --git a/lib/base/dependencygraph.cpp b/lib/base/dependencygraph.cpp new file mode 100644 index 0000000..025eb3e --- /dev/null +++ b/lib/base/dependencygraph.cpp @@ -0,0 +1,50 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#include "base/dependencygraph.hpp" + +using namespace icinga; + +std::mutex DependencyGraph::m_Mutex; +std::map<Object *, std::map<Object *, int> > DependencyGraph::m_Dependencies; + +void DependencyGraph::AddDependency(Object *parent, Object *child) +{ + std::unique_lock<std::mutex> lock(m_Mutex); + m_Dependencies[child][parent]++; +} + +void DependencyGraph::RemoveDependency(Object *parent, Object *child) +{ + std::unique_lock<std::mutex> lock(m_Mutex); + + auto& refs = m_Dependencies[child]; + auto it = refs.find(parent); + + if (it == refs.end()) + return; + + it->second--; + + if (it->second == 0) + refs.erase(it); + + if (refs.empty()) + m_Dependencies.erase(child); +} + +std::vector<Object::Ptr> DependencyGraph::GetParents(const Object::Ptr& child) +{ + std::vector<Object::Ptr> objects; + + std::unique_lock<std::mutex> lock(m_Mutex); + auto it = m_Dependencies.find(child.get()); + + if (it != m_Dependencies.end()) { + typedef std::pair<Object *, int> kv_pair; + for (const kv_pair& kv : it->second) { + objects.emplace_back(kv.first); + } + } + + return objects; +} diff --git a/lib/base/dependencygraph.hpp b/lib/base/dependencygraph.hpp new file mode 100644 index 0000000..51aa90e --- /dev/null +++ b/lib/base/dependencygraph.hpp @@ -0,0 +1,34 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#ifndef DEPENDENCYGRAPH_H +#define DEPENDENCYGRAPH_H + +#include "base/i2-base.hpp" +#include "base/object.hpp" +#include <map> +#include <mutex> + +namespace icinga { + +/** + * A graph that tracks dependencies between objects. + * + * @ingroup base + */ +class DependencyGraph +{ +public: + static void AddDependency(Object *parent, Object *child); + static void RemoveDependency(Object *parent, Object *child); + static std::vector<Object::Ptr> GetParents(const Object::Ptr& child); + +private: + DependencyGraph(); + + static std::mutex m_Mutex; + static std::map<Object *, std::map<Object *, int> > m_Dependencies; +}; + +} + +#endif /* DEPENDENCYGRAPH_H */ diff --git a/lib/base/dictionary-script.cpp b/lib/base/dictionary-script.cpp new file mode 100644 index 0000000..ad19c5b --- /dev/null +++ b/lib/base/dictionary-script.cpp @@ -0,0 +1,119 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#include "base/dictionary.hpp" +#include "base/function.hpp" +#include "base/functionwrapper.hpp" +#include "base/scriptframe.hpp" +#include "base/array.hpp" + +using namespace icinga; + +static double DictionaryLen() +{ + ScriptFrame *vframe = ScriptFrame::GetCurrentFrame(); + Dictionary::Ptr self = static_cast<Dictionary::Ptr>(vframe->Self); + REQUIRE_NOT_NULL(self); + return self->GetLength(); +} + +static void DictionarySet(const String& key, const Value& value) +{ + ScriptFrame *vframe = ScriptFrame::GetCurrentFrame(); + Dictionary::Ptr self = static_cast<Dictionary::Ptr>(vframe->Self); + REQUIRE_NOT_NULL(self); + self->Set(key, value); +} + +static Value DictionaryGet(const String& key) +{ + ScriptFrame *vframe = ScriptFrame::GetCurrentFrame(); + Dictionary::Ptr self = static_cast<Dictionary::Ptr>(vframe->Self); + REQUIRE_NOT_NULL(self); + return self->Get(key); +} + +static void DictionaryRemove(const String& key) +{ + ScriptFrame *vframe = ScriptFrame::GetCurrentFrame(); + Dictionary::Ptr self = static_cast<Dictionary::Ptr>(vframe->Self); + REQUIRE_NOT_NULL(self); + self->Remove(key); +} + +static void DictionaryClear() +{ + ScriptFrame *vframe = ScriptFrame::GetCurrentFrame(); + Dictionary::Ptr self = static_cast<Dictionary::Ptr>(vframe->Self); + REQUIRE_NOT_NULL(self); + self->Clear(); +} + +static bool DictionaryContains(const String& key) +{ + ScriptFrame *vframe = ScriptFrame::GetCurrentFrame(); + Dictionary::Ptr self = static_cast<Dictionary::Ptr>(vframe->Self); + REQUIRE_NOT_NULL(self); + return self->Contains(key); +} + +static Dictionary::Ptr DictionaryShallowClone() +{ + ScriptFrame *vframe = ScriptFrame::GetCurrentFrame(); + Dictionary::Ptr self = static_cast<Dictionary::Ptr>(vframe->Self); + REQUIRE_NOT_NULL(self); + return self->ShallowClone(); +} + +static Array::Ptr DictionaryKeys() +{ + ScriptFrame *vframe = ScriptFrame::GetCurrentFrame(); + Dictionary::Ptr self = static_cast<Dictionary::Ptr>(vframe->Self); + REQUIRE_NOT_NULL(self); + + ArrayData keys; + ObjectLock olock(self); + for (const Dictionary::Pair& kv : self) { + keys.push_back(kv.first); + } + return new Array(std::move(keys)); +} + +static Array::Ptr DictionaryValues() +{ + ScriptFrame *vframe = ScriptFrame::GetCurrentFrame(); + Dictionary::Ptr self = static_cast<Dictionary::Ptr>(vframe->Self); + REQUIRE_NOT_NULL(self); + + ArrayData values; + ObjectLock olock(self); + for (const Dictionary::Pair& kv : self) { + values.push_back(kv.second); + } + return new Array(std::move(values)); +} + +static void DictionaryFreeze() +{ + ScriptFrame *vframe = ScriptFrame::GetCurrentFrame(); + Dictionary::Ptr self = static_cast<Dictionary::Ptr>(vframe->Self); + self->Freeze(); +} + +Object::Ptr Dictionary::GetPrototype() +{ + static Dictionary::Ptr prototype = new Dictionary({ + { "len", new Function("Dictionary#len", DictionaryLen, {}, true) }, + { "set", new Function("Dictionary#set", DictionarySet, { "key", "value" }) }, + { "get", new Function("Dictionary#get", DictionaryGet, { "key" }) }, + { "remove", new Function("Dictionary#remove", DictionaryRemove, { "key" }) }, + { "clear", new Function("Dictionary#clear", DictionaryClear, {}) }, + { "contains", new Function("Dictionary#contains", DictionaryContains, { "key" }, true) }, + { "shallow_clone", new Function("Dictionary#shallow_clone", DictionaryShallowClone, {}, true) }, + { "keys", new Function("Dictionary#keys", DictionaryKeys, {}, true) }, + { "values", new Function("Dictionary#values", DictionaryValues, {}, true) }, + { "freeze", new Function("Dictionary#freeze", DictionaryFreeze, {}) } + }); + + return prototype; +} + diff --git a/lib/base/dictionary.cpp b/lib/base/dictionary.cpp new file mode 100644 index 0000000..8d3f80d --- /dev/null +++ b/lib/base/dictionary.cpp @@ -0,0 +1,317 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#include "base/dictionary.hpp" +#include "base/objectlock.hpp" +#include "base/debug.hpp" +#include "base/primitivetype.hpp" +#include "base/configwriter.hpp" +#include <sstream> + +using namespace icinga; + +template class std::map<String, Value>; + +REGISTER_PRIMITIVE_TYPE(Dictionary, Object, Dictionary::GetPrototype()); + +Dictionary::Dictionary(const DictionaryData& other) +{ + for (const auto& kv : other) + m_Data.insert(kv); +} + +Dictionary::Dictionary(DictionaryData&& other) +{ + for (auto& kv : other) + m_Data.insert(std::move(kv)); +} + +Dictionary::Dictionary(std::initializer_list<Dictionary::Pair> init) + : m_Data(init) +{ } + +/** + * Retrieves a value from a dictionary. + * + * @param key The key whose value should be retrieved. + * @returns The value of an empty value if the key was not found. + */ +Value Dictionary::Get(const String& key) const +{ + std::shared_lock<std::shared_timed_mutex> lock (m_DataMutex); + + auto it = m_Data.find(key); + + if (it == m_Data.end()) + return Empty; + + return it->second; +} + +/** + * Retrieves a value from a dictionary. + * + * @param key The key whose value should be retrieved. + * @param result The value of the dictionary item (only set when the key exists) + * @returns true if the key exists, false otherwise. + */ +bool Dictionary::Get(const String& key, Value *result) const +{ + std::shared_lock<std::shared_timed_mutex> lock (m_DataMutex); + + auto it = m_Data.find(key); + + if (it == m_Data.end()) + return false; + + *result = it->second; + return true; +} + +/** + * Retrieves a value's address from a dictionary. + * + * @param key The key whose value's address should be retrieved. + * @returns nullptr if the key was not found. + */ +const Value * Dictionary::GetRef(const String& key) const +{ + std::shared_lock<std::shared_timed_mutex> lock (m_DataMutex); + auto it (m_Data.find(key)); + + return it == m_Data.end() ? nullptr : &it->second; +} + +/** + * Sets a value in the dictionary. + * + * @param key The key. + * @param value The value. + * @param overrideFrozen Whether to allow modifying frozen dictionaries. + */ +void Dictionary::Set(const String& key, Value value, bool overrideFrozen) +{ + ObjectLock olock(this); + std::unique_lock<std::shared_timed_mutex> lock (m_DataMutex); + + if (m_Frozen && !overrideFrozen) + BOOST_THROW_EXCEPTION(std::invalid_argument("Value in dictionary must not be modified.")); + + m_Data[key] = std::move(value); +} + +/** + * Returns the number of elements in the dictionary. + * + * @returns Number of elements. + */ +size_t Dictionary::GetLength() const +{ + std::shared_lock<std::shared_timed_mutex> lock (m_DataMutex); + + return m_Data.size(); +} + +/** + * Checks whether the dictionary contains the specified key. + * + * @param key The key. + * @returns true if the dictionary contains the key, false otherwise. + */ +bool Dictionary::Contains(const String& key) const +{ + std::shared_lock<std::shared_timed_mutex> lock (m_DataMutex); + + return (m_Data.find(key) != m_Data.end()); +} + +/** + * Returns an iterator to the beginning of the dictionary. + * + * Note: Caller must hold the object lock while using the iterator. + * + * @returns An iterator. + */ +Dictionary::Iterator Dictionary::Begin() +{ + ASSERT(OwnsLock()); + + return m_Data.begin(); +} + +/** + * Returns an iterator to the end of the dictionary. + * + * Note: Caller must hold the object lock while using the iterator. + * + * @returns An iterator. + */ +Dictionary::Iterator Dictionary::End() +{ + ASSERT(OwnsLock()); + + return m_Data.end(); +} + +/** + * Removes the item specified by the iterator from the dictionary. + * + * @param it The iterator. + */ +void Dictionary::Remove(Dictionary::Iterator it) +{ + ASSERT(OwnsLock()); + std::unique_lock<std::shared_timed_mutex> lock (m_DataMutex); + + if (m_Frozen) + BOOST_THROW_EXCEPTION(std::invalid_argument("Dictionary must not be modified.")); + + m_Data.erase(it); +} + +/** + * Removes the specified key from the dictionary. + * + * @param key The key. + */ +void Dictionary::Remove(const String& key) +{ + ObjectLock olock(this); + std::unique_lock<std::shared_timed_mutex> lock (m_DataMutex); + + if (m_Frozen) + BOOST_THROW_EXCEPTION(std::invalid_argument("Dictionary must not be modified.")); + + Dictionary::Iterator it; + it = m_Data.find(key); + + if (it == m_Data.end()) + return; + + m_Data.erase(it); +} + +/** + * Removes all dictionary items. + */ +void Dictionary::Clear() +{ + ObjectLock olock(this); + std::unique_lock<std::shared_timed_mutex> lock (m_DataMutex); + + if (m_Frozen) + BOOST_THROW_EXCEPTION(std::invalid_argument("Dictionary must not be modified.")); + + m_Data.clear(); +} + +void Dictionary::CopyTo(const Dictionary::Ptr& dest) const +{ + std::shared_lock<std::shared_timed_mutex> lock (m_DataMutex); + + for (const Dictionary::Pair& kv : m_Data) { + dest->Set(kv.first, kv.second); + } +} + +/** + * Makes a shallow copy of a dictionary. + * + * @returns a copy of the dictionary. + */ +Dictionary::Ptr Dictionary::ShallowClone() const +{ + Dictionary::Ptr clone = new Dictionary(); + CopyTo(clone); + return clone; +} + +/** + * Makes a deep clone of a dictionary + * and its elements. + * + * @returns a copy of the dictionary. + */ +Object::Ptr Dictionary::Clone() const +{ + DictionaryData dict; + + { + std::shared_lock<std::shared_timed_mutex> lock (m_DataMutex); + + dict.reserve(GetLength()); + + for (const Dictionary::Pair& kv : m_Data) { + dict.emplace_back(kv.first, kv.second.Clone()); + } + } + + return new Dictionary(std::move(dict)); +} + +/** + * Returns an ordered vector containing all keys + * which are currently set in this directory. + * + * @returns an ordered vector of key names + */ +std::vector<String> Dictionary::GetKeys() const +{ + std::shared_lock<std::shared_timed_mutex> lock (m_DataMutex); + + std::vector<String> keys; + + for (const Dictionary::Pair& kv : m_Data) { + keys.push_back(kv.first); + } + + return keys; +} + +String Dictionary::ToString() const +{ + std::ostringstream msgbuf; + ConfigWriter::EmitScope(msgbuf, 1, const_cast<Dictionary *>(this)); + return msgbuf.str(); +} + +void Dictionary::Freeze() +{ + ObjectLock olock(this); + m_Frozen = true; +} + +Value Dictionary::GetFieldByName(const String& field, bool, const DebugInfo& debugInfo) const +{ + Value value; + + if (Get(field, &value)) + return value; + else + return GetPrototypeField(const_cast<Dictionary *>(this), field, false, debugInfo); +} + +void Dictionary::SetFieldByName(const String& field, const Value& value, bool overrideFrozen, const DebugInfo&) +{ + Set(field, value, overrideFrozen); +} + +bool Dictionary::HasOwnField(const String& field) const +{ + return Contains(field); +} + +bool Dictionary::GetOwnField(const String& field, Value *result) const +{ + return Get(field, result); +} + +Dictionary::Iterator icinga::begin(const Dictionary::Ptr& x) +{ + return x->Begin(); +} + +Dictionary::Iterator icinga::end(const Dictionary::Ptr& x) +{ + return x->End(); +} + diff --git a/lib/base/dictionary.hpp b/lib/base/dictionary.hpp new file mode 100644 index 0000000..ffccd63 --- /dev/null +++ b/lib/base/dictionary.hpp @@ -0,0 +1,91 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#ifndef DICTIONARY_H +#define DICTIONARY_H + +#include "base/i2-base.hpp" +#include "base/object.hpp" +#include "base/value.hpp" +#include <boost/range/iterator.hpp> +#include <map> +#include <shared_mutex> +#include <vector> + +namespace icinga +{ + +typedef std::vector<std::pair<String, Value> > DictionaryData; + +/** + * A container that holds key-value pairs. + * + * @ingroup base + */ +class Dictionary final : public Object +{ +public: + DECLARE_OBJECT(Dictionary); + + /** + * An iterator that can be used to iterate over dictionary elements. + */ + typedef std::map<String, Value>::iterator Iterator; + + typedef std::map<String, Value>::size_type SizeType; + + typedef std::map<String, Value>::value_type Pair; + + Dictionary() = default; + Dictionary(const DictionaryData& other); + Dictionary(DictionaryData&& other); + Dictionary(std::initializer_list<Pair> init); + + Value Get(const String& key) const; + bool Get(const String& key, Value *result) const; + const Value * GetRef(const String& key) const; + void Set(const String& key, Value value, bool overrideFrozen = false); + bool Contains(const String& key) const; + + Iterator Begin(); + Iterator End(); + + size_t GetLength() const; + + void Remove(const String& key); + + void Remove(Iterator it); + + void Clear(); + + void CopyTo(const Dictionary::Ptr& dest) const; + Dictionary::Ptr ShallowClone() const; + + std::vector<String> GetKeys() const; + + static Object::Ptr GetPrototype(); + + Object::Ptr Clone() const override; + + String ToString() const override; + + void Freeze(); + + Value GetFieldByName(const String& field, bool sandboxed, const DebugInfo& debugInfo) const override; + void SetFieldByName(const String& field, const Value& value, bool overrideFrozen, const DebugInfo& debugInfo) override; + bool HasOwnField(const String& field) const override; + bool GetOwnField(const String& field, Value *result) const override; + +private: + std::map<String, Value> m_Data; /**< The data for the dictionary. */ + mutable std::shared_timed_mutex m_DataMutex; + bool m_Frozen{false}; +}; + +Dictionary::Iterator begin(const Dictionary::Ptr& x); +Dictionary::Iterator end(const Dictionary::Ptr& x); + +} + +extern template class std::map<icinga::String, icinga::Value>; + +#endif /* DICTIONARY_H */ diff --git a/lib/base/exception.cpp b/lib/base/exception.cpp new file mode 100644 index 0000000..57b324b --- /dev/null +++ b/lib/base/exception.cpp @@ -0,0 +1,507 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#include "base/exception.hpp" +#include "base/stacktrace.hpp" +#include <boost/thread/tss.hpp> +#include <utility> + +#ifdef _WIN32 +# include "base/utility.hpp" +#endif /* _WIN32 */ + +#ifdef HAVE_CXXABI_H +# include <cxxabi.h> +#endif /* HAVE_CXXABI_H */ + +using namespace icinga; + +static boost::thread_specific_ptr<boost::stacktrace::stacktrace> l_LastExceptionStack; +static boost::thread_specific_ptr<ContextTrace> l_LastExceptionContext; + +#ifdef HAVE_CXXABI_H + +#ifdef _LIBCPPABI_VERSION +class libcxx_type_info : public std::type_info +{ +public: + ~libcxx_type_info() override; + + virtual void noop1() const; + virtual void noop2() const; + virtual bool can_catch(const libcxx_type_info *thrown_type, void *&adjustedPtr) const = 0; +}; +#endif /* _LIBCPPABI_VERSION */ + + +#if defined(__GLIBCXX__) || defined(_LIBCPPABI_VERSION) +/** + * Attempts to cast an exception to a destination type + * + * @param obj Exception to be casted + * @param src Type information of obj + * @param dst Information of which type to cast to + * @return Pointer to the exception if the cast is possible, nullptr otherwise + */ +inline void *cast_exception(void *obj, const std::type_info *src, const std::type_info *dst) +{ +#ifdef __GLIBCXX__ + void *thrown_ptr = obj; + + /* Check if the exception is a pointer type. */ + if (src->__is_pointer_p()) + thrown_ptr = *(void **)thrown_ptr; + + if (dst->__do_catch(src, &thrown_ptr, 1)) + return thrown_ptr; + else + return nullptr; +#else /* __GLIBCXX__ */ + const auto *srcInfo = static_cast<const libcxx_type_info *>(src); + const auto *dstInfo = static_cast<const libcxx_type_info *>(dst); + + void *adj = obj; + + if (dstInfo->can_catch(srcInfo, adj)) + return adj; + else + return nullptr; +#endif /* __GLIBCXX__ */ + +} +#else /* defined(__GLIBCXX__) || defined(_LIBCPPABI_VERSION) */ +#define NO_CAST_EXCEPTION +#endif /* defined(__GLIBCXX__) || defined(_LIBCPPABI_VERSION) */ + +# if __clang_major__ > 3 || (__clang_major__ == 3 && __clang_minor__ > 3) +# define TYPEINFO_TYPE std::type_info +# else +# define TYPEINFO_TYPE void +# endif + +# if !defined(__GLIBCXX__) && !defined(_WIN32) +static boost::thread_specific_ptr<void *> l_LastExceptionObj; +static boost::thread_specific_ptr<TYPEINFO_TYPE *> l_LastExceptionPvtInfo; + +typedef void (*DestCallback)(void *); +static boost::thread_specific_ptr<DestCallback> l_LastExceptionDest; +# endif /* !__GLIBCXX__ && !_WIN32 */ + +extern "C" void __cxa_throw(void *obj, TYPEINFO_TYPE *pvtinfo, void (*dest)(void *)); +#endif /* HAVE_CXXABI_H */ + +void icinga::RethrowUncaughtException() +{ +#if defined(__GLIBCXX__) || !defined(HAVE_CXXABI_H) + throw; +#else /* __GLIBCXX__ || !HAVE_CXXABI_H */ + __cxa_throw(*l_LastExceptionObj.get(), *l_LastExceptionPvtInfo.get(), *l_LastExceptionDest.get()); +#endif /* __GLIBCXX__ || !HAVE_CXXABI_H */ +} + +#ifdef HAVE_CXXABI_H +extern "C" +void __cxa_throw(void *obj, TYPEINFO_TYPE *pvtinfo, void (*dest)(void *)) +{ + /* This function overrides an internal function of libstdc++ that is called when a C++ exception is thrown in order + * to capture as much information as possible at that time and then call the original implementation. This + * information includes: + * - stack trace (for later use in DiagnosticInformation) + * - context trace (for later use in DiagnosticInformation) + */ + + auto *tinfo = static_cast<std::type_info *>(pvtinfo); + + typedef void (*cxa_throw_fn)(void *, std::type_info *, void (*)(void *)) __attribute__((noreturn)); + static cxa_throw_fn real_cxa_throw; + +#if !defined(__GLIBCXX__) && !defined(_WIN32) + l_LastExceptionObj.reset(new void *(obj)); + l_LastExceptionPvtInfo.reset(new TYPEINFO_TYPE *(pvtinfo)); + l_LastExceptionDest.reset(new DestCallback(dest)); +#endif /* !defined(__GLIBCXX__) && !defined(_WIN32) */ + + // resolve symbol to original implementation of __cxa_throw for the call at the end of this function + if (real_cxa_throw == nullptr) + real_cxa_throw = (cxa_throw_fn)dlsym(RTLD_NEXT, "__cxa_throw"); + +#ifndef NO_CAST_EXCEPTION + void *uex = cast_exception(obj, tinfo, &typeid(user_error)); + auto *ex = reinterpret_cast<boost::exception *>(cast_exception(obj, tinfo, &typeid(boost::exception))); + + if (!uex) { +#endif /* NO_CAST_EXCEPTION */ + // save the current stack trace in a thread-local variable + boost::stacktrace::stacktrace stack; + SetLastExceptionStack(stack); + +#ifndef NO_CAST_EXCEPTION + // save the current stack trace in the boost exception error info if the exception is a boost::exception + if (ex && !boost::get_error_info<StackTraceErrorInfo>(*ex)) + *ex << StackTraceErrorInfo(stack); + } +#endif /* NO_CAST_EXCEPTION */ + + ContextTrace context; + SetLastExceptionContext(context); + +#ifndef NO_CAST_EXCEPTION + // save the current context trace in the boost exception error info if the exception is a boost::exception + if (ex && !boost::get_error_info<ContextTraceErrorInfo>(*ex)) + *ex << ContextTraceErrorInfo(context); +#endif /* NO_CAST_EXCEPTION */ + + real_cxa_throw(obj, tinfo, dest); +} +#endif /* HAVE_CXXABI_H */ + +boost::stacktrace::stacktrace *icinga::GetLastExceptionStack() +{ + return l_LastExceptionStack.get(); +} + +void icinga::SetLastExceptionStack(const boost::stacktrace::stacktrace& trace) +{ + l_LastExceptionStack.reset(new boost::stacktrace::stacktrace(trace)); +} + +ContextTrace *icinga::GetLastExceptionContext() +{ + return l_LastExceptionContext.get(); +} + +void icinga::SetLastExceptionContext(const ContextTrace& context) +{ + l_LastExceptionContext.reset(new ContextTrace(context)); +} + +String icinga::DiagnosticInformation(const std::exception& ex, bool verbose, boost::stacktrace::stacktrace *stack, ContextTrace *context) +{ + std::ostringstream result; + + String message = ex.what(); + +#ifdef _WIN32 + const auto *win32_err = dynamic_cast<const win32_error *>(&ex); + if (win32_err) { + message = to_string(*win32_err); + } +#endif /* _WIN32 */ + + const auto *vex = dynamic_cast<const ValidationError *>(&ex); + + if (message.IsEmpty()) + result << boost::diagnostic_information(ex) << "\n"; + else + result << "Error: " << message << "\n"; + + const auto *dex = dynamic_cast<const ScriptError *>(&ex); + + if (dex && !dex->GetDebugInfo().Path.IsEmpty()) + ShowCodeLocation(result, dex->GetDebugInfo()); + + if (vex) { + DebugInfo di; + + ConfigObject::Ptr dobj = vex->GetObject(); + if (dobj) + di = dobj->GetDebugInfo(); + + Dictionary::Ptr currentHint = vex->GetDebugHint(); + Array::Ptr messages; + + if (currentHint) { + for (const String& attr : vex->GetAttributePath()) { + Dictionary::Ptr props = currentHint->Get("properties"); + + if (!props) + break; + + currentHint = props->Get(attr); + + if (!currentHint) + break; + + messages = currentHint->Get("messages"); + } + } + + if (messages && messages->GetLength() > 0) { + Array::Ptr message = messages->Get(messages->GetLength() - 1); + + di.Path = message->Get(1); + di.FirstLine = message->Get(2); + di.FirstColumn = message->Get(3); + di.LastLine = message->Get(4); + di.LastColumn = message->Get(5); + } + + if (!di.Path.IsEmpty()) + ShowCodeLocation(result, di); + } + + const auto *uex = dynamic_cast<const user_error *>(&ex); + const auto *pex = dynamic_cast<const posix_error *>(&ex); + + if (!uex && !pex && verbose) { + // Print the first of the following stack traces (if any exists) + // 1. stack trace from boost exception error information + const boost::stacktrace::stacktrace *st = boost::get_error_info<StackTraceErrorInfo>(ex); + // 2. stack trace explicitly passed as a parameter + if (!st) { + st = stack; + } + // 3. stack trace saved when the last exception was thrown + if (!st) { + st = GetLastExceptionStack(); + } + + if (st && !st->empty()) { + result << "\nStacktrace:\n" << StackTraceFormatter(*st); + } + } + + // Print the first of the following context traces (if any exists) + // 1. context trace from boost exception error information + const ContextTrace *ct = boost::get_error_info<ContextTraceErrorInfo>(ex); + // 2. context trace explicitly passed as a parameter + if (!ct) { + ct = context; + } + // 3. context trace saved when the last exception was thrown + if (!ct) { + ct = GetLastExceptionContext(); + } + + if (ct && ct->GetLength() > 0) { + result << "\nContext:\n" << *ct; + } + + return result.str(); +} + +String icinga::DiagnosticInformation(const boost::exception_ptr& eptr, bool verbose) +{ + boost::stacktrace::stacktrace *pt = GetLastExceptionStack(); + boost::stacktrace::stacktrace stack; + + ContextTrace *pc = GetLastExceptionContext(); + ContextTrace context; + + if (pt) + stack = *pt; + + if (pc) + context = *pc; + + try { + boost::rethrow_exception(eptr); + } catch (const std::exception& ex) { + return DiagnosticInformation(ex, verbose, pt ? &stack : nullptr, pc ? &context : nullptr); + } + + return boost::diagnostic_information(eptr); +} + +ScriptError::ScriptError(String message) + : m_Message(std::move(message)), m_IncompleteExpr(false) +{ } + +ScriptError::ScriptError(String message, DebugInfo di, bool incompleteExpr) + : m_Message(std::move(message)), m_DebugInfo(std::move(di)), m_IncompleteExpr(incompleteExpr), m_HandledByDebugger(false) +{ } + +const char *ScriptError::what() const throw() +{ + return m_Message.CStr(); +} + +DebugInfo ScriptError::GetDebugInfo() const +{ + return m_DebugInfo; +} + +bool ScriptError::IsIncompleteExpression() const +{ + return m_IncompleteExpr; +} + +bool ScriptError::IsHandledByDebugger() const +{ + return m_HandledByDebugger; +} + +void ScriptError::SetHandledByDebugger(bool handled) +{ + m_HandledByDebugger = handled; +} + +posix_error::~posix_error() throw() +{ + free(m_Message); +} + +const char *posix_error::what() const throw() +{ + if (!m_Message) { + std::ostringstream msgbuf; + + const char * const *func = boost::get_error_info<boost::errinfo_api_function>(*this); + + if (func) + msgbuf << "Function call '" << *func << "'"; + else + msgbuf << "Function call"; + + const std::string *fname = boost::get_error_info<boost::errinfo_file_name>(*this); + + if (fname) + msgbuf << " for file '" << *fname << "'"; + + msgbuf << " failed"; + + const int *errnum = boost::get_error_info<boost::errinfo_errno>(*this); + + if (errnum) + msgbuf << " with error code " << *errnum << ", '" << strerror(*errnum) << "'"; + + String str = msgbuf.str(); + m_Message = strdup(str.CStr()); + } + + return m_Message; +} + +ValidationError::ValidationError(const ConfigObject::Ptr& object, const std::vector<String>& attributePath, const String& message) + : m_Object(object), m_AttributePath(attributePath), m_Message(message) +{ + String path; + + for (const String& attribute : attributePath) { + if (!path.IsEmpty()) + path += " -> "; + + path += "'" + attribute + "'"; + } + + Type::Ptr type = object->GetReflectionType(); + m_What = "Validation failed for object '" + object->GetName() + "' of type '" + type->GetName() + "'"; + + if (!path.IsEmpty()) + m_What += "; Attribute " + path; + + m_What += ": " + message; +} + +ValidationError::~ValidationError() throw() +{ } + +const char *ValidationError::what() const throw() +{ + return m_What.CStr(); +} + +ConfigObject::Ptr ValidationError::GetObject() const +{ + return m_Object; +} + +std::vector<String> ValidationError::GetAttributePath() const +{ + return m_AttributePath; +} + +String ValidationError::GetMessage() const +{ + return m_Message; +} + +void ValidationError::SetDebugHint(const Dictionary::Ptr& dhint) +{ + m_DebugHint = dhint; +} + +Dictionary::Ptr ValidationError::GetDebugHint() const +{ + return m_DebugHint; +} + +std::string icinga::to_string(const StackTraceErrorInfo&) +{ + return ""; +} + +#ifdef _WIN32 +const char *win32_error::what() const noexcept +{ + return "win32_error"; +} + +std::string icinga::to_string(const win32_error &e) { + std::ostringstream msgbuf; + + const char * const *func = boost::get_error_info<boost::errinfo_api_function>(e); + + if (func) { + msgbuf << "Function call '" << *func << "'"; + } else { + msgbuf << "Function call"; + } + + const std::string *fname = boost::get_error_info<boost::errinfo_file_name>(e); + + if (fname) { + msgbuf << " for file '" << *fname << "'"; + } + + msgbuf << " failed"; + + const int *errnum = boost::get_error_info<errinfo_win32_error>(e); + + if (errnum) { + msgbuf << " with error code " << Utility::FormatErrorNumber(*errnum); + } + + return msgbuf.str(); +} + +std::string icinga::to_string(const errinfo_win32_error& e) +{ + return "[errinfo_win32_error] = " + Utility::FormatErrorNumber(e.value()) + "\n"; +} +#endif /* _WIN32 */ + +std::string icinga::to_string(const errinfo_getaddrinfo_error& e) +{ + String msg; + +#ifdef _WIN32 + msg = gai_strerrorA(e.value()); +#else /* _WIN32 */ + msg = gai_strerror(e.value()); +#endif /* _WIN32 */ + + return "[errinfo_getaddrinfo_error] = " + String(msg) + "\n"; +} + +std::string icinga::to_string(const ContextTraceErrorInfo& e) +{ + std::ostringstream msgbuf; + msgbuf << "[Context] = " << e.value(); + return msgbuf.str(); +} + +invalid_downtime_removal_error::invalid_downtime_removal_error(String message) + : m_Message(std::move(message)) +{ } + +invalid_downtime_removal_error::invalid_downtime_removal_error(const char *message) + : m_Message(message) +{ } + +invalid_downtime_removal_error::~invalid_downtime_removal_error() noexcept +{ } + +const char *invalid_downtime_removal_error::what() const noexcept +{ + return m_Message.CStr(); +} diff --git a/lib/base/exception.hpp b/lib/base/exception.hpp new file mode 100644 index 0000000..18dab65 --- /dev/null +++ b/lib/base/exception.hpp @@ -0,0 +1,166 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#ifndef EXCEPTION_H +#define EXCEPTION_H + +#include "base/i2-base.hpp" +#include "base/string.hpp" +#include "base/context.hpp" +#include "base/debuginfo.hpp" +#include "base/dictionary.hpp" +#include "base/configobject.hpp" +#include <boost/exception/errinfo_api_function.hpp> +#include <boost/exception/errinfo_errno.hpp> +#include <boost/exception/errinfo_file_name.hpp> +#include <boost/exception/diagnostic_information.hpp> +#include <boost/exception_ptr.hpp> +#include <boost/stacktrace.hpp> + +#ifdef _WIN32 +# include <boost/algorithm/string/trim.hpp> +#endif /* _WIN32 */ + +namespace icinga +{ + +class user_error : virtual public std::exception, virtual public boost::exception +{ }; + +/* + * @ingroup base + */ +class ScriptError : virtual public user_error +{ +public: + ScriptError(String message); + ScriptError(String message, DebugInfo di, bool incompleteExpr = false); + + const char *what(void) const throw() final; + + DebugInfo GetDebugInfo() const; + bool IsIncompleteExpression() const; + + bool IsHandledByDebugger() const; + void SetHandledByDebugger(bool handled); + +private: + String m_Message; + DebugInfo m_DebugInfo; + bool m_IncompleteExpr; + bool m_HandledByDebugger; +}; + +/* + * @ingroup base + */ +class ValidationError : virtual public user_error +{ +public: + ValidationError(const ConfigObject::Ptr& object, const std::vector<String>& attributePath, const String& message); + ~ValidationError() throw() override; + + const char *what() const throw() override; + + ConfigObject::Ptr GetObject() const; + std::vector<String> GetAttributePath() const; + String GetMessage() const; + + void SetDebugHint(const Dictionary::Ptr& dhint); + Dictionary::Ptr GetDebugHint() const; + +private: + ConfigObject::Ptr m_Object; + std::vector<String> m_AttributePath; + String m_Message; + String m_What; + Dictionary::Ptr m_DebugHint; +}; + +boost::stacktrace::stacktrace *GetLastExceptionStack(); +void SetLastExceptionStack(const boost::stacktrace::stacktrace& trace); + +ContextTrace *GetLastExceptionContext(); +void SetLastExceptionContext(const ContextTrace& context); + +void RethrowUncaughtException(); + +struct errinfo_stacktrace_; +typedef boost::error_info<struct errinfo_stacktrace_, boost::stacktrace::stacktrace> StackTraceErrorInfo; + +std::string to_string(const StackTraceErrorInfo&); + +typedef boost::error_info<ContextTrace, ContextTrace> ContextTraceErrorInfo; + +std::string to_string(const ContextTraceErrorInfo& e); + +/** + * Generate diagnostic information about an exception + * + * The following information is gathered in the result: + * - Exception error message + * - Debug information about the Icinga config if the exception is a ValidationError + * - Stack trace + * - Context trace + * + * Each, stack trace and the context trace, are printed if the they were saved in the boost exception error + * information, are explicitly passed as a parameter, or were stored when the last exception was thrown. If multiple + * of these exist, the first one is used. + * + * @param ex exception to print diagnostic information about + * @param verbose if verbose is set, a stack trace is added + * @param stack optionally supply a stack trace + * @param context optionally supply a context trace + * @return string containing the aforementioned information + */ +String DiagnosticInformation(const std::exception& ex, bool verbose = true, + boost::stacktrace::stacktrace *stack = nullptr, ContextTrace *context = nullptr); +String DiagnosticInformation(const boost::exception_ptr& eptr, bool verbose = true); + +class posix_error : virtual public std::exception, virtual public boost::exception { +public: + ~posix_error() throw() override; + + const char *what(void) const throw() final; + +private: + mutable char *m_Message{nullptr}; +}; + +#ifdef _WIN32 +class win32_error : virtual public std::exception, virtual public boost::exception { +public: + const char *what() const noexcept override; +}; + +std::string to_string(const win32_error& e); + +struct errinfo_win32_error_; +typedef boost::error_info<struct errinfo_win32_error_, int> errinfo_win32_error; + +std::string to_string(const errinfo_win32_error& e); +#endif /* _WIN32 */ + +struct errinfo_getaddrinfo_error_; +typedef boost::error_info<struct errinfo_getaddrinfo_error_, int> errinfo_getaddrinfo_error; + +std::string to_string(const errinfo_getaddrinfo_error& e); + +struct errinfo_message_; +typedef boost::error_info<struct errinfo_message_, std::string> errinfo_message; + +class invalid_downtime_removal_error : virtual public std::exception, virtual public boost::exception { +public: + explicit invalid_downtime_removal_error(String message); + explicit invalid_downtime_removal_error(const char* message); + + ~invalid_downtime_removal_error() noexcept override; + + const char *what() const noexcept final; + +private: + String m_Message; +}; + +} + +#endif /* EXCEPTION_H */ diff --git a/lib/base/fifo.cpp b/lib/base/fifo.cpp new file mode 100644 index 0000000..8653f51 --- /dev/null +++ b/lib/base/fifo.cpp @@ -0,0 +1,124 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#include "base/fifo.hpp" + +using namespace icinga; + +/** + * Destructor for the FIFO class. + */ +FIFO::~FIFO() +{ + free(m_Buffer); +} + +/** + * Resizes the FIFO's buffer so that it is at least newSize bytes long. + * + * @param newSize The minimum new size of the FIFO buffer. + */ +void FIFO::ResizeBuffer(size_t newSize, bool decrease) +{ + if (m_AllocSize >= newSize && !decrease) + return; + + newSize = (newSize / FIFO::BlockSize + 1) * FIFO::BlockSize; + + if (newSize == m_AllocSize) + return; + + auto *newBuffer = static_cast<char *>(realloc(m_Buffer, newSize)); + + if (!newBuffer) + BOOST_THROW_EXCEPTION(std::bad_alloc()); + + m_Buffer = newBuffer; + + m_AllocSize = newSize; +} + +/** + * Optimizes memory usage of the FIFO buffer by reallocating + * and moving the buffer. + */ +void FIFO::Optimize() +{ + if (m_Offset > m_DataSize / 10 && m_Offset - m_DataSize > 1024) { + std::memmove(m_Buffer, m_Buffer + m_Offset, m_DataSize); + m_Offset = 0; + + if (m_DataSize > 0) + ResizeBuffer(m_DataSize, true); + + return; + } +} + +size_t FIFO::Peek(void *buffer, size_t count, bool allow_partial) +{ + ASSERT(allow_partial); + + if (count > m_DataSize) + count = m_DataSize; + + if (buffer) + std::memcpy(buffer, m_Buffer + m_Offset, count); + + return count; +} + +/** + * Implements IOQueue::Read. + */ +size_t FIFO::Read(void *buffer, size_t count, bool allow_partial) +{ + ASSERT(allow_partial); + + if (count > m_DataSize) + count = m_DataSize; + + if (buffer) + std::memcpy(buffer, m_Buffer + m_Offset, count); + + m_DataSize -= count; + m_Offset += count; + + Optimize(); + + return count; +} + +/** + * Implements IOQueue::Write. + */ +void FIFO::Write(const void *buffer, size_t count) +{ + ResizeBuffer(m_Offset + m_DataSize + count, false); + std::memcpy(m_Buffer + m_Offset + m_DataSize, buffer, count); + m_DataSize += count; + + SignalDataAvailable(); +} + +void FIFO::Close() +{ } + +bool FIFO::IsEof() const +{ + return false; +} + +size_t FIFO::GetAvailableBytes() const +{ + return m_DataSize; +} + +bool FIFO::SupportsWaiting() const +{ + return true; +} + +bool FIFO::IsDataAvailable() const +{ + return m_DataSize > 0; +} diff --git a/lib/base/fifo.hpp b/lib/base/fifo.hpp new file mode 100644 index 0000000..a8273c1 --- /dev/null +++ b/lib/base/fifo.hpp @@ -0,0 +1,48 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#ifndef FIFO_H +#define FIFO_H + +#include "base/i2-base.hpp" +#include "base/stream.hpp" + +namespace icinga +{ + +/** + * A byte-based FIFO buffer. + * + * @ingroup base + */ +class FIFO final : public Stream +{ +public: + DECLARE_PTR_TYPEDEFS(FIFO); + + static const size_t BlockSize = 512; + + ~FIFO() override; + + size_t Peek(void *buffer, size_t count, bool allow_partial = false) override; + size_t Read(void *buffer, size_t count, bool allow_partial = false) override; + void Write(const void *buffer, size_t count) override; + void Close() override; + bool IsEof() const override; + bool SupportsWaiting() const override; + bool IsDataAvailable() const override; + + size_t GetAvailableBytes() const; + +private: + char *m_Buffer{nullptr}; + size_t m_DataSize{0}; + size_t m_AllocSize{0}; + size_t m_Offset{0}; + + void ResizeBuffer(size_t newSize, bool decrease); + void Optimize(); +}; + +} + +#endif /* FIFO_H */ diff --git a/lib/base/filelogger.cpp b/lib/base/filelogger.cpp new file mode 100644 index 0000000..c3da84a --- /dev/null +++ b/lib/base/filelogger.cpp @@ -0,0 +1,59 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#include "base/filelogger.hpp" +#include "base/filelogger-ti.cpp" +#include "base/configtype.hpp" +#include "base/statsfunction.hpp" +#include "base/application.hpp" +#include <fstream> + +using namespace icinga; + +REGISTER_TYPE(FileLogger); + +REGISTER_STATSFUNCTION(FileLogger, &FileLogger::StatsFunc); + +void FileLogger::StatsFunc(const Dictionary::Ptr& status, const Array::Ptr&) +{ + DictionaryData nodes; + + for (const FileLogger::Ptr& filelogger : ConfigType::GetObjectsByType<FileLogger>()) { + nodes.emplace_back(filelogger->GetName(), 1); //add more stats + } + + status->Set("filelogger", new Dictionary(std::move(nodes))); +} + +/** + * Constructor for the FileLogger class. + */ +void FileLogger::Start(bool runtimeCreated) +{ + ReopenLogFile(); + + Application::OnReopenLogs.connect([this]() { ReopenLogFile(); }); + + ObjectImpl<FileLogger>::Start(runtimeCreated); + + Log(LogInformation, "FileLogger") + << "'" << GetName() << "' started."; +} + +void FileLogger::ReopenLogFile() +{ + auto *stream = new std::ofstream(); + + String path = GetPath(); + + try { + stream->open(path.CStr(), std::fstream::app | std::fstream::out); + + if (!stream->good()) + BOOST_THROW_EXCEPTION(std::runtime_error("Could not open logfile '" + path + "'")); + } catch (...) { + delete stream; + throw; + } + + BindStream(stream, true); +} diff --git a/lib/base/filelogger.hpp b/lib/base/filelogger.hpp new file mode 100644 index 0000000..420337f --- /dev/null +++ b/lib/base/filelogger.hpp @@ -0,0 +1,33 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#ifndef FILELOGGER_H +#define FILELOGGER_H + +#include "base/i2-base.hpp" +#include "base/filelogger-ti.hpp" + +namespace icinga +{ + +/** + * A logger that logs to a file. + * + * @ingroup base + */ +class FileLogger final : public ObjectImpl<FileLogger> +{ +public: + DECLARE_OBJECT(FileLogger); + DECLARE_OBJECTNAME(FileLogger); + + static void StatsFunc(const Dictionary::Ptr& status, const Array::Ptr& perfdata); + + void Start(bool runtimeCreated) override; + +private: + void ReopenLogFile(); +}; + +} + +#endif /* FILELOGGER_H */ diff --git a/lib/base/filelogger.ti b/lib/base/filelogger.ti new file mode 100644 index 0000000..8af2498 --- /dev/null +++ b/lib/base/filelogger.ti @@ -0,0 +1,17 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#include "base/streamlogger.hpp" + +library base; + +namespace icinga +{ + +class FileLogger : StreamLogger +{ + activation_priority -100; + + [config, required] String path; +}; + +} diff --git a/lib/base/function-script.cpp b/lib/base/function-script.cpp new file mode 100644 index 0000000..e59e84d --- /dev/null +++ b/lib/base/function-script.cpp @@ -0,0 +1,50 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#include "base/function.hpp" +#include "base/functionwrapper.hpp" +#include "base/scriptframe.hpp" +#include "base/objectlock.hpp" +#include "base/exception.hpp" + +using namespace icinga; + +static Value FunctionCall(const std::vector<Value>& args) +{ + if (args.size() < 1) + BOOST_THROW_EXCEPTION(std::invalid_argument("Too few arguments for call()")); + + ScriptFrame *vframe = ScriptFrame::GetCurrentFrame(); + Function::Ptr self = static_cast<Function::Ptr>(vframe->Self); + REQUIRE_NOT_NULL(self); + + std::vector<Value> uargs(args.begin() + 1, args.end()); + return self->InvokeThis(args[0], uargs); +} + +static Value FunctionCallV(const Value& thisArg, const Array::Ptr& args) +{ + ScriptFrame *vframe = ScriptFrame::GetCurrentFrame(); + Function::Ptr self = static_cast<Function::Ptr>(vframe->Self); + REQUIRE_NOT_NULL(self); + + std::vector<Value> uargs; + + { + ObjectLock olock(args); + uargs = std::vector<Value>(args->Begin(), args->End()); + } + + return self->InvokeThis(thisArg, uargs); +} + + +Object::Ptr Function::GetPrototype() +{ + static Dictionary::Ptr prototype = new Dictionary({ + { "call", new Function("Function#call", FunctionCall) }, + { "callv", new Function("Function#callv", FunctionCallV) } + }); + + return prototype; +} + diff --git a/lib/base/function.cpp b/lib/base/function.cpp new file mode 100644 index 0000000..f9a261d --- /dev/null +++ b/lib/base/function.cpp @@ -0,0 +1,37 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#include "base/function.hpp" +#include "base/function-ti.cpp" +#include "base/array.hpp" +#include "base/scriptframe.hpp" + +using namespace icinga; + +REGISTER_TYPE_WITH_PROTOTYPE(Function, Function::GetPrototype()); + +Function::Function(const String& name, Callback function, const std::vector<String>& args, + bool side_effect_free, bool deprecated) + : m_Callback(std::move(function)) +{ + SetName(name, true); + SetSideEffectFree(side_effect_free, true); + SetDeprecated(deprecated, true); + SetArguments(Array::FromVector(args), true); +} + +Value Function::Invoke(const std::vector<Value>& arguments) +{ + ScriptFrame frame(false); + return m_Callback(arguments); +} + +Value Function::InvokeThis(const Value& otherThis, const std::vector<Value>& arguments) +{ + ScriptFrame frame(false, otherThis); + return m_Callback(arguments); +} + +Object::Ptr Function::Clone() const +{ + return const_cast<Function *>(this); +} diff --git a/lib/base/function.hpp b/lib/base/function.hpp new file mode 100644 index 0000000..d52a230 --- /dev/null +++ b/lib/base/function.hpp @@ -0,0 +1,89 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#ifndef FUNCTION_H +#define FUNCTION_H + +#include "base/i2-base.hpp" +#include "base/function-ti.hpp" +#include "base/value.hpp" +#include "base/functionwrapper.hpp" +#include "base/scriptglobal.hpp" +#include <vector> + +namespace icinga +{ + +/** + * A script function that can be used to execute a script task. + * + * @ingroup base + */ +class Function final : public ObjectImpl<Function> +{ +public: + DECLARE_OBJECT(Function); + + typedef std::function<Value (const std::vector<Value>& arguments)> Callback; + + template<typename F> + Function(const String& name, F function, const std::vector<String>& args = std::vector<String>(), + bool side_effect_free = false, bool deprecated = false) + : Function(name, WrapFunction(function), args, side_effect_free, deprecated) + { } + + Value Invoke(const std::vector<Value>& arguments = std::vector<Value>()); + Value InvokeThis(const Value& otherThis, const std::vector<Value>& arguments = std::vector<Value>()); + + bool IsSideEffectFree() const + { + return GetSideEffectFree(); + } + + bool IsDeprecated() const + { + return GetDeprecated(); + } + + static Object::Ptr GetPrototype(); + + Object::Ptr Clone() const override; + +private: + Callback m_Callback; + + Function(const String& name, Callback function, const std::vector<String>& args, + bool side_effect_free, bool deprecated); +}; + +/* Ensure that the priority is lower than the basic namespace initialization in scriptframe.cpp. */ +#define REGISTER_FUNCTION(ns, name, callback, args) \ + INITIALIZE_ONCE_WITH_PRIORITY([]() { \ + Function::Ptr sf = new icinga::Function(#ns "#" #name, callback, String(args).Split(":"), false); \ + Namespace::Ptr nsp = ScriptGlobal::Get(#ns); \ + nsp->Set(#name, sf, true); \ + }, InitializePriority::RegisterFunctions) + +#define REGISTER_SAFE_FUNCTION(ns, name, callback, args) \ + INITIALIZE_ONCE_WITH_PRIORITY([]() { \ + Function::Ptr sf = new icinga::Function(#ns "#" #name, callback, String(args).Split(":"), true); \ + Namespace::Ptr nsp = ScriptGlobal::Get(#ns); \ + nsp->Set(#name, sf, true); \ + }, InitializePriority::RegisterFunctions) + +#define REGISTER_FUNCTION_NONCONST(ns, name, callback, args) \ + INITIALIZE_ONCE_WITH_PRIORITY([]() { \ + Function::Ptr sf = new icinga::Function(#ns "#" #name, callback, String(args).Split(":"), false); \ + Namespace::Ptr nsp = ScriptGlobal::Get(#ns); \ + nsp->Set(#name, sf, false); \ + }, InitializePriority::RegisterFunctions) + +#define REGISTER_SAFE_FUNCTION_NONCONST(ns, name, callback, args) \ + INITIALIZE_ONCE_WITH_PRIORITY([]() { \ + Function::Ptr sf = new icinga::Function(#ns "#" #name, callback, String(args).Split(":"), true); \ + Namespace::Ptr nsp = ScriptGlobal::Get(#ns); \ + nsp->SetAttribute(#name, sf, false); \ + }, InitializePriority::RegisterFunctions) + +} + +#endif /* FUNCTION_H */ diff --git a/lib/base/function.ti b/lib/base/function.ti new file mode 100644 index 0000000..f2623c1 --- /dev/null +++ b/lib/base/function.ti @@ -0,0 +1,18 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#include "base/configobject.hpp" + +library base; + +namespace icinga +{ + +abstract class Function +{ + [config] String "name"; + [config] bool side_effect_free; + [config] bool "deprecated"; + [config] Array::Ptr arguments; +}; + +} diff --git a/lib/base/functionwrapper.hpp b/lib/base/functionwrapper.hpp new file mode 100644 index 0000000..57cf1cb --- /dev/null +++ b/lib/base/functionwrapper.hpp @@ -0,0 +1,149 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#ifndef FUNCTIONWRAPPER_H +#define FUNCTIONWRAPPER_H + +#include "base/i2-base.hpp" +#include "base/value.hpp" +#include <boost/function_types/function_type.hpp> +#include <boost/function_types/parameter_types.hpp> +#include <boost/function_types/result_type.hpp> +#include <boost/function_types/function_arity.hpp> +#include <vector> + +namespace icinga +{ + +template<typename FuncType> +typename std::enable_if< + std::is_class<FuncType>::value && + std::is_same<typename boost::function_types::result_type<decltype(&FuncType::operator())>::type, Value>::value && + boost::function_types::function_arity<decltype(&FuncType::operator())>::value == 2, + std::function<Value (const std::vector<Value>&)>>::type +WrapFunction(FuncType function) +{ + static_assert(std::is_same<typename boost::mpl::at_c<typename boost::function_types::parameter_types<decltype(&FuncType::operator())>, 1>::type, const std::vector<Value>&>::value, "Argument type must be const std::vector<Value>"); + return function; +} + +inline std::function<Value (const std::vector<Value>&)> WrapFunction(void (*function)(const std::vector<Value>&)) +{ + return [function](const std::vector<Value>& arguments) { + function(arguments); + return Empty; + }; +} + +template<typename Return> +std::function<Value (const std::vector<Value>&)> WrapFunction(Return (*function)(const std::vector<Value>&)) +{ + return [function](const std::vector<Value>& values) -> Value { return function(values); }; +} + +template <std::size_t... Indices> +struct indices { + using next = indices<Indices..., sizeof...(Indices)>; +}; + +template <std::size_t N> +struct build_indices { + using type = typename build_indices<N-1>::type::next; +}; + +template <> +struct build_indices<0> { + using type = indices<>; +}; + +template <std::size_t N> +using BuildIndices = typename build_indices<N>::type; + +struct UnpackCaller +{ +private: + template <typename FuncType, size_t... I> + auto Invoke(FuncType f, const std::vector<Value>& args, indices<I...>) -> decltype(f(args[I]...)) + { + return f(args[I]...); + } + +public: + template <typename FuncType, int Arity> + auto operator() (FuncType f, const std::vector<Value>& args) -> decltype(Invoke(f, args, BuildIndices<Arity>{})) + { + return Invoke(f, args, BuildIndices<Arity>{}); + } +}; + +template<typename FuncType, int Arity, typename ReturnType> +struct FunctionWrapper +{ + static Value Invoke(FuncType function, const std::vector<Value>& arguments) + { + return UnpackCaller().operator()<FuncType, Arity>(function, arguments); + } +}; + +template<typename FuncType, int Arity> +struct FunctionWrapper<FuncType, Arity, void> +{ + static Value Invoke(FuncType function, const std::vector<Value>& arguments) + { + UnpackCaller().operator()<FuncType, Arity>(function, arguments); + return Empty; + } +}; + +template<typename FuncType> +typename std::enable_if< + std::is_function<typename std::remove_pointer<FuncType>::type>::value && !std::is_same<FuncType, Value(*)(const std::vector<Value>&)>::value, + std::function<Value (const std::vector<Value>&)>>::type +WrapFunction(FuncType function) +{ + return [function](const std::vector<Value>& arguments) { + constexpr size_t arity = boost::function_types::function_arity<typename std::remove_pointer<FuncType>::type>::value; + + if (arity > 0) { + if (arguments.size() < arity) + BOOST_THROW_EXCEPTION(std::invalid_argument("Too few arguments for function.")); + else if (arguments.size() > arity) + BOOST_THROW_EXCEPTION(std::invalid_argument("Too many arguments for function.")); + } + + using ReturnType = decltype(UnpackCaller().operator()<FuncType, arity>(*static_cast<FuncType *>(nullptr), std::vector<Value>())); + + return FunctionWrapper<FuncType, arity, ReturnType>::Invoke(function, arguments); + }; +} + +template<typename FuncType> +typename std::enable_if< + std::is_class<FuncType>::value && + !(std::is_same<typename boost::function_types::result_type<decltype(&FuncType::operator())>::type, Value>::value && + boost::function_types::function_arity<decltype(&FuncType::operator())>::value == 2), + std::function<Value (const std::vector<Value>&)>>::type +WrapFunction(FuncType function) +{ + static_assert(!std::is_same<typename boost::mpl::at_c<typename boost::function_types::parameter_types<decltype(&FuncType::operator())>, 1>::type, const std::vector<Value>&>::value, "Argument type must be const std::vector<Value>"); + + using FuncTypeInvoker = decltype(&FuncType::operator()); + + return [function](const std::vector<Value>& arguments) { + constexpr size_t arity = boost::function_types::function_arity<FuncTypeInvoker>::value - 1; + + if (arity > 0) { + if (arguments.size() < arity) + BOOST_THROW_EXCEPTION(std::invalid_argument("Too few arguments for function.")); + else if (arguments.size() > arity) + BOOST_THROW_EXCEPTION(std::invalid_argument("Too many arguments for function.")); + } + + using ReturnType = decltype(UnpackCaller().operator()<FuncType, arity>(*static_cast<FuncType *>(nullptr), std::vector<Value>())); + + return FunctionWrapper<FuncType, arity, ReturnType>::Invoke(function, arguments); + }; +} + +} + +#endif /* FUNCTIONWRAPPER_H */ diff --git a/lib/base/i2-base.hpp b/lib/base/i2-base.hpp new file mode 100644 index 0000000..a7bfc6a --- /dev/null +++ b/lib/base/i2-base.hpp @@ -0,0 +1,79 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#ifndef I2BASE_H +#define I2BASE_H + +/** + * @mainpage Icinga Documentation + * + * Icinga implements a framework for run-time-loadable components which can + * pass messages between each other. These components can either be hosted in + * the same process or in several host processes (either on the same machine or + * on different machines). + * + * The framework's code critically depends on the following patterns: + * + * <list type="bullet"> + * <item>Smart pointers + * + * The shared_ptr and weak_ptr template classes are used to simplify memory + * management and to avoid accidental memory leaks and use-after-free + * bugs.</item> + * + * <item>Observer pattern + * + * Framework classes expose events which other objects can subscribe to. This + * is used to decouple clients of a class from the class' internal + * implementation.</item> + * </list> + */ + +/** + * @defgroup base Base class library + * + * The base class library implements commonly-used functionality like + * event handling for sockets and timers. + */ + +#include <boost/config.hpp> + +#if defined(__clang__) && __cplusplus >= 201103L +# undef BOOST_NO_CXX11_HDR_TUPLE +#endif + +#ifdef _MSC_VER +# pragma warning(disable:4251) +# pragma warning(disable:4275) +# pragma warning(disable:4345) +#endif /* _MSC_VER */ + +#include "config.h" + +#ifdef _WIN32 +# include "base/win32.hpp" +#else +# include "base/unix.hpp" +#endif + +#include <cstdlib> +#include <cstdarg> +#include <cstdio> +#include <cstring> +#include <cerrno> + +#include <sys/types.h> +#include <sys/stat.h> +#include <signal.h> + +#include <exception> +#include <stdexcept> + +#if defined(__APPLE__) && defined(__MACH__) +# pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif + +#define BOOST_BIND_NO_PLACEHOLDERS + +#include <functional> + +#endif /* I2BASE_H */ diff --git a/lib/base/initialize.cpp b/lib/base/initialize.cpp new file mode 100644 index 0000000..49b653f --- /dev/null +++ b/lib/base/initialize.cpp @@ -0,0 +1,13 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#include "base/initialize.hpp" +#include "base/loader.hpp" + +using namespace icinga; + +bool icinga::InitializeOnceHelper(const std::function<void()>& func, InitializePriority priority) +{ + Loader::AddDeferredInitializer(func, priority); + return true; +} + diff --git a/lib/base/initialize.hpp b/lib/base/initialize.hpp new file mode 100644 index 0000000..adc995f --- /dev/null +++ b/lib/base/initialize.hpp @@ -0,0 +1,49 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#ifndef INITIALIZE_H +#define INITIALIZE_H + +#include "base/i2-base.hpp" +#include <functional> + +namespace icinga +{ + +/** + * Priority values for use with the INITIALIZE_ONCE_WITH_PRIORITY macro. + * + * The values are given in the order of initialization. + */ +enum class InitializePriority { + CreateNamespaces, + InitIcingaApplication, + RegisterTypeType, + RegisterObjectType, + RegisterPrimitiveTypes, + RegisterBuiltinTypes, + RegisterFunctions, + RegisterTypes, + EvaluateConfigFragments, + Default, + FreezeNamespaces, +}; + +#define I2_TOKENPASTE(x, y) x ## y +#define I2_TOKENPASTE2(x, y) I2_TOKENPASTE(x, y) + +#define I2_UNIQUE_NAME(prefix) I2_TOKENPASTE2(prefix, __COUNTER__) + +bool InitializeOnceHelper(const std::function<void()>& func, InitializePriority priority = InitializePriority::Default); + +#define INITIALIZE_ONCE(func) \ + namespace { namespace I2_UNIQUE_NAME(io) { \ + bool l_InitializeOnce(icinga::InitializeOnceHelper(func)); \ + } } + +#define INITIALIZE_ONCE_WITH_PRIORITY(func, priority) \ + namespace { namespace I2_UNIQUE_NAME(io) { \ + bool l_InitializeOnce(icinga::InitializeOnceHelper(func, priority)); \ + } } +} + +#endif /* INITIALIZE_H */ diff --git a/lib/base/io-engine.cpp b/lib/base/io-engine.cpp new file mode 100644 index 0000000..26125fe --- /dev/null +++ b/lib/base/io-engine.cpp @@ -0,0 +1,155 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#include "base/configuration.hpp" +#include "base/exception.hpp" +#include "base/io-engine.hpp" +#include "base/lazy-init.hpp" +#include "base/logger.hpp" +#include <exception> +#include <memory> +#include <thread> +#include <boost/asio/io_context.hpp> +#include <boost/asio/spawn.hpp> +#include <boost/asio/post.hpp> +#include <boost/date_time/posix_time/ptime.hpp> +#include <boost/system/error_code.hpp> + +using namespace icinga; + +CpuBoundWork::CpuBoundWork(boost::asio::yield_context yc) + : m_Done(false) +{ + auto& ioEngine (IoEngine::Get()); + + for (;;) { + auto availableSlots (ioEngine.m_CpuBoundSemaphore.fetch_sub(1)); + + if (availableSlots < 1) { + ioEngine.m_CpuBoundSemaphore.fetch_add(1); + IoEngine::YieldCurrentCoroutine(yc); + continue; + } + + break; + } +} + +CpuBoundWork::~CpuBoundWork() +{ + if (!m_Done) { + IoEngine::Get().m_CpuBoundSemaphore.fetch_add(1); + } +} + +void CpuBoundWork::Done() +{ + if (!m_Done) { + IoEngine::Get().m_CpuBoundSemaphore.fetch_add(1); + + m_Done = true; + } +} + +IoBoundWorkSlot::IoBoundWorkSlot(boost::asio::yield_context yc) + : yc(yc) +{ + IoEngine::Get().m_CpuBoundSemaphore.fetch_add(1); +} + +IoBoundWorkSlot::~IoBoundWorkSlot() +{ + auto& ioEngine (IoEngine::Get()); + + for (;;) { + auto availableSlots (ioEngine.m_CpuBoundSemaphore.fetch_sub(1)); + + if (availableSlots < 1) { + ioEngine.m_CpuBoundSemaphore.fetch_add(1); + IoEngine::YieldCurrentCoroutine(yc); + continue; + } + + break; + } +} + +LazyInit<std::unique_ptr<IoEngine>> IoEngine::m_Instance ([]() { return std::unique_ptr<IoEngine>(new IoEngine()); }); + +IoEngine& IoEngine::Get() +{ + return *m_Instance.Get(); +} + +boost::asio::io_context& IoEngine::GetIoContext() +{ + return m_IoContext; +} + +IoEngine::IoEngine() : m_IoContext(), m_KeepAlive(boost::asio::make_work_guard(m_IoContext)), m_Threads(decltype(m_Threads)::size_type(Configuration::Concurrency * 2u)), m_AlreadyExpiredTimer(m_IoContext) +{ + m_AlreadyExpiredTimer.expires_at(boost::posix_time::neg_infin); + m_CpuBoundSemaphore.store(Configuration::Concurrency * 3u / 2u); + + for (auto& thread : m_Threads) { + thread = std::thread(&IoEngine::RunEventLoop, this); + } +} + +IoEngine::~IoEngine() +{ + for (auto& thread : m_Threads) { + boost::asio::post(m_IoContext, []() { + throw TerminateIoThread(); + }); + } + + for (auto& thread : m_Threads) { + thread.join(); + } +} + +void IoEngine::RunEventLoop() +{ + for (;;) { + try { + m_IoContext.run(); + + break; + } catch (const TerminateIoThread&) { + break; + } catch (const std::exception& e) { + Log(LogCritical, "IoEngine", "Exception during I/O operation!"); + Log(LogDebug, "IoEngine") << "Exception during I/O operation: " << DiagnosticInformation(e); + } + } +} + +AsioConditionVariable::AsioConditionVariable(boost::asio::io_context& io, bool init) + : m_Timer(io) +{ + m_Timer.expires_at(init ? boost::posix_time::neg_infin : boost::posix_time::pos_infin); +} + +void AsioConditionVariable::Set() +{ + m_Timer.expires_at(boost::posix_time::neg_infin); +} + +void AsioConditionVariable::Clear() +{ + m_Timer.expires_at(boost::posix_time::pos_infin); +} + +void AsioConditionVariable::Wait(boost::asio::yield_context yc) +{ + boost::system::error_code ec; + m_Timer.async_wait(yc[ec]); +} + +void Timeout::Cancel() +{ + m_Cancelled.store(true); + + boost::system::error_code ec; + m_Timer.cancel(ec); +} diff --git a/lib/base/io-engine.hpp b/lib/base/io-engine.hpp new file mode 100644 index 0000000..684d3ac --- /dev/null +++ b/lib/base/io-engine.hpp @@ -0,0 +1,216 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#ifndef IO_ENGINE_H +#define IO_ENGINE_H + +#include "base/exception.hpp" +#include "base/lazy-init.hpp" +#include "base/logger.hpp" +#include "base/shared-object.hpp" +#include <atomic> +#include <exception> +#include <memory> +#include <thread> +#include <utility> +#include <vector> +#include <stdexcept> +#include <boost/exception/all.hpp> +#include <boost/asio/deadline_timer.hpp> +#include <boost/asio/io_context.hpp> +#include <boost/asio/spawn.hpp> + +namespace icinga +{ + +/** + * Scope lock for CPU-bound work done in an I/O thread + * + * @ingroup base + */ +class CpuBoundWork +{ +public: + CpuBoundWork(boost::asio::yield_context yc); + CpuBoundWork(const CpuBoundWork&) = delete; + CpuBoundWork(CpuBoundWork&&) = delete; + CpuBoundWork& operator=(const CpuBoundWork&) = delete; + CpuBoundWork& operator=(CpuBoundWork&&) = delete; + ~CpuBoundWork(); + + void Done(); + +private: + bool m_Done; +}; + +/** + * Scope break for CPU-bound work done in an I/O thread + * + * @ingroup base + */ +class IoBoundWorkSlot +{ +public: + IoBoundWorkSlot(boost::asio::yield_context yc); + IoBoundWorkSlot(const IoBoundWorkSlot&) = delete; + IoBoundWorkSlot(IoBoundWorkSlot&&) = delete; + IoBoundWorkSlot& operator=(const IoBoundWorkSlot&) = delete; + IoBoundWorkSlot& operator=(IoBoundWorkSlot&&) = delete; + ~IoBoundWorkSlot(); + +private: + boost::asio::yield_context yc; +}; + +/** + * Async I/O engine + * + * @ingroup base + */ +class IoEngine +{ + friend CpuBoundWork; + friend IoBoundWorkSlot; + +public: + IoEngine(const IoEngine&) = delete; + IoEngine(IoEngine&&) = delete; + IoEngine& operator=(const IoEngine&) = delete; + IoEngine& operator=(IoEngine&&) = delete; + ~IoEngine(); + + static IoEngine& Get(); + + boost::asio::io_context& GetIoContext(); + + static inline size_t GetCoroutineStackSize() { +#ifdef _WIN32 + // Increase the stack size for Windows coroutines to prevent exception corruption. + // Rationale: Low cost Windows agent only & https://github.com/Icinga/icinga2/issues/7431 + return 8 * 1024 * 1024; +#else /* _WIN32 */ + // Increase the stack size for Linux/Unix coroutines for many JSON objects on the stack. + // This may help mitigate possible stack overflows. https://github.com/Icinga/icinga2/issues/7532 + return 256 * 1024; + //return boost::coroutines::stack_allocator::traits_type::default_size(); // Default 64 KB +#endif /* _WIN32 */ + } + + template <typename Handler, typename Function> + static void SpawnCoroutine(Handler& h, Function f) { + + boost::asio::spawn(h, + [f](boost::asio::yield_context yc) { + + try { + f(yc); + } catch (const boost::coroutines::detail::forced_unwind &) { + // Required for proper stack unwinding when coroutines are destroyed. + // https://github.com/boostorg/coroutine/issues/39 + throw; + } catch (const std::exception& ex) { + Log(LogCritical, "IoEngine", "Exception in coroutine!"); + Log(LogDebug, "IoEngine") << "Exception in coroutine: " << DiagnosticInformation(ex); + } catch (...) { + Log(LogCritical, "IoEngine", "Exception in coroutine!"); + } + }, + boost::coroutines::attributes(GetCoroutineStackSize()) // Set a pre-defined stack size. + ); + } + + static inline + void YieldCurrentCoroutine(boost::asio::yield_context yc) + { + Get().m_AlreadyExpiredTimer.async_wait(yc); + } + +private: + IoEngine(); + + void RunEventLoop(); + + static LazyInit<std::unique_ptr<IoEngine>> m_Instance; + + boost::asio::io_context m_IoContext; + boost::asio::executor_work_guard<boost::asio::io_context::executor_type> m_KeepAlive; + std::vector<std::thread> m_Threads; + boost::asio::deadline_timer m_AlreadyExpiredTimer; + std::atomic_int_fast32_t m_CpuBoundSemaphore; +}; + +class TerminateIoThread : public std::exception +{ +}; + +/** + * Condition variable which doesn't block I/O threads + * + * @ingroup base + */ +class AsioConditionVariable +{ +public: + AsioConditionVariable(boost::asio::io_context& io, bool init = false); + + void Set(); + void Clear(); + void Wait(boost::asio::yield_context yc); + +private: + boost::asio::deadline_timer m_Timer; +}; + +/** + * I/O timeout emulator + * + * @ingroup base + */ +class Timeout : public SharedObject +{ +public: + DECLARE_PTR_TYPEDEFS(Timeout); + + template<class Executor, class TimeoutFromNow, class OnTimeout> + Timeout(boost::asio::io_context& io, Executor& executor, TimeoutFromNow timeoutFromNow, OnTimeout onTimeout) + : m_Timer(io) + { + Ptr keepAlive (this); + + m_Cancelled.store(false); + m_Timer.expires_from_now(std::move(timeoutFromNow)); + + IoEngine::SpawnCoroutine(executor, [this, keepAlive, onTimeout](boost::asio::yield_context yc) { + if (m_Cancelled.load()) { + return; + } + + { + boost::system::error_code ec; + + m_Timer.async_wait(yc[ec]); + + if (ec) { + return; + } + } + + if (m_Cancelled.load()) { + return; + } + + auto f (onTimeout); + f(std::move(yc)); + }); + } + + void Cancel(); + +private: + boost::asio::deadline_timer m_Timer; + std::atomic<bool> m_Cancelled; +}; + +} + +#endif /* IO_ENGINE_H */ diff --git a/lib/base/journaldlogger.cpp b/lib/base/journaldlogger.cpp new file mode 100644 index 0000000..92d6af7 --- /dev/null +++ b/lib/base/journaldlogger.cpp @@ -0,0 +1,87 @@ +/* Icinga 2 | (c) 2021 Icinga GmbH | GPLv2+ */ + +#include "base/i2-base.hpp" +#if !defined(_WIN32) && defined(HAVE_SYSTEMD) +#include "base/journaldlogger.hpp" +#include "base/journaldlogger-ti.cpp" +#include "base/configtype.hpp" +#include "base/statsfunction.hpp" +#include "base/sysloglogger.hpp" +#include <systemd/sd-journal.h> + +using namespace icinga; + +REGISTER_TYPE(JournaldLogger); + +REGISTER_STATSFUNCTION(JournaldLogger, &JournaldLogger::StatsFunc); + +void JournaldLogger::StatsFunc(const Dictionary::Ptr& status, const Array::Ptr&) +{ + DictionaryData nodes; + + for (const JournaldLogger::Ptr& journaldlogger : ConfigType::GetObjectsByType<JournaldLogger>()) { + nodes.emplace_back(journaldlogger->GetName(), 1); //add more stats + } + + status->Set("journaldlogger", new Dictionary(std::move(nodes))); +} + +void JournaldLogger::OnConfigLoaded() +{ + ObjectImpl<JournaldLogger>::OnConfigLoaded(); + m_ConfiguredJournalFields.clear(); + m_ConfiguredJournalFields.push_back( + String("SYSLOG_FACILITY=") + Value(SyslogHelper::FacilityToNumber(GetFacility()))); + const String identifier = GetIdentifier(); + if (!identifier.IsEmpty()) { + m_ConfiguredJournalFields.push_back(String("SYSLOG_IDENTIFIER=" + identifier)); + } +} + +void JournaldLogger::ValidateFacility(const Lazy<String>& lvalue, const ValidationUtils& utils) +{ + ObjectImpl<JournaldLogger>::ValidateFacility(lvalue, utils); + if (!SyslogHelper::ValidateFacility(lvalue())) + BOOST_THROW_EXCEPTION(ValidationError(this, { "facility" }, "Invalid facility specified.")); +} + +/** + * Processes a log entry and outputs it to journald. + * + * @param entry The log entry. + */ +void JournaldLogger::ProcessLogEntry(const LogEntry& entry) +{ + const std::vector<String> sdFields { + String("MESSAGE=") + entry.Message.GetData(), + String("PRIORITY=") + Value(SyslogHelper::SeverityToNumber(entry.Severity)), + String("ICINGA2_FACILITY=") + entry.Facility, + }; + SystemdJournalSend(sdFields); +} + +void JournaldLogger::Flush() +{ + /* Nothing to do here. */ +} + +void JournaldLogger::SystemdJournalSend(const std::vector<String>& varJournalFields) const +{ + struct iovec iovec[m_ConfiguredJournalFields.size() + varJournalFields.size()]; + int iovecCount = 0; + + for (const String& journalField: m_ConfiguredJournalFields) { + iovec[iovecCount] = IovecFromString(journalField); + iovecCount++; + } + for (const String& journalField: varJournalFields) { + iovec[iovecCount] = IovecFromString(journalField); + iovecCount++; + } + sd_journal_sendv(iovec, iovecCount); +} + +struct iovec JournaldLogger::IovecFromString(const String& s) { + return { const_cast<char *>(s.CStr()), s.GetLength() }; +} +#endif /* !_WIN32 && HAVE_SYSTEMD */ diff --git a/lib/base/journaldlogger.hpp b/lib/base/journaldlogger.hpp new file mode 100644 index 0000000..373dd1a --- /dev/null +++ b/lib/base/journaldlogger.hpp @@ -0,0 +1,44 @@ +/* Icinga 2 | (c) 2021 Icinga GmbH | GPLv2+ */ + +#ifndef JOURNALDLOGGER_H +#define JOURNALDLOGGER_H + +#include "base/i2-base.hpp" +#if !defined(_WIN32) && defined(HAVE_SYSTEMD) +#include "base/journaldlogger-ti.hpp" +#include <sys/uio.h> + +namespace icinga +{ + +/** + * A logger that logs to systemd journald. + * + * @ingroup base + */ +class JournaldLogger final : public ObjectImpl<JournaldLogger> +{ +public: + DECLARE_OBJECT(JournaldLogger); + DECLARE_OBJECTNAME(JournaldLogger); + + static void StaticInitialize(); + static void StatsFunc(const Dictionary::Ptr& status, const Array::Ptr& perfdata); + + void OnConfigLoaded() override; + void ValidateFacility(const Lazy<String>& lvalue, const ValidationUtils& utils) override; + +protected: + void SystemdJournalSend(const std::vector<String>& varJournalFields) const; + static struct iovec IovecFromString(const String& s); + + std::vector<String> m_ConfiguredJournalFields; + + void ProcessLogEntry(const LogEntry& entry) override; + void Flush() override; +}; + +} +#endif /* !_WIN32 && HAVE_SYSTEMD */ + +#endif /* JOURNALDLOGGER_H */ diff --git a/lib/base/journaldlogger.ti b/lib/base/journaldlogger.ti new file mode 100644 index 0000000..88e9ca1 --- /dev/null +++ b/lib/base/journaldlogger.ti @@ -0,0 +1,21 @@ +/* Icinga 2 | (c) 2021 Icinga GmbH | GPLv2+ */ + +#include "base/logger.hpp" + +library base; + +namespace icinga +{ + +class JournaldLogger : Logger +{ + activation_priority -100; + + [config] String facility { + default {{{ return "LOG_USER"; }}} + }; + + [config] String identifier; +}; + +} diff --git a/lib/base/json-script.cpp b/lib/base/json-script.cpp new file mode 100644 index 0000000..90595c8 --- /dev/null +++ b/lib/base/json-script.cpp @@ -0,0 +1,28 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#include "base/dictionary.hpp" +#include "base/function.hpp" +#include "base/functionwrapper.hpp" +#include "base/scriptframe.hpp" +#include "base/initialize.hpp" +#include "base/json.hpp" + +using namespace icinga; + +static String JsonEncodeShim(const Value& value) +{ + return JsonEncode(value); +} + +INITIALIZE_ONCE([]() { + Namespace::Ptr jsonNS = new Namespace(true); + + /* Methods */ + jsonNS->Set("encode", new Function("Json#encode", JsonEncodeShim, { "value" }, true)); + jsonNS->Set("decode", new Function("Json#decode", JsonDecode, { "value" }, true)); + + jsonNS->Freeze(); + + Namespace::Ptr systemNS = ScriptGlobal::Get("System"); + systemNS->Set("Json", jsonNS, true); +}); diff --git a/lib/base/json.cpp b/lib/base/json.cpp new file mode 100644 index 0000000..5689330 --- /dev/null +++ b/lib/base/json.cpp @@ -0,0 +1,525 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#include "base/json.hpp" +#include "base/debug.hpp" +#include "base/namespace.hpp" +#include "base/dictionary.hpp" +#include "base/array.hpp" +#include "base/objectlock.hpp" +#include "base/convert.hpp" +#include "base/utility.hpp" +#include <bitset> +#include <boost/exception_ptr.hpp> +#include <cstdint> +#include <json.hpp> +#include <stack> +#include <utility> +#include <vector> + +using namespace icinga; + +class JsonSax : public nlohmann::json_sax<nlohmann::json> +{ +public: + bool null() override; + bool boolean(bool val) override; + bool number_integer(number_integer_t val) override; + bool number_unsigned(number_unsigned_t val) override; + bool number_float(number_float_t val, const string_t& s) override; + bool string(string_t& val) override; + bool binary(binary_t& val) override; + bool start_object(std::size_t elements) override; + bool key(string_t& val) override; + bool end_object() override; + bool start_array(std::size_t elements) override; + bool end_array() override; + bool parse_error(std::size_t position, const std::string& last_token, const nlohmann::detail::exception& ex) override; + + Value GetResult(); + +private: + Value m_Root; + std::stack<std::pair<Dictionary*, Array*>> m_CurrentSubtree; + String m_CurrentKey; + + void FillCurrentTarget(Value value); +}; + +const char l_Null[] = "null"; +const char l_False[] = "false"; +const char l_True[] = "true"; +const char l_Indent[] = " "; + +// https://github.com/nlohmann/json/issues/1512 +template<bool prettyPrint> +class JsonEncoder +{ +public: + void Null(); + void Boolean(bool value); + void NumberFloat(double value); + void Strng(String value); + void StartObject(); + void Key(String value); + void EndObject(); + void StartArray(); + void EndArray(); + + String GetResult(); + +private: + std::vector<char> m_Result; + String m_CurrentKey; + std::stack<std::bitset<2>> m_CurrentSubtree; + + void AppendChar(char c); + + template<class Iterator> + void AppendChars(Iterator begin, Iterator end); + + void AppendJson(nlohmann::json json); + + void BeforeItem(); + + void FinishContainer(char terminator); +}; + +template<bool prettyPrint> +void Encode(JsonEncoder<prettyPrint>& stateMachine, const Value& value); + +template<bool prettyPrint> +inline +void EncodeNamespace(JsonEncoder<prettyPrint>& stateMachine, const Namespace::Ptr& ns) +{ + stateMachine.StartObject(); + + ObjectLock olock(ns); + for (const Namespace::Pair& kv : ns) { + stateMachine.Key(Utility::ValidateUTF8(kv.first)); + Encode(stateMachine, kv.second.Val); + } + + stateMachine.EndObject(); +} + +template<bool prettyPrint> +inline +void EncodeDictionary(JsonEncoder<prettyPrint>& stateMachine, const Dictionary::Ptr& dict) +{ + stateMachine.StartObject(); + + ObjectLock olock(dict); + for (const Dictionary::Pair& kv : dict) { + stateMachine.Key(Utility::ValidateUTF8(kv.first)); + Encode(stateMachine, kv.second); + } + + stateMachine.EndObject(); +} + +template<bool prettyPrint> +inline +void EncodeArray(JsonEncoder<prettyPrint>& stateMachine, const Array::Ptr& arr) +{ + stateMachine.StartArray(); + + ObjectLock olock(arr); + for (const Value& value : arr) { + Encode(stateMachine, value); + } + + stateMachine.EndArray(); +} + +template<bool prettyPrint> +void Encode(JsonEncoder<prettyPrint>& stateMachine, const Value& value) +{ + switch (value.GetType()) { + case ValueNumber: + stateMachine.NumberFloat(value.Get<double>()); + break; + + case ValueBoolean: + stateMachine.Boolean(value.ToBool()); + break; + + case ValueString: + stateMachine.Strng(Utility::ValidateUTF8(value.Get<String>())); + break; + + case ValueObject: + { + const Object::Ptr& obj = value.Get<Object::Ptr>(); + + { + Namespace::Ptr ns = dynamic_pointer_cast<Namespace>(obj); + if (ns) { + EncodeNamespace(stateMachine, ns); + break; + } + } + + { + Dictionary::Ptr dict = dynamic_pointer_cast<Dictionary>(obj); + if (dict) { + EncodeDictionary(stateMachine, dict); + break; + } + } + + { + Array::Ptr arr = dynamic_pointer_cast<Array>(obj); + if (arr) { + EncodeArray(stateMachine, arr); + break; + } + } + + // obj is most likely a function => "Object of type 'Function'" + Encode(stateMachine, obj->ToString()); + break; + } + + case ValueEmpty: + stateMachine.Null(); + break; + + default: + VERIFY(!"Invalid variant type."); + } +} + +String icinga::JsonEncode(const Value& value, bool pretty_print) +{ + if (pretty_print) { + JsonEncoder<true> stateMachine; + + Encode(stateMachine, value); + + return stateMachine.GetResult() + "\n"; + } else { + JsonEncoder<false> stateMachine; + + Encode(stateMachine, value); + + return stateMachine.GetResult(); + } +} + +Value icinga::JsonDecode(const String& data) +{ + String sanitized (Utility::ValidateUTF8(data)); + + JsonSax stateMachine; + + nlohmann::json::sax_parse(sanitized.Begin(), sanitized.End(), &stateMachine); + + return stateMachine.GetResult(); +} + +inline +bool JsonSax::null() +{ + FillCurrentTarget(Value()); + + return true; +} + +inline +bool JsonSax::boolean(bool val) +{ + FillCurrentTarget(val); + + return true; +} + +inline +bool JsonSax::number_integer(JsonSax::number_integer_t val) +{ + FillCurrentTarget((double)val); + + return true; +} + +inline +bool JsonSax::number_unsigned(JsonSax::number_unsigned_t val) +{ + FillCurrentTarget((double)val); + + return true; +} + +inline +bool JsonSax::number_float(JsonSax::number_float_t val, const JsonSax::string_t&) +{ + FillCurrentTarget((double)val); + + return true; +} + +inline +bool JsonSax::string(JsonSax::string_t& val) +{ + FillCurrentTarget(String(std::move(val))); + + return true; +} + +inline +bool JsonSax::binary(JsonSax::binary_t& val) +{ + FillCurrentTarget(String(val.begin(), val.end())); + + return true; +} + +inline +bool JsonSax::start_object(std::size_t) +{ + auto object (new Dictionary()); + + FillCurrentTarget(object); + + m_CurrentSubtree.push({object, nullptr}); + + return true; +} + +inline +bool JsonSax::key(JsonSax::string_t& val) +{ + m_CurrentKey = String(std::move(val)); + + return true; +} + +inline +bool JsonSax::end_object() +{ + m_CurrentSubtree.pop(); + m_CurrentKey = String(); + + return true; +} + +inline +bool JsonSax::start_array(std::size_t) +{ + auto array (new Array()); + + FillCurrentTarget(array); + + m_CurrentSubtree.push({nullptr, array}); + + return true; +} + +inline +bool JsonSax::end_array() +{ + m_CurrentSubtree.pop(); + + return true; +} + +inline +bool JsonSax::parse_error(std::size_t, const std::string&, const nlohmann::detail::exception& ex) +{ + throw std::invalid_argument(ex.what()); +} + +inline +Value JsonSax::GetResult() +{ + return m_Root; +} + +inline +void JsonSax::FillCurrentTarget(Value value) +{ + if (m_CurrentSubtree.empty()) { + m_Root = value; + } else { + auto& node (m_CurrentSubtree.top()); + + if (node.first) { + node.first->Set(m_CurrentKey, value); + } else { + node.second->Add(value); + } + } +} + +template<bool prettyPrint> +inline +void JsonEncoder<prettyPrint>::Null() +{ + BeforeItem(); + AppendChars((const char*)l_Null, (const char*)l_Null + 4); +} + +template<bool prettyPrint> +inline +void JsonEncoder<prettyPrint>::Boolean(bool value) +{ + BeforeItem(); + + if (value) { + AppendChars((const char*)l_True, (const char*)l_True + 4); + } else { + AppendChars((const char*)l_False, (const char*)l_False + 5); + } +} + +template<bool prettyPrint> +inline +void JsonEncoder<prettyPrint>::NumberFloat(double value) +{ + BeforeItem(); + + // Make sure 0.0 is serialized as 0, so e.g. Icinga DB can parse it as int. + if (value < 0) { + long long i = value; + + if (i == value) { + AppendJson(i); + } else { + AppendJson(value); + } + } else { + unsigned long long i = value; + + if (i == value) { + AppendJson(i); + } else { + AppendJson(value); + } + } +} + +template<bool prettyPrint> +inline +void JsonEncoder<prettyPrint>::Strng(String value) +{ + BeforeItem(); + AppendJson(std::move(value)); +} + +template<bool prettyPrint> +inline +void JsonEncoder<prettyPrint>::StartObject() +{ + BeforeItem(); + AppendChar('{'); + + m_CurrentSubtree.push(2); +} + +template<bool prettyPrint> +inline +void JsonEncoder<prettyPrint>::Key(String value) +{ + m_CurrentKey = std::move(value); +} + +template<bool prettyPrint> +inline +void JsonEncoder<prettyPrint>::EndObject() +{ + FinishContainer('}'); +} + +template<bool prettyPrint> +inline +void JsonEncoder<prettyPrint>::StartArray() +{ + BeforeItem(); + AppendChar('['); + + m_CurrentSubtree.push(0); +} + +template<bool prettyPrint> +inline +void JsonEncoder<prettyPrint>::EndArray() +{ + FinishContainer(']'); +} + +template<bool prettyPrint> +inline +String JsonEncoder<prettyPrint>::GetResult() +{ + return String(m_Result.begin(), m_Result.end()); +} + +template<bool prettyPrint> +inline +void JsonEncoder<prettyPrint>::AppendChar(char c) +{ + m_Result.emplace_back(c); +} + +template<bool prettyPrint> +template<class Iterator> +inline +void JsonEncoder<prettyPrint>::AppendChars(Iterator begin, Iterator end) +{ + m_Result.insert(m_Result.end(), begin, end); +} + +template<bool prettyPrint> +inline +void JsonEncoder<prettyPrint>::AppendJson(nlohmann::json json) +{ + nlohmann::detail::serializer<nlohmann::json>(nlohmann::detail::output_adapter<char>(m_Result), ' ').dump(std::move(json), prettyPrint, true, 0); +} + +template<bool prettyPrint> +inline +void JsonEncoder<prettyPrint>::BeforeItem() +{ + if (!m_CurrentSubtree.empty()) { + auto& node (m_CurrentSubtree.top()); + + if (node[0]) { + AppendChar(','); + } else { + node[0] = true; + } + + if (prettyPrint) { + AppendChar('\n'); + + for (auto i (m_CurrentSubtree.size()); i; --i) { + AppendChars((const char*)l_Indent, (const char*)l_Indent + 4); + } + } + + if (node[1]) { + AppendJson(std::move(m_CurrentKey)); + AppendChar(':'); + + if (prettyPrint) { + AppendChar(' '); + } + } + } +} + +template<bool prettyPrint> +inline +void JsonEncoder<prettyPrint>::FinishContainer(char terminator) +{ + if (prettyPrint && m_CurrentSubtree.top()[0]) { + AppendChar('\n'); + + for (auto i (m_CurrentSubtree.size() - 1u); i; --i) { + AppendChars((const char*)l_Indent, (const char*)l_Indent + 4); + } + } + + AppendChar(terminator); + + m_CurrentSubtree.pop(); +} diff --git a/lib/base/json.hpp b/lib/base/json.hpp new file mode 100644 index 0000000..df0ea18 --- /dev/null +++ b/lib/base/json.hpp @@ -0,0 +1,19 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#ifndef JSON_H +#define JSON_H + +#include "base/i2-base.hpp" + +namespace icinga +{ + +class String; +class Value; + +String JsonEncode(const Value& value, bool pretty_print = false); +Value JsonDecode(const String& data); + +} + +#endif /* JSON_H */ diff --git a/lib/base/lazy-init.hpp b/lib/base/lazy-init.hpp new file mode 100644 index 0000000..c1da2cd --- /dev/null +++ b/lib/base/lazy-init.hpp @@ -0,0 +1,72 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#ifndef LAZY_INIT +#define LAZY_INIT + +#include <atomic> +#include <functional> +#include <mutex> +#include <utility> + +namespace icinga +{ + +/** + * Lazy object initialization abstraction inspired from + * <https://docs.microsoft.com/en-us/dotnet/api/system.lazy-1?view=netframework-4.7.2>. + * + * @ingroup base + */ +template<class T> +class LazyInit +{ +public: + inline + LazyInit(std::function<T()> initializer = []() { return T(); }) : m_Initializer(std::move(initializer)) + { + m_Underlying.store(nullptr, std::memory_order_release); + } + + LazyInit(const LazyInit&) = delete; + LazyInit(LazyInit&&) = delete; + LazyInit& operator=(const LazyInit&) = delete; + LazyInit& operator=(LazyInit&&) = delete; + + inline + ~LazyInit() + { + auto ptr (m_Underlying.load(std::memory_order_acquire)); + + if (ptr != nullptr) { + delete ptr; + } + } + + inline + T& Get() + { + auto ptr (m_Underlying.load(std::memory_order_acquire)); + + if (ptr == nullptr) { + std::unique_lock<std::mutex> lock (m_Mutex); + + ptr = m_Underlying.load(std::memory_order_acquire); + + if (ptr == nullptr) { + ptr = new T(m_Initializer()); + m_Underlying.store(ptr, std::memory_order_release); + } + } + + return *ptr; + } + +private: + std::function<T()> m_Initializer; + std::mutex m_Mutex; + std::atomic<T*> m_Underlying; +}; + +} + +#endif /* LAZY_INIT */ diff --git a/lib/base/library.cpp b/lib/base/library.cpp new file mode 100644 index 0000000..541ed74 --- /dev/null +++ b/lib/base/library.cpp @@ -0,0 +1,68 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#include "base/library.hpp" +#include "base/loader.hpp" +#include "base/logger.hpp" +#include "base/exception.hpp" +#include "base/application.hpp" + +using namespace icinga; + +/** + * Loads the specified library. + * + * @param name The name of the library. + */ +Library::Library(const String& name) +{ + String path; +#if defined(_WIN32) + path = name + ".dll"; +#elif defined(__APPLE__) + path = "lib" + name + "." + Application::GetAppSpecVersion() + ".dylib"; +#else /* __APPLE__ */ + path = "lib" + name + ".so." + Application::GetAppSpecVersion(); +#endif /* _WIN32 */ + + Log(LogNotice, "Library") + << "Loading library '" << path << "'"; + +#ifdef _WIN32 + HMODULE hModule = LoadLibrary(path.CStr()); + + if (!hModule) { + BOOST_THROW_EXCEPTION(win32_error() + << boost::errinfo_api_function("LoadLibrary") + << errinfo_win32_error(GetLastError()) + << boost::errinfo_file_name(path)); + } +#else /* _WIN32 */ + void *hModule = dlopen(path.CStr(), RTLD_NOW | RTLD_GLOBAL); + + if (!hModule) { + BOOST_THROW_EXCEPTION(std::runtime_error("Could not load library '" + path + "': " + dlerror())); + } +#endif /* _WIN32 */ + + Loader::ExecuteDeferredInitializers(); + + m_Handle.reset(new LibraryHandle(hModule), [](LibraryHandle *handle) { +#ifdef _WIN32 + FreeLibrary(*handle); +#else /* _WIN32 */ + dlclose(*handle); +#endif /* _WIN32 */ + }); +} + +void *Library::GetSymbolAddress(const String& name) const +{ + if (!m_Handle) + BOOST_THROW_EXCEPTION(std::runtime_error("Invalid library handle")); + +#ifdef _WIN32 + return GetProcAddress(*m_Handle.get(), name.CStr()); +#else /* _WIN32 */ + return dlsym(*m_Handle.get(), name.CStr()); +#endif /* _WIN32 */ +} diff --git a/lib/base/library.hpp b/lib/base/library.hpp new file mode 100644 index 0000000..6bd2065 --- /dev/null +++ b/lib/base/library.hpp @@ -0,0 +1,41 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#ifndef LIBRARY_H +#define LIBRARY_H + +#include "base/i2-base.hpp" +#include "base/string.hpp" +#include <memory> + +namespace icinga +{ + +#ifndef _WIN32 +typedef void *LibraryHandle; +#else /* _WIN32 */ +typedef HMODULE LibraryHandle; +#endif /* _WIN32 */ + +class Library +{ +public: + Library() = default; + Library(const String& name); + + void *GetSymbolAddress(const String& name) const; + + template<typename T> + T GetSymbolAddress(const String& name) const + { + static_assert(!std::is_same<T, void *>::value, "T must not be void *"); + + return reinterpret_cast<T>(GetSymbolAddress(name)); + } + +private: + std::shared_ptr<LibraryHandle> m_Handle; +}; + +} + +#endif /* LIBRARY_H */ diff --git a/lib/base/loader.cpp b/lib/base/loader.cpp new file mode 100644 index 0000000..a4364de --- /dev/null +++ b/lib/base/loader.cpp @@ -0,0 +1,38 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#include "base/loader.hpp" +#include "base/logger.hpp" +#include "base/exception.hpp" +#include "base/application.hpp" + +using namespace icinga; + +boost::thread_specific_ptr<Loader::DeferredInitializerPriorityQueue>& Loader::GetDeferredInitializers() +{ + static boost::thread_specific_ptr<DeferredInitializerPriorityQueue> initializers; + return initializers; +} + +void Loader::ExecuteDeferredInitializers() +{ + auto& initializers = GetDeferredInitializers(); + if (!initializers.get()) + return; + + while (!initializers->empty()) { + DeferredInitializer initializer = initializers->top(); + initializers->pop(); + initializer(); + } +} + +void Loader::AddDeferredInitializer(const std::function<void()>& callback, InitializePriority priority) +{ + auto& initializers = GetDeferredInitializers(); + if (!initializers.get()) { + initializers.reset(new Loader::DeferredInitializerPriorityQueue()); + } + + initializers->push(DeferredInitializer(callback, priority)); +} + diff --git a/lib/base/loader.hpp b/lib/base/loader.hpp new file mode 100644 index 0000000..f1c7759 --- /dev/null +++ b/lib/base/loader.hpp @@ -0,0 +1,61 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#ifndef LOADER_H +#define LOADER_H + +#include "base/i2-base.hpp" +#include "base/initialize.hpp" +#include "base/string.hpp" +#include <boost/thread/tss.hpp> +#include <queue> + +namespace icinga +{ + +struct DeferredInitializer +{ +public: + DeferredInitializer(std::function<void ()> callback, InitializePriority priority) + : m_Callback(std::move(callback)), m_Priority(priority) + { } + + bool operator>(const DeferredInitializer& other) const + { + return m_Priority > other.m_Priority; + } + + void operator()() + { + m_Callback(); + } + +private: + std::function<void ()> m_Callback; + InitializePriority m_Priority; +}; + +/** + * Loader helper functions. + * + * @ingroup base + */ +class Loader +{ +public: + static void AddDeferredInitializer(const std::function<void ()>& callback, InitializePriority priority = InitializePriority::Default); + static void ExecuteDeferredInitializers(); + +private: + Loader(); + + // Deferred initializers are run in the order of the definition of their enum values. + // Therefore, initializers that should be run first have lower enum values and + // the order of the std::priority_queue has to be reversed using std::greater. + using DeferredInitializerPriorityQueue = std::priority_queue<DeferredInitializer, std::vector<DeferredInitializer>, std::greater<>>; + + static boost::thread_specific_ptr<DeferredInitializerPriorityQueue>& GetDeferredInitializers(); +}; + +} + +#endif /* LOADER_H */ diff --git a/lib/base/logger.cpp b/lib/base/logger.cpp new file mode 100644 index 0000000..38a2c67 --- /dev/null +++ b/lib/base/logger.cpp @@ -0,0 +1,326 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#include "base/logger.hpp" +#include "base/logger-ti.cpp" +#include "base/application.hpp" +#include "base/streamlogger.hpp" +#include "base/configtype.hpp" +#include "base/utility.hpp" +#include "base/objectlock.hpp" +#include "base/context.hpp" +#include "base/scriptglobal.hpp" +#ifdef _WIN32 +#include "base/windowseventloglogger.hpp" +#endif /* _WIN32 */ +#include <algorithm> +#include <iostream> +#include <utility> + +using namespace icinga; + +template Log& Log::operator<<(const Value&); +template Log& Log::operator<<(const String&); +template Log& Log::operator<<(const std::string&); +template Log& Log::operator<<(const bool&); +template Log& Log::operator<<(const unsigned int&); +template Log& Log::operator<<(const int&); +template Log& Log::operator<<(const unsigned long&); +template Log& Log::operator<<(const long&); +template Log& Log::operator<<(const double&); + +REGISTER_TYPE(Logger); + +std::set<Logger::Ptr> Logger::m_Loggers; +std::mutex Logger::m_Mutex; +bool Logger::m_ConsoleLogEnabled = true; +std::atomic<bool> Logger::m_EarlyLoggingEnabled (true); +bool Logger::m_TimestampEnabled = true; +LogSeverity Logger::m_ConsoleLogSeverity = LogInformation; +std::mutex Logger::m_UpdateMinLogSeverityMutex; +Atomic<LogSeverity> Logger::m_MinLogSeverity (LogDebug); + +INITIALIZE_ONCE([]() { + ScriptGlobal::Set("System.LogDebug", LogDebug); + ScriptGlobal::Set("System.LogNotice", LogNotice); + ScriptGlobal::Set("System.LogInformation", LogInformation); + ScriptGlobal::Set("System.LogWarning", LogWarning); + ScriptGlobal::Set("System.LogCritical", LogCritical); +}); + +/** + * Constructor for the Logger class. + */ +void Logger::Start(bool runtimeCreated) +{ + ObjectImpl<Logger>::Start(runtimeCreated); + + { + std::unique_lock<std::mutex> lock(m_Mutex); + m_Loggers.insert(this); + } + + UpdateMinLogSeverity(); +} + +void Logger::Stop(bool runtimeRemoved) +{ + { + std::unique_lock<std::mutex> lock(m_Mutex); + m_Loggers.erase(this); + } + + UpdateMinLogSeverity(); + + ObjectImpl<Logger>::Stop(runtimeRemoved); +} + +std::set<Logger::Ptr> Logger::GetLoggers() +{ + std::unique_lock<std::mutex> lock(m_Mutex); + return m_Loggers; +} + +/** + * Retrieves the minimum severity for this logger. + * + * @returns The minimum severity. + */ +LogSeverity Logger::GetMinSeverity() const +{ + String severity = GetSeverity(); + if (severity.IsEmpty()) + return LogInformation; + else { + LogSeverity ls = LogInformation; + + try { + ls = Logger::StringToSeverity(severity); + } catch (const std::exception&) { /* use the default level */ } + + return ls; + } +} + +/** + * Converts a severity enum value to a string. + * + * @param severity The severity value. + */ +String Logger::SeverityToString(LogSeverity severity) +{ + switch (severity) { + case LogDebug: + return "debug"; + case LogNotice: + return "notice"; + case LogInformation: + return "information"; + case LogWarning: + return "warning"; + case LogCritical: + return "critical"; + default: + BOOST_THROW_EXCEPTION(std::invalid_argument("Invalid severity.")); + } +} + +/** + * Converts a string to a severity enum value. + * + * @param severity The severity. + */ +LogSeverity Logger::StringToSeverity(const String& severity) +{ + if (severity == "debug") + return LogDebug; + else if (severity == "notice") + return LogNotice; + else if (severity == "information") + return LogInformation; + else if (severity == "warning") + return LogWarning; + else if (severity == "critical") + return LogCritical; + else + BOOST_THROW_EXCEPTION(std::invalid_argument("Invalid severity: " + severity)); +} + +void Logger::DisableConsoleLog() +{ + m_ConsoleLogEnabled = false; + + UpdateMinLogSeverity(); +} + +void Logger::EnableConsoleLog() +{ + m_ConsoleLogEnabled = true; + + UpdateMinLogSeverity(); +} + +bool Logger::IsConsoleLogEnabled() +{ + return m_ConsoleLogEnabled; +} + +void Logger::SetConsoleLogSeverity(LogSeverity logSeverity) +{ + m_ConsoleLogSeverity = logSeverity; +} + +LogSeverity Logger::GetConsoleLogSeverity() +{ + return m_ConsoleLogSeverity; +} + +void Logger::DisableEarlyLogging() { + m_EarlyLoggingEnabled = false; + + UpdateMinLogSeverity(); +} + +bool Logger::IsEarlyLoggingEnabled() { + return m_EarlyLoggingEnabled; +} + +void Logger::DisableTimestamp() +{ + m_TimestampEnabled = false; +} + +void Logger::EnableTimestamp() +{ + m_TimestampEnabled = true; +} + +bool Logger::IsTimestampEnabled() +{ + return m_TimestampEnabled; +} + +void Logger::SetSeverity(const String& value, bool suppress_events, const Value& cookie) +{ + ObjectImpl<Logger>::SetSeverity(value, suppress_events, cookie); + + UpdateMinLogSeverity(); +} + +void Logger::ValidateSeverity(const Lazy<String>& lvalue, const ValidationUtils& utils) +{ + ObjectImpl<Logger>::ValidateSeverity(lvalue, utils); + + try { + StringToSeverity(lvalue()); + } catch (...) { + BOOST_THROW_EXCEPTION(ValidationError(this, { "severity" }, "Invalid severity specified: " + lvalue())); + } +} + +void Logger::UpdateMinLogSeverity() +{ + std::unique_lock<std::mutex> lock (m_UpdateMinLogSeverityMutex); + auto result (LogNothing); + + for (auto& logger : Logger::GetLoggers()) { + ObjectLock llock (logger); + + if (logger->IsActive()) { + result = std::min(result, logger->GetMinSeverity()); + } + } + + if (Logger::IsConsoleLogEnabled()) { + result = std::min(result, Logger::GetConsoleLogSeverity()); + } + +#ifdef _WIN32 + if (Logger::IsEarlyLoggingEnabled()) { + result = std::min(result, LogCritical); + } +#endif /* _WIN32 */ + + m_MinLogSeverity.store(result); +} + +Log::Log(LogSeverity severity, String facility, const String& message) + : Log(severity, std::move(facility)) +{ + if (!m_IsNoOp) { + m_Buffer << message; + } +} + +Log::Log(LogSeverity severity, String facility) + : m_Severity(severity), m_Facility(std::move(facility)), m_IsNoOp(severity < Logger::GetMinLogSeverity()) +{ } + +/** + * Writes the message to the application's log. + */ +Log::~Log() +{ + if (m_IsNoOp) { + return; + } + + LogEntry entry; + entry.Timestamp = Utility::GetTime(); + entry.Severity = m_Severity; + entry.Facility = m_Facility; + + { + auto msg (m_Buffer.str()); + msg.erase(msg.find_last_not_of("\n") + 1u); + + entry.Message = std::move(msg); + } + + if (m_Severity >= LogWarning) { + ContextTrace context; + + if (context.GetLength() > 0) { + std::ostringstream trace; + trace << context; + entry.Message += "\nContext:" + trace.str(); + } + } + + for (const Logger::Ptr& logger : Logger::GetLoggers()) { + ObjectLock llock(logger); + + if (!logger->IsActive()) + continue; + + if (entry.Severity >= logger->GetMinSeverity()) + logger->ProcessLogEntry(entry); + +#ifdef I2_DEBUG /* I2_DEBUG */ + /* Always flush, don't depend on the timer. Enable this for development sprints on Linux/macOS only. Windows crashes. */ + //logger->Flush(); +#endif /* I2_DEBUG */ + } + + if (Logger::IsConsoleLogEnabled() && entry.Severity >= Logger::GetConsoleLogSeverity()) { + StreamLogger::ProcessLogEntry(std::cout, entry); + + /* "Console" might be a pipe/socket (systemd, daemontools, docker, ...), + * then cout will not flush lines automatically. */ + std::cout << std::flush; + } + +#ifdef _WIN32 + if (Logger::IsEarlyLoggingEnabled() && entry.Severity >= LogCritical) { + WindowsEventLogLogger::WriteToWindowsEventLog(entry); + } +#endif /* _WIN32 */ +} + +Log& Log::operator<<(const char *val) +{ + if (!m_IsNoOp) { + m_Buffer << val; + } + + return *this; +} diff --git a/lib/base/logger.hpp b/lib/base/logger.hpp new file mode 100644 index 0000000..10e0872 --- /dev/null +++ b/lib/base/logger.hpp @@ -0,0 +1,149 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#ifndef LOGGER_H +#define LOGGER_H + +#include "base/atomic.hpp" +#include "base/i2-base.hpp" +#include "base/logger-ti.hpp" +#include <set> +#include <sstream> + +namespace icinga +{ + +/** + * Log severity. + * + * @ingroup base + */ +enum LogSeverity +{ + LogDebug, + LogNotice, + LogInformation, + LogWarning, + LogCritical, + + // Just for internal comparision + LogNothing, +}; + +/** + * A log entry. + * + * @ingroup base + */ +struct LogEntry { + double Timestamp; /**< The timestamp when this log entry was created. */ + LogSeverity Severity; /**< The severity of this log entry. */ + String Facility; /**< The facility this log entry belongs to. */ + String Message; /**< The log entry's message. */ +}; + +/** + * A log provider. + * + * @ingroup base + */ +class Logger : public ObjectImpl<Logger> +{ +public: + DECLARE_OBJECT(Logger); + + static String SeverityToString(LogSeverity severity); + static LogSeverity StringToSeverity(const String& severity); + + LogSeverity GetMinSeverity() const; + + /** + * Processes the log entry and writes it to the log that is + * represented by this ILogger object. + * + * @param entry The log entry that is to be processed. + */ + virtual void ProcessLogEntry(const LogEntry& entry) = 0; + + virtual void Flush() = 0; + + static std::set<Logger::Ptr> GetLoggers(); + + static void DisableConsoleLog(); + static void EnableConsoleLog(); + static bool IsConsoleLogEnabled(); + static void DisableEarlyLogging(); + static bool IsEarlyLoggingEnabled(); + static void DisableTimestamp(); + static void EnableTimestamp(); + static bool IsTimestampEnabled(); + + static void SetConsoleLogSeverity(LogSeverity logSeverity); + static LogSeverity GetConsoleLogSeverity(); + + static inline + LogSeverity GetMinLogSeverity() + { + return m_MinLogSeverity.load(); + } + + void SetSeverity(const String& value, bool suppress_events = false, const Value& cookie = Empty) override; + void ValidateSeverity(const Lazy<String>& lvalue, const ValidationUtils& utils) final; + +protected: + void Start(bool runtimeCreated) override; + void Stop(bool runtimeRemoved) override; + +private: + static void UpdateMinLogSeverity(); + + static std::mutex m_Mutex; + static std::set<Logger::Ptr> m_Loggers; + static bool m_ConsoleLogEnabled; + static std::atomic<bool> m_EarlyLoggingEnabled; + static bool m_TimestampEnabled; + static LogSeverity m_ConsoleLogSeverity; + static std::mutex m_UpdateMinLogSeverityMutex; + static Atomic<LogSeverity> m_MinLogSeverity; +}; + +class Log +{ +public: + Log() = delete; + Log(const Log& other) = delete; + Log& operator=(const Log& rhs) = delete; + + Log(LogSeverity severity, String facility, const String& message); + Log(LogSeverity severity, String facility); + + ~Log(); + + template<typename T> + Log& operator<<(const T& val) + { + m_Buffer << val; + return *this; + } + + Log& operator<<(const char *val); + +private: + LogSeverity m_Severity; + String m_Facility; + std::ostringstream m_Buffer; + bool m_IsNoOp; +}; + +extern template Log& Log::operator<<(const Value&); +extern template Log& Log::operator<<(const String&); +extern template Log& Log::operator<<(const std::string&); +extern template Log& Log::operator<<(const bool&); +extern template Log& Log::operator<<(const unsigned int&); +extern template Log& Log::operator<<(const int&); +extern template Log& Log::operator<<(const unsigned long&); +extern template Log& Log::operator<<(const long&); +extern template Log& Log::operator<<(const double&); + +} + +#endif /* LOGGER_H */ diff --git a/lib/base/logger.ti b/lib/base/logger.ti new file mode 100644 index 0000000..44226ce --- /dev/null +++ b/lib/base/logger.ti @@ -0,0 +1,17 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#include "base/configobject.hpp" + +library base; + +namespace icinga +{ + +abstract class Logger : ConfigObject +{ + [config, virtual] String severity { + default {{{ return "information"; }}} + }; +}; + +} diff --git a/lib/base/math-script.cpp b/lib/base/math-script.cpp new file mode 100644 index 0000000..6cd7b0e --- /dev/null +++ b/lib/base/math-script.cpp @@ -0,0 +1,184 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#include "base/dictionary.hpp" +#include "base/function.hpp" +#include "base/functionwrapper.hpp" +#include "base/scriptframe.hpp" +#include "base/initialize.hpp" +#include "base/namespace.hpp" +#include <boost/math/special_functions/round.hpp> +#include <cmath> + +using namespace icinga; + +static double MathAbs(double x) +{ + return std::fabs(x); +} + +static double MathAcos(double x) +{ + return std::acos(x); +} + +static double MathAsin(double x) +{ + return std::asin(x); +} + +static double MathAtan(double x) +{ + return std::atan(x); +} + +static double MathAtan2(double y, double x) +{ + return std::atan2(y, x); +} + +static double MathCeil(double x) +{ + return std::ceil(x); +} + +static double MathCos(double x) +{ + return std::cos(x); +} + +static double MathExp(double x) +{ + return std::exp(x); +} + +static double MathFloor(double x) +{ + return std::floor(x); +} + +static double MathLog(double x) +{ + return std::log(x); +} + +static Value MathMax(const std::vector<Value>& args) +{ + bool first = true; + Value result = -INFINITY; + + for (const Value& arg : args) { + if (first || arg > result) { + first = false; + result = arg; + } + } + + return result; +} + +static Value MathMin(const std::vector<Value>& args) +{ + bool first = true; + Value result = INFINITY; + + for (const Value& arg : args) { + if (first || arg < result) { + first = false; + result = arg; + } + } + + return result; +} + +static double MathPow(double x, double y) +{ + return std::pow(x, y); +} + +static double MathRandom() +{ + return (double)std::rand() / RAND_MAX; +} + +static double MathRound(double x) +{ + return boost::math::round(x); +} + +static double MathSin(double x) +{ + return std::sin(x); +} + +static double MathSqrt(double x) +{ + return std::sqrt(x); +} + +static double MathTan(double x) +{ + return std::tan(x); +} + +static bool MathIsnan(double x) +{ + return boost::math::isnan(x); +} + +static bool MathIsinf(double x) +{ + return boost::math::isinf(x); +} + +static double MathSign(double x) +{ + if (x > 0) + return 1; + else if (x < 0) + return -1; + else + return 0; +} + +INITIALIZE_ONCE([]() { + Namespace::Ptr mathNS = new Namespace(true); + + /* Constants */ + mathNS->Set("E", 2.71828182845904523536); + mathNS->Set("LN2", 0.693147180559945309417); + mathNS->Set("LN10", 2.30258509299404568402); + mathNS->Set("LOG2E", 1.44269504088896340736); + mathNS->Set("LOG10E", 0.434294481903251827651); + mathNS->Set("PI", 3.14159265358979323846); + mathNS->Set("SQRT1_2", 0.707106781186547524401); + mathNS->Set("SQRT2", 1.41421356237309504880); + + /* Methods */ + mathNS->Set("abs", new Function("Math#abs", MathAbs, { "x" }, true)); + mathNS->Set("acos", new Function("Math#acos", MathAcos, { "x" }, true)); + mathNS->Set("asin", new Function("Math#asin", MathAsin, { "x" }, true)); + mathNS->Set("atan", new Function("Math#atan", MathAtan, { "x" }, true)); + mathNS->Set("atan2", new Function("Math#atan2", MathAtan2, { "x", "y" }, true)); + mathNS->Set("ceil", new Function("Math#ceil", MathCeil, { "x" }, true)); + mathNS->Set("cos", new Function("Math#cos", MathCos, { "x" }, true)); + mathNS->Set("exp", new Function("Math#exp", MathExp, { "x" }, true)); + mathNS->Set("floor", new Function("Math#floor", MathFloor, { "x" }, true)); + mathNS->Set("log", new Function("Math#log", MathLog, { "x" }, true)); + mathNS->Set("max", new Function("Math#max", MathMax, {}, true)); + mathNS->Set("min", new Function("Math#min", MathMin, {}, true)); + mathNS->Set("pow", new Function("Math#pow", MathPow, { "x", "y" }, true)); + mathNS->Set("random", new Function("Math#random", MathRandom, {}, true)); + mathNS->Set("round", new Function("Math#round", MathRound, { "x" }, true)); + mathNS->Set("sin", new Function("Math#sin", MathSin, { "x" }, true)); + mathNS->Set("sqrt", new Function("Math#sqrt", MathSqrt, { "x" }, true)); + mathNS->Set("tan", new Function("Math#tan", MathTan, { "x" }, true)); + mathNS->Set("isnan", new Function("Math#isnan", MathIsnan, { "x" }, true)); + mathNS->Set("isinf", new Function("Math#isinf", MathIsinf, { "x" }, true)); + mathNS->Set("sign", new Function("Math#sign", MathSign, { "x" }, true)); + + mathNS->Freeze(); + + Namespace::Ptr systemNS = ScriptGlobal::Get("System"); + systemNS->Set("Math", mathNS, true); +}); diff --git a/lib/base/namespace-script.cpp b/lib/base/namespace-script.cpp new file mode 100644 index 0000000..deaae7d --- /dev/null +++ b/lib/base/namespace-script.cpp @@ -0,0 +1,84 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#include "base/namespace.hpp" +#include "base/function.hpp" +#include "base/functionwrapper.hpp" +#include "base/scriptframe.hpp" +#include "base/array.hpp" + +using namespace icinga; + +static void NamespaceSet(const String& key, const Value& value) +{ + ScriptFrame *vframe = ScriptFrame::GetCurrentFrame(); + Namespace::Ptr self = static_cast<Namespace::Ptr>(vframe->Self); + REQUIRE_NOT_NULL(self); + self->Set(key, value); +} + +static Value NamespaceGet(const String& key) +{ + ScriptFrame *vframe = ScriptFrame::GetCurrentFrame(); + Namespace::Ptr self = static_cast<Namespace::Ptr>(vframe->Self); + REQUIRE_NOT_NULL(self); + return self->Get(key); +} + +static void NamespaceRemove(const String& key) +{ + ScriptFrame *vframe = ScriptFrame::GetCurrentFrame(); + Namespace::Ptr self = static_cast<Namespace::Ptr>(vframe->Self); + REQUIRE_NOT_NULL(self); + self->Remove(key); +} + +static bool NamespaceContains(const String& key) +{ + ScriptFrame *vframe = ScriptFrame::GetCurrentFrame(); + Namespace::Ptr self = static_cast<Namespace::Ptr>(vframe->Self); + REQUIRE_NOT_NULL(self); + return self->Contains(key); +} + +static Array::Ptr NamespaceKeys() +{ + ScriptFrame *vframe = ScriptFrame::GetCurrentFrame(); + Namespace::Ptr self = static_cast<Namespace::Ptr>(vframe->Self); + REQUIRE_NOT_NULL(self); + + ArrayData keys; + ObjectLock olock(self); + for (const Namespace::Pair& kv : self) { + keys.push_back(kv.first); + } + return new Array(std::move(keys)); +} + +static Array::Ptr NamespaceValues() +{ + ScriptFrame *vframe = ScriptFrame::GetCurrentFrame(); + Namespace::Ptr self = static_cast<Namespace::Ptr>(vframe->Self); + REQUIRE_NOT_NULL(self); + + ArrayData values; + ObjectLock olock(self); + for (const Namespace::Pair& kv : self) { + values.push_back(kv.second.Val); + } + return new Array(std::move(values)); +} + +Object::Ptr Namespace::GetPrototype() +{ + static Dictionary::Ptr prototype = new Dictionary({ + { "set", new Function("Namespace#set", NamespaceSet, { "key", "value" }) }, + { "get", new Function("Namespace#get", NamespaceGet, { "key" }) }, + { "remove", new Function("Namespace#remove", NamespaceRemove, { "key" }) }, + { "contains", new Function("Namespace#contains", NamespaceContains, { "key" }, true) }, + { "keys", new Function("Namespace#keys", NamespaceKeys, {}, true) }, + { "values", new Function("Namespace#values", NamespaceValues, {}, true) }, + }); + + return prototype; +} + diff --git a/lib/base/namespace.cpp b/lib/base/namespace.cpp new file mode 100644 index 0000000..4c5f4f6 --- /dev/null +++ b/lib/base/namespace.cpp @@ -0,0 +1,189 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#include "base/namespace.hpp" +#include "base/objectlock.hpp" +#include "base/debug.hpp" +#include "base/primitivetype.hpp" +#include "base/debuginfo.hpp" +#include "base/exception.hpp" +#include <sstream> + +using namespace icinga; + +template class std::map<icinga::String, std::shared_ptr<icinga::NamespaceValue> >; + +REGISTER_PRIMITIVE_TYPE(Namespace, Object, Namespace::GetPrototype()); + +/** + * Creates a new namespace. + * + * @param constValues If true, all values inserted into the namespace are treated as constants and can't be updated. + */ +Namespace::Namespace(bool constValues) + : m_ConstValues(constValues), m_Frozen(false) +{ } + +Value Namespace::Get(const String& field) const +{ + Value value; + if (!Get(field, &value)) + BOOST_THROW_EXCEPTION(ScriptError("Namespace does not contain field '" + field + "'")); + return value; +} + +bool Namespace::Get(const String& field, Value *value) const +{ + auto lock(ReadLockUnlessFrozen()); + + auto nsVal = m_Data.find(field); + + if (nsVal == m_Data.end()) { + return false; + } + + *value = nsVal->second.Val; + return true; +} + +void Namespace::Set(const String& field, const Value& value, bool isConst, const DebugInfo& debugInfo) +{ + ObjectLock olock(this); + + if (m_Frozen) { + BOOST_THROW_EXCEPTION(ScriptError("Namespace is read-only and must not be modified.", debugInfo)); + } + + std::unique_lock<decltype(m_DataMutex)> dlock (m_DataMutex); + + auto nsVal = m_Data.find(field); + + if (nsVal == m_Data.end()) { + m_Data[field] = NamespaceValue{value, isConst || m_ConstValues}; + } else { + if (nsVal->second.Const) { + BOOST_THROW_EXCEPTION(ScriptError("Constant must not be modified.", debugInfo)); + } + + nsVal->second.Val = value; + } +} + +/** + * Returns the number of elements in the namespace. + * + * @returns Number of elements. + */ +size_t Namespace::GetLength() const +{ + auto lock(ReadLockUnlessFrozen()); + + return m_Data.size(); +} + +bool Namespace::Contains(const String& field) const +{ + auto lock (ReadLockUnlessFrozen()); + + return m_Data.find(field) != m_Data.end(); +} + +void Namespace::Remove(const String& field) +{ + ObjectLock olock(this); + + if (m_Frozen) { + BOOST_THROW_EXCEPTION(ScriptError("Namespace is read-only and must not be modified.")); + } + + std::unique_lock<decltype(m_DataMutex)> dlock (m_DataMutex); + + auto it = m_Data.find(field); + + if (it == m_Data.end()) { + return; + } + + if (it->second.Const) { + BOOST_THROW_EXCEPTION(ScriptError("Constants must not be removed.")); + } + + m_Data.erase(it); +} + +/** + * Freeze the namespace, preventing further updates. + * + * This only prevents inserting, replacing or deleting values from the namespace. This operation has no effect on + * objects referenced by the values, these remain mutable if they were before. + */ +void Namespace::Freeze() { + ObjectLock olock(this); + + m_Frozen = true; +} + +std::shared_lock<std::shared_timed_mutex> Namespace::ReadLockUnlessFrozen() const +{ + if (m_Frozen.load(std::memory_order_relaxed)) { + return std::shared_lock<std::shared_timed_mutex>(); + } else { + return std::shared_lock<std::shared_timed_mutex>(m_DataMutex); + } +} + +Value Namespace::GetFieldByName(const String& field, bool, const DebugInfo& debugInfo) const +{ + auto lock (ReadLockUnlessFrozen()); + + auto nsVal = m_Data.find(field); + + if (nsVal != m_Data.end()) + return nsVal->second.Val; + else + return GetPrototypeField(const_cast<Namespace *>(this), field, false, debugInfo); /* Ignore indexer not found errors similar to the Dictionary class. */ +} + +void Namespace::SetFieldByName(const String& field, const Value& value, bool overrideFrozen, const DebugInfo& debugInfo) +{ + // The override frozen parameter is mandated by the interface but ignored here. If the namespace is frozen, this + // disables locking for read operations, so it must not be modified again to ensure the consistency of the internal + // data structures. + (void) overrideFrozen; + + Set(field, value, false, debugInfo); +} + +bool Namespace::HasOwnField(const String& field) const +{ + return Contains(field); +} + +bool Namespace::GetOwnField(const String& field, Value *result) const +{ + return Get(field, result); +} + +Namespace::Iterator Namespace::Begin() +{ + ASSERT(OwnsLock()); + + return m_Data.begin(); +} + +Namespace::Iterator Namespace::End() +{ + ASSERT(OwnsLock()); + + return m_Data.end(); +} + +Namespace::Iterator icinga::begin(const Namespace::Ptr& x) +{ + return x->Begin(); +} + +Namespace::Iterator icinga::end(const Namespace::Ptr& x) +{ + return x->End(); +} + diff --git a/lib/base/namespace.hpp b/lib/base/namespace.hpp new file mode 100644 index 0000000..94f2055 --- /dev/null +++ b/lib/base/namespace.hpp @@ -0,0 +1,105 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#ifndef NAMESPACE_H +#define NAMESPACE_H + +#include "base/i2-base.hpp" +#include "base/object.hpp" +#include "base/shared-object.hpp" +#include "base/value.hpp" +#include "base/debuginfo.hpp" +#include <atomic> +#include <map> +#include <vector> +#include <memory> +#include <shared_mutex> + +namespace icinga +{ + +struct NamespaceValue +{ + Value Val; + bool Const; +}; + + +/** + * A namespace. + * + * ## External Locking + * + * Synchronization is handled internally, so almost all functions are safe for concurrent use without external locking. + * The only exception to this are functions returning an iterator. To use these, the caller has to acquire an ObjectLock + * on the namespace. The iterators only remain valid for as long as that ObjectLock is held. Note that this also + * includes range-based for loops. + * + * If consistency across multiple operations is required, an ObjectLock must also be acquired to prevent concurrent + * modifications. + * + * ## Internal Locking + * + * Two mutex objects are involved in locking a namespace: the recursive mutex inherited from the Object class that is + * acquired and released using the ObjectLock class and the m_DataMutex shared mutex contained directly in the + * Namespace class. The ObjectLock is used to synchronize multiple write operations against each other. The shared mutex + * is only used to ensure the consistency of the m_Data data structure. + * + * Read operations must acquire a shared lock on m_DataMutex. This prevents concurrent writes to that data structure + * but still allows concurrent reads. + * + * Write operations must first obtain an ObjectLock and then a shared lock on m_DataMutex. This order is important for + * preventing deadlocks. The ObjectLock prevents concurrent write operations while the shared lock prevents concurrent + * read operations. + * + * External read access to iterators is synchronized by the caller holding an ObjectLock. This ensures no concurrent + * write operations as these require the ObjectLock but still allows concurrent reads as m_DataMutex is not locked. + * + * @ingroup base + */ +class Namespace final : public Object +{ +public: + DECLARE_OBJECT(Namespace); + + typedef std::map<String, NamespaceValue>::iterator Iterator; + + typedef std::map<String, NamespaceValue>::value_type Pair; + + explicit Namespace(bool constValues = false); + + Value Get(const String& field) const; + bool Get(const String& field, Value *value) const; + void Set(const String& field, const Value& value, bool isConst = false, const DebugInfo& debugInfo = DebugInfo()); + bool Contains(const String& field) const; + void Remove(const String& field); + void Freeze(); + + Iterator Begin(); + Iterator End(); + + size_t GetLength() const; + + Value GetFieldByName(const String& field, bool sandboxed, const DebugInfo& debugInfo) const override; + void SetFieldByName(const String& field, const Value& value, bool overrideFrozen, const DebugInfo& debugInfo) override; + bool HasOwnField(const String& field) const override; + bool GetOwnField(const String& field, Value *result) const override; + + static Object::Ptr GetPrototype(); + +private: + std::shared_lock<std::shared_timed_mutex> ReadLockUnlessFrozen() const; + + std::map<String, NamespaceValue> m_Data; + mutable std::shared_timed_mutex m_DataMutex; + bool m_ConstValues; + std::atomic<bool> m_Frozen; +}; + +Namespace::Iterator begin(const Namespace::Ptr& x); +Namespace::Iterator end(const Namespace::Ptr& x); + +} + +extern template class std::map<icinga::String, std::shared_ptr<icinga::NamespaceValue> >; + +#endif /* NAMESPACE_H */ diff --git a/lib/base/netstring.cpp b/lib/base/netstring.cpp new file mode 100644 index 0000000..60f08c2 --- /dev/null +++ b/lib/base/netstring.cpp @@ -0,0 +1,334 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#include "base/netstring.hpp" +#include "base/debug.hpp" +#include "base/tlsstream.hpp" +#include <cstdint> +#include <memory> +#include <sstream> +#include <utility> +#include <boost/asio/buffer.hpp> +#include <boost/asio/read.hpp> +#include <boost/asio/spawn.hpp> +#include <boost/asio/write.hpp> + +using namespace icinga; + +/** + * Reads data from a stream in netstring format. + * + * @param stream The stream to read from. + * @param[out] str The String that has been read from the IOQueue. + * @returns true if a complete String was read from the IOQueue, false otherwise. + * @exception invalid_argument The input stream is invalid. + * @see https://github.com/PeterScott/netstring-c/blob/master/netstring.c + */ +StreamReadStatus NetString::ReadStringFromStream(const Stream::Ptr& stream, String *str, StreamReadContext& context, + bool may_wait, ssize_t maxMessageLength) +{ + if (context.Eof) + return StatusEof; + + if (context.MustRead) { + if (!context.FillFromStream(stream, may_wait)) { + context.Eof = true; + return StatusEof; + } + + context.MustRead = false; + } + + size_t header_length = 0; + + for (size_t i = 0; i < context.Size; i++) { + if (context.Buffer[i] == ':') { + header_length = i; + + /* make sure there's a header */ + if (header_length == 0) + BOOST_THROW_EXCEPTION(std::invalid_argument("Invalid NetString (no length specifier)")); + + break; + } else if (i > 16) + BOOST_THROW_EXCEPTION(std::invalid_argument("Invalid NetString (missing :)")); + } + + if (header_length == 0) { + context.MustRead = true; + return StatusNeedData; + } + + /* no leading zeros allowed */ + if (context.Buffer[0] == '0' && isdigit(context.Buffer[1])) + BOOST_THROW_EXCEPTION(std::invalid_argument("Invalid NetString (leading zero)")); + + size_t len, i; + + len = 0; + for (i = 0; i < header_length && isdigit(context.Buffer[i]); i++) { + /* length specifier must have at most 9 characters */ + if (i >= 9) + BOOST_THROW_EXCEPTION(std::invalid_argument("Length specifier must not exceed 9 characters")); + + len = len * 10 + (context.Buffer[i] - '0'); + } + + /* read the whole message */ + size_t data_length = len + 1; + + if (maxMessageLength >= 0 && data_length > (size_t)maxMessageLength) { + std::stringstream errorMessage; + errorMessage << "Max data length exceeded: " << (maxMessageLength / 1024) << " KB"; + + BOOST_THROW_EXCEPTION(std::invalid_argument(errorMessage.str())); + } + + char *data = context.Buffer + header_length + 1; + + if (context.Size < header_length + 1 + data_length) { + context.MustRead = true; + return StatusNeedData; + } + + if (data[len] != ',') + BOOST_THROW_EXCEPTION(std::invalid_argument("Invalid NetString (missing ,)")); + + *str = String(&data[0], &data[len]); + + context.DropData(header_length + 1 + len + 1); + + return StatusNewItem; +} + +/** + * Writes data into a stream using the netstring format and returns bytes written. + * + * @param stream The stream. + * @param str The String that is to be written. + * + * @return The amount of bytes written. + */ +size_t NetString::WriteStringToStream(const Stream::Ptr& stream, const String& str) +{ + std::ostringstream msgbuf; + WriteStringToStream(msgbuf, str); + + String msg = msgbuf.str(); + stream->Write(msg.CStr(), msg.GetLength()); + return msg.GetLength(); +} + +/** + * Reads data from a stream in netstring format. + * + * @param stream The stream to read from. + * @returns The String that has been read from the IOQueue. + * @exception invalid_argument The input stream is invalid. + * @see https://github.com/PeterScott/netstring-c/blob/master/netstring.c + */ +String NetString::ReadStringFromStream(const Shared<AsioTlsStream>::Ptr& stream, + ssize_t maxMessageLength) +{ + namespace asio = boost::asio; + + size_t len = 0; + bool leadingZero = false; + + for (uint_fast8_t readBytes = 0;; ++readBytes) { + char byte = 0; + + { + asio::mutable_buffer byteBuf (&byte, 1); + asio::read(*stream, byteBuf); + } + + if (isdigit(byte)) { + if (readBytes == 9) { + BOOST_THROW_EXCEPTION(std::invalid_argument("Length specifier must not exceed 9 characters")); + } + + if (leadingZero) { + BOOST_THROW_EXCEPTION(std::invalid_argument("Invalid NetString (leading zero)")); + } + + len = len * 10u + size_t(byte - '0'); + + if (!readBytes && byte == '0') { + leadingZero = true; + } + } else if (byte == ':') { + if (!readBytes) { + BOOST_THROW_EXCEPTION(std::invalid_argument("Invalid NetString (no length specifier)")); + } + + break; + } else { + BOOST_THROW_EXCEPTION(std::invalid_argument("Invalid NetString (missing :)")); + } + } + + if (maxMessageLength >= 0 && len > maxMessageLength) { + std::stringstream errorMessage; + errorMessage << "Max data length exceeded: " << (maxMessageLength / 1024) << " KB"; + + BOOST_THROW_EXCEPTION(std::invalid_argument(errorMessage.str())); + } + + String payload; + + if (len) { + payload.Append(len, 0); + + asio::mutable_buffer payloadBuf (&*payload.Begin(), payload.GetLength()); + asio::read(*stream, payloadBuf); + } + + char trailer = 0; + + { + asio::mutable_buffer trailerBuf (&trailer, 1); + asio::read(*stream, trailerBuf); + } + + if (trailer != ',') { + BOOST_THROW_EXCEPTION(std::invalid_argument("Invalid NetString (missing ,)")); + } + + return payload; +} + +/** + * Reads data from a stream in netstring format. + * + * @param stream The stream to read from. + * @returns The String that has been read from the IOQueue. + * @exception invalid_argument The input stream is invalid. + * @see https://github.com/PeterScott/netstring-c/blob/master/netstring.c + */ +String NetString::ReadStringFromStream(const Shared<AsioTlsStream>::Ptr& stream, + boost::asio::yield_context yc, ssize_t maxMessageLength) +{ + namespace asio = boost::asio; + + size_t len = 0; + bool leadingZero = false; + + for (uint_fast8_t readBytes = 0;; ++readBytes) { + char byte = 0; + + { + asio::mutable_buffer byteBuf (&byte, 1); + asio::async_read(*stream, byteBuf, yc); + } + + if (isdigit(byte)) { + if (readBytes == 9) { + BOOST_THROW_EXCEPTION(std::invalid_argument("Length specifier must not exceed 9 characters")); + } + + if (leadingZero) { + BOOST_THROW_EXCEPTION(std::invalid_argument("Invalid NetString (leading zero)")); + } + + len = len * 10u + size_t(byte - '0'); + + if (!readBytes && byte == '0') { + leadingZero = true; + } + } else if (byte == ':') { + if (!readBytes) { + BOOST_THROW_EXCEPTION(std::invalid_argument("Invalid NetString (no length specifier)")); + } + + break; + } else { + BOOST_THROW_EXCEPTION(std::invalid_argument("Invalid NetString (missing :)")); + } + } + + if (maxMessageLength >= 0 && len > maxMessageLength) { + std::stringstream errorMessage; + errorMessage << "Max data length exceeded: " << (maxMessageLength / 1024) << " KB"; + + BOOST_THROW_EXCEPTION(std::invalid_argument(errorMessage.str())); + } + + String payload; + + if (len) { + payload.Append(len, 0); + + asio::mutable_buffer payloadBuf (&*payload.Begin(), payload.GetLength()); + asio::async_read(*stream, payloadBuf, yc); + } + + char trailer = 0; + + { + asio::mutable_buffer trailerBuf (&trailer, 1); + asio::async_read(*stream, trailerBuf, yc); + } + + if (trailer != ',') { + BOOST_THROW_EXCEPTION(std::invalid_argument("Invalid NetString (missing ,)")); + } + + return payload; +} + +/** + * Writes data into a stream using the netstring format and returns bytes written. + * + * @param stream The stream. + * @param str The String that is to be written. + * + * @return The amount of bytes written. + */ +size_t NetString::WriteStringToStream(const Shared<AsioTlsStream>::Ptr& stream, const String& str) +{ + namespace asio = boost::asio; + + std::ostringstream msgbuf; + WriteStringToStream(msgbuf, str); + + String msg = msgbuf.str(); + asio::const_buffer msgBuf (msg.CStr(), msg.GetLength()); + + asio::write(*stream, msgBuf); + + return msg.GetLength(); +} + +/** + * Writes data into a stream using the netstring format and returns bytes written. + * + * @param stream The stream. + * @param str The String that is to be written. + * + * @return The amount of bytes written. + */ +size_t NetString::WriteStringToStream(const Shared<AsioTlsStream>::Ptr& stream, const String& str, boost::asio::yield_context yc) +{ + namespace asio = boost::asio; + + std::ostringstream msgbuf; + WriteStringToStream(msgbuf, str); + + String msg = msgbuf.str(); + asio::const_buffer msgBuf (msg.CStr(), msg.GetLength()); + + asio::async_write(*stream, msgBuf, yc); + + return msg.GetLength(); +} + +/** + * Writes data into a stream using the netstring format. + * + * @param stream The stream. + * @param str The String that is to be written. + */ +void NetString::WriteStringToStream(std::ostream& stream, const String& str) +{ + stream << str.GetLength() << ":" << str << ","; +} diff --git a/lib/base/netstring.hpp b/lib/base/netstring.hpp new file mode 100644 index 0000000..e5ec051 --- /dev/null +++ b/lib/base/netstring.hpp @@ -0,0 +1,43 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#ifndef NETSTRING_H +#define NETSTRING_H + +#include "base/i2-base.hpp" +#include "base/stream.hpp" +#include "base/tlsstream.hpp" +#include <memory> +#include <boost/asio/spawn.hpp> + +namespace icinga +{ + +class String; + +/** + * Helper functions for reading/writing messages in the netstring format. + * + * @see https://cr.yp.to/proto/netstrings.txt + * + * @ingroup base + */ +class NetString +{ +public: + static StreamReadStatus ReadStringFromStream(const Stream::Ptr& stream, String *message, StreamReadContext& context, + bool may_wait = false, ssize_t maxMessageLength = -1); + static String ReadStringFromStream(const Shared<AsioTlsStream>::Ptr& stream, ssize_t maxMessageLength = -1); + static String ReadStringFromStream(const Shared<AsioTlsStream>::Ptr& stream, + boost::asio::yield_context yc, ssize_t maxMessageLength = -1); + static size_t WriteStringToStream(const Stream::Ptr& stream, const String& message); + static size_t WriteStringToStream(const Shared<AsioTlsStream>::Ptr& stream, const String& message); + static size_t WriteStringToStream(const Shared<AsioTlsStream>::Ptr& stream, const String& message, boost::asio::yield_context yc); + static void WriteStringToStream(std::ostream& stream, const String& message); + +private: + NetString(); +}; + +} + +#endif /* NETSTRING_H */ diff --git a/lib/base/networkstream.cpp b/lib/base/networkstream.cpp new file mode 100644 index 0000000..57da507 --- /dev/null +++ b/lib/base/networkstream.cpp @@ -0,0 +1,81 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#include "base/networkstream.hpp" + +using namespace icinga; + +NetworkStream::NetworkStream(Socket::Ptr socket) + : m_Socket(std::move(socket)), m_Eof(false) +{ } + +void NetworkStream::Close() +{ + Stream::Close(); + + m_Socket->Close(); +} + +/** + * Reads data from the stream. + * + * @param buffer The buffer where data should be stored. May be nullptr if you're + * not actually interested in the data. + * @param count The number of bytes to read from the queue. + * @returns The number of bytes actually read. + */ +size_t NetworkStream::Read(void *buffer, size_t count, bool allow_partial) +{ + size_t rc; + + ASSERT(allow_partial); + + if (m_Eof) + BOOST_THROW_EXCEPTION(std::invalid_argument("Tried to read from closed socket.")); + + try { + rc = m_Socket->Read(buffer, count); + } catch (...) { + m_Eof = true; + + throw; + } + + if (rc == 0) + m_Eof = true; + + return rc; +} + +/** + * Writes data to the stream. + * + * @param buffer The data that is to be written. + * @param count The number of bytes to write. + * @returns The number of bytes written + */ +void NetworkStream::Write(const void *buffer, size_t count) +{ + size_t rc; + + if (m_Eof) + BOOST_THROW_EXCEPTION(std::invalid_argument("Tried to write to closed socket.")); + + try { + rc = m_Socket->Write(buffer, count); + } catch (...) { + m_Eof = true; + + throw; + } + + if (rc < count) { + m_Eof = true; + + BOOST_THROW_EXCEPTION(std::runtime_error("Short write for socket.")); + } +} + +bool NetworkStream::IsEof() const +{ + return m_Eof; +} diff --git a/lib/base/networkstream.hpp b/lib/base/networkstream.hpp new file mode 100644 index 0000000..453d7ad --- /dev/null +++ b/lib/base/networkstream.hpp @@ -0,0 +1,39 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#ifndef NETWORKSTREAM_H +#define NETWORKSTREAM_H + +#include "base/i2-base.hpp" +#include "base/stream.hpp" +#include "base/socket.hpp" + +namespace icinga +{ + +/** + * A network stream. DEPRECATED - Use Boost ASIO instead. + * + * @ingroup base + */ +class NetworkStream final : public Stream +{ +public: + DECLARE_PTR_TYPEDEFS(NetworkStream); + + NetworkStream(Socket::Ptr socket); + + size_t Read(void *buffer, size_t count, bool allow_partial = false) override; + void Write(const void *buffer, size_t count) override; + + void Close() override; + + bool IsEof() const override; + +private: + Socket::Ptr m_Socket; + bool m_Eof; +}; + +} + +#endif /* NETWORKSTREAM_H */ diff --git a/lib/base/number-script.cpp b/lib/base/number-script.cpp new file mode 100644 index 0000000..0dcaca5 --- /dev/null +++ b/lib/base/number-script.cpp @@ -0,0 +1,25 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#include "base/number.hpp" +#include "base/convert.hpp" +#include "base/function.hpp" +#include "base/functionwrapper.hpp" +#include "base/scriptframe.hpp" + +using namespace icinga; + +static String NumberToString() +{ + ScriptFrame *vframe = ScriptFrame::GetCurrentFrame(); + return vframe->Self; +} + +Object::Ptr Number::GetPrototype() +{ + static Dictionary::Ptr prototype = new Dictionary({ + { "to_string", new Function("Number#to_string", NumberToString, {}, true) } + }); + + return prototype; +} + diff --git a/lib/base/number.cpp b/lib/base/number.cpp new file mode 100644 index 0000000..a336519 --- /dev/null +++ b/lib/base/number.cpp @@ -0,0 +1,9 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#include "base/number.hpp" +#include "base/primitivetype.hpp" + +using namespace icinga; + +REGISTER_BUILTIN_TYPE(Number, Number::GetPrototype()); + diff --git a/lib/base/number.hpp b/lib/base/number.hpp new file mode 100644 index 0000000..dd5196f --- /dev/null +++ b/lib/base/number.hpp @@ -0,0 +1,27 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#ifndef NUMBER_H +#define NUMBER_H + +#include "base/i2-base.hpp" +#include "base/object.hpp" + +namespace icinga { + +class Value; + +/** + * Number class. + */ +class Number +{ +public: + static Object::Ptr GetPrototype(); + +private: + Number(); +}; + +} + +#endif /* NUMBER_H */ diff --git a/lib/base/object-packer.cpp b/lib/base/object-packer.cpp new file mode 100644 index 0000000..123ddad --- /dev/null +++ b/lib/base/object-packer.cpp @@ -0,0 +1,246 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#include "base/object-packer.hpp" +#include "base/debug.hpp" +#include "base/dictionary.hpp" +#include "base/array.hpp" +#include "base/objectlock.hpp" +#include <algorithm> +#include <climits> +#include <cstdint> +#include <string> +#include <utility> + +using namespace icinga; + +union EndiannessDetector +{ + EndiannessDetector() + { + i = 1; + } + + int i; + char buf[sizeof(int)]; +}; + +static const EndiannessDetector l_EndiannessDetector; + +// Assumption: The compiler will optimize (away) if/else statements using this. +#define MACHINE_LITTLE_ENDIAN (l_EndiannessDetector.buf[0]) + +static void PackAny(const Value& value, std::string& builder); + +/** + * std::swap() seems not to work + */ +static inline void SwapBytes(char& a, char& b) +{ + char c = a; + a = b; + b = c; +} + +#if CHAR_MIN != 0 +union CharU2SConverter +{ + CharU2SConverter() + { + s = 0; + } + + unsigned char u; + signed char s; +}; +#endif + +/** + * Avoid implementation-defined overflows during unsigned to signed casts + */ +static inline char UIntToByte(unsigned i) +{ +#if CHAR_MIN == 0 + return i; +#else + CharU2SConverter converter; + + converter.u = i; + return converter.s; +#endif +} + +/** + * Append the given int as big-endian 64-bit unsigned int + */ +static inline void PackUInt64BE(uint_least64_t i, std::string& builder) +{ + char buf[8] = { + UIntToByte(i >> 56u), + UIntToByte((i >> 48u) & 255u), + UIntToByte((i >> 40u) & 255u), + UIntToByte((i >> 32u) & 255u), + UIntToByte((i >> 24u) & 255u), + UIntToByte((i >> 16u) & 255u), + UIntToByte((i >> 8u) & 255u), + UIntToByte(i & 255u) + }; + + builder.append((char*)buf, 8); +} + +union Double2BytesConverter +{ + Double2BytesConverter() + { + buf[0] = 0; + buf[1] = 0; + buf[2] = 0; + buf[3] = 0; + buf[4] = 0; + buf[5] = 0; + buf[6] = 0; + buf[7] = 0; + } + + double f; + char buf[8]; +}; + +/** + * Append the given double as big-endian IEEE 754 binary64 + */ +static inline void PackFloat64BE(double f, std::string& builder) +{ + Double2BytesConverter converter; + + converter.f = f; + + if (MACHINE_LITTLE_ENDIAN) { + SwapBytes(converter.buf[0], converter.buf[7]); + SwapBytes(converter.buf[1], converter.buf[6]); + SwapBytes(converter.buf[2], converter.buf[5]); + SwapBytes(converter.buf[3], converter.buf[4]); + } + + builder.append((char*)converter.buf, 8); +} + +/** + * Append the given string's length (BE uint64) and the string itself + */ +static inline void PackString(const String& string, std::string& builder) +{ + PackUInt64BE(string.GetLength(), builder); + builder += string.GetData(); +} + +/** + * Append the given array + */ +static inline void PackArray(const Array::Ptr& arr, std::string& builder) +{ + ObjectLock olock(arr); + + builder += '\5'; + PackUInt64BE(arr->GetLength(), builder); + + for (const Value& value : arr) { + PackAny(value, builder); + } +} + +/** + * Append the given dictionary + */ +static inline void PackDictionary(const Dictionary::Ptr& dict, std::string& builder) +{ + ObjectLock olock(dict); + + builder += '\6'; + PackUInt64BE(dict->GetLength(), builder); + + for (const Dictionary::Pair& kv : dict) { + PackString(kv.first, builder); + PackAny(kv.second, builder); + } +} + +/** + * Append any JSON-encodable value + */ +static void PackAny(const Value& value, std::string& builder) +{ + switch (value.GetType()) { + case ValueString: + builder += '\4'; + PackString(value.Get<String>(), builder); + break; + + case ValueNumber: + builder += '\3'; + PackFloat64BE(value.Get<double>(), builder); + break; + + case ValueBoolean: + builder += (value.ToBool() ? '\2' : '\1'); + break; + + case ValueEmpty: + builder += '\0'; + break; + + case ValueObject: + { + const Object::Ptr& obj = value.Get<Object::Ptr>(); + + Dictionary::Ptr dict = dynamic_pointer_cast<Dictionary>(obj); + if (dict) { + PackDictionary(dict, builder); + break; + } + + Array::Ptr arr = dynamic_pointer_cast<Array>(obj); + if (arr) { + PackArray(arr, builder); + break; + } + } + + builder += '\0'; + break; + + default: + VERIFY(!"Invalid variant type."); + } +} + +/** + * Pack any JSON-encodable value to a BSON-similar structure suitable for consistent hashing + * + * Spec: + * null: 0x00 + * false: 0x01 + * true: 0x02 + * number: 0x03 (ieee754_binary64_bigendian)payload + * string: 0x04 (uint64_bigendian)payload.length (char[])payload + * array: 0x05 (uint64_bigendian)payload.length (any[])payload + * object: 0x06 (uint64_bigendian)payload.length (keyvalue[])payload.sort() + * + * any: null|false|true|number|string|array|object + * keyvalue: (uint64_bigendian)key.length (char[])key (any)value + * + * Assumptions: + * - double is IEEE 754 binary64 + * - all int types (signed and unsigned) and all float types share the same endianness + * - char is exactly 8 bits wide and one char is exactly one byte affected by the machine endianness + * - all input strings, arrays and dictionaries are at most 2^64-1 long + * + * If not, this function will silently produce invalid results. + */ +String icinga::PackObject(const Value& value) +{ + std::string builder; + PackAny(value, builder); + + return std::move(builder); +} diff --git a/lib/base/object-packer.hpp b/lib/base/object-packer.hpp new file mode 100644 index 0000000..00f7b99 --- /dev/null +++ b/lib/base/object-packer.hpp @@ -0,0 +1,18 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#ifndef OBJECT_PACKER +#define OBJECT_PACKER + +#include "base/i2-base.hpp" + +namespace icinga +{ + +class String; +class Value; + +String PackObject(const Value& value); + +} + +#endif /* OBJECT_PACKER */ diff --git a/lib/base/object-script.cpp b/lib/base/object-script.cpp new file mode 100644 index 0000000..fff7df0 --- /dev/null +++ b/lib/base/object-script.cpp @@ -0,0 +1,45 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#include "base/object.hpp" +#include "base/dictionary.hpp" +#include "base/function.hpp" +#include "base/functionwrapper.hpp" +#include "base/scriptframe.hpp" + +using namespace icinga; + +static String ObjectToString() +{ + ScriptFrame *vframe = ScriptFrame::GetCurrentFrame(); + Object::Ptr self = static_cast<Object::Ptr>(vframe->Self); + REQUIRE_NOT_NULL(self); + return self->ToString(); +} + +static void ObjectNotifyAttribute(const String& attribute) +{ + ScriptFrame *vframe = ScriptFrame::GetCurrentFrame(); + Object::Ptr self = static_cast<Object::Ptr>(vframe->Self); + REQUIRE_NOT_NULL(self); + self->NotifyField(self->GetReflectionType()->GetFieldId(attribute)); +} + +static Object::Ptr ObjectClone() +{ + ScriptFrame *vframe = ScriptFrame::GetCurrentFrame(); + Object::Ptr self = static_cast<Object::Ptr>(vframe->Self); + REQUIRE_NOT_NULL(self); + return self->Clone(); +} + +Object::Ptr Object::GetPrototype() +{ + static Dictionary::Ptr prototype = new Dictionary({ + { "to_string", new Function("Object#to_string", ObjectToString, {}, true) }, + { "notify_attribute", new Function("Object#notify_attribute", ObjectNotifyAttribute, { "attribute" }, false) }, + { "clone", new Function("Object#clone", ObjectClone, {}, true) } + }); + + return prototype; +} + diff --git a/lib/base/object.cpp b/lib/base/object.cpp new file mode 100644 index 0000000..92a43b9 --- /dev/null +++ b/lib/base/object.cpp @@ -0,0 +1,275 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#include "base/object.hpp" +#include "base/value.hpp" +#include "base/dictionary.hpp" +#include "base/primitivetype.hpp" +#include "base/utility.hpp" +#include "base/timer.hpp" +#include "base/logger.hpp" +#include "base/exception.hpp" +#include <boost/lexical_cast.hpp> +#include <boost/thread/recursive_mutex.hpp> +#include <thread> + +using namespace icinga; + +DEFINE_TYPE_INSTANCE(Object); + +#ifdef I2_LEAK_DEBUG +static std::mutex l_ObjectCountLock; +static std::map<String, int> l_ObjectCounts; +static Timer::Ptr l_ObjectCountTimer; +#endif /* I2_LEAK_DEBUG */ + +/** + * Constructor for the Object class. + */ +Object::Object() +{ + m_References.store(0); + +#ifdef I2_DEBUG + m_LockOwner.store(decltype(m_LockOwner.load())()); +#endif /* I2_DEBUG */ +} + +/** + * Destructor for the Object class. + */ +Object::~Object() +{ +} + +/** + * Returns a string representation for the object. + */ +String Object::ToString() const +{ + return "Object of type '" + GetReflectionType()->GetName() + "'"; +} + +#ifdef I2_DEBUG +/** + * Checks if the calling thread owns the lock on this object. + * + * @returns True if the calling thread owns the lock, false otherwise. + */ +bool Object::OwnsLock() const +{ + return m_LockOwner.load() == std::this_thread::get_id(); +} +#endif /* I2_DEBUG */ + +void Object::SetField(int id, const Value&, bool, const Value&) +{ + if (id == 0) + BOOST_THROW_EXCEPTION(std::runtime_error("Type field cannot be set.")); + else + BOOST_THROW_EXCEPTION(std::runtime_error("Invalid field ID.")); +} + +Value Object::GetField(int id) const +{ + if (id == 0) + return GetReflectionType()->GetName(); + else + BOOST_THROW_EXCEPTION(std::runtime_error("Invalid field ID.")); +} + +bool Object::HasOwnField(const String& field) const +{ + Type::Ptr type = GetReflectionType(); + + if (!type) + return false; + + return type->GetFieldId(field) != -1; +} + +bool Object::GetOwnField(const String& field, Value *result) const +{ + Type::Ptr type = GetReflectionType(); + + if (!type) + return false; + + int tid = type->GetFieldId(field); + + if (tid == -1) + return false; + + *result = GetField(tid); + return true; +} + +Value Object::GetFieldByName(const String& field, bool sandboxed, const DebugInfo& debugInfo) const +{ + Type::Ptr type = GetReflectionType(); + + if (!type) + return Empty; + + int fid = type->GetFieldId(field); + + if (fid == -1) + return GetPrototypeField(const_cast<Object *>(this), field, true, debugInfo); + + if (sandboxed) { + Field fieldInfo = type->GetFieldInfo(fid); + + if (fieldInfo.Attributes & FANoUserView) + BOOST_THROW_EXCEPTION(ScriptError("Accessing the field '" + field + "' for type '" + type->GetName() + "' is not allowed in sandbox mode.", debugInfo)); + } + + return GetField(fid); +} + +void Object::SetFieldByName(const String& field, const Value& value, bool overrideFrozen, const DebugInfo& debugInfo) +{ + Type::Ptr type = GetReflectionType(); + + if (!type) + BOOST_THROW_EXCEPTION(ScriptError("Cannot set field on object.", debugInfo)); + + int fid = type->GetFieldId(field); + + if (fid == -1) + BOOST_THROW_EXCEPTION(ScriptError("Attribute '" + field + "' does not exist.", debugInfo)); + + try { + SetField(fid, value); + } catch (const boost::bad_lexical_cast&) { + Field fieldInfo = type->GetFieldInfo(fid); + Type::Ptr ftype = Type::GetByName(fieldInfo.TypeName); + BOOST_THROW_EXCEPTION(ScriptError("Attribute '" + field + "' cannot be set to value of type '" + value.GetTypeName() + "', expected '" + ftype->GetName() + "'", debugInfo)); + } catch (const std::bad_cast&) { + Field fieldInfo = type->GetFieldInfo(fid); + Type::Ptr ftype = Type::GetByName(fieldInfo.TypeName); + BOOST_THROW_EXCEPTION(ScriptError("Attribute '" + field + "' cannot be set to value of type '" + value.GetTypeName() + "', expected '" + ftype->GetName() + "'", debugInfo)); + } +} + +void Object::Validate(int types, const ValidationUtils& utils) +{ + /* Nothing to do here. */ +} + +void Object::ValidateField(int id, const Lazy<Value>& lvalue, const ValidationUtils& utils) +{ + /* Nothing to do here. */ +} + +void Object::NotifyField(int id, const Value& cookie) +{ + BOOST_THROW_EXCEPTION(std::runtime_error("Invalid field ID.")); +} + +Object::Ptr Object::NavigateField(int id) const +{ + BOOST_THROW_EXCEPTION(std::runtime_error("Invalid field ID.")); +} + +Object::Ptr Object::Clone() const +{ + BOOST_THROW_EXCEPTION(std::runtime_error("Object cannot be cloned.")); +} + +Type::Ptr Object::GetReflectionType() const +{ + return Object::TypeInstance; +} + +Value icinga::GetPrototypeField(const Value& context, const String& field, bool not_found_error, const DebugInfo& debugInfo) +{ + Type::Ptr ctype = context.GetReflectionType(); + Type::Ptr type = ctype; + + do { + Object::Ptr object = type->GetPrototype(); + + if (object && object->HasOwnField(field)) + return object->GetFieldByName(field, false, debugInfo); + + type = type->GetBaseType(); + } while (type); + + if (not_found_error) + BOOST_THROW_EXCEPTION(ScriptError("Invalid field access (for value of type '" + ctype->GetName() + "'): '" + field + "'", debugInfo)); + else + return Empty; +} + +#ifdef I2_LEAK_DEBUG +void icinga::TypeAddObject(Object *object) +{ + std::unique_lock<std::mutex> lock(l_ObjectCountLock); + String typeName = Utility::GetTypeName(typeid(*object)); + l_ObjectCounts[typeName]++; +} + +void icinga::TypeRemoveObject(Object *object) +{ + std::unique_lock<std::mutex> lock(l_ObjectCountLock); + String typeName = Utility::GetTypeName(typeid(*object)); + l_ObjectCounts[typeName]--; +} + +static void TypeInfoTimerHandler() +{ + std::unique_lock<std::mutex> lock(l_ObjectCountLock); + + typedef std::map<String, int>::value_type kv_pair; + for (kv_pair& kv : l_ObjectCounts) { + if (kv.second == 0) + continue; + + Log(LogInformation, "TypeInfo") + << kv.second << " " << kv.first << " objects"; + + kv.second = 0; + } +} + +INITIALIZE_ONCE([]() { + l_ObjectCountTimer = Timer::Create(); + l_ObjectCountTimer->SetInterval(10); + l_ObjectCountTimer->OnTimerExpired.connect([](const Timer * const&) { TypeInfoTimerHandler(); }); + l_ObjectCountTimer->Start(); +}); +#endif /* I2_LEAK_DEBUG */ + +void icinga::intrusive_ptr_add_ref(Object *object) +{ +#ifdef I2_LEAK_DEBUG + if (object->m_References.fetch_add(1) == 0u) + TypeAddObject(object); +#else /* I2_LEAK_DEBUG */ + object->m_References.fetch_add(1); +#endif /* I2_LEAK_DEBUG */ +} + +void icinga::intrusive_ptr_release(Object *object) +{ + auto previous (object->m_References.fetch_sub(1)); + + if (previous == 1u) { +#ifdef I2_LEAK_DEBUG + TypeRemoveObject(object); +#endif /* I2_LEAK_DEBUG */ + + delete object; + } +} + +void icinga::DefaultObjectFactoryCheckArgs(const std::vector<Value>& args) +{ + if (!args.empty()) + BOOST_THROW_EXCEPTION(std::invalid_argument("Constructor does not take any arguments.")); +} + +void icinga::RequireNotNullInternal(const intrusive_ptr<Object>& object, const char *description) +{ + if (!object) + BOOST_THROW_EXCEPTION(std::invalid_argument("Pointer must not be null: " + String(description))); +} diff --git a/lib/base/object.hpp b/lib/base/object.hpp new file mode 100644 index 0000000..5a90cfa --- /dev/null +++ b/lib/base/object.hpp @@ -0,0 +1,225 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#ifndef OBJECT_H +#define OBJECT_H + +#include "base/i2-base.hpp" +#include "base/debug.hpp" +#include <boost/smart_ptr/intrusive_ptr.hpp> +#include <atomic> +#include <cstddef> +#include <cstdint> +#include <mutex> +#include <thread> +#include <vector> + +using boost::intrusive_ptr; +using boost::dynamic_pointer_cast; +using boost::static_pointer_cast; + +namespace icinga +{ + +class Value; +class Object; +class Type; +class String; +struct DebugInfo; +class ValidationUtils; + +extern Value Empty; + +#define DECLARE_PTR_TYPEDEFS(klass) \ + typedef intrusive_ptr<klass> Ptr + +#define IMPL_TYPE_LOOKUP_SUPER() \ + +#define IMPL_TYPE_LOOKUP() \ + static intrusive_ptr<Type> TypeInstance; \ + virtual intrusive_ptr<Type> GetReflectionType() const override \ + { \ + return TypeInstance; \ + } + +#define DECLARE_OBJECT(klass) \ + DECLARE_PTR_TYPEDEFS(klass); \ + IMPL_TYPE_LOOKUP(); + +#define REQUIRE_NOT_NULL(ptr) RequireNotNullInternal(ptr, #ptr) + +void RequireNotNullInternal(const intrusive_ptr<Object>& object, const char *description); + +void DefaultObjectFactoryCheckArgs(const std::vector<Value>& args); + +template<typename T> +intrusive_ptr<Object> DefaultObjectFactory(const std::vector<Value>& args) +{ + DefaultObjectFactoryCheckArgs(args); + + return new T(); +} + +template<typename T> +intrusive_ptr<Object> DefaultObjectFactoryVA(const std::vector<Value>& args) +{ + return new T(args); +} + +typedef intrusive_ptr<Object> (*ObjectFactory)(const std::vector<Value>&); + +template<typename T, bool VA> +struct TypeHelper +{ +}; + +template<typename T> +struct TypeHelper<T, false> +{ + static ObjectFactory GetFactory() + { + return DefaultObjectFactory<T>; + } +}; + +template<typename T> +struct TypeHelper<T, true> +{ + static ObjectFactory GetFactory() + { + return DefaultObjectFactoryVA<T>; + } +}; + +template<typename T> +struct Lazy +{ + using Accessor = std::function<T ()>; + + explicit Lazy(T value) + : m_Cached(true), m_Value(value) + { } + + explicit Lazy(Accessor accessor) + : m_Accessor(accessor) + { } + + template<typename U> + explicit Lazy(const Lazy<U>& other) + { + if (other.m_Cached) { + m_Accessor = Accessor(); + m_Value = static_cast<T>(other.m_Value); + m_Cached = true; + } else { + auto accessor = other.m_Accessor; + m_Accessor = [accessor]() { return static_cast<T>(accessor()); }; + m_Cached = false; + } + } + + template<typename U> + operator Lazy<U>() const + { + if (m_Cached) + return Lazy<U>(static_cast<U>(m_Value)); + else { + Accessor accessor = m_Accessor; + return Lazy<U>(static_cast<typename Lazy<U>::Accessor>([accessor]() { return static_cast<U>(accessor()); })); + } + } + + const T& operator()() const + { + if (!m_Cached) { + m_Value = m_Accessor(); + m_Cached = true; + } + + return m_Value; + } + +private: + Accessor m_Accessor; + mutable bool m_Cached{false}; + mutable T m_Value; + + template<typename U> + friend struct Lazy; +}; + +/** + * Base class for all heap-allocated objects. At least one of its methods + * has to be virtual for RTTI to work. + * + * @ingroup base + */ +class Object +{ +public: + DECLARE_PTR_TYPEDEFS(Object); + + Object(); + virtual ~Object(); + + virtual String ToString() const; + + virtual intrusive_ptr<Type> GetReflectionType() const; + + virtual void Validate(int types, const ValidationUtils& utils); + + virtual void SetField(int id, const Value& value, bool suppress_events = false, const Value& cookie = Empty); + virtual Value GetField(int id) const; + virtual Value GetFieldByName(const String& field, bool sandboxed, const DebugInfo& debugInfo) const; + virtual void SetFieldByName(const String& field, const Value& value, bool overrideFrozen, const DebugInfo& debugInfo); + virtual bool HasOwnField(const String& field) const; + virtual bool GetOwnField(const String& field, Value *result) const; + virtual void ValidateField(int id, const Lazy<Value>& lvalue, const ValidationUtils& utils); + virtual void NotifyField(int id, const Value& cookie = Empty); + virtual Object::Ptr NavigateField(int id) const; + +#ifdef I2_DEBUG + bool OwnsLock() const; +#endif /* I2_DEBUG */ + + static Object::Ptr GetPrototype(); + + virtual Object::Ptr Clone() const; + + static intrusive_ptr<Type> TypeInstance; + +private: + Object(const Object& other) = delete; + Object& operator=(const Object& rhs) = delete; + + std::atomic<uint_fast64_t> m_References; + mutable std::recursive_mutex m_Mutex; + +#ifdef I2_DEBUG + mutable std::atomic<std::thread::id> m_LockOwner; + mutable size_t m_LockCount = 0; +#endif /* I2_DEBUG */ + + friend struct ObjectLock; + + friend void intrusive_ptr_add_ref(Object *object); + friend void intrusive_ptr_release(Object *object); +}; + +Value GetPrototypeField(const Value& context, const String& field, bool not_found_error, const DebugInfo& debugInfo); + +void TypeAddObject(Object *object); +void TypeRemoveObject(Object *object); + +void intrusive_ptr_add_ref(Object *object); +void intrusive_ptr_release(Object *object); + +template<typename T> +class ObjectImpl +{ +}; + +} + +#endif /* OBJECT_H */ + +#include "base/type.hpp" diff --git a/lib/base/objectlock.cpp b/lib/base/objectlock.cpp new file mode 100644 index 0000000..fc0c7c6 --- /dev/null +++ b/lib/base/objectlock.cpp @@ -0,0 +1,55 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#include "base/objectlock.hpp" +#include <thread> + +using namespace icinga; + +#define I2MUTEX_UNLOCKED 0 +#define I2MUTEX_LOCKED 1 + +ObjectLock::~ObjectLock() +{ + Unlock(); +} + +ObjectLock::ObjectLock(const Object::Ptr& object) + : ObjectLock(object.get()) +{ +} + +ObjectLock::ObjectLock(const Object *object) + : m_Object(object), m_Locked(false) +{ + if (m_Object) + Lock(); +} + +void ObjectLock::Lock() +{ + ASSERT(!m_Locked && m_Object); + + m_Object->m_Mutex.lock(); + + m_Locked = true; + +#ifdef I2_DEBUG + if (++m_Object->m_LockCount == 1u) { + m_Object->m_LockOwner.store(std::this_thread::get_id()); + } +#endif /* I2_DEBUG */ +} + +void ObjectLock::Unlock() +{ +#ifdef I2_DEBUG + if (m_Locked && !--m_Object->m_LockCount) { + m_Object->m_LockOwner.store(decltype(m_Object->m_LockOwner.load())()); + } +#endif /* I2_DEBUG */ + + if (m_Locked) { + m_Object->m_Mutex.unlock(); + m_Locked = false; + } +} diff --git a/lib/base/objectlock.hpp b/lib/base/objectlock.hpp new file mode 100644 index 0000000..8e98641 --- /dev/null +++ b/lib/base/objectlock.hpp @@ -0,0 +1,35 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#ifndef OBJECTLOCK_H +#define OBJECTLOCK_H + +#include "base/object.hpp" + +namespace icinga +{ + +/** + * A scoped lock for Objects. + */ +struct ObjectLock +{ +public: + ObjectLock(const Object::Ptr& object); + ObjectLock(const Object *object); + + ObjectLock(const ObjectLock&) = delete; + ObjectLock& operator=(const ObjectLock&) = delete; + + ~ObjectLock(); + + void Lock(); + void Unlock(); + +private: + const Object *m_Object{nullptr}; + bool m_Locked{false}; +}; + +} + +#endif /* OBJECTLOCK_H */ diff --git a/lib/base/objecttype.cpp b/lib/base/objecttype.cpp new file mode 100644 index 0000000..b871555 --- /dev/null +++ b/lib/base/objecttype.cpp @@ -0,0 +1,57 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#include "base/objecttype.hpp" +#include "base/initialize.hpp" +#include <boost/throw_exception.hpp> + +using namespace icinga; + +/* Ensure that the priority is lower than the basic namespace initialization in scriptframe.cpp. */ +INITIALIZE_ONCE_WITH_PRIORITY([]() { + Type::Ptr type = new ObjectType(); + type->SetPrototype(Object::GetPrototype()); + Type::Register(type); + Object::TypeInstance = type; +}, InitializePriority::RegisterObjectType); + +String ObjectType::GetName() const +{ + return "Object"; +} + +Type::Ptr ObjectType::GetBaseType() const +{ + return nullptr; +} + +int ObjectType::GetAttributes() const +{ + return 0; +} + +int ObjectType::GetFieldId(const String& name) const +{ + if (name == "type") + return 0; + else + return -1; +} + +Field ObjectType::GetFieldInfo(int id) const +{ + if (id == 0) + return {1, "String", "type", nullptr, nullptr, 0, 0}; + else + BOOST_THROW_EXCEPTION(std::runtime_error("Invalid field ID.")); +} + +int ObjectType::GetFieldCount() const +{ + return 1; +} + +ObjectFactory ObjectType::GetFactory() const +{ + return DefaultObjectFactory<Object>; +} + diff --git a/lib/base/objecttype.hpp b/lib/base/objecttype.hpp new file mode 100644 index 0000000..0db715e --- /dev/null +++ b/lib/base/objecttype.hpp @@ -0,0 +1,29 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#ifndef OBJECTTYPE_H +#define OBJECTTYPE_H + +#include "base/i2-base.hpp" +#include "base/type.hpp" +#include "base/initialize.hpp" + +namespace icinga +{ + +class ObjectType final : public Type +{ +public: + String GetName() const override; + Type::Ptr GetBaseType() const override; + int GetAttributes() const override; + int GetFieldId(const String& name) const override; + Field GetFieldInfo(int id) const override; + int GetFieldCount() const override; + +protected: + ObjectFactory GetFactory() const override; +}; + +} + +#endif /* OBJECTTYPE_H */ diff --git a/lib/base/perfdatavalue.cpp b/lib/base/perfdatavalue.cpp new file mode 100644 index 0000000..60a39e4 --- /dev/null +++ b/lib/base/perfdatavalue.cpp @@ -0,0 +1,395 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#include "base/perfdatavalue.hpp" +#include "base/perfdatavalue-ti.cpp" +#include "base/convert.hpp" +#include "base/exception.hpp" +#include "base/logger.hpp" +#include "base/function.hpp" +#include <boost/algorithm/string.hpp> +#include <cmath> +#include <stdexcept> +#include <string> +#include <unordered_map> +#include <utility> + +using namespace icinga; + +REGISTER_TYPE(PerfdataValue); +REGISTER_FUNCTION(System, parse_performance_data, PerfdataValue::Parse, "perfdata"); + +struct UoM +{ + double Factor; + const char* Out; +}; + +typedef std::unordered_map<std::string /* in */, UoM> UoMs; +typedef std::unordered_multimap<std::string /* in */, UoM> DupUoMs; + +static const UoMs l_CsUoMs (([]() -> UoMs { + DupUoMs uoms ({ + // Misc: + { "", { 1, "" } }, + { "%", { 1, "percent" } }, + { "c", { 1, "" } }, + { "C", { 1, "degrees-celsius" } } + }); + + { + // Data (rate): + + struct { const char* Char; int Power; } prefixes[] = { + { "k", 1 }, { "K", 1 }, + { "m", 2 }, { "M", 2 }, + { "g", 3 }, { "G", 3 }, + { "t", 4 }, { "T", 4 }, + { "p", 5 }, { "P", 5 }, + { "e", 6 }, { "E", 6 }, + { "z", 7 }, { "Z", 7 }, + { "y", 8 }, { "Y", 8 } + }; + + struct { const char* Char; double Factor; } siIecs[] = { + { "", 1000 }, + { "i", 1024 }, { "I", 1024 } + }; + + struct { const char *In, *Out; } bases[] = { + { "b", "bits" }, + { "B", "bytes" } + }; + + for (auto base : bases) { + uoms.emplace(base.In, UoM{1, base.Out}); + } + + for (auto prefix : prefixes) { + for (auto siIec : siIecs) { + auto factor (pow(siIec.Factor, prefix.Power)); + + for (auto base : bases) { + uoms.emplace( + std::string(prefix.Char) + siIec.Char + base.In, + UoM{factor, base.Out} + ); + } + } + } + } + + { + // Energy: + + struct { const char* Char; int Power; } prefixes[] = { + { "n", -3 }, { "N", -3 }, + { "u", -2 }, { "U", -2 }, + { "m", -1 }, + { "", 0 }, + { "k", 1 }, { "K", 1 }, + { "M", 2 }, + { "g", 3 }, { "G", 3 }, + { "t", 4 }, { "T", 4 }, + { "p", 5 }, { "P", 5 }, + { "e", 6 }, { "E", 6 }, + { "z", 7 }, { "Z", 7 }, + { "y", 8 }, { "Y", 8 } + }; + + { + struct { const char* Ins[2]; const char* Out; } bases[] = { + { { "a", "A" }, "amperes" }, + { { "o", "O" }, "ohms" }, + { { "v", "V" }, "volts" }, + { { "w", "W" }, "watts" } + }; + + for (auto prefix : prefixes) { + auto factor (pow(1000.0, prefix.Power)); + + for (auto base : bases) { + for (auto b : base.Ins) { + uoms.emplace(std::string(prefix.Char) + b, UoM{factor, base.Out}); + } + } + } + } + + struct { const char* Char; double Factor; } suffixes[] = { + { "s", 1 }, { "S", 1 }, + { "m", 60 }, { "M", 60 }, + { "h", 60 * 60 }, { "H", 60 * 60 } + }; + + struct { const char* Ins[2]; double Factor; const char* Out; } bases[] = { + { { "a", "A" }, 1, "ampere-seconds" }, + { { "w", "W" }, 60 * 60, "watt-hours" } + }; + + for (auto prefix : prefixes) { + auto factor (pow(1000.0, prefix.Power)); + + for (auto suffix : suffixes) { + auto timeFactor (factor * suffix.Factor); + + for (auto& base : bases) { + auto baseFactor (timeFactor / base.Factor); + + for (auto b : base.Ins) { + uoms.emplace( + std::string(prefix.Char) + b + suffix.Char, + UoM{baseFactor, base.Out} + ); + } + } + } + } + } + + UoMs uniqUoms; + + for (auto& uom : uoms) { + if (!uniqUoms.emplace(uom).second) { + throw std::logic_error("Duplicate case-sensitive UoM detected: " + uom.first); + } + } + + return uniqUoms; +})()); + +static const UoMs l_CiUoMs (([]() -> UoMs { + DupUoMs uoms ({ + // Time: + { "ns", { 1.0 / 1000 / 1000 / 1000, "seconds" } }, + { "us", { 1.0 / 1000 / 1000, "seconds" } }, + { "ms", { 1.0 / 1000, "seconds" } }, + { "s", { 1, "seconds" } }, + { "m", { 60, "seconds" } }, + { "h", { 60 * 60, "seconds" } }, + { "d", { 60 * 60 * 24, "seconds" } }, + + // Mass: + { "ng", { 1.0 / 1000 / 1000 / 1000, "grams" } }, + { "ug", { 1.0 / 1000 / 1000, "grams" } }, + { "mg", { 1.0 / 1000, "grams" } }, + { "g", { 1, "grams" } }, + { "kg", { 1000, "grams" } }, + { "t", { 1000 * 1000, "grams" } }, + + // Volume: + { "ml", { 1.0 / 1000, "liters" } }, + { "l", { 1, "liters" } }, + { "hl", { 100, "liters" } }, + + // Misc: + { "packets", { 1, "packets" } }, + { "lm", { 1, "lumens" } }, + { "dbm", { 1, "decibel-milliwatts" } }, + { "f", { 1, "degrees-fahrenheit" } }, + { "k", { 1, "degrees-kelvin" } } + }); + + UoMs uniqUoms; + + for (auto& uom : uoms) { + if (!uniqUoms.emplace(uom).second) { + throw std::logic_error("Duplicate case-insensitive UoM detected: " + uom.first); + } + } + + for (auto& uom : l_CsUoMs) { + auto input (uom.first); + boost::algorithm::to_lower(input); + + auto pos (uoms.find(input)); + + if (pos != uoms.end()) { + throw std::logic_error("Duplicate case-sensitive/case-insensitive UoM detected: " + pos->first); + } + } + + return uniqUoms; +})()); + +PerfdataValue::PerfdataValue(const String& label, double value, bool counter, + const String& unit, const Value& warn, const Value& crit, const Value& min, + const Value& max) +{ + SetLabel(label, true); + SetValue(value, true); + SetCounter(counter, true); + SetUnit(unit, true); + SetWarn(warn, true); + SetCrit(crit, true); + SetMin(min, true); + SetMax(max, true); +} + +PerfdataValue::Ptr PerfdataValue::Parse(const String& perfdata) +{ + size_t eqp = perfdata.FindLastOf('='); + + if (eqp == String::NPos) + BOOST_THROW_EXCEPTION(std::invalid_argument("Invalid performance data value: " + perfdata)); + + String label = perfdata.SubStr(0, eqp); + + if (label.GetLength() > 2 && label[0] == '\'' && label[label.GetLength() - 1] == '\'') + label = label.SubStr(1, label.GetLength() - 2); + + size_t spq = perfdata.FindFirstOf(' ', eqp); + + if (spq == String::NPos) + spq = perfdata.GetLength(); + + String valueStr = perfdata.SubStr(eqp + 1, spq - eqp - 1); + std::vector<String> tokens = valueStr.Split(";"); + + if (valueStr.FindFirstOf(',') != String::NPos || tokens.empty()) { + BOOST_THROW_EXCEPTION(std::invalid_argument("Invalid performance data value: " + perfdata)); + } + + // Find the position where to split value and unit. Possible values of tokens[0] include: + // "1000", "1.0", "1.", "-.1", "+1", "1e10", "1GB", "1e10GB", "1e10EB", "1E10EB", "1.5GB", "1.GB", "+1.E-1EW" + // Consider everything up to and including the last digit or decimal point as part of the value. + size_t pos = tokens[0].FindLastOf("0123456789."); + if (pos != String::NPos) { + pos++; + } + + double value = Convert::ToDouble(tokens[0].SubStr(0, pos)); + + bool counter = false; + String unit; + Value warn, crit, min, max; + + if (pos != String::NPos) + unit = tokens[0].SubStr(pos, String::NPos); + + double base; + + { + auto uom (l_CsUoMs.find(unit.GetData())); + + if (uom == l_CsUoMs.end()) { + auto ciUnit (unit.ToLower()); + auto uom (l_CiUoMs.find(ciUnit.GetData())); + + if (uom == l_CiUoMs.end()) { + Log(LogDebug, "PerfdataValue") + << "Invalid performance data unit: " << unit; + + unit = ""; + base = 1.0; + } else { + unit = uom->second.Out; + base = uom->second.Factor; + } + } else { + unit = uom->second.Out; + base = uom->second.Factor; + } + } + + if (unit == "c") { + counter = true; + } + + warn = ParseWarnCritMinMaxToken(tokens, 1, "warning"); + crit = ParseWarnCritMinMaxToken(tokens, 2, "critical"); + min = ParseWarnCritMinMaxToken(tokens, 3, "minimum"); + max = ParseWarnCritMinMaxToken(tokens, 4, "maximum"); + + value = value * base; + + if (!warn.IsEmpty()) + warn = warn * base; + + if (!crit.IsEmpty()) + crit = crit * base; + + if (!min.IsEmpty()) + min = min * base; + + if (!max.IsEmpty()) + max = max * base; + + return new PerfdataValue(label, value, counter, unit, warn, crit, min, max); +} + +static const std::unordered_map<std::string, const char*> l_FormatUoMs ({ + { "ampere-seconds", "As" }, + { "amperes", "A" }, + { "bits", "b" }, + { "bytes", "B" }, + { "decibel-milliwatts", "dBm" }, + { "degrees-celsius", "C" }, + { "degrees-fahrenheit", "F" }, + { "degrees-kelvin", "K" }, + { "grams", "g" }, + { "liters", "l" }, + { "lumens", "lm" }, + { "ohms", "O" }, + { "percent", "%" }, + { "seconds", "s" }, + { "volts", "V" }, + { "watt-hours", "Wh" }, + { "watts", "W" } +}); + +String PerfdataValue::Format() const +{ + std::ostringstream result; + + if (GetLabel().FindFirstOf(" ") != String::NPos) + result << "'" << GetLabel() << "'"; + else + result << GetLabel(); + + result << "=" << Convert::ToString(GetValue()); + + String unit; + + if (GetCounter()) { + unit = "c"; + } else { + auto myUnit (GetUnit()); + auto uom (l_FormatUoMs.find(myUnit.GetData())); + + if (uom != l_FormatUoMs.end()) { + unit = uom->second; + } + } + + result << unit; + + if (!GetWarn().IsEmpty()) { + result << ";" << Convert::ToString(GetWarn()); + + if (!GetCrit().IsEmpty()) { + result << ";" << Convert::ToString(GetCrit()); + + if (!GetMin().IsEmpty()) { + result << ";" << Convert::ToString(GetMin()); + + if (!GetMax().IsEmpty()) { + result << ";" << Convert::ToString(GetMax()); + } + } + } + } + + return result.str(); +} + +Value PerfdataValue::ParseWarnCritMinMaxToken(const std::vector<String>& tokens, std::vector<String>::size_type index, const String& description) +{ + if (tokens.size() > index && tokens[index] != "U" && tokens[index] != "" && tokens[index].FindFirstNotOf("+-0123456789.eE") == String::NPos) + return Convert::ToDouble(tokens[index]); + else { + if (tokens.size() > index && tokens[index] != "") + Log(LogDebug, "PerfdataValue") + << "Ignoring unsupported perfdata " << description << " range, value: '" << tokens[index] << "'."; + return Empty; + } +} diff --git a/lib/base/perfdatavalue.hpp b/lib/base/perfdatavalue.hpp new file mode 100644 index 0000000..05b2c34 --- /dev/null +++ b/lib/base/perfdatavalue.hpp @@ -0,0 +1,38 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#ifndef PERFDATAVALUE_H +#define PERFDATAVALUE_H + +#include "base/i2-base.hpp" +#include "base/perfdatavalue-ti.hpp" + +namespace icinga +{ + +/** + * A performance data value. + * + * @ingroup base + */ +class PerfdataValue final : public ObjectImpl<PerfdataValue> +{ +public: + DECLARE_OBJECT(PerfdataValue); + + PerfdataValue() = default; + + PerfdataValue(const String& label, double value, bool counter = false, const String& unit = "", + const Value& warn = Empty, const Value& crit = Empty, + const Value& min = Empty, const Value& max = Empty); + + static PerfdataValue::Ptr Parse(const String& perfdata); + String Format() const; + +private: + static Value ParseWarnCritMinMaxToken(const std::vector<String>& tokens, + std::vector<String>::size_type index, const String& description); +}; + +} + +#endif /* PERFDATA_VALUE */ diff --git a/lib/base/perfdatavalue.ti b/lib/base/perfdatavalue.ti new file mode 100644 index 0000000..b2692e9 --- /dev/null +++ b/lib/base/perfdatavalue.ti @@ -0,0 +1,20 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +library base; + +namespace icinga +{ + +class PerfdataValue +{ + [state] String label; + [state] double value; + [state] bool counter; + [state] String unit; + [state] Value crit; + [state] Value warn; + [state] Value min; + [state] Value max; +}; + +} diff --git a/lib/base/primitivetype.cpp b/lib/base/primitivetype.cpp new file mode 100644 index 0000000..10286c7 --- /dev/null +++ b/lib/base/primitivetype.cpp @@ -0,0 +1,64 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#include "base/primitivetype.hpp" +#include "base/dictionary.hpp" + +using namespace icinga; + +PrimitiveType::PrimitiveType(String name, String base, const ObjectFactory& factory) + : m_Name(std::move(name)), m_Base(std::move(base)), m_Factory(factory) +{ } + +String PrimitiveType::GetName() const +{ + return m_Name; +} + +Type::Ptr PrimitiveType::GetBaseType() const +{ + if (m_Base == "None") + return nullptr; + else + return Type::GetByName(m_Base); +} + +int PrimitiveType::GetAttributes() const +{ + return 0; +} + +int PrimitiveType::GetFieldId(const String& name) const +{ + Type::Ptr base = GetBaseType(); + + if (base) + return base->GetFieldId(name); + else + return -1; +} + +Field PrimitiveType::GetFieldInfo(int id) const +{ + Type::Ptr base = GetBaseType(); + + if (base) + return base->GetFieldInfo(id); + else + throw std::runtime_error("Invalid field ID."); +} + +int PrimitiveType::GetFieldCount() const +{ + Type::Ptr base = GetBaseType(); + + if (base) + return Object::TypeInstance->GetFieldCount(); + else + return 0; +} + +ObjectFactory PrimitiveType::GetFactory() const +{ + return m_Factory; +} + diff --git a/lib/base/primitivetype.hpp b/lib/base/primitivetype.hpp new file mode 100644 index 0000000..439e20f --- /dev/null +++ b/lib/base/primitivetype.hpp @@ -0,0 +1,62 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#ifndef PRIMITIVETYPE_H +#define PRIMITIVETYPE_H + +#include "base/i2-base.hpp" +#include "base/type.hpp" +#include "base/initialize.hpp" + +namespace icinga +{ + +class PrimitiveType final : public Type +{ +public: + PrimitiveType(String name, String base, const ObjectFactory& factory = ObjectFactory()); + + String GetName() const override; + Type::Ptr GetBaseType() const override; + int GetAttributes() const override; + int GetFieldId(const String& name) const override; + Field GetFieldInfo(int id) const override; + int GetFieldCount() const override; + +protected: + ObjectFactory GetFactory() const override; + +private: + String m_Name; + String m_Base; + ObjectFactory m_Factory; +}; + +/* Ensure that the priority is lower than the basic namespace initialization in scriptframe.cpp. */ +#define REGISTER_BUILTIN_TYPE(type, prototype) \ + INITIALIZE_ONCE_WITH_PRIORITY([]() { \ + icinga::Type::Ptr t = new PrimitiveType(#type, "None"); \ + t->SetPrototype(prototype); \ + icinga::Type::Register(t); \ + }, InitializePriority::RegisterBuiltinTypes) + +#define REGISTER_PRIMITIVE_TYPE_FACTORY(type, base, prototype, factory) \ + INITIALIZE_ONCE_WITH_PRIORITY([]() { \ + icinga::Type::Ptr t = new PrimitiveType(#type, #base, factory); \ + t->SetPrototype(prototype); \ + icinga::Type::Register(t); \ + type::TypeInstance = t; \ + }, InitializePriority::RegisterPrimitiveTypes); \ + DEFINE_TYPE_INSTANCE(type) + +#define REGISTER_PRIMITIVE_TYPE(type, base, prototype) \ + REGISTER_PRIMITIVE_TYPE_FACTORY(type, base, prototype, DefaultObjectFactory<type>) + +#define REGISTER_PRIMITIVE_TYPE_VA(type, base, prototype) \ + REGISTER_PRIMITIVE_TYPE_FACTORY(type, base, prototype, DefaultObjectFactoryVA<type>) + +#define REGISTER_PRIMITIVE_TYPE_NOINST(type, base, prototype) \ + REGISTER_PRIMITIVE_TYPE_FACTORY(type, base, prototype, nullptr) + +} + +#endif /* PRIMITIVETYPE_H */ diff --git a/lib/base/process.cpp b/lib/base/process.cpp new file mode 100644 index 0000000..d4246a6 --- /dev/null +++ b/lib/base/process.cpp @@ -0,0 +1,1207 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#include "base/process.hpp" +#include "base/exception.hpp" +#include "base/convert.hpp" +#include "base/array.hpp" +#include "base/objectlock.hpp" +#include "base/utility.hpp" +#include "base/initialize.hpp" +#include "base/logger.hpp" +#include "base/utility.hpp" +#include "base/scriptglobal.hpp" +#include "base/json.hpp" +#include <boost/algorithm/string/join.hpp> +#include <boost/thread/once.hpp> +#include <thread> +#include <iostream> + +#ifndef _WIN32 +# include <execvpe.h> +# include <poll.h> +# include <string.h> + +# ifndef __APPLE__ +extern char **environ; +# else /* __APPLE__ */ +# include <crt_externs.h> +# define environ (*_NSGetEnviron()) +# endif /* __APPLE__ */ +#endif /* _WIN32 */ + +using namespace icinga; + +#define IOTHREADS 4 + +static std::mutex l_ProcessMutex[IOTHREADS]; +static std::map<Process::ProcessHandle, Process::Ptr> l_Processes[IOTHREADS]; +#ifdef _WIN32 +static HANDLE l_Events[IOTHREADS]; +#else /* _WIN32 */ +static int l_EventFDs[IOTHREADS][2]; +static std::map<Process::ConsoleHandle, Process::ProcessHandle> l_FDs[IOTHREADS]; + +static std::mutex l_ProcessControlMutex; +static int l_ProcessControlFD = -1; +static pid_t l_ProcessControlPID; +#endif /* _WIN32 */ +static boost::once_flag l_ProcessOnceFlag = BOOST_ONCE_INIT; +static boost::once_flag l_SpawnHelperOnceFlag = BOOST_ONCE_INIT; + +Process::Process(Process::Arguments arguments, Dictionary::Ptr extraEnvironment) + : m_Arguments(std::move(arguments)), m_ExtraEnvironment(std::move(extraEnvironment)), + m_Timeout(600) +#ifdef _WIN32 + , m_ReadPending(false), m_ReadFailed(false), m_Overlapped() +#else /* _WIN32 */ + , m_SentSigterm(false) +#endif /* _WIN32 */ + , m_AdjustPriority(false), m_ResultAvailable(false) +{ +#ifdef _WIN32 + m_Overlapped.hEvent = CreateEvent(nullptr, TRUE, FALSE, nullptr); +#endif /* _WIN32 */ +} + +Process::~Process() +{ +#ifdef _WIN32 + CloseHandle(m_Overlapped.hEvent); +#endif /* _WIN32 */ +} + +#ifndef _WIN32 +static Value ProcessSpawnImpl(struct msghdr *msgh, const Dictionary::Ptr& request) +{ + struct cmsghdr *cmsg = CMSG_FIRSTHDR(msgh); + + if (cmsg == nullptr || cmsg->cmsg_level != SOL_SOCKET || cmsg->cmsg_len != CMSG_LEN(sizeof(int) * 3)) { + std::cerr << "Invalid 'spawn' request: FDs missing" << std::endl; + return Empty; + } + + auto *fds = (int *)CMSG_DATA(cmsg); + + Array::Ptr arguments = request->Get("arguments"); + Dictionary::Ptr extraEnvironment = request->Get("extraEnvironment"); + bool adjustPriority = request->Get("adjustPriority"); + + // build argv + auto **argv = new char *[arguments->GetLength() + 1]; + + for (unsigned int i = 0; i < arguments->GetLength(); i++) { + String arg = arguments->Get(i); + argv[i] = strdup(arg.CStr()); + } + + argv[arguments->GetLength()] = nullptr; + + // build envp + int envc = 0; + + /* count existing environment variables */ + while (environ[envc]) + envc++; + + auto **envp = new char *[envc + (extraEnvironment ? extraEnvironment->GetLength() : 0) + 2]; + const char* lcnumeric = "LC_NUMERIC="; + const char* notifySocket = "NOTIFY_SOCKET="; + int j = 0; + + for (int i = 0; i < envc; i++) { + if (strncmp(environ[i], lcnumeric, strlen(lcnumeric)) == 0) { + continue; + } + + if (strncmp(environ[i], notifySocket, strlen(notifySocket)) == 0) { + continue; + } + + envp[j] = strdup(environ[i]); + ++j; + } + + if (extraEnvironment) { + ObjectLock olock(extraEnvironment); + + for (const Dictionary::Pair& kv : extraEnvironment) { + String skv = kv.first + "=" + Convert::ToString(kv.second); + envp[j] = strdup(skv.CStr()); + j++; + } + } + + envp[j] = strdup("LC_NUMERIC=C"); + envp[j + 1] = nullptr; + + extraEnvironment.reset(); + + pid_t pid = fork(); + + int errorCode = 0; + + if (pid < 0) + errorCode = errno; + + if (pid == 0) { + // child process + + (void)close(l_ProcessControlFD); + + if (setsid() < 0) { + perror("setsid() failed"); + _exit(128); + } + + if (dup2(fds[0], STDIN_FILENO) < 0 || dup2(fds[1], STDOUT_FILENO) < 0 || dup2(fds[2], STDERR_FILENO) < 0) { + perror("dup2() failed"); + _exit(128); + } + + (void)close(fds[0]); + (void)close(fds[1]); + (void)close(fds[2]); + +#ifdef HAVE_NICE + if (adjustPriority) { + // Cheating the compiler on "warning: ignoring return value of 'int nice(int)', declared with attribute warn_unused_result [-Wunused-result]". + auto x (nice(5)); + (void)x; + } +#endif /* HAVE_NICE */ + + sigset_t mask; + sigemptyset(&mask); + sigprocmask(SIG_SETMASK, &mask, nullptr); + + if (icinga2_execvpe(argv[0], argv, envp) < 0) { + char errmsg[512]; + strcpy(errmsg, "execvpe("); + strncat(errmsg, argv[0], sizeof(errmsg) - strlen(errmsg) - 1); + strncat(errmsg, ") failed", sizeof(errmsg) - strlen(errmsg) - 1); + errmsg[sizeof(errmsg) - 1] = '\0'; + perror(errmsg); + } + + _exit(128); + } + + (void)close(fds[0]); + (void)close(fds[1]); + (void)close(fds[2]); + + // free arguments + for (int i = 0; argv[i]; i++) + free(argv[i]); + + delete[] argv; + + // free environment + for (int i = 0; envp[i]; i++) + free(envp[i]); + + delete[] envp; + + Dictionary::Ptr response = new Dictionary({ + { "rc", pid }, + { "errno", errorCode } + }); + + return response; +} + +static Value ProcessKillImpl(struct msghdr *msgh, const Dictionary::Ptr& request) +{ + pid_t pid = request->Get("pid"); + int signum = request->Get("signum"); + + errno = 0; + kill(pid, signum); + int error = errno; + + Dictionary::Ptr response = new Dictionary({ + { "errno", error } + }); + + return response; +} + +static Value ProcessWaitPIDImpl(struct msghdr *msgh, const Dictionary::Ptr& request) +{ + pid_t pid = request->Get("pid"); + + int status; + int rc = waitpid(pid, &status, 0); + + Dictionary::Ptr response = new Dictionary({ + { "status", status }, + { "rc", rc } + }); + + return response; +} + +static void ProcessHandler() +{ + sigset_t mask; + sigfillset(&mask); + sigprocmask(SIG_SETMASK, &mask, nullptr); + + Utility::CloseAllFDs({0, 1, 2, l_ProcessControlFD}); + + for (;;) { + size_t length; + + struct msghdr msg; + memset(&msg, 0, sizeof(msg)); + + struct iovec io; + io.iov_base = &length; + io.iov_len = sizeof(length); + + msg.msg_iov = &io; + msg.msg_iovlen = 1; + + char cbuf[4096]; + msg.msg_control = cbuf; + msg.msg_controllen = sizeof(cbuf); + + int rc = recvmsg(l_ProcessControlFD, &msg, 0); + + if (rc <= 0) { + if (rc < 0 && (errno == EINTR || errno == EAGAIN)) + continue; + + break; + } + + auto *mbuf = new char[length]; + + size_t count = 0; + while (count < length) { + rc = recv(l_ProcessControlFD, mbuf + count, length - count, 0); + + if (rc <= 0) { + if (rc < 0 && (errno == EINTR || errno == EAGAIN)) + continue; + + delete [] mbuf; + + _exit(0); + } + + count += rc; + + if (rc == 0) + break; + } + + String jrequest = String(mbuf, mbuf + count); + + delete [] mbuf; + + Dictionary::Ptr request = JsonDecode(jrequest); + + String command = request->Get("command"); + + Value response; + + if (command == "spawn") + response = ProcessSpawnImpl(&msg, request); + else if (command == "waitpid") + response = ProcessWaitPIDImpl(&msg, request); + else if (command == "kill") + response = ProcessKillImpl(&msg, request); + else + response = Empty; + + String jresponse = JsonEncode(response); + + if (send(l_ProcessControlFD, jresponse.CStr(), jresponse.GetLength(), 0) < 0) { + BOOST_THROW_EXCEPTION(posix_error() + << boost::errinfo_api_function("send") + << boost::errinfo_errno(errno)); + } + } + + _exit(0); +} + +static void StartSpawnProcessHelper() +{ + if (l_ProcessControlFD != -1) { + (void)close(l_ProcessControlFD); + + int status; + (void)waitpid(l_ProcessControlPID, &status, 0); + } + + int controlFDs[2]; + if (socketpair(AF_UNIX, SOCK_STREAM, 0, controlFDs) < 0) { + BOOST_THROW_EXCEPTION(posix_error() + << boost::errinfo_api_function("socketpair") + << boost::errinfo_errno(errno)); + } + + pid_t pid = fork(); + + if (pid < 0) { + BOOST_THROW_EXCEPTION(posix_error() + << boost::errinfo_api_function("fork") + << boost::errinfo_errno(errno)); + } + + if (pid == 0) { + (void)close(controlFDs[1]); + + l_ProcessControlFD = controlFDs[0]; + + ProcessHandler(); + + _exit(1); + } + + (void)close(controlFDs[0]); + + l_ProcessControlFD = controlFDs[1]; + l_ProcessControlPID = pid; +} + +static pid_t ProcessSpawn(const std::vector<String>& arguments, const Dictionary::Ptr& extraEnvironment, bool adjustPriority, int fds[3]) +{ + Dictionary::Ptr request = new Dictionary({ + { "command", "spawn" }, + { "arguments", Array::FromVector(arguments) }, + { "extraEnvironment", extraEnvironment }, + { "adjustPriority", adjustPriority } + }); + + String jrequest = JsonEncode(request); + size_t length = jrequest.GetLength(); + + std::unique_lock<std::mutex> lock(l_ProcessControlMutex); + + struct msghdr msg; + memset(&msg, 0, sizeof(msg)); + + struct iovec io; + io.iov_base = &length; + io.iov_len = sizeof(length); + + msg.msg_iov = &io; + msg.msg_iovlen = 1; + + char cbuf[CMSG_SPACE(sizeof(int) * 3)]; + msg.msg_control = cbuf; + msg.msg_controllen = sizeof(cbuf); + + struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg); + cmsg->cmsg_level = SOL_SOCKET; + cmsg->cmsg_type = SCM_RIGHTS; + cmsg->cmsg_len = CMSG_LEN(sizeof(int) * 3); + + memcpy(CMSG_DATA(cmsg), fds, sizeof(int) * 3); + + msg.msg_controllen = cmsg->cmsg_len; + + do { + while (sendmsg(l_ProcessControlFD, &msg, 0) < 0) { + StartSpawnProcessHelper(); + } + } while (send(l_ProcessControlFD, jrequest.CStr(), jrequest.GetLength(), 0) < 0); + + char buf[4096]; + + ssize_t rc = recv(l_ProcessControlFD, buf, sizeof(buf), 0); + + if (rc <= 0) + return -1; + + String jresponse = String(buf, buf + rc); + + Dictionary::Ptr response = JsonDecode(jresponse); + + if (response->Get("rc") == -1) + errno = response->Get("errno"); + + return response->Get("rc"); +} + +static int ProcessKill(pid_t pid, int signum) +{ + Dictionary::Ptr request = new Dictionary({ + { "command", "kill" }, + { "pid", pid }, + { "signum", signum } + }); + + String jrequest = JsonEncode(request); + size_t length = jrequest.GetLength(); + + std::unique_lock<std::mutex> lock(l_ProcessControlMutex); + + do { + while (send(l_ProcessControlFD, &length, sizeof(length), 0) < 0) { + StartSpawnProcessHelper(); + } + } while (send(l_ProcessControlFD, jrequest.CStr(), jrequest.GetLength(), 0) < 0); + + char buf[4096]; + + ssize_t rc = recv(l_ProcessControlFD, buf, sizeof(buf), 0); + + if (rc <= 0) + return -1; + + String jresponse = String(buf, buf + rc); + + Dictionary::Ptr response = JsonDecode(jresponse); + return response->Get("errno"); +} + +static int ProcessWaitPID(pid_t pid, int *status) +{ + Dictionary::Ptr request = new Dictionary({ + { "command", "waitpid" }, + { "pid", pid } + }); + + String jrequest = JsonEncode(request); + size_t length = jrequest.GetLength(); + + std::unique_lock<std::mutex> lock(l_ProcessControlMutex); + + do { + while (send(l_ProcessControlFD, &length, sizeof(length), 0) < 0) { + StartSpawnProcessHelper(); + } + } while (send(l_ProcessControlFD, jrequest.CStr(), jrequest.GetLength(), 0) < 0); + + char buf[4096]; + + ssize_t rc = recv(l_ProcessControlFD, buf, sizeof(buf), 0); + + if (rc <= 0) + return -1; + + String jresponse = String(buf, buf + rc); + + Dictionary::Ptr response = JsonDecode(jresponse); + *status = response->Get("status"); + return response->Get("rc"); +} + +void Process::InitializeSpawnHelper() +{ + if (l_ProcessControlFD == -1) + StartSpawnProcessHelper(); +} +#endif /* _WIN32 */ + +static void InitializeProcess() +{ +#ifdef _WIN32 + for (auto& event : l_Events) { + event = CreateEvent(nullptr, TRUE, FALSE, nullptr); + } +#else /* _WIN32 */ + for (auto& eventFD : l_EventFDs) { +# ifdef HAVE_PIPE2 + if (pipe2(eventFD, O_CLOEXEC) < 0) { + if (errno == ENOSYS) { +# endif /* HAVE_PIPE2 */ + if (pipe(eventFD) < 0) { + BOOST_THROW_EXCEPTION(posix_error() + << boost::errinfo_api_function("pipe") + << boost::errinfo_errno(errno)); + } + + Utility::SetCloExec(eventFD[0]); + Utility::SetCloExec(eventFD[1]); +# ifdef HAVE_PIPE2 + } else { + BOOST_THROW_EXCEPTION(posix_error() + << boost::errinfo_api_function("pipe2") + << boost::errinfo_errno(errno)); + } + } +# endif /* HAVE_PIPE2 */ + } +#endif /* _WIN32 */ +} + +INITIALIZE_ONCE(InitializeProcess); + +void Process::ThreadInitialize() +{ + /* Note to self: Make sure this runs _after_ we've daemonized. */ + for (int tid = 0; tid < IOTHREADS; tid++) { + std::thread t([tid]() { IOThreadProc(tid); }); + t.detach(); + } +} + +Process::Arguments Process::PrepareCommand(const Value& command) +{ +#ifdef _WIN32 + String args; +#else /* _WIN32 */ + std::vector<String> args; +#endif /* _WIN32 */ + + if (command.IsObjectType<Array>()) { + Array::Ptr arguments = command; + + ObjectLock olock(arguments); + for (const Value& argument : arguments) { +#ifdef _WIN32 + if (args != "") + args += " "; + + args += Utility::EscapeCreateProcessArg(argument); +#else /* _WIN32 */ + args.push_back(argument); +#endif /* _WIN32 */ + } + + return args; + } + +#ifdef _WIN32 + return command; +#else /* _WIN32 */ + return { "sh", "-c", command }; +#endif +} + +void Process::SetTimeout(double timeout) +{ + m_Timeout = timeout; +} + +double Process::GetTimeout() const +{ + return m_Timeout; +} + +void Process::SetAdjustPriority(bool adjust) +{ + m_AdjustPriority = adjust; +} + +bool Process::GetAdjustPriority() const +{ + return m_AdjustPriority; +} + +void Process::IOThreadProc(int tid) +{ +#ifdef _WIN32 + HANDLE *handles = nullptr; + HANDLE *fhandles = nullptr; +#else /* _WIN32 */ + pollfd *pfds = nullptr; +#endif /* _WIN32 */ + int count = 0; + double now; + + Utility::SetThreadName("ProcessIO"); + + for (;;) { + double timeout = -1; + + now = Utility::GetTime(); + + { + std::unique_lock<std::mutex> lock(l_ProcessMutex[tid]); + + count = 1 + l_Processes[tid].size(); +#ifdef _WIN32 + handles = reinterpret_cast<HANDLE *>(realloc(handles, sizeof(HANDLE) * count)); + fhandles = reinterpret_cast<HANDLE *>(realloc(fhandles, sizeof(HANDLE) * count)); + + fhandles[0] = l_Events[tid]; + +#else /* _WIN32 */ + pfds = reinterpret_cast<pollfd *>(realloc(pfds, sizeof(pollfd) * count)); + + pfds[0].fd = l_EventFDs[tid][0]; + pfds[0].events = POLLIN; + pfds[0].revents = 0; +#endif /* _WIN32 */ + + int i = 1; + typedef std::pair<ProcessHandle, Process::Ptr> kv_pair; + for (const kv_pair& kv : l_Processes[tid]) { + const Process::Ptr& process = kv.second; +#ifdef _WIN32 + handles[i] = kv.first; + + if (!process->m_ReadPending) { + process->m_ReadPending = true; + + BOOL res = ReadFile(process->m_FD, process->m_ReadBuffer, sizeof(process->m_ReadBuffer), 0, &process->m_Overlapped); + if (res || GetLastError() != ERROR_IO_PENDING) { + process->m_ReadFailed = !res; + SetEvent(process->m_Overlapped.hEvent); + } + } + + fhandles[i] = process->m_Overlapped.hEvent; +#else /* _WIN32 */ + pfds[i].fd = process->m_FD; + pfds[i].events = POLLIN; + pfds[i].revents = 0; +#endif /* _WIN32 */ + + if (process->m_Timeout != 0) { + double delta = process->GetNextTimeout() - (now - process->m_Result.ExecutionStart); + + if (timeout == -1 || delta < timeout) + timeout = delta; + } + + i++; + } + } + + if (timeout < 0.01) + timeout = 0.5; + + timeout *= 1000; + +#ifdef _WIN32 + DWORD rc = WaitForMultipleObjects(count, fhandles, FALSE, timeout == -1 ? INFINITE : static_cast<DWORD>(timeout)); +#else /* _WIN32 */ + int rc = poll(pfds, count, timeout); + + if (rc < 0) + continue; +#endif /* _WIN32 */ + + now = Utility::GetTime(); + + { + std::unique_lock<std::mutex> lock(l_ProcessMutex[tid]); + +#ifdef _WIN32 + if (rc == WAIT_OBJECT_0) + ResetEvent(l_Events[tid]); +#else /* _WIN32 */ + if (pfds[0].revents & (POLLIN | POLLHUP | POLLERR)) { + char buffer[512]; + if (read(l_EventFDs[tid][0], buffer, sizeof(buffer)) < 0) + Log(LogCritical, "base", "Read from event FD failed."); + } +#endif /* _WIN32 */ + + for (int i = 1; i < count; i++) { +#ifdef _WIN32 + auto it = l_Processes[tid].find(handles[i]); +#else /* _WIN32 */ + auto it2 = l_FDs[tid].find(pfds[i].fd); + + if (it2 == l_FDs[tid].end()) + continue; /* This should never happen. */ + + auto it = l_Processes[tid].find(it2->second); +#endif /* _WIN32 */ + + if (it == l_Processes[tid].end()) + continue; /* This should never happen. */ + + bool is_timeout = false; + + if (it->second->m_Timeout != 0) { + double timeout = it->second->m_Result.ExecutionStart + it->second->GetNextTimeout(); + + if (timeout < now) + is_timeout = true; + } + +#ifdef _WIN32 + if (rc == WAIT_OBJECT_0 + i || is_timeout) { +#else /* _WIN32 */ + if (pfds[i].revents & (POLLIN | POLLHUP | POLLERR) || is_timeout) { +#endif /* _WIN32 */ + if (!it->second->DoEvents()) { +#ifdef _WIN32 + CloseHandle(it->first); + CloseHandle(it->second->m_FD); +#else /* _WIN32 */ + l_FDs[tid].erase(it->second->m_FD); + (void)close(it->second->m_FD); +#endif /* _WIN32 */ + l_Processes[tid].erase(it); + } + } + } + } + } +} + +String Process::PrettyPrintArguments(const Process::Arguments& arguments) +{ +#ifdef _WIN32 + return "'" + arguments + "'"; +#else /* _WIN32 */ + return "'" + boost::algorithm::join(arguments, "' '") + "'"; +#endif /* _WIN32 */ +} + +#ifdef _WIN32 +static BOOL CreatePipeOverlapped(HANDLE *outReadPipe, HANDLE *outWritePipe, + SECURITY_ATTRIBUTES *securityAttributes, DWORD size, DWORD readMode, DWORD writeMode) +{ + static LONG pipeIndex = 0; + + if (size == 0) + size = 8192; + + LONG currentIndex = InterlockedIncrement(&pipeIndex); + + char pipeName[128]; + sprintf(pipeName, "\\\\.\\Pipe\\OverlappedPipe.%d.%d", (int)GetCurrentProcessId(), (int)currentIndex); + + *outReadPipe = CreateNamedPipe(pipeName, PIPE_ACCESS_INBOUND | readMode, + PIPE_TYPE_BYTE | PIPE_WAIT, 1, size, size, 60 * 1000, securityAttributes); + + if (*outReadPipe == INVALID_HANDLE_VALUE) + return FALSE; + + *outWritePipe = CreateFile(pipeName, GENERIC_WRITE, 0, securityAttributes, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | writeMode, nullptr); + + if (*outWritePipe == INVALID_HANDLE_VALUE) { + DWORD error = GetLastError(); + CloseHandle(*outReadPipe); + SetLastError(error); + return FALSE; + } + + return TRUE; +} +#endif /* _WIN32 */ + +void Process::Run(const std::function<void(const ProcessResult&)>& callback) +{ +#ifndef _WIN32 + boost::call_once(l_SpawnHelperOnceFlag, &Process::InitializeSpawnHelper); +#endif /* _WIN32 */ + boost::call_once(l_ProcessOnceFlag, &Process::ThreadInitialize); + + m_Result.ExecutionStart = Utility::GetTime(); + +#ifdef _WIN32 + SECURITY_ATTRIBUTES sa = {}; + sa.nLength = sizeof(sa); + sa.bInheritHandle = TRUE; + + HANDLE outReadPipe, outWritePipe; + if (!CreatePipeOverlapped(&outReadPipe, &outWritePipe, &sa, 0, FILE_FLAG_OVERLAPPED, 0)) + BOOST_THROW_EXCEPTION(win32_error() + << boost::errinfo_api_function("CreatePipe") + << errinfo_win32_error(GetLastError())); + + if (!SetHandleInformation(outReadPipe, HANDLE_FLAG_INHERIT, 0)) + BOOST_THROW_EXCEPTION(win32_error() + << boost::errinfo_api_function("SetHandleInformation") + << errinfo_win32_error(GetLastError())); + + HANDLE outWritePipeDup; + if (!DuplicateHandle(GetCurrentProcess(), outWritePipe, GetCurrentProcess(), + &outWritePipeDup, 0, TRUE, DUPLICATE_SAME_ACCESS)) + BOOST_THROW_EXCEPTION(win32_error() + << boost::errinfo_api_function("DuplicateHandle") + << errinfo_win32_error(GetLastError())); + +/* LPPROC_THREAD_ATTRIBUTE_LIST lpAttributeList; + SIZE_T cbSize; + + if (!InitializeProcThreadAttributeList(nullptr, 1, 0, &cbSize) && GetLastError() != ERROR_INSUFFICIENT_BUFFER) + BOOST_THROW_EXCEPTION(win32_error() + << boost::errinfo_api_function("InitializeProcThreadAttributeList") + << errinfo_win32_error(GetLastError())); + + lpAttributeList = reinterpret_cast<LPPROC_THREAD_ATTRIBUTE_LIST>(new char[cbSize]); + + if (!InitializeProcThreadAttributeList(lpAttributeList, 1, 0, &cbSize)) + BOOST_THROW_EXCEPTION(win32_error() + << boost::errinfo_api_function("InitializeProcThreadAttributeList") + << errinfo_win32_error(GetLastError())); + + HANDLE rgHandles[3]; + rgHandles[0] = outWritePipe; + rgHandles[1] = outWritePipeDup; + rgHandles[2] = GetStdHandle(STD_INPUT_HANDLE); + + if (!UpdateProcThreadAttribute(lpAttributeList, 0, PROC_THREAD_ATTRIBUTE_HANDLE_LIST, + rgHandles, sizeof(rgHandles), nullptr, nullptr)) + BOOST_THROW_EXCEPTION(win32_error() + << boost::errinfo_api_function("UpdateProcThreadAttribute") + << errinfo_win32_error(GetLastError())); +*/ + + STARTUPINFOEX si = {}; + si.StartupInfo.cb = sizeof(si); + si.StartupInfo.hStdError = outWritePipe; + si.StartupInfo.hStdOutput = outWritePipeDup; + si.StartupInfo.hStdInput = GetStdHandle(STD_INPUT_HANDLE); + si.StartupInfo.dwFlags = STARTF_USESTDHANDLES; +// si.lpAttributeList = lpAttributeList; + + PROCESS_INFORMATION pi; + + char *args = new char[m_Arguments.GetLength() + 1]; + strncpy(args, m_Arguments.CStr(), m_Arguments.GetLength() + 1); + args[m_Arguments.GetLength()] = '\0'; + + LPCH pEnvironment = GetEnvironmentStrings(); + size_t ioffset = 0, offset = 0; + + char *envp = nullptr; + + for (;;) { + size_t len = strlen(pEnvironment + ioffset); + + if (len == 0) + break; + + char *eqp = strchr(pEnvironment + ioffset, '='); + if (eqp && m_ExtraEnvironment && m_ExtraEnvironment->Contains(String(pEnvironment + ioffset, eqp))) { + ioffset += len + 1; + continue; + } + + envp = static_cast<char *>(realloc(envp, offset + len + 1)); + + if (!envp) + BOOST_THROW_EXCEPTION(std::bad_alloc()); + + strcpy(envp + offset, pEnvironment + ioffset); + offset += len + 1; + ioffset += len + 1; + } + + FreeEnvironmentStrings(pEnvironment); + + if (m_ExtraEnvironment) { + ObjectLock olock(m_ExtraEnvironment); + + for (const Dictionary::Pair& kv : m_ExtraEnvironment) { + String skv = kv.first + "=" + Convert::ToString(kv.second); + + envp = static_cast<char *>(realloc(envp, offset + skv.GetLength() + 1)); + + if (!envp) + BOOST_THROW_EXCEPTION(std::bad_alloc()); + + strcpy(envp + offset, skv.CStr()); + offset += skv.GetLength() + 1; + } + } + + envp = static_cast<char *>(realloc(envp, offset + 1)); + + if (!envp) + BOOST_THROW_EXCEPTION(std::bad_alloc()); + + envp[offset] = '\0'; + + if (!CreateProcess(nullptr, args, nullptr, nullptr, TRUE, + 0 /*EXTENDED_STARTUPINFO_PRESENT*/, envp, nullptr, &si.StartupInfo, &pi)) { + DWORD error = GetLastError(); + CloseHandle(outWritePipe); + CloseHandle(outWritePipeDup); + free(envp); +/* DeleteProcThreadAttributeList(lpAttributeList); + delete [] reinterpret_cast<char *>(lpAttributeList); */ + + m_Result.PID = 0; + m_Result.ExecutionEnd = Utility::GetTime(); + m_Result.ExitStatus = 127; + m_Result.Output = "Command " + String(args) + " failed to execute: " + Utility::FormatErrorNumber(error); + + delete [] args; + + if (callback) { + /* + * Explicitly use Process::Ptr to keep the reference counted while the + * callback is active and making it crash safe + */ + Process::Ptr process(this); + Utility::QueueAsyncCallback([this, process, callback]() { callback(m_Result); }); + } + + return; + } + + delete [] args; + free(envp); +/* DeleteProcThreadAttributeList(lpAttributeList); + delete [] reinterpret_cast<char *>(lpAttributeList); */ + + CloseHandle(outWritePipe); + CloseHandle(outWritePipeDup); + CloseHandle(pi.hThread); + + m_Process = pi.hProcess; + m_FD = outReadPipe; + m_PID = pi.dwProcessId; + + Log(LogNotice, "Process") + << "Running command " << PrettyPrintArguments(m_Arguments) << ": PID " << m_PID; + +#else /* _WIN32 */ + int outfds[2]; + +#ifdef HAVE_PIPE2 + if (pipe2(outfds, O_CLOEXEC) < 0) { + if (errno == ENOSYS) { +#endif /* HAVE_PIPE2 */ + if (pipe(outfds) < 0) { + BOOST_THROW_EXCEPTION(posix_error() + << boost::errinfo_api_function("pipe") + << boost::errinfo_errno(errno)); + } + + Utility::SetCloExec(outfds[0]); + Utility::SetCloExec(outfds[1]); +#ifdef HAVE_PIPE2 + } else { + BOOST_THROW_EXCEPTION(posix_error() + << boost::errinfo_api_function("pipe2") + << boost::errinfo_errno(errno)); + } + } +#endif /* HAVE_PIPE2 */ + + int fds[3]; + fds[0] = STDIN_FILENO; + fds[1] = outfds[1]; + fds[2] = outfds[1]; + + m_Process = ProcessSpawn(m_Arguments, m_ExtraEnvironment, m_AdjustPriority, fds); + m_PID = m_Process; + + if (m_PID == -1) { + m_OutputStream << "Fork failed with error code " << errno << " (" << Utility::FormatErrorNumber(errno) << ")"; + Log(LogCritical, "Process", m_OutputStream.str()); + } + + Log(LogNotice, "Process") + << "Running command " << PrettyPrintArguments(m_Arguments) << ": PID " << m_PID; + + (void)close(outfds[1]); + + Utility::SetNonBlocking(outfds[0]); + + m_FD = outfds[0]; +#endif /* _WIN32 */ + + m_Callback = callback; + + int tid = GetTID(); + + { + std::unique_lock<std::mutex> lock(l_ProcessMutex[tid]); + l_Processes[tid][m_Process] = this; +#ifndef _WIN32 + l_FDs[tid][m_FD] = m_Process; +#endif /* _WIN32 */ + } + +#ifdef _WIN32 + SetEvent(l_Events[tid]); +#else /* _WIN32 */ + if (write(l_EventFDs[tid][1], "T", 1) < 0 && errno != EINTR && errno != EAGAIN) + Log(LogCritical, "base", "Write to event FD failed."); +#endif /* _WIN32 */ +} + +const ProcessResult& Process::WaitForResult() { + std::unique_lock<std::mutex> lock(m_ResultMutex); + m_ResultCondition.wait(lock, [this]{ return m_ResultAvailable; }); + return m_Result; +} + +bool Process::DoEvents() +{ + bool is_timeout = false; +#ifndef _WIN32 + bool could_not_kill = false; +#endif /* _WIN32 */ + + if (m_Timeout != 0) { + auto now (Utility::GetTime()); + +#ifndef _WIN32 + { + auto timeout (GetNextTimeout()); + auto deadline (m_Result.ExecutionStart + timeout); + + if (deadline < now && !m_SentSigterm) { + Log(LogWarning, "Process") + << "Terminating process " << m_PID << " (" << PrettyPrintArguments(m_Arguments) + << ") after timeout of " << timeout << " seconds"; + + m_OutputStream << "<Timeout exceeded.>"; + + int error = ProcessKill(m_Process, SIGTERM); + if (error) { + Log(LogWarning, "Process") + << "Couldn't terminate the process " << m_PID << " (" << PrettyPrintArguments(m_Arguments) + << "): [errno " << error << "] " << strerror(error); + } + + m_SentSigterm = true; + } + } +#endif /* _WIN32 */ + + auto timeout (GetNextTimeout()); + auto deadline (m_Result.ExecutionStart + timeout); + + if (deadline < now) { + Log(LogWarning, "Process") + << "Killing process group " << m_PID << " (" << PrettyPrintArguments(m_Arguments) + << ") after timeout of " << timeout << " seconds"; + +#ifdef _WIN32 + m_OutputStream << "<Timeout exceeded.>"; + TerminateProcess(m_Process, 3); +#else /* _WIN32 */ + int error = ProcessKill(-m_Process, SIGKILL); + if (error) { + Log(LogWarning, "Process") + << "Couldn't kill the process group " << m_PID << " (" << PrettyPrintArguments(m_Arguments) + << "): [errno " << error << "] " << strerror(error); + could_not_kill = true; + } +#endif /* _WIN32 */ + + is_timeout = true; + } + } + + if (!is_timeout) { +#ifdef _WIN32 + m_ReadPending = false; + + DWORD rc; + if (!m_ReadFailed && GetOverlappedResult(m_FD, &m_Overlapped, &rc, TRUE) && rc > 0) { + m_OutputStream.write(m_ReadBuffer, rc); + return true; + } +#else /* _WIN32 */ + char buffer[512]; + for (;;) { + int rc = read(m_FD, buffer, sizeof(buffer)); + + if (rc < 0 && (errno == EAGAIN || errno == EWOULDBLOCK)) + return true; + + if (rc > 0) { + m_OutputStream.write(buffer, rc); + continue; + } + + break; + } +#endif /* _WIN32 */ + } + + String output = m_OutputStream.str(); + +#ifdef _WIN32 + WaitForSingleObject(m_Process, INFINITE); + + DWORD exitcode; + GetExitCodeProcess(m_Process, &exitcode); + + Log(LogNotice, "Process") + << "PID " << m_PID << " (" << PrettyPrintArguments(m_Arguments) << ") terminated with exit code " << exitcode; +#else /* _WIN32 */ + int status, exitcode; + if (could_not_kill || m_PID == -1) { + exitcode = 128; + } else if (ProcessWaitPID(m_Process, &status) != m_Process) { + exitcode = 128; + + Log(LogWarning, "Process") + << "PID " << m_PID << " (" << PrettyPrintArguments(m_Arguments) << ") died mysteriously: waitpid failed"; + } else if (WIFEXITED(status)) { + exitcode = WEXITSTATUS(status); + + Log msg(LogNotice, "Process"); + msg << "PID " << m_PID << " (" << PrettyPrintArguments(m_Arguments) + << ") terminated with exit code " << exitcode; + + if (m_SentSigterm) { + exitcode = 128; + msg << " after sending SIGTERM"; + } + } else if (WIFSIGNALED(status)) { + int signum = WTERMSIG(status); + const char *zsigname = strsignal(signum); + + String signame = Convert::ToString(signum); + + if (zsigname) { + signame += " ("; + signame += zsigname; + signame += ")"; + } + + Log(LogWarning, "Process") + << "PID " << m_PID << " was terminated by signal " << signame; + + std::ostringstream outputbuf; + outputbuf << "<Terminated by signal " << signame << ".>"; + output = output + outputbuf.str(); + exitcode = 128; + } else { + exitcode = 128; + } +#endif /* _WIN32 */ + + { + std::lock_guard<std::mutex> lock(m_ResultMutex); + m_Result.PID = m_PID; + m_Result.ExecutionEnd = Utility::GetTime(); + m_Result.ExitStatus = exitcode; + m_Result.Output = output; + m_ResultAvailable = true; + } + m_ResultCondition.notify_all(); + + if (m_Callback) { + /* + * Explicitly use Process::Ptr to keep the reference counted while the + * callback is active and making it crash safe + */ + Process::Ptr process(this); + Utility::QueueAsyncCallback([this, process]() { m_Callback(m_Result); }); + } + + return false; +} + +pid_t Process::GetPID() const +{ + return m_PID; +} + + +int Process::GetTID() const +{ + return (reinterpret_cast<uintptr_t>(this) / sizeof(void *)) % IOTHREADS; +} + +double Process::GetNextTimeout() const +{ +#ifdef _WIN32 + return m_Timeout; +#else /* _WIN32 */ + return m_SentSigterm ? m_Timeout * 1.1 : m_Timeout; +#endif /* _WIN32 */ +} diff --git a/lib/base/process.hpp b/lib/base/process.hpp new file mode 100644 index 0000000..d83ba6e --- /dev/null +++ b/lib/base/process.hpp @@ -0,0 +1,117 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#ifndef PROCESS_H +#define PROCESS_H + +#include "base/i2-base.hpp" +#include "base/dictionary.hpp" +#include <iosfwd> +#include <deque> +#include <vector> +#include <sstream> +#include <mutex> +#include <condition_variable> + +namespace icinga +{ + +/** + * The result of a Process task. + * + * @ingroup base + */ +struct ProcessResult +{ + pid_t PID; + double ExecutionStart; + double ExecutionEnd; + long ExitStatus; + String Output; +}; + +/** + * A process task. Executes an external application and returns the exit + * code and console output. + * + * @ingroup base + */ +class Process final : public Object +{ +public: + DECLARE_PTR_TYPEDEFS(Process); + +#ifdef _WIN32 + typedef String Arguments; + typedef HANDLE ProcessHandle; + typedef HANDLE ConsoleHandle; +#else /* _WIN32 */ + typedef std::vector<String> Arguments; + typedef pid_t ProcessHandle; + typedef int ConsoleHandle; +#endif /* _WIN32 */ + + static const std::deque<Process::Ptr>::size_type MaxTasksPerThread = 512; + + Process(Arguments arguments, Dictionary::Ptr extraEnvironment = nullptr); + ~Process() override; + + void SetTimeout(double timeout); + double GetTimeout() const; + + void SetAdjustPriority(bool adjust); + bool GetAdjustPriority() const; + + void Run(const std::function<void (const ProcessResult&)>& callback = std::function<void (const ProcessResult&)>()); + + const ProcessResult& WaitForResult(); + + pid_t GetPID() const; + + static Arguments PrepareCommand(const Value& command); + + static void ThreadInitialize(); + + static String PrettyPrintArguments(const Arguments& arguments); + +#ifndef _WIN32 + static void InitializeSpawnHelper(); +#endif /* _WIN32 */ + +private: + Arguments m_Arguments; + Dictionary::Ptr m_ExtraEnvironment; + + double m_Timeout; +#ifndef _WIN32 + bool m_SentSigterm; +#endif /* _WIN32 */ + + bool m_AdjustPriority; + + ProcessHandle m_Process; + pid_t m_PID; + ConsoleHandle m_FD; + +#ifdef _WIN32 + bool m_ReadPending; + bool m_ReadFailed; + OVERLAPPED m_Overlapped; + char m_ReadBuffer[1024]; +#endif /* _WIN32 */ + + std::ostringstream m_OutputStream; + std::function<void (const ProcessResult&)> m_Callback; + ProcessResult m_Result; + bool m_ResultAvailable; + std::mutex m_ResultMutex; + std::condition_variable m_ResultCondition; + + static void IOThreadProc(int tid); + bool DoEvents(); + int GetTID() const; + double GetNextTimeout() const; +}; + +} + +#endif /* PROCESS_H */ diff --git a/lib/base/reference-script.cpp b/lib/base/reference-script.cpp new file mode 100644 index 0000000..9408245 --- /dev/null +++ b/lib/base/reference-script.cpp @@ -0,0 +1,35 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#include "base/reference.hpp" +#include "base/function.hpp" +#include "base/functionwrapper.hpp" +#include "base/scriptframe.hpp" +#include "base/exception.hpp" + +using namespace icinga; + +static void ReferenceSet(const Value& value) +{ + ScriptFrame *vframe = ScriptFrame::GetCurrentFrame(); + Reference::Ptr self = static_cast<Reference::Ptr>(vframe->Self); + REQUIRE_NOT_NULL(self); + self->Set(value); +} + +static Value ReferenceGet() +{ + ScriptFrame *vframe = ScriptFrame::GetCurrentFrame(); + Reference::Ptr self = static_cast<Reference::Ptr>(vframe->Self); + REQUIRE_NOT_NULL(self); + return self->Get(); +} + +Object::Ptr Reference::GetPrototype() +{ + static Dictionary::Ptr prototype = new Dictionary({ + { "set", new Function("Reference#set", ReferenceSet, { "value" }) }, + { "get", new Function("Reference#get", ReferenceGet, {}, true) }, + }); + + return prototype; +} diff --git a/lib/base/reference.cpp b/lib/base/reference.cpp new file mode 100644 index 0000000..b0104af --- /dev/null +++ b/lib/base/reference.cpp @@ -0,0 +1,38 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#include "base/reference.hpp" +#include "base/debug.hpp" +#include "base/primitivetype.hpp" +#include "base/dictionary.hpp" +#include "base/configwriter.hpp" +#include "base/convert.hpp" +#include "base/exception.hpp" + +using namespace icinga; + +REGISTER_PRIMITIVE_TYPE_NOINST(Reference, Object, Reference::GetPrototype()); + +Reference::Reference(const Object::Ptr& parent, const String& index) + : m_Parent(parent), m_Index(index) +{ +} + +Value Reference::Get() const +{ + return m_Parent->GetFieldByName(m_Index, true, DebugInfo()); +} + +void Reference::Set(const Value& value) +{ + m_Parent->SetFieldByName(m_Index, value, false, DebugInfo()); +} + +Object::Ptr Reference::GetParent() const +{ + return m_Parent; +} + +String Reference::GetIndex() const +{ + return m_Index; +} diff --git a/lib/base/reference.hpp b/lib/base/reference.hpp new file mode 100644 index 0000000..30faabe --- /dev/null +++ b/lib/base/reference.hpp @@ -0,0 +1,40 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#ifndef REFERENCE_H +#define REFERENCE_H + +#include "base/i2-base.hpp" +#include "base/objectlock.hpp" +#include "base/value.hpp" + +namespace icinga +{ + +/** + * A reference. + * + * @ingroup base + */ +class Reference final : public Object +{ +public: + DECLARE_OBJECT(Reference); + + Reference(const Object::Ptr& parent, const String& index); + + Value Get() const; + void Set(const Value& value); + + Object::Ptr GetParent() const; + String GetIndex() const; + + static Object::Ptr GetPrototype(); + +private: + Object::Ptr m_Parent; + String m_Index; +}; + +} + +#endif /* REFERENCE_H */ diff --git a/lib/base/registry.hpp b/lib/base/registry.hpp new file mode 100644 index 0000000..c13f7e1 --- /dev/null +++ b/lib/base/registry.hpp @@ -0,0 +1,121 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#ifndef REGISTRY_H +#define REGISTRY_H + +#include "base/i2-base.hpp" +#include "base/string.hpp" +#include <boost/signals2.hpp> +#include <map> +#include <mutex> + +namespace icinga +{ + +/** + * A registry. + * + * @ingroup base + */ +template<typename U, typename T> +class Registry +{ +public: + typedef std::map<String, T> ItemMap; + + void RegisterIfNew(const String& name, const T& item) + { + std::unique_lock<std::mutex> lock(m_Mutex); + + if (m_Items.find(name) != m_Items.end()) + return; + + RegisterInternal(name, item, lock); + } + + void Register(const String& name, const T& item) + { + std::unique_lock<std::mutex> lock(m_Mutex); + + RegisterInternal(name, item, lock); + } + + void Unregister(const String& name) + { + size_t erased; + + { + std::unique_lock<std::mutex> lock(m_Mutex); + erased = m_Items.erase(name); + } + + if (erased > 0) + OnUnregistered(name); + } + + void Clear() + { + typename Registry<U, T>::ItemMap items; + + { + std::unique_lock<std::mutex> lock(m_Mutex); + items = m_Items; + } + + for (const auto& kv : items) { + OnUnregistered(kv.first); + } + + { + std::unique_lock<std::mutex> lock(m_Mutex); + m_Items.clear(); + } + } + + T GetItem(const String& name) const + { + std::unique_lock<std::mutex> lock(m_Mutex); + + auto it = m_Items.find(name); + + if (it == m_Items.end()) + return T(); + + return it->second; + } + + ItemMap GetItems() const + { + std::unique_lock<std::mutex> lock(m_Mutex); + + return m_Items; /* Makes a copy of the map. */ + } + + boost::signals2::signal<void (const String&, const T&)> OnRegistered; + boost::signals2::signal<void (const String&)> OnUnregistered; + +private: + mutable std::mutex m_Mutex; + typename Registry<U, T>::ItemMap m_Items; + + void RegisterInternal(const String& name, const T& item, std::unique_lock<std::mutex>& lock) + { + bool old_item = false; + + if (m_Items.erase(name) > 0) + old_item = true; + + m_Items[name] = item; + + lock.unlock(); + + if (old_item) + OnUnregistered(name); + + OnRegistered(name, item); + } +}; + +} + +#endif /* REGISTRY_H */ diff --git a/lib/base/ringbuffer.cpp b/lib/base/ringbuffer.cpp new file mode 100644 index 0000000..52e2ae5 --- /dev/null +++ b/lib/base/ringbuffer.cpp @@ -0,0 +1,91 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#include "base/ringbuffer.hpp" +#include "base/objectlock.hpp" +#include "base/utility.hpp" +#include <algorithm> + +using namespace icinga; + +RingBuffer::RingBuffer(RingBuffer::SizeType slots) + : m_Slots(slots, 0), m_TimeValue(0), m_InsertedValues(0) +{ } + +RingBuffer::SizeType RingBuffer::GetLength() const +{ + std::unique_lock<std::mutex> lock(m_Mutex); + return m_Slots.size(); +} + +void RingBuffer::InsertValue(RingBuffer::SizeType tv, int num) +{ + std::unique_lock<std::mutex> lock(m_Mutex); + + InsertValueUnlocked(tv, num); +} + +void RingBuffer::InsertValueUnlocked(RingBuffer::SizeType tv, int num) +{ + RingBuffer::SizeType offsetTarget = tv % m_Slots.size(); + + if (m_TimeValue == 0) + m_InsertedValues = 1; + + if (tv > m_TimeValue) { + RingBuffer::SizeType offset = m_TimeValue % m_Slots.size(); + + /* walk towards the target offset, resetting slots to 0 */ + while (offset != offsetTarget) { + offset++; + + if (offset >= m_Slots.size()) + offset = 0; + + m_Slots[offset] = 0; + + if (m_TimeValue != 0 && m_InsertedValues < m_Slots.size()) + m_InsertedValues++; + } + + m_TimeValue = tv; + } + + m_Slots[offsetTarget] += num; +} + +int RingBuffer::UpdateAndGetValues(RingBuffer::SizeType tv, RingBuffer::SizeType span) +{ + std::unique_lock<std::mutex> lock(m_Mutex); + + return UpdateAndGetValuesUnlocked(tv, span); +} + +int RingBuffer::UpdateAndGetValuesUnlocked(RingBuffer::SizeType tv, RingBuffer::SizeType span) +{ + InsertValueUnlocked(tv, 0); + + if (span > m_Slots.size()) + span = m_Slots.size(); + + int off = m_TimeValue % m_Slots.size(); + int sum = 0; + while (span > 0) { + sum += m_Slots[off]; + + if (off == 0) + off = m_Slots.size(); + + off--; + span--; + } + + return sum; +} + +double RingBuffer::CalculateRate(RingBuffer::SizeType tv, RingBuffer::SizeType span) +{ + std::unique_lock<std::mutex> lock(m_Mutex); + + int sum = UpdateAndGetValuesUnlocked(tv, span); + return sum / static_cast<double>(std::min(span, m_InsertedValues)); +} diff --git a/lib/base/ringbuffer.hpp b/lib/base/ringbuffer.hpp new file mode 100644 index 0000000..9fbef53 --- /dev/null +++ b/lib/base/ringbuffer.hpp @@ -0,0 +1,45 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#ifndef RINGBUFFER_H +#define RINGBUFFER_H + +#include "base/i2-base.hpp" +#include "base/object.hpp" +#include <vector> +#include <mutex> + +namespace icinga +{ + +/** + * A ring buffer that holds a pre-defined number of integers. + * + * @ingroup base + */ +class RingBuffer final +{ +public: + DECLARE_PTR_TYPEDEFS(RingBuffer); + + typedef std::vector<int>::size_type SizeType; + + RingBuffer(SizeType slots); + + SizeType GetLength() const; + void InsertValue(SizeType tv, int num); + int UpdateAndGetValues(SizeType tv, SizeType span); + double CalculateRate(SizeType tv, SizeType span); + +private: + mutable std::mutex m_Mutex; + std::vector<int> m_Slots; + SizeType m_TimeValue; + SizeType m_InsertedValues; + + void InsertValueUnlocked(SizeType tv, int num); + int UpdateAndGetValuesUnlocked(SizeType tv, SizeType span); +}; + +} + +#endif /* RINGBUFFER_H */ diff --git a/lib/base/scriptframe.cpp b/lib/base/scriptframe.cpp new file mode 100644 index 0000000..7a7f44c --- /dev/null +++ b/lib/base/scriptframe.cpp @@ -0,0 +1,130 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#include "base/scriptframe.hpp" +#include "base/scriptglobal.hpp" +#include "base/namespace.hpp" +#include "base/exception.hpp" +#include "base/configuration.hpp" +#include "base/utility.hpp" + +using namespace icinga; + +boost::thread_specific_ptr<std::stack<ScriptFrame *> > ScriptFrame::m_ScriptFrames; + +static Namespace::Ptr l_SystemNS, l_StatsNS; + +/* Ensure that this gets called with highest priority + * and wins against other static initializers in lib/icinga, etc. + * LTO-enabled builds will cause trouble otherwise, see GH #6575. + */ +INITIALIZE_ONCE_WITH_PRIORITY([]() { + Namespace::Ptr globalNS = ScriptGlobal::GetGlobals(); + + l_SystemNS = new Namespace(true); + l_SystemNS->Set("PlatformKernel", Utility::GetPlatformKernel()); + l_SystemNS->Set("PlatformKernelVersion", Utility::GetPlatformKernelVersion()); + l_SystemNS->Set("PlatformName", Utility::GetPlatformName()); + l_SystemNS->Set("PlatformVersion", Utility::GetPlatformVersion()); + l_SystemNS->Set("PlatformArchitecture", Utility::GetPlatformArchitecture()); + l_SystemNS->Set("BuildHostName", ICINGA_BUILD_HOST_NAME); + l_SystemNS->Set("BuildCompilerName", ICINGA_BUILD_COMPILER_NAME); + l_SystemNS->Set("BuildCompilerVersion", ICINGA_BUILD_COMPILER_VERSION); + globalNS->Set("System", l_SystemNS, true); + + l_SystemNS->Set("Configuration", new Configuration()); + + l_StatsNS = new Namespace(true); + globalNS->Set("StatsFunctions", l_StatsNS, true); + + globalNS->Set("Internal", new Namespace(), true); +}, InitializePriority::CreateNamespaces); + +INITIALIZE_ONCE_WITH_PRIORITY([]() { + l_SystemNS->Freeze(); + l_StatsNS->Freeze(); +}, InitializePriority::FreezeNamespaces); + +ScriptFrame::ScriptFrame(bool allocLocals) + : Locals(allocLocals ? new Dictionary() : nullptr), Self(ScriptGlobal::GetGlobals()), Sandboxed(false), Depth(0) +{ + InitializeFrame(); +} + +ScriptFrame::ScriptFrame(bool allocLocals, Value self) + : Locals(allocLocals ? new Dictionary() : nullptr), Self(std::move(self)), Sandboxed(false), Depth(0) +{ + InitializeFrame(); +} + +void ScriptFrame::InitializeFrame() +{ + std::stack<ScriptFrame *> *frames = m_ScriptFrames.get(); + + if (frames && !frames->empty()) { + ScriptFrame *frame = frames->top(); + + Sandboxed = frame->Sandboxed; + } + + PushFrame(this); +} + +ScriptFrame::~ScriptFrame() +{ + ScriptFrame *frame = PopFrame(); + ASSERT(frame == this); + +#ifndef I2_DEBUG + (void)frame; +#endif /* I2_DEBUG */ +} + +void ScriptFrame::IncreaseStackDepth() +{ + if (Depth + 1 > 300) + BOOST_THROW_EXCEPTION(ScriptError("Stack overflow while evaluating expression: Recursion level too deep.")); + + Depth++; +} + +void ScriptFrame::DecreaseStackDepth() +{ + Depth--; +} + +ScriptFrame *ScriptFrame::GetCurrentFrame() +{ + std::stack<ScriptFrame *> *frames = m_ScriptFrames.get(); + + ASSERT(!frames->empty()); + return frames->top(); +} + +ScriptFrame *ScriptFrame::PopFrame() +{ + std::stack<ScriptFrame *> *frames = m_ScriptFrames.get(); + + ASSERT(!frames->empty()); + + ScriptFrame *frame = frames->top(); + frames->pop(); + + return frame; +} + +void ScriptFrame::PushFrame(ScriptFrame *frame) +{ + std::stack<ScriptFrame *> *frames = m_ScriptFrames.get(); + + if (!frames) { + frames = new std::stack<ScriptFrame *>(); + m_ScriptFrames.reset(frames); + } + + if (!frames->empty()) { + ScriptFrame *parent = frames->top(); + frame->Depth += parent->Depth; + } + + frames->push(frame); +} diff --git a/lib/base/scriptframe.hpp b/lib/base/scriptframe.hpp new file mode 100644 index 0000000..18e23ef --- /dev/null +++ b/lib/base/scriptframe.hpp @@ -0,0 +1,42 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#ifndef SCRIPTFRAME_H +#define SCRIPTFRAME_H + +#include "base/i2-base.hpp" +#include "base/dictionary.hpp" +#include "base/array.hpp" +#include <boost/thread/tss.hpp> +#include <stack> + +namespace icinga +{ + +struct ScriptFrame +{ + Dictionary::Ptr Locals; + Value Self; + bool Sandboxed; + int Depth; + + ScriptFrame(bool allocLocals); + ScriptFrame(bool allocLocals, Value self); + ~ScriptFrame(); + + void IncreaseStackDepth(); + void DecreaseStackDepth(); + + static ScriptFrame *GetCurrentFrame(); + +private: + static boost::thread_specific_ptr<std::stack<ScriptFrame *> > m_ScriptFrames; + + static void PushFrame(ScriptFrame *frame); + static ScriptFrame *PopFrame(); + + void InitializeFrame(); +}; + +} + +#endif /* SCRIPTFRAME_H */ diff --git a/lib/base/scriptglobal.cpp b/lib/base/scriptglobal.cpp new file mode 100644 index 0000000..e85e9ec --- /dev/null +++ b/lib/base/scriptglobal.cpp @@ -0,0 +1,110 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#include "base/atomic-file.hpp" +#include "base/scriptglobal.hpp" +#include "base/singleton.hpp" +#include "base/logger.hpp" +#include "base/stdiostream.hpp" +#include "base/netstring.hpp" +#include "base/json.hpp" +#include "base/convert.hpp" +#include "base/objectlock.hpp" +#include "base/exception.hpp" +#include "base/namespace.hpp" +#include "base/utility.hpp" +#include <fstream> + +using namespace icinga; + +Namespace::Ptr ScriptGlobal::m_Globals = new Namespace(); + +Value ScriptGlobal::Get(const String& name, const Value *defaultValue) +{ + Value result; + + if (!m_Globals->Get(name, &result)) { + if (defaultValue) + return *defaultValue; + + BOOST_THROW_EXCEPTION(std::invalid_argument("Tried to access undefined script variable '" + name + "'")); + } + + return result; +} + +void ScriptGlobal::Set(const String& name, const Value& value) +{ + std::vector<String> tokens = name.Split("."); + + if (tokens.empty()) + BOOST_THROW_EXCEPTION(std::invalid_argument("Name must not be empty")); + + { + ObjectLock olock(m_Globals); + + Namespace::Ptr parent = m_Globals; + + for (std::vector<String>::size_type i = 0; i < tokens.size(); i++) { + const String& token = tokens[i]; + + if (i + 1 != tokens.size()) { + Value vparent; + + if (!parent->Get(token, &vparent)) { + Namespace::Ptr dict = new Namespace(); + parent->Set(token, dict); + parent = dict; + } else { + parent = vparent; + } + } + } + + parent->Set(tokens[tokens.size() - 1], value); + } +} + +void ScriptGlobal::SetConst(const String& name, const Value& value) +{ + GetGlobals()->Set(name, value, true); +} + +bool ScriptGlobal::Exists(const String& name) +{ + return m_Globals->Contains(name); +} + +Namespace::Ptr ScriptGlobal::GetGlobals() +{ + return m_Globals; +} + +void ScriptGlobal::WriteToFile(const String& filename) +{ + Log(LogInformation, "ScriptGlobal") + << "Dumping variables to file '" << filename << "'"; + + AtomicFile fp (filename, 0600); + StdioStream::Ptr sfp = new StdioStream(&fp, false); + + ObjectLock olock(m_Globals); + for (const Namespace::Pair& kv : m_Globals) { + Value value = kv.second.Val; + + if (value.IsObject()) + value = Convert::ToString(value); + + Dictionary::Ptr persistentVariable = new Dictionary({ + { "name", kv.first }, + { "value", value } + }); + + String json = JsonEncode(persistentVariable); + + NetString::WriteStringToStream(sfp, json); + } + + sfp->Close(); + fp.Commit(); +} + diff --git a/lib/base/scriptglobal.hpp b/lib/base/scriptglobal.hpp new file mode 100644 index 0000000..f349b7b --- /dev/null +++ b/lib/base/scriptglobal.hpp @@ -0,0 +1,35 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#ifndef SCRIPTGLOBAL_H +#define SCRIPTGLOBAL_H + +#include "base/i2-base.hpp" +#include "base/namespace.hpp" + +namespace icinga +{ + +/** + * Global script variables. + * + * @ingroup base + */ +class ScriptGlobal +{ +public: + static Value Get(const String& name, const Value *defaultValue = nullptr); + static void Set(const String& name, const Value& value); + static void SetConst(const String& name, const Value& value); + static bool Exists(const String& name); + + static void WriteToFile(const String& filename); + + static Namespace::Ptr GetGlobals(); + +private: + static Namespace::Ptr m_Globals; +}; + +} + +#endif /* SCRIPTGLOBAL_H */ diff --git a/lib/base/scriptutils.cpp b/lib/base/scriptutils.cpp new file mode 100644 index 0000000..7fe856d --- /dev/null +++ b/lib/base/scriptutils.cpp @@ -0,0 +1,570 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#include "base/scriptutils.hpp" +#include "base/function.hpp" +#include "base/scriptframe.hpp" +#include "base/exception.hpp" +#include "base/utility.hpp" +#include "base/convert.hpp" +#include "base/json.hpp" +#include "base/logger.hpp" +#include "base/objectlock.hpp" +#include "base/configtype.hpp" +#include "base/application.hpp" +#include "base/dependencygraph.hpp" +#include "base/initialize.hpp" +#include "base/namespace.hpp" +#include "config/configitem.hpp" +#include <boost/regex.hpp> +#include <algorithm> +#include <set> +#ifdef _WIN32 +#include <msi.h> +#endif /* _WIN32 */ + +using namespace icinga; + +REGISTER_SAFE_FUNCTION(System, regex, &ScriptUtils::Regex, "pattern:text:mode"); +REGISTER_SAFE_FUNCTION(System, match, &ScriptUtils::Match, "pattern:text:mode"); +REGISTER_SAFE_FUNCTION(System, cidr_match, &ScriptUtils::CidrMatch, "pattern:ip:mode"); +REGISTER_SAFE_FUNCTION(System, len, &ScriptUtils::Len, "value"); +REGISTER_SAFE_FUNCTION(System, union, &ScriptUtils::Union, ""); +REGISTER_SAFE_FUNCTION(System, intersection, &ScriptUtils::Intersection, ""); +REGISTER_FUNCTION(System, log, &ScriptUtils::Log, "severity:facility:value"); +REGISTER_FUNCTION(System, range, &ScriptUtils::Range, "start:end:increment"); +REGISTER_FUNCTION(System, exit, &Application::Exit, "status"); +REGISTER_SAFE_FUNCTION(System, typeof, &ScriptUtils::TypeOf, "value"); +REGISTER_SAFE_FUNCTION(System, keys, &ScriptUtils::Keys, "value"); +REGISTER_SAFE_FUNCTION(System, random, &Utility::Random, ""); +REGISTER_SAFE_FUNCTION(System, get_template, &ScriptUtils::GetTemplate, "type:name"); +REGISTER_SAFE_FUNCTION(System, get_templates, &ScriptUtils::GetTemplates, "type"); +REGISTER_SAFE_FUNCTION(System, get_object, &ScriptUtils::GetObject, "type:name"); +REGISTER_SAFE_FUNCTION(System, get_objects, &ScriptUtils::GetObjects, "type"); +REGISTER_FUNCTION(System, assert, &ScriptUtils::Assert, "value"); +REGISTER_SAFE_FUNCTION(System, string, &ScriptUtils::CastString, "value"); +REGISTER_SAFE_FUNCTION(System, number, &ScriptUtils::CastNumber, "value"); +REGISTER_SAFE_FUNCTION(System, bool, &ScriptUtils::CastBool, "value"); +REGISTER_SAFE_FUNCTION(System, get_time, &Utility::GetTime, ""); +REGISTER_SAFE_FUNCTION(System, basename, &Utility::BaseName, "path"); +REGISTER_SAFE_FUNCTION(System, dirname, &Utility::DirName, "path"); +REGISTER_SAFE_FUNCTION(System, getenv, &ScriptUtils::GetEnv, "value"); +REGISTER_SAFE_FUNCTION(System, msi_get_component_path, &ScriptUtils::MsiGetComponentPathShim, "component"); +REGISTER_SAFE_FUNCTION(System, track_parents, &ScriptUtils::TrackParents, "child"); +REGISTER_SAFE_FUNCTION(System, escape_shell_cmd, &Utility::EscapeShellCmd, "cmd"); +REGISTER_SAFE_FUNCTION(System, escape_shell_arg, &Utility::EscapeShellArg, "arg"); +#ifdef _WIN32 +REGISTER_SAFE_FUNCTION(System, escape_create_process_arg, &Utility::EscapeCreateProcessArg, "arg"); +#endif /* _WIN32 */ +REGISTER_FUNCTION(System, ptr, &ScriptUtils::Ptr, "object"); +REGISTER_FUNCTION(System, sleep, &Utility::Sleep, "interval"); +REGISTER_FUNCTION(System, path_exists, &Utility::PathExists, "path"); +REGISTER_FUNCTION(System, glob, &ScriptUtils::Glob, "pathspec:callback:type"); +REGISTER_FUNCTION(System, glob_recursive, &ScriptUtils::GlobRecursive, "pathspec:callback:type"); + +INITIALIZE_ONCE(&ScriptUtils::StaticInitialize); + +enum MatchType +{ + MatchAll, + MatchAny +}; + +void ScriptUtils::StaticInitialize() +{ + ScriptGlobal::Set("System.MatchAll", MatchAll); + ScriptGlobal::Set("System.MatchAny", MatchAny); + + ScriptGlobal::Set("System.GlobFile", GlobFile); + ScriptGlobal::Set("System.GlobDirectory", GlobDirectory); +} + +String ScriptUtils::CastString(const Value& value) +{ + return value; +} + +double ScriptUtils::CastNumber(const Value& value) +{ + return value; +} + +bool ScriptUtils::CastBool(const Value& value) +{ + return value.ToBool(); +} + +bool ScriptUtils::Regex(const std::vector<Value>& args) +{ + if (args.size() < 2) + BOOST_THROW_EXCEPTION(std::invalid_argument("Regular expression and text must be specified for regex().")); + + String pattern = args[0]; + const Value& argTexts = args[1]; + + if (argTexts.IsObjectType<Dictionary>()) + BOOST_THROW_EXCEPTION(std::invalid_argument("Dictionaries are not supported by regex().")); + + MatchType mode; + + if (args.size() > 2) + mode = static_cast<MatchType>(static_cast<int>(args[2])); + else + mode = MatchAll; + + boost::regex expr(pattern.GetData()); + + Array::Ptr texts; + + if (argTexts.IsObject()) + texts = argTexts; + + if (texts) { + ObjectLock olock(texts); + + if (texts->GetLength() == 0) + return false; + + for (const String& text : texts) { + bool res = false; + try { + boost::smatch what; + res = boost::regex_search(text.GetData(), what, expr); + } catch (boost::exception&) { + res = false; /* exception means something went terribly wrong */ + } + + if (mode == MatchAny && res) + return true; + else if (mode == MatchAll && !res) + return false; + } + + /* MatchAny: Nothing matched. MatchAll: Everything matched. */ + return mode == MatchAll; + } else { + String text = argTexts; + boost::smatch what; + return boost::regex_search(text.GetData(), what, expr); + } +} + +bool ScriptUtils::Match(const std::vector<Value>& args) +{ + if (args.size() < 2) + BOOST_THROW_EXCEPTION(std::invalid_argument("Pattern and text must be specified for match().")); + + String pattern = args[0]; + const Value& argTexts = args[1]; + + if (argTexts.IsObjectType<Dictionary>()) + BOOST_THROW_EXCEPTION(std::invalid_argument("Dictionaries are not supported by match().")); + + MatchType mode; + + if (args.size() > 2) + mode = static_cast<MatchType>(static_cast<int>(args[2])); + else + mode = MatchAll; + + Array::Ptr texts; + + if (argTexts.IsObject()) + texts = argTexts; + + if (texts) { + ObjectLock olock(texts); + + if (texts->GetLength() == 0) + return false; + + for (const String& text : texts) { + bool res = Utility::Match(pattern, text); + + if (mode == MatchAny && res) + return true; + else if (mode == MatchAll && !res) + return false; + } + + /* MatchAny: Nothing matched. MatchAll: Everything matched. */ + return mode == MatchAll; + } else { + String text = argTexts; + return Utility::Match(pattern, argTexts); + } +} + +bool ScriptUtils::CidrMatch(const std::vector<Value>& args) +{ + if (args.size() < 2) + BOOST_THROW_EXCEPTION(std::invalid_argument("CIDR and IP address must be specified for cidr_match().")); + + String pattern = args[0]; + const Value& argIps = args[1]; + + if (argIps.IsObjectType<Dictionary>()) + BOOST_THROW_EXCEPTION(std::invalid_argument("Dictionaries are not supported by cidr_match().")); + + MatchType mode; + + if (args.size() > 2) + mode = static_cast<MatchType>(static_cast<int>(args[2])); + else + mode = MatchAll; + + Array::Ptr ips; + + if (argIps.IsObject()) + ips = argIps; + + if (ips) { + ObjectLock olock(ips); + + if (ips->GetLength() == 0) + return false; + + for (const String& ip : ips) { + bool res = Utility::CidrMatch(pattern, ip); + + if (mode == MatchAny && res) + return true; + else if (mode == MatchAll && !res) + return false; + } + + /* MatchAny: Nothing matched. MatchAll: Everything matched. */ + return mode == MatchAll; + } else { + String ip = argIps; + return Utility::CidrMatch(pattern, ip); + } +} + +double ScriptUtils::Len(const Value& value) +{ + if (value.IsObjectType<Dictionary>()) { + Dictionary::Ptr dict = value; + return dict->GetLength(); + } else if (value.IsObjectType<Array>()) { + Array::Ptr array = value; + return array->GetLength(); + } else if (value.IsString()) { + return Convert::ToString(value).GetLength(); + } else { + return 0; + } +} + +Array::Ptr ScriptUtils::Union(const std::vector<Value>& arguments) +{ + std::set<Value> values; + + for (const Value& varr : arguments) { + Array::Ptr arr = varr; + + if (arr) { + ObjectLock olock(arr); + for (const Value& value : arr) { + values.insert(value); + } + } + } + + return Array::FromSet(values); +} + +Array::Ptr ScriptUtils::Intersection(const std::vector<Value>& arguments) +{ + if (arguments.size() == 0) + return new Array(); + + Array::Ptr result = new Array(); + + Array::Ptr arg1 = arguments[0]; + + if (!arg1) + return result; + + Array::Ptr arr1 = arg1->ShallowClone(); + + for (std::vector<Value>::size_type i = 1; i < arguments.size(); i++) { + { + ObjectLock olock(arr1); + std::sort(arr1->Begin(), arr1->End()); + } + + Array::Ptr arg2 = arguments[i]; + + if (!arg2) + return result; + + Array::Ptr arr2 = arg2->ShallowClone(); + { + ObjectLock olock(arr2); + std::sort(arr2->Begin(), arr2->End()); + } + + result->Resize(std::max(arr1->GetLength(), arr2->GetLength())); + Array::SizeType len; + { + ObjectLock olock(arr1), xlock(arr2), ylock(result); + auto it = std::set_intersection(arr1->Begin(), arr1->End(), arr2->Begin(), arr2->End(), result->Begin()); + len = it - result->Begin(); + } + result->Resize(len); + arr1 = result; + } + + return result; +} + +void ScriptUtils::Log(const std::vector<Value>& arguments) +{ + if (arguments.size() != 1 && arguments.size() != 3) + BOOST_THROW_EXCEPTION(std::invalid_argument("Invalid number of arguments for log()")); + + LogSeverity severity; + String facility; + Value message; + + if (arguments.size() == 1) { + severity = LogInformation; + facility = "config"; + message = arguments[0]; + } else { + auto sval = static_cast<int>(arguments[0]); + severity = static_cast<LogSeverity>(sval); + facility = arguments[1]; + message = arguments[2]; + } + + if (message.IsString() || (!message.IsObjectType<Array>() && !message.IsObjectType<Dictionary>())) + ::Log(severity, facility, message); + else + ::Log(severity, facility, JsonEncode(message)); +} + +Array::Ptr ScriptUtils::Range(const std::vector<Value>& arguments) +{ + double start, end, increment; + + switch (arguments.size()) { + case 1: + start = 0; + end = arguments[0]; + increment = 1; + break; + case 2: + start = arguments[0]; + end = arguments[1]; + increment = 1; + break; + case 3: + start = arguments[0]; + end = arguments[1]; + increment = arguments[2]; + break; + default: + BOOST_THROW_EXCEPTION(std::invalid_argument("Invalid number of arguments for range()")); + } + + ArrayData result; + + if ((start < end && increment <= 0) || + (start > end && increment >= 0)) + return new Array(); + + for (double i = start; (increment > 0 ? i < end : i > end); i += increment) + result.push_back(i); + + return new Array(std::move(result)); +} + +Type::Ptr ScriptUtils::TypeOf(const Value& value) +{ + return value.GetReflectionType(); +} + +Array::Ptr ScriptUtils::Keys(const Object::Ptr& obj) +{ + ArrayData result; + + Dictionary::Ptr dict = dynamic_pointer_cast<Dictionary>(obj); + + if (dict) { + ObjectLock olock(dict); + for (const Dictionary::Pair& kv : dict) { + result.push_back(kv.first); + } + } + + Namespace::Ptr ns = dynamic_pointer_cast<Namespace>(obj); + + if (ns) { + ObjectLock olock(ns); + for (const Namespace::Pair& kv : ns) { + result.push_back(kv.first); + } + } + + return new Array(std::move(result)); +} + +static Dictionary::Ptr GetTargetForTemplate(const ConfigItem::Ptr& item) +{ + DebugInfo di = item->GetDebugInfo(); + + return new Dictionary({ + { "name", item->GetName() }, + { "type", item->GetType()->GetName() }, + { "location", new Dictionary({ + { "path", di.Path }, + { "first_line", di.FirstLine }, + { "first_column", di.FirstColumn }, + { "last_line", di.LastLine }, + { "last_column", di.LastColumn } + }) } + }); +} + +Dictionary::Ptr ScriptUtils::GetTemplate(const Value& vtype, const String& name) +{ + Type::Ptr ptype; + + if (vtype.IsObjectType<Type>()) + ptype = vtype; + else + ptype = Type::GetByName(vtype); + + ConfigItem::Ptr item = ConfigItem::GetByTypeAndName(ptype, name); + + if (!item || !item->IsAbstract()) + return nullptr; + + DebugInfo di = item->GetDebugInfo(); + + return GetTargetForTemplate(item); +} + +Array::Ptr ScriptUtils::GetTemplates(const Type::Ptr& type) +{ + if (!type) + BOOST_THROW_EXCEPTION(std::invalid_argument("Invalid type: Must not be null")); + + ArrayData result; + + for (const ConfigItem::Ptr& item : ConfigItem::GetItems(type)) { + if (item->IsAbstract()) + result.push_back(GetTargetForTemplate(item)); + } + + return new Array(std::move(result)); +} + +ConfigObject::Ptr ScriptUtils::GetObject(const Value& vtype, const String& name) +{ + Type::Ptr ptype; + + if (vtype.IsObjectType<Type>()) + ptype = vtype; + else + ptype = Type::GetByName(vtype); + + auto *ctype = dynamic_cast<ConfigType *>(ptype.get()); + + if (!ctype) + return nullptr; + + return ctype->GetObject(name); +} + +Array::Ptr ScriptUtils::GetObjects(const Type::Ptr& type) +{ + if (!type) + BOOST_THROW_EXCEPTION(std::invalid_argument("Invalid type: Must not be null")); + + auto *ctype = dynamic_cast<ConfigType *>(type.get()); + + if (!ctype) + BOOST_THROW_EXCEPTION(std::invalid_argument("Invalid type: Type must inherit from 'ConfigObject'")); + + ArrayData result; + + for (const ConfigObject::Ptr& object : ctype->GetObjects()) + result.push_back(object); + + return new Array(std::move(result)); +} + +void ScriptUtils::Assert(const Value& arg) +{ + if (!arg.ToBool()) + BOOST_THROW_EXCEPTION(std::runtime_error("Assertion failed")); +} + +String ScriptUtils::MsiGetComponentPathShim(const String& component) +{ +#ifdef _WIN32 + TCHAR productCode[39]; + if (MsiGetProductCode(component.CStr(), productCode) != ERROR_SUCCESS) + return ""; + TCHAR path[2048]; + DWORD szPath = sizeof(path); + path[0] = '\0'; + MsiGetComponentPath(productCode, component.CStr(), path, &szPath); + return path; +#else /* _WIN32 */ + return String(); +#endif /* _WIN32 */ +} + +Array::Ptr ScriptUtils::TrackParents(const Object::Ptr& child) +{ + return Array::FromVector(DependencyGraph::GetParents(child)); +} + +double ScriptUtils::Ptr(const Object::Ptr& object) +{ + return reinterpret_cast<intptr_t>(object.get()); +} + +Value ScriptUtils::Glob(const std::vector<Value>& args) +{ + if (args.size() < 1) + BOOST_THROW_EXCEPTION(std::invalid_argument("Path must be specified.")); + + String pathSpec = args[0]; + int type = GlobFile | GlobDirectory; + + if (args.size() > 1) + type = args[1]; + + std::vector<String> paths; + Utility::Glob(pathSpec, [&paths](const String& path) { paths.push_back(path); }, type); + + return Array::FromVector(paths); +} + +Value ScriptUtils::GlobRecursive(const std::vector<Value>& args) +{ + if (args.size() < 2) + BOOST_THROW_EXCEPTION(std::invalid_argument("Path and pattern must be specified.")); + + String path = args[0]; + String pattern = args[1]; + + int type = GlobFile | GlobDirectory; + + if (args.size() > 2) + type = args[2]; + + std::vector<String> paths; + Utility::GlobRecursive(path, pattern, [&paths](const String& newPath) { paths.push_back(newPath); }, type); + + return Array::FromVector(paths); +} + +String ScriptUtils::GetEnv(const String& key) +{ + return Utility::GetFromEnvironment(key); +} diff --git a/lib/base/scriptutils.hpp b/lib/base/scriptutils.hpp new file mode 100644 index 0000000..7bd3e8b --- /dev/null +++ b/lib/base/scriptutils.hpp @@ -0,0 +1,54 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#ifndef SCRIPTUTILS_H +#define SCRIPTUTILS_H + +#include "base/i2-base.hpp" +#include "base/string.hpp" +#include "base/array.hpp" +#include "base/dictionary.hpp" +#include "base/type.hpp" +#include "base/configobject.hpp" + +namespace icinga +{ + +/** + * @ingroup base + */ +class ScriptUtils +{ +public: + static void StaticInitialize(); + static String CastString(const Value& value); + static double CastNumber(const Value& value); + static bool CastBool(const Value& value); + static bool Regex(const std::vector<Value>& args); + static bool Match(const std::vector<Value>& args); + static bool CidrMatch(const std::vector<Value>& args); + static double Len(const Value& value); + static Array::Ptr Union(const std::vector<Value>& arguments); + static Array::Ptr Intersection(const std::vector<Value>& arguments); + static void Log(const std::vector<Value>& arguments); + static Array::Ptr Range(const std::vector<Value>& arguments); + static Type::Ptr TypeOf(const Value& value); + static Array::Ptr Keys(const Object::Ptr& obj); + static Dictionary::Ptr GetTemplate(const Value& vtype, const String& name); + static Array::Ptr GetTemplates(const Type::Ptr& type); + static ConfigObject::Ptr GetObject(const Value& type, const String& name); + static Array::Ptr GetObjects(const Type::Ptr& type); + static void Assert(const Value& arg); + static String MsiGetComponentPathShim(const String& component); + static Array::Ptr TrackParents(const Object::Ptr& parent); + static double Ptr(const Object::Ptr& object); + static Value Glob(const std::vector<Value>& args); + static Value GlobRecursive(const std::vector<Value>& args); + static String GetEnv(const String& key); + +private: + ScriptUtils(); +}; + +} + +#endif /* SCRIPTUTILS_H */ diff --git a/lib/base/serializer.cpp b/lib/base/serializer.cpp new file mode 100644 index 0000000..b8b140a --- /dev/null +++ b/lib/base/serializer.cpp @@ -0,0 +1,331 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#include "base/serializer.hpp" +#include "base/type.hpp" +#include "base/application.hpp" +#include "base/objectlock.hpp" +#include "base/convert.hpp" +#include "base/exception.hpp" +#include "base/namespace.hpp" +#include <boost/algorithm/string/join.hpp> +#include <deque> +#include <utility> + +using namespace icinga; + +struct SerializeStackEntry +{ + String Name; + Value Val; +}; + +CircularReferenceError::CircularReferenceError(String message, std::vector<String> path) + : m_Message(message), m_Path(path) +{ } + +const char *CircularReferenceError::what(void) const throw() +{ + return m_Message.CStr(); +} + +std::vector<String> CircularReferenceError::GetPath() const +{ + return m_Path; +} + +struct SerializeStack +{ + std::deque<SerializeStackEntry> Entries; + + inline void Push(const String& name, const Value& val) + { + Object::Ptr obj; + + if (val.IsObject()) + obj = val; + + if (obj) { + for (const auto& entry : Entries) { + if (entry.Val == obj) { + std::vector<String> path; + for (const auto& entry : Entries) + path.push_back(entry.Name); + path.push_back(name); + BOOST_THROW_EXCEPTION(CircularReferenceError("Cannot serialize object which recursively refers to itself. Attribute path which leads to the cycle: " + boost::algorithm::join(path, " -> "), path)); + } + } + } + + Entries.push_back({ name, obj }); + } + + inline void Pop() + { + Entries.pop_back(); + } +}; + +static Value SerializeInternal(const Value& value, int attributeTypes, SerializeStack& stack, bool dryRun); + +static Array::Ptr SerializeArray(const Array::Ptr& input, int attributeTypes, SerializeStack& stack, bool dryRun) +{ + ArrayData result; + + if (!dryRun) { + result.reserve(input->GetLength()); + } + + ObjectLock olock(input); + + int index = 0; + + for (const Value& value : input) { + stack.Push(Convert::ToString(index), value); + + auto serialized (SerializeInternal(value, attributeTypes, stack, dryRun)); + + if (!dryRun) { + result.emplace_back(std::move(serialized)); + } + + stack.Pop(); + index++; + } + + return dryRun ? nullptr : new Array(std::move(result)); +} + +static Dictionary::Ptr SerializeDictionary(const Dictionary::Ptr& input, int attributeTypes, SerializeStack& stack, bool dryRun) +{ + DictionaryData result; + + if (!dryRun) { + result.reserve(input->GetLength()); + } + + ObjectLock olock(input); + + for (const Dictionary::Pair& kv : input) { + stack.Push(kv.first, kv.second); + + auto serialized (SerializeInternal(kv.second, attributeTypes, stack, dryRun)); + + if (!dryRun) { + result.emplace_back(kv.first, std::move(serialized)); + } + + stack.Pop(); + } + + return dryRun ? nullptr : new Dictionary(std::move(result)); +} + +static Dictionary::Ptr SerializeNamespace(const Namespace::Ptr& input, int attributeTypes, SerializeStack& stack, bool dryRun) +{ + DictionaryData result; + + if (!dryRun) { + result.reserve(input->GetLength()); + } + + ObjectLock olock(input); + + for (const Namespace::Pair& kv : input) { + Value val = kv.second.Val; + stack.Push(kv.first, val); + + auto serialized (SerializeInternal(val, attributeTypes, stack, dryRun)); + + if (!dryRun) { + result.emplace_back(kv.first, std::move(serialized)); + } + + stack.Pop(); + } + + return dryRun ? nullptr : new Dictionary(std::move(result)); +} + +static Object::Ptr SerializeObject(const Object::Ptr& input, int attributeTypes, SerializeStack& stack, bool dryRun) +{ + Type::Ptr type = input->GetReflectionType(); + + if (!type) + return nullptr; + + DictionaryData fields; + + if (!dryRun) { + fields.reserve(type->GetFieldCount() + 1); + } + + ObjectLock olock(input); + + for (int i = 0; i < type->GetFieldCount(); i++) { + Field field = type->GetFieldInfo(i); + + if (attributeTypes != 0 && (field.Attributes & attributeTypes) == 0) + continue; + + if (strcmp(field.Name, "type") == 0) + continue; + + Value value = input->GetField(i); + stack.Push(field.Name, value); + + auto serialized (SerializeInternal(value, attributeTypes, stack, dryRun)); + + if (!dryRun) { + fields.emplace_back(field.Name, std::move(serialized)); + } + + stack.Pop(); + } + + if (!dryRun) { + fields.emplace_back("type", type->GetName()); + } + + return dryRun ? nullptr : new Dictionary(std::move(fields)); +} + +static Array::Ptr DeserializeArray(const Array::Ptr& input, bool safe_mode, int attributeTypes) +{ + ArrayData result; + + result.reserve(input->GetLength()); + + ObjectLock olock(input); + + for (const Value& value : input) { + result.emplace_back(Deserialize(value, safe_mode, attributeTypes)); + } + + return new Array(std::move(result)); +} + +static Dictionary::Ptr DeserializeDictionary(const Dictionary::Ptr& input, bool safe_mode, int attributeTypes) +{ + DictionaryData result; + + result.reserve(input->GetLength()); + + ObjectLock olock(input); + + for (const Dictionary::Pair& kv : input) { + result.emplace_back(kv.first, Deserialize(kv.second, safe_mode, attributeTypes)); + } + + return new Dictionary(std::move(result)); +} + +static Object::Ptr DeserializeObject(const Object::Ptr& object, const Dictionary::Ptr& input, bool safe_mode, int attributeTypes) +{ + if (!object && safe_mode) + BOOST_THROW_EXCEPTION(std::runtime_error("Tried to instantiate object while safe mode is enabled.")); + + Type::Ptr type; + + if (object) + type = object->GetReflectionType(); + else + type = Type::GetByName(input->Get("type")); + + if (!type) + return object; + + Object::Ptr instance; + + if (object) + instance = object; + else + instance = type->Instantiate(std::vector<Value>()); + + ObjectLock olock(input); + for (const Dictionary::Pair& kv : input) { + if (kv.first.IsEmpty()) + continue; + + int fid = type->GetFieldId(kv.first); + + if (fid < 0) + continue; + + Field field = type->GetFieldInfo(fid); + + if ((field.Attributes & attributeTypes) == 0) + continue; + + try { + instance->SetField(fid, Deserialize(kv.second, safe_mode, attributeTypes), true); + } catch (const std::exception&) { + instance->SetField(fid, Empty); + } + } + + return instance; +} + +static Value SerializeInternal(const Value& value, int attributeTypes, SerializeStack& stack, bool dryRun) +{ + if (!value.IsObject()) + return dryRun ? Empty : value; + + Object::Ptr input = value; + + Array::Ptr array = dynamic_pointer_cast<Array>(input); + + if (array) + return SerializeArray(array, attributeTypes, stack, dryRun); + + Dictionary::Ptr dict = dynamic_pointer_cast<Dictionary>(input); + + if (dict) + return SerializeDictionary(dict, attributeTypes, stack, dryRun); + + Namespace::Ptr ns = dynamic_pointer_cast<Namespace>(input); + + if (ns) + return SerializeNamespace(ns, attributeTypes, stack, dryRun); + + return SerializeObject(input, attributeTypes, stack, dryRun); +} + +void icinga::AssertNoCircularReferences(const Value& value) +{ + SerializeStack stack; + SerializeInternal(value, FAConfig, stack, true); +} + +Value icinga::Serialize(const Value& value, int attributeTypes) +{ + SerializeStack stack; + return SerializeInternal(value, attributeTypes, stack, false); +} + +Value icinga::Deserialize(const Value& value, bool safe_mode, int attributeTypes) +{ + return Deserialize(nullptr, value, safe_mode, attributeTypes); +} + +Value icinga::Deserialize(const Object::Ptr& object, const Value& value, bool safe_mode, int attributeTypes) +{ + if (!value.IsObject()) + return value; + + Object::Ptr input = value; + + Array::Ptr array = dynamic_pointer_cast<Array>(input); + + if (array) + return DeserializeArray(array, safe_mode, attributeTypes); + + Dictionary::Ptr dict = dynamic_pointer_cast<Dictionary>(input); + + ASSERT(dict); + + if ((safe_mode && !object) || !dict->Contains("type")) + return DeserializeDictionary(dict, safe_mode, attributeTypes); + else + return DeserializeObject(object, dict, safe_mode, attributeTypes); +} diff --git a/lib/base/serializer.hpp b/lib/base/serializer.hpp new file mode 100644 index 0000000..f055b3b --- /dev/null +++ b/lib/base/serializer.hpp @@ -0,0 +1,34 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#ifndef SERIALIZER_H +#define SERIALIZER_H + +#include "base/i2-base.hpp" +#include "base/type.hpp" +#include "base/value.hpp" +#include "base/exception.hpp" + +namespace icinga +{ + +class CircularReferenceError : virtual public user_error +{ +public: + CircularReferenceError(String message, std::vector<String> path); + + const char *what(void) const throw() final; + std::vector<String> GetPath() const; + +private: + String m_Message; + std::vector<String> m_Path; +}; + +void AssertNoCircularReferences(const Value& value); +Value Serialize(const Value& value, int attributeTypes = FAState); +Value Deserialize(const Value& value, bool safe_mode = false, int attributeTypes = FAState); +Value Deserialize(const Object::Ptr& object, const Value& value, bool safe_mode = false, int attributeTypes = FAState); + +} + +#endif /* SERIALIZER_H */ diff --git a/lib/base/shared-memory.hpp b/lib/base/shared-memory.hpp new file mode 100644 index 0000000..dd350c8 --- /dev/null +++ b/lib/base/shared-memory.hpp @@ -0,0 +1,45 @@ +/* Icinga 2 | (c) 2023 Icinga GmbH | GPLv2+ */ + +#pragma once + +#include <boost/interprocess/anonymous_shared_memory.hpp> +#include <utility> + +namespace icinga +{ + +/** + * Type-safe memory shared across fork(2). + * + * @ingroup base + */ +template<class T> +class SharedMemory +{ +public: + template<class... Args> + SharedMemory(Args&&... args) : m_Memory(boost::interprocess::anonymous_shared_memory(sizeof(T))) + { + new(GetAddress()) T(std::forward<Args>(args)...); + } + + SharedMemory(const SharedMemory&) = delete; + SharedMemory(SharedMemory&&) = delete; + SharedMemory& operator=(const SharedMemory&) = delete; + SharedMemory& operator=(SharedMemory&&) = delete; + + inline T& Get() const + { + return *GetAddress(); + } + +private: + inline T* GetAddress() const + { + return (T*)m_Memory.get_address(); + } + + boost::interprocess::mapped_region m_Memory; +}; + +} diff --git a/lib/base/shared-object.hpp b/lib/base/shared-object.hpp new file mode 100644 index 0000000..58636dc --- /dev/null +++ b/lib/base/shared-object.hpp @@ -0,0 +1,73 @@ +/* Icinga 2 | (c) 2019 Icinga GmbH | GPLv2+ */ + +#ifndef SHARED_OBJECT_H +#define SHARED_OBJECT_H + +#include "base/atomic.hpp" +#include "base/object.hpp" +#include <cstdint> + +namespace icinga +{ + +class SharedObject; + +inline void intrusive_ptr_add_ref(SharedObject *object); +inline void intrusive_ptr_release(SharedObject *object); + +/** + * Seamless and polymorphistic base for any class to create shared pointers of. + * Saves a memory allocation compared to std::shared_ptr. + * + * @ingroup base + */ +class SharedObject +{ + friend void intrusive_ptr_add_ref(SharedObject *object); + friend void intrusive_ptr_release(SharedObject *object); + +protected: + inline SharedObject() : m_References(0) + { + } + + inline SharedObject(const SharedObject&) : SharedObject() + { + } + + inline SharedObject(SharedObject&&) : SharedObject() + { + } + + inline SharedObject& operator=(const SharedObject&) + { + return *this; + } + + inline SharedObject& operator=(SharedObject&&) + { + return *this; + } + + inline virtual + ~SharedObject() = default; + +private: + Atomic<uint_fast64_t> m_References; +}; + +inline void intrusive_ptr_add_ref(SharedObject *object) +{ + object->m_References.fetch_add(1); +} + +inline void intrusive_ptr_release(SharedObject *object) +{ + if (object->m_References.fetch_sub(1) == 1u) { + delete object; + } +} + +} + +#endif /* SHARED_OBJECT_H */ diff --git a/lib/base/shared.hpp b/lib/base/shared.hpp new file mode 100644 index 0000000..63b35cb --- /dev/null +++ b/lib/base/shared.hpp @@ -0,0 +1,101 @@ +/* Icinga 2 | (c) 2019 Icinga GmbH | GPLv2+ */ + +#ifndef SHARED_H +#define SHARED_H + +#include "base/atomic.hpp" +#include <boost/smart_ptr/intrusive_ptr.hpp> +#include <cstdint> +#include <utility> + +namespace icinga +{ + +template<class T> +class Shared; + +template<class T> +inline void intrusive_ptr_add_ref(Shared<T> *object) +{ + object->m_References.fetch_add(1); +} + +template<class T> +inline void intrusive_ptr_release(Shared<T> *object) +{ + if (object->m_References.fetch_sub(1) == 1u) { + delete object; + } +} + +/** + * Seamless wrapper for any class to create shared pointers of. + * Saves a memory allocation compared to std::shared_ptr. + * + * @ingroup base + */ +template<class T> +class Shared : public T +{ + friend void intrusive_ptr_add_ref<>(Shared<T> *object); + friend void intrusive_ptr_release<>(Shared<T> *object); + +public: + typedef boost::intrusive_ptr<Shared> Ptr; + + /** + * Like std::make_shared, but for this class. + * + * @param args Constructor arguments + * + * @return Ptr + */ + template<class... Args> + static inline + Ptr Make(Args&&... args) + { + return new Shared(std::forward<Args>(args)...); + } + + inline Shared(const Shared& origin) : Shared((const T&)origin) + { + } + + inline Shared(Shared&& origin) : Shared((T&&)origin) + { + } + + template<class... Args> + inline Shared(Args&&... args) : T(std::forward<Args>(args)...), m_References(0) + { + } + + inline Shared& operator=(const Shared& rhs) + { + return operator=((const T&)rhs); + } + + inline Shared& operator=(Shared&& rhs) + { + return operator=((T&&)rhs); + } + + inline Shared& operator=(const T& rhs) + { + T::operator=(rhs); + return *this; + } + + inline Shared& operator=(T&& rhs) + { + T::operator=(std::move(rhs)); + return *this; + } + +private: + Atomic<uint_fast64_t> m_References; +}; + +} + +#endif /* SHARED_H */ diff --git a/lib/base/singleton.hpp b/lib/base/singleton.hpp new file mode 100644 index 0000000..77511c0 --- /dev/null +++ b/lib/base/singleton.hpp @@ -0,0 +1,29 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#ifndef SINGLETON_H +#define SINGLETON_H + +#include "base/i2-base.hpp" + +namespace icinga +{ + +/** + * A singleton. + * + * @ingroup base + */ +template<typename T> +class Singleton +{ +public: + static T *GetInstance() + { + static T instance; + return &instance; + } +}; + +} + +#endif /* SINGLETON_H */ diff --git a/lib/base/socket.cpp b/lib/base/socket.cpp new file mode 100644 index 0000000..4c967de --- /dev/null +++ b/lib/base/socket.cpp @@ -0,0 +1,430 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#include "base/socket.hpp" +#include "base/objectlock.hpp" +#include "base/utility.hpp" +#include "base/exception.hpp" +#include "base/logger.hpp" +#include <sstream> +#include <iostream> +#include <boost/exception/errinfo_api_function.hpp> +#include <boost/exception/errinfo_errno.hpp> +#include <socketpair.h> + +#ifndef _WIN32 +# include <poll.h> +#endif /* _WIN32 */ + +using namespace icinga; + +/** + * Constructor for the Socket class. + */ +Socket::Socket(SOCKET fd) +{ + SetFD(fd); +} + +/** + * Destructor for the Socket class. + */ +Socket::~Socket() +{ + Close(); +} + +/** + * Sets the file descriptor for this socket object. + * + * @param fd The file descriptor. + */ +void Socket::SetFD(SOCKET fd) +{ + if (fd != INVALID_SOCKET) { +#ifndef _WIN32 + /* mark the socket as close-on-exec */ + Utility::SetCloExec(fd); +#endif /* _WIN32 */ + } + + ObjectLock olock(this); + m_FD = fd; +} + +/** + * Retrieves the file descriptor for this socket object. + * + * @returns The file descriptor. + */ +SOCKET Socket::GetFD() const +{ + ObjectLock olock(this); + + return m_FD; +} + +/** + * Closes the socket. + */ +void Socket::Close() +{ + ObjectLock olock(this); + + if (m_FD != INVALID_SOCKET) { + closesocket(m_FD); + m_FD = INVALID_SOCKET; + } +} + +/** + * Retrieves the last error that occurred for the socket. + * + * @returns An error code. + */ +int Socket::GetError() const +{ + int opt; + socklen_t optlen = sizeof(opt); + + int rc = getsockopt(GetFD(), SOL_SOCKET, SO_ERROR, (char *)&opt, &optlen); + + if (rc >= 0) + return opt; + + return 0; +} + +/** + * Formats a sockaddr in a human-readable way. + * + * @returns A pair of host and service. + */ +String Socket::GetHumanReadableAddress(const std::pair<String, String>& socketDetails) +{ + std::ostringstream s; + s << "[" << socketDetails.first << "]:" << socketDetails.second; + return s.str(); +} + +/** + * Returns host and service as pair. + * + * @returns A pair with host and service. + */ +std::pair<String, String> Socket::GetDetailsFromSockaddr(sockaddr *address, socklen_t len) +{ + char host[NI_MAXHOST]; + char service[NI_MAXSERV]; + + if (getnameinfo(address, len, host, sizeof(host), service, + sizeof(service), NI_NUMERICHOST | NI_NUMERICSERV) < 0) { +#ifndef _WIN32 + Log(LogCritical, "Socket") + << "getnameinfo() failed with error code " << errno << ", \"" << Utility::FormatErrorNumber(errno) << "\""; + + BOOST_THROW_EXCEPTION(socket_error() + << boost::errinfo_api_function("getnameinfo") + << boost::errinfo_errno(errno)); +#else /* _WIN32 */ + Log(LogCritical, "Socket") + << "getnameinfo() failed with error code " << WSAGetLastError() << ", \"" << Utility::FormatErrorNumber(WSAGetLastError()) << "\""; + + BOOST_THROW_EXCEPTION(socket_error() + << boost::errinfo_api_function("getnameinfo") + << errinfo_win32_error(WSAGetLastError())); +#endif /* _WIN32 */ + } + + return std::make_pair(host, service); +} + +/** + * Returns a pair describing the local host and service of the socket. + * + * @returns A pair describing the local host and service. + */ +std::pair<String, String> Socket::GetClientAddressDetails() +{ + std::unique_lock<std::mutex> lock(m_SocketMutex); + + sockaddr_storage sin; + socklen_t len = sizeof(sin); + + if (getsockname(GetFD(), (sockaddr *)&sin, &len) < 0) { +#ifndef _WIN32 + Log(LogCritical, "Socket") + << "getsockname() failed with error code " << errno << ", \"" << Utility::FormatErrorNumber(errno) << "\""; + + BOOST_THROW_EXCEPTION(socket_error() + << boost::errinfo_api_function("getsockname") + << boost::errinfo_errno(errno)); +#else /* _WIN32 */ + Log(LogCritical, "Socket") + << "getsockname() failed with error code " << WSAGetLastError() << ", \"" << Utility::FormatErrorNumber(WSAGetLastError()) << "\""; + + BOOST_THROW_EXCEPTION(socket_error() + << boost::errinfo_api_function("getsockname") + << errinfo_win32_error(WSAGetLastError())); +#endif /* _WIN32 */ + } + + std::pair<String, String> details; + try { + details = GetDetailsFromSockaddr((sockaddr *)&sin, len); + } catch (const std::exception&) { + /* already logged */ + } + + return details; +} + +/** + * Returns a String describing the local address of the socket. + * + * @returns A String describing the local address. + */ +String Socket::GetClientAddress() +{ + return GetHumanReadableAddress(GetClientAddressDetails()); +} + +/** + * Returns a pair describing the peer host and service of the socket. + * + * @returns A pair describing the peer host and service. + */ +std::pair<String, String> Socket::GetPeerAddressDetails() +{ + std::unique_lock<std::mutex> lock(m_SocketMutex); + + sockaddr_storage sin; + socklen_t len = sizeof(sin); + + if (getpeername(GetFD(), (sockaddr *)&sin, &len) < 0) { +#ifndef _WIN32 + Log(LogCritical, "Socket") + << "getpeername() failed with error code " << errno << ", \"" << Utility::FormatErrorNumber(errno) << "\""; + + BOOST_THROW_EXCEPTION(socket_error() + << boost::errinfo_api_function("getpeername") + << boost::errinfo_errno(errno)); +#else /* _WIN32 */ + Log(LogCritical, "Socket") + << "getpeername() failed with error code " << WSAGetLastError() << ", \"" << Utility::FormatErrorNumber(WSAGetLastError()) << "\""; + + BOOST_THROW_EXCEPTION(socket_error() + << boost::errinfo_api_function("getpeername") + << errinfo_win32_error(WSAGetLastError())); +#endif /* _WIN32 */ + } + + std::pair<String, String> details; + try { + details = GetDetailsFromSockaddr((sockaddr *)&sin, len); + } catch (const std::exception&) { + /* already logged */ + } + + return details; +} + +/** + * Returns a String describing the peer address of the socket. + * + * @returns A String describing the peer address. + */ +String Socket::GetPeerAddress() +{ + return GetHumanReadableAddress(GetPeerAddressDetails()); +} + +/** + * Starts listening for incoming client connections. + */ +void Socket::Listen() +{ + if (listen(GetFD(), SOMAXCONN) < 0) { +#ifndef _WIN32 + Log(LogCritical, "Socket") + << "listen() failed with error code " << errno << ", \"" << Utility::FormatErrorNumber(errno) << "\""; + + BOOST_THROW_EXCEPTION(socket_error() + << boost::errinfo_api_function("listen") + << boost::errinfo_errno(errno)); +#else /* _WIN32 */ + Log(LogCritical, "Socket") + << "listen() failed with error code " << WSAGetLastError() << ", \"" << Utility::FormatErrorNumber(WSAGetLastError()) << "\""; + + BOOST_THROW_EXCEPTION(socket_error() + << boost::errinfo_api_function("listen") + << errinfo_win32_error(WSAGetLastError())); +#endif /* _WIN32 */ + } +} + +/** + * Sends data for the socket. + */ +size_t Socket::Write(const void *buffer, size_t count) +{ + int rc; + +#ifndef _WIN32 + rc = write(GetFD(), (const char *)buffer, count); +#else /* _WIN32 */ + rc = send(GetFD(), (const char *)buffer, count, 0); +#endif /* _WIN32 */ + + if (rc < 0) { +#ifndef _WIN32 + Log(LogCritical, "Socket") + << "send() failed with error code " << errno << ", \"" << Utility::FormatErrorNumber(errno) << "\""; + + BOOST_THROW_EXCEPTION(socket_error() + << boost::errinfo_api_function("send") + << boost::errinfo_errno(errno)); +#else /* _WIN32 */ + Log(LogCritical, "Socket") + << "send() failed with error code " << WSAGetLastError() << ", \"" << Utility::FormatErrorNumber(WSAGetLastError()) << "\""; + + BOOST_THROW_EXCEPTION(socket_error() + << boost::errinfo_api_function("send") + << errinfo_win32_error(WSAGetLastError())); +#endif /* _WIN32 */ + } + + return rc; +} + +/** + * Processes data that can be written for this socket. + */ +size_t Socket::Read(void *buffer, size_t count) +{ + int rc; + +#ifndef _WIN32 + rc = read(GetFD(), (char *)buffer, count); +#else /* _WIN32 */ + rc = recv(GetFD(), (char *)buffer, count, 0); +#endif /* _WIN32 */ + + if (rc < 0) { +#ifndef _WIN32 + Log(LogCritical, "Socket") + << "recv() failed with error code " << errno << ", \"" << Utility::FormatErrorNumber(errno) << "\""; + + BOOST_THROW_EXCEPTION(socket_error() + << boost::errinfo_api_function("recv") + << boost::errinfo_errno(errno)); +#else /* _WIN32 */ + Log(LogCritical, "Socket") + << "recv() failed with error code " << WSAGetLastError() << ", \"" << Utility::FormatErrorNumber(WSAGetLastError()) << "\""; + + BOOST_THROW_EXCEPTION(socket_error() + << boost::errinfo_api_function("recv") + << errinfo_win32_error(WSAGetLastError())); +#endif /* _WIN32 */ + } + + return rc; +} + +/** + * Accepts a new client and creates a new client object for it. + */ +Socket::Ptr Socket::Accept() +{ + sockaddr_storage addr; + socklen_t addrlen = sizeof(addr); + + SOCKET fd = accept(GetFD(), (sockaddr *)&addr, &addrlen); + +#ifndef _WIN32 + if (fd < 0) { + Log(LogCritical, "Socket") + << "accept() failed with error code " << errno << ", \"" << Utility::FormatErrorNumber(errno) << "\""; + + BOOST_THROW_EXCEPTION(socket_error() + << boost::errinfo_api_function("accept") + << boost::errinfo_errno(errno)); + } +#else /* _WIN32 */ + if (fd == INVALID_SOCKET) { + Log(LogCritical, "Socket") + << "accept() failed with error code " << WSAGetLastError() << ", \"" << Utility::FormatErrorNumber(WSAGetLastError()) << "\""; + + BOOST_THROW_EXCEPTION(socket_error() + << boost::errinfo_api_function("accept") + << errinfo_win32_error(WSAGetLastError())); + } +#endif /* _WIN32 */ + + return new Socket(fd); +} + +bool Socket::Poll(bool read, bool write, struct timeval *timeout) +{ + int rc; + +#ifdef _WIN32 + fd_set readfds, writefds, exceptfds; + + FD_ZERO(&readfds); + if (read) + FD_SET(GetFD(), &readfds); + + FD_ZERO(&writefds); + if (write) + FD_SET(GetFD(), &writefds); + + FD_ZERO(&exceptfds); + FD_SET(GetFD(), &exceptfds); + + rc = select(GetFD() + 1, &readfds, &writefds, &exceptfds, timeout); + + if (rc < 0) { + Log(LogCritical, "Socket") + << "select() failed with error code " << WSAGetLastError() << ", \"" << Utility::FormatErrorNumber(WSAGetLastError()) << "\""; + + BOOST_THROW_EXCEPTION(socket_error() + << boost::errinfo_api_function("select") + << errinfo_win32_error(WSAGetLastError())); + } +#else /* _WIN32 */ + pollfd pfd; + pfd.fd = GetFD(); + pfd.events = (read ? POLLIN : 0) | (write ? POLLOUT : 0); + pfd.revents = 0; + + rc = poll(&pfd, 1, timeout ? (timeout->tv_sec + 1000 + timeout->tv_usec / 1000) : -1); + + if (rc < 0) { + Log(LogCritical, "Socket") + << "poll() failed with error code " << errno << ", \"" << Utility::FormatErrorNumber(errno) << "\""; + + BOOST_THROW_EXCEPTION(socket_error() + << boost::errinfo_api_function("poll") + << boost::errinfo_errno(errno)); + } +#endif /* _WIN32 */ + + return (rc != 0); +} + +void Socket::MakeNonBlocking() +{ +#ifdef _WIN32 + Utility::SetNonBlockingSocket(GetFD()); +#else /* _WIN32 */ + Utility::SetNonBlocking(GetFD()); +#endif /* _WIN32 */ +} + +void Socket::SocketPair(SOCKET s[2]) +{ + if (dumb_socketpair(s, 0) < 0) + BOOST_THROW_EXCEPTION(socket_error() + << boost::errinfo_api_function("socketpair") + << boost::errinfo_errno(errno)); +} diff --git a/lib/base/socket.hpp b/lib/base/socket.hpp new file mode 100644 index 0000000..f7acf7f --- /dev/null +++ b/lib/base/socket.hpp @@ -0,0 +1,66 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#ifndef SOCKET_H +#define SOCKET_H + +#include "base/i2-base.hpp" +#include "base/object.hpp" +#include <mutex> + +namespace icinga +{ + +/** + * Base class for connection-oriented sockets. + * + * @ingroup base + */ +class Socket : public Object +{ +public: + DECLARE_PTR_TYPEDEFS(Socket); + + Socket() = default; + Socket(SOCKET fd); + ~Socket() override; + + SOCKET GetFD() const; + + void Close(); + + std::pair<String, String> GetClientAddressDetails(); + String GetClientAddress(); + std::pair<String, String> GetPeerAddressDetails(); + String GetPeerAddress(); + + size_t Read(void *buffer, size_t size); + size_t Write(const void *buffer, size_t size); + + void Listen(); + Socket::Ptr Accept(); + + bool Poll(bool read, bool write, struct timeval *timeout = nullptr); + + void MakeNonBlocking(); + + static void SocketPair(SOCKET s[2]); + +protected: + void SetFD(SOCKET fd); + + int GetError() const; + + mutable std::mutex m_SocketMutex; + +private: + SOCKET m_FD{INVALID_SOCKET}; /**< The socket descriptor. */ + + static std::pair<String, String> GetDetailsFromSockaddr(sockaddr *address, socklen_t len); + static String GetHumanReadableAddress(const std::pair<String, String>& socketDetails); +}; + +class socket_error : virtual public std::exception, virtual public boost::exception { }; + +} + +#endif /* SOCKET_H */ diff --git a/lib/base/stacktrace.cpp b/lib/base/stacktrace.cpp new file mode 100644 index 0000000..e3f15ce --- /dev/null +++ b/lib/base/stacktrace.cpp @@ -0,0 +1,43 @@ +/* Icinga 2 | (c) 2020 Icinga GmbH | GPLv2+ */ + +#include <base/i2-base.hpp> +#include "base/stacktrace.hpp" +#include <iostream> +#include <iomanip> +#include <vector> + +#ifdef HAVE_BACKTRACE_SYMBOLS +# include <execinfo.h> +#endif /* HAVE_BACKTRACE_SYMBOLS */ + +using namespace icinga; + +std::ostream &icinga::operator<<(std::ostream &os, const StackTraceFormatter &f) +{ + /* In most cases, this operator<< just relies on the operator<< for the `boost::stacktrace::stacktrace` wrapped in + * the `StackTraceFormatter`. But as this operator turned out to not work properly on some platforms, there is a + * fallback implementation that can be enabled using the `-DICINGA2_STACKTRACE_USE_BACKTRACE_SYMBOLS` flag at + * compile time. This will then switch to `backtrace_symbols()` from `<execinfo.h>` instead of the implementation + * provided by Boost. + */ + + const boost::stacktrace::stacktrace &stack = f.m_Stack; + +#ifdef ICINGA2_STACKTRACE_USE_BACKTRACE_SYMBOLS + std::vector<void *> addrs; + addrs.reserve(stack.size()); + std::transform(stack.begin(), stack.end(), std::back_inserter(addrs), [](const boost::stacktrace::frame &f) { + return const_cast<void *>(f.address()); + }); + + char **symbols = backtrace_symbols(addrs.data(), addrs.size()); + for (size_t i = 0; i < addrs.size(); i++) { + os << std::setw(2) << i << "# " << symbols[i] << std::endl; + } + std::free(symbols); +#else /* ICINGA2_STACKTRACE_USE_BACKTRACE_SYMBOLS */ + os << stack; +#endif /* ICINGA2_STACKTRACE_USE_BACKTRACE_SYMBOLS */ + + return os; +} diff --git a/lib/base/stacktrace.hpp b/lib/base/stacktrace.hpp new file mode 100644 index 0000000..b4a9765 --- /dev/null +++ b/lib/base/stacktrace.hpp @@ -0,0 +1,31 @@ +/* Icinga 2 | (c) 2020 Icinga GmbH | GPLv2+ */ + +#ifndef STACKTRACE_H +#define STACKTRACE_H + +#include <boost/stacktrace.hpp> + +namespace icinga +{ + +/** + * Formatter for `boost::stacktrace::stacktrace` objects + * + * This class wraps `boost::stacktrace::stacktrace` objects and provides an operator<< + * for printing them to an `std::ostream` in a custom format. + */ +class StackTraceFormatter { +public: + StackTraceFormatter(const boost::stacktrace::stacktrace &stack) : m_Stack(stack) {} + +private: + const boost::stacktrace::stacktrace &m_Stack; + + friend std::ostream &operator<<(std::ostream &os, const StackTraceFormatter &f); +}; + +std::ostream& operator<<(std::ostream& os, const StackTraceFormatter &f); + +} + +#endif /* STACKTRACE_H */ diff --git a/lib/base/statsfunction.hpp b/lib/base/statsfunction.hpp new file mode 100644 index 0000000..ecac33c --- /dev/null +++ b/lib/base/statsfunction.hpp @@ -0,0 +1,17 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#ifndef STATSFUNCTION_H +#define STATSFUNCTION_H + +#include "base/i2-base.hpp" +#include "base/function.hpp" + +namespace icinga +{ + +#define REGISTER_STATSFUNCTION(name, callback) \ + REGISTER_FUNCTION(StatsFunctions, name, callback, "status:perfdata") + +} + +#endif /* STATSFUNCTION_H */ diff --git a/lib/base/stdiostream.cpp b/lib/base/stdiostream.cpp new file mode 100644 index 0000000..449036f --- /dev/null +++ b/lib/base/stdiostream.cpp @@ -0,0 +1,57 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#include "base/stdiostream.hpp" +#include "base/objectlock.hpp" + +using namespace icinga; + +/** + * Constructor for the StdioStream class. + * + * @param innerStream The inner stream. + * @param ownsStream Whether the new object owns the inner stream. If true + * the stream's destructor deletes the inner stream. + */ +StdioStream::StdioStream(std::iostream *innerStream, bool ownsStream) + : m_InnerStream(innerStream), m_OwnsStream(ownsStream) +{ } + +StdioStream::~StdioStream() +{ + Close(); +} + +size_t StdioStream::Read(void *buffer, size_t size, bool allow_partial) +{ + ObjectLock olock(this); + + m_InnerStream->read(static_cast<char *>(buffer), size); + return m_InnerStream->gcount(); +} + +void StdioStream::Write(const void *buffer, size_t size) +{ + ObjectLock olock(this); + + m_InnerStream->write(static_cast<const char *>(buffer), size); +} + +void StdioStream::Close() +{ + Stream::Close(); + + if (m_OwnsStream) { + delete m_InnerStream; + m_OwnsStream = false; + } +} + +bool StdioStream::IsDataAvailable() const +{ + return !IsEof(); +} + +bool StdioStream::IsEof() const +{ + return !m_InnerStream->good(); +} diff --git a/lib/base/stdiostream.hpp b/lib/base/stdiostream.hpp new file mode 100644 index 0000000..b305c7f --- /dev/null +++ b/lib/base/stdiostream.hpp @@ -0,0 +1,36 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#ifndef STDIOSTREAM_H +#define STDIOSTREAM_H + +#include "base/i2-base.hpp" +#include "base/stream.hpp" +#include <iosfwd> +#include <iostream> + +namespace icinga { + +class StdioStream final : public Stream +{ +public: + DECLARE_PTR_TYPEDEFS(StdioStream); + + StdioStream(std::iostream *innerStream, bool ownsStream); + ~StdioStream() override; + + size_t Read(void *buffer, size_t size, bool allow_partial = false) override; + void Write(const void *buffer, size_t size) override; + + void Close() override; + + bool IsDataAvailable() const override; + bool IsEof() const override; + +private: + std::iostream *m_InnerStream; + bool m_OwnsStream; +}; + +} + +#endif /* STDIOSTREAM_H */ diff --git a/lib/base/stream.cpp b/lib/base/stream.cpp new file mode 100644 index 0000000..e558385 --- /dev/null +++ b/lib/base/stream.cpp @@ -0,0 +1,149 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#include "base/stream.hpp" +#include <boost/algorithm/string/trim.hpp> +#include <chrono> + +using namespace icinga; + +void Stream::RegisterDataHandler(const std::function<void(const Stream::Ptr&)>& handler) +{ + if (SupportsWaiting()) + OnDataAvailable.connect(handler); + else + BOOST_THROW_EXCEPTION(std::runtime_error("Stream does not support waiting.")); +} + +bool Stream::SupportsWaiting() const +{ + return false; +} + +bool Stream::IsDataAvailable() const +{ + return false; +} + +void Stream::Shutdown() +{ + BOOST_THROW_EXCEPTION(std::runtime_error("Stream does not support Shutdown().")); +} + +size_t Stream::Peek(void *buffer, size_t count, bool allow_partial) +{ + BOOST_THROW_EXCEPTION(std::runtime_error("Stream does not support Peek().")); +} + +void Stream::SignalDataAvailable() +{ + OnDataAvailable(this); + + { + std::unique_lock<std::mutex> lock(m_Mutex); + m_CV.notify_all(); + } +} + +bool Stream::WaitForData() +{ + if (!SupportsWaiting()) + BOOST_THROW_EXCEPTION(std::runtime_error("Stream does not support waiting.")); + + std::unique_lock<std::mutex> lock(m_Mutex); + + while (!IsDataAvailable() && !IsEof()) + m_CV.wait(lock); + + return IsDataAvailable() || IsEof(); +} + +bool Stream::WaitForData(int timeout) +{ + namespace ch = std::chrono; + + if (!SupportsWaiting()) + BOOST_THROW_EXCEPTION(std::runtime_error("Stream does not support waiting.")); + + if (timeout < 0) + BOOST_THROW_EXCEPTION(std::runtime_error("Timeout can't be negative")); + + std::unique_lock<std::mutex> lock(m_Mutex); + + return m_CV.wait_for(lock, ch::duration<int>(timeout), [this]() { return IsDataAvailable() || IsEof(); }); +} + +void Stream::Close() +{ + OnDataAvailable.disconnect_all_slots(); + + /* Force signals2 to remove the slots, see https://stackoverflow.com/questions/2049291/force-deletion-of-slot-in-boostsignals2 + * for details. */ + OnDataAvailable.connect([](const Stream::Ptr&) { }); +} + +StreamReadStatus Stream::ReadLine(String *line, StreamReadContext& context, bool may_wait) +{ + if (context.Eof) + return StatusEof; + + if (context.MustRead) { + if (!context.FillFromStream(this, may_wait)) { + context.Eof = true; + + *line = String(context.Buffer, &(context.Buffer[context.Size])); + boost::algorithm::trim_right(*line); + + return StatusNewItem; + } + } + + for (size_t i = 0; i < context.Size; i++) { + if (context.Buffer[i] == '\n') { + *line = String(context.Buffer, context.Buffer + i); + boost::algorithm::trim_right(*line); + + context.DropData(i + 1u); + + context.MustRead = !context.Size; + return StatusNewItem; + } + } + + context.MustRead = true; + return StatusNeedData; +} + +bool StreamReadContext::FillFromStream(const Stream::Ptr& stream, bool may_wait) +{ + if (may_wait && stream->SupportsWaiting()) + stream->WaitForData(); + + size_t count = 0; + + do { + Buffer = (char *)realloc(Buffer, Size + 4096); + + if (!Buffer) + throw std::bad_alloc(); + + if (stream->IsEof()) + break; + + size_t rc = stream->Read(Buffer + Size, 4096, true); + + Size += rc; + count += rc; + } while (count < 64 * 1024 && stream->IsDataAvailable()); + + if (count == 0 && stream->IsEof()) + return false; + else + return true; +} + +void StreamReadContext::DropData(size_t count) +{ + ASSERT(count <= Size); + memmove(Buffer, Buffer + count, Size - count); + Size -= count; +} diff --git a/lib/base/stream.hpp b/lib/base/stream.hpp new file mode 100644 index 0000000..6bc8fed --- /dev/null +++ b/lib/base/stream.hpp @@ -0,0 +1,133 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#ifndef STREAM_H +#define STREAM_H + +#include "base/i2-base.hpp" +#include "base/object.hpp" +#include <boost/signals2.hpp> +#include <condition_variable> +#include <mutex> + +namespace icinga +{ + +class String; +class Stream; + +enum ConnectionRole +{ + RoleClient, + RoleServer +}; + +struct StreamReadContext +{ + ~StreamReadContext() + { + free(Buffer); + } + + bool FillFromStream(const intrusive_ptr<Stream>& stream, bool may_wait); + void DropData(size_t count); + + char *Buffer{nullptr}; + size_t Size{0}; + bool MustRead{true}; + bool Eof{false}; +}; + +enum StreamReadStatus +{ + StatusNewItem, + StatusNeedData, + StatusEof +}; + +/** + * A stream. + * + * @ingroup base + */ +class Stream : public Object +{ +public: + DECLARE_PTR_TYPEDEFS(Stream); + + /** + * Reads data from the stream without removing it from the stream buffer. + * + * @param buffer The buffer where data should be stored. May be nullptr if you're + * not actually interested in the data. + * @param count The number of bytes to read from the queue. + * @param allow_partial Whether to allow partial reads. + * @returns The number of bytes actually read. + */ + virtual size_t Peek(void *buffer, size_t count, bool allow_partial = false); + + /** + * Reads data from the stream. + * + * @param buffer The buffer where data should be stored. May be nullptr if you're + * not actually interested in the data. + * @param count The number of bytes to read from the queue. + * @param allow_partial Whether to allow partial reads. + * @returns The number of bytes actually read. + */ + virtual size_t Read(void *buffer, size_t count, bool allow_partial = false) = 0; + + /** + * Writes data to the stream. + * + * @param buffer The data that is to be written. + * @param count The number of bytes to write. + * @returns The number of bytes written + */ + virtual void Write(const void *buffer, size_t count) = 0; + + /** + * Causes the stream to be closed (via Close()) once all pending data has been + * written. + */ + virtual void Shutdown(); + + /** + * Closes the stream and releases resources. + */ + virtual void Close(); + + /** + * Checks whether we've reached the end-of-file condition. + * + * @returns true if EOF. + */ + virtual bool IsEof() const = 0; + + /** + * Waits until data can be read from the stream. + * Optionally with a timeout. + */ + bool WaitForData(); + bool WaitForData(int timeout); + + virtual bool SupportsWaiting() const; + + virtual bool IsDataAvailable() const; + + void RegisterDataHandler(const std::function<void(const Stream::Ptr&)>& handler); + + StreamReadStatus ReadLine(String *line, StreamReadContext& context, bool may_wait = false); + +protected: + void SignalDataAvailable(); + +private: + boost::signals2::signal<void(const Stream::Ptr&)> OnDataAvailable; + + std::mutex m_Mutex; + std::condition_variable m_CV; +}; + +} + +#endif /* STREAM_H */ diff --git a/lib/base/streamlogger.cpp b/lib/base/streamlogger.cpp new file mode 100644 index 0000000..162b9c3 --- /dev/null +++ b/lib/base/streamlogger.cpp @@ -0,0 +1,119 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#include "base/streamlogger.hpp" +#include "base/streamlogger-ti.cpp" +#include "base/utility.hpp" +#include "base/objectlock.hpp" +#include "base/console.hpp" +#include <iostream> + +using namespace icinga; + +REGISTER_TYPE(StreamLogger); + +std::mutex StreamLogger::m_Mutex; + +void StreamLogger::Stop(bool runtimeRemoved) +{ + ObjectImpl<StreamLogger>::Stop(runtimeRemoved); + + // make sure we flush the log data on shutdown, even if we don't call the destructor + if (m_Stream) + m_Stream->flush(); +} + +/** + * Destructor for the StreamLogger class. + */ +StreamLogger::~StreamLogger() +{ + if (m_FlushLogTimer) + m_FlushLogTimer->Stop(true); + + if (m_Stream && m_OwnsStream) + delete m_Stream; +} + +void StreamLogger::FlushLogTimerHandler() +{ + Flush(); +} + +void StreamLogger::Flush() +{ + ObjectLock oLock (this); + + if (m_Stream) + m_Stream->flush(); +} + +void StreamLogger::BindStream(std::ostream *stream, bool ownsStream) +{ + ObjectLock olock(this); + + if (m_Stream && m_OwnsStream) + delete m_Stream; + + m_Stream = stream; + m_OwnsStream = ownsStream; + + if (!m_FlushLogTimer) { + m_FlushLogTimer = Timer::Create(); + m_FlushLogTimer->SetInterval(1); + m_FlushLogTimer->OnTimerExpired.connect([this](const Timer * const&) { FlushLogTimerHandler(); }); + m_FlushLogTimer->Start(); + } +} + +/** + * Processes a log entry and outputs it to a stream. + * + * @param stream The output stream. + * @param entry The log entry. + */ +void StreamLogger::ProcessLogEntry(std::ostream& stream, const LogEntry& entry) +{ + String timestamp = Utility::FormatDateTime("%Y-%m-%d %H:%M:%S %z", entry.Timestamp); + + std::unique_lock<std::mutex> lock(m_Mutex); + + if (Logger::IsTimestampEnabled()) + stream << "[" << timestamp << "] "; + + int color; + + switch (entry.Severity) { + case LogDebug: + color = Console_ForegroundCyan; + break; + case LogNotice: + color = Console_ForegroundBlue; + break; + case LogInformation: + color = Console_ForegroundGreen; + break; + case LogWarning: + color = Console_ForegroundYellow | Console_Bold; + break; + case LogCritical: + color = Console_ForegroundRed | Console_Bold; + break; + default: + return; + } + + stream << ConsoleColorTag(color); + stream << Logger::SeverityToString(entry.Severity); + stream << ConsoleColorTag(Console_Normal); + stream << "/" << entry.Facility << ": " << entry.Message << "\n"; +} + +/** + * Processes a log entry and outputs it to a stream. + * + * @param entry The log entry. + */ +void StreamLogger::ProcessLogEntry(const LogEntry& entry) +{ + ProcessLogEntry(*m_Stream, entry); +} diff --git a/lib/base/streamlogger.hpp b/lib/base/streamlogger.hpp new file mode 100644 index 0000000..8cbe313 --- /dev/null +++ b/lib/base/streamlogger.hpp @@ -0,0 +1,47 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#ifndef STREAMLOGGER_H +#define STREAMLOGGER_H + +#include "base/i2-base.hpp" +#include "base/streamlogger-ti.hpp" +#include "base/timer.hpp" +#include <iosfwd> + +namespace icinga +{ + +/** + * A logger that logs to an iostream. + * + * @ingroup base + */ +class StreamLogger : public ObjectImpl<StreamLogger> +{ +public: + DECLARE_OBJECT(StreamLogger); + + void Stop(bool runtimeRemoved) override; + ~StreamLogger() override; + + void BindStream(std::ostream *stream, bool ownsStream); + + static void ProcessLogEntry(std::ostream& stream, const LogEntry& entry); + +protected: + void ProcessLogEntry(const LogEntry& entry) final; + void Flush() final; + +private: + static std::mutex m_Mutex; + std::ostream *m_Stream{nullptr}; + bool m_OwnsStream{false}; + + Timer::Ptr m_FlushLogTimer; + + void FlushLogTimerHandler(); +}; + +} + +#endif /* STREAMLOGGER_H */ diff --git a/lib/base/streamlogger.ti b/lib/base/streamlogger.ti new file mode 100644 index 0000000..6dc36e0 --- /dev/null +++ b/lib/base/streamlogger.ti @@ -0,0 +1,14 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#include "base/logger.hpp" + +library base; + +namespace icinga +{ + +abstract class StreamLogger : Logger +{ +}; + +} diff --git a/lib/base/string-script.cpp b/lib/base/string-script.cpp new file mode 100644 index 0000000..323f99c --- /dev/null +++ b/lib/base/string-script.cpp @@ -0,0 +1,138 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#include "base/object.hpp" +#include "base/dictionary.hpp" +#include "base/function.hpp" +#include "base/functionwrapper.hpp" +#include "base/scriptframe.hpp" +#include "base/exception.hpp" +#include <boost/algorithm/string.hpp> + +using namespace icinga; + +static int StringLen() +{ + ScriptFrame *vframe = ScriptFrame::GetCurrentFrame(); + String self = vframe->Self; + return self.GetLength(); +} + +static String StringToString() +{ + ScriptFrame *vframe = ScriptFrame::GetCurrentFrame(); + return vframe->Self; +} + +static String StringSubstr(const std::vector<Value>& args) +{ + ScriptFrame *vframe = ScriptFrame::GetCurrentFrame(); + String self = vframe->Self; + + if (args.empty()) + BOOST_THROW_EXCEPTION(std::invalid_argument("Too few arguments")); + + if (static_cast<double>(args[0]) < 0 || static_cast<double>(args[0]) >= self.GetLength()) + BOOST_THROW_EXCEPTION(std::invalid_argument("String index is out of range")); + + if (args.size() > 1) + return self.SubStr(args[0], args[1]); + else + return self.SubStr(args[0]); +} + +static String StringUpper() +{ + ScriptFrame *vframe = ScriptFrame::GetCurrentFrame(); + String self = vframe->Self; + return boost::to_upper_copy(self); +} + +static String StringLower() +{ + ScriptFrame *vframe = ScriptFrame::GetCurrentFrame(); + String self = vframe->Self; + return boost::to_lower_copy(self); +} + +static Array::Ptr StringSplit(const String& delims) +{ + ScriptFrame *vframe = ScriptFrame::GetCurrentFrame(); + String self = vframe->Self; + std::vector<String> tokens = self.Split(delims.CStr()); + + return Array::FromVector(tokens); +} + +static int StringFind(const std::vector<Value>& args) +{ + ScriptFrame *vframe = ScriptFrame::GetCurrentFrame(); + String self = vframe->Self; + + if (args.empty()) + BOOST_THROW_EXCEPTION(std::invalid_argument("Too few arguments")); + + String::SizeType result; + + if (args.size() > 1) { + if (static_cast<double>(args[1]) < 0) + BOOST_THROW_EXCEPTION(std::invalid_argument("String index is out of range")); + + result = self.Find(args[0], args[1]); + } else + result = self.Find(args[0]); + + if (result == String::NPos) + return -1; + else + return result; +} + +static bool StringContains(const String& str) +{ + ScriptFrame *vframe = ScriptFrame::GetCurrentFrame(); + String self = vframe->Self; + return self.Contains(str); +} + +static Value StringReplace(const String& search, const String& replacement) +{ + ScriptFrame *vframe = ScriptFrame::GetCurrentFrame(); + String self = vframe->Self; + + boost::algorithm::replace_all(self, search, replacement); + return self; +} + +static String StringReverse() +{ + ScriptFrame *vframe = ScriptFrame::GetCurrentFrame(); + String self = vframe->Self; + return self.Reverse(); +} + +static String StringTrim() +{ + ScriptFrame *vframe = ScriptFrame::GetCurrentFrame(); + String self = vframe->Self; + return self.Trim(); +} + +Object::Ptr String::GetPrototype() +{ + static Dictionary::Ptr prototype = new Dictionary({ + { "len", new Function("String#len", StringLen, {}, true) }, + { "to_string", new Function("String#to_string", StringToString, {}, true) }, + { "substr", new Function("String#substr", StringSubstr, { "start", "len" }, true) }, + { "upper", new Function("String#upper", StringUpper, {}, true) }, + { "lower", new Function("String#lower", StringLower, {}, true) }, + { "split", new Function("String#split", StringSplit, { "delims" }, true) }, + { "find", new Function("String#find", StringFind, { "str", "start" }, true) }, + { "contains", new Function("String#contains", StringContains, { "str" }, true) }, + { "replace", new Function("String#replace", StringReplace, { "search", "replacement" }, true) }, + { "reverse", new Function("String#reverse", StringReverse, {}, true) }, + { "trim", new Function("String#trim", StringTrim, {}, true) } + }); + + return prototype; +} + diff --git a/lib/base/string.cpp b/lib/base/string.cpp new file mode 100644 index 0000000..3c440cd --- /dev/null +++ b/lib/base/string.cpp @@ -0,0 +1,468 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#include "base/string.hpp" +#include "base/value.hpp" +#include "base/primitivetype.hpp" +#include "base/dictionary.hpp" +#include <boost/algorithm/string/case_conv.hpp> +#include <boost/algorithm/string/trim.hpp> +#include <boost/algorithm/string/split.hpp> +#include <ostream> + +using namespace icinga; + +template class std::vector<String>; + +REGISTER_BUILTIN_TYPE(String, String::GetPrototype()); + +const String::SizeType String::NPos = std::string::npos; + +String::String(const char *data) + : m_Data(data) +{ } + +String::String(std::string data) + : m_Data(std::move(data)) +{ } + +String::String(String::SizeType n, char c) + : m_Data(n, c) +{ } + +String::String(const String& other) + : m_Data(other) +{ } + +String::String(String&& other) + : m_Data(std::move(other.m_Data)) +{ } + +#ifndef _MSC_VER +String::String(Value&& other) +{ + *this = std::move(other); +} +#endif /* _MSC_VER */ + +String& String::operator=(Value&& other) +{ + if (other.IsString()) + m_Data = std::move(other.Get<String>()); + else + *this = static_cast<String>(other); + + return *this; +} + +String& String::operator+=(const Value& rhs) +{ + m_Data += static_cast<String>(rhs); + return *this; +} + +String& String::operator=(const String& rhs) +{ + m_Data = rhs.m_Data; + return *this; +} + +String& String::operator=(String&& rhs) +{ + m_Data = std::move(rhs.m_Data); + return *this; +} + +String& String::operator=(const std::string& rhs) +{ + m_Data = rhs; + return *this; +} + +String& String::operator=(const char *rhs) +{ + m_Data = rhs; + return *this; +} + +const char& String::operator[](String::SizeType pos) const +{ + return m_Data[pos]; +} + +char& String::operator[](String::SizeType pos) +{ + return m_Data[pos]; +} + +String& String::operator+=(const String& rhs) +{ + m_Data += rhs.m_Data; + return *this; +} + +String& String::operator+=(const char *rhs) +{ + m_Data += rhs; + return *this; +} + +String& String::operator+=(char rhs) +{ + m_Data += rhs; + return *this; +} + +bool String::IsEmpty() const +{ + return m_Data.empty(); +} + +bool String::operator<(const String& rhs) const +{ + return m_Data < rhs.m_Data; +} + +String::operator const std::string&() const +{ + return m_Data; +} + +/** + * Conversion function to boost::beast::string_view. + * + * This allows using String as the value for HTTP headers in boost::beast::http::basic_fields::set. + * + * @return A boost::beast::string_view representing this string. + */ +String::operator boost::beast::string_view() const +{ + return boost::beast::string_view(m_Data); +} + +const char *String::CStr() const +{ + return m_Data.c_str(); +} + +void String::Clear() +{ + m_Data.clear(); +} + +String::SizeType String::GetLength() const +{ + return m_Data.size(); +} + +std::string& String::GetData() +{ + return m_Data; +} + +const std::string& String::GetData() const +{ + return m_Data; +} + +String::SizeType String::Find(const String& str, String::SizeType pos) const +{ + return m_Data.find(str, pos); +} + +String::SizeType String::RFind(const String& str, String::SizeType pos) const +{ + return m_Data.rfind(str, pos); +} + +String::SizeType String::FindFirstOf(const char *s, String::SizeType pos) const +{ + return m_Data.find_first_of(s, pos); +} + +String::SizeType String::FindFirstOf(char ch, String::SizeType pos) const +{ + return m_Data.find_first_of(ch, pos); +} + +String::SizeType String::FindFirstNotOf(const char *s, String::SizeType pos) const +{ + return m_Data.find_first_not_of(s, pos); +} + +String::SizeType String::FindFirstNotOf(char ch, String::SizeType pos) const +{ + return m_Data.find_first_not_of(ch, pos); +} + +String::SizeType String::FindLastOf(const char *s, String::SizeType pos) const +{ + return m_Data.find_last_of(s, pos); +} + +String::SizeType String::FindLastOf(char ch, String::SizeType pos) const +{ + return m_Data.find_last_of(ch, pos); +} + +String String::SubStr(String::SizeType first, String::SizeType len) const +{ + return m_Data.substr(first, len); +} + +std::vector<String> String::Split(const char *separators) const +{ + std::vector<String> result; + boost::algorithm::split(result, m_Data, boost::is_any_of(separators)); + return result; +} + +void String::Replace(String::SizeType first, String::SizeType second, const String& str) +{ + m_Data.replace(first, second, str); +} + +String String::Trim() const +{ + String t = m_Data; + boost::algorithm::trim(t); + return t; +} + +String String::ToLower() const +{ + String t = m_Data; + boost::algorithm::to_lower(t); + return t; +} + +String String::ToUpper() const +{ + String t = m_Data; + boost::algorithm::to_upper(t); + return t; +} + +String String::Reverse() const +{ + String t = m_Data; + std::reverse(t.m_Data.begin(), t.m_Data.end()); + return t; +} + +void String::Append(int count, char ch) +{ + m_Data.append(count, ch); +} + +bool String::Contains(const String& str) const +{ + return (m_Data.find(str) != std::string::npos); +} + +void String::swap(String& str) +{ + m_Data.swap(str.m_Data); +} + +String::Iterator String::erase(String::Iterator first, String::Iterator last) +{ + return m_Data.erase(first, last); +} + +String::Iterator String::Begin() +{ + return m_Data.begin(); +} + +String::ConstIterator String::Begin() const +{ + return m_Data.begin(); +} + +String::Iterator String::End() +{ + return m_Data.end(); +} + +String::ConstIterator String::End() const +{ + return m_Data.end(); +} + +String::ReverseIterator String::RBegin() +{ + return m_Data.rbegin(); +} + +String::ConstReverseIterator String::RBegin() const +{ + return m_Data.rbegin(); +} + +String::ReverseIterator String::REnd() +{ + return m_Data.rend(); +} + +String::ConstReverseIterator String::REnd() const +{ + return m_Data.rend(); +} + +std::ostream& icinga::operator<<(std::ostream& stream, const String& str) +{ + stream << str.GetData(); + return stream; +} + +std::istream& icinga::operator>>(std::istream& stream, String& str) +{ + std::string tstr; + stream >> tstr; + str = tstr; + return stream; +} + +String icinga::operator+(const String& lhs, const String& rhs) +{ + return lhs.GetData() + rhs.GetData(); +} + +String icinga::operator+(const String& lhs, const char *rhs) +{ + return lhs.GetData() + rhs; +} + +String icinga::operator+(const char *lhs, const String& rhs) +{ + return lhs + rhs.GetData(); +} + +bool icinga::operator==(const String& lhs, const String& rhs) +{ + return lhs.GetData() == rhs.GetData(); +} + +bool icinga::operator==(const String& lhs, const char *rhs) +{ + return lhs.GetData() == rhs; +} + +bool icinga::operator==(const char *lhs, const String& rhs) +{ + return lhs == rhs.GetData(); +} + +bool icinga::operator<(const String& lhs, const char *rhs) +{ + return lhs.GetData() < rhs; +} + +bool icinga::operator<(const char *lhs, const String& rhs) +{ + return lhs < rhs.GetData(); +} + +bool icinga::operator>(const String& lhs, const String& rhs) +{ + return lhs.GetData() > rhs.GetData(); +} + +bool icinga::operator>(const String& lhs, const char *rhs) +{ + return lhs.GetData() > rhs; +} + +bool icinga::operator>(const char *lhs, const String& rhs) +{ + return lhs > rhs.GetData(); +} + +bool icinga::operator<=(const String& lhs, const String& rhs) +{ + return lhs.GetData() <= rhs.GetData(); +} + +bool icinga::operator<=(const String& lhs, const char *rhs) +{ + return lhs.GetData() <= rhs; +} + +bool icinga::operator<=(const char *lhs, const String& rhs) +{ + return lhs <= rhs.GetData(); +} + +bool icinga::operator>=(const String& lhs, const String& rhs) +{ + return lhs.GetData() >= rhs.GetData(); +} + +bool icinga::operator>=(const String& lhs, const char *rhs) +{ + return lhs.GetData() >= rhs; +} + +bool icinga::operator>=(const char *lhs, const String& rhs) +{ + return lhs >= rhs.GetData(); +} + +bool icinga::operator!=(const String& lhs, const String& rhs) +{ + return lhs.GetData() != rhs.GetData(); +} + +bool icinga::operator!=(const String& lhs, const char *rhs) +{ + return lhs.GetData() != rhs; +} + +bool icinga::operator!=(const char *lhs, const String& rhs) +{ + return lhs != rhs.GetData(); +} + +String::Iterator icinga::begin(String& x) +{ + return x.Begin(); +} + +String::ConstIterator icinga::begin(const String& x) +{ + return x.Begin(); +} + +String::Iterator icinga::end(String& x) +{ + return x.End(); +} + +String::ConstIterator icinga::end(const String& x) +{ + return x.End(); +} +String::Iterator icinga::range_begin(String& x) +{ + return x.Begin(); +} + +String::ConstIterator icinga::range_begin(const String& x) +{ + return x.Begin(); +} + +String::Iterator icinga::range_end(String& x) +{ + return x.End(); +} + +String::ConstIterator icinga::range_end(const String& x) +{ + return x.End(); +} + +std::size_t std::hash<String>::operator()(const String& s) const noexcept +{ + return std::hash<std::string>{}(s.GetData()); +} diff --git a/lib/base/string.hpp b/lib/base/string.hpp new file mode 100644 index 0000000..0eb08b5 --- /dev/null +++ b/lib/base/string.hpp @@ -0,0 +1,208 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#ifndef STRING_H +#define STRING_H + +#include "base/i2-base.hpp" +#include "base/object.hpp" +#include <boost/beast/core.hpp> +#include <boost/range/iterator.hpp> +#include <boost/utility/string_view.hpp> +#include <functional> +#include <string> +#include <iosfwd> + +namespace icinga { + +class Value; + +/** + * String class. + * + * Rationale for having this: The std::string class has an ambiguous assignment + * operator when used in conjunction with the Value class. + */ +class String +{ +public: + typedef std::string::iterator Iterator; + typedef std::string::const_iterator ConstIterator; + + typedef std::string::iterator iterator; + typedef std::string::const_iterator const_iterator; + + typedef std::string::reverse_iterator ReverseIterator; + typedef std::string::const_reverse_iterator ConstReverseIterator; + + typedef std::string::reverse_iterator reverse_iterator; + typedef std::string::const_reverse_iterator const_reverse_iterator; + + typedef std::string::size_type SizeType; + + String() = default; + String(const char *data); + String(std::string data); + String(String::SizeType n, char c); + String(const String& other); + String(String&& other); + +#ifndef _MSC_VER + String(Value&& other); +#endif /* _MSC_VER */ + + template<typename InputIterator> + String(InputIterator begin, InputIterator end) + : m_Data(begin, end) + { } + + String& operator=(const String& rhs); + String& operator=(String&& rhs); + String& operator=(Value&& rhs); + String& operator=(const std::string& rhs); + String& operator=(const char *rhs); + + const char& operator[](SizeType pos) const; + char& operator[](SizeType pos); + + String& operator+=(const String& rhs); + String& operator+=(const char *rhs); + String& operator+=(const Value& rhs); + String& operator+=(char rhs); + + bool IsEmpty() const; + + bool operator<(const String& rhs) const; + + operator const std::string&() const; + operator boost::beast::string_view() const; + + const char *CStr() const; + + void Clear(); + + SizeType GetLength() const; + + std::string& GetData(); + const std::string& GetData() const; + + SizeType Find(const String& str, SizeType pos = 0) const; + SizeType RFind(const String& str, SizeType pos = NPos) const; + SizeType FindFirstOf(const char *s, SizeType pos = 0) const; + SizeType FindFirstOf(char ch, SizeType pos = 0) const; + SizeType FindFirstNotOf(const char *s, SizeType pos = 0) const; + SizeType FindFirstNotOf(char ch, SizeType pos = 0) const; + SizeType FindLastOf(const char *s, SizeType pos = NPos) const; + SizeType FindLastOf(char ch, SizeType pos = NPos) const; + + String SubStr(SizeType first, SizeType len = NPos) const; + + std::vector<String> Split(const char *separators) const; + + void Replace(SizeType first, SizeType second, const String& str); + + String Trim() const; + + String ToLower() const; + + String ToUpper() const; + + String Reverse() const; + + void Append(int count, char ch); + + bool Contains(const String& str) const; + + void swap(String& str); + + Iterator erase(Iterator first, Iterator last); + + template<typename InputIterator> + void insert(Iterator p, InputIterator first, InputIterator last) + { + m_Data.insert(p, first, last); + } + + Iterator Begin(); + ConstIterator Begin() const; + Iterator End(); + ConstIterator End() const; + ReverseIterator RBegin(); + ConstReverseIterator RBegin() const; + ReverseIterator REnd(); + ConstReverseIterator REnd() const; + + static const SizeType NPos; + + static Object::Ptr GetPrototype(); + +private: + std::string m_Data; +}; + +std::ostream& operator<<(std::ostream& stream, const String& str); +std::istream& operator>>(std::istream& stream, String& str); + +String operator+(const String& lhs, const String& rhs); +String operator+(const String& lhs, const char *rhs); +String operator+(const char *lhs, const String& rhs); + +bool operator==(const String& lhs, const String& rhs); +bool operator==(const String& lhs, const char *rhs); +bool operator==(const char *lhs, const String& rhs); + +bool operator<(const String& lhs, const char *rhs); +bool operator<(const char *lhs, const String& rhs); + +bool operator>(const String& lhs, const String& rhs); +bool operator>(const String& lhs, const char *rhs); +bool operator>(const char *lhs, const String& rhs); + +bool operator<=(const String& lhs, const String& rhs); +bool operator<=(const String& lhs, const char *rhs); +bool operator<=(const char *lhs, const String& rhs); + +bool operator>=(const String& lhs, const String& rhs); +bool operator>=(const String& lhs, const char *rhs); +bool operator>=(const char *lhs, const String& rhs); + +bool operator!=(const String& lhs, const String& rhs); +bool operator!=(const String& lhs, const char *rhs); +bool operator!=(const char *lhs, const String& rhs); + +String::Iterator begin(String& x); +String::ConstIterator begin(const String& x); +String::Iterator end(String& x); +String::ConstIterator end(const String& x); +String::Iterator range_begin(String& x); +String::ConstIterator range_begin(const String& x); +String::Iterator range_end(String& x); +String::ConstIterator range_end(const String& x); + +} + +template<> +struct std::hash<icinga::String> +{ + std::size_t operator()(const icinga::String& s) const noexcept; +}; + +extern template class std::vector<icinga::String>; + +namespace boost +{ + +template<> +struct range_mutable_iterator<icinga::String> +{ + typedef icinga::String::Iterator type; +}; + +template<> +struct range_const_iterator<icinga::String> +{ + typedef icinga::String::ConstIterator type; +}; + +} + +#endif /* STRING_H */ diff --git a/lib/base/sysloglogger.cpp b/lib/base/sysloglogger.cpp new file mode 100644 index 0000000..fc2ec09 --- /dev/null +++ b/lib/base/sysloglogger.cpp @@ -0,0 +1,144 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#ifndef _WIN32 +#include "base/sysloglogger.hpp" +#include "base/sysloglogger-ti.cpp" +#include "base/configtype.hpp" +#include "base/statsfunction.hpp" +#include <syslog.h> + +using namespace icinga; + +REGISTER_TYPE(SyslogLogger); + +REGISTER_STATSFUNCTION(SyslogLogger, &SyslogLogger::StatsFunc); + +INITIALIZE_ONCE(&SyslogHelper::StaticInitialize); + +std::map<String, int> SyslogHelper::m_FacilityMap; + +void SyslogHelper::StaticInitialize() +{ + ScriptGlobal::Set("System.FacilityAuth", "LOG_AUTH"); + ScriptGlobal::Set("System.FacilityAuthPriv", "LOG_AUTHPRIV"); + ScriptGlobal::Set("System.FacilityCron", "LOG_CRON"); + ScriptGlobal::Set("System.FacilityDaemon", "LOG_DAEMON"); + ScriptGlobal::Set("System.FacilityFtp", "LOG_FTP"); + ScriptGlobal::Set("System.FacilityKern", "LOG_KERN"); + ScriptGlobal::Set("System.FacilityLocal0", "LOG_LOCAL0"); + ScriptGlobal::Set("System.FacilityLocal1", "LOG_LOCAL1"); + ScriptGlobal::Set("System.FacilityLocal2", "LOG_LOCAL2"); + ScriptGlobal::Set("System.FacilityLocal3", "LOG_LOCAL3"); + ScriptGlobal::Set("System.FacilityLocal4", "LOG_LOCAL4"); + ScriptGlobal::Set("System.FacilityLocal5", "LOG_LOCAL5"); + ScriptGlobal::Set("System.FacilityLocal6", "LOG_LOCAL6"); + ScriptGlobal::Set("System.FacilityLocal7", "LOG_LOCAL7"); + ScriptGlobal::Set("System.FacilityLpr", "LOG_LPR"); + ScriptGlobal::Set("System.FacilityMail", "LOG_MAIL"); + ScriptGlobal::Set("System.FacilityNews", "LOG_NEWS"); + ScriptGlobal::Set("System.FacilitySyslog", "LOG_SYSLOG"); + ScriptGlobal::Set("System.FacilityUser", "LOG_USER"); + ScriptGlobal::Set("System.FacilityUucp", "LOG_UUCP"); + + m_FacilityMap["LOG_AUTH"] = LOG_AUTH; + m_FacilityMap["LOG_AUTHPRIV"] = LOG_AUTHPRIV; + m_FacilityMap["LOG_CRON"] = LOG_CRON; + m_FacilityMap["LOG_DAEMON"] = LOG_DAEMON; +#ifdef LOG_FTP + m_FacilityMap["LOG_FTP"] = LOG_FTP; +#endif /* LOG_FTP */ + m_FacilityMap["LOG_KERN"] = LOG_KERN; + m_FacilityMap["LOG_LOCAL0"] = LOG_LOCAL0; + m_FacilityMap["LOG_LOCAL1"] = LOG_LOCAL1; + m_FacilityMap["LOG_LOCAL2"] = LOG_LOCAL2; + m_FacilityMap["LOG_LOCAL3"] = LOG_LOCAL3; + m_FacilityMap["LOG_LOCAL4"] = LOG_LOCAL4; + m_FacilityMap["LOG_LOCAL5"] = LOG_LOCAL5; + m_FacilityMap["LOG_LOCAL6"] = LOG_LOCAL6; + m_FacilityMap["LOG_LOCAL7"] = LOG_LOCAL7; + m_FacilityMap["LOG_LPR"] = LOG_LPR; + m_FacilityMap["LOG_MAIL"] = LOG_MAIL; + m_FacilityMap["LOG_NEWS"] = LOG_NEWS; + m_FacilityMap["LOG_SYSLOG"] = LOG_SYSLOG; + m_FacilityMap["LOG_USER"] = LOG_USER; + m_FacilityMap["LOG_UUCP"] = LOG_UUCP; +} + +bool SyslogHelper::ValidateFacility(const String& facility) +{ + if (m_FacilityMap.find(facility) == m_FacilityMap.end()) { + try { + Convert::ToLong(facility); + } catch (const std::exception&) { + return false; + } + } + return true; +} + +int SyslogHelper::SeverityToNumber(LogSeverity severity) +{ + switch (severity) { + case LogDebug: + return LOG_DEBUG; + case LogNotice: + return LOG_NOTICE; + case LogWarning: + return LOG_WARNING; + case LogCritical: + return LOG_CRIT; + case LogInformation: + default: + return LOG_INFO; + } +} + +int SyslogHelper::FacilityToNumber(const String& facility) +{ + auto it = m_FacilityMap.find(facility); + if (it != m_FacilityMap.end()) + return it->second; + else + return Convert::ToLong(facility); +} + +void SyslogLogger::StatsFunc(const Dictionary::Ptr& status, const Array::Ptr&) +{ + DictionaryData nodes; + + for (const SyslogLogger::Ptr& sysloglogger : ConfigType::GetObjectsByType<SyslogLogger>()) { + nodes.emplace_back(sysloglogger->GetName(), 1); //add more stats + } + + status->Set("sysloglogger", new Dictionary(std::move(nodes))); +} + +void SyslogLogger::OnConfigLoaded() +{ + ObjectImpl<SyslogLogger>::OnConfigLoaded(); + m_Facility = SyslogHelper::FacilityToNumber(GetFacility()); +} + +void SyslogLogger::ValidateFacility(const Lazy<String>& lvalue, const ValidationUtils& utils) +{ + ObjectImpl<SyslogLogger>::ValidateFacility(lvalue, utils); + if (!SyslogHelper::ValidateFacility(lvalue())) + BOOST_THROW_EXCEPTION(ValidationError(this, { "facility" }, "Invalid facility specified.")); +} + +/** + * Processes a log entry and outputs it to syslog. + * + * @param entry The log entry. + */ +void SyslogLogger::ProcessLogEntry(const LogEntry& entry) +{ + syslog(SyslogHelper::SeverityToNumber(entry.Severity) | m_Facility, + "%s", entry.Message.CStr()); +} + +void SyslogLogger::Flush() +{ + /* Nothing to do here. */ +} +#endif /* _WIN32 */ diff --git a/lib/base/sysloglogger.hpp b/lib/base/sysloglogger.hpp new file mode 100644 index 0000000..d1d6859 --- /dev/null +++ b/lib/base/sysloglogger.hpp @@ -0,0 +1,56 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#ifndef SYSLOGLOGGER_H +#define SYSLOGLOGGER_H + +#ifndef _WIN32 +#include "base/i2-base.hpp" +#include "base/sysloglogger-ti.hpp" + +namespace icinga +{ + +/** + * Helper class to handle syslog facility strings and numbers. + * + * @ingroup base + */ +class SyslogHelper final +{ +public: + static void StaticInitialize(); + static bool ValidateFacility(const String& facility); + static int SeverityToNumber(LogSeverity severity); + static int FacilityToNumber(const String& facility); + +private: + static std::map<String, int> m_FacilityMap; +}; + +/** + * A logger that logs to syslog. + * + * @ingroup base + */ +class SyslogLogger final : public ObjectImpl<SyslogLogger> +{ +public: + DECLARE_OBJECT(SyslogLogger); + DECLARE_OBJECTNAME(SyslogLogger); + + static void StatsFunc(const Dictionary::Ptr& status, const Array::Ptr& perfdata); + + void OnConfigLoaded() override; + void ValidateFacility(const Lazy<String>& lvalue, const ValidationUtils& utils) override; + +protected: + int m_Facility; + + void ProcessLogEntry(const LogEntry& entry) override; + void Flush() override; +}; + +} +#endif /* _WIN32 */ + +#endif /* SYSLOGLOGGER_H */ diff --git a/lib/base/sysloglogger.ti b/lib/base/sysloglogger.ti new file mode 100644 index 0000000..8f34359 --- /dev/null +++ b/lib/base/sysloglogger.ti @@ -0,0 +1,19 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#include "base/logger.hpp" + +library base; + +namespace icinga +{ + +class SyslogLogger : Logger +{ + activation_priority -100; + + [config] String facility { + default {{{ return "LOG_USER"; }}} + }; +}; + +} diff --git a/lib/base/tcpsocket.cpp b/lib/base/tcpsocket.cpp new file mode 100644 index 0000000..a9390e5 --- /dev/null +++ b/lib/base/tcpsocket.cpp @@ -0,0 +1,211 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#include "base/tcpsocket.hpp" +#include "base/logger.hpp" +#include "base/utility.hpp" +#include "base/exception.hpp" +#include <boost/exception/errinfo_api_function.hpp> +#include <boost/exception/errinfo_errno.hpp> +#include <iostream> + +using namespace icinga; + +/** + * Creates a socket and binds it to the specified service. + * + * @param service The service. + * @param family The address family for the socket. + */ +void TcpSocket::Bind(const String& service, int family) +{ + Bind(String(), service, family); +} + +/** + * Creates a socket and binds it to the specified node and service. + * + * @param node The node. + * @param service The service. + * @param family The address family for the socket. + */ +void TcpSocket::Bind(const String& node, const String& service, int family) +{ + addrinfo hints; + addrinfo *result; + int error; + const char *func; + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = family; + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = IPPROTO_TCP; + hints.ai_flags = AI_PASSIVE; + + int rc = getaddrinfo(node.IsEmpty() ? nullptr : node.CStr(), + service.CStr(), &hints, &result); + + if (rc != 0) { + Log(LogCritical, "TcpSocket") + << "getaddrinfo() failed with error code " << rc << ", \"" << gai_strerror(rc) << "\""; + + BOOST_THROW_EXCEPTION(socket_error() + << boost::errinfo_api_function("getaddrinfo") + << errinfo_getaddrinfo_error(rc)); + } + + int fd = INVALID_SOCKET; + + for (addrinfo *info = result; info != nullptr; info = info->ai_next) { + fd = socket(info->ai_family, info->ai_socktype, info->ai_protocol); + + if (fd == INVALID_SOCKET) { +#ifdef _WIN32 + error = WSAGetLastError(); +#else /* _WIN32 */ + error = errno; +#endif /* _WIN32 */ + func = "socket"; + + continue; + } + + const int optFalse = 0; + setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, reinterpret_cast<const char *>(&optFalse), sizeof(optFalse)); + + const int optTrue = 1; + setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast<const char *>(&optTrue), sizeof(optTrue)); +#ifdef SO_REUSEPORT + setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, reinterpret_cast<const char *>(&optTrue), sizeof(optTrue)); +#endif /* SO_REUSEPORT */ + + int rc = bind(fd, info->ai_addr, info->ai_addrlen); + + if (rc < 0) { +#ifdef _WIN32 + error = WSAGetLastError(); +#else /* _WIN32 */ + error = errno; +#endif /* _WIN32 */ + func = "bind"; + + closesocket(fd); + + continue; + } + + SetFD(fd); + + break; + } + + freeaddrinfo(result); + + if (GetFD() == INVALID_SOCKET) { + Log(LogCritical, "TcpSocket") + << "Invalid socket: " << Utility::FormatErrorNumber(error); + +#ifndef _WIN32 + BOOST_THROW_EXCEPTION(socket_error() + << boost::errinfo_api_function(func) + << boost::errinfo_errno(error)); +#else /* _WIN32 */ + BOOST_THROW_EXCEPTION(socket_error() + << boost::errinfo_api_function(func) + << errinfo_win32_error(error)); +#endif /* _WIN32 */ + } +} + +/** + * Creates a socket and connects to the specified node and service. + * + * @param node The node. + * @param service The service. + */ +void TcpSocket::Connect(const String& node, const String& service) +{ + addrinfo hints; + addrinfo *result; + int error; + const char *func; + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = IPPROTO_TCP; + + int rc = getaddrinfo(node.CStr(), service.CStr(), &hints, &result); + + if (rc != 0) { + Log(LogCritical, "TcpSocket") + << "getaddrinfo() failed with error code " << rc << ", \"" << gai_strerror(rc) << "\""; + + BOOST_THROW_EXCEPTION(socket_error() + << boost::errinfo_api_function("getaddrinfo") + << errinfo_getaddrinfo_error(rc)); + } + + SOCKET fd = INVALID_SOCKET; + + for (addrinfo *info = result; info != nullptr; info = info->ai_next) { + fd = socket(info->ai_family, info->ai_socktype, info->ai_protocol); + + if (fd == INVALID_SOCKET) { +#ifdef _WIN32 + error = WSAGetLastError(); +#else /* _WIN32 */ + error = errno; +#endif /* _WIN32 */ + func = "socket"; + + continue; + } + + const int optTrue = 1; + if (setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, reinterpret_cast<const char *>(&optTrue), sizeof(optTrue)) != 0) { +#ifdef _WIN32 + error = WSAGetLastError(); +#else /* _WIN32 */ + error = errno; +#endif /* _WIN32 */ + Log(LogWarning, "TcpSocket") + << "setsockopt() unable to enable TCP keep-alives with error code " << rc; + } + + rc = connect(fd, info->ai_addr, info->ai_addrlen); + + if (rc < 0) { +#ifdef _WIN32 + error = WSAGetLastError(); +#else /* _WIN32 */ + error = errno; +#endif /* _WIN32 */ + func = "connect"; + + closesocket(fd); + + continue; + } + + SetFD(fd); + + break; + } + + freeaddrinfo(result); + + if (GetFD() == INVALID_SOCKET) { + Log(LogCritical, "TcpSocket") + << "Invalid socket: " << Utility::FormatErrorNumber(error); + +#ifndef _WIN32 + BOOST_THROW_EXCEPTION(socket_error() + << boost::errinfo_api_function(func) + << boost::errinfo_errno(error)); +#else /* _WIN32 */ + BOOST_THROW_EXCEPTION(socket_error() + << boost::errinfo_api_function(func) + << errinfo_win32_error(error)); +#endif /* _WIN32 */ + } +} diff --git a/lib/base/tcpsocket.hpp b/lib/base/tcpsocket.hpp new file mode 100644 index 0000000..471ad8d --- /dev/null +++ b/lib/base/tcpsocket.hpp @@ -0,0 +1,102 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#ifndef TCPSOCKET_H +#define TCPSOCKET_H + +#include "base/i2-base.hpp" +#include "base/io-engine.hpp" +#include "base/socket.hpp" +#include <boost/asio/error.hpp> +#include <boost/asio/ip/tcp.hpp> +#include <boost/asio/spawn.hpp> +#include <boost/system/system_error.hpp> + +namespace icinga +{ + +/** + * A TCP socket. DEPRECATED - Use Boost ASIO instead. + * + * @ingroup base + */ +class TcpSocket final : public Socket +{ +public: + DECLARE_PTR_TYPEDEFS(TcpSocket); + + void Bind(const String& service, int family); + void Bind(const String& node, const String& service, int family); + + void Connect(const String& node, const String& service); +}; + +/** + * TCP Connect based on Boost ASIO. + * + * @ingroup base + */ +template<class Socket> +void Connect(Socket& socket, const String& node, const String& service) +{ + using boost::asio::ip::tcp; + + tcp::resolver resolver (IoEngine::Get().GetIoContext()); + tcp::resolver::query query (node, service); + auto result (resolver.resolve(query)); + auto current (result.begin()); + + for (;;) { + try { + socket.open(current->endpoint().protocol()); + socket.set_option(tcp::socket::keep_alive(true)); + socket.connect(current->endpoint()); + + break; + } catch (const std::exception& ex) { + auto se (dynamic_cast<const boost::system::system_error*>(&ex)); + + if (se && se->code() == boost::asio::error::operation_aborted || ++current == result.end()) { + throw; + } + + if (socket.is_open()) { + socket.close(); + } + } + } +} + +template<class Socket> +void Connect(Socket& socket, const String& node, const String& service, boost::asio::yield_context yc) +{ + using boost::asio::ip::tcp; + + tcp::resolver resolver (IoEngine::Get().GetIoContext()); + tcp::resolver::query query (node, service); + auto result (resolver.async_resolve(query, yc)); + auto current (result.begin()); + + for (;;) { + try { + socket.open(current->endpoint().protocol()); + socket.set_option(tcp::socket::keep_alive(true)); + socket.async_connect(current->endpoint(), yc); + + break; + } catch (const std::exception& ex) { + auto se (dynamic_cast<const boost::system::system_error*>(&ex)); + + if (se && se->code() == boost::asio::error::operation_aborted || ++current == result.end()) { + throw; + } + + if (socket.is_open()) { + socket.close(); + } + } + } +} + +} + +#endif /* TCPSOCKET_H */ diff --git a/lib/base/threadpool.cpp b/lib/base/threadpool.cpp new file mode 100644 index 0000000..dc76e7b --- /dev/null +++ b/lib/base/threadpool.cpp @@ -0,0 +1,51 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#include "base/threadpool.hpp" +#include <boost/thread/locks.hpp> + +using namespace icinga; + +ThreadPool::ThreadPool() : m_Pending(0) +{ + Start(); +} + +ThreadPool::~ThreadPool() +{ + Stop(); +} + +void ThreadPool::Start() +{ + boost::unique_lock<decltype(m_Mutex)> lock (m_Mutex); + + if (!m_Pool) { + InitializePool(); + } +} + +void ThreadPool::InitializePool() +{ + m_Pool = decltype(m_Pool)(new boost::asio::thread_pool(Configuration::Concurrency * 2u)); +} + +void ThreadPool::Stop() +{ + boost::unique_lock<decltype(m_Mutex)> lock (m_Mutex); + + if (m_Pool) { + m_Pool->join(); + m_Pool = nullptr; + } +} + +void ThreadPool::Restart() +{ + boost::unique_lock<decltype(m_Mutex)> lock (m_Mutex); + + if (m_Pool) { + m_Pool->join(); + } + + InitializePool(); +} diff --git a/lib/base/threadpool.hpp b/lib/base/threadpool.hpp new file mode 100644 index 0000000..d30fa69 --- /dev/null +++ b/lib/base/threadpool.hpp @@ -0,0 +1,101 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#ifndef THREADPOOL_H +#define THREADPOOL_H + +#include "base/atomic.hpp" +#include "base/configuration.hpp" +#include "base/exception.hpp" +#include "base/logger.hpp" +#include <cstddef> +#include <exception> +#include <functional> +#include <memory> +#include <thread> +#include <boost/asio/post.hpp> +#include <boost/asio/thread_pool.hpp> +#include <boost/thread/locks.hpp> +#include <boost/thread/shared_mutex.hpp> +#include <cstdint> + +namespace icinga +{ + +enum SchedulerPolicy +{ + DefaultScheduler, + LowLatencyScheduler +}; + +/** + * A thread pool. + * + * @ingroup base + */ +class ThreadPool +{ +public: + typedef std::function<void ()> WorkFunction; + + ThreadPool(); + ~ThreadPool(); + + void Start(); + void Stop(); + void Restart(); + + /** + * Appends a work item to the work queue. Work items will be processed in FIFO order. + * + * @param callback The callback function for the work item. + * @returns true if the item was queued, false otherwise. + */ + template<class T> + bool Post(T callback, SchedulerPolicy) + { + boost::shared_lock<decltype(m_Mutex)> lock (m_Mutex); + + if (m_Pool) { + m_Pending.fetch_add(1); + + boost::asio::post(*m_Pool, [this, callback]() { + m_Pending.fetch_sub(1); + + try { + callback(); + } catch (const std::exception& ex) { + Log(LogCritical, "ThreadPool") + << "Exception thrown in event handler:\n" + << DiagnosticInformation(ex); + } catch (...) { + Log(LogCritical, "ThreadPool", "Exception of unknown type thrown in event handler."); + } + }); + + return true; + } else { + return false; + } + } + + /** + * Returns the amount of queued tasks not started yet. + * + * @returns amount of queued tasks. + */ + inline uint_fast64_t GetPending() + { + return m_Pending.load(); + } + +private: + boost::shared_mutex m_Mutex; + std::unique_ptr<boost::asio::thread_pool> m_Pool; + Atomic<uint_fast64_t> m_Pending; + + void InitializePool(); +}; + +} + +#endif /* THREADPOOL_H */ diff --git a/lib/base/timer.cpp b/lib/base/timer.cpp new file mode 100644 index 0000000..ffe1c39 --- /dev/null +++ b/lib/base/timer.cpp @@ -0,0 +1,354 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#include "base/defer.hpp" +#include "base/timer.hpp" +#include "base/debug.hpp" +#include "base/logger.hpp" +#include "base/utility.hpp" +#include <boost/multi_index_container.hpp> +#include <boost/multi_index/ordered_index.hpp> +#include <boost/multi_index/key_extractors.hpp> +#include <chrono> +#include <condition_variable> +#include <mutex> +#include <thread> +#include <utility> + +using namespace icinga; + +namespace icinga { + +class TimerHolder { +public: + TimerHolder(Timer *timer) + : m_Timer(timer) + { } + + inline Timer *GetObject() const + { + return m_Timer; + } + + inline double GetNextUnlocked() const + { + return m_Timer->m_Next; + } + + operator Timer *() const + { + return m_Timer; + } + +private: + Timer *m_Timer; +}; + +} + +typedef boost::multi_index_container< + TimerHolder, + boost::multi_index::indexed_by< + boost::multi_index::ordered_unique<boost::multi_index::const_mem_fun<TimerHolder, Timer *, &TimerHolder::GetObject> >, + boost::multi_index::ordered_non_unique<boost::multi_index::const_mem_fun<TimerHolder, double, &TimerHolder::GetNextUnlocked> > + > +> TimerSet; + +static std::mutex l_TimerMutex; +static std::condition_variable l_TimerCV; +static std::thread l_TimerThread; +static bool l_StopTimerThread; +static TimerSet l_Timers; +static int l_AliveTimers = 0; + +static Defer l_ShutdownTimersCleanlyOnExit (&Timer::Uninitialize); + +Timer::Ptr Timer::Create() +{ + Ptr t (new Timer()); + + t->m_Self = t; + + return t; +} + +/** + * Destructor for the Timer class. + */ +Timer::~Timer() +{ + Stop(true); +} + +void Timer::Initialize() +{ + std::unique_lock<std::mutex> lock(l_TimerMutex); + + if (l_AliveTimers > 0) { + InitializeThread(); + } +} + +void Timer::Uninitialize() +{ + std::unique_lock<std::mutex> lock(l_TimerMutex); + + if (l_AliveTimers > 0) { + UninitializeThread(); + } +} + +void Timer::InitializeThread() +{ + l_StopTimerThread = false; + l_TimerThread = std::thread(&Timer::TimerThreadProc); +} + +void Timer::UninitializeThread() +{ + { + l_StopTimerThread = true; + l_TimerCV.notify_all(); + } + + l_TimerMutex.unlock(); + + if (l_TimerThread.joinable()) + l_TimerThread.join(); + + l_TimerMutex.lock(); +} + +/** + * Calls this timer. + */ +void Timer::Call() +{ + try { + OnTimerExpired(this); + } catch (...) { + InternalReschedule(true); + + throw; + } + + InternalReschedule(true); +} + +/** + * Sets the interval for this timer. + * + * @param interval The new interval. + */ +void Timer::SetInterval(double interval) +{ + std::unique_lock<std::mutex> lock(l_TimerMutex); + m_Interval = interval; +} + +/** + * Retrieves the interval for this timer. + * + * @returns The interval. + */ +double Timer::GetInterval() const +{ + std::unique_lock<std::mutex> lock(l_TimerMutex); + return m_Interval; +} + +/** + * Registers the timer and starts processing events for it. + */ +void Timer::Start() +{ + std::unique_lock<std::mutex> lock(l_TimerMutex); + + if (!m_Started && ++l_AliveTimers == 1) { + InitializeThread(); + } + + m_Started = true; + + InternalRescheduleUnlocked(false, m_Interval > 0 ? -1 : m_Next); +} + +/** + * Unregisters the timer and stops processing events for it. + */ +void Timer::Stop(bool wait) +{ + if (l_StopTimerThread) + return; + + std::unique_lock<std::mutex> lock(l_TimerMutex); + + if (m_Started && --l_AliveTimers == 0) { + UninitializeThread(); + } + + m_Started = false; + l_Timers.erase(this); + + /* Notify the worker thread that we've disabled a timer. */ + l_TimerCV.notify_all(); + + while (wait && m_Running) + l_TimerCV.wait(lock); +} + +void Timer::Reschedule(double next) +{ + InternalReschedule(false, next); +} + +void Timer::InternalReschedule(bool completed, double next) +{ + std::unique_lock<std::mutex> lock (l_TimerMutex); + + InternalRescheduleUnlocked(completed, next); +} + +/** + * Reschedules this timer. + * + * @param completed Whether the timer has just completed its callback. + * @param next The time when this timer should be called again. Use -1 to let + * the timer figure out a suitable time based on the interval. + */ +void Timer::InternalRescheduleUnlocked(bool completed, double next) +{ + if (completed) + m_Running = false; + + if (next < 0) { + /* Don't schedule the next call if this is not a periodic timer. */ + if (m_Interval <= 0) + return; + + next = Utility::GetTime() + m_Interval; + } + + m_Next = next; + + if (m_Started && !m_Running) { + /* Remove and re-add the timer to update the index. */ + l_Timers.erase(this); + l_Timers.insert(this); + + /* Notify the worker that we've rescheduled a timer. */ + l_TimerCV.notify_all(); + } +} + +/** + * Retrieves when the timer is next due. + * + * @returns The timestamp. + */ +double Timer::GetNext() const +{ + std::unique_lock<std::mutex> lock(l_TimerMutex); + return m_Next; +} + +/** + * Adjusts all periodic timers by adding the specified amount of time to their + * next scheduled timestamp. + * + * @param adjustment The adjustment. + */ +void Timer::AdjustTimers(double adjustment) +{ + std::unique_lock<std::mutex> lock(l_TimerMutex); + + double now = Utility::GetTime(); + + typedef boost::multi_index::nth_index<TimerSet, 1>::type TimerView; + TimerView& idx = boost::get<1>(l_Timers); + + std::vector<Timer *> timers; + + for (Timer *timer : idx) { + /* Don't schedule the next call if this is not a periodic timer. */ + if (timer->m_Interval <= 0) { + continue; + } + + if (std::fabs(now - (timer->m_Next + adjustment)) < + std::fabs(now - timer->m_Next)) { + timer->m_Next += adjustment; + timers.push_back(timer); + } + } + + for (Timer *timer : timers) { + l_Timers.erase(timer); + l_Timers.insert(timer); + } + + /* Notify the worker that we've rescheduled some timers. */ + l_TimerCV.notify_all(); +} + +/** + * Worker thread proc for Timer objects. + */ +void Timer::TimerThreadProc() +{ + namespace ch = std::chrono; + + Log(LogDebug, "Timer", "TimerThreadProc started."); + + Utility::SetThreadName("Timer Thread"); + + std::unique_lock<std::mutex> lock (l_TimerMutex); + + for (;;) { + typedef boost::multi_index::nth_index<TimerSet, 1>::type NextTimerView; + NextTimerView& idx = boost::get<1>(l_Timers); + + /* Wait until there is at least one timer. */ + while (idx.empty() && !l_StopTimerThread) + l_TimerCV.wait(lock); + + if (l_StopTimerThread) + break; + + auto it = idx.begin(); + + // timer->~Timer() may be called at any moment (if the last + // smart pointer gets destroyed) or even already waiting for + // l_TimerMutex (before doing anything else) which we have + // locked at the moment. Until our unlock using *timer is safe. + Timer *timer = *it; + + ch::time_point<ch::system_clock, ch::duration<double>> next (ch::duration<double>(timer->m_Next)); + + if (next - ch::system_clock::now() > ch::duration<double>(0.01)) { + /* Wait for the next timer. */ + l_TimerCV.wait_until(lock, next); + + continue; + } + + /* Remove the timer from the list so it doesn't get called again + * until the current call is completed. */ + l_Timers.erase(timer); + + auto keepAlive (timer->m_Self.lock()); + + if (!keepAlive) { + // The last std::shared_ptr is gone, let ~Timer() proceed + continue; + } + + timer->m_Running = true; + + lock.unlock(); + + /* Asynchronously call the timer. */ + Utility::QueueAsyncCallback([timer=std::move(keepAlive)]() { timer->Call(); }); + + lock.lock(); + } +} diff --git a/lib/base/timer.hpp b/lib/base/timer.hpp new file mode 100644 index 0000000..db0f0b7 --- /dev/null +++ b/lib/base/timer.hpp @@ -0,0 +1,65 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#ifndef TIMER_H +#define TIMER_H + +#include "base/i2-base.hpp" +#include <boost/signals2.hpp> +#include <memory> + +namespace icinga { + +class TimerHolder; + +/** + * A timer that periodically triggers an event. + * + * @ingroup base + */ +class Timer final +{ +public: + typedef std::shared_ptr<Timer> Ptr; + + static Ptr Create(); + + ~Timer(); + + static void Initialize(); + static void Uninitialize(); + static void InitializeThread(); + static void UninitializeThread(); + + void SetInterval(double interval); + double GetInterval() const; + + static void AdjustTimers(double adjustment); + + void Start(); + void Stop(bool wait = false); + + void Reschedule(double next = -1); + double GetNext() const; + + boost::signals2::signal<void(const Timer * const&)> OnTimerExpired; + +private: + double m_Interval{0}; /**< The interval of the timer. */ + double m_Next{0}; /**< When the next event should happen. */ + bool m_Started{false}; /**< Whether the timer is enabled. */ + bool m_Running{false}; /**< Whether the timer proc is currently running. */ + std::weak_ptr<Timer> m_Self; + + Timer() = default; + void Call(); + void InternalReschedule(bool completed, double next = -1); + void InternalRescheduleUnlocked(bool completed, double next = -1); + + static void TimerThreadProc(); + + friend class TimerHolder; +}; + +} + +#endif /* TIMER_H */ diff --git a/lib/base/tlsstream.cpp b/lib/base/tlsstream.cpp new file mode 100644 index 0000000..db54c91 --- /dev/null +++ b/lib/base/tlsstream.cpp @@ -0,0 +1,71 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#include "base/tlsstream.hpp" +#include "base/application.hpp" +#include "base/utility.hpp" +#include "base/exception.hpp" +#include "base/logger.hpp" +#include "base/configuration.hpp" +#include "base/convert.hpp" +#include <boost/asio/ssl/context.hpp> +#include <boost/asio/ssl/verify_context.hpp> +#include <boost/asio/ssl/verify_mode.hpp> +#include <iostream> +#include <openssl/ssl.h> +#include <openssl/tls1.h> +#include <openssl/x509.h> +#include <sstream> + +using namespace icinga; + +bool UnbufferedAsioTlsStream::IsVerifyOK() const +{ + return m_VerifyOK; +} + +String UnbufferedAsioTlsStream::GetVerifyError() const +{ + return m_VerifyError; +} + +std::shared_ptr<X509> UnbufferedAsioTlsStream::GetPeerCertificate() +{ + return std::shared_ptr<X509>(SSL_get_peer_certificate(native_handle()), X509_free); +} + +void UnbufferedAsioTlsStream::BeforeHandshake(handshake_type type) +{ + namespace ssl = boost::asio::ssl; + + if (!m_Hostname.IsEmpty()) { + X509_VERIFY_PARAM_set1_host(SSL_get0_param(native_handle()), m_Hostname.CStr(), m_Hostname.GetLength()); + } + + set_verify_mode(ssl::verify_peer | ssl::verify_client_once); + + set_verify_callback([this](bool preverified, ssl::verify_context& ctx) { + if (!preverified) { + m_VerifyOK = false; + + std::ostringstream msgbuf; + int err = X509_STORE_CTX_get_error(ctx.native_handle()); + + msgbuf << "code " << err << ": " << X509_verify_cert_error_string(err); + m_VerifyError = msgbuf.str(); + } + + return true; + }); + +#ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME + if (type == client && !m_Hostname.IsEmpty()) { + String environmentName = Application::GetAppEnvironment(); + String serverName = m_Hostname; + + if (!environmentName.IsEmpty()) + serverName += ":" + environmentName; + + SSL_set_tlsext_host_name(native_handle(), serverName.CStr()); + } +#endif /* SSL_CTRL_SET_TLSEXT_HOSTNAME */ +} diff --git a/lib/base/tlsstream.hpp b/lib/base/tlsstream.hpp new file mode 100644 index 0000000..f6e5209 --- /dev/null +++ b/lib/base/tlsstream.hpp @@ -0,0 +1,129 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#ifndef TLSSTREAM_H +#define TLSSTREAM_H + +#include "base/i2-base.hpp" +#include "base/shared.hpp" +#include "base/socket.hpp" +#include "base/stream.hpp" +#include "base/tlsutility.hpp" +#include "base/fifo.hpp" +#include "base/utility.hpp" +#include <atomic> +#include <memory> +#include <utility> +#include <boost/asio/buffered_stream.hpp> +#include <boost/asio/io_context.hpp> +#include <boost/asio/ip/tcp.hpp> +#include <boost/asio/spawn.hpp> +#include <boost/asio/ssl/context.hpp> +#include <boost/asio/ssl/stream.hpp> + +namespace icinga +{ + +template<class ARS> +class SeenStream : public ARS +{ +public: + template<class... Args> + SeenStream(Args&&... args) : ARS(std::forward<Args>(args)...) + { + m_Seen.store(nullptr); + } + + template<class... Args> + auto async_read_some(Args&&... args) -> decltype(((ARS*)nullptr)->async_read_some(std::forward<Args>(args)...)) + { + { + auto seen (m_Seen.load()); + + if (seen) { + *seen = Utility::GetTime(); + } + } + + return ((ARS*)this)->async_read_some(std::forward<Args>(args)...); + } + + inline void SetSeen(double* seen) + { + m_Seen.store(seen); + } + +private: + std::atomic<double*> m_Seen; +}; + +struct UnbufferedAsioTlsStreamParams +{ + boost::asio::io_context& IoContext; + boost::asio::ssl::context& SslContext; + const String& Hostname; +}; + +typedef SeenStream<boost::asio::ssl::stream<boost::asio::ip::tcp::socket>> AsioTcpTlsStream; + +class UnbufferedAsioTlsStream : public AsioTcpTlsStream +{ +public: + inline + UnbufferedAsioTlsStream(UnbufferedAsioTlsStreamParams& init) + : AsioTcpTlsStream(init.IoContext, init.SslContext), m_VerifyOK(true), m_Hostname(init.Hostname) + { + } + + bool IsVerifyOK() const; + String GetVerifyError() const; + std::shared_ptr<X509> GetPeerCertificate(); + + template<class... Args> + inline + auto async_handshake(handshake_type type, Args&&... args) -> decltype(((AsioTcpTlsStream*)nullptr)->async_handshake(type, std::forward<Args>(args)...)) + { + BeforeHandshake(type); + + return AsioTcpTlsStream::async_handshake(type, std::forward<Args>(args)...); + } + + template<class... Args> + inline + auto handshake(handshake_type type, Args&&... args) -> decltype(((AsioTcpTlsStream*)nullptr)->handshake(type, std::forward<Args>(args)...)) + { + BeforeHandshake(type); + + return AsioTcpTlsStream::handshake(type, std::forward<Args>(args)...); + } + +private: + bool m_VerifyOK; + String m_VerifyError; + String m_Hostname; + + void BeforeHandshake(handshake_type type); +}; + +class AsioTlsStream : public boost::asio::buffered_stream<UnbufferedAsioTlsStream> +{ +public: + inline + AsioTlsStream(boost::asio::io_context& ioContext, boost::asio::ssl::context& sslContext, const String& hostname = String()) + : AsioTlsStream(UnbufferedAsioTlsStreamParams{ioContext, sslContext, hostname}) + { + } + +private: + inline + AsioTlsStream(UnbufferedAsioTlsStreamParams init) + : buffered_stream(init) + { + } +}; + +typedef boost::asio::buffered_stream<boost::asio::ip::tcp::socket> AsioTcpStream; +typedef std::pair<Shared<AsioTlsStream>::Ptr, Shared<AsioTcpStream>::Ptr> OptionalTlsStream; + +} + +#endif /* TLSSTREAM_H */ diff --git a/lib/base/tlsutility.cpp b/lib/base/tlsutility.cpp new file mode 100644 index 0000000..2e1b90a --- /dev/null +++ b/lib/base/tlsutility.cpp @@ -0,0 +1,1086 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#include "base/tlsutility.hpp" +#include "base/convert.hpp" +#include "base/logger.hpp" +#include "base/context.hpp" +#include "base/convert.hpp" +#include "base/utility.hpp" +#include "base/application.hpp" +#include "base/exception.hpp" +#include <boost/asio/ssl/context.hpp> +#include <openssl/opensslv.h> +#include <openssl/crypto.h> +#include <openssl/ssl.h> +#include <openssl/ssl3.h> +#include <fstream> + +namespace icinga +{ + +static bool l_SSLInitialized = false; +static std::mutex *l_Mutexes; +static std::mutex l_RandomMutex; + +String GetOpenSSLVersion() +{ +#if OPENSSL_VERSION_NUMBER >= 0x10100000L + return OpenSSL_version(OPENSSL_VERSION); +#else /* OPENSSL_VERSION_NUMBER >= 0x10100000L */ + return SSLeay_version(SSLEAY_VERSION); +#endif /* OPENSSL_VERSION_NUMBER >= 0x10100000L */ +} + +#ifdef CRYPTO_LOCK +static void OpenSSLLockingCallback(int mode, int type, const char *, int) +{ + if (mode & CRYPTO_LOCK) + l_Mutexes[type].lock(); + else + l_Mutexes[type].unlock(); +} + +static unsigned long OpenSSLIDCallback() +{ +#ifdef _WIN32 + return (unsigned long)GetCurrentThreadId(); +#else /* _WIN32 */ + return (unsigned long)pthread_self(); +#endif /* _WIN32 */ +} +#endif /* CRYPTO_LOCK */ + +/** + * Initializes the OpenSSL library. + */ +void InitializeOpenSSL() +{ + if (l_SSLInitialized) + return; + + SSL_library_init(); + SSL_load_error_strings(); + + SSL_COMP_get_compression_methods(); + +#ifdef CRYPTO_LOCK + l_Mutexes = new std::mutex[CRYPTO_num_locks()]; + CRYPTO_set_locking_callback(&OpenSSLLockingCallback); + CRYPTO_set_id_callback(&OpenSSLIDCallback); +#endif /* CRYPTO_LOCK */ + + l_SSLInitialized = true; +} + +static void InitSslContext(const Shared<boost::asio::ssl::context>::Ptr& context, const String& pubkey, const String& privkey, const String& cakey) +{ + char errbuf[256]; + + // Enforce TLS v1.2 as minimum + context->set_options( + boost::asio::ssl::context::default_workarounds | + boost::asio::ssl::context::no_compression | + boost::asio::ssl::context::no_sslv2 | + boost::asio::ssl::context::no_sslv3 | + boost::asio::ssl::context::no_tlsv1 | + boost::asio::ssl::context::no_tlsv1_1 + ); + + // Custom TLS flags + SSL_CTX *sslContext = context->native_handle(); + + long flags = SSL_CTX_get_options(sslContext); + + flags |= SSL_OP_CIPHER_SERVER_PREFERENCE; + +#if OPENSSL_VERSION_NUMBER < 0x10100000L + SSL_CTX_set_info_callback(sslContext, [](const SSL* ssl, int where, int) { + if (where & SSL_CB_HANDSHAKE_DONE) { + ssl->s3->flags |= SSL3_FLAGS_NO_RENEGOTIATE_CIPHERS; + } + }); +#else /* OPENSSL_VERSION_NUMBER < 0x10100000L */ + flags |= SSL_OP_NO_RENEGOTIATION; +#endif /* OPENSSL_VERSION_NUMBER < 0x10100000L */ + + SSL_CTX_set_options(sslContext, flags); + + SSL_CTX_set_mode(sslContext, SSL_MODE_ENABLE_PARTIAL_WRITE | SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER); + SSL_CTX_set_session_id_context(sslContext, (const unsigned char *)"Icinga 2", 8); + + // Explicitly load ECC ciphers, required on el7 - https://github.com/Icinga/icinga2/issues/7247 + // SSL_CTX_set_ecdh_auto is deprecated and removed in OpenSSL 1.1.x - https://github.com/openssl/openssl/issues/1437 +#if OPENSSL_VERSION_NUMBER < 0x10100000L +# ifdef SSL_CTX_set_ecdh_auto + SSL_CTX_set_ecdh_auto(sslContext, 1); +# endif /* SSL_CTX_set_ecdh_auto */ +#endif /* OPENSSL_VERSION_NUMBER < 0x10100000L */ + +#if OPENSSL_VERSION_NUMBER >= 0x10100000L + // The built-in DH parameters have to be enabled explicitly to allow the use of ciphers that use a DHE key exchange. + // SSL_CTX_set_dh_auto is only documented in OpenSSL starting from version 3.0.0 but was already added in 1.1.0. + // https://github.com/openssl/openssl/commit/09599b52d4e295c380512ba39958a11994d63401 + // https://github.com/openssl/openssl/commit/0437309fdf544492e272943e892523653df2f189 + SSL_CTX_set_dh_auto(sslContext, 1); +#endif /* OPENSSL_VERSION_NUMBER >= 0x10100000L */ + + if (!pubkey.IsEmpty()) { + if (!SSL_CTX_use_certificate_chain_file(sslContext, pubkey.CStr())) { + ERR_error_string_n(ERR_peek_error(), errbuf, sizeof errbuf); + Log(LogCritical, "SSL") + << "Error with public key file '" << pubkey << "': " << ERR_peek_error() << ", \"" << errbuf << "\""; + BOOST_THROW_EXCEPTION(openssl_error() + << boost::errinfo_api_function("SSL_CTX_use_certificate_chain_file") + << errinfo_openssl_error(ERR_peek_error()) + << boost::errinfo_file_name(pubkey)); + } + } + + if (!privkey.IsEmpty()) { + if (!SSL_CTX_use_PrivateKey_file(sslContext, privkey.CStr(), SSL_FILETYPE_PEM)) { + ERR_error_string_n(ERR_peek_error(), errbuf, sizeof errbuf); + Log(LogCritical, "SSL") + << "Error with private key file '" << privkey << "': " << ERR_peek_error() << ", \"" << errbuf << "\""; + BOOST_THROW_EXCEPTION(openssl_error() + << boost::errinfo_api_function("SSL_CTX_use_PrivateKey_file") + << errinfo_openssl_error(ERR_peek_error()) + << boost::errinfo_file_name(privkey)); + } + + if (!SSL_CTX_check_private_key(sslContext)) { + ERR_error_string_n(ERR_peek_error(), errbuf, sizeof errbuf); + Log(LogCritical, "SSL") + << "Error checking private key '" << privkey << "': " << ERR_peek_error() << ", \"" << errbuf << "\""; + BOOST_THROW_EXCEPTION(openssl_error() + << boost::errinfo_api_function("SSL_CTX_check_private_key") + << errinfo_openssl_error(ERR_peek_error())); + } + } + + if (cakey.IsEmpty()) { + if (!SSL_CTX_set_default_verify_paths(sslContext)) { + ERR_error_string_n(ERR_peek_error(), errbuf, sizeof errbuf); + Log(LogCritical, "SSL") + << "Error loading system's root CAs: " << ERR_peek_error() << ", \"" << errbuf << "\""; + BOOST_THROW_EXCEPTION(openssl_error() + << boost::errinfo_api_function("SSL_CTX_set_default_verify_paths") + << errinfo_openssl_error(ERR_peek_error())); + } + } else { + if (!SSL_CTX_load_verify_locations(sslContext, cakey.CStr(), nullptr)) { + ERR_error_string_n(ERR_peek_error(), errbuf, sizeof errbuf); + Log(LogCritical, "SSL") + << "Error loading and verifying locations in ca key file '" << cakey << "': " << ERR_peek_error() << ", \"" << errbuf << "\""; + BOOST_THROW_EXCEPTION(openssl_error() + << boost::errinfo_api_function("SSL_CTX_load_verify_locations") + << errinfo_openssl_error(ERR_peek_error()) + << boost::errinfo_file_name(cakey)); + } + + STACK_OF(X509_NAME) *cert_names; + + cert_names = SSL_load_client_CA_file(cakey.CStr()); + if (!cert_names) { + ERR_error_string_n(ERR_peek_error(), errbuf, sizeof errbuf); + Log(LogCritical, "SSL") + << "Error loading client ca key file '" << cakey << "': " << ERR_peek_error() << ", \"" << errbuf << "\""; + BOOST_THROW_EXCEPTION(openssl_error() + << boost::errinfo_api_function("SSL_load_client_CA_file") + << errinfo_openssl_error(ERR_peek_error()) + << boost::errinfo_file_name(cakey)); + } + + SSL_CTX_set_client_CA_list(sslContext, cert_names); + } +} + +/** + * Initializes an SSL context using the specified certificates. + * + * @param pubkey The public key. + * @param privkey The matching private key. + * @param cakey CA certificate chain file. + * @returns An SSL context. + */ +Shared<boost::asio::ssl::context>::Ptr MakeAsioSslContext(const String& pubkey, const String& privkey, const String& cakey) +{ + namespace ssl = boost::asio::ssl; + + InitializeOpenSSL(); + + auto context (Shared<ssl::context>::Make(ssl::context::tls)); + + InitSslContext(context, pubkey, privkey, cakey); + + return context; +} + +/** + * Set the cipher list to the specified SSL context. + * @param context The ssl context. + * @param cipherList The ciper list. + **/ +void SetCipherListToSSLContext(const Shared<boost::asio::ssl::context>::Ptr& context, const String& cipherList) +{ + char errbuf[256]; + + if (SSL_CTX_set_cipher_list(context->native_handle(), cipherList.CStr()) == 0) { + ERR_error_string_n(ERR_peek_error(), errbuf, sizeof errbuf); + Log(LogCritical, "SSL") + << "Cipher list '" + << cipherList + << "' does not specify any usable ciphers: " + << ERR_peek_error() << ", \"" + << errbuf << "\""; + + BOOST_THROW_EXCEPTION(openssl_error() + << boost::errinfo_api_function("SSL_CTX_set_cipher_list") + << errinfo_openssl_error(ERR_peek_error())); + } + +#if OPENSSL_VERSION_NUMBER >= 0x10100000L + //With OpenSSL 1.1.0, there might not be any returned 0. + STACK_OF(SSL_CIPHER) *ciphers; + Array::Ptr cipherNames = new Array(); + + ciphers = SSL_CTX_get_ciphers(context->native_handle()); + for (int i = 0; i < sk_SSL_CIPHER_num(ciphers); i++) { + const SSL_CIPHER *cipher = sk_SSL_CIPHER_value(ciphers, i); + String cipher_name = SSL_CIPHER_get_name(cipher); + + cipherNames->Add(cipher_name); + } + + Log(LogNotice, "TlsUtility") + << "Available TLS cipher list: " << cipherNames->Join(" "); +#endif /* OPENSSL_VERSION_NUMBER >= 0x10100000L */ +} + +/** + * Resolves a string describing a TLS protocol version to the value of a TLS*_VERSION macro of OpenSSL. + * + * Throws an exception if the version is unknown or not supported. + * + * @param version String of a TLS version, for example "TLSv1.2". + * @return The value of the corresponding TLS*_VERSION macro. + */ +int ResolveTlsProtocolVersion(const std::string& version) { + if (version == "TLSv1.2") { + return TLS1_2_VERSION; + } else if (version == "TLSv1.3") { +#if OPENSSL_VERSION_NUMBER >= 0x10101000L + return TLS1_3_VERSION; +#else /* OPENSSL_VERSION_NUMBER >= 0x10101000L */ + throw std::runtime_error("'" + version + "' is only supported with OpenSSL 1.1.1 or newer"); +#endif /* OPENSSL_VERSION_NUMBER >= 0x10101000L */ + } else { + throw std::runtime_error("Unknown TLS protocol version '" + version + "'"); + } +} + +Shared<boost::asio::ssl::context>::Ptr SetupSslContext(String certPath, String keyPath, + String caPath, String crlPath, String cipherList, String protocolmin, DebugInfo di) +{ + namespace ssl = boost::asio::ssl; + + Shared<ssl::context>::Ptr context; + + try { + context = MakeAsioSslContext(certPath, keyPath, caPath); + } catch (const std::exception&) { + BOOST_THROW_EXCEPTION(ScriptError("Cannot make SSL context for cert path: '" + + certPath + "' key path: '" + keyPath + "' ca path: '" + caPath + "'.", di)); + } + + if (!crlPath.IsEmpty()) { + try { + AddCRLToSSLContext(context, crlPath); + } catch (const std::exception&) { + BOOST_THROW_EXCEPTION(ScriptError("Cannot add certificate revocation list to SSL context for crl path: '" + + crlPath + "'.", di)); + } + } + + if (!cipherList.IsEmpty()) { + try { + SetCipherListToSSLContext(context, cipherList); + } catch (const std::exception&) { + BOOST_THROW_EXCEPTION(ScriptError("Cannot set cipher list to SSL context for cipher list: '" + + cipherList + "'.", di)); + } + } + + if (!protocolmin.IsEmpty()){ + try { + SetTlsProtocolminToSSLContext(context, protocolmin); + } catch (const std::exception&) { + BOOST_THROW_EXCEPTION(ScriptError("Cannot set minimum TLS protocol version to SSL context with tls_protocolmin: '" + protocolmin + "'.", di)); + } + } + + return context; +} + +/** + * Set the minimum TLS protocol version to the specified SSL context. + * + * @param context The ssl context. + * @param tlsProtocolmin The minimum TLS protocol version. + */ +void SetTlsProtocolminToSSLContext(const Shared<boost::asio::ssl::context>::Ptr& context, const String& tlsProtocolmin) +{ +#if OPENSSL_VERSION_NUMBER >= 0x10100000L + int ret = SSL_CTX_set_min_proto_version(context->native_handle(), ResolveTlsProtocolVersion(tlsProtocolmin)); + + if (ret != 1) { + char errbuf[256]; + + ERR_error_string_n(ERR_peek_error(), errbuf, sizeof errbuf); + Log(LogCritical, "SSL") + << "Error setting minimum TLS protocol version: " << ERR_peek_error() << ", \"" << errbuf << "\""; + BOOST_THROW_EXCEPTION(openssl_error() + << boost::errinfo_api_function("SSL_CTX_set_min_proto_version") + << errinfo_openssl_error(ERR_peek_error())); + } +#else /* OPENSSL_VERSION_NUMBER >= 0x10100000L */ + // This should never happen. On this OpenSSL version, ResolveTlsProtocolVersion() should either return TLS 1.2 + // or throw an exception, as that's the only TLS version supported by both Icinga and ancient OpenSSL. + VERIFY(ResolveTlsProtocolVersion(tlsProtocolmin) == TLS1_2_VERSION); +#endif /* OPENSSL_VERSION_NUMBER >= 0x10100000L */ +} + +/** + * Loads a CRL and appends its certificates to the specified Boost SSL context. + * + * @param context The SSL context. + * @param crlPath The path to the CRL file. + */ +void AddCRLToSSLContext(const Shared<boost::asio::ssl::context>::Ptr& context, const String& crlPath) +{ + X509_STORE *x509_store = SSL_CTX_get_cert_store(context->native_handle()); + AddCRLToSSLContext(x509_store, crlPath); +} + +/** + * Loads a CRL and appends its certificates to the specified OpenSSL X509 store. + * + * @param context The SSL context. + * @param crlPath The path to the CRL file. + */ +void AddCRLToSSLContext(X509_STORE *x509_store, const String& crlPath) +{ + char errbuf[256]; + + X509_LOOKUP *lookup; + lookup = X509_STORE_add_lookup(x509_store, X509_LOOKUP_file()); + + if (!lookup) { + ERR_error_string_n(ERR_peek_error(), errbuf, sizeof errbuf); + Log(LogCritical, "SSL") + << "Error adding X509 store lookup: " << ERR_peek_error() << ", \"" << errbuf << "\""; + BOOST_THROW_EXCEPTION(openssl_error() + << boost::errinfo_api_function("X509_STORE_add_lookup") + << errinfo_openssl_error(ERR_peek_error())); + } + + if (X509_LOOKUP_load_file(lookup, crlPath.CStr(), X509_FILETYPE_PEM) != 1) { + ERR_error_string_n(ERR_peek_error(), errbuf, sizeof errbuf); + Log(LogCritical, "SSL") + << "Error loading crl file '" << crlPath << "': " << ERR_peek_error() << ", \"" << errbuf << "\""; + BOOST_THROW_EXCEPTION(openssl_error() + << boost::errinfo_api_function("X509_LOOKUP_load_file") + << errinfo_openssl_error(ERR_peek_error()) + << boost::errinfo_file_name(crlPath)); + } + + X509_VERIFY_PARAM *param = X509_VERIFY_PARAM_new(); + X509_VERIFY_PARAM_set_flags(param, X509_V_FLAG_CRL_CHECK); + X509_STORE_set1_param(x509_store, param); + X509_VERIFY_PARAM_free(param); +} + +static String GetX509NameCN(X509_NAME *name) +{ + char errbuf[256]; + char buffer[256]; + + int rc = X509_NAME_get_text_by_NID(name, NID_commonName, buffer, sizeof(buffer)); + + if (rc == -1) { + ERR_error_string_n(ERR_peek_error(), errbuf, sizeof errbuf); + Log(LogCritical, "SSL") + << "Error with x509 NAME getting text by NID: " << ERR_peek_error() << ", \"" << errbuf << "\""; + BOOST_THROW_EXCEPTION(openssl_error() + << boost::errinfo_api_function("X509_NAME_get_text_by_NID") + << errinfo_openssl_error(ERR_peek_error())); + } + + return buffer; +} + +/** + * Retrieves the common name for an X509 certificate. + * + * @param certificate The X509 certificate. + * @returns The common name. + */ +String GetCertificateCN(const std::shared_ptr<X509>& certificate) +{ + return GetX509NameCN(X509_get_subject_name(certificate.get())); +} + +/** + * Retrieves an X509 certificate from the specified file. + * + * @param pemfile The filename. + * @returns An X509 certificate. + */ +std::shared_ptr<X509> GetX509Certificate(const String& pemfile) +{ + char errbuf[256]; + X509 *cert; + BIO *fpcert = BIO_new(BIO_s_file()); + + if (!fpcert) { + ERR_error_string_n(ERR_peek_error(), errbuf, sizeof errbuf); + Log(LogCritical, "SSL") + << "Error creating new BIO: " << ERR_peek_error() << ", \"" << errbuf << "\""; + BOOST_THROW_EXCEPTION(openssl_error() + << boost::errinfo_api_function("BIO_new") + << errinfo_openssl_error(ERR_peek_error())); + } + + if (BIO_read_filename(fpcert, pemfile.CStr()) < 0) { + ERR_error_string_n(ERR_peek_error(), errbuf, sizeof errbuf); + Log(LogCritical, "SSL") + << "Error reading pem file '" << pemfile << "': " << ERR_peek_error() << ", \"" << errbuf << "\""; + BOOST_THROW_EXCEPTION(openssl_error() + << boost::errinfo_api_function("BIO_read_filename") + << errinfo_openssl_error(ERR_peek_error()) + << boost::errinfo_file_name(pemfile)); + } + + cert = PEM_read_bio_X509_AUX(fpcert, nullptr, nullptr, nullptr); + if (!cert) { + ERR_error_string_n(ERR_peek_error(), errbuf, sizeof errbuf); + Log(LogCritical, "SSL") + << "Error on bio X509 AUX reading pem file '" << pemfile << "': " << ERR_peek_error() << ", \"" << errbuf << "\""; + BOOST_THROW_EXCEPTION(openssl_error() + << boost::errinfo_api_function("PEM_read_bio_X509_AUX") + << errinfo_openssl_error(ERR_peek_error()) + << boost::errinfo_file_name(pemfile)); + } + + BIO_free(fpcert); + + return std::shared_ptr<X509>(cert, X509_free); +} + +int MakeX509CSR(const String& cn, const String& keyfile, const String& csrfile, const String& certfile, bool ca) +{ + char errbuf[256]; + + InitializeOpenSSL(); + + RSA *rsa = RSA_new(); + BIGNUM *e = BN_new(); + + if (!rsa || !e) { + ERR_error_string_n(ERR_peek_error(), errbuf, sizeof errbuf); + Log(LogCritical, "SSL") + << "Error while creating RSA key: " << ERR_peek_error() << ", \"" << errbuf << "\""; + BOOST_THROW_EXCEPTION(openssl_error() + << boost::errinfo_api_function("RSA_generate_key") + << errinfo_openssl_error(ERR_peek_error())); + } + + BN_set_word(e, RSA_F4); + + if (!RSA_generate_key_ex(rsa, 4096, e, nullptr)) { + ERR_error_string_n(ERR_peek_error(), errbuf, sizeof errbuf); + Log(LogCritical, "SSL") + << "Error while creating RSA key: " << ERR_peek_error() << ", \"" << errbuf << "\""; + BOOST_THROW_EXCEPTION(openssl_error() + << boost::errinfo_api_function("RSA_generate_key") + << errinfo_openssl_error(ERR_peek_error())); + } + + BN_free(e); + + Log(LogInformation, "base") + << "Writing private key to '" << keyfile << "'."; + + BIO *bio = BIO_new_file(const_cast<char *>(keyfile.CStr()), "w"); + + if (!bio) { + ERR_error_string_n(ERR_peek_error(), errbuf, sizeof errbuf); + Log(LogCritical, "SSL") + << "Error while opening private RSA key file '" << keyfile << "': " << ERR_peek_error() << ", \"" << errbuf << "\""; + BOOST_THROW_EXCEPTION(openssl_error() + << boost::errinfo_api_function("BIO_new_file") + << errinfo_openssl_error(ERR_peek_error()) + << boost::errinfo_file_name(keyfile)); + } + + if (!PEM_write_bio_RSAPrivateKey(bio, rsa, nullptr, nullptr, 0, nullptr, nullptr)) { + ERR_error_string_n(ERR_peek_error(), errbuf, sizeof errbuf); + Log(LogCritical, "SSL") + << "Error while writing private RSA key to file '" << keyfile << "': " << ERR_peek_error() << ", \"" << errbuf << "\""; + BOOST_THROW_EXCEPTION(openssl_error() + << boost::errinfo_api_function("PEM_write_bio_RSAPrivateKey") + << errinfo_openssl_error(ERR_peek_error()) + << boost::errinfo_file_name(keyfile)); + } + + BIO_free(bio); + +#ifndef _WIN32 + chmod(keyfile.CStr(), 0600); +#endif /* _WIN32 */ + + EVP_PKEY *key = EVP_PKEY_new(); + EVP_PKEY_assign_RSA(key, rsa); + + if (!certfile.IsEmpty()) { + X509_NAME *subject = X509_NAME_new(); + X509_NAME_add_entry_by_txt(subject, "CN", MBSTRING_ASC, (unsigned char *)cn.CStr(), -1, -1, 0); + + std::shared_ptr<X509> cert = CreateCert(key, subject, subject, key, ca); + + X509_NAME_free(subject); + + Log(LogInformation, "base") + << "Writing X509 certificate to '" << certfile << "'."; + + bio = BIO_new_file(const_cast<char *>(certfile.CStr()), "w"); + + if (!bio) { + ERR_error_string_n(ERR_peek_error(), errbuf, sizeof errbuf); + Log(LogCritical, "SSL") + << "Error while opening certificate file '" << certfile << "': " << ERR_peek_error() << ", \"" << errbuf << "\""; + BOOST_THROW_EXCEPTION(openssl_error() + << boost::errinfo_api_function("BIO_new_file") + << errinfo_openssl_error(ERR_peek_error()) + << boost::errinfo_file_name(certfile)); + } + + if (!PEM_write_bio_X509(bio, cert.get())) { + ERR_error_string_n(ERR_peek_error(), errbuf, sizeof errbuf); + Log(LogCritical, "SSL") + << "Error while writing certificate to file '" << certfile << "': " << ERR_peek_error() << ", \"" << errbuf << "\""; + BOOST_THROW_EXCEPTION(openssl_error() + << boost::errinfo_api_function("PEM_write_bio_X509") + << errinfo_openssl_error(ERR_peek_error()) + << boost::errinfo_file_name(certfile)); + } + + BIO_free(bio); + } + + if (!csrfile.IsEmpty()) { + X509_REQ *req = X509_REQ_new(); + + if (!req) + return 0; + + X509_REQ_set_version(req, 0); + X509_REQ_set_pubkey(req, key); + + X509_NAME *name = X509_REQ_get_subject_name(req); + X509_NAME_add_entry_by_txt(name, "CN", MBSTRING_ASC, (unsigned char *)cn.CStr(), -1, -1, 0); + + if (!ca) { + String san = "DNS:" + cn; + X509_EXTENSION *subjectAltNameExt = X509V3_EXT_conf_nid(nullptr, nullptr, NID_subject_alt_name, const_cast<char *>(san.CStr())); + if (subjectAltNameExt) { + /* OpenSSL 0.9.8 requires STACK_OF(X509_EXTENSION), otherwise we would just use stack_st_X509_EXTENSION. */ + STACK_OF(X509_EXTENSION) *exts = sk_X509_EXTENSION_new_null(); + sk_X509_EXTENSION_push(exts, subjectAltNameExt); + X509_REQ_add_extensions(req, exts); + sk_X509_EXTENSION_pop_free(exts, X509_EXTENSION_free); + } + } + + X509_REQ_sign(req, key, EVP_sha256()); + + Log(LogInformation, "base") + << "Writing certificate signing request to '" << csrfile << "'."; + + bio = BIO_new_file(const_cast<char *>(csrfile.CStr()), "w"); + + if (!bio) { + ERR_error_string_n(ERR_peek_error(), errbuf, sizeof errbuf); + Log(LogCritical, "SSL") + << "Error while opening CSR file '" << csrfile << "': " << ERR_peek_error() << ", \"" << errbuf << "\""; + BOOST_THROW_EXCEPTION(openssl_error() + << boost::errinfo_api_function("BIO_new_file") + << errinfo_openssl_error(ERR_peek_error()) + << boost::errinfo_file_name(csrfile)); + } + + if (!PEM_write_bio_X509_REQ(bio, req)) { + ERR_error_string_n(ERR_peek_error(), errbuf, sizeof errbuf); + Log(LogCritical, "SSL") + << "Error while writing CSR to file '" << csrfile << "': " << ERR_peek_error() << ", \"" << errbuf << "\""; + BOOST_THROW_EXCEPTION(openssl_error() + << boost::errinfo_api_function("PEM_write_bio_X509") + << errinfo_openssl_error(ERR_peek_error()) + << boost::errinfo_file_name(csrfile)); + } + + BIO_free(bio); + + X509_REQ_free(req); + } + + EVP_PKEY_free(key); + + return 1; +} + +std::shared_ptr<X509> CreateCert(EVP_PKEY *pubkey, X509_NAME *subject, X509_NAME *issuer, EVP_PKEY *cakey, bool ca) +{ + X509 *cert = X509_new(); + X509_set_version(cert, 2); + X509_gmtime_adj(X509_get_notBefore(cert), 0); + X509_gmtime_adj(X509_get_notAfter(cert), ca ? ROOT_VALID_FOR : LEAF_VALID_FOR); + X509_set_pubkey(cert, pubkey); + + X509_set_subject_name(cert, subject); + X509_set_issuer_name(cert, issuer); + + String id = Utility::NewUniqueID(); + + char errbuf[256]; + SHA_CTX context; + unsigned char digest[SHA_DIGEST_LENGTH]; + + if (!SHA1_Init(&context)) { + ERR_error_string_n(ERR_peek_error(), errbuf, sizeof errbuf); + Log(LogCritical, "SSL") + << "Error on SHA1 Init: " << ERR_peek_error() << ", \"" << errbuf << "\""; + BOOST_THROW_EXCEPTION(openssl_error() + << boost::errinfo_api_function("SHA1_Init") + << errinfo_openssl_error(ERR_peek_error())); + } + + if (!SHA1_Update(&context, (unsigned char*)id.CStr(), id.GetLength())) { + ERR_error_string_n(ERR_peek_error(), errbuf, sizeof errbuf); + Log(LogCritical, "SSL") + << "Error on SHA1 Update: " << ERR_peek_error() << ", \"" << errbuf << "\""; + BOOST_THROW_EXCEPTION(openssl_error() + << boost::errinfo_api_function("SHA1_Update") + << errinfo_openssl_error(ERR_peek_error())); + } + + if (!SHA1_Final(digest, &context)) { + ERR_error_string_n(ERR_peek_error(), errbuf, sizeof errbuf); + Log(LogCritical, "SSL") + << "Error on SHA1 Final: " << ERR_peek_error() << ", \"" << errbuf << "\""; + BOOST_THROW_EXCEPTION(openssl_error() + << boost::errinfo_api_function("SHA1_Final") + << errinfo_openssl_error(ERR_peek_error())); + } + + BIGNUM *bn = BN_new(); + BN_bin2bn(digest, sizeof(digest), bn); + BN_to_ASN1_INTEGER(bn, X509_get_serialNumber(cert)); + BN_free(bn); + + X509V3_CTX ctx; + X509V3_set_ctx_nodb(&ctx); + X509V3_set_ctx(&ctx, cert, cert, nullptr, nullptr, 0); + + const char *attr; + + if (ca) + attr = "critical,CA:TRUE"; + else + attr = "critical,CA:FALSE"; + + X509_EXTENSION *basicConstraintsExt = X509V3_EXT_conf_nid(nullptr, &ctx, NID_basic_constraints, const_cast<char *>(attr)); + + if (basicConstraintsExt) { + X509_add_ext(cert, basicConstraintsExt, -1); + X509_EXTENSION_free(basicConstraintsExt); + } + + String cn = GetX509NameCN(subject); + + if (!ca) { + String san = "DNS:" + cn; + X509_EXTENSION *subjectAltNameExt = X509V3_EXT_conf_nid(nullptr, &ctx, NID_subject_alt_name, const_cast<char *>(san.CStr())); + if (subjectAltNameExt) { + X509_add_ext(cert, subjectAltNameExt, -1); + X509_EXTENSION_free(subjectAltNameExt); + } + } + + X509_sign(cert, cakey, EVP_sha256()); + + return std::shared_ptr<X509>(cert, X509_free); +} + +String GetIcingaCADir() +{ + return Configuration::DataDir + "/ca"; +} + +std::shared_ptr<X509> CreateCertIcingaCA(EVP_PKEY *pubkey, X509_NAME *subject, bool ca) +{ + char errbuf[256]; + + String cadir = GetIcingaCADir(); + + String cakeyfile = cadir + "/ca.key"; + + RSA *rsa; + + BIO *cakeybio = BIO_new_file(const_cast<char *>(cakeyfile.CStr()), "r"); + + if (!cakeybio) { + ERR_error_string_n(ERR_peek_error(), errbuf, sizeof errbuf); + Log(LogCritical, "SSL") + << "Could not open CA key file '" << cakeyfile << "': " << ERR_peek_error() << ", \"" << errbuf << "\""; + return std::shared_ptr<X509>(); + } + + rsa = PEM_read_bio_RSAPrivateKey(cakeybio, nullptr, nullptr, nullptr); + + if (!rsa) { + ERR_error_string_n(ERR_peek_error(), errbuf, sizeof errbuf); + Log(LogCritical, "SSL") + << "Could not read RSA key from CA key file '" << cakeyfile << "': " << ERR_peek_error() << ", \"" << errbuf << "\""; + return std::shared_ptr<X509>(); + } + + BIO_free(cakeybio); + + String cacertfile = cadir + "/ca.crt"; + + std::shared_ptr<X509> cacert = GetX509Certificate(cacertfile); + + EVP_PKEY *privkey = EVP_PKEY_new(); + EVP_PKEY_assign_RSA(privkey, rsa); + + return CreateCert(pubkey, subject, X509_get_subject_name(cacert.get()), privkey, ca); +} + +std::shared_ptr<X509> CreateCertIcingaCA(const std::shared_ptr<X509>& cert) +{ + std::shared_ptr<EVP_PKEY> pkey = std::shared_ptr<EVP_PKEY>(X509_get_pubkey(cert.get()), EVP_PKEY_free); + return CreateCertIcingaCA(pkey.get(), X509_get_subject_name(cert.get())); +} + +static inline +bool CertExpiresWithin(X509* cert, int seconds) +{ + time_t renewalStart = time(nullptr) + seconds; + + return X509_cmp_time(X509_get_notAfter(cert), &renewalStart) < 0; +} + +bool IsCertUptodate(const std::shared_ptr<X509>& cert) +{ + if (CertExpiresWithin(cert.get(), RENEW_THRESHOLD)) { + return false; + } + + /* auto-renew all certificates which were created before 2017 to force an update of the CA, + * because Icinga versions older than 2.4 sometimes create certificates with an invalid + * serial number. */ + time_t forceRenewalEnd = 1483228800; /* January 1st, 2017 */ + + return X509_cmp_time(X509_get_notBefore(cert.get()), &forceRenewalEnd) >= 0; +} + +bool IsCaUptodate(X509* cert) +{ + return !CertExpiresWithin(cert, LEAF_VALID_FOR); +} + +String CertificateToString(X509* cert) +{ + BIO *mem = BIO_new(BIO_s_mem()); + PEM_write_bio_X509(mem, cert); + + char *data; + long len = BIO_get_mem_data(mem, &data); + + String result = String(data, data + len); + + BIO_free(mem); + + return result; +} + +std::shared_ptr<X509> StringToCertificate(const String& cert) +{ + BIO *bio = BIO_new(BIO_s_mem()); + BIO_write(bio, (const void *)cert.CStr(), cert.GetLength()); + + X509 *rawCert = PEM_read_bio_X509_AUX(bio, nullptr, nullptr, nullptr); + + BIO_free(bio); + + if (!rawCert) + BOOST_THROW_EXCEPTION(std::invalid_argument("The specified X509 certificate is invalid.")); + + return std::shared_ptr<X509>(rawCert, X509_free); +} + +String PBKDF2_SHA1(const String& password, const String& salt, int iterations) +{ + unsigned char digest[SHA_DIGEST_LENGTH]; + PKCS5_PBKDF2_HMAC_SHA1(password.CStr(), password.GetLength(), reinterpret_cast<const unsigned char *>(salt.CStr()), salt.GetLength(), + iterations, sizeof(digest), digest); + + char output[SHA_DIGEST_LENGTH*2+1]; + for (int i = 0; i < SHA_DIGEST_LENGTH; i++) + sprintf(output + 2 * i, "%02x", digest[i]); + + return output; +} + +String PBKDF2_SHA256(const String& password, const String& salt, int iterations) +{ + unsigned char digest[SHA256_DIGEST_LENGTH]; + PKCS5_PBKDF2_HMAC(password.CStr(), password.GetLength(), reinterpret_cast<const unsigned char *>(salt.CStr()), + salt.GetLength(), iterations, EVP_sha256(), SHA256_DIGEST_LENGTH, digest); + + char output[SHA256_DIGEST_LENGTH*2+1]; + for (int i = 0; i < SHA256_DIGEST_LENGTH; i++) + sprintf(output + 2 * i, "%02x", digest[i]); + + return output; +} + +String SHA1(const String& s, bool binary) +{ + char errbuf[256]; + SHA_CTX context; + unsigned char digest[SHA_DIGEST_LENGTH]; + + if (!SHA1_Init(&context)) { + ERR_error_string_n(ERR_peek_error(), errbuf, sizeof errbuf); + Log(LogCritical, "SSL") + << "Error on SHA Init: " << ERR_peek_error() << ", \"" << errbuf << "\""; + BOOST_THROW_EXCEPTION(openssl_error() + << boost::errinfo_api_function("SHA1_Init") + << errinfo_openssl_error(ERR_peek_error())); + } + + if (!SHA1_Update(&context, (unsigned char*)s.CStr(), s.GetLength())) { + ERR_error_string_n(ERR_peek_error(), errbuf, sizeof errbuf); + Log(LogCritical, "SSL") + << "Error on SHA Update: " << ERR_peek_error() << ", \"" << errbuf << "\""; + BOOST_THROW_EXCEPTION(openssl_error() + << boost::errinfo_api_function("SHA1_Update") + << errinfo_openssl_error(ERR_peek_error())); + } + + if (!SHA1_Final(digest, &context)) { + ERR_error_string_n(ERR_peek_error(), errbuf, sizeof errbuf); + Log(LogCritical, "SSL") + << "Error on SHA Final: " << ERR_peek_error() << ", \"" << errbuf << "\""; + BOOST_THROW_EXCEPTION(openssl_error() + << boost::errinfo_api_function("SHA1_Final") + << errinfo_openssl_error(ERR_peek_error())); + } + + if (binary) + return String(reinterpret_cast<const char*>(digest), reinterpret_cast<const char *>(digest + SHA_DIGEST_LENGTH)); + + return BinaryToHex(digest, SHA_DIGEST_LENGTH); +} + +String SHA256(const String& s) +{ + char errbuf[256]; + SHA256_CTX context; + unsigned char digest[SHA256_DIGEST_LENGTH]; + + if (!SHA256_Init(&context)) { + ERR_error_string_n(ERR_peek_error(), errbuf, sizeof errbuf); + Log(LogCritical, "SSL") + << "Error on SHA256 Init: " << ERR_peek_error() << ", \"" << errbuf << "\""; + BOOST_THROW_EXCEPTION(openssl_error() + << boost::errinfo_api_function("SHA256_Init") + << errinfo_openssl_error(ERR_peek_error())); + } + + if (!SHA256_Update(&context, (unsigned char*)s.CStr(), s.GetLength())) { + ERR_error_string_n(ERR_peek_error(), errbuf, sizeof errbuf); + Log(LogCritical, "SSL") + << "Error on SHA256 Update: " << ERR_peek_error() << ", \"" << errbuf << "\""; + BOOST_THROW_EXCEPTION(openssl_error() + << boost::errinfo_api_function("SHA256_Update") + << errinfo_openssl_error(ERR_peek_error())); + } + + if (!SHA256_Final(digest, &context)) { + ERR_error_string_n(ERR_peek_error(), errbuf, sizeof errbuf); + Log(LogCritical, "SSL") + << "Error on SHA256 Final: " << ERR_peek_error() << ", \"" << errbuf << "\""; + BOOST_THROW_EXCEPTION(openssl_error() + << boost::errinfo_api_function("SHA256_Final") + << errinfo_openssl_error(ERR_peek_error())); + } + + char output[SHA256_DIGEST_LENGTH*2+1]; + for (int i = 0; i < 32; i++) + sprintf(output + 2 * i, "%02x", digest[i]); + + return output; +} + +String RandomString(int length) +{ + auto *bytes = new unsigned char[length]; + + /* Ensure that password generation is atomic. RAND_bytes is not thread-safe + * in OpenSSL < 1.1.0. + */ + std::unique_lock<std::mutex> lock(l_RandomMutex); + + if (!RAND_bytes(bytes, length)) { + delete [] bytes; + + char errbuf[256]; + ERR_error_string_n(ERR_peek_error(), errbuf, sizeof errbuf); + + Log(LogCritical, "SSL") + << "Error for RAND_bytes: " << ERR_peek_error() << ", \"" << errbuf << "\""; + BOOST_THROW_EXCEPTION(openssl_error() + << boost::errinfo_api_function("RAND_bytes") + << errinfo_openssl_error(ERR_peek_error())); + } + + lock.unlock(); + + auto *output = new char[length * 2 + 1]; + for (int i = 0; i < length; i++) + sprintf(output + 2 * i, "%02x", bytes[i]); + + String result = output; + delete [] bytes; + delete [] output; + + return result; +} + +String BinaryToHex(const unsigned char* data, size_t length) { + static const char hexdigits[] = "0123456789abcdef"; + + String output(2*length, 0); + for (int i = 0; i < SHA_DIGEST_LENGTH; i++) { + output[2 * i] = hexdigits[data[i] >> 4]; + output[2 * i + 1] = hexdigits[data[i] & 0xf]; + } + + return output; +} + +bool VerifyCertificate(const std::shared_ptr<X509> &caCertificate, const std::shared_ptr<X509> &certificate, const String& crlFile) +{ + X509_STORE *store = X509_STORE_new(); + + if (!store) + return false; + + X509_STORE_add_cert(store, caCertificate.get()); + + if (!crlFile.IsEmpty()) { + AddCRLToSSLContext(store, crlFile); + } + + X509_STORE_CTX *csc = X509_STORE_CTX_new(); + X509_STORE_CTX_init(csc, store, certificate.get(), nullptr); + + int rc = X509_verify_cert(csc); + + X509_STORE_CTX_free(csc); + X509_STORE_free(store); + + if (rc == 0) { + int err = X509_STORE_CTX_get_error(csc); + + BOOST_THROW_EXCEPTION(openssl_error() + << boost::errinfo_api_function("X509_verify_cert") + << errinfo_openssl_error(err)); + } + + return rc == 1; +} + +bool IsCa(const std::shared_ptr<X509>& cacert) +{ +#if OPENSSL_VERSION_NUMBER >= 0x10100000L + /* OpenSSL 1.1.x provides https://www.openssl.org/docs/man1.1.0/man3/X509_check_ca.html + * + * 0 if it is not CA certificate, + * 1 if it is proper X509v3 CA certificate with basicConstraints extension CA:TRUE, + * 3 if it is self-signed X509 v1 certificate + * 4 if it is certificate with keyUsage extension with bit keyCertSign set, but without basicConstraints, + * 5 if it has outdated Netscape Certificate Type extension telling that it is CA certificate. + */ + return (X509_check_ca(cacert.get()) == 1); +#else /* OPENSSL_VERSION_NUMBER >= 0x10100000L */ + BOOST_THROW_EXCEPTION(std::invalid_argument("Not supported on this platform, OpenSSL version too old.")); +#endif /* OPENSSL_VERSION_NUMBER >= 0x10100000L */ +} + +int GetCertificateVersion(const std::shared_ptr<X509>& cert) +{ + return X509_get_version(cert.get()) + 1; +} + +String GetSignatureAlgorithm(const std::shared_ptr<X509>& cert) +{ + int alg; + int sign_alg; + X509_PUBKEY *key; + X509_ALGOR *algor; + + key = X509_get_X509_PUBKEY(cert.get()); + + X509_PUBKEY_get0_param(nullptr, nullptr, 0, &algor, key); //TODO: Error handling + + alg = OBJ_obj2nid (algor->algorithm); + +#if OPENSSL_VERSION_NUMBER < 0x10100000L + sign_alg = OBJ_obj2nid((cert.get())->sig_alg->algorithm); +#else /* OPENSSL_VERSION_NUMBER < 0x10100000L */ + sign_alg = X509_get_signature_nid(cert.get()); +#endif /* OPENSSL_VERSION_NUMBER < 0x10100000L */ + + return Convert::ToString((sign_alg == NID_undef) ? "Unknown" : OBJ_nid2ln(sign_alg)); +} + +Array::Ptr GetSubjectAltNames(const std::shared_ptr<X509>& cert) +{ + GENERAL_NAMES* subjectAltNames = (GENERAL_NAMES*)X509_get_ext_d2i(cert.get(), NID_subject_alt_name, nullptr, nullptr); + + Array::Ptr sans = new Array(); + + for (int i = 0; i < sk_GENERAL_NAME_num(subjectAltNames); i++) { + GENERAL_NAME* gen = sk_GENERAL_NAME_value(subjectAltNames, i); + if (gen->type == GEN_URI || gen->type == GEN_DNS || gen->type == GEN_EMAIL) { + ASN1_IA5STRING *asn1_str = gen->d.uniformResourceIdentifier; + +#if OPENSSL_VERSION_NUMBER < 0x10100000L + String san = Convert::ToString(ASN1_STRING_data(asn1_str)); +#else /* OPENSSL_VERSION_NUMBER < 0x10100000L */ + String san = Convert::ToString(ASN1_STRING_get0_data(asn1_str)); +#endif /* OPENSSL_VERSION_NUMBER < 0x10100000L */ + + sans->Add(san); + } + } + + GENERAL_NAMES_free(subjectAltNames); + + return sans; +} + +} diff --git a/lib/base/tlsutility.hpp b/lib/base/tlsutility.hpp new file mode 100644 index 0000000..b064120 --- /dev/null +++ b/lib/base/tlsutility.hpp @@ -0,0 +1,94 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#ifndef TLSUTILITY_H +#define TLSUTILITY_H + +#include "base/i2-base.hpp" +#include "base/debuginfo.hpp" +#include "base/object.hpp" +#include "base/shared.hpp" +#include "base/array.hpp" +#include "base/string.hpp" +#include <openssl/ssl.h> +#include <openssl/bio.h> +#include <openssl/err.h> +#include <openssl/comp.h> +#include <openssl/sha.h> +#include <openssl/pem.h> +#include <openssl/x509.h> +#include <openssl/x509v3.h> +#include <openssl/evp.h> +#include <openssl/rand.h> +#include <boost/asio/ssl/context.hpp> +#include <boost/exception/info.hpp> + +namespace icinga +{ + +// Source: https://ssl-config.mozilla.org, i.e. +// ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-CHACHA20-POLY1305 +// Modified so that AES256 is preferred over AES128. +const char * const DEFAULT_TLS_CIPHERS = "ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256"; + +const char * const DEFAULT_TLS_PROTOCOLMIN = "TLSv1.2"; +const unsigned int DEFAULT_CONNECT_TIMEOUT = 15; + +const auto ROOT_VALID_FOR = 60 * 60 * 24 * 365 * 15; +const auto LEAF_VALID_FOR = 60 * 60 * 24 * 397; +const auto RENEW_THRESHOLD = 60 * 60 * 24 * 30; +const auto RENEW_INTERVAL = 60 * 60 * 24; + +void InitializeOpenSSL(); + +String GetOpenSSLVersion(); + +Shared<boost::asio::ssl::context>::Ptr MakeAsioSslContext(const String& pubkey = String(), const String& privkey = String(), const String& cakey = String()); +void AddCRLToSSLContext(const Shared<boost::asio::ssl::context>::Ptr& context, const String& crlPath); +void AddCRLToSSLContext(X509_STORE *x509_store, const String& crlPath); +void SetCipherListToSSLContext(const Shared<boost::asio::ssl::context>::Ptr& context, const String& cipherList); +void SetTlsProtocolminToSSLContext(const Shared<boost::asio::ssl::context>::Ptr& context, const String& tlsProtocolmin); +int ResolveTlsProtocolVersion(const std::string& version); + +Shared<boost::asio::ssl::context>::Ptr SetupSslContext(String certPath, String keyPath, + String caPath, String crlPath, String cipherList, String protocolmin, DebugInfo di); + +String GetCertificateCN(const std::shared_ptr<X509>& certificate); +std::shared_ptr<X509> GetX509Certificate(const String& pemfile); +int MakeX509CSR(const String& cn, const String& keyfile, const String& csrfile = String(), const String& certfile = String(), bool ca = false); +std::shared_ptr<X509> CreateCert(EVP_PKEY *pubkey, X509_NAME *subject, X509_NAME *issuer, EVP_PKEY *cakey, bool ca); + +String GetIcingaCADir(); +String CertificateToString(X509* cert); + +inline String CertificateToString(const std::shared_ptr<X509>& cert) +{ + return CertificateToString(cert.get()); +} + +std::shared_ptr<X509> StringToCertificate(const String& cert); +std::shared_ptr<X509> CreateCertIcingaCA(EVP_PKEY *pubkey, X509_NAME *subject, bool ca = false); +std::shared_ptr<X509> CreateCertIcingaCA(const std::shared_ptr<X509>& cert); +bool IsCertUptodate(const std::shared_ptr<X509>& cert); +bool IsCaUptodate(X509* cert); + +String PBKDF2_SHA1(const String& password, const String& salt, int iterations); +String PBKDF2_SHA256(const String& password, const String& salt, int iterations); +String SHA1(const String& s, bool binary = false); +String SHA256(const String& s); +String RandomString(int length); +String BinaryToHex(const unsigned char* data, size_t length); + +bool VerifyCertificate(const std::shared_ptr<X509>& caCertificate, const std::shared_ptr<X509>& certificate, const String& crlFile); +bool IsCa(const std::shared_ptr<X509>& cacert); +int GetCertificateVersion(const std::shared_ptr<X509>& cert); +String GetSignatureAlgorithm(const std::shared_ptr<X509>& cert); +Array::Ptr GetSubjectAltNames(const std::shared_ptr<X509>& cert); + +class openssl_error : virtual public std::exception, virtual public boost::exception { }; + +struct errinfo_openssl_error_; +typedef boost::error_info<struct errinfo_openssl_error_, unsigned long> errinfo_openssl_error; + +} + +#endif /* TLSUTILITY_H */ diff --git a/lib/base/type.cpp b/lib/base/type.cpp new file mode 100644 index 0000000..14794cb --- /dev/null +++ b/lib/base/type.cpp @@ -0,0 +1,217 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#include "base/type.hpp" +#include "base/scriptglobal.hpp" +#include "base/namespace.hpp" +#include "base/objectlock.hpp" + +using namespace icinga; + +Type::Ptr Type::TypeInstance; + +static Namespace::Ptr l_TypesNS = new Namespace(true); + +INITIALIZE_ONCE_WITH_PRIORITY([]() { + ScriptGlobal::GetGlobals()->Set("Types", l_TypesNS, true); +}, InitializePriority::CreateNamespaces); + +INITIALIZE_ONCE_WITH_PRIORITY([]() { + l_TypesNS->Freeze(); + + ObjectLock olock (l_TypesNS); + for (const auto& t : l_TypesNS) { + VERIFY(t.second.Val.IsObjectType<Type>()); + } +}, InitializePriority::FreezeNamespaces); + +/* Ensure that the priority is lower than the basic namespace initialization in scriptframe.cpp. */ +INITIALIZE_ONCE_WITH_PRIORITY([]() { + Type::Ptr type = new TypeType(); + type->SetPrototype(TypeType::GetPrototype()); + Type::TypeInstance = type; + Type::Register(type); +}, InitializePriority::RegisterTypeType); + +String Type::ToString() const +{ + return "type '" + GetName() + "'"; +} + +void Type::Register(const Type::Ptr& type) +{ + ScriptGlobal::Set("Types." + type->GetName(), type); +} + +Type::Ptr Type::GetByName(const String& name) +{ + Value ptype; + + if (!l_TypesNS->Get(name, &ptype)) + return nullptr; + + return ptype; +} + +std::vector<Type::Ptr> Type::GetAllTypes() +{ + std::vector<Type::Ptr> types; + + Namespace::Ptr typesNS = ScriptGlobal::Get("Types", &Empty); + + if (typesNS) { + ObjectLock olock(typesNS); + + for (const Namespace::Pair& kv : typesNS) { + Value value = kv.second.Val; + + if (value.IsObjectType<Type>()) + types.push_back(value); + } + } + + return types; +} + +String Type::GetPluralName() const +{ + String name = GetName(); + + if (name.GetLength() >= 2 && name[name.GetLength() - 1] == 'y' && + name.SubStr(name.GetLength() - 2, 1).FindFirstOf("aeiou") == String::NPos) + return name.SubStr(0, name.GetLength() - 1) + "ies"; + else + return name + "s"; +} + +Object::Ptr Type::Instantiate(const std::vector<Value>& args) const +{ + ObjectFactory factory = GetFactory(); + + if (!factory) + BOOST_THROW_EXCEPTION(std::runtime_error("Type does not have a factory function.")); + + return factory(args); +} + +bool Type::IsAbstract() const +{ + return ((GetAttributes() & TAAbstract) != 0); +} + +bool Type::IsAssignableFrom(const Type::Ptr& other) const +{ + for (Type::Ptr t = other; t; t = t->GetBaseType()) { + if (t.get() == this) + return true; + } + + return false; +} + +Object::Ptr Type::GetPrototype() const +{ + return m_Prototype; +} + +void Type::SetPrototype(const Object::Ptr& object) +{ + m_Prototype = object; +} + +void Type::SetField(int id, const Value& value, bool suppress_events, const Value& cookie) +{ + if (id == 1) { + SetPrototype(value); + return; + } + + Object::SetField(id, value, suppress_events, cookie); +} + +Value Type::GetField(int id) const +{ + int real_id = id - Object::TypeInstance->GetFieldCount(); + if (real_id < 0) + return Object::GetField(id); + + if (real_id == 0) + return GetName(); + else if (real_id == 1) + return GetPrototype(); + else if (real_id == 2) + return GetBaseType(); + + BOOST_THROW_EXCEPTION(std::runtime_error("Invalid field ID.")); +} + +const std::unordered_set<Type*>& Type::GetLoadDependencies() const +{ + static const std::unordered_set<Type*> noDeps; + return noDeps; +} + +int Type::GetActivationPriority() const +{ + return 0; +} + +void Type::RegisterAttributeHandler(int fieldId, const AttributeHandler& callback) +{ + throw std::runtime_error("Invalid field ID."); +} + +String TypeType::GetName() const +{ + return "Type"; +} + +Type::Ptr TypeType::GetBaseType() const +{ + return Object::TypeInstance; +} + +int TypeType::GetAttributes() const +{ + return 0; +} + +int TypeType::GetFieldId(const String& name) const +{ + int base_field_count = GetBaseType()->GetFieldCount(); + + if (name == "name") + return base_field_count + 0; + else if (name == "prototype") + return base_field_count + 1; + else if (name == "base") + return base_field_count + 2; + + return GetBaseType()->GetFieldId(name); +} + +Field TypeType::GetFieldInfo(int id) const +{ + int real_id = id - GetBaseType()->GetFieldCount(); + if (real_id < 0) + return GetBaseType()->GetFieldInfo(id); + + if (real_id == 0) + return {0, "String", "name", "", nullptr, 0, 0}; + else if (real_id == 1) + return Field(1, "Object", "prototype", "", nullptr, 0, 0); + else if (real_id == 2) + return Field(2, "Type", "base", "", nullptr, 0, 0); + + throw std::runtime_error("Invalid field ID."); +} + +int TypeType::GetFieldCount() const +{ + return GetBaseType()->GetFieldCount() + 3; +} + +ObjectFactory TypeType::GetFactory() const +{ + return nullptr; +} + diff --git a/lib/base/type.hpp b/lib/base/type.hpp new file mode 100644 index 0000000..7b8d1ca --- /dev/null +++ b/lib/base/type.hpp @@ -0,0 +1,148 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#ifndef TYPE_H +#define TYPE_H + +#include "base/i2-base.hpp" +#include "base/string.hpp" +#include "base/object.hpp" +#include "base/initialize.hpp" +#include <unordered_set> +#include <vector> + +namespace icinga +{ + +/* keep this in sync with tools/mkclass/classcompiler.hpp */ +enum FieldAttribute +{ + FAEphemeral = 1, + FAConfig = 2, + FAState = 4, + FARequired = 256, + FANavigation = 512, + FANoUserModify = 1024, + FANoUserView = 2048, + FADeprecated = 4096, +}; + +class Type; + +struct Field +{ + int ID; + const char *TypeName; + const char *Name; + const char *NavigationName; + const char *RefTypeName; + int Attributes; + int ArrayRank; + + Field(int id, const char *type, const char *name, const char *navigationName, const char *reftype, int attributes, int arrayRank) + : ID(id), TypeName(type), Name(name), NavigationName(navigationName), RefTypeName(reftype), Attributes(attributes), ArrayRank(arrayRank) + { } +}; + +enum TypeAttribute +{ + TAAbstract = 1 +}; + +class ValidationUtils +{ +public: + virtual bool ValidateName(const String& type, const String& name) const = 0; +}; + +class Type : public Object +{ +public: + DECLARE_OBJECT(Type); + + String ToString() const override; + + virtual String GetName() const = 0; + virtual Type::Ptr GetBaseType() const = 0; + virtual int GetAttributes() const = 0; + virtual int GetFieldId(const String& name) const = 0; + virtual Field GetFieldInfo(int id) const = 0; + virtual int GetFieldCount() const = 0; + + String GetPluralName() const; + + Object::Ptr Instantiate(const std::vector<Value>& args) const; + + bool IsAssignableFrom(const Type::Ptr& other) const; + + bool IsAbstract() const; + + Object::Ptr GetPrototype() const; + void SetPrototype(const Object::Ptr& object); + + static void Register(const Type::Ptr& type); + static Type::Ptr GetByName(const String& name); + static std::vector<Type::Ptr> GetAllTypes(); + + void SetField(int id, const Value& value, bool suppress_events = false, const Value& cookie = Empty) override; + Value GetField(int id) const override; + + virtual const std::unordered_set<Type*>& GetLoadDependencies() const; + virtual int GetActivationPriority() const; + + typedef std::function<void (const Object::Ptr&, const Value&)> AttributeHandler; + virtual void RegisterAttributeHandler(int fieldId, const AttributeHandler& callback); + +protected: + virtual ObjectFactory GetFactory() const = 0; + +private: + Object::Ptr m_Prototype; +}; + +class TypeType final : public Type +{ +public: + DECLARE_PTR_TYPEDEFS(Type); + + String GetName() const override; + Type::Ptr GetBaseType() const override; + int GetAttributes() const override; + int GetFieldId(const String& name) const override; + Field GetFieldInfo(int id) const override; + int GetFieldCount() const override; + + static Object::Ptr GetPrototype(); + +protected: + ObjectFactory GetFactory() const override; +}; + +template<typename T> +class TypeImpl +{ +}; + +/* Ensure that the priority is lower than the basic namespace initialization in scriptframe.cpp. */ +#define REGISTER_TYPE(type) \ + INITIALIZE_ONCE_WITH_PRIORITY([]() { \ + icinga::Type::Ptr t = new TypeImpl<type>(); \ + type::TypeInstance = t; \ + icinga::Type::Register(t); \ + }, InitializePriority::RegisterTypes); \ + DEFINE_TYPE_INSTANCE(type) + +#define REGISTER_TYPE_WITH_PROTOTYPE(type, prototype) \ + INITIALIZE_ONCE_WITH_PRIORITY([]() { \ + icinga::Type::Ptr t = new TypeImpl<type>(); \ + t->SetPrototype(prototype); \ + type::TypeInstance = t; \ + icinga::Type::Register(t); \ + }, InitializePriority::RegisterTypes); \ + DEFINE_TYPE_INSTANCE(type) + +#define DEFINE_TYPE_INSTANCE(type) \ + Type::Ptr type::TypeInstance + +} + +#endif /* TYPE_H */ diff --git a/lib/base/typetype-script.cpp b/lib/base/typetype-script.cpp new file mode 100644 index 0000000..9077de8 --- /dev/null +++ b/lib/base/typetype-script.cpp @@ -0,0 +1,31 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#include "base/type.hpp" +#include "base/dictionary.hpp" +#include "base/function.hpp" +#include "base/functionwrapper.hpp" +#include "base/scriptframe.hpp" + +using namespace icinga; + +static void TypeRegisterAttributeHandler(const String& fieldName, const Function::Ptr& callback) +{ + ScriptFrame *vframe = ScriptFrame::GetCurrentFrame(); + Type::Ptr self = static_cast<Type::Ptr>(vframe->Self); + REQUIRE_NOT_NULL(self); + + int fid = self->GetFieldId(fieldName); + self->RegisterAttributeHandler(fid, [callback](const Object::Ptr& object, const Value& cookie) { + callback->Invoke({ object }); + }); +} + +Object::Ptr TypeType::GetPrototype() +{ + static Dictionary::Ptr prototype = new Dictionary({ + { "register_attribute_handler", new Function("Type#register_attribute_handler", TypeRegisterAttributeHandler, { "field", "callback" }, false) } + }); + + return prototype; +} + diff --git a/lib/base/unix.hpp b/lib/base/unix.hpp new file mode 100644 index 0000000..7413a5b --- /dev/null +++ b/lib/base/unix.hpp @@ -0,0 +1,49 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#ifndef UNIX_H +#define UNIX_H + +#include <limits.h> +#include <unistd.h> +#include <fcntl.h> +#include <sys/socket.h> +#include <sys/types.h> +#include <sys/un.h> +#include <netdb.h> +#include <sys/ioctl.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <libgen.h> +#include <syslog.h> +#include <sys/file.h> +#include <sys/wait.h> +#include <glob.h> +#include <dlfcn.h> +#include <sys/stat.h> +#include <dirent.h> +#include <sys/time.h> +#include <sys/resource.h> +#include <strings.h> +#include <errno.h> + +typedef int SOCKET; +#define INVALID_SOCKET (-1) + +#define closesocket close +#define ioctlsocket ioctl + +#ifndef SUN_LEN +/* TODO: Ideally this should take into the account how + * long the socket path really is. + */ +# define SUN_LEN(sun) (sizeof(sockaddr_un)) +#endif /* SUN_LEN */ + +#ifndef PATH_MAX +# define PATH_MAX 1024 +#endif /* PATH_MAX */ + +#ifndef MAXPATHLEN +# define MAXPATHLEN PATH_MAX +#endif /* MAXPATHLEN */ +#endif /* UNIX_H */ diff --git a/lib/base/unixsocket.cpp b/lib/base/unixsocket.cpp new file mode 100644 index 0000000..dcc56ff --- /dev/null +++ b/lib/base/unixsocket.cpp @@ -0,0 +1,53 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#include "base/unixsocket.hpp" +#include "base/exception.hpp" + +#ifndef _WIN32 +using namespace icinga; + +UnixSocket::UnixSocket() +{ + int fd = socket(AF_UNIX, SOCK_STREAM, 0); + + if (fd < 0) { + BOOST_THROW_EXCEPTION(posix_error() + << boost::errinfo_api_function("socket") + << boost::errinfo_errno(errno)); + } + + SetFD(fd); +} + +void UnixSocket::Bind(const String& path) +{ + unlink(path.CStr()); + + sockaddr_un s_un; + memset(&s_un, 0, sizeof(s_un)); + s_un.sun_family = AF_UNIX; + strncpy(s_un.sun_path, path.CStr(), sizeof(s_un.sun_path)); + s_un.sun_path[sizeof(s_un.sun_path) - 1] = '\0'; + + if (bind(GetFD(), (sockaddr *)&s_un, SUN_LEN(&s_un)) < 0) { + BOOST_THROW_EXCEPTION(posix_error() + << boost::errinfo_api_function("bind") + << boost::errinfo_errno(errno)); + } +} + +void UnixSocket::Connect(const String& path) +{ + sockaddr_un s_un; + memset(&s_un, 0, sizeof(s_un)); + s_un.sun_family = AF_UNIX; + strncpy(s_un.sun_path, path.CStr(), sizeof(s_un.sun_path)); + s_un.sun_path[sizeof(s_un.sun_path) - 1] = '\0'; + + if (connect(GetFD(), (sockaddr *)&s_un, SUN_LEN(&s_un)) < 0 && errno != EINPROGRESS) { + BOOST_THROW_EXCEPTION(posix_error() + << boost::errinfo_api_function("connect") + << boost::errinfo_errno(errno)); + } +} +#endif /* _WIN32 */ diff --git a/lib/base/unixsocket.hpp b/lib/base/unixsocket.hpp new file mode 100644 index 0000000..80a9f25 --- /dev/null +++ b/lib/base/unixsocket.hpp @@ -0,0 +1,32 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#ifndef UNIXSOCKET_H +#define UNIXSOCKET_H + +#include "base/socket.hpp" + +#ifndef _WIN32 +namespace icinga +{ + +/** + * A TCP socket. DEPRECATED - Use Boost ASIO instead. + * + * @ingroup base + */ +class UnixSocket final : public Socket +{ +public: + DECLARE_PTR_TYPEDEFS(UnixSocket); + + UnixSocket(); + + void Bind(const String& path); + + void Connect(const String& path); +}; + +} +#endif /* _WIN32 */ + +#endif /* UNIXSOCKET_H */ diff --git a/lib/base/utility.cpp b/lib/base/utility.cpp new file mode 100644 index 0000000..6ff84ae --- /dev/null +++ b/lib/base/utility.cpp @@ -0,0 +1,1975 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#include "base/atomic-file.hpp" +#include "base/utility.hpp" +#include "base/convert.hpp" +#include "base/application.hpp" +#include "base/logger.hpp" +#include "base/exception.hpp" +#include "base/socket.hpp" +#include "base/utility.hpp" +#include "base/json.hpp" +#include "base/objectlock.hpp" +#include <algorithm> +#include <cstdint> +#include <mmatch.h> +#include <boost/filesystem.hpp> +#include <boost/lexical_cast.hpp> +#include <boost/system/error_code.hpp> +#include <boost/thread/tss.hpp> +#include <boost/algorithm/string/trim.hpp> +#include <boost/algorithm/string/replace.hpp> +#include <boost/uuid/uuid_io.hpp> +#include <boost/uuid/uuid_generators.hpp> +#include <boost/regex.hpp> +#include <ios> +#include <fstream> +#include <iostream> +#include <iterator> +#include <stdlib.h> +#include <future> +#include <set> +#include <utf8.h> +#include <vector> + +#ifdef __FreeBSD__ +# include <pthread_np.h> +#endif /* __FreeBSD__ */ + +#ifdef HAVE_CXXABI_H +# include <cxxabi.h> +#endif /* HAVE_CXXABI_H */ + +#ifndef _WIN32 +# include <sys/types.h> +# include <sys/utsname.h> +# include <pwd.h> +# include <grp.h> +# include <errno.h> +# include <unistd.h> +#endif /* _WIN32 */ + +#ifdef _WIN32 +# include <VersionHelpers.h> +# include <windows.h> +# include <io.h> +# include <msi.h> +# include <shlobj.h> +#endif /*_WIN32*/ + +using namespace icinga; + +boost::thread_specific_ptr<String> Utility::m_ThreadName; +boost::thread_specific_ptr<unsigned int> Utility::m_RandSeed; + +#ifdef I2_DEBUG +double Utility::m_DebugTime = -1; +#endif /* I2_DEBUG */ + +/** + * Demangles a symbol name. + * + * @param sym The symbol name. + * @returns A human-readable version of the symbol name. + */ +String Utility::DemangleSymbolName(const String& sym) +{ + String result = sym; + +#ifdef HAVE_CXXABI_H + int status; + char *realname = abi::__cxa_demangle(sym.CStr(), nullptr, nullptr, &status); + + if (realname) { + result = String(realname); + free(realname); + } +#elif defined(_MSC_VER) /* HAVE_CXXABI_H */ + CHAR output[256]; + + if (UnDecorateSymbolName(sym.CStr(), output, sizeof(output), UNDNAME_COMPLETE) > 0) + result = output; +#else /* _MSC_VER */ + /* We're pretty much out of options here. */ +#endif /* _MSC_VER */ + + return result; +} + +/** + * Returns a human-readable type name of a type_info object. + * + * @param ti A type_info object. + * @returns The type name of the object. + */ +String Utility::GetTypeName(const std::type_info& ti) +{ + return DemangleSymbolName(ti.name()); +} + +String Utility::GetSymbolName(const void *addr) +{ +#ifdef HAVE_DLADDR + Dl_info dli; + + if (dladdr(const_cast<void *>(addr), &dli) > 0) + return dli.dli_sname; +#endif /* HAVE_DLADDR */ + +#ifdef _WIN32 + char buffer[sizeof(SYMBOL_INFO)+MAX_SYM_NAME * sizeof(TCHAR)]; + PSYMBOL_INFO pSymbol = (PSYMBOL_INFO)buffer; + pSymbol->SizeOfStruct = sizeof(SYMBOL_INFO); + pSymbol->MaxNameLen = MAX_SYM_NAME; + + DWORD64 dwAddress = (DWORD64)addr; + DWORD64 dwDisplacement; + + IMAGEHLP_LINE64 line; + line.SizeOfStruct = sizeof(IMAGEHLP_LINE64); + + if (SymFromAddr(GetCurrentProcess(), dwAddress, &dwDisplacement, pSymbol)) { + char output[256]; + if (UnDecorateSymbolName(pSymbol->Name, output, sizeof(output), UNDNAME_COMPLETE)) + return String(output) + "+" + Convert::ToString(dwDisplacement); + else + return String(pSymbol->Name) + "+" + Convert::ToString(dwDisplacement); + } +#endif /* _WIN32 */ + + return "(unknown function)"; +} + +/** + * Performs wildcard pattern matching. + * + * @param pattern The wildcard pattern. + * @param text The String that should be checked. + * @returns true if the wildcard pattern matches, false otherwise. + */ +bool Utility::Match(const String& pattern, const String& text) +{ + return (match(pattern.CStr(), text.CStr()) == 0); +} + +static bool ParseIp(const String& ip, char addr[16], int *proto) +{ + if (inet_pton(AF_INET, ip.CStr(), addr + 12) == 1) { + /* IPv4-mapped IPv6 address (::ffff:<ipv4-bits>) */ + memset(addr, 0, 10); + memset(addr + 10, 0xff, 2); + *proto = AF_INET; + + return true; + } + + if (inet_pton(AF_INET6, ip.CStr(), addr) == 1) { + *proto = AF_INET6; + + return true; + } + + return false; +} + +static void ParseIpMask(const String& ip, char mask[16], int *bits) +{ + String::SizeType slashp = ip.FindFirstOf("/"); + String uip; + + if (slashp == String::NPos) { + uip = ip; + *bits = 0; + } else { + uip = ip.SubStr(0, slashp); + *bits = Convert::ToLong(ip.SubStr(slashp + 1)); + } + + int proto; + + if (!ParseIp(uip, mask, &proto)) + BOOST_THROW_EXCEPTION(std::invalid_argument("Invalid IP address specified.")); + + if (proto == AF_INET) { + if (*bits > 32 || *bits < 0) + BOOST_THROW_EXCEPTION(std::invalid_argument("Mask must be between 0 and 32 for IPv4 CIDR masks.")); + + *bits += 96; + } + + if (slashp == String::NPos) + *bits = 128; + + if (*bits > 128 || *bits < 0) + BOOST_THROW_EXCEPTION(std::invalid_argument("Mask must be between 0 and 128 for IPv6 CIDR masks.")); + + for (int i = 0; i < 16; i++) { + int lbits = std::max(0, *bits - i * 8); + + if (lbits >= 8) + continue; + + if (mask[i] & (0xff >> lbits)) + BOOST_THROW_EXCEPTION(std::invalid_argument("Masked-off bits must all be zero.")); + } +} + +static bool IpMaskCheck(char addr[16], char mask[16], int bits) +{ + for (int i = 0; i < 16; i++) { + if (bits < 8) + return !((addr[i] ^ mask[i]) >> (8 - bits)); + + if (mask[i] != addr[i]) + return false; + + bits -= 8; + + if (bits == 0) + return true; + } + + return true; +} + +bool Utility::CidrMatch(const String& pattern, const String& ip) +{ + char mask[16]; + int bits; + + ParseIpMask(pattern, mask, &bits); + + char addr[16]; + int proto; + + if (!ParseIp(ip, addr, &proto)) + return false; + + return IpMaskCheck(addr, mask, bits); +} + +/** + * Returns the directory component of a path. See dirname(3) for details. + * + * @param path The full path. + * @returns The directory. + */ +String Utility::DirName(const String& path) +{ + return boost::filesystem::path(path.Begin(), path.End()).parent_path().string(); +} + +/** + * Returns the file component of a path. See basename(3) for details. + * + * @param path The full path. + * @returns The filename. + */ +String Utility::BaseName(const String& path) +{ + return boost::filesystem::path(path.Begin(), path.End()).filename().string(); +} + +/** + * Null deleter. Used as a parameter for the shared_ptr constructor. + * + * @param - The object that should be deleted. + */ +void Utility::NullDeleter(void *) +{ + /* Nothing to do here. */ +} + +#ifdef I2_DEBUG +/** + * (DEBUG / TESTING ONLY) Sets the current system time to a static value, + * that will be be retrieved by any component of Icinga, when using GetTime(). + * + * This should be only used for testing purposes, e.g. unit tests and debugging of certain functionalities. + */ +void Utility::SetTime(double time) +{ + m_DebugTime = time; +} + +/** + * (DEBUG / TESTING ONLY) Increases the set debug system time by X seconds. + * + * This should be only used for testing purposes, e.g. unit tests and debugging of certain functionalities. + */ +void Utility::IncrementTime(double diff) +{ + m_DebugTime += diff; +} +#endif /* I2_DEBUG */ + +/** + * Returns the current UNIX timestamp including fractions of seconds. + * + * @returns The current time. + */ +double Utility::GetTime() +{ +#ifdef I2_DEBUG + if (m_DebugTime >= 0) { + // (DEBUG / TESTING ONLY) this will return a *STATIC* system time, if the value has been set! + return m_DebugTime; + } +#endif /* I2_DEBUG */ +#ifdef _WIN32 + FILETIME cft; + GetSystemTimeAsFileTime(&cft); + + ULARGE_INTEGER ucft; + ucft.HighPart = cft.dwHighDateTime; + ucft.LowPart = cft.dwLowDateTime; + + SYSTEMTIME est = { 1970, 1, 4, 1, 0, 0, 0, 0}; + FILETIME eft; + SystemTimeToFileTime(&est, &eft); + + ULARGE_INTEGER ueft; + ueft.HighPart = eft.dwHighDateTime; + ueft.LowPart = eft.dwLowDateTime; + + return ((ucft.QuadPart - ueft.QuadPart) / 10000) / 1000.0; +#else /* _WIN32 */ + struct timeval tv; + + int rc = gettimeofday(&tv, nullptr); + VERIFY(rc >= 0); + + return tv.tv_sec + tv.tv_usec / 1000000.0; +#endif /* _WIN32 */ +} + +/** + * Returns the ID of the current process. + * + * @returns The PID. + */ +pid_t Utility::GetPid() +{ +#ifndef _WIN32 + return getpid(); +#else /* _WIN32 */ + return GetCurrentProcessId(); +#endif /* _WIN32 */ +} + +/** + * Sleeps for the specified amount of time. + * + * @param timeout The timeout in seconds. + */ +void Utility::Sleep(double timeout) +{ +#ifndef _WIN32 + unsigned long micros = timeout * 1000000u; + if (timeout >= 1.0) + sleep((unsigned)timeout); + + usleep(micros % 1000000u); +#else /* _WIN32 */ + ::Sleep(timeout * 1000); +#endif /* _WIN32 */ +} + +/** + * Generates a new unique ID. + * + * @returns The new unique ID. + */ +String Utility::NewUniqueID() +{ + return boost::lexical_cast<std::string>(boost::uuids::random_generator()()); +} + +#ifdef _WIN32 +static bool GlobHelper(const String& pathSpec, int type, std::vector<String>& files, std::vector<String>& dirs) +{ + HANDLE handle; + WIN32_FIND_DATA wfd; + + handle = FindFirstFile(pathSpec.CStr(), &wfd); + + if (handle == INVALID_HANDLE_VALUE) { + DWORD errorCode = GetLastError(); + + if (errorCode == ERROR_FILE_NOT_FOUND) + return false; + + BOOST_THROW_EXCEPTION(win32_error() + << boost::errinfo_api_function("FindFirstFile") + << errinfo_win32_error(errorCode) + << boost::errinfo_file_name(pathSpec)); + } + + do { + if (strcmp(wfd.cFileName, ".") == 0 || strcmp(wfd.cFileName, "..") == 0) + continue; + + String path = Utility::DirName(pathSpec) + "/" + wfd.cFileName; + + if ((wfd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) && (type & GlobDirectory)) + dirs.push_back(path); + else if (!(wfd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) && (type & GlobFile)) + files.push_back(path); + } while (FindNextFile(handle, &wfd)); + + if (!FindClose(handle)) { + BOOST_THROW_EXCEPTION(win32_error() + << boost::errinfo_api_function("FindClose") + << errinfo_win32_error(GetLastError())); + } + + return true; +} +#endif /* _WIN32 */ + +#ifndef _WIN32 +static int GlobErrorHandler(const char *epath, int eerrno) +{ + if (eerrno == ENOTDIR) + return 0; + + return eerrno; +} +#endif /* _WIN32 */ + +/** + * Calls the specified callback for each file matching the path specification. + * + * @param pathSpec The path specification. + * @param callback The callback which is invoked for each matching file. + * @param type The file type (a combination of GlobFile and GlobDirectory) + */ +bool Utility::Glob(const String& pathSpec, const std::function<void (const String&)>& callback, int type) +{ + std::vector<String> files, dirs; + +#ifdef _WIN32 + std::vector<String> tokens = pathSpec.Split("\\/"); + + String part1; + + for (std::vector<String>::size_type i = 0; i < tokens.size() - 1; i++) { + const String& token = tokens[i]; + + if (!part1.IsEmpty()) + part1 += "/"; + + part1 += token; + + if (token.FindFirstOf("?*") != String::NPos) { + String part2; + + for (std::vector<String>::size_type k = i + 1; k < tokens.size(); k++) { + if (!part2.IsEmpty()) + part2 += "/"; + + part2 += tokens[k]; + } + + std::vector<String> files2, dirs2; + + if (!GlobHelper(part1, GlobDirectory, files2, dirs2)) + return false; + + for (const String& dir : dirs2) { + if (!Utility::Glob(dir + "/" + part2, callback, type)) + return false; + } + + return true; + } + } + + if (!GlobHelper(part1 + "/" + tokens[tokens.size() - 1], type, files, dirs)) + return false; +#else /* _WIN32 */ + glob_t gr; + + int rc = glob(pathSpec.CStr(), GLOB_NOSORT, GlobErrorHandler, &gr); + + if (rc) { + if (rc == GLOB_NOMATCH) + return false; + + BOOST_THROW_EXCEPTION(posix_error() + << boost::errinfo_api_function("glob") + << boost::errinfo_errno(errno) + << boost::errinfo_file_name(pathSpec)); + } + + if (gr.gl_pathc == 0) { + globfree(&gr); + return false; + } + + size_t left; + char **gp; + for (gp = gr.gl_pathv, left = gr.gl_pathc; left > 0; gp++, left--) { + struct stat statbuf; + + if (stat(*gp, &statbuf) < 0) + continue; + + if (!S_ISDIR(statbuf.st_mode) && !S_ISREG(statbuf.st_mode)) + continue; + + if (S_ISDIR(statbuf.st_mode) && (type & GlobDirectory)) + dirs.emplace_back(*gp); + else if (!S_ISDIR(statbuf.st_mode) && (type & GlobFile)) + files.emplace_back(*gp); + } + + globfree(&gr); +#endif /* _WIN32 */ + + std::sort(files.begin(), files.end()); + for (const String& cpath : files) { + callback(cpath); + } + + std::sort(dirs.begin(), dirs.end()); + for (const String& cpath : dirs) { + callback(cpath); + } + + return true; +} + +/** + * Calls the specified callback for each file in the specified directory + * or any of its child directories if the file name matches the specified + * pattern. + * + * @param path The path. + * @param pattern The pattern. + * @param callback The callback which is invoked for each matching file. + * @param type The file type (a combination of GlobFile and GlobDirectory) + */ +bool Utility::GlobRecursive(const String& path, const String& pattern, const std::function<void (const String&)>& callback, int type) +{ + std::vector<String> files, dirs, alldirs; + +#ifdef _WIN32 + HANDLE handle; + WIN32_FIND_DATA wfd; + + String pathSpec = path + "/*"; + + handle = FindFirstFile(pathSpec.CStr(), &wfd); + + if (handle == INVALID_HANDLE_VALUE) { + DWORD errorCode = GetLastError(); + + if (errorCode == ERROR_FILE_NOT_FOUND) + return false; + + BOOST_THROW_EXCEPTION(win32_error() + << boost::errinfo_api_function("FindFirstFile") + << errinfo_win32_error(errorCode) + << boost::errinfo_file_name(pathSpec)); + } + + do { + if (strcmp(wfd.cFileName, ".") == 0 || strcmp(wfd.cFileName, "..") == 0) + continue; + + String cpath = path + "/" + wfd.cFileName; + + if (wfd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) + alldirs.push_back(cpath); + + if (!Utility::Match(pattern, wfd.cFileName)) + continue; + + if (!(wfd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) && (type & GlobFile)) + files.push_back(cpath); + + if ((wfd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) && (type & GlobDirectory)) + dirs.push_back(cpath); + } while (FindNextFile(handle, &wfd)); + + if (!FindClose(handle)) { + BOOST_THROW_EXCEPTION(win32_error() + << boost::errinfo_api_function("FindClose") + << errinfo_win32_error(GetLastError())); + } +#else /* _WIN32 */ + DIR *dirp; + + dirp = opendir(path.CStr()); + + if (!dirp) + BOOST_THROW_EXCEPTION(posix_error() + << boost::errinfo_api_function("opendir") + << boost::errinfo_errno(errno) + << boost::errinfo_file_name(path)); + + while (dirp) { + dirent *pent; + + errno = 0; + pent = readdir(dirp); + if (!pent && errno != 0) { + closedir(dirp); + + BOOST_THROW_EXCEPTION(posix_error() + << boost::errinfo_api_function("readdir") + << boost::errinfo_errno(errno) + << boost::errinfo_file_name(path)); + } + + if (!pent) + break; + + if (strcmp(pent->d_name, ".") == 0 || strcmp(pent->d_name, "..") == 0) + continue; + + String cpath = path + "/" + pent->d_name; + + struct stat statbuf; + + if (stat(cpath.CStr(), &statbuf) < 0) + continue; + + if (S_ISDIR(statbuf.st_mode)) + alldirs.push_back(cpath); + + if (!Utility::Match(pattern, pent->d_name)) + continue; + + if (S_ISDIR(statbuf.st_mode) && (type & GlobDirectory)) + dirs.push_back(cpath); + + if (!S_ISDIR(statbuf.st_mode) && (type & GlobFile)) + files.push_back(cpath); + } + + closedir(dirp); + +#endif /* _WIN32 */ + + std::sort(files.begin(), files.end()); + for (const String& cpath : files) { + callback(cpath); + } + + std::sort(dirs.begin(), dirs.end()); + for (const String& cpath : dirs) { + callback(cpath); + } + + std::sort(alldirs.begin(), alldirs.end()); + for (const String& cpath : alldirs) { + GlobRecursive(cpath, pattern, callback, type); + } + + return true; +} + + +void Utility::MkDir(const String& path, int mode) +{ + +#ifndef _WIN32 + if (mkdir(path.CStr(), mode) < 0 && errno != EEXIST) { +#else /*_ WIN32 */ + if (mkdir(path.CStr()) < 0 && errno != EEXIST) { +#endif /* _WIN32 */ + + BOOST_THROW_EXCEPTION(posix_error() + << boost::errinfo_api_function("mkdir") + << boost::errinfo_errno(errno) + << boost::errinfo_file_name(path)); + } +} + +void Utility::MkDirP(const String& path, int mode) +{ + size_t pos = 0; + + while (pos != String::NPos) { +#ifndef _WIN32 + pos = path.Find("/", pos + 1); +#else /*_ WIN32 */ + pos = path.FindFirstOf("/\\", pos + 1); +#endif /* _WIN32 */ + + String spath = path.SubStr(0, pos + 1); + struct stat statbuf; + if (stat(spath.CStr(), &statbuf) < 0 && errno == ENOENT) + MkDir(path.SubStr(0, pos), mode); + } +} + +void Utility::Remove(const String& path) +{ + namespace fs = boost::filesystem; + + (void)fs::remove(fs::path(path.Begin(), path.End())); +} + +void Utility::RemoveDirRecursive(const String& path) +{ + namespace fs = boost::filesystem; + + (void)fs::remove_all(fs::path(path.Begin(), path.End())); +} + +/* + * Copies a source file to a target location. + * Caller must ensure that the target's base directory exists and is writable. + */ +void Utility::CopyFile(const String& source, const String& target) +{ + namespace fs = boost::filesystem; + +#if BOOST_VERSION >= 107400 + fs::copy_file(fs::path(source.Begin(), source.End()), fs::path(target.Begin(), target.End()), fs::copy_options::overwrite_existing); +#else /* BOOST_VERSION */ + fs::copy_file(fs::path(source.Begin(), source.End()), fs::path(target.Begin(), target.End()), fs::copy_option::overwrite_if_exists); +#endif /* BOOST_VERSION */ +} + +/* + * Renames a source file to a target location. + * Caller must ensure that the target's base directory exists and is writable. + */ +void Utility::RenameFile(const String& source, const String& target) +{ + namespace fs = boost::filesystem; + + fs::path sourcePath(source.Begin(), source.End()), targetPath(target.Begin(), target.End()); + +#ifndef _WIN32 + fs::rename(sourcePath, targetPath); +#else /* _WIN32 */ + /* + * Renaming files can be tricky on Windows, especially if your application is built around POSIX filesystem + * semantics. For example, the quite common pattern of replacing a file by writing a new version to a temporary + * location and then moving it to the final location can fail if the destination file already exists and any + * process has an open file handle to it. + * + * We try to handle this situation as best as we can by retrying the rename operation a few times hoping the other + * process closes its file handle in the meantime. This is similar to what for example Go does internally in some + * situations (https://golang.org/pkg/cmd/go/internal/robustio/#Rename): + * + * robustio.Rename is like os.Rename, but on Windows retries errors that may occur if the file is concurrently + * read or overwritten. (See https://golang.org/issue/31247 and https://golang.org/issue/32188) + */ + + double sleep = 0.1; + int last_error = ERROR_SUCCESS; + + for (int retries = 0, remaining = 15;; retries++, remaining--) { + try { + fs::rename(sourcePath, targetPath); + + if (retries > 0) { + Log(LogWarning, "Utility") << "Renaming '" << source << "' to '" << target + << "' succeeded after " << retries << " retries"; + } + + break; + } catch (const fs::filesystem_error& ex) { + int error = ex.code().value(); + bool ephemeral = error == ERROR_ACCESS_DENIED || + error == ERROR_FILE_NOT_FOUND || + error == ERROR_SHARING_VIOLATION; + + if (remaining <= 0 || !ephemeral) { + throw; // giving up + } + + if (error != last_error) { + Log(LogWarning, "Utility") << "Renaming '" << source << "' to '" << target << "' failed: " + << ex.code().message() << " (trying up to " << remaining << " more times)"; + last_error = error; + } + + Utility::Sleep(sleep); + sleep *= 1.3; + } + } +#endif /* _WIN32 */ +} + +/* + * Set file permissions + */ +bool Utility::SetFileOwnership(const String& file, const String& user, const String& group) +{ +#ifndef _WIN32 + errno = 0; + struct passwd *pw = getpwnam(user.CStr()); + + if (!pw) { + if (errno == 0) { + Log(LogCritical, "cli") + << "Invalid user specified: " << user; + return false; + } else { + Log(LogCritical, "cli") + << "getpwnam() failed with error code " << errno << ", \"" << Utility::FormatErrorNumber(errno) << "\""; + return false; + } + } + + errno = 0; + struct group *gr = getgrnam(group.CStr()); + + if (!gr) { + if (errno == 0) { + Log(LogCritical, "cli") + << "Invalid group specified: " << group; + return false; + } else { + Log(LogCritical, "cli") + << "getgrnam() failed with error code " << errno << ", \"" << Utility::FormatErrorNumber(errno) << "\""; + return false; + } + } + + if (chown(file.CStr(), pw->pw_uid, gr->gr_gid) < 0) { + Log(LogCritical, "cli") + << "chown() failed with error code " << errno << ", \"" << Utility::FormatErrorNumber(errno) << "\""; + return false; + } +#endif /* _WIN32 */ + + return true; +} + +#ifndef _WIN32 +void Utility::SetNonBlocking(int fd, bool nb) +{ + int flags = fcntl(fd, F_GETFL, 0); + + if (flags < 0) { + BOOST_THROW_EXCEPTION(posix_error() + << boost::errinfo_api_function("fcntl") + << boost::errinfo_errno(errno)); + } + + if (nb) + flags |= O_NONBLOCK; + else + flags &= ~O_NONBLOCK; + + if (fcntl(fd, F_SETFL, flags) < 0) { + BOOST_THROW_EXCEPTION(posix_error() + << boost::errinfo_api_function("fcntl") + << boost::errinfo_errno(errno)); + } +} + +void Utility::SetCloExec(int fd, bool cloexec) +{ + int flags = fcntl(fd, F_GETFD, 0); + + if (flags < 0) { + BOOST_THROW_EXCEPTION(posix_error() + << boost::errinfo_api_function("fcntl") + << boost::errinfo_errno(errno)); + } + + if (cloexec) + flags |= FD_CLOEXEC; + else + flags &= ~FD_CLOEXEC; + + if (fcntl(fd, F_SETFD, flags) < 0) { + BOOST_THROW_EXCEPTION(posix_error() + << boost::errinfo_api_function("fcntl") + << boost::errinfo_errno(errno)); + } +} + +void Utility::CloseAllFDs(const std::vector<int>& except, std::function<void(int)> onClose) +{ +#if defined(__linux__) || defined(__APPLE__) + namespace fs = boost::filesystem; + + std::set<int> fds; + +#ifdef __linux__ + const char *dir = "/proc/self/fd"; +#endif /* __linux__ */ +#ifdef __APPLE__ + const char *dir = "/dev/fd"; +#endif /* __APPLE__ */ + + for (fs::directory_iterator current {fs::path(dir)}, end; current != end; ++current) { + auto entry (current->path().filename()); + int fd; + + try { + fd = boost::lexical_cast<int>(entry.c_str()); + } catch (...) { + continue; + } + + fds.emplace(fd); + } + + for (auto fd : except) { + fds.erase(fd); + } + + for (auto fd : fds) { + if (close(fd) >= 0 && onClose) { + onClose(fd); + } + } +#else /* __linux__ || __APPLE__ */ + rlimit rl; + + if (getrlimit(RLIMIT_NOFILE, &rl) >= 0) { + rlim_t maxfds = rl.rlim_max; + + if (maxfds == RLIM_INFINITY) { + maxfds = 65536; + } + + for (int fd = 0; fd < maxfds; ++fd) { + if (std::find(except.begin(), except.end(), fd) == except.end() && close(fd) >= 0 && onClose) { + onClose(fd); + } + } + } +#endif /* __linux__ || __APPLE__ */ +} +#endif /* _WIN32 */ + +void Utility::SetNonBlockingSocket(SOCKET s, bool nb) +{ +#ifndef _WIN32 + SetNonBlocking(s, nb); +#else /* _WIN32 */ + unsigned long lflag = nb; + ioctlsocket(s, FIONBIO, &lflag); +#endif /* _WIN32 */ +} + +void Utility::QueueAsyncCallback(const std::function<void ()>& callback, SchedulerPolicy policy) +{ + Application::GetTP().Post(callback, policy); +} + +String Utility::NaturalJoin(const std::vector<String>& tokens) +{ + String result; + + for (std::vector<String>::size_type i = 0; i < tokens.size(); i++) { + result += tokens[i]; + + if (tokens.size() > i + 1) { + if (i < tokens.size() - 2) + result += ", "; + else if (i == tokens.size() - 2) + result += " and "; + } + } + + return result; +} + +String Utility::Join(const Array::Ptr& tokens, char separator, bool escapeSeparator) +{ + String result; + bool first = true; + + ObjectLock olock(tokens); + for (const Value& vtoken : tokens) { + String token = Convert::ToString(vtoken); + + if (escapeSeparator) { + boost::algorithm::replace_all(token, "\\", "\\\\"); + + char sep_before[2], sep_after[3]; + sep_before[0] = separator; + sep_before[1] = '\0'; + sep_after[0] = '\\'; + sep_after[1] = separator; + sep_after[2] = '\0'; + boost::algorithm::replace_all(token, sep_before, sep_after); + } + + if (first) + first = false; + else + result += String(1, separator); + + result += token; + } + + return result; +} + +String Utility::FormatDuration(double duration) +{ + std::vector<String> tokens; + String result; + + if (duration >= 86400) { + int days = duration / 86400; + tokens.emplace_back(Convert::ToString(days) + (days != 1 ? " days" : " day")); + duration = static_cast<int>(duration) % 86400; + } + + if (duration >= 3600) { + int hours = duration / 3600; + tokens.emplace_back(Convert::ToString(hours) + (hours != 1 ? " hours" : " hour")); + duration = static_cast<int>(duration) % 3600; + } + + if (duration >= 60) { + int minutes = duration / 60; + tokens.emplace_back(Convert::ToString(minutes) + (minutes != 1 ? " minutes" : " minute")); + duration = static_cast<int>(duration) % 60; + } + + if (duration >= 1) { + int seconds = duration; + tokens.emplace_back(Convert::ToString(seconds) + (seconds != 1 ? " seconds" : " second")); + } + + if (tokens.size() == 0) { + int milliseconds = std::floor(duration * 1000); + if (milliseconds >= 1) + tokens.emplace_back(Convert::ToString(milliseconds) + (milliseconds != 1 ? " milliseconds" : " millisecond")); + else + tokens.emplace_back("less than 1 millisecond"); + } + + return NaturalJoin(tokens); +} + +String Utility::FormatDateTime(const char *format, double ts) +{ + char timestamp[128]; + auto tempts = (time_t)ts; /* We don't handle sub-second timestamps here just yet. */ + tm tmthen; + +#ifdef _MSC_VER + tm *temp = localtime(&tempts); + + if (!temp) { + BOOST_THROW_EXCEPTION(posix_error() + << boost::errinfo_api_function("localtime") + << boost::errinfo_errno(errno)); + } + + tmthen = *temp; +#else /* _MSC_VER */ + if (!localtime_r(&tempts, &tmthen)) { + BOOST_THROW_EXCEPTION(posix_error() + << boost::errinfo_api_function("localtime_r") + << boost::errinfo_errno(errno)); + } +#endif /* _MSC_VER */ + + strftime(timestamp, sizeof(timestamp), format, &tmthen); + + return timestamp; +} + +String Utility::FormatErrorNumber(int code) { + std::ostringstream msgbuf; + +#ifdef _WIN32 + char *message; + String result = "Unknown error."; + + DWORD rc = FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, nullptr, code, 0, (char *)&message, + 0, nullptr); + + if (rc != 0) { + result = String(message); + LocalFree(message); + + /* remove trailing new-line characters */ + boost::algorithm::trim_right(result); + } + + msgbuf << code << ", \"" << result << "\""; +#else + msgbuf << strerror(code); +#endif + return msgbuf.str(); +} + +String Utility::EscapeShellCmd(const String& s) +{ + String result; + size_t prev_quote = String::NPos; + int index = -1; + + for (char ch : s) { + bool escape = false; + + index++; + +#ifdef _WIN32 + if (ch == '%' || ch == '"' || ch == '\'') + escape = true; +#else /* _WIN32 */ + if (ch == '"' || ch == '\'') { + /* Find a matching closing quotation character. */ + if (prev_quote == String::NPos && (prev_quote = s.FindFirstOf(ch, index + 1)) != String::NPos) + ; /* Empty statement. */ + else if (prev_quote != String::NPos && s[prev_quote] == ch) + prev_quote = String::NPos; + else + escape = true; + } +#endif /* _WIN32 */ + + if (ch == '#' || ch == '&' || ch == ';' || ch == '`' || ch == '|' || + ch == '*' || ch == '?' || ch == '~' || ch == '<' || ch == '>' || + ch == '^' || ch == '(' || ch == ')' || ch == '[' || ch == ']' || + ch == '{' || ch == '}' || ch == '$' || ch == '\\' || ch == '\x0A' || + ch == '\xFF') + escape = true; + + if (escape) +#ifdef _WIN32 + result += '^'; +#else /* _WIN32 */ + result += '\\'; +#endif /* _WIN32 */ + + result += ch; + } + + return result; +} + +String Utility::EscapeShellArg(const String& s) +{ + String result; + +#ifdef _WIN32 + result = "\""; +#else /* _WIN32 */ + result = "'"; +#endif /* _WIN32 */ + + for (char ch : s) { +#ifdef _WIN32 + if (ch == '"' || ch == '%') { + result += ' '; + } +#else /* _WIN32 */ + if (ch == '\'') + result += "'\\'"; +#endif + result += ch; + } + +#ifdef _WIN32 + result += '"'; +#else /* _WIN32 */ + result += '\''; +#endif /* _WIN32 */ + + return result; +} + +#ifdef _WIN32 +String Utility::EscapeCreateProcessArg(const String& arg) +{ + if (arg.FindFirstOf(" \t\n\v\"") == String::NPos) + return arg; + + String result = "\""; + + for (String::ConstIterator it = arg.Begin(); ; it++) { + int numBackslashes = 0; + + while (it != arg.End() && *it == '\\') { + it++; + numBackslashes++; + } + + if (it == arg.End()) { + result.Append(numBackslashes * 2, '\\'); + break; + } else if (*it == '"') { + result.Append(numBackslashes * 2 + 1, '\\'); + result.Append(1, *it); + } else { + result.Append(numBackslashes, '\\'); + result.Append(1, *it); + } + } + + result += "\""; + + return result; +} +#endif /* _WIN32 */ + +#ifdef _WIN32 +static void WindowsSetThreadName(const char *name) +{ + THREADNAME_INFO info; + info.dwType = 0x1000; + info.szName = name; + info.dwThreadID = -1; + info.dwFlags = 0; + + __try { + RaiseException(MS_VC_EXCEPTION, 0, sizeof(info) / sizeof(ULONG_PTR), (ULONG_PTR *)&info); + } __except(EXCEPTION_EXECUTE_HANDLER) { + /* Nothing to do here. */ + } +} +#endif /* _WIN32 */ + +void Utility::SetThreadName(const String& name, bool os) +{ + m_ThreadName.reset(new String(name)); + + if (!os) + return; + +#ifdef _WIN32 + WindowsSetThreadName(name.CStr()); +#endif /* _WIN32 */ + +#ifdef HAVE_PTHREAD_SET_NAME_NP + pthread_set_name_np(pthread_self(), name.CStr()); +#endif /* HAVE_PTHREAD_SET_NAME_NP */ + +#ifdef HAVE_PTHREAD_SETNAME_NP +# ifdef __APPLE__ + pthread_setname_np(name.CStr()); +# else /* __APPLE__ */ + String tname = name.SubStr(0, 15); + pthread_setname_np(pthread_self(), tname.CStr()); +# endif /* __APPLE__ */ +#endif /* HAVE_PTHREAD_SETNAME_NP */ +} + +String Utility::GetThreadName() +{ + String *name = m_ThreadName.get(); + + if (!name) { + std::ostringstream idbuf; + idbuf << std::this_thread::get_id(); + return idbuf.str(); + } + + return *name; +} + +unsigned long Utility::SDBM(const String& str, size_t len) +{ + unsigned long hash = 0; + size_t current = 0; + + for (char c : str) { + if (current >= len) + break; + + hash = c + (hash << 6) + (hash << 16) - hash; + + current++; + } + + return hash; +} + +String Utility::ParseVersion(const String& v) +{ + /* + * 2.11.0-0.rc1.1 + * v2.10.5 + * r2.10.3 + * v2.11.0-rc1-58-g7c1f716da + */ + boost::regex pattern("^[vr]?(2\\.\\d+\\.\\d+).*$"); + boost::smatch result; + + if (boost::regex_search(v.GetData(), result, pattern)) { + String res(result[1].first, result[1].second); + return res; + } + + // Couldn't not extract anything, return unparsed version + return v; +} + +int Utility::CompareVersion(const String& v1, const String& v2) +{ + std::vector<String> tokensv1 = v1.Split("."); + std::vector<String> tokensv2 = v2.Split("."); + + for (std::vector<String>::size_type i = 0; i < tokensv2.size() - tokensv1.size(); i++) + tokensv1.emplace_back("0"); + + for (std::vector<String>::size_type i = 0; i < tokensv1.size() - tokensv2.size(); i++) + tokensv2.emplace_back("0"); + + for (std::vector<String>::size_type i = 0; i < tokensv1.size(); i++) { + if (Convert::ToLong(tokensv2[i]) > Convert::ToLong(tokensv1[i])) + return 1; + else if (Convert::ToLong(tokensv2[i]) < Convert::ToLong(tokensv1[i])) + return -1; + } + + return 0; +} + +String Utility::GetHostName() +{ + char name[255]; + + if (gethostname(name, sizeof(name)) < 0) + return "localhost"; + + return name; +} + +/** + * Returns the fully-qualified domain name for the host + * we're running on. + * + * @returns The FQDN. + */ +String Utility::GetFQDN() +{ + String hostname = GetHostName(); + + addrinfo hints; + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_DGRAM; + hints.ai_flags = AI_CANONNAME; + + addrinfo *result; + int rc = getaddrinfo(hostname.CStr(), nullptr, &hints, &result); + + if (rc != 0) + result = nullptr; + + if (result) { + if (strcmp(result->ai_canonname, "localhost") != 0) + hostname = result->ai_canonname; + + freeaddrinfo(result); + } + + return hostname; +} + +int Utility::Random() +{ +#ifdef _WIN32 + return rand(); +#else /* _WIN32 */ + unsigned int *seed = m_RandSeed.get(); + + if (!seed) { + seed = new unsigned int(Utility::GetTime()); + m_RandSeed.reset(seed); + } + + return rand_r(seed); +#endif /* _WIN32 */ +} + +tm Utility::LocalTime(time_t ts) +{ +#ifdef _MSC_VER + tm *result = localtime(&ts); + + if (!result) { + BOOST_THROW_EXCEPTION(posix_error() + << boost::errinfo_api_function("localtime") + << boost::errinfo_errno(errno)); + } + + return *result; +#else /* _MSC_VER */ + tm result; + + if (!localtime_r(&ts, &result)) { + BOOST_THROW_EXCEPTION(posix_error() + << boost::errinfo_api_function("localtime_r") + << boost::errinfo_errno(errno)); + } + + return result; +#endif /* _MSC_VER */ +} + +bool Utility::PathExists(const String& path) +{ + namespace fs = boost::filesystem; + + boost::system::error_code ec; + + return fs::exists(fs::path(path.Begin(), path.End()), ec) && !ec; +} + +time_t Utility::GetFileCreationTime(const String& path) +{ + namespace fs = boost::filesystem; + + return fs::last_write_time(boost::lexical_cast<fs::path>(path)); +} + +Value Utility::LoadJsonFile(const String& path) +{ + std::ifstream fp; + fp.open(path.CStr()); + + String json((std::istreambuf_iterator<char>(fp)), std::istreambuf_iterator<char>()); + + fp.close(); + + if (fp.fail()) + BOOST_THROW_EXCEPTION(std::runtime_error("Could not read JSON file '" + path + "'.")); + + return JsonDecode(json); +} + +void Utility::SaveJsonFile(const String& path, int mode, const Value& value) +{ + AtomicFile::Write(path, mode, JsonEncode(value)); +} + +static void HexEncode(char ch, std::ostream& os) +{ + const char *hex_chars = "0123456789ABCDEF"; + + os << hex_chars[ch >> 4 & 0x0f]; + os << hex_chars[ch & 0x0f]; +} + +static int HexDecode(char hc) +{ + if (hc >= '0' && hc <= '9') + return hc - '0'; + else if (hc >= 'a' && hc <= 'f') + return hc - 'a' + 10; + else if (hc >= 'A' && hc <= 'F') + return hc - 'A' + 10; + else + BOOST_THROW_EXCEPTION(std::invalid_argument("Invalid hex character.")); +} + +String Utility::EscapeString(const String& s, const String& chars, const bool illegal) +{ + std::ostringstream result; + if (illegal) { + for (char ch : s) { + if (chars.FindFirstOf(ch) != String::NPos || ch == '%') { + result << '%'; + HexEncode(ch, result); + } else + result << ch; + } + } else { + for (char ch : s) { + if (chars.FindFirstOf(ch) == String::NPos || ch == '%') { + result << '%'; + HexEncode(ch, result); + } else + result << ch; + } + } + + return result.str(); +} + +String Utility::UnescapeString(const String& s) +{ + std::ostringstream result; + + for (String::SizeType i = 0; i < s.GetLength(); i++) { + if (s[i] == '%') { + if (i + 2 > s.GetLength() - 1) + BOOST_THROW_EXCEPTION(std::invalid_argument("Invalid escape sequence.")); + + char ch = HexDecode(s[i + 1]) * 16 + HexDecode(s[i + 2]); + result << ch; + + i += 2; + } else + result << s[i]; + } + + return result.str(); +} + +#ifndef _WIN32 +static String UnameHelper(char type) +{ + struct utsname name; + uname(&name); + + switch (type) { + case 'm': + return (char*)name.machine; + case 'n': + return (char*)name.nodename; + case 'r': + return (char*)name.release; + case 's': + return (char*)name.sysname; + case 'v': + return (char*)name.version; + default: + VERIFY(!"Invalid uname query."); + } +} +#endif /* _WIN32 */ +static bool ReleaseHelper(String *platformName, String *platformVersion) +{ +#ifdef _WIN32 + if (platformName) + *platformName = "Windows"; + + if (platformVersion) { + *platformVersion = "Vista"; + if (IsWindowsVistaSP1OrGreater()) + *platformVersion = "Vista SP1"; + if (IsWindowsVistaSP2OrGreater()) + *platformVersion = "Vista SP2"; + if (IsWindows7OrGreater()) + *platformVersion = "7"; + if (IsWindows7SP1OrGreater()) + *platformVersion = "7 SP1"; + if (IsWindows8OrGreater()) + *platformVersion = "8"; + if (IsWindows8Point1OrGreater()) + *platformVersion = "8.1 or greater"; + if (IsWindowsServer()) + *platformVersion += " (Server)"; + } + + return true; +#else /* _WIN32 */ + if (platformName) + *platformName = "Unknown"; + + if (platformVersion) + *platformVersion = "Unknown"; + + /* You have systemd or Ubuntu etc. */ + std::ifstream release("/etc/os-release"); + if (release.is_open()) { + std::string release_line; + while (getline(release, release_line)) { + std::string::size_type pos = release_line.find("="); + + if (pos == std::string::npos) + continue; + + std::string key = release_line.substr(0, pos); + std::string value = release_line.substr(pos + 1); + + std::string::size_type firstQuote = value.find("\""); + + if (firstQuote != std::string::npos) + value.erase(0, firstQuote + 1); + + std::string::size_type lastQuote = value.rfind("\""); + + if (lastQuote != std::string::npos) + value.erase(lastQuote); + + if (platformName && key == "NAME") + *platformName = value; + + if (platformVersion && key == "VERSION") + *platformVersion = value; + } + + return true; + } + + /* You are using a distribution which supports LSB. */ + FILE *fp = popen("type lsb_release >/dev/null 2>&1 && lsb_release -s -i 2>&1", "r"); + + if (fp) { + std::ostringstream msgbuf; + char line[1024]; + while (fgets(line, sizeof(line), fp)) + msgbuf << line; + int status = pclose(fp); + if (WEXITSTATUS(status) == 0) { + if (platformName) + *platformName = msgbuf.str(); + } + } + + fp = popen("type lsb_release >/dev/null 2>&1 && lsb_release -s -r 2>&1", "r"); + + if (fp) { + std::ostringstream msgbuf; + char line[1024]; + while (fgets(line, sizeof(line), fp)) + msgbuf << line; + int status = pclose(fp); + if (WEXITSTATUS(status) == 0) { + if (platformVersion) + *platformVersion = msgbuf.str(); + } + } + + /* OS X */ + fp = popen("type sw_vers >/dev/null 2>&1 && sw_vers -productName 2>&1", "r"); + + if (fp) { + std::ostringstream msgbuf; + char line[1024]; + while (fgets(line, sizeof(line), fp)) + msgbuf << line; + int status = pclose(fp); + if (WEXITSTATUS(status) == 0) { + String info = msgbuf.str(); + info = info.Trim(); + + if (platformName) + *platformName = info; + } + } + + fp = popen("type sw_vers >/dev/null 2>&1 && sw_vers -productVersion 2>&1", "r"); + + if (fp) { + std::ostringstream msgbuf; + char line[1024]; + while (fgets(line, sizeof(line), fp)) + msgbuf << line; + int status = pclose(fp); + if (WEXITSTATUS(status) == 0) { + String info = msgbuf.str(); + info = info.Trim(); + + if (platformVersion) + *platformVersion = info; + + return true; + } + } + + /* Centos/RHEL < 7 */ + release.close(); + release.open("/etc/redhat-release"); + if (release.is_open()) { + std::string release_line; + getline(release, release_line); + + String info = release_line; + + /* example: Red Hat Enterprise Linux Server release 6.7 (Santiago) */ + if (platformName) + *platformName = info.SubStr(0, info.Find("release") - 1); + + if (platformVersion) + *platformVersion = info.SubStr(info.Find("release") + 8); + + return true; + } + + /* sles 11 sp3, opensuse w/e */ + release.close(); + release.open("/etc/SuSE-release"); + if (release.is_open()) { + std::string release_line; + getline(release, release_line); + + String info = release_line; + + if (platformName) + *platformName = info.SubStr(0, info.FindFirstOf(" ")); + + if (platformVersion) + *platformVersion = info.SubStr(info.FindFirstOf(" ") + 1); + + return true; + } + + /* Just give up */ + return false; +#endif /* _WIN32 */ +} + +String Utility::GetPlatformKernel() +{ +#ifdef _WIN32 + return "Windows"; +#else /* _WIN32 */ + return UnameHelper('s'); +#endif /* _WIN32 */ +} + +String Utility::GetPlatformKernelVersion() +{ +#ifdef _WIN32 + OSVERSIONINFO info; + info.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); + GetVersionEx(&info); + + std::ostringstream msgbuf; + msgbuf << info.dwMajorVersion << "." << info.dwMinorVersion; + + return msgbuf.str(); +#else /* _WIN32 */ + return UnameHelper('r'); +#endif /* _WIN32 */ +} + +String Utility::GetPlatformName() +{ + String platformName; + if (!ReleaseHelper(&platformName, nullptr)) + return "Unknown"; + return platformName; +} + +String Utility::GetPlatformVersion() +{ + String platformVersion; + if (!ReleaseHelper(nullptr, &platformVersion)) + return "Unknown"; + return platformVersion; +} + +String Utility::GetPlatformArchitecture() +{ +#ifdef _WIN32 + SYSTEM_INFO info; + GetNativeSystemInfo(&info); + switch (info.wProcessorArchitecture) { + case PROCESSOR_ARCHITECTURE_AMD64: + return "x86_64"; + case PROCESSOR_ARCHITECTURE_ARM: + return "arm"; + case PROCESSOR_ARCHITECTURE_INTEL: + return "x86"; + default: + return "unknown"; + } +#else /* _WIN32 */ + return UnameHelper('m'); +#endif /* _WIN32 */ +} + +const char l_Utf8Replacement[] = "\xEF\xBF\xBD"; + +String Utility::ValidateUTF8(const String& input) +{ + std::string output; + output.reserve(input.GetLength()); + + try { + utf8::replace_invalid(input.Begin(), input.End(), std::back_inserter(output)); + } catch (const utf8::not_enough_room&) { + output.insert(output.end(), (const char*)l_Utf8Replacement, (const char*)l_Utf8Replacement + 3); + } + + return String(std::move(output)); +} + +#ifdef _WIN32 +/* mkstemp extracted from libc/sysdeps/posix/tempname.c. Copyright + * (C) 1991-1999, 2000, 2001, 2006 Free Software Foundation, Inc. + * + * The GNU C Library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + */ + +#define _O_EXCL 0x0400 +#define _O_CREAT 0x0100 +#define _O_RDWR 0x0002 +#define O_EXCL _O_EXCL +#define O_CREAT _O_CREAT +#define O_RDWR _O_RDWR + +static const char letters[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; + +/* Generate a temporary file name based on TMPL. TMPL must match the + * rules for mk[s]temp (i.e. end in "XXXXXX"). The name constructed + * does not exist at the time of the call to mkstemp. TMPL is + * overwritten with the result. + */ +int Utility::MksTemp(char *tmpl) +{ + int len; + char *XXXXXX; + static unsigned long long value; + unsigned long long random_time_bits; + unsigned int count; + int fd = -1; + int save_errno = errno; + + /* A lower bound on the number of temporary files to attempt to + * generate. The maximum total number of temporary file names that + * can exist for a given template is 62**6. It should never be + * necessary to try all these combinations. Instead if a reasonable + * number of names is tried (we define reasonable as 62**3) fail to + * give the system administrator the chance to remove the problems. + */ +#define ATTEMPTS_MIN (62 * 62 * 62) + + /* The number of times to attempt to generate a temporary file + * To conform to POSIX, this must be no smaller than TMP_MAX. + */ +#if ATTEMPTS_MIN < TMP_MAX + unsigned int attempts = TMP_MAX; +#else + unsigned int attempts = ATTEMPTS_MIN; +#endif + + len = strlen (tmpl); + if (len < 6 || strcmp (&tmpl[len - 6], "XXXXXX")) { + errno = EINVAL; + return -1; + } + + /* This is where the Xs start. */ + XXXXXX = &tmpl[len - 6]; + + /* Get some more or less random data. */ + { + SYSTEMTIME stNow; + FILETIME ftNow; + + // get system time + GetSystemTime(&stNow); + stNow.wMilliseconds = 500; + if (!SystemTimeToFileTime(&stNow, &ftNow)) { + errno = -1; + return -1; + } + + random_time_bits = (((unsigned long long)ftNow.dwHighDateTime << 32) | (unsigned long long)ftNow.dwLowDateTime); + } + + value += random_time_bits ^ (unsigned long long)GetCurrentThreadId(); + + for (count = 0; count < attempts; value += 7777, ++count) { + unsigned long long v = value; + + /* Fill in the random bits. */ + XXXXXX[0] = letters[v % 62]; + v /= 62; + XXXXXX[1] = letters[v % 62]; + v /= 62; + XXXXXX[2] = letters[v % 62]; + v /= 62; + XXXXXX[3] = letters[v % 62]; + v /= 62; + XXXXXX[4] = letters[v % 62]; + v /= 62; + XXXXXX[5] = letters[v % 62]; + + fd = open(tmpl, O_RDWR | O_CREAT | O_EXCL, _S_IREAD | _S_IWRITE); + if (fd >= 0) { + errno = save_errno; + return fd; + } else if (errno != EEXIST) + return -1; + } + + /* We got out of the loop because we ran out of combinations to try. */ + errno = EEXIST; + return -1; +} + +String Utility::GetIcingaInstallPath() +{ + char szProduct[39]; + + for (int i = 0; MsiEnumProducts(i, szProduct) == ERROR_SUCCESS; i++) { + char szName[128]; + DWORD cbName = sizeof(szName); + if (MsiGetProductInfo(szProduct, INSTALLPROPERTY_INSTALLEDPRODUCTNAME, szName, &cbName) != ERROR_SUCCESS) + continue; + + if (strcmp(szName, "Icinga 2") != 0) + continue; + + char szLocation[1024]; + DWORD cbLocation = sizeof(szLocation); + if (MsiGetProductInfo(szProduct, INSTALLPROPERTY_INSTALLLOCATION, szLocation, &cbLocation) == ERROR_SUCCESS) + return szLocation; + } + + return ""; +} + +String Utility::GetIcingaDataPath() +{ + char path[MAX_PATH]; + if (!SUCCEEDED(SHGetFolderPath(nullptr, CSIDL_COMMON_APPDATA, nullptr, 0, path))) + return ""; + return String(path) + "\\icinga2"; +} + +#endif /* _WIN32 */ + +/** + * Retrieve the environment variable value by given key. + * + * @param env Environment variable name. + */ + +String Utility::GetFromEnvironment(const String& env) +{ + const char *envValue = getenv(env.CStr()); + + if (envValue == NULL) + return String(); + else + return String(envValue); +} + +/** + * Compare the password entered by a client with the actual password. + * The comparision is safe against timing attacks. + */ +bool Utility::ComparePasswords(const String& enteredPassword, const String& actualPassword) +{ + volatile const char * volatile enteredPasswordCStr = enteredPassword.CStr(); + volatile size_t enteredPasswordLen = enteredPassword.GetLength(); + + volatile const char * volatile actualPasswordCStr = actualPassword.CStr(); + volatile size_t actualPasswordLen = actualPassword.GetLength(); + + volatile uint_fast8_t result = enteredPasswordLen == actualPasswordLen; + + if (result) { + auto cStr (actualPasswordCStr); + auto len (actualPasswordLen); + + actualPasswordCStr = cStr; + actualPasswordLen = len; + } else { + auto cStr (enteredPasswordCStr); + auto len (enteredPasswordLen); + + actualPasswordCStr = cStr; + actualPasswordLen = len; + } + + for (volatile size_t i = 0; i < enteredPasswordLen; ++i) { + result &= uint_fast8_t(enteredPasswordCStr[i] == actualPasswordCStr[i]); + } + + return result; +} diff --git a/lib/base/utility.hpp b/lib/base/utility.hpp new file mode 100644 index 0000000..47b68d2 --- /dev/null +++ b/lib/base/utility.hpp @@ -0,0 +1,200 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#ifndef UTILITY_H +#define UTILITY_H + +#include "base/i2-base.hpp" +#include "base/string.hpp" +#include "base/array.hpp" +#include "base/threadpool.hpp" +#include "base/tlsutility.hpp" +#include <boost/thread/tss.hpp> +#include <openssl/sha.h> +#include <functional> +#include <typeinfo> +#include <vector> + +namespace icinga +{ + +#ifdef _WIN32 +#define MS_VC_EXCEPTION 0x406D1388 + +# pragma pack(push, 8) +struct THREADNAME_INFO +{ + DWORD dwType; + LPCSTR szName; + DWORD dwThreadID; + DWORD dwFlags; +}; +# pragma pack(pop) +#endif + +enum GlobType +{ + GlobFile = 1, + GlobDirectory = 2 +}; + +/** + * Helper functions. + * + * @ingroup base + */ +class Utility +{ +public: + static String DemangleSymbolName(const String& sym); + static String GetTypeName(const std::type_info& ti); + static String GetSymbolName(const void *addr); + + static bool Match(const String& pattern, const String& text); + static bool CidrMatch(const String& pattern, const String& ip); + + static String DirName(const String& path); + static String BaseName(const String& path); + + static void NullDeleter(void *); + + static double GetTime(); + + static pid_t GetPid(); + + static void Sleep(double timeout); + + static String NewUniqueID(); + + static bool Glob(const String& pathSpec, const std::function<void (const String&)>& callback, int type = GlobFile | GlobDirectory); + static bool GlobRecursive(const String& path, const String& pattern, const std::function<void (const String&)>& callback, int type = GlobFile | GlobDirectory); + static void MkDir(const String& path, int mode); + static void MkDirP(const String& path, int mode); + static bool SetFileOwnership(const String& file, const String& user, const String& group); + + static void QueueAsyncCallback(const std::function<void ()>& callback, SchedulerPolicy policy = DefaultScheduler); + + static String NaturalJoin(const std::vector<String>& tokens); + static String Join(const Array::Ptr& tokens, char separator, bool escapeSeparator = true); + + static String FormatDuration(double duration); + static String FormatDateTime(const char *format, double ts); + static String FormatErrorNumber(int code); + +#ifndef _WIN32 + static void SetNonBlocking(int fd, bool nb = true); + static void SetCloExec(int fd, bool cloexec = true); + + static void CloseAllFDs(const std::vector<int>& except, std::function<void(int)> onClose = nullptr); +#endif /* _WIN32 */ + + static void SetNonBlockingSocket(SOCKET s, bool nb = true); + + static String EscapeShellCmd(const String& s); + static String EscapeShellArg(const String& s); +#ifdef _WIN32 + static String EscapeCreateProcessArg(const String& arg); +#endif /* _WIN32 */ + + static String EscapeString(const String& s, const String& chars, const bool illegal); + static String UnescapeString(const String& s); + + static void SetThreadName(const String& name, bool os = true); + static String GetThreadName(); + + static unsigned long SDBM(const String& str, size_t len = String::NPos); + + static String ParseVersion(const String& v); + static int CompareVersion(const String& v1, const String& v2); + + static int Random(); + + static String GetHostName(); + static String GetFQDN(); + + static tm LocalTime(time_t ts); + + static bool PathExists(const String& path); + static time_t GetFileCreationTime(const String& path); + + static void Remove(const String& path); + static void RemoveDirRecursive(const String& path); + static void CopyFile(const String& source, const String& target); + static void RenameFile(const String& source, const String& target); + + static Value LoadJsonFile(const String& path); + static void SaveJsonFile(const String& path, int mode, const Value& value); + + static String GetPlatformKernel(); + static String GetPlatformKernelVersion(); + static String GetPlatformName(); + static String GetPlatformVersion(); + static String GetPlatformArchitecture(); + + static String ValidateUTF8(const String& input); + +#ifdef _WIN32 + static int MksTemp(char *tmpl); +#endif /* _WIN32 */ + +#ifdef _WIN32 + static String GetIcingaInstallPath(); + static String GetIcingaDataPath(); +#endif /* _WIN32 */ + + static String GetFromEnvironment(const String& env); + + static bool ComparePasswords(const String& enteredPassword, const String& actualPassword); + +#ifdef I2_DEBUG + static void SetTime(double); + static void IncrementTime(double); +#endif /* I2_DEBUG */ + + /** + * TruncateUsingHash truncates a given string to an allowed maximum length while avoiding collisions in the output + * using a hash function (SHA1). + * + * For inputs shorter than the maximum output length, the output will be the same as the input. If the input has at + * least the maximum output length, it is hashed used SHA1 and the output has the format "A...B" where A is a prefix + * of the input and B is the hex-encoded SHA1 hash of the input. The length of A is chosen so that the result has + * the maximum allowed output length. + * + * @tparam maxLength Maximum length of the output string (must be at least 44) + * @param in String to truncate + * @return A truncated string derived from in of at most length maxLength + */ + template<size_t maxLength> + static String TruncateUsingHash(const String &in) { + /* + * Note: be careful when changing this function as it is used to derive file names that should not change + * between versions or would need special handling if they do (/var/lib/icinga2/api/packages/_api). + */ + + const size_t sha1HexLength = SHA_DIGEST_LENGTH*2; + static_assert(maxLength >= 1 + 3 + sha1HexLength, + "maxLength must be at least 44 to hold one character, '...', and a hex-encoded SHA1 hash"); + + /* If the input is shorter than the limit, no truncation is needed */ + if (in.GetLength() < maxLength) { + return in; + } + + const char *trunc = "..."; + + return in.SubStr(0, maxLength - sha1HexLength - strlen(trunc)) + trunc + SHA1(in); + } + +private: + Utility(); + +#ifdef I2_DEBUG + static double m_DebugTime; +#endif /* I2_DEBUG */ + + static boost::thread_specific_ptr<String> m_ThreadName; + static boost::thread_specific_ptr<unsigned int> m_RandSeed; +}; + +} + +#endif /* UTILITY_H */ diff --git a/lib/base/value-operators.cpp b/lib/base/value-operators.cpp new file mode 100644 index 0000000..d00c3e2 --- /dev/null +++ b/lib/base/value-operators.cpp @@ -0,0 +1,719 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#include "base/value.hpp" +#include "base/array.hpp" +#include "base/dictionary.hpp" +#include "base/datetime.hpp" +#include "base/convert.hpp" +#include "base/utility.hpp" +#include "base/objectlock.hpp" +#include <boost/lexical_cast.hpp> + +using namespace icinga; + +Value::operator double() const +{ + const double *value = boost::get<double>(&m_Value); + + if (value) + return *value; + + const bool *fvalue = boost::get<bool>(&m_Value); + + if (fvalue) + return *fvalue; + + if (IsEmpty()) + return 0; + + try { + return boost::lexical_cast<double>(m_Value); + } catch (const std::exception&) { + std::ostringstream msgbuf; + msgbuf << "Can't convert '" << *this << "' to a floating point number."; + BOOST_THROW_EXCEPTION(std::invalid_argument(msgbuf.str())); + } +} + +Value::operator String() const +{ + Object *object; + + switch (GetType()) { + case ValueEmpty: + return String(); + case ValueNumber: + return Convert::ToString(boost::get<double>(m_Value)); + case ValueBoolean: + if (boost::get<bool>(m_Value)) + return "true"; + else + return "false"; + case ValueString: + return boost::get<String>(m_Value); + case ValueObject: + object = boost::get<Object::Ptr>(m_Value).get(); + return object->ToString(); + default: + BOOST_THROW_EXCEPTION(std::runtime_error("Unknown value type.")); + } +} + +std::ostream& icinga::operator<<(std::ostream& stream, const Value& value) +{ + if (value.IsBoolean()) + stream << static_cast<int>(value); + else + stream << static_cast<String>(value); + + return stream; +} + +std::istream& icinga::operator>>(std::istream& stream, Value& value) +{ + String tstr; + stream >> tstr; + value = tstr; + return stream; +} + +bool Value::operator==(bool rhs) const +{ + return *this == Value(rhs); +} + +bool Value::operator!=(bool rhs) const +{ + return !(*this == rhs); +} + +bool Value::operator==(int rhs) const +{ + return *this == Value(rhs); +} + +bool Value::operator!=(int rhs) const +{ + return !(*this == rhs); +} + +bool Value::operator==(double rhs) const +{ + return *this == Value(rhs); +} + +bool Value::operator!=(double rhs) const +{ + return !(*this == rhs); +} + +bool Value::operator==(const char *rhs) const +{ + return static_cast<String>(*this) == rhs; +} + +bool Value::operator!=(const char *rhs) const +{ + return !(*this == rhs); +} + +bool Value::operator==(const String& rhs) const +{ + return static_cast<String>(*this) == rhs; +} + +bool Value::operator!=(const String& rhs) const +{ + return !(*this == rhs); +} + +bool Value::operator==(const Value& rhs) const +{ + if (IsNumber() && rhs.IsNumber()) + return Get<double>() == rhs.Get<double>(); + else if ((IsBoolean() || IsNumber()) && (rhs.IsBoolean() || rhs.IsNumber()) && !(IsEmpty() && rhs.IsEmpty())) + return static_cast<double>(*this) == static_cast<double>(rhs); + + if (IsString() && rhs.IsString()) + return Get<String>() == rhs.Get<String>(); + else if ((IsString() || IsEmpty()) && (rhs.IsString() || rhs.IsEmpty()) && !(IsEmpty() && rhs.IsEmpty())) + return static_cast<String>(*this) == static_cast<String>(rhs); + + if (IsEmpty() != rhs.IsEmpty()) + return false; + + if (IsEmpty()) + return true; + + if (IsObject() != rhs.IsObject()) + return false; + + if (IsObject()) { + if (IsObjectType<DateTime>() && rhs.IsObjectType<DateTime>()) { + DateTime::Ptr dt1 = *this; + DateTime::Ptr dt2 = rhs; + + return dt1->GetValue() == dt2->GetValue(); + } + + if (IsObjectType<Array>() && rhs.IsObjectType<Array>()) { + Array::Ptr arr1 = *this; + Array::Ptr arr2 = rhs; + + if (arr1 == arr2) + return true; + + if (arr1->GetLength() != arr2->GetLength()) + return false; + + for (Array::SizeType i = 0; i < arr1->GetLength(); i++) { + if (arr1->Get(i) != arr2->Get(i)) + return false; + } + + return true; + } + + return Get<Object::Ptr>() == rhs.Get<Object::Ptr>(); + } + + return false; +} + +bool Value::operator!=(const Value& rhs) const +{ + return !(*this == rhs); +} + +Value icinga::operator+(const Value& lhs, const char *rhs) +{ + return lhs + Value(rhs); +} + +Value icinga::operator+(const char *lhs, const Value& rhs) +{ + return Value(lhs) + rhs; +} + +Value icinga::operator+(const Value& lhs, const String& rhs) +{ + return lhs + Value(rhs); +} + +Value icinga::operator+(const String& lhs, const Value& rhs) +{ + return Value(lhs) + rhs; +} + +Value icinga::operator+(const Value& lhs, const Value& rhs) +{ + if ((lhs.IsEmpty() || lhs.IsNumber()) && !lhs.IsString() && (rhs.IsEmpty() || rhs.IsNumber()) && !rhs.IsString() && !(lhs.IsEmpty() && rhs.IsEmpty())) + return static_cast<double>(lhs) + static_cast<double>(rhs); + if ((lhs.IsString() || lhs.IsEmpty() || lhs.IsNumber()) && (rhs.IsString() || rhs.IsEmpty() || rhs.IsNumber()) && (!(lhs.IsEmpty() && rhs.IsEmpty()) || lhs.IsString() || rhs.IsString())) + return static_cast<String>(lhs) + static_cast<String>(rhs); + else if ((lhs.IsNumber() || lhs.IsEmpty()) && (rhs.IsNumber() || rhs.IsEmpty()) && !(lhs.IsEmpty() && rhs.IsEmpty())) + return static_cast<double>(lhs) + static_cast<double>(rhs); + else if (lhs.IsObjectType<DateTime>() && rhs.IsNumber()) + return new DateTime(Convert::ToDateTimeValue(lhs) + rhs); + else if ((lhs.IsObjectType<Array>() || lhs.IsEmpty()) && (rhs.IsObjectType<Array>() || rhs.IsEmpty()) && !(lhs.IsEmpty() && rhs.IsEmpty())) { + Array::Ptr result = new Array(); + if (!lhs.IsEmpty()) + static_cast<Array::Ptr>(lhs)->CopyTo(result); + if (!rhs.IsEmpty()) + static_cast<Array::Ptr>(rhs)->CopyTo(result); + return result; + } else if ((lhs.IsObjectType<Dictionary>() || lhs.IsEmpty()) && (rhs.IsObjectType<Dictionary>() || rhs.IsEmpty()) && !(lhs.IsEmpty() && rhs.IsEmpty())) { + Dictionary::Ptr result = new Dictionary(); + if (!lhs.IsEmpty()) + static_cast<Dictionary::Ptr>(lhs)->CopyTo(result); + if (!rhs.IsEmpty()) + static_cast<Dictionary::Ptr>(rhs)->CopyTo(result); + return result; + } else { + BOOST_THROW_EXCEPTION(std::invalid_argument("Operator + cannot be applied to values of type '" + lhs.GetTypeName() + "' and '" + rhs.GetTypeName() + "'")); + } +} + +Value icinga::operator+(const Value& lhs, double rhs) +{ + return lhs + Value(rhs); +} + +Value icinga::operator+(double lhs, const Value& rhs) +{ + return Value(lhs) + rhs; +} + +Value icinga::operator+(const Value& lhs, int rhs) +{ + return lhs + Value(rhs); +} + +Value icinga::operator+(int lhs, const Value& rhs) +{ + return Value(lhs) + rhs; +} + +Value icinga::operator-(const Value& lhs, const Value& rhs) +{ + if ((lhs.IsNumber() || lhs.IsEmpty()) && !lhs.IsString() && (rhs.IsNumber() || rhs.IsEmpty()) && !rhs.IsString() && !(lhs.IsEmpty() && rhs.IsEmpty())) + return static_cast<double>(lhs) - static_cast<double>(rhs); + else if (lhs.IsObjectType<DateTime>() && rhs.IsNumber()) + return new DateTime(Convert::ToDateTimeValue(lhs) - rhs); + else if (lhs.IsObjectType<DateTime>() && rhs.IsObjectType<DateTime>()) + return Convert::ToDateTimeValue(lhs) - Convert::ToDateTimeValue(rhs); + else if ((lhs.IsObjectType<DateTime>() || lhs.IsEmpty()) && (rhs.IsObjectType<DateTime>() || rhs.IsEmpty()) && !(lhs.IsEmpty() && rhs.IsEmpty())) + return new DateTime(Convert::ToDateTimeValue(lhs) - Convert::ToDateTimeValue(rhs)); + else if ((lhs.IsObjectType<Array>() || lhs.IsEmpty()) && (rhs.IsObjectType<Array>() || rhs.IsEmpty()) && !(lhs.IsEmpty() && rhs.IsEmpty())) { + if (lhs.IsEmpty()) + return new Array(); + + ArrayData result; + Array::Ptr left = lhs; + Array::Ptr right = rhs; + + ObjectLock olock(left); + for (const Value& lv : left) { + bool found = false; + ObjectLock xlock(right); + for (const Value& rv : right) { + if (lv == rv) { + found = true; + break; + } + } + + if (found) + continue; + + result.push_back(lv); + } + + return new Array(std::move(result)); + } else + BOOST_THROW_EXCEPTION(std::invalid_argument("Operator - cannot be applied to values of type '" + lhs.GetTypeName() + "' and '" + rhs.GetTypeName() + "'")); +} + +Value icinga::operator-(const Value& lhs, double rhs) +{ + return lhs - Value(rhs); +} + +Value icinga::operator-(double lhs, const Value& rhs) +{ + return Value(lhs) - rhs; +} + +Value icinga::operator-(const Value& lhs, int rhs) +{ + return lhs - Value(rhs); +} + +Value icinga::operator-(int lhs, const Value& rhs) +{ + return Value(lhs) - rhs; +} + +Value icinga::operator*(const Value& lhs, const Value& rhs) +{ + if ((lhs.IsNumber() || lhs.IsEmpty()) && (rhs.IsNumber() || rhs.IsEmpty()) && !(lhs.IsEmpty() && rhs.IsEmpty())) + return static_cast<double>(lhs) * static_cast<double>(rhs); + else + BOOST_THROW_EXCEPTION(std::invalid_argument("Operator * cannot be applied to values of type '" + lhs.GetTypeName() + "' and '" + rhs.GetTypeName() + "'")); +} + +Value icinga::operator*(const Value& lhs, double rhs) +{ + return lhs * Value(rhs); +} + +Value icinga::operator*(double lhs, const Value& rhs) +{ + return Value(lhs) * rhs; +} + +Value icinga::operator*(const Value& lhs, int rhs) +{ + return lhs * Value(rhs); +} + +Value icinga::operator*(int lhs, const Value& rhs) +{ + return Value(lhs) * rhs; +} + +Value icinga::operator/(const Value& lhs, const Value& rhs) +{ + if (rhs.IsEmpty()) + BOOST_THROW_EXCEPTION(std::invalid_argument("Right-hand side argument for operator / is Empty.")); + else if ((lhs.IsEmpty() || lhs.IsNumber()) && rhs.IsNumber()) { + if (static_cast<double>(rhs) == 0) + BOOST_THROW_EXCEPTION(std::invalid_argument("Right-hand side argument for operator / is 0.")); + + return static_cast<double>(lhs) / static_cast<double>(rhs); + } else + BOOST_THROW_EXCEPTION(std::invalid_argument("Operator / cannot be applied to values of type '" + lhs.GetTypeName() + "' and '" + rhs.GetTypeName() + "'")); +} + +Value icinga::operator/(const Value& lhs, double rhs) +{ + return lhs / Value(rhs); +} + +Value icinga::operator/(double lhs, const Value& rhs) +{ + return Value(lhs) / rhs; +} + +Value icinga::operator/(const Value& lhs, int rhs) +{ + return lhs / Value(rhs); +} + +Value icinga::operator/(int lhs, const Value& rhs) +{ + return Value(lhs) / rhs; +} + +Value icinga::operator%(const Value& lhs, const Value& rhs) +{ + if (rhs.IsEmpty()) + BOOST_THROW_EXCEPTION(std::invalid_argument("Right-hand side argument for operator % is Empty.")); + else if ((rhs.IsNumber() || lhs.IsNumber()) && rhs.IsNumber()) { + if (static_cast<double>(rhs) == 0) + BOOST_THROW_EXCEPTION(std::invalid_argument("Right-hand side argument for operator % is 0.")); + + return static_cast<int>(lhs) % static_cast<int>(rhs); + } else + BOOST_THROW_EXCEPTION(std::invalid_argument("Operator % cannot be applied to values of type '" + lhs.GetTypeName() + "' and '" + rhs.GetTypeName() + "'")); +} + +Value icinga::operator%(const Value& lhs, double rhs) +{ + return lhs % Value(rhs); +} + +Value icinga::operator%(double lhs, const Value& rhs) +{ + return Value(lhs) % rhs; +} + +Value icinga::operator%(const Value& lhs, int rhs) +{ + return lhs % Value(rhs); +} + +Value icinga::operator%(int lhs, const Value& rhs) +{ + return Value(lhs) % rhs; +} + +Value icinga::operator^(const Value& lhs, const Value& rhs) +{ + if ((lhs.IsNumber() || lhs.IsEmpty()) && (rhs.IsNumber() || rhs.IsEmpty()) && !(lhs.IsEmpty() && rhs.IsEmpty())) + return static_cast<int>(lhs) ^ static_cast<int>(rhs); + else + BOOST_THROW_EXCEPTION(std::invalid_argument("Operator & cannot be applied to values of type '" + lhs.GetTypeName() + "' and '" + rhs.GetTypeName() + "'")); +} + +Value icinga::operator^(const Value& lhs, double rhs) +{ + return lhs ^ Value(rhs); +} + +Value icinga::operator^(double lhs, const Value& rhs) +{ + return Value(lhs) ^ rhs; +} + +Value icinga::operator^(const Value& lhs, int rhs) +{ + return lhs ^ Value(rhs); +} + +Value icinga::operator^(int lhs, const Value& rhs) +{ + return Value(lhs) ^ rhs; +} + +Value icinga::operator&(const Value& lhs, const Value& rhs) +{ + if ((lhs.IsNumber() || lhs.IsEmpty()) && (rhs.IsNumber() || rhs.IsEmpty()) && !(lhs.IsEmpty() && rhs.IsEmpty())) + return static_cast<int>(lhs) & static_cast<int>(rhs); + else + BOOST_THROW_EXCEPTION(std::invalid_argument("Operator & cannot be applied to values of type '" + lhs.GetTypeName() + "' and '" + rhs.GetTypeName() + "'")); +} + +Value icinga::operator&(const Value& lhs, double rhs) +{ + return lhs & Value(rhs); +} + +Value icinga::operator&(double lhs, const Value& rhs) +{ + return Value(lhs) & rhs; +} + +Value icinga::operator&(const Value& lhs, int rhs) +{ + return lhs & Value(rhs); +} + +Value icinga::operator&(int lhs, const Value& rhs) +{ + return Value(lhs) & rhs; +} + +Value icinga::operator|(const Value& lhs, const Value& rhs) +{ + if ((lhs.IsNumber() || lhs.IsEmpty()) && (rhs.IsNumber() || rhs.IsEmpty()) && !(lhs.IsEmpty() && rhs.IsEmpty())) + return static_cast<int>(lhs) | static_cast<int>(rhs); + else + BOOST_THROW_EXCEPTION(std::invalid_argument("Operator | cannot be applied to values of type '" + lhs.GetTypeName() + "' and '" + rhs.GetTypeName() + "'")); +} + +Value icinga::operator|(const Value& lhs, double rhs) +{ + return lhs | Value(rhs); +} + +Value icinga::operator|(double lhs, const Value& rhs) +{ + return Value(lhs) | rhs; +} + +Value icinga::operator|(const Value& lhs, int rhs) +{ + return lhs | Value(rhs); +} + +Value icinga::operator|(int lhs, const Value& rhs) +{ + return Value(lhs) | rhs; +} + +Value icinga::operator<<(const Value& lhs, const Value& rhs) +{ + if ((lhs.IsNumber() || lhs.IsEmpty()) && (rhs.IsNumber() || rhs.IsEmpty()) && !(lhs.IsEmpty() && rhs.IsEmpty())) + return static_cast<int>(lhs) << static_cast<int>(rhs); + else + BOOST_THROW_EXCEPTION(std::invalid_argument("Operator << cannot be applied to values of type '" + lhs.GetTypeName() + "' and '" + rhs.GetTypeName() + "'")); +} + +Value icinga::operator<<(const Value& lhs, double rhs) +{ + return lhs << Value(rhs); +} + +Value icinga::operator<<(double lhs, const Value& rhs) +{ + return Value(lhs) << rhs; +} + +Value icinga::operator<<(const Value& lhs, int rhs) +{ + return lhs << Value(rhs); +} + +Value icinga::operator<<(int lhs, const Value& rhs) +{ + return Value(lhs) << rhs; +} + +Value icinga::operator>>(const Value& lhs, const Value& rhs) +{ + if ((lhs.IsNumber() || lhs.IsEmpty()) && (rhs.IsNumber() || rhs.IsEmpty()) && !(lhs.IsEmpty() && rhs.IsEmpty())) + return static_cast<int>(lhs) >> static_cast<int>(rhs); + else + BOOST_THROW_EXCEPTION(std::invalid_argument("Operator >> cannot be applied to values of type '" + lhs.GetTypeName() + "' and '" + rhs.GetTypeName() + "'")); +} + +Value icinga::operator>>(const Value& lhs, double rhs) +{ + return lhs >> Value(rhs); +} + +Value icinga::operator>>(double lhs, const Value& rhs) +{ + return Value(lhs) >> rhs; +} + +Value icinga::operator>>(const Value& lhs, int rhs) +{ + return lhs >> Value(rhs); +} + +Value icinga::operator>>(int lhs, const Value& rhs) +{ + return Value(lhs) >> rhs; +} + +bool icinga::operator<(const Value& lhs, const Value& rhs) +{ + if (lhs.IsString() && rhs.IsString()) + return static_cast<String>(lhs) < static_cast<String>(rhs); + else if ((lhs.IsNumber() || lhs.IsEmpty()) && (rhs.IsNumber() || rhs.IsEmpty()) && !(lhs.IsEmpty() && rhs.IsEmpty())) + return static_cast<double>(lhs) < static_cast<double>(rhs); + else if ((lhs.IsObjectType<DateTime>() || lhs.IsEmpty()) && (rhs.IsObjectType<DateTime>() || rhs.IsEmpty()) && !(lhs.IsEmpty() && rhs.IsEmpty())) + return Convert::ToDateTimeValue(lhs) < Convert::ToDateTimeValue(rhs); + else if (lhs.IsObjectType<Array>() && rhs.IsObjectType<Array>()) { + Array::Ptr larr = lhs; + Array::Ptr rarr = rhs; + + ObjectLock llock(larr); + ObjectLock rlock(rarr); + + Array::SizeType llen = larr->GetLength(); + Array::SizeType rlen = rarr->GetLength(); + + for (Array::SizeType i = 0; i < std::max(llen, rlen); i++) { + Value lval = (i >= llen) ? Empty : larr->Get(i); + Value rval = (i >= rlen) ? Empty : rarr->Get(i); + + if (lval < rval) + return true; + else if (lval > rval) + return false; + } + + return false; + } else + BOOST_THROW_EXCEPTION(std::invalid_argument("Operator < cannot be applied to values of type '" + lhs.GetTypeName() + "' and '" + rhs.GetTypeName() + "'")); +} + +bool icinga::operator<(const Value& lhs, double rhs) +{ + return lhs < Value(rhs); +} + +bool icinga::operator<(double lhs, const Value& rhs) +{ + return Value(lhs) < rhs; +} + +bool icinga::operator<(const Value& lhs, int rhs) +{ + return lhs < Value(rhs); +} + +bool icinga::operator<(int lhs, const Value& rhs) +{ + return Value(lhs) < rhs; +} + +bool icinga::operator>(const Value& lhs, const Value& rhs) +{ + if (lhs.IsString() && rhs.IsString()) + return static_cast<String>(lhs) > static_cast<String>(rhs); + else if ((lhs.IsNumber() || lhs.IsEmpty()) && (rhs.IsNumber() || rhs.IsEmpty()) && !(lhs.IsEmpty() && rhs.IsEmpty())) + return static_cast<double>(lhs) > static_cast<double>(rhs); + else if ((lhs.IsObjectType<DateTime>() || lhs.IsEmpty()) && (rhs.IsObjectType<DateTime>() || rhs.IsEmpty()) && !(lhs.IsEmpty() && rhs.IsEmpty())) + return Convert::ToDateTimeValue(lhs) > Convert::ToDateTimeValue(rhs); + else if (lhs.IsObjectType<Array>() && rhs.IsObjectType<Array>()) { + Array::Ptr larr = lhs; + Array::Ptr rarr = rhs; + + ObjectLock llock(larr); + ObjectLock rlock(rarr); + + Array::SizeType llen = larr->GetLength(); + Array::SizeType rlen = rarr->GetLength(); + + for (Array::SizeType i = 0; i < std::max(llen, rlen); i++) { + Value lval = (i >= llen) ? Empty : larr->Get(i); + Value rval = (i >= rlen) ? Empty : rarr->Get(i); + + if (lval > rval) + return true; + else if (lval < rval) + return false; + } + + return false; + } else + BOOST_THROW_EXCEPTION(std::invalid_argument("Operator > cannot be applied to values of type '" + lhs.GetTypeName() + "' and '" + rhs.GetTypeName() + "'")); +} + +bool icinga::operator>(const Value& lhs, double rhs) +{ + return lhs > Value(rhs); +} + +bool icinga::operator>(double lhs, const Value& rhs) +{ + return Value(lhs) > rhs; +} + +bool icinga::operator>(const Value& lhs, int rhs) +{ + return lhs > Value(rhs); +} + +bool icinga::operator>(int lhs, const Value& rhs) +{ + return Value(lhs) > rhs; +} + +bool icinga::operator<=(const Value& lhs, const Value& rhs) +{ + if (lhs.IsString() && rhs.IsString()) + return static_cast<String>(lhs) <= static_cast<String>(rhs); + else if ((lhs.IsNumber() || lhs.IsEmpty()) && (rhs.IsNumber() || rhs.IsEmpty()) && !(lhs.IsEmpty() && rhs.IsEmpty())) + return static_cast<double>(lhs) <= static_cast<double>(rhs); + else if ((lhs.IsObjectType<DateTime>() || lhs.IsEmpty()) && (rhs.IsObjectType<DateTime>() || rhs.IsEmpty()) && !(lhs.IsEmpty() && rhs.IsEmpty())) + return Convert::ToDateTimeValue(lhs) <= Convert::ToDateTimeValue(rhs); + else + BOOST_THROW_EXCEPTION(std::invalid_argument("Operator <= cannot be applied to values of type '" + lhs.GetTypeName() + "' and '" + rhs.GetTypeName() + "'")); +} + +bool icinga::operator<=(const Value& lhs, double rhs) +{ + return lhs <= Value(rhs); +} + +bool icinga::operator<=(double lhs, const Value& rhs) +{ + return Value(lhs) <= rhs; +} + +bool icinga::operator<=(const Value& lhs, int rhs) +{ + return lhs <= Value(rhs); +} + +bool icinga::operator<=(int lhs, const Value& rhs) +{ + return Value(lhs) <= rhs; +} + +bool icinga::operator>=(const Value& lhs, const Value& rhs) +{ + if (lhs.IsString() && rhs.IsString()) + return static_cast<String>(lhs) >= static_cast<String>(rhs); + else if ((lhs.IsNumber() || lhs.IsEmpty()) && (rhs.IsNumber() || rhs.IsEmpty()) && !(lhs.IsEmpty() && rhs.IsEmpty())) + return static_cast<double>(lhs) >= static_cast<double>(rhs); + else if ((lhs.IsObjectType<DateTime>() || lhs.IsEmpty()) && (rhs.IsObjectType<DateTime>() || rhs.IsEmpty()) && !(lhs.IsEmpty() && rhs.IsEmpty())) + return Convert::ToDateTimeValue(lhs) >= Convert::ToDateTimeValue(rhs); + else + BOOST_THROW_EXCEPTION(std::invalid_argument("Operator >= cannot be applied to values of type '" + lhs.GetTypeName() + "' and '" + rhs.GetTypeName() + "'")); +} + +bool icinga::operator>=(const Value& lhs, double rhs) +{ + return lhs >= Value(rhs); +} + +bool icinga::operator>=(double lhs, const Value& rhs) +{ + return Value(lhs) >= rhs; +} + +bool icinga::operator>=(const Value& lhs, int rhs) +{ + return lhs >= Value(rhs); +} + +bool icinga::operator>=(int lhs, const Value& rhs) +{ + return Value(lhs) >= rhs; +} diff --git a/lib/base/value.cpp b/lib/base/value.cpp new file mode 100644 index 0000000..867c821 --- /dev/null +++ b/lib/base/value.cpp @@ -0,0 +1,264 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#include "base/value.hpp" +#include "base/array.hpp" +#include "base/dictionary.hpp" +#include "base/type.hpp" + +using namespace icinga; + +template class boost::variant<boost::blank, double, bool, String, Object::Ptr>; +template const double& Value::Get<double>() const; +template const bool& Value::Get<bool>() const; +template const String& Value::Get<String>() const; +template const Object::Ptr& Value::Get<Object::Ptr>() const; + +Value icinga::Empty; + +Value::Value(std::nullptr_t) +{ } + +Value::Value(int value) + : m_Value(double(value)) +{ } + +Value::Value(unsigned int value) + : m_Value(double(value)) +{ } + +Value::Value(long value) + : m_Value(double(value)) +{ } + +Value::Value(unsigned long value) + : m_Value(double(value)) +{ } + +Value::Value(long long value) + : m_Value(double(value)) +{ } + +Value::Value(unsigned long long value) + : m_Value(double(value)) +{ } + +Value::Value(double value) + : m_Value(value) +{ } + +Value::Value(bool value) + : m_Value(value) +{ } + +Value::Value(const String& value) + : m_Value(value) +{ } + +Value::Value(String&& value) + : m_Value(value) +{ } + +Value::Value(const char *value) + : m_Value(String(value)) +{ } + +Value::Value(const Value& other) + : m_Value(other.m_Value) +{ } + +Value::Value(Value&& other) +{ +#if BOOST_VERSION >= 105400 + m_Value = std::move(other.m_Value); +#else /* BOOST_VERSION */ + m_Value.swap(other.m_Value); +#endif /* BOOST_VERSION */ +} + +Value::Value(Object *value) + : Value(Object::Ptr(value)) +{ } + +Value::Value(const intrusive_ptr<Object>& value) +{ + if (value) + m_Value = value; +} + +Value& Value::operator=(const Value& other) +{ + m_Value = other.m_Value; + return *this; +} + +Value& Value::operator=(Value&& other) +{ +#if BOOST_VERSION >= 105400 + m_Value = std::move(other.m_Value); +#else /* BOOST_VERSION */ + m_Value.swap(other.m_Value); +#endif /* BOOST_VERSION */ + + return *this; +} + +/** + * Checks whether the variant is empty. + * + * @returns true if the variant is empty, false otherwise. + */ +bool Value::IsEmpty() const +{ + return (GetType() == ValueEmpty || (IsString() && boost::get<String>(m_Value).IsEmpty())); +} + +/** + * Checks whether the variant is scalar (i.e. not an object and not empty). + * + * @returns true if the variant is scalar, false otherwise. + */ +bool Value::IsScalar() const +{ + return !IsEmpty() && !IsObject(); +} + +/** +* Checks whether the variant is a number. +* +* @returns true if the variant is a number. +*/ +bool Value::IsNumber() const +{ + return (GetType() == ValueNumber); +} + +/** + * Checks whether the variant is a boolean. + * + * @returns true if the variant is a boolean. + */ +bool Value::IsBoolean() const +{ + return (GetType() == ValueBoolean); +} + +/** + * Checks whether the variant is a string. + * + * @returns true if the variant is a string. + */ +bool Value::IsString() const +{ + return (GetType() == ValueString); +} + +/** + * Checks whether the variant is a non-null object. + * + * @returns true if the variant is a non-null object, false otherwise. + */ +bool Value::IsObject() const +{ + return (GetType() == ValueObject); +} + +/** + * Returns the type of the value. + * + * @returns The type. + */ +ValueType Value::GetType() const +{ + return static_cast<ValueType>(m_Value.which()); +} + +void Value::Swap(Value& other) +{ + m_Value.swap(other.m_Value); +} + +bool Value::ToBool() const +{ + switch (GetType()) { + case ValueNumber: + return static_cast<bool>(boost::get<double>(m_Value)); + + case ValueBoolean: + return boost::get<bool>(m_Value); + + case ValueString: + return !boost::get<String>(m_Value).IsEmpty(); + + case ValueObject: + if (IsObjectType<Dictionary>()) { + Dictionary::Ptr dictionary = *this; + return dictionary->GetLength() > 0; + } else if (IsObjectType<Array>()) { + Array::Ptr array = *this; + return array->GetLength() > 0; + } else { + return true; + } + + case ValueEmpty: + return false; + + default: + BOOST_THROW_EXCEPTION(std::runtime_error("Invalid variant type.")); + } +} + +String Value::GetTypeName() const +{ + Type::Ptr t; + + switch (GetType()) { + case ValueEmpty: + return "Empty"; + case ValueNumber: + return "Number"; + case ValueBoolean: + return "Boolean"; + case ValueString: + return "String"; + case ValueObject: + t = boost::get<Object::Ptr>(m_Value)->GetReflectionType(); + if (!t) { + if (IsObjectType<Array>()) + return "Array"; + else if (IsObjectType<Dictionary>()) + return "Dictionary"; + else + return "Object"; + } else + return t->GetName(); + default: + return "Invalid"; + } +} + +Type::Ptr Value::GetReflectionType() const +{ + switch (GetType()) { + case ValueEmpty: + return Object::TypeInstance; + case ValueNumber: + return Type::GetByName("Number"); + case ValueBoolean: + return Type::GetByName("Boolean"); + case ValueString: + return Type::GetByName("String"); + case ValueObject: + return boost::get<Object::Ptr>(m_Value)->GetReflectionType(); + default: + return nullptr; + } +} + +Value Value::Clone() const +{ + if (IsObject()) + return static_cast<Object::Ptr>(*this)->Clone(); + else + return *this; +} diff --git a/lib/base/value.hpp b/lib/base/value.hpp new file mode 100644 index 0000000..86a3b11 --- /dev/null +++ b/lib/base/value.hpp @@ -0,0 +1,251 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#ifndef VALUE_H +#define VALUE_H + +#include "base/object.hpp" +#include "base/string.hpp" +#include <boost/variant/variant.hpp> +#include <boost/variant/get.hpp> +#include <boost/throw_exception.hpp> + +namespace icinga +{ + +typedef double Timestamp; + +/** + * The type of a Value. + * + * @ingroup base + */ +enum ValueType +{ + ValueEmpty = 0, + ValueNumber = 1, + ValueBoolean = 2, + ValueString = 3, + ValueObject = 4 +}; + +/** + * A type that can hold an arbitrary value. + * + * @ingroup base + */ +class Value +{ +public: + Value() = default; + Value(std::nullptr_t); + Value(int value); + Value(unsigned int value); + Value(long value); + Value(unsigned long value); + Value(long long value); + Value(unsigned long long value); + Value(double value); + Value(bool value); + Value(const String& value); + Value(String&& value); + Value(const char *value); + Value(const Value& other); + Value(Value&& other); + Value(Object *value); + Value(const intrusive_ptr<Object>& value); + + template<typename T> + Value(const intrusive_ptr<T>& value) + : Value(static_pointer_cast<Object>(value)) + { + static_assert(!std::is_same<T, Object>::value, "T must not be Object"); + } + + bool ToBool() const; + + operator double() const; + operator String() const; + + Value& operator=(const Value& other); + Value& operator=(Value&& other); + + bool operator==(bool rhs) const; + bool operator!=(bool rhs) const; + + bool operator==(int rhs) const; + bool operator!=(int rhs) const; + + bool operator==(double rhs) const; + bool operator!=(double rhs) const; + + bool operator==(const char *rhs) const; + bool operator!=(const char *rhs) const; + + bool operator==(const String& rhs) const; + bool operator!=(const String& rhs) const; + + bool operator==(const Value& rhs) const; + bool operator!=(const Value& rhs) const; + + template<typename T> + operator intrusive_ptr<T>() const + { + if (IsEmpty() && !IsString()) + return intrusive_ptr<T>(); + + if (!IsObject()) + BOOST_THROW_EXCEPTION(std::runtime_error("Cannot convert value of type '" + GetTypeName() + "' to an object.")); + + const auto& object = Get<Object::Ptr>(); + + ASSERT(object); + + intrusive_ptr<T> tobject = dynamic_pointer_cast<T>(object); + + if (!tobject) + BOOST_THROW_EXCEPTION(std::bad_cast()); + + return tobject; + } + + bool IsEmpty() const; + bool IsScalar() const; + bool IsNumber() const; + bool IsBoolean() const; + bool IsString() const; + bool IsObject() const; + + template<typename T> + bool IsObjectType() const + { + if (!IsObject()) + return false; + + return dynamic_cast<T *>(Get<Object::Ptr>().get()); + } + + ValueType GetType() const; + + void Swap(Value& other); + + String GetTypeName() const; + + Type::Ptr GetReflectionType() const; + + Value Clone() const; + + template<typename T> + const T& Get() const + { + return boost::get<T>(m_Value); + } + +private: + boost::variant<boost::blank, double, bool, String, Object::Ptr> m_Value; +}; + +extern template const double& Value::Get<double>() const; +extern template const bool& Value::Get<bool>() const; +extern template const String& Value::Get<String>() const; +extern template const Object::Ptr& Value::Get<Object::Ptr>() const; + +extern Value Empty; + +Value operator+(const Value& lhs, const char *rhs); +Value operator+(const char *lhs, const Value& rhs); + +Value operator+(const Value& lhs, const String& rhs); +Value operator+(const String& lhs, const Value& rhs); + +Value operator+(const Value& lhs, const Value& rhs); +Value operator+(const Value& lhs, double rhs); +Value operator+(double lhs, const Value& rhs); +Value operator+(const Value& lhs, int rhs); +Value operator+(int lhs, const Value& rhs); + +Value operator-(const Value& lhs, const Value& rhs); +Value operator-(const Value& lhs, double rhs); +Value operator-(double lhs, const Value& rhs); +Value operator-(const Value& lhs, int rhs); +Value operator-(int lhs, const Value& rhs); + +Value operator*(const Value& lhs, const Value& rhs); +Value operator*(const Value& lhs, double rhs); +Value operator*(double lhs, const Value& rhs); +Value operator*(const Value& lhs, int rhs); +Value operator*(int lhs, const Value& rhs); + +Value operator/(const Value& lhs, const Value& rhs); +Value operator/(const Value& lhs, double rhs); +Value operator/(double lhs, const Value& rhs); +Value operator/(const Value& lhs, int rhs); +Value operator/(int lhs, const Value& rhs); + +Value operator%(const Value& lhs, const Value& rhs); +Value operator%(const Value& lhs, double rhs); +Value operator%(double lhs, const Value& rhs); +Value operator%(const Value& lhs, int rhs); +Value operator%(int lhs, const Value& rhs); + +Value operator^(const Value& lhs, const Value& rhs); +Value operator^(const Value& lhs, double rhs); +Value operator^(double lhs, const Value& rhs); +Value operator^(const Value& lhs, int rhs); +Value operator^(int lhs, const Value& rhs); + +Value operator&(const Value& lhs, const Value& rhs); +Value operator&(const Value& lhs, double rhs); +Value operator&(double lhs, const Value& rhs); +Value operator&(const Value& lhs, int rhs); +Value operator&(int lhs, const Value& rhs); + +Value operator|(const Value& lhs, const Value& rhs); +Value operator|(const Value& lhs, double rhs); +Value operator|(double lhs, const Value& rhs); +Value operator|(const Value& lhs, int rhs); +Value operator|(int lhs, const Value& rhs); + +Value operator<<(const Value& lhs, const Value& rhs); +Value operator<<(const Value& lhs, double rhs); +Value operator<<(double lhs, const Value& rhs); +Value operator<<(const Value& lhs, int rhs); +Value operator<<(int lhs, const Value& rhs); + +Value operator>>(const Value& lhs, const Value& rhs); +Value operator>>(const Value& lhs, double rhs); +Value operator>>(double lhs, const Value& rhs); +Value operator>>(const Value& lhs, int rhs); +Value operator>>(int lhs, const Value& rhs); + +bool operator<(const Value& lhs, const Value& rhs); +bool operator<(const Value& lhs, double rhs); +bool operator<(double lhs, const Value& rhs); +bool operator<(const Value& lhs, int rhs); +bool operator<(int lhs, const Value& rhs); + +bool operator>(const Value& lhs, const Value& rhs); +bool operator>(const Value& lhs, double rhs); +bool operator>(double lhs, const Value& rhs); +bool operator>(const Value& lhs, int rhs); +bool operator>(int lhs, const Value& rhs); + +bool operator<=(const Value& lhs, const Value& rhs); +bool operator<=(const Value& lhs, double rhs); +bool operator<=(double lhs, const Value& rhs); +bool operator<=(const Value& lhs, int rhs); +bool operator<=(int lhs, const Value& rhs); + +bool operator>=(const Value& lhs, const Value& rhs); +bool operator>=(const Value& lhs, double rhs); +bool operator>=(double lhs, const Value& rhs); +bool operator>=(const Value& lhs, int rhs); +bool operator>=(int lhs, const Value& rhs); + +std::ostream& operator<<(std::ostream& stream, const Value& value); +std::istream& operator>>(std::istream& stream, Value& value); + +} + +extern template class boost::variant<boost::blank, double, bool, icinga::String, icinga::Object::Ptr>; + +#endif /* VALUE_H */ diff --git a/lib/base/win32.hpp b/lib/base/win32.hpp new file mode 100644 index 0000000..064c5d6 --- /dev/null +++ b/lib/base/win32.hpp @@ -0,0 +1,35 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#ifndef WIN32_H +#define WIN32_H + +#define WIN32_LEAN_AND_MEAN +#ifndef _WIN32_WINNT +#define _WIN32_WINNT _WIN32_WINNT_VISTA +#endif /* _WIN32_WINNT */ +#define NOMINMAX +#include <winsock2.h> +#include <windows.h> +#include <ws2tcpip.h> +#include <imagehlp.h> +#include <shlwapi.h> + +#include <direct.h> + +#ifdef __MINGW32__ +# ifndef IPV6_V6ONLY +# define IPV6_V6ONLY 27 +# endif /* IPV6_V6ONLY */ +#endif /* __MINGW32__ */ + +typedef int socklen_t; +typedef SSIZE_T ssize_t; + +#define MAXPATHLEN MAX_PATH + +#ifdef _MSC_VER +typedef DWORD pid_t; +#define strcasecmp stricmp +#endif /* _MSC_VER */ + +#endif /* WIN32_H */ diff --git a/lib/base/windowseventloglogger-provider.mc b/lib/base/windowseventloglogger-provider.mc new file mode 100644 index 0000000..09e65ba --- /dev/null +++ b/lib/base/windowseventloglogger-provider.mc @@ -0,0 +1,5 @@ +MessageId=0x1 +SymbolicName=MSG_PLAIN_LOG_ENTRY +Language=English +%1 +. diff --git a/lib/base/windowseventloglogger.cpp b/lib/base/windowseventloglogger.cpp new file mode 100644 index 0000000..cc28358 --- /dev/null +++ b/lib/base/windowseventloglogger.cpp @@ -0,0 +1,83 @@ +/* Icinga 2 | (c) 2021 Icinga GmbH | GPLv2+ */ + +#ifdef _WIN32 +#include "base/windowseventloglogger.hpp" +#include "base/windowseventloglogger-ti.cpp" +#include "base/windowseventloglogger-provider.h" +#include "base/configtype.hpp" +#include "base/statsfunction.hpp" +#include <windows.h> + +using namespace icinga; + +REGISTER_TYPE(WindowsEventLogLogger); + +REGISTER_STATSFUNCTION(WindowsEventLogLogger, &WindowsEventLogLogger::StatsFunc); + +INITIALIZE_ONCE(&WindowsEventLogLogger::StaticInitialize); + +static HANDLE l_EventLog = nullptr; + +void WindowsEventLogLogger::StaticInitialize() +{ + l_EventLog = RegisterEventSourceA(nullptr, "Icinga 2"); +} + +void WindowsEventLogLogger::StatsFunc(const Dictionary::Ptr& status, const Array::Ptr&) +{ + DictionaryData nodes; + + for (const WindowsEventLogLogger::Ptr& logger : ConfigType::GetObjectsByType<WindowsEventLogLogger>()) { + nodes.emplace_back(logger->GetName(), 1); + } + + status->Set("windowseventloglogger", new Dictionary(std::move(nodes))); +} + +/** + * Processes a log entry and outputs it to the Windows Event Log. + * + * This function implements the interface expected by the Logger base class and passes + * the log entry to WindowsEventLogLogger::WriteToWindowsEventLog(). + * + * @param entry The log entry. + */ +void WindowsEventLogLogger::ProcessLogEntry(const LogEntry& entry) { + WindowsEventLogLogger::WriteToWindowsEventLog(entry); +} + +/** + * Writes a LogEntry object to the Windows Event Log. + * + * @param entry The log entry. + */ +void WindowsEventLogLogger::WriteToWindowsEventLog(const LogEntry& entry) +{ + if (l_EventLog != nullptr) { + std::string message = Logger::SeverityToString(entry.Severity) + "/" + entry.Facility + ": " + entry.Message; + std::array<const char *, 1> strings{ + message.c_str() + }; + + WORD eventType; + switch (entry.Severity) { + case LogCritical: + eventType = EVENTLOG_ERROR_TYPE; + break; + case LogWarning: + eventType = EVENTLOG_WARNING_TYPE; + break; + default: + eventType = EVENTLOG_INFORMATION_TYPE; + } + + ReportEventA(l_EventLog, eventType, 0, MSG_PLAIN_LOG_ENTRY, NULL, strings.size(), 0, strings.data(), NULL); + } +} + +void WindowsEventLogLogger::Flush() +{ + /* Nothing to do here. */ +} + +#endif /* _WIN32 */ diff --git a/lib/base/windowseventloglogger.hpp b/lib/base/windowseventloglogger.hpp new file mode 100644 index 0000000..cefc245 --- /dev/null +++ b/lib/base/windowseventloglogger.hpp @@ -0,0 +1,37 @@ +/* Icinga 2 | (c) 2021 Icinga GmbH | GPLv2+ */ + +#ifndef WINDOWSEVENTLOGLOGGER_H +#define WINDOWSEVENTLOGLOGGER_H + +#ifdef _WIN32 +#include "base/i2-base.hpp" +#include "base/windowseventloglogger-ti.hpp" + +namespace icinga +{ + +/** + * A logger that logs to the Windows Event Log. + * + * @ingroup base + */ +class WindowsEventLogLogger final : public ObjectImpl<WindowsEventLogLogger> +{ +public: + DECLARE_OBJECT(WindowsEventLogLogger); + DECLARE_OBJECTNAME(WindowsEventLogLogger); + + static void StaticInitialize(); + static void StatsFunc(const Dictionary::Ptr& status, const Array::Ptr& perfdata); + + static void WriteToWindowsEventLog(const LogEntry& entry); + +protected: + void ProcessLogEntry(const LogEntry& entry) override; + void Flush() override; +}; + +} +#endif /* _WIN32 */ + +#endif /* WINDOWSEVENTLOGLOGGER_H */ diff --git a/lib/base/windowseventloglogger.ti b/lib/base/windowseventloglogger.ti new file mode 100644 index 0000000..edf65fc --- /dev/null +++ b/lib/base/windowseventloglogger.ti @@ -0,0 +1,15 @@ +/* Icinga 2 | (c) 2021 Icinga GmbH | GPLv2+ */ + +#include "base/logger.hpp" + +library base; + +namespace icinga +{ + +class WindowsEventLogLogger : Logger +{ + activation_priority -100; +}; + +} diff --git a/lib/base/workqueue.cpp b/lib/base/workqueue.cpp new file mode 100644 index 0000000..0b1214b --- /dev/null +++ b/lib/base/workqueue.cpp @@ -0,0 +1,318 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#include "base/workqueue.hpp" +#include "base/utility.hpp" +#include "base/logger.hpp" +#include "base/convert.hpp" +#include "base/application.hpp" +#include "base/exception.hpp" +#include <boost/thread/tss.hpp> +#include <math.h> + +using namespace icinga; + +std::atomic<int> WorkQueue::m_NextID(1); +boost::thread_specific_ptr<WorkQueue *> l_ThreadWorkQueue; + +WorkQueue::WorkQueue(size_t maxItems, int threadCount, LogSeverity statsLogLevel) + : m_ID(m_NextID++), m_ThreadCount(threadCount), m_MaxItems(maxItems), + m_TaskStats(15 * 60), m_StatsLogLevel(statsLogLevel) +{ + /* Initialize logger. */ + m_StatusTimerTimeout = Utility::GetTime(); + + m_StatusTimer = Timer::Create(); + m_StatusTimer->SetInterval(10); + m_StatusTimer->OnTimerExpired.connect([this](const Timer * const&) { StatusTimerHandler(); }); + m_StatusTimer->Start(); +} + +WorkQueue::~WorkQueue() +{ + m_StatusTimer->Stop(true); + + Join(true); +} + +void WorkQueue::SetName(const String& name) +{ + m_Name = name; +} + +String WorkQueue::GetName() const +{ + return m_Name; +} + +std::unique_lock<std::mutex> WorkQueue::AcquireLock() +{ + return std::unique_lock<std::mutex>(m_Mutex); +} + +/** + * Enqueues a task. Tasks are guaranteed to be executed in the order + * they were enqueued in except if there is more than one worker thread. + */ +void WorkQueue::EnqueueUnlocked(std::unique_lock<std::mutex>& lock, std::function<void ()>&& function, WorkQueuePriority priority) +{ + if (!m_Spawned) { + Log(LogNotice, "WorkQueue") + << "Spawning WorkQueue threads for '" << m_Name << "'"; + + for (int i = 0; i < m_ThreadCount; i++) { + m_Threads.create_thread([this]() { WorkerThreadProc(); }); + } + + m_Spawned = true; + } + + bool wq_thread = IsWorkerThread(); + + if (!wq_thread) { + while (m_Tasks.size() >= m_MaxItems && m_MaxItems != 0) + m_CVFull.wait(lock); + } + + m_Tasks.emplace(std::move(function), priority, ++m_NextTaskID); + + m_CVEmpty.notify_one(); +} + +/** + * Enqueues a task. Tasks are guaranteed to be executed in the order + * they were enqueued in except if there is more than one worker thread or when + * allowInterleaved is true in which case the new task might be run + * immediately if it's being enqueued from within the WorkQueue thread. + */ +void WorkQueue::Enqueue(std::function<void ()>&& function, WorkQueuePriority priority, + bool allowInterleaved) +{ + bool wq_thread = IsWorkerThread(); + + if (wq_thread && allowInterleaved) { + function(); + + return; + } + + auto lock = AcquireLock(); + EnqueueUnlocked(lock, std::move(function), priority); +} + +/** + * Waits until all currently enqueued tasks have completed. This only works reliably + * when no other thread is enqueuing new tasks when this method is called. + * + * @param stop Whether to stop the worker threads + */ +void WorkQueue::Join(bool stop) +{ + std::unique_lock<std::mutex> lock(m_Mutex); + + while (m_Processing || !m_Tasks.empty()) + m_CVStarved.wait(lock); + + if (stop) { + m_Stopped = true; + m_CVEmpty.notify_all(); + lock.unlock(); + + m_Threads.join_all(); + m_Spawned = false; + + Log(LogNotice, "WorkQueue") + << "Stopped WorkQueue threads for '" << m_Name << "'"; + } +} + +/** + * Checks whether the calling thread is one of the worker threads + * for this work queue. + * + * @returns true if called from one of the worker threads, false otherwise + */ +bool WorkQueue::IsWorkerThread() const +{ + WorkQueue **pwq = l_ThreadWorkQueue.get(); + + if (!pwq) + return false; + + return *pwq == this; +} + +void WorkQueue::SetExceptionCallback(const ExceptionCallback& callback) +{ + m_ExceptionCallback = callback; +} + +/** + * Checks whether any exceptions have occurred while executing tasks for this + * work queue. When a custom exception callback is set this method will always + * return false. + */ +bool WorkQueue::HasExceptions() const +{ + std::unique_lock<std::mutex> lock(m_Mutex); + + return !m_Exceptions.empty(); +} + +/** + * Returns all exceptions which have occurred for tasks in this work queue. When a + * custom exception callback is set this method will always return an empty list. + */ +std::vector<boost::exception_ptr> WorkQueue::GetExceptions() const +{ + std::unique_lock<std::mutex> lock(m_Mutex); + + return m_Exceptions; +} + +void WorkQueue::ReportExceptions(const String& facility, bool verbose) const +{ + std::vector<boost::exception_ptr> exceptions = GetExceptions(); + + for (const auto& eptr : exceptions) { + Log(LogCritical, facility) + << DiagnosticInformation(eptr, verbose); + } + + Log(LogCritical, facility) + << exceptions.size() << " error" << (exceptions.size() != 1 ? "s" : ""); +} + +size_t WorkQueue::GetLength() const +{ + std::unique_lock<std::mutex> lock(m_Mutex); + + return m_Tasks.size(); +} + +void WorkQueue::StatusTimerHandler() +{ + std::unique_lock<std::mutex> lock(m_Mutex); + + ASSERT(!m_Name.IsEmpty()); + + size_t pending = m_Tasks.size(); + + double now = Utility::GetTime(); + double gradient = (pending - m_PendingTasks) / (now - m_PendingTasksTimestamp); + double timeToZero = pending / gradient; + + String timeInfo; + + if (pending > GetTaskCount(5)) { + timeInfo = " empty in "; + if (timeToZero < 0 || std::isinf(timeToZero)) + timeInfo += "infinite time, your task handler isn't able to keep up"; + else + timeInfo += Utility::FormatDuration(timeToZero); + } + + m_PendingTasks = pending; + m_PendingTasksTimestamp = now; + + /* Log if there are pending items, or 5 minute timeout is reached. */ + if (pending > 0 || m_StatusTimerTimeout < now) { + Log(m_StatsLogLevel, "WorkQueue") + << "#" << m_ID << " (" << m_Name << ") " + << "items: " << pending << ", " + << "rate: " << std::setw(2) << GetTaskCount(60) / 60.0 << "/s " + << "(" << GetTaskCount(60) << "/min " << GetTaskCount(60 * 5) << "/5min " << GetTaskCount(60 * 15) << "/15min);" + << timeInfo; + } + + /* Reschedule next log entry in 5 minutes. */ + if (m_StatusTimerTimeout < now) { + m_StatusTimerTimeout = now + 60 * 5; + } +} + +void WorkQueue::RunTaskFunction(const TaskFunction& func) +{ + try { + func(); + } catch (const std::exception&) { + boost::exception_ptr eptr = boost::current_exception(); + + { + std::unique_lock<std::mutex> mutex(m_Mutex); + + if (!m_ExceptionCallback) + m_Exceptions.push_back(eptr); + } + + if (m_ExceptionCallback) + m_ExceptionCallback(eptr); + } +} + +void WorkQueue::WorkerThreadProc() +{ + std::ostringstream idbuf; + idbuf << "WQ #" << m_ID; + Utility::SetThreadName(idbuf.str()); + + l_ThreadWorkQueue.reset(new WorkQueue *(this)); + + std::unique_lock<std::mutex> lock(m_Mutex); + + for (;;) { + while (m_Tasks.empty() && !m_Stopped) + m_CVEmpty.wait(lock); + + if (m_Stopped) + break; + + if (m_Tasks.size() >= m_MaxItems && m_MaxItems != 0) + m_CVFull.notify_all(); + + Task task = m_Tasks.top(); + m_Tasks.pop(); + + m_Processing++; + + lock.unlock(); + + RunTaskFunction(task.Function); + + /* clear the task so whatever other resources it holds are released _before_ we re-acquire the mutex */ + task = Task(); + + IncreaseTaskCount(); + + lock.lock(); + + m_Processing--; + + if (m_Tasks.empty()) + m_CVStarved.notify_all(); + } +} + +void WorkQueue::IncreaseTaskCount() +{ + m_TaskStats.InsertValue(Utility::GetTime(), 1); +} + +size_t WorkQueue::GetTaskCount(RingBuffer::SizeType span) +{ + return m_TaskStats.UpdateAndGetValues(Utility::GetTime(), span); +} + +bool icinga::operator<(const Task& a, const Task& b) +{ + if (a.Priority < b.Priority) + return true; + + if (a.Priority == b.Priority) { + if (a.ID > b.ID) + return true; + else + return false; + } + + return false; +} diff --git a/lib/base/workqueue.hpp b/lib/base/workqueue.hpp new file mode 100644 index 0000000..9c8a6b8 --- /dev/null +++ b/lib/base/workqueue.hpp @@ -0,0 +1,154 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#ifndef WORKQUEUE_H +#define WORKQUEUE_H + +#include "base/i2-base.hpp" +#include "base/timer.hpp" +#include "base/ringbuffer.hpp" +#include "base/logger.hpp" +#include <boost/thread/thread.hpp> +#include <boost/exception_ptr.hpp> +#include <condition_variable> +#include <mutex> +#include <queue> +#include <deque> +#include <atomic> + +namespace icinga +{ + +enum WorkQueuePriority +{ + PriorityLow = 0, + PriorityNormal = 1, + PriorityHigh = 2, + PriorityImmediate = 4 +}; + +using TaskFunction = std::function<void ()>; + +struct Task +{ + Task() = default; + + Task(TaskFunction function, WorkQueuePriority priority, int id) + : Function(std::move(function)), Priority(priority), ID(id) + { } + + TaskFunction Function; + WorkQueuePriority Priority{PriorityNormal}; + int ID{-1}; +}; + +bool operator<(const Task& a, const Task& b); + +/** + * A workqueue. + * + * @ingroup base + */ +class WorkQueue +{ +public: + typedef std::function<void (boost::exception_ptr)> ExceptionCallback; + + WorkQueue(size_t maxItems = 0, int threadCount = 1, LogSeverity statsLogLevel = LogInformation); + ~WorkQueue(); + + void SetName(const String& name); + String GetName() const; + + std::unique_lock<std::mutex> AcquireLock(); + void EnqueueUnlocked(std::unique_lock<std::mutex>& lock, TaskFunction&& function, WorkQueuePriority priority = PriorityNormal); + void Enqueue(TaskFunction&& function, WorkQueuePriority priority = PriorityNormal, + bool allowInterleaved = false); + void Join(bool stop = false); + + template<typename VectorType, typename FuncType> + void ParallelFor(const VectorType& items, const FuncType& func) + { + ParallelFor(items, true, func); + } + + template<typename VectorType, typename FuncType> + void ParallelFor(const VectorType& items, bool preChunk, const FuncType& func) + { + using SizeType = decltype(items.size()); + + SizeType totalCount = items.size(); + SizeType chunks = preChunk ? m_ThreadCount : totalCount; + + auto lock = AcquireLock(); + + SizeType offset = 0; + + for (SizeType i = 0; i < chunks; i++) { + SizeType count = totalCount / chunks; + if (i < totalCount % chunks) + count++; + + EnqueueUnlocked(lock, [&items, func, offset, count, this]() { + for (SizeType j = offset; j < offset + count; j++) { + RunTaskFunction([&func, &items, j]() { + func(items[j]); + }); + } + }); + + offset += count; + } + + ASSERT(offset == items.size()); + } + + bool IsWorkerThread() const; + + size_t GetLength() const; + size_t GetTaskCount(RingBuffer::SizeType span); + + void SetExceptionCallback(const ExceptionCallback& callback); + + bool HasExceptions() const; + std::vector<boost::exception_ptr> GetExceptions() const; + void ReportExceptions(const String& facility, bool verbose = false) const; + +protected: + void IncreaseTaskCount(); + +private: + int m_ID; + String m_Name; + static std::atomic<int> m_NextID; + int m_ThreadCount; + bool m_Spawned{false}; + + mutable std::mutex m_Mutex; + std::condition_variable m_CVEmpty; + std::condition_variable m_CVFull; + std::condition_variable m_CVStarved; + boost::thread_group m_Threads; + size_t m_MaxItems; + bool m_Stopped{false}; + int m_Processing{0}; + std::priority_queue<Task, std::deque<Task> > m_Tasks; + int m_NextTaskID{0}; + ExceptionCallback m_ExceptionCallback; + std::vector<boost::exception_ptr> m_Exceptions; + Timer::Ptr m_StatusTimer; + double m_StatusTimerTimeout; + LogSeverity m_StatsLogLevel; + + RingBuffer m_TaskStats; + size_t m_PendingTasks{0}; + double m_PendingTasksTimestamp{0}; + + void WorkerThreadProc(); + void StatusTimerHandler(); + + void RunTaskFunction(const TaskFunction& func); +}; + +} + +#endif /* WORKQUEUE_H */ |