]> granicus.if.org Git - icinga2/blob - lib/cli/daemoncommand.cpp
add some object locking to the Dump method (which could theoreticylly suffer from...
[icinga2] / lib / cli / daemoncommand.cpp
1 /* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
2
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"
18 #include "config.h"
19 #include <boost/program_options.hpp>
20 #include <boost/tuple/tuple.hpp>
21 #include <iostream>
22 #include <fstream>
23
24 using namespace icinga;
25 namespace po = boost::program_options;
26
27 static po::variables_map g_AppParams;
28
29 REGISTER_CLICOMMAND("daemon", DaemonCommand);
30
31 #ifndef _WIN32
32 static void SigHupHandler(int)
33 {
34         Application::RequestRestart();
35 }
36 #endif /* _WIN32 */
37
38 /*
39  * Daemonize().  On error, this function logs by itself and exits (i.e. does not return).
40  *
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().
43  */
44 static void Daemonize() noexcept
45 {
46 #ifndef _WIN32
47         try {
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);
52                 exit(EXIT_FAILURE);
53         }
54
55         pid_t pid = fork();
56         if (pid == -1) {
57                 Log(LogCritical, "cli")
58                         << "fork() failed with error code " << errno << ", \"" << Utility::FormatErrorNumber(errno) << "\"";
59                 exit(EXIT_FAILURE);
60         }
61
62         if (pid) {
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.
65
66                 int status;
67                 int ret;
68                 pid_t readpid;
69                 do {
70                         Utility::Sleep(0.1);
71
72                         readpid = Application::ReadPidFile(Configuration::PidPath);
73                         ret = waitpid(pid, &status, WNOHANG);
74                 } while (readpid != pid && ret == 0);
75
76                 if (ret == pid) {
77                         Log(LogCritical, "cli", "The daemon could not be started. See log output for details.");
78                         _exit(EXIT_FAILURE);
79                 } else if (ret == -1) {
80                         Log(LogCritical, "cli")
81                                 << "waitpid() failed with error code " << errno << ", \"" << Utility::FormatErrorNumber(errno) << "\"";
82                         _exit(EXIT_FAILURE);
83                 }
84
85                 _exit(EXIT_SUCCESS);
86         }
87
88         Log(LogDebug, "Daemonize()")
89                 << "Child process with PID " << Utility::GetPid() << " continues; re-initializing base.";
90
91         // Detach from controlling terminal
92         pid_t sid = setsid();
93         if (sid == -1) {
94                 Log(LogCritical, "cli")
95                         << "setsid() failed with error code " << errno << ", \"" << Utility::FormatErrorNumber(errno) << "\"";
96                 exit(EXIT_FAILURE);
97         }
98
99         try {
100                 Application::InitializeBase();
101         } catch (const std::exception& ex) {
102                 Log(LogCritical, "cli")
103                         << "Failed to re-initialize thread pool after daemonizing: " << DiagnosticInformation(ex);
104                 exit(EXIT_FAILURE);
105         }
106 #endif /* _WIN32 */
107 }
108
109 static void CloseStdIO(const String& stderrFile)
110 {
111 #ifndef _WIN32
112         int fdnull = open("/dev/null", O_RDWR);
113         if (fdnull >= 0) {
114                 if (fdnull != 0)
115                         dup2(fdnull, 0);
116
117                 if (fdnull != 1)
118                         dup2(fdnull, 1);
119
120                 if (fdnull > 1)
121                         close(fdnull);
122         }
123
124         const char *errPath = "/dev/null";
125
126         if (!stderrFile.IsEmpty())
127                 errPath = stderrFile.CStr();
128
129         int fderr = open(errPath, O_WRONLY | O_APPEND);
130
131         if (fderr < 0 && errno == ENOENT)
132                 fderr = open(errPath, O_CREAT | O_WRONLY | O_APPEND, 0600);
133
134         if (fderr >= 0) {
135                 if (fderr != 2)
136                         dup2(fderr, 2);
137
138                 if (fderr > 2)
139                         close(fderr);
140         }
141 #endif
142 }
143
144 String DaemonCommand::GetDescription() const
145 {
146         return "Starts Icinga 2.";
147 }
148
149 String DaemonCommand::GetShortDescription() const
150 {
151         return "starts Icinga 2";
152 }
153
154 void DaemonCommand::InitParameters(boost::program_options::options_description& visibleDesc,
155         boost::program_options::options_description& hiddenDesc) const
156 {
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)")
162 #ifndef _WIN32
163                 ("daemonize,d", "detach from the controlling terminal")
164                 ("close-stdio", "do not log to stdout (or stderr) after startup")
165 #endif /* _WIN32 */
166         ;
167
168 #ifndef _WIN32
169         hiddenDesc.add_options()
170                 ("reload-internal", po::value<int>(), "used internally to implement config reload: do not call manually, send SIGHUP instead");
171 #endif /* _WIN32 */
172 }
173
174 std::vector<String> DaemonCommand::GetArgumentSuggestions(const String& argument, const String& word) const
175 {
176         if (argument == "config" || argument == "errorlog")
177                 return GetBashCompletionSuggestions("file", word);
178         else
179                 return CLICommand::GetArgumentSuggestions(argument, word);
180 }
181
182 /**
183  * The entry point for the "daemon" CLI command.
184  *
185  * @returns An exit status.
186  */
187 int DaemonCommand::Run(const po::variables_map& vm, const std::vector<std::string>& ap) const
188 {
189         Logger::EnableTimestamp();
190
191         Log(LogInformation, "cli")
192                 << "Icinga application loader (version: " << Application::GetAppVersion()
193 #ifdef I2_DEBUG
194                 << "; debug"
195 #endif /* I2_DEBUG */
196                 << ")";
197
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;
203                         return EXIT_FAILURE;
204                 }
205         }
206
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");
214         }
215
216         Log(LogInformation, "cli", "Loading configuration file(s).");
217
218         std::vector<ConfigItem::Ptr> newItems;
219
220         if (!DaemonUtility::LoadConfigFiles(configs, newItems, Configuration::ObjectsPath, Configuration::VarsPath))
221                 return EXIT_FAILURE;
222
223         if (vm.count("validate")) {
224                 Log(LogInformation, "cli", "Finished validating the configuration file(s).");
225                 return EXIT_SUCCESS;
226         }
227
228 #ifndef _WIN32
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);
233                 if (rc) {
234                         Log(LogCritical, "cli")
235                                 << "Failed to send signal to \"" << vm["reload-internal"].as<int>() <<  "\" with " << strerror(errno);
236                         return EXIT_FAILURE;
237                 }
238
239                 double start = Utility::GetTime();
240                 while (kill(vm["reload-internal"].as<int>(), SIGCHLD) == 0)
241                         Utility::Sleep(0.2);
242
243                 Log(LogNotice, "cli")
244                         << "Waited for " << Utility::FormatDuration(Utility::GetTime() - start) << " on old process to exit.";
245         }
246 #endif /* _WIN32 */
247
248         if (vm.count("daemonize")) {
249                 if (!vm.count("reload-internal")) {
250                         // no additional fork neccessary on reload
251
252                         // this subroutine either succeeds, or logs an error
253                         // and terminates the process (does not return).
254                         Daemonize();
255                 }
256         }
257
258         /* restore the previous program state */
259         try {
260                 ConfigObject::RestoreObjects(Configuration::StatePath);
261         } catch (const std::exception& ex) {
262                 Log(LogCritical, "cli")
263                         << "Failed to restore state file: " << DiagnosticInformation(ex);
264                 return EXIT_FAILURE;
265         }
266
267         {
268                 WorkQueue upq(25000, Configuration::Concurrency);
269                 upq.SetName("DaemonCommand::Run");
270
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.");
274                         return EXIT_FAILURE;
275                 }
276         }
277
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.");
282
283                 String errorLog;
284                 if (vm.count("errorlog"))
285                         errorLog = vm["errorlog"].as<std::string>();
286
287                 CloseStdIO(errorLog);
288                 Logger::DisableConsoleLog();
289         }
290
291         /* Remove ignored Downtime/Comment objects. */
292         ConfigItem::RemoveIgnoredItems(ConfigObjectUtility::GetConfigDir());
293
294 #ifndef _WIN32
295         struct sigaction sa;
296         memset(&sa, 0, sizeof(sa));
297         sa.sa_handler = &SigHupHandler;
298         sigaction(SIGHUP, &sa, nullptr);
299 #endif /* _WIN32 */
300
301         ApiListener::UpdateObjectAuthority();
302
303         return Application::GetInstance()->Run();
304 }