summaryrefslogtreecommitdiffstats
path: root/lib/base
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-13 11:32:39 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-13 11:32:39 +0000
commit56ae875861ab260b80a030f50c4aff9f9dc8fff0 (patch)
tree531412110fc901a5918c7f7442202804a83cada9 /lib/base
parentInitial commit. (diff)
downloadicinga2-56ae875861ab260b80a030f50c4aff9f9dc8fff0.tar.xz
icinga2-56ae875861ab260b80a030f50c4aff9f9dc8fff0.zip
Adding upstream version 2.14.2.upstream/2.14.2upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--lib/base/CMakeLists.txt160
-rw-r--r--lib/base/application-environment.cpp17
-rw-r--r--lib/base/application-version.cpp17
-rw-r--r--lib/base/application.cpp1238
-rw-r--r--lib/base/application.hpp170
-rw-r--r--lib/base/application.ti14
-rw-r--r--lib/base/array-script.cpp260
-rw-r--r--lib/base/array.cpp380
-rw-r--r--lib/base/array.hpp117
-rw-r--r--lib/base/atomic-file.cpp123
-rw-r--r--lib/base/atomic-file.hpp41
-rw-r--r--lib/base/atomic.hpp91
-rw-r--r--lib/base/base64.cpp53
-rw-r--r--lib/base/base64.hpp25
-rw-r--r--lib/base/boolean-script.cpp26
-rw-r--r--lib/base/boolean.cpp9
-rw-r--r--lib/base/boolean.hpp27
-rw-r--r--lib/base/bulker.hpp119
-rw-r--r--lib/base/configobject-script.cpp36
-rw-r--r--lib/base/configobject.cpp701
-rw-r--r--lib/base/configobject.hpp101
-rw-r--r--lib/base/configobject.ti94
-rw-r--r--lib/base/configtype.cpp76
-rw-r--r--lib/base/configtype.hpp64
-rw-r--r--lib/base/configuration.cpp379
-rw-r--r--lib/base/configuration.hpp157
-rw-r--r--lib/base/configuration.ti164
-rw-r--r--lib/base/configwriter.cpp260
-rw-r--r--lib/base/configwriter.hpp67
-rw-r--r--lib/base/console.cpp203
-rw-r--r--lib/base/console.hpp91
-rw-r--r--lib/base/context.cpp64
-rw-r--r--lib/base/context.hpp54
-rw-r--r--lib/base/convert.cpp46
-rw-r--r--lib/base/convert.hpp84
-rw-r--r--lib/base/datetime-script.cpp28
-rw-r--r--lib/base/datetime.cpp58
-rw-r--r--lib/base/datetime.hpp40
-rw-r--r--lib/base/datetime.ti15
-rw-r--r--lib/base/debug.hpp49
-rw-r--r--lib/base/debuginfo.cpp98
-rw-r--r--lib/base/debuginfo.hpp36
-rw-r--r--lib/base/defer.hpp54
-rw-r--r--lib/base/dependencygraph.cpp50
-rw-r--r--lib/base/dependencygraph.hpp34
-rw-r--r--lib/base/dictionary-script.cpp119
-rw-r--r--lib/base/dictionary.cpp317
-rw-r--r--lib/base/dictionary.hpp91
-rw-r--r--lib/base/exception.cpp507
-rw-r--r--lib/base/exception.hpp166
-rw-r--r--lib/base/fifo.cpp124
-rw-r--r--lib/base/fifo.hpp48
-rw-r--r--lib/base/filelogger.cpp59
-rw-r--r--lib/base/filelogger.hpp33
-rw-r--r--lib/base/filelogger.ti17
-rw-r--r--lib/base/function-script.cpp50
-rw-r--r--lib/base/function.cpp37
-rw-r--r--lib/base/function.hpp89
-rw-r--r--lib/base/function.ti18
-rw-r--r--lib/base/functionwrapper.hpp149
-rw-r--r--lib/base/i2-base.hpp79
-rw-r--r--lib/base/initialize.cpp13
-rw-r--r--lib/base/initialize.hpp49
-rw-r--r--lib/base/io-engine.cpp155
-rw-r--r--lib/base/io-engine.hpp216
-rw-r--r--lib/base/journaldlogger.cpp87
-rw-r--r--lib/base/journaldlogger.hpp44
-rw-r--r--lib/base/journaldlogger.ti21
-rw-r--r--lib/base/json-script.cpp28
-rw-r--r--lib/base/json.cpp525
-rw-r--r--lib/base/json.hpp19
-rw-r--r--lib/base/lazy-init.hpp72
-rw-r--r--lib/base/library.cpp68
-rw-r--r--lib/base/library.hpp41
-rw-r--r--lib/base/loader.cpp38
-rw-r--r--lib/base/loader.hpp61
-rw-r--r--lib/base/logger.cpp326
-rw-r--r--lib/base/logger.hpp149
-rw-r--r--lib/base/logger.ti17
-rw-r--r--lib/base/math-script.cpp184
-rw-r--r--lib/base/namespace-script.cpp84
-rw-r--r--lib/base/namespace.cpp189
-rw-r--r--lib/base/namespace.hpp105
-rw-r--r--lib/base/netstring.cpp334
-rw-r--r--lib/base/netstring.hpp43
-rw-r--r--lib/base/networkstream.cpp81
-rw-r--r--lib/base/networkstream.hpp39
-rw-r--r--lib/base/number-script.cpp25
-rw-r--r--lib/base/number.cpp9
-rw-r--r--lib/base/number.hpp27
-rw-r--r--lib/base/object-packer.cpp246
-rw-r--r--lib/base/object-packer.hpp18
-rw-r--r--lib/base/object-script.cpp45
-rw-r--r--lib/base/object.cpp275
-rw-r--r--lib/base/object.hpp225
-rw-r--r--lib/base/objectlock.cpp55
-rw-r--r--lib/base/objectlock.hpp35
-rw-r--r--lib/base/objecttype.cpp57
-rw-r--r--lib/base/objecttype.hpp29
-rw-r--r--lib/base/perfdatavalue.cpp395
-rw-r--r--lib/base/perfdatavalue.hpp38
-rw-r--r--lib/base/perfdatavalue.ti20
-rw-r--r--lib/base/primitivetype.cpp64
-rw-r--r--lib/base/primitivetype.hpp62
-rw-r--r--lib/base/process.cpp1207
-rw-r--r--lib/base/process.hpp117
-rw-r--r--lib/base/reference-script.cpp35
-rw-r--r--lib/base/reference.cpp38
-rw-r--r--lib/base/reference.hpp40
-rw-r--r--lib/base/registry.hpp121
-rw-r--r--lib/base/ringbuffer.cpp91
-rw-r--r--lib/base/ringbuffer.hpp45
-rw-r--r--lib/base/scriptframe.cpp130
-rw-r--r--lib/base/scriptframe.hpp42
-rw-r--r--lib/base/scriptglobal.cpp110
-rw-r--r--lib/base/scriptglobal.hpp35
-rw-r--r--lib/base/scriptutils.cpp570
-rw-r--r--lib/base/scriptutils.hpp54
-rw-r--r--lib/base/serializer.cpp331
-rw-r--r--lib/base/serializer.hpp34
-rw-r--r--lib/base/shared-memory.hpp45
-rw-r--r--lib/base/shared-object.hpp73
-rw-r--r--lib/base/shared.hpp101
-rw-r--r--lib/base/singleton.hpp29
-rw-r--r--lib/base/socket.cpp430
-rw-r--r--lib/base/socket.hpp66
-rw-r--r--lib/base/stacktrace.cpp43
-rw-r--r--lib/base/stacktrace.hpp31
-rw-r--r--lib/base/statsfunction.hpp17
-rw-r--r--lib/base/stdiostream.cpp57
-rw-r--r--lib/base/stdiostream.hpp36
-rw-r--r--lib/base/stream.cpp149
-rw-r--r--lib/base/stream.hpp133
-rw-r--r--lib/base/streamlogger.cpp119
-rw-r--r--lib/base/streamlogger.hpp47
-rw-r--r--lib/base/streamlogger.ti14
-rw-r--r--lib/base/string-script.cpp138
-rw-r--r--lib/base/string.cpp468
-rw-r--r--lib/base/string.hpp208
-rw-r--r--lib/base/sysloglogger.cpp144
-rw-r--r--lib/base/sysloglogger.hpp56
-rw-r--r--lib/base/sysloglogger.ti19
-rw-r--r--lib/base/tcpsocket.cpp211
-rw-r--r--lib/base/tcpsocket.hpp102
-rw-r--r--lib/base/threadpool.cpp51
-rw-r--r--lib/base/threadpool.hpp101
-rw-r--r--lib/base/timer.cpp354
-rw-r--r--lib/base/timer.hpp65
-rw-r--r--lib/base/tlsstream.cpp71
-rw-r--r--lib/base/tlsstream.hpp129
-rw-r--r--lib/base/tlsutility.cpp1086
-rw-r--r--lib/base/tlsutility.hpp94
-rw-r--r--lib/base/type.cpp217
-rw-r--r--lib/base/type.hpp148
-rw-r--r--lib/base/typetype-script.cpp31
-rw-r--r--lib/base/unix.hpp49
-rw-r--r--lib/base/unixsocket.cpp53
-rw-r--r--lib/base/unixsocket.hpp32
-rw-r--r--lib/base/utility.cpp1975
-rw-r--r--lib/base/utility.hpp200
-rw-r--r--lib/base/value-operators.cpp719
-rw-r--r--lib/base/value.cpp264
-rw-r--r--lib/base/value.hpp251
-rw-r--r--lib/base/win32.hpp35
-rw-r--r--lib/base/windowseventloglogger-provider.mc5
-rw-r--r--lib/base/windowseventloglogger.cpp83
-rw-r--r--lib/base/windowseventloglogger.hpp37
-rw-r--r--lib/base/windowseventloglogger.ti15
-rw-r--r--lib/base/workqueue.cpp318
-rw-r--r--lib/base/workqueue.hpp154
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, &current)) {
+ 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 */