]> granicus.if.org Git - icinga2/blob - lib/cli/daemoncommand.cpp
Implement more unit tests
[icinga2] / lib / cli / daemoncommand.cpp
1 /******************************************************************************
2  * Icinga 2                                                                   *
3  * Copyright (C) 2012-2014 Icinga Development Team (http://www.icinga.org)    *
4  *                                                                            *
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.                     *
9  *                                                                            *
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.                               *
14  *                                                                            *
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  ******************************************************************************/
19
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"
34 #include "config.h"
35 #include <boost/program_options.hpp>
36 #include <boost/tuple/tuple.hpp>
37 #include <boost/foreach.hpp>
38 #include <iostream>
39
40 using namespace icinga;
41 namespace po = boost::program_options;
42
43 static po::variables_map g_AppParams;
44
45 REGISTER_CLICOMMAND("daemon", DaemonCommand);
46 REGISTER_SCRIPTSIGNAL(onload);
47
48 static String LoadAppType(const String& typeSpec)
49 {
50         Log(LogInformation, "cli")
51             << "Loading application type: " << typeSpec;
52
53         String::SizeType index = typeSpec.FindFirstOf('/');
54
55         if (index == String::NPos)
56                 return typeSpec;
57
58         String library = typeSpec.SubStr(0, index);
59
60         (void) Utility::LoadExtensionLibrary(library);
61
62         return typeSpec.SubStr(index + 1);
63 }
64
65 static void ExecuteExpression(Expression *expression)
66 {
67         try {
68                 VMFrame frame;
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));
75         }
76 }
77
78 static void IncludeZoneDirRecursive(const String& path)
79 {
80         String zoneName = Utility::BaseName(path);
81
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);
86 }
87
88 static void IncludeNonLocalZone(const String& zonePath)
89 {
90         String etcPath = Application::GetZonesDir() + "/" + Utility::BaseName(zonePath);
91
92         if (Utility::PathExists(etcPath))
93                 return;
94
95         IncludeZoneDirRecursive(zonePath);
96 }
97
98 static bool LoadConfigFiles(const boost::program_options::variables_map& vm, const String& appType,
99     const String& objectsFile = String(), const String& varsfile = String())
100 {
101         ConfigCompilerContext::GetInstance()->Reset();
102
103         if (!objectsFile.IsEmpty())
104                 ConfigCompilerContext::GetInstance()->OpenObjectsFile(objectsFile);
105
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);
109                         if (expression)
110                                 ExecuteExpression(expression);
111                         delete expression;
112                 }
113         } else if (!vm.count("no-config")) {
114                 Expression *expression = ConfigCompiler::CompileFile(Application::GetSysconfDir() + "/icinga2/icinga2.conf");
115                 if (expression)
116                         ExecuteExpression(expression);
117                 delete expression;
118         }
119
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);
125
126         String zonesVarDir = Application::GetLocalStateDir() + "/lib/icinga2/api/zones";
127         if (Utility::PathExists(zonesVarDir))
128                 Utility::Glob(zonesVarDir + "/*", &IncludeNonLocalZone, GlobDirectory);
129
130         String name, fragment;
131         BOOST_FOREACH(boost::tie(name, fragment), ConfigFragmentRegistry::GetInstance()->GetItems()) {
132                 Expression *expression = ConfigCompiler::CompileText(name, fragment);
133                 if (expression)
134                         ExecuteExpression(expression);
135                 delete expression;
136         }
137
138         ConfigItemBuilder::Ptr builder = new ConfigItemBuilder();
139         builder->SetType(appType);
140         builder->SetName("application");
141         ConfigItem::Ptr item = builder->Compile();
142         item->Register();
143
144         bool result = ConfigItem::ValidateItems();
145
146         int warnings = 0, errors = 0;
147
148         BOOST_FOREACH(const ConfigCompilerMessage& message, ConfigCompilerContext::GetInstance()->GetMessages()) {
149                 std::ostringstream locbuf;
150                 ShowCodeFragment(locbuf, message.Location, true);
151                 String location = locbuf.str();
152
153                 String logmsg;
154
155                 if (!location.IsEmpty())
156                         logmsg = "Location:\n" + location;
157
158                 logmsg += String("\nConfig ") + (message.Error ? "error" : "warning") + ": " + message.Text;
159
160                 if (message.Error) {
161                         Log(LogCritical, "config", logmsg);
162                         errors++;
163                 } else {
164                         Log(LogWarning, "config", logmsg);
165                         warnings++;
166                 }
167         }
168
169         if (warnings > 0 || errors > 0) {
170                 LogSeverity severity;
171
172                 if (errors == 0)
173                         severity = LogWarning;
174                 else
175                         severity = LogCritical;
176
177                 Log(severity, "config")
178                     << errors << " errors, " << warnings << " warnings.";
179         }
180
181         if (!result)
182                 return false;
183
184         ConfigCompilerContext::GetInstance()->FinishObjectsFile();
185
186         ScriptVariable::WriteVariablesFile(varsfile);
187
188         ScriptSignal::Ptr loadSignal = ScriptSignal::GetByName("onload");
189         loadSignal->Invoke();
190
191         return true;
192 }
193
194 #ifndef _WIN32
195 static void SigHupHandler(int)
196 {
197         Application::RequestRestart();
198 }
199 #endif /* _WIN32 */
200
201 static bool Daemonize(void)
202 {
203 #ifndef _WIN32
204         pid_t pid = fork();
205         if (pid == -1) {
206                 return false;
207         }
208
209         if (pid) {
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.
212
213                 int status;
214                 int ret;
215                 pid_t readpid;
216                 do {
217                         Utility::Sleep(0.1);
218
219                         readpid = Application::ReadPidFile(Application::GetPidPath());
220                         ret = waitpid(pid, &status, WNOHANG);
221                 } while (readpid != pid && ret == 0);
222
223                 if (ret == pid) {
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);
230                 }
231
232                 Application::Exit(0);
233         }
234 #endif /* _WIN32 */
235
236         return true;
237 }
238
239 static bool SetDaemonIO(const String& stderrFile)
240 {
241 #ifndef _WIN32
242         int fdnull = open("/dev/null", O_RDWR);
243         if (fdnull >= 0) {
244                 if (fdnull != 0)
245                         dup2(fdnull, 0);
246
247                 if (fdnull != 1)
248                         dup2(fdnull, 1);
249
250                 if (fdnull > 1)
251                         close(fdnull);
252         }
253
254         const char *errPath = "/dev/null";
255
256         if (!stderrFile.IsEmpty())
257                 errPath = stderrFile.CStr();
258
259         int fderr = open(errPath, O_WRONLY | O_APPEND);
260
261         if (fderr < 0 && errno == ENOENT)
262                 fderr = open(errPath, O_CREAT | O_WRONLY | O_APPEND, 0600);
263
264         if (fderr > 0) {
265                 if (fderr != 2)
266                         dup2(fderr, 2);
267
268                 if (fderr > 2)
269                         close(fderr);
270         }
271
272         pid_t sid = setsid();
273         if (sid == -1) {
274                 return false;
275         }
276 #endif
277
278         return true;
279 }
280
281 /**
282  * Terminate another process and wait till it has ended
283  *
284  * @params target PID of the process to end
285  */
286 static void TerminateAndWaitForEnd(pid_t target)
287 {
288 #ifndef _WIN32
289         // allow 30 seconds timeout
290         double timeout = Utility::GetTime() + 30;
291
292         int ret = kill(target, SIGTERM);
293
294         while (Utility::GetTime() < timeout && (ret == 0 || errno != ESRCH)) {
295                 Utility::Sleep(0.1);
296                 ret = kill(target, 0);
297         }
298
299         // timeout and the process still seems to live: kill it
300         if (ret == 0 || errno != ESRCH)
301                 kill(target, SIGKILL);
302
303 #else
304         // TODO: implement this for Win32
305 #endif /* _WIN32 */
306 }
307
308 String DaemonCommand::GetDescription(void) const
309 {
310         return "Starts Icinga 2.";
311 }
312
313 String DaemonCommand::GetShortDescription(void) const
314 {
315         return "starts Icinga 2";
316 }
317
318 void DaemonCommand::InitParameters(boost::program_options::options_description& visibleDesc,
319     boost::program_options::options_description& hiddenDesc) const
320 {
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)")
326 #ifndef _WIN32
327                 ("daemonize,d", "detach from the controlling terminal")
328 #endif /* _WIN32 */
329         ;
330
331 #ifndef _WIN32
332         hiddenDesc.add_options()
333                 ("reload-internal", po::value<int>(), "used internally to implement config reload: do not call manually, send SIGHUP instead");
334 #endif /* _WIN32 */
335 }
336
337 std::vector<String> DaemonCommand::GetArgumentSuggestions(const String& argument, const String& word) const
338 {
339         if (argument == "config" || argument == "errorlog")
340                 return GetBashCompletionSuggestions("file", word);
341         else
342                 return CLICommand::GetArgumentSuggestions(argument, word);
343 }
344
345 /**
346  * The entry point for the "daemon" CLI command.
347  *
348  * @returns An exit status.
349  */
350 int DaemonCommand::Run(const po::variables_map& vm, const std::vector<std::string>& ap) const
351 {
352         if (!vm.count("validate"))
353                 Logger::DisableTimestamp(false);
354
355         ScriptVariable::Set("UseVfork", true, false, true);
356
357         Application::MakeVariablesConstant();
358
359         Log(LogInformation, "cli")
360             << "Icinga application loader (version: " << Application::GetVersion()
361 #ifdef _DEBUG
362             << "; debug"
363 #endif /* _DEBUG */
364             << ")";
365
366         String appType = LoadAppType(Application::GetApplicationType());
367
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;
373                         return EXIT_FAILURE;
374                 }
375         }
376
377         if (!LoadConfigFiles(vm, appType, Application::GetObjectsPath(), Application::GetVarsPath()))
378                 return EXIT_FAILURE;
379
380         if (vm.count("validate")) {
381                 Log(LogInformation, "cli", "Finished validating the configuration file(s).");
382                 return EXIT_SUCCESS;
383         }
384
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.");
391         }
392
393         if (vm.count("daemonize")) {
394                 if (!vm.count("reload-internal")) {
395                         // no additional fork neccessary on reload
396                         try {
397                                 Daemonize();
398                         } catch (std::exception&) {
399                                 Log(LogCritical, "cli", "Daemonize failed. Exiting.");
400                                 return EXIT_FAILURE;
401                         }
402                 }
403         }
404
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.");
408                 return EXIT_FAILURE;
409         }
410
411         if (vm.count("daemonize")) {
412                 String errorLog;
413                 if (vm.count("errorlog"))
414                         errorLog = vm["errorlog"].as<std::string>();
415
416                 SetDaemonIO(errorLog);
417                 Logger::DisableConsoleLog();
418         }
419
420 #ifndef _WIN32
421         struct sigaction sa;
422         memset(&sa, 0, sizeof(sa));
423         sa.sa_handler = &SigHupHandler;
424         sigaction(SIGHUP, &sa, NULL);
425 #endif /* _WIN32 */
426
427         return Application::GetInstance()->Run();
428 }