diff --git a/lib/base/application.cpp b/lib/base/application.cpp index e450b330006..3b946b3973c 100644 --- a/lib/base/application.cpp +++ b/lib/base/application.cpp @@ -300,6 +300,137 @@ void Application::SetArgV(char **argv) m_ArgV = argv; } +#ifndef _WIN32 +/** + * Checks if one of the given signals is pending. + * + * This encapsulates most of the nastiness of POSIX and un-POSIX (i.e. MacOS and OpenBSD) + * signal handling so the rest of the code can remain sane. + * + * However on OpenBSD and MacOS the returned siginfo_t structure does only contain one + * valid field, which is .si_signo. Especially when checking against the pid using .si_pid + * it has to be taken into account that on these operating systems it will always be zero. + * + * If wait is given as false or a zero duration, the function will return immediately. + * If wait is given as true, the function will wait for the signal until it is received. + * + * @param signal The signals to check + * @param wait Whether the function should wait for a signal and if yes, how long + * @return A struct containing the signal id and pid of the sender + */ +std::optional Application::GetPendingSignal(std::initializer_list signals, + std::variant wait) +{ + sigset_t sigSet; + sigemptyset(&sigSet); + for(auto signal : signals){ + sigaddset(&sigSet, signal); + } + + int ret = 0; + siginfo_t info{}; + + if (wait == decltype(wait)(0us) || wait == decltype(wait)(false)) { +#if defined(__APPLE__) || defined(__OpenBSD__) + sigset_t pendingSigs; + sigpending(&pendingSigs); + ret = -1; + for(auto signal : signals){ + if (sigismember(&pendingSigs, signal)) { + ret = sigwait(&sigSet, &info.si_signo); + VERIFY(ret != EINVAL); + break; + }; + } +#else + timespec ts{}; + ret = sigtimedwait(&sigSet, &info, &ts); + VERIFY(ret != -1 || errno != EINVAL); +#endif + } else { + if (wait != decltype(wait)(true)) { + itimerval tv{}; + tv.it_value.tv_sec = std::get(wait).count() / 1000000; + tv.it_value.tv_usec = std::get(wait).count() % 1000000; + ret = setitimer(ITIMER_REAL, &tv, nullptr); + VERIFY(ret != -1); + sigaddset(&sigSet, SIGALRM); + } + +#if defined(__APPLE__) || defined(__OpenBSD__) + ret = sigwait(&sigSet, &info.si_signo); + VERIFY(ret != EINVAL); +#else + do{ + ret = sigwaitinfo(&sigSet, &info); + VERIFY(ret != -1 || errno != EINVAL); + // sigwaitinfo can be interrupted by other signal handlers (see signal(7)) + } while (ret == -1 && errno == EINTR); +#endif + } + + if (ret == -1 || info.si_signo == SIGALRM) { + return std::nullopt; + } + + return info; +} + +/** + * Checks if the given signal is pending. + * + * @param signal The signal id of the signal to check + * @param wait Whether the function should wait for a signal and if yes, how long + * @return A std::optional containing a siginfo_t if one of the signals was received + */ +std::optional Application::GetPendingSignal(int signal, + std::variant wait) +{ + return Application::GetPendingSignal({signal}, wait); +} + +/** + * Seamless worker's signal handlers + */ +void Application::WorkerSignalHandler(std::chrono::microseconds timeout) +{ + auto sig = GetPendingSignal({SIGUSR1, SIGUSR2, SIGTERM, SIGINT}, timeout); + if (!sig){ + return; + } + + switch(sig->si_signo){ + case SIGUSR1: + if (sig->si_pid == 0 || sig->si_pid == getppid()) { + Log(LogInformation, "Application") + << "Received USR1 signal, reopening application logs."; + + RequestReopenLogs(); + } else { + Log(LogWarning, "Application") + << "Received USR1 from unknown pid: " << sig->si_pid; + } + break; + case SIGUSR2: + Log(LogWarning, "Application") + << "Received superfluous USR2"; + break; + case SIGINT: + case SIGTERM: + if (sig->si_pid == 0 || sig->si_pid == getppid()) { + // The umbrella process requested our termination + RequestShutdown(); + } else { + Log(LogCritical, "Application") + << "Received " << strsignal(sig->si_signo) << " from unknown pid: " << sig->si_pid; + } + break; + default: + VERIFY(!"Error: Unhandled Signal"); + } +} +#endif /* _WIN32 */ + /** * Processes events for registered sockets and timers and calls whatever * handlers have been set up for these events. @@ -325,9 +456,6 @@ void Application::RunEventLoop() (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; @@ -337,6 +465,7 @@ void Application::RunEventLoop() double now = Utility::GetTime(); double timeDiff = lastLoop - now; + // Watches for changes to the system time. Adjusts timers if necessary. if (std::fabs(timeDiff) > 15) { /* We made a significant jump in time. */ Log(LogInformation, "Application") @@ -349,6 +478,11 @@ void Application::RunEventLoop() lastLoop = now; } +#ifndef _WIN32 + WorkerSignalHandler(2500ms); +#else + Utility::Sleep(2.5); +#endif /* _WIN32 */ } Log(LogInformation, "Application", "Shutting down..."); @@ -706,20 +840,6 @@ void Application::AttachDebugger(const String& filename, bool interactive) #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. * @@ -995,13 +1115,7 @@ void Application::InstallExceptionHandlers() */ 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 */ +#ifdef _WIN32 SetConsoleCtrlHandler(&Application::CtrlHandler, TRUE); #endif /* _WIN32 */ diff --git a/lib/base/application.hpp b/lib/base/application.hpp index f45c8bdd74a..12e739ad151 100644 --- a/lib/base/application.hpp +++ b/lib/base/application.hpp @@ -12,10 +12,15 @@ #include #include #include +#include +#include +#include namespace icinga { +using namespace std::literals::chrono_literals; + class ThreadPool; /** @@ -63,6 +68,11 @@ class Application : public ObjectImpl { #ifndef _WIN32 static void SetUmbrellaProcess(pid_t pid); + + static std::optional GetPendingSignal(std::initializer_list signals, + std::variant wait = 0us); + static std::optional GetPendingSignal(int signal, + std::variant wait = 0us); #endif /* _WIN32 */ static bool IsShuttingDown(); @@ -157,8 +167,8 @@ class Application : public ObjectImpl { static void DisplayBugMessage(std::ostream& os); static void SigAbrtHandler(int signum); - static void SigUsr1Handler(int signum); static void ExceptionHandler(); + static void WorkerSignalHandler(std::chrono::microseconds timeout); static String GetCrashReportFilename(); diff --git a/lib/cli/daemoncommand.cpp b/lib/cli/daemoncommand.cpp index 3a9ce8c0a74..babc1121de4 100644 --- a/lib/cli/daemoncommand.cpp +++ b/lib/cli/daemoncommand.cpp @@ -274,12 +274,11 @@ int RunWorker(const std::vector& configs, bool closeConsoleLog = fa Logger::DisableConsoleLog(); } - while (!l_AllowedToWork.load()) { - Utility::Sleep(0.2); - } + // The umbrella process will send this signal when the previous worker has shut down. + Application::GetPendingSignal(SIGUSR2, true); Log(LogNotice, "cli") - << "The umbrella process let us continuing"; + << "The umbrella process let us continue"; #endif /* _WIN32 */ NotifyStatus("Restoring the previous program state..."); @@ -328,104 +327,40 @@ int RunWorker(const std::vector& configs, bool closeConsoleLog = fa } #ifndef _WIN32 -// The signals to block temporarily in StartUnixWorker(). -static const sigset_t l_UnixWorkerSignals = ([]() -> sigset_t { - sigset_t s; - - (void)sigemptyset(&s); - (void)sigaddset(&s, SIGUSR1); - (void)sigaddset(&s, SIGUSR2); - (void)sigaddset(&s, SIGINT); - (void)sigaddset(&s, SIGTERM); - (void)sigaddset(&s, SIGHUP); - - return s; -})(); - -// The PID of the seamless worker currently being started by StartUnixWorker() -static Atomic l_CurrentlyStartingUnixWorkerPid (-1); - -// The state of the seamless worker currently being started by StartUnixWorker() -static Atomic l_CurrentlyStartingUnixWorkerReady (false); - -// The last temination signal we received -static Atomic l_TermSignal (-1); - -// Whether someone requested to re-load config (and we didn't handle that request, yet) -static Atomic l_RequestedReload (false); - -// Whether someone requested to re-open logs (and we didn't handle that request, yet) -static Atomic l_RequestedReopenLogs (false); /** - * Umbrella process' signal handlers + * Block the signals from the given list. + * + * @param signals A std::initializer_list containing the signals to block */ -static void UmbrellaSignalHandler(int num, siginfo_t *info, void*) -{ - switch (num) { - case SIGUSR1: - // Someone requested to re-open logs - l_RequestedReopenLogs.store(true); - break; - case SIGUSR2: - if (!l_CurrentlyStartingUnixWorkerReady.load() - && (info->si_pid == 0 || info->si_pid == l_CurrentlyStartingUnixWorkerPid.load()) ) { - // The seamless worker currently being started by StartUnixWorker() successfully loaded its config - l_CurrentlyStartingUnixWorkerReady.store(true); - } - break; - case SIGINT: - case SIGTERM: - // Someone requested our termination - - { - struct sigaction sa; - memset(&sa, 0, sizeof(sa)); - - sa.sa_handler = SIG_DFL; +static void BlockUnixSignals(std::initializer_list signals){ + sigset_t sigSet; - (void)sigaction(num, &sa, nullptr); - } - - l_TermSignal.store(num); - break; - case SIGHUP: - // Someone requested to re-load config - l_RequestedReload.store(true); - break; - default: - // Programming error (or someone has broken the userspace) - VERIFY(!"Caught unexpected signal"); + (void)sigemptyset(&sigSet); + for (auto signal : signals) { + (void)sigaddset(&sigSet, signal); } -} + + (void)pthread_sigmask(SIG_BLOCK, &sigSet, nullptr); +}; /** - * Seamless worker's signal handlers + * Unblocks the signal and sets the handler to the given value. + * + * @param signal The signal to unblock and reset + * @param handler The new handler after unblocking */ -static void WorkerSignalHandler(int num, siginfo_t *info, void*) +static void UnblockSignalAndSetHandler(int signal, void (*handler)(int)) { - switch (num) { - case SIGUSR1: - // Catches SIGUSR1 as long as the actual handler (logrotate) - // has not been installed not to let SIGUSR1 terminate the process - break; - case SIGUSR2: - if (info->si_pid == 0 || info->si_pid == l_UmbrellaPid) { - // The umbrella process allowed us to continue working beyond config validation - l_AllowedToWork.store(true); - } - break; - case SIGINT: - case SIGTERM: - if (info->si_pid == 0 || info->si_pid == l_UmbrellaPid) { - // The umbrella process requested our termination - Application::RequestShutdown(); - } - break; - default: - // Programming error (or someone has broken the userspace) - VERIFY(!"Caught unexpected signal"); - } + struct sigaction sa; + memset(&sa, 0, sizeof(sa)); + sa.sa_handler = handler; + (void)sigaction(signal, &sa, nullptr); + + sigset_t sigSet; + (void)sigemptyset(&sigSet); + (void)sigaddset(&sigSet, signal); + (void)pthread_sigmask(SIG_UNBLOCK, &sigSet, nullptr); } #ifdef HAVE_SYSTEMD @@ -468,11 +403,6 @@ static pid_t StartUnixWorker(const std::vector& configs, bool close exit(EXIT_FAILURE); } - /* Block the signal handlers we'd like to change in the child process until we changed them. - * Block SIGUSR2 handler until we've set l_CurrentlyStartingUnixWorkerPid. - */ - (void)sigprocmask(SIG_BLOCK, &l_UnixWorkerSignals, nullptr); - pid_t pid = fork(); switch (pid) { @@ -487,44 +417,15 @@ static pid_t StartUnixWorker(const std::vector& configs, bool close << "Failed to re-initialize thread pool after forking (parent): " << DiagnosticInformation(ex); exit(EXIT_FAILURE); } - - (void)sigprocmask(SIG_UNBLOCK, &l_UnixWorkerSignals, nullptr); return -1; case 0: try { - { - struct sigaction sa; - memset(&sa, 0, sizeof(sa)); - - sa.sa_handler = SIG_DFL; - - (void)sigaction(SIGUSR1, &sa, nullptr); - } - - { - struct sigaction sa; - memset(&sa, 0, sizeof(sa)); - - sa.sa_handler = SIG_IGN; - - (void)sigaction(SIGHUP, &sa, nullptr); - } - - { - struct sigaction sa; - memset(&sa, 0, sizeof(sa)); - - sa.sa_sigaction = &WorkerSignalHandler; - sa.sa_flags = SA_RESTART | SA_SIGINFO; - - (void)sigaction(SIGUSR1, &sa, nullptr); - (void)sigaction(SIGUSR2, &sa, nullptr); - (void)sigaction(SIGINT, &sa, nullptr); - (void)sigaction(SIGTERM, &sa, nullptr); - } - - (void)sigprocmask(SIG_UNBLOCK, &l_UnixWorkerSignals, nullptr); + /* We'll leave the other signals blocked since we're going + * to handle them synchronously with sigtimedwait in + * Application::RunEventLoop(). + */ + UnblockSignalAndSetHandler(SIGHUP, SIG_IGN); try { Application::InitializeBase(); @@ -551,9 +452,6 @@ static pid_t StartUnixWorker(const std::vector& configs, bool close } default: - l_CurrentlyStartingUnixWorkerPid.store(pid); - (void)sigprocmask(SIG_UNBLOCK, &l_UnixWorkerSignals, nullptr); - Log(LogNotice, "cli") << "Spawned worker process (PID " << pid << "), waiting for it to load its config"; @@ -571,7 +469,8 @@ static pid_t StartUnixWorker(const std::vector& configs, bool close break; } - if (l_CurrentlyStartingUnixWorkerReady.load()) { + auto sig = Application::GetPendingSignal(SIGUSR2); + if (sig && (sig->si_pid == 0 || sig->si_pid == pid)) { Log(LogNotice, "cli") << "Worker process successfully loaded its config"; break; @@ -580,10 +479,6 @@ static pid_t StartUnixWorker(const std::vector& configs, bool close Utility::Sleep(0.2); } - // Reset flags for the next time - l_CurrentlyStartingUnixWorkerPid.store(-1); - l_CurrentlyStartingUnixWorkerReady.store(false); - try { Application::InitializeBase(); } catch (const std::exception& ex) { @@ -620,6 +515,13 @@ int DaemonCommand::Run(const po::variables_map& vm, const std::vectorsi_signo, SIG_DFL); + Log(LogCritical, "cli") + << "Got signal " << sig->si_signo << ", forwarding to seamless worker (PID " << currentWorker << ")"; - (void)kill(currentWorker, termSig); + (void)kill(currentWorker, sig->si_signo); requestedTermination = true; #ifdef HAVE_SYSTEMD @@ -794,7 +683,7 @@ int DaemonCommand::Run(const po::variables_map& vm, const std::vector