icinga2/lib/cli/daemoncommand.cpp
Michael Friedrich 6cce9c0fdd API: Automatically repair broken packages
This partially reverts #7150 and avoids exceptions
inside the flow. Each time an empty active stage
is detected, Icinga tries to repair it from the
the given directory tree.

Also, the code now takes into account that it should
create the package storage on startup, whether within
the API object, or if disabled, inside the application.

Caching the active stages for packages in memory
only is in effect with the API feature being enabled.
This is useful for other deployed config packages,
not only the internal one.

fixes #7173
refs #7150
refs #7119
fixes #6959
2019-05-10 12:48:34 +02:00

314 lines
8.8 KiB
C++

/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
#include "cli/daemoncommand.hpp"
#include "cli/daemonutility.hpp"
#include "remote/apilistener.hpp"
#include "remote/configobjectutility.hpp"
#include "config/configcompiler.hpp"
#include "config/configcompilercontext.hpp"
#include "config/configitembuilder.hpp"
#include "base/logger.hpp"
#include "base/application.hpp"
#include "base/timer.hpp"
#include "base/utility.hpp"
#include "base/exception.hpp"
#include "base/convert.hpp"
#include "base/scriptglobal.hpp"
#include "base/context.hpp"
#include "config.h"
#include <boost/program_options.hpp>
#include <boost/tuple/tuple.hpp>
#include <iostream>
#include <fstream>
using namespace icinga;
namespace po = boost::program_options;
static po::variables_map g_AppParams;
REGISTER_CLICOMMAND("daemon", DaemonCommand);
#ifndef _WIN32
static void SigHupHandler(int)
{
Application::RequestRestart();
}
#endif /* _WIN32 */
/*
* Daemonize(). On error, this function logs by itself and exits (i.e. does not return).
*
* Implementation note: We're only supposed to call exit() in one of the forked processes.
* The other process calls _exit(). This prevents issues with exit handlers like atexit().
*/
static void Daemonize() noexcept
{
#ifndef _WIN32
try {
Application::UninitializeBase();
} catch (const std::exception& ex) {
Log(LogCritical, "cli")
<< "Failed to stop thread pool before daemonizing, unexpected error: " << DiagnosticInformation(ex);
exit(EXIT_FAILURE);
}
pid_t pid = fork();
if (pid == -1) {
Log(LogCritical, "cli")
<< "fork() failed with error code " << errno << ", \"" << Utility::FormatErrorNumber(errno) << "\"";
exit(EXIT_FAILURE);
}
if (pid) {
// systemd requires that the pidfile of the daemon is written before the forking
// process terminates. So wait till either the forked daemon has written a pidfile or died.
int status;
int ret;
pid_t readpid;
do {
Utility::Sleep(0.1);
readpid = Application::ReadPidFile(Configuration::PidPath);
ret = waitpid(pid, &status, WNOHANG);
} while (readpid != pid && ret == 0);
if (ret == pid) {
Log(LogCritical, "cli", "The daemon could not be started. See log output for details.");
_exit(EXIT_FAILURE);
} else if (ret == -1) {
Log(LogCritical, "cli")
<< "waitpid() failed with error code " << errno << ", \"" << Utility::FormatErrorNumber(errno) << "\"";
_exit(EXIT_FAILURE);
}
_exit(EXIT_SUCCESS);
}
Log(LogDebug, "Daemonize()")
<< "Child process with PID " << Utility::GetPid() << " continues; re-initializing base.";
// Detach from controlling terminal
pid_t sid = setsid();
if (sid == -1) {
Log(LogCritical, "cli")
<< "setsid() failed with error code " << errno << ", \"" << Utility::FormatErrorNumber(errno) << "\"";
exit(EXIT_FAILURE);
}
try {
Application::InitializeBase();
} catch (const std::exception& ex) {
Log(LogCritical, "cli")
<< "Failed to re-initialize thread pool after daemonizing: " << DiagnosticInformation(ex);
exit(EXIT_FAILURE);
}
#endif /* _WIN32 */
}
static void CloseStdIO(const String& stderrFile)
{
#ifndef _WIN32
int fdnull = open("/dev/null", O_RDWR);
if (fdnull >= 0) {
if (fdnull != 0)
dup2(fdnull, 0);
if (fdnull != 1)
dup2(fdnull, 1);
if (fdnull > 1)
close(fdnull);
}
const char *errPath = "/dev/null";
if (!stderrFile.IsEmpty())
errPath = stderrFile.CStr();
int fderr = open(errPath, O_WRONLY | O_APPEND);
if (fderr < 0 && errno == ENOENT)
fderr = open(errPath, O_CREAT | O_WRONLY | O_APPEND, 0600);
if (fderr >= 0) {
if (fderr != 2)
dup2(fderr, 2);
if (fderr > 2)
close(fderr);
}
#endif
}
String DaemonCommand::GetDescription() const
{
return "Starts Icinga 2.";
}
String DaemonCommand::GetShortDescription() const
{
return "starts Icinga 2";
}
void DaemonCommand::InitParameters(boost::program_options::options_description& visibleDesc,
boost::program_options::options_description& hiddenDesc) const
{
visibleDesc.add_options()
("config,c", po::value<std::vector<std::string> >(), "parse a configuration file")
("no-config,z", "start without a configuration file")
("validate,C", "exit after validating the configuration")
("errorlog,e", po::value<std::string>(), "log fatal errors to the specified log file (only works in combination with --daemonize or --close-stdio)")
#ifndef _WIN32
("daemonize,d", "detach from the controlling terminal")
("close-stdio", "do not log to stdout (or stderr) after startup")
#endif /* _WIN32 */
;
#ifndef _WIN32
hiddenDesc.add_options()
("reload-internal", po::value<int>(), "used internally to implement config reload: do not call manually, send SIGHUP instead");
#endif /* _WIN32 */
}
std::vector<String> DaemonCommand::GetArgumentSuggestions(const String& argument, const String& word) const
{
if (argument == "config" || argument == "errorlog")
return GetBashCompletionSuggestions("file", word);
else
return CLICommand::GetArgumentSuggestions(argument, word);
}
/**
* The entry point for the "daemon" CLI command.
*
* @returns An exit status.
*/
int DaemonCommand::Run(const po::variables_map& vm, const std::vector<std::string>& ap) const
{
Logger::EnableTimestamp();
Log(LogInformation, "cli")
<< "Icinga application loader (version: " << Application::GetAppVersion()
#ifdef I2_DEBUG
<< "; debug"
#endif /* I2_DEBUG */
<< ")";
if (!vm.count("validate") && !vm.count("reload-internal")) {
pid_t runningpid = Application::ReadPidFile(Configuration::PidPath);
if (runningpid > 0) {
Log(LogCritical, "cli")
<< "Another instance of Icinga already running with PID " << runningpid;
return EXIT_FAILURE;
}
}
std::vector<std::string> configs;
if (vm.count("config") > 0)
configs = vm["config"].as<std::vector<std::string> >();
else if (!vm.count("no-config")) {
/* The implicit string assignment is needed for Windows builds. */
String configDir = Configuration::ConfigDir;
configs.push_back(configDir + "/icinga2.conf");
}
Log(LogInformation, "cli", "Loading configuration file(s).");
std::vector<ConfigItem::Ptr> newItems;
if (!DaemonUtility::LoadConfigFiles(configs, newItems, Configuration::ObjectsPath, Configuration::VarsPath))
return EXIT_FAILURE;
if (vm.count("validate")) {
Log(LogInformation, "cli", "Finished validating the configuration file(s).");
return EXIT_SUCCESS;
}
#ifndef _WIN32
if (vm.count("reload-internal")) {
/* We went through validation and now ask the old process kindly to die */
Log(LogInformation, "cli", "Requesting to take over.");
int rc = kill(vm["reload-internal"].as<int>(), SIGUSR2);
if (rc) {
Log(LogCritical, "cli")
<< "Failed to send signal to \"" << vm["reload-internal"].as<int>() << "\" with " << strerror(errno);
return EXIT_FAILURE;
}
double start = Utility::GetTime();
while (kill(vm["reload-internal"].as<int>(), SIGCHLD) == 0)
Utility::Sleep(0.2);
Log(LogNotice, "cli")
<< "Waited for " << Utility::FormatDuration(Utility::GetTime() - start) << " on old process to exit.";
}
#endif /* _WIN32 */
if (vm.count("daemonize")) {
if (!vm.count("reload-internal")) {
// no additional fork neccessary on reload
// this subroutine either succeeds, or logs an error
// and terminates the process (does not return).
Daemonize();
}
}
/* restore the previous program state */
try {
ConfigObject::RestoreObjects(Configuration::StatePath);
} catch (const std::exception& ex) {
Log(LogCritical, "cli")
<< "Failed to restore state file: " << DiagnosticInformation(ex);
return EXIT_FAILURE;
}
{
WorkQueue upq(25000, Configuration::Concurrency);
upq.SetName("DaemonCommand::Run");
// activate config only after daemonization: it starts threads and that is not compatible with fork()
if (!ConfigItem::ActivateItems(upq, newItems, false, false, true)) {
Log(LogCritical, "cli", "Error activating configuration.");
return EXIT_FAILURE;
}
}
if (vm.count("daemonize") || vm.count("close-stdio")) {
// After disabling the console log, any further errors will go to the configured log only.
// Let's try to make this clear and say good bye.
Log(LogInformation, "cli", "Closing console log.");
String errorLog;
if (vm.count("errorlog"))
errorLog = vm["errorlog"].as<std::string>();
CloseStdIO(errorLog);
Logger::DisableConsoleLog();
}
/* Create the internal API object storage. Do this here too with setups without API. */
ConfigObjectUtility::CreateStorage();
/* Remove ignored Downtime/Comment objects. */
try {
String configDir = ConfigObjectUtility::GetConfigDir();
ConfigItem::RemoveIgnoredItems(configDir);
} catch (const std::exception& ex) {
Log(LogNotice, "cli")
<< "Cannot clean ignored downtimes/comments: " << ex.what();
}
#ifndef _WIN32
struct sigaction sa;
memset(&sa, 0, sizeof(sa));
sa.sa_handler = &SigHupHandler;
sigaction(SIGHUP, &sa, nullptr);
#endif /* _WIN32 */
ApiListener::UpdateObjectAuthority();
return Application::GetInstance()->Run();
}