1 /******************************************************************************
3 * Copyright (C) 2012-2018 Icinga Development Team (https://icinga.com/) *
5 * This program is free software; you can redistribute it and/or *
6 * modify it under the terms of the GNU General Public License *
7 * as published by the Free Software Foundation; either version 2 *
8 * of the License, or (at your option) any later version. *
10 * This program is distributed in the hope that it will be useful, *
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
13 * GNU General Public License for more details. *
15 * You should have received a copy of the GNU General Public License *
16 * along with this program; if not, write to the Free Software Foundation *
17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. *
18 ******************************************************************************/
20 #include "cli/daemoncommand.hpp"
21 #include "cli/daemonutility.hpp"
22 #include "remote/apilistener.hpp"
23 #include "remote/configobjectutility.hpp"
24 #include "config/configcompiler.hpp"
25 #include "config/configcompilercontext.hpp"
26 #include "config/configitembuilder.hpp"
27 #include "base/logger.hpp"
28 #include "base/application.hpp"
29 #include "base/timer.hpp"
30 #include "base/utility.hpp"
31 #include "base/exception.hpp"
32 #include "base/convert.hpp"
33 #include "base/scriptglobal.hpp"
34 #include "base/context.hpp"
36 #include <boost/program_options.hpp>
37 #include <boost/tuple/tuple.hpp>
41 using namespace icinga;
42 namespace po = boost::program_options;
44 static po::variables_map g_AppParams;
46 REGISTER_CLICOMMAND("daemon", DaemonCommand);
49 static void SigHupHandler(int)
51 Application::RequestRestart();
56 * Daemonize(). On error, this function logs by itself and exits (i.e. does not return).
58 * Implementation note: We're only supposed to call exit() in one of the forked processes.
59 * The other process calls _exit(). This prevents issues with exit handlers like atexit().
61 static void Daemonize() noexcept
65 Application::UninitializeBase();
66 } catch (const std::exception& ex) {
67 Log(LogCritical, "cli")
68 << "Failed to stop thread pool before daemonizing, unexpected error: " << DiagnosticInformation(ex);
74 Log(LogCritical, "cli")
75 << "fork() failed with error code " << errno << ", \"" << Utility::FormatErrorNumber(errno) << "\"";
80 // systemd requires that the pidfile of the daemon is written before the forking
81 // process terminates. So wait till either the forked daemon has written a pidfile or died.
89 readpid = Application::ReadPidFile(Configuration::PidPath);
90 ret = waitpid(pid, &status, WNOHANG);
91 } while (readpid != pid && ret == 0);
94 Log(LogCritical, "cli", "The daemon could not be started. See log output for details.");
96 } else if (ret == -1) {
97 Log(LogCritical, "cli")
98 << "waitpid() failed with error code " << errno << ", \"" << Utility::FormatErrorNumber(errno) << "\"";
105 Log(LogDebug, "Daemonize()")
106 << "Child process with PID " << Utility::GetPid() << " continues; re-initializing base.";
108 // Detach from controlling terminal
109 pid_t sid = setsid();
111 Log(LogCritical, "cli")
112 << "setsid() failed with error code " << errno << ", \"" << Utility::FormatErrorNumber(errno) << "\"";
117 Application::InitializeBase();
118 } catch (const std::exception& ex) {
119 Log(LogCritical, "cli")
120 << "Failed to re-initialize thread pool after daemonizing: " << DiagnosticInformation(ex);
126 static void CloseStdIO(const String& stderrFile)
129 int fdnull = open("/dev/null", O_RDWR);
141 const char *errPath = "/dev/null";
143 if (!stderrFile.IsEmpty())
144 errPath = stderrFile.CStr();
146 int fderr = open(errPath, O_WRONLY | O_APPEND);
148 if (fderr < 0 && errno == ENOENT)
149 fderr = open(errPath, O_CREAT | O_WRONLY | O_APPEND, 0600);
161 String DaemonCommand::GetDescription() const
163 return "Starts Icinga 2.";
166 String DaemonCommand::GetShortDescription() const
168 return "starts Icinga 2";
171 void DaemonCommand::InitParameters(boost::program_options::options_description& visibleDesc,
172 boost::program_options::options_description& hiddenDesc) const
174 visibleDesc.add_options()
175 ("config,c", po::value<std::vector<std::string> >(), "parse a configuration file")
176 ("no-config,z", "start without a configuration file")
177 ("validate,C", "exit after validating the configuration")
178 ("errorlog,e", po::value<std::string>(), "log fatal errors to the specified log file (only works in combination with --daemonize or --close-stdio)")
180 ("daemonize,d", "detach from the controlling terminal")
181 ("close-stdio", "do not log to stdout (or stderr) after startup")
186 hiddenDesc.add_options()
187 ("reload-internal", po::value<int>(), "used internally to implement config reload: do not call manually, send SIGHUP instead");
191 std::vector<String> DaemonCommand::GetArgumentSuggestions(const String& argument, const String& word) const
193 if (argument == "config" || argument == "errorlog")
194 return GetBashCompletionSuggestions("file", word);
196 return CLICommand::GetArgumentSuggestions(argument, word);
200 * The entry point for the "daemon" CLI command.
202 * @returns An exit status.
204 int DaemonCommand::Run(const po::variables_map& vm, const std::vector<std::string>& ap) const
206 Logger::EnableTimestamp();
208 Log(LogInformation, "cli")
209 << "Icinga application loader (version: " << Application::GetAppVersion()
212 #endif /* I2_DEBUG */
215 if (!vm.count("validate") && !vm.count("reload-internal")) {
216 pid_t runningpid = Application::ReadPidFile(Configuration::PidPath);
217 if (runningpid > 0) {
218 Log(LogCritical, "cli")
219 << "Another instance of Icinga already running with PID " << runningpid;
224 std::vector<std::string> configs;
225 if (vm.count("config") > 0)
226 configs = vm["config"].as<std::vector<std::string> >();
227 else if (!vm.count("no-config")) {
228 /* The implicit string assignment is needed for Windows builds. */
229 String configDir = Configuration::ConfigDir;
230 configs.push_back(configDir + "/icinga2.conf");
233 Log(LogInformation, "cli", "Loading configuration file(s).");
235 std::vector<ConfigItem::Ptr> newItems;
237 if (!DaemonUtility::LoadConfigFiles(configs, newItems, Configuration::ObjectsPath, Configuration::VarsPath))
240 if (vm.count("validate")) {
241 Log(LogInformation, "cli", "Finished validating the configuration file(s).");
246 if (vm.count("reload-internal")) {
247 /* We went through validation and now ask the old process kindly to die */
248 Log(LogInformation, "cli", "Requesting to take over.");
249 int rc = kill(vm["reload-internal"].as<int>(), SIGUSR2);
251 Log(LogCritical, "cli")
252 << "Failed to send signal to \"" << vm["reload-internal"].as<int>() << "\" with " << strerror(errno);
256 double start = Utility::GetTime();
257 while (kill(vm["reload-internal"].as<int>(), SIGCHLD) == 0)
260 Log(LogNotice, "cli")
261 << "Waited for " << Utility::FormatDuration(Utility::GetTime() - start) << " on old process to exit.";
265 if (vm.count("daemonize")) {
266 if (!vm.count("reload-internal")) {
267 // no additional fork neccessary on reload
269 // this subroutine either succeeds, or logs an error
270 // and terminates the process (does not return).
275 /* restore the previous program state */
277 ConfigObject::RestoreObjects(Configuration::StatePath);
278 } catch (const std::exception& ex) {
279 Log(LogCritical, "cli")
280 << "Failed to restore state file: " << DiagnosticInformation(ex);
285 WorkQueue upq(25000, Configuration::Concurrency);
286 upq.SetName("DaemonCommand::Run");
288 // activate config only after daemonization: it starts threads and that is not compatible with fork()
289 if (!ConfigItem::ActivateItems(upq, newItems, false, false, true)) {
290 Log(LogCritical, "cli", "Error activating configuration.");
295 if (vm.count("daemonize") || vm.count("close-stdio")) {
296 // After disabling the console log, any further errors will go to the configured log only.
297 // Let's try to make this clear and say good bye.
298 Log(LogInformation, "cli", "Closing console log.");
301 if (vm.count("errorlog"))
302 errorLog = vm["errorlog"].as<std::string>();
304 CloseStdIO(errorLog);
305 Logger::DisableConsoleLog();
308 /* Remove ignored Downtime/Comment objects. */
309 ConfigItem::RemoveIgnoredItems(ConfigObjectUtility::GetConfigDir());
313 memset(&sa, 0, sizeof(sa));
314 sa.sa_handler = &SigHupHandler;
315 sigaction(SIGHUP, &sa, nullptr);
318 ApiListener::UpdateObjectAuthority();
320 return Application::GetInstance()->Run();