1 /* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
3 #include "cli/daemoncommand.hpp"
4 #include "cli/daemonutility.hpp"
5 #include "remote/apilistener.hpp"
6 #include "remote/configobjectutility.hpp"
7 #include "config/configcompiler.hpp"
8 #include "config/configcompilercontext.hpp"
9 #include "config/configitembuilder.hpp"
10 #include "base/logger.hpp"
11 #include "base/application.hpp"
12 #include "base/timer.hpp"
13 #include "base/utility.hpp"
14 #include "base/exception.hpp"
15 #include "base/convert.hpp"
16 #include "base/scriptglobal.hpp"
17 #include "base/context.hpp"
19 #include <boost/program_options.hpp>
20 #include <boost/tuple/tuple.hpp>
24 using namespace icinga;
25 namespace po = boost::program_options;
27 static po::variables_map g_AppParams;
29 REGISTER_CLICOMMAND("daemon", DaemonCommand);
32 static void SigHupHandler(int)
34 Application::RequestRestart();
39 * Daemonize(). On error, this function logs by itself and exits (i.e. does not return).
41 * Implementation note: We're only supposed to call exit() in one of the forked processes.
42 * The other process calls _exit(). This prevents issues with exit handlers like atexit().
44 static void Daemonize() noexcept
48 Application::UninitializeBase();
49 } catch (const std::exception& ex) {
50 Log(LogCritical, "cli")
51 << "Failed to stop thread pool before daemonizing, unexpected error: " << DiagnosticInformation(ex);
57 Log(LogCritical, "cli")
58 << "fork() failed with error code " << errno << ", \"" << Utility::FormatErrorNumber(errno) << "\"";
63 // systemd requires that the pidfile of the daemon is written before the forking
64 // process terminates. So wait till either the forked daemon has written a pidfile or died.
72 readpid = Application::ReadPidFile(Configuration::PidPath);
73 ret = waitpid(pid, &status, WNOHANG);
74 } while (readpid != pid && ret == 0);
77 Log(LogCritical, "cli", "The daemon could not be started. See log output for details.");
79 } else if (ret == -1) {
80 Log(LogCritical, "cli")
81 << "waitpid() failed with error code " << errno << ", \"" << Utility::FormatErrorNumber(errno) << "\"";
88 Log(LogDebug, "Daemonize()")
89 << "Child process with PID " << Utility::GetPid() << " continues; re-initializing base.";
91 // Detach from controlling terminal
94 Log(LogCritical, "cli")
95 << "setsid() failed with error code " << errno << ", \"" << Utility::FormatErrorNumber(errno) << "\"";
100 Application::InitializeBase();
101 } catch (const std::exception& ex) {
102 Log(LogCritical, "cli")
103 << "Failed to re-initialize thread pool after daemonizing: " << DiagnosticInformation(ex);
109 static void CloseStdIO(const String& stderrFile)
112 int fdnull = open("/dev/null", O_RDWR);
124 const char *errPath = "/dev/null";
126 if (!stderrFile.IsEmpty())
127 errPath = stderrFile.CStr();
129 int fderr = open(errPath, O_WRONLY | O_APPEND);
131 if (fderr < 0 && errno == ENOENT)
132 fderr = open(errPath, O_CREAT | O_WRONLY | O_APPEND, 0600);
144 String DaemonCommand::GetDescription() const
146 return "Starts Icinga 2.";
149 String DaemonCommand::GetShortDescription() const
151 return "starts Icinga 2";
154 void DaemonCommand::InitParameters(boost::program_options::options_description& visibleDesc,
155 boost::program_options::options_description& hiddenDesc) const
157 visibleDesc.add_options()
158 ("config,c", po::value<std::vector<std::string> >(), "parse a configuration file")
159 ("no-config,z", "start without a configuration file")
160 ("validate,C", "exit after validating the configuration")
161 ("errorlog,e", po::value<std::string>(), "log fatal errors to the specified log file (only works in combination with --daemonize or --close-stdio)")
163 ("daemonize,d", "detach from the controlling terminal")
164 ("close-stdio", "do not log to stdout (or stderr) after startup")
169 hiddenDesc.add_options()
170 ("reload-internal", po::value<int>(), "used internally to implement config reload: do not call manually, send SIGHUP instead");
174 std::vector<String> DaemonCommand::GetArgumentSuggestions(const String& argument, const String& word) const
176 if (argument == "config" || argument == "errorlog")
177 return GetBashCompletionSuggestions("file", word);
179 return CLICommand::GetArgumentSuggestions(argument, word);
183 * The entry point for the "daemon" CLI command.
185 * @returns An exit status.
187 int DaemonCommand::Run(const po::variables_map& vm, const std::vector<std::string>& ap) const
189 Logger::EnableTimestamp();
191 Log(LogInformation, "cli")
192 << "Icinga application loader (version: " << Application::GetAppVersion()
195 #endif /* I2_DEBUG */
198 if (!vm.count("validate") && !vm.count("reload-internal")) {
199 pid_t runningpid = Application::ReadPidFile(Configuration::PidPath);
200 if (runningpid > 0) {
201 Log(LogCritical, "cli")
202 << "Another instance of Icinga already running with PID " << runningpid;
207 std::vector<std::string> configs;
208 if (vm.count("config") > 0)
209 configs = vm["config"].as<std::vector<std::string> >();
210 else if (!vm.count("no-config")) {
211 /* The implicit string assignment is needed for Windows builds. */
212 String configDir = Configuration::ConfigDir;
213 configs.push_back(configDir + "/icinga2.conf");
216 Log(LogInformation, "cli", "Loading configuration file(s).");
218 std::vector<ConfigItem::Ptr> newItems;
220 if (!DaemonUtility::LoadConfigFiles(configs, newItems, Configuration::ObjectsPath, Configuration::VarsPath))
223 if (vm.count("validate")) {
224 Log(LogInformation, "cli", "Finished validating the configuration file(s).");
229 if (vm.count("reload-internal")) {
230 /* We went through validation and now ask the old process kindly to die */
231 Log(LogInformation, "cli", "Requesting to take over.");
232 int rc = kill(vm["reload-internal"].as<int>(), SIGUSR2);
234 Log(LogCritical, "cli")
235 << "Failed to send signal to \"" << vm["reload-internal"].as<int>() << "\" with " << strerror(errno);
239 double start = Utility::GetTime();
240 while (kill(vm["reload-internal"].as<int>(), SIGCHLD) == 0)
243 Log(LogNotice, "cli")
244 << "Waited for " << Utility::FormatDuration(Utility::GetTime() - start) << " on old process to exit.";
248 if (vm.count("daemonize")) {
249 if (!vm.count("reload-internal")) {
250 // no additional fork neccessary on reload
252 // this subroutine either succeeds, or logs an error
253 // and terminates the process (does not return).
258 /* restore the previous program state */
260 ConfigObject::RestoreObjects(Configuration::StatePath);
261 } catch (const std::exception& ex) {
262 Log(LogCritical, "cli")
263 << "Failed to restore state file: " << DiagnosticInformation(ex);
268 WorkQueue upq(25000, Configuration::Concurrency);
269 upq.SetName("DaemonCommand::Run");
271 // activate config only after daemonization: it starts threads and that is not compatible with fork()
272 if (!ConfigItem::ActivateItems(upq, newItems, false, false, true)) {
273 Log(LogCritical, "cli", "Error activating configuration.");
278 if (vm.count("daemonize") || vm.count("close-stdio")) {
279 // After disabling the console log, any further errors will go to the configured log only.
280 // Let's try to make this clear and say good bye.
281 Log(LogInformation, "cli", "Closing console log.");
284 if (vm.count("errorlog"))
285 errorLog = vm["errorlog"].as<std::string>();
287 CloseStdIO(errorLog);
288 Logger::DisableConsoleLog();
291 /* Remove ignored Downtime/Comment objects. */
292 ConfigItem::RemoveIgnoredItems(ConfigObjectUtility::GetConfigDir());
296 memset(&sa, 0, sizeof(sa));
297 sa.sa_handler = &SigHupHandler;
298 sigaction(SIGHUP, &sa, nullptr);
301 ApiListener::UpdateObjectAuthority();
303 return Application::GetInstance()->Run();