1 /******************************************************************************
3 * Copyright (C) 2012-2014 Icinga Development Team (http://www.icinga.org) *
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 "config/configcompilercontext.hpp"
22 #include "config/configcompiler.hpp"
23 #include "config/configitembuilder.hpp"
24 #include "base/logger.hpp"
25 #include "base/application.hpp"
26 #include "base/logger.hpp"
27 #include "base/timer.hpp"
28 #include "base/utility.hpp"
29 #include "base/exception.hpp"
30 #include "base/convert.hpp"
31 #include "base/scriptvariable.hpp"
32 #include "base/context.hpp"
33 #include "base/scriptsignal.hpp"
35 #include <boost/program_options.hpp>
36 #include <boost/tuple/tuple.hpp>
37 #include <boost/foreach.hpp>
40 using namespace icinga;
41 namespace po = boost::program_options;
43 static po::variables_map g_AppParams;
45 REGISTER_CLICOMMAND("daemon", DaemonCommand);
46 REGISTER_SCRIPTSIGNAL(onload);
48 static String LoadAppType(const String& typeSpec)
50 Log(LogInformation, "cli")
51 << "Loading application type: " << typeSpec;
53 String::SizeType index = typeSpec.FindFirstOf('/');
55 if (index == String::NPos)
58 String library = typeSpec.SubStr(0, index);
60 (void) Utility::LoadExtensionLibrary(library);
62 return typeSpec.SubStr(index + 1);
65 static void ExecuteExpression(Expression *expression)
69 expression->Evaluate(frame);
70 } catch (const ConfigError& ex) {
71 const DebugInfo *di = boost::get_error_info<errinfo_debuginfo>(ex);
72 ConfigCompilerContext::GetInstance()->AddMessage(true, ex.what(), di ? *di : DebugInfo());
73 } catch (const std::exception& ex) {
74 ConfigCompilerContext::GetInstance()->AddMessage(true, DiagnosticInformation(ex));
78 static void IncludeZoneDirRecursive(const String& path)
80 String zoneName = Utility::BaseName(path);
82 std::vector<Expression *> expressions;
83 Utility::GlobRecursive(path, "*.conf", boost::bind(&ConfigCompiler::CollectIncludes, boost::ref(expressions), _1, zoneName), GlobFile);
84 DictExpression expr(expressions);
85 ExecuteExpression(&expr);
88 static void IncludeNonLocalZone(const String& zonePath)
90 String etcPath = Application::GetZonesDir() + "/" + Utility::BaseName(zonePath);
92 if (Utility::PathExists(etcPath))
95 IncludeZoneDirRecursive(zonePath);
98 static bool LoadConfigFiles(const boost::program_options::variables_map& vm, const String& appType,
99 const String& objectsFile = String(), const String& varsfile = String())
101 ConfigCompilerContext::GetInstance()->Reset();
103 if (!objectsFile.IsEmpty())
104 ConfigCompilerContext::GetInstance()->OpenObjectsFile(objectsFile);
106 if (vm.count("config") > 0) {
107 BOOST_FOREACH(const String& configPath, vm["config"].as<std::vector<std::string> >()) {
108 Expression *expression = ConfigCompiler::CompileFile(configPath);
110 ExecuteExpression(expression);
113 } else if (!vm.count("no-config")) {
114 Expression *expression = ConfigCompiler::CompileFile(Application::GetSysconfDir() + "/icinga2/icinga2.conf");
116 ExecuteExpression(expression);
120 /* Load cluster config files - this should probably be in libremote but
121 * unfortunately moving it there is somewhat non-trivial. */
122 String zonesEtcDir = Application::GetZonesDir();
123 if (!zonesEtcDir.IsEmpty() && Utility::PathExists(zonesEtcDir))
124 Utility::Glob(zonesEtcDir + "/*", &IncludeZoneDirRecursive, GlobDirectory);
126 String zonesVarDir = Application::GetLocalStateDir() + "/lib/icinga2/api/zones";
127 if (Utility::PathExists(zonesVarDir))
128 Utility::Glob(zonesVarDir + "/*", &IncludeNonLocalZone, GlobDirectory);
130 String name, fragment;
131 BOOST_FOREACH(boost::tie(name, fragment), ConfigFragmentRegistry::GetInstance()->GetItems()) {
132 Expression *expression = ConfigCompiler::CompileText(name, fragment);
134 ExecuteExpression(expression);
138 ConfigItemBuilder::Ptr builder = new ConfigItemBuilder();
139 builder->SetType(appType);
140 builder->SetName("application");
141 ConfigItem::Ptr item = builder->Compile();
144 bool result = ConfigItem::ValidateItems();
146 int warnings = 0, errors = 0;
148 BOOST_FOREACH(const ConfigCompilerMessage& message, ConfigCompilerContext::GetInstance()->GetMessages()) {
149 std::ostringstream locbuf;
150 ShowCodeFragment(locbuf, message.Location, true);
151 String location = locbuf.str();
155 if (!location.IsEmpty())
156 logmsg = "Location:\n" + location;
158 logmsg += String("\nConfig ") + (message.Error ? "error" : "warning") + ": " + message.Text;
161 Log(LogCritical, "config", logmsg);
164 Log(LogWarning, "config", logmsg);
169 if (warnings > 0 || errors > 0) {
170 LogSeverity severity;
173 severity = LogWarning;
175 severity = LogCritical;
177 Log(severity, "config")
178 << errors << " errors, " << warnings << " warnings.";
184 ConfigCompilerContext::GetInstance()->FinishObjectsFile();
186 ScriptVariable::WriteVariablesFile(varsfile);
188 ScriptSignal::Ptr loadSignal = ScriptSignal::GetByName("onload");
189 loadSignal->Invoke();
195 static void SigHupHandler(int)
197 Application::RequestRestart();
201 static bool Daemonize(void)
210 // systemd requires that the pidfile of the daemon is written before the forking
211 // process terminates. So wait till either the forked daemon has written a pidfile or died.
219 readpid = Application::ReadPidFile(Application::GetPidPath());
220 ret = waitpid(pid, &status, WNOHANG);
221 } while (readpid != pid && ret == 0);
224 Log(LogCritical, "cli", "The daemon could not be started. See log output for details.");
225 Application::Exit(EXIT_FAILURE);
226 } else if (ret == -1) {
227 Log(LogCritical, "cli")
228 << "waitpid() failed with error code " << errno << ", \"" << Utility::FormatErrorNumber(errno) << "\"";
229 Application::Exit(EXIT_FAILURE);
232 Application::Exit(0);
239 static bool SetDaemonIO(const String& stderrFile)
242 int fdnull = open("/dev/null", O_RDWR);
254 const char *errPath = "/dev/null";
256 if (!stderrFile.IsEmpty())
257 errPath = stderrFile.CStr();
259 int fderr = open(errPath, O_WRONLY | O_APPEND);
261 if (fderr < 0 && errno == ENOENT)
262 fderr = open(errPath, O_CREAT | O_WRONLY | O_APPEND, 0600);
272 pid_t sid = setsid();
282 * Terminate another process and wait till it has ended
284 * @params target PID of the process to end
286 static void TerminateAndWaitForEnd(pid_t target)
289 // allow 30 seconds timeout
290 double timeout = Utility::GetTime() + 30;
292 int ret = kill(target, SIGTERM);
294 while (Utility::GetTime() < timeout && (ret == 0 || errno != ESRCH)) {
296 ret = kill(target, 0);
299 // timeout and the process still seems to live: kill it
300 if (ret == 0 || errno != ESRCH)
301 kill(target, SIGKILL);
304 // TODO: implement this for Win32
308 String DaemonCommand::GetDescription(void) const
310 return "Starts Icinga 2.";
313 String DaemonCommand::GetShortDescription(void) const
315 return "starts Icinga 2";
318 void DaemonCommand::InitParameters(boost::program_options::options_description& visibleDesc,
319 boost::program_options::options_description& hiddenDesc) const
321 visibleDesc.add_options()
322 ("config,c", po::value<std::vector<std::string> >(), "parse a configuration file")
323 ("no-config,z", "start without a configuration file")
324 ("validate,C", "exit after validating the configuration")
325 ("errorlog,e", po::value<std::string>(), "log fatal errors to the specified log file (only works in combination with --daemonize)")
327 ("daemonize,d", "detach from the controlling terminal")
332 hiddenDesc.add_options()
333 ("reload-internal", po::value<int>(), "used internally to implement config reload: do not call manually, send SIGHUP instead");
337 std::vector<String> DaemonCommand::GetArgumentSuggestions(const String& argument, const String& word) const
339 if (argument == "config" || argument == "errorlog")
340 return GetBashCompletionSuggestions("file", word);
342 return CLICommand::GetArgumentSuggestions(argument, word);
346 * The entry point for the "daemon" CLI command.
348 * @returns An exit status.
350 int DaemonCommand::Run(const po::variables_map& vm, const std::vector<std::string>& ap) const
352 if (!vm.count("validate"))
353 Logger::DisableTimestamp(false);
355 ScriptVariable::Set("UseVfork", true, false, true);
357 Application::MakeVariablesConstant();
359 Log(LogInformation, "cli")
360 << "Icinga application loader (version: " << Application::GetVersion()
366 String appType = LoadAppType(Application::GetApplicationType());
368 if (!vm.count("validate") && !vm.count("reload-internal")) {
369 pid_t runningpid = Application::ReadPidFile(Application::GetPidPath());
370 if (runningpid > 0) {
371 Log(LogCritical, "cli")
372 << "Another instance of Icinga already running with PID " << runningpid;
377 if (!LoadConfigFiles(vm, appType, Application::GetObjectsPath(), Application::GetVarsPath()))
380 if (vm.count("validate")) {
381 Log(LogInformation, "cli", "Finished validating the configuration file(s).");
385 if(vm.count("reload-internal")) {
386 int parentpid = vm["reload-internal"].as<int>();
387 Log(LogInformation, "cli")
388 << "Terminating previous instance of Icinga (PID " << parentpid << ")";
389 TerminateAndWaitForEnd(parentpid);
390 Log(LogInformation, "cli", "Previous instance has ended, taking over now.");
393 if (vm.count("daemonize")) {
394 if (!vm.count("reload-internal")) {
395 // no additional fork neccessary on reload
398 } catch (std::exception&) {
399 Log(LogCritical, "cli", "Daemonize failed. Exiting.");
405 // activate config only after daemonization: it starts threads and that is not compatible with fork()
406 if (!ConfigItem::ActivateItems()) {
407 Log(LogCritical, "cli", "Error activating configuration.");
411 if (vm.count("daemonize")) {
413 if (vm.count("errorlog"))
414 errorLog = vm["errorlog"].as<std::string>();
416 SetDaemonIO(errorLog);
417 Logger::DisableConsoleLog();
422 memset(&sa, 0, sizeof(sa));
423 sa.sa_handler = &SigHupHandler;
424 sigaction(SIGHUP, &sa, NULL);
427 return Application::GetInstance()->Run();