]> granicus.if.org Git - icinga2/blob - lib/cli/daemoncommand.cpp
ApiListener#ApiTimerHandler(): delete all replayed logs
[icinga2] / lib / cli / daemoncommand.cpp
1 /******************************************************************************
2  * Icinga 2                                                                   *
3  * Copyright (C) 2012-2018 Icinga Development Team (https://icinga.com/)      *
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 "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"
35 #include "config.h"
36 #include <boost/program_options.hpp>
37 #include <boost/tuple/tuple.hpp>
38 #include <iostream>
39 #include <fstream>
40
41 using namespace icinga;
42 namespace po = boost::program_options;
43
44 static po::variables_map g_AppParams;
45
46 REGISTER_CLICOMMAND("daemon", DaemonCommand);
47
48 #ifndef _WIN32
49 static void SigHupHandler(int)
50 {
51         Application::RequestRestart();
52 }
53 #endif /* _WIN32 */
54
55 /*
56  * Daemonize().  On error, this function logs by itself and exits (i.e. does not return).
57  *
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().
60  */
61 static void Daemonize() noexcept
62 {
63 #ifndef _WIN32
64         try {
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);
69                 exit(EXIT_FAILURE);
70         }
71
72         pid_t pid = fork();
73         if (pid == -1) {
74                 Log(LogCritical, "cli")
75                         << "fork() failed with error code " << errno << ", \"" << Utility::FormatErrorNumber(errno) << "\"";
76                 exit(EXIT_FAILURE);
77         }
78
79         if (pid) {
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.
82
83                 int status;
84                 int ret;
85                 pid_t readpid;
86                 do {
87                         Utility::Sleep(0.1);
88
89                         readpid = Application::ReadPidFile(Configuration::PidPath);
90                         ret = waitpid(pid, &status, WNOHANG);
91                 } while (readpid != pid && ret == 0);
92
93                 if (ret == pid) {
94                         Log(LogCritical, "cli", "The daemon could not be started. See log output for details.");
95                         _exit(EXIT_FAILURE);
96                 } else if (ret == -1) {
97                         Log(LogCritical, "cli")
98                                 << "waitpid() failed with error code " << errno << ", \"" << Utility::FormatErrorNumber(errno) << "\"";
99                         _exit(EXIT_FAILURE);
100                 }
101
102                 _exit(EXIT_SUCCESS);
103         }
104
105         Log(LogDebug, "Daemonize()")
106                 << "Child process with PID " << Utility::GetPid() << " continues; re-initializing base.";
107
108         // Detach from controlling terminal
109         pid_t sid = setsid();
110         if (sid == -1) {
111                 Log(LogCritical, "cli")
112                         << "setsid() failed with error code " << errno << ", \"" << Utility::FormatErrorNumber(errno) << "\"";
113                 exit(EXIT_FAILURE);
114         }
115
116         try {
117                 Application::InitializeBase();
118         } catch (const std::exception& ex) {
119                 Log(LogCritical, "cli")
120                         << "Failed to re-initialize thread pool after daemonizing: " << DiagnosticInformation(ex);
121                 exit(EXIT_FAILURE);
122         }
123 #endif /* _WIN32 */
124 }
125
126 static void CloseStdIO(const String& stderrFile)
127 {
128 #ifndef _WIN32
129         int fdnull = open("/dev/null", O_RDWR);
130         if (fdnull >= 0) {
131                 if (fdnull != 0)
132                         dup2(fdnull, 0);
133
134                 if (fdnull != 1)
135                         dup2(fdnull, 1);
136
137                 if (fdnull > 1)
138                         close(fdnull);
139         }
140
141         const char *errPath = "/dev/null";
142
143         if (!stderrFile.IsEmpty())
144                 errPath = stderrFile.CStr();
145
146         int fderr = open(errPath, O_WRONLY | O_APPEND);
147
148         if (fderr < 0 && errno == ENOENT)
149                 fderr = open(errPath, O_CREAT | O_WRONLY | O_APPEND, 0600);
150
151         if (fderr >= 0) {
152                 if (fderr != 2)
153                         dup2(fderr, 2);
154
155                 if (fderr > 2)
156                         close(fderr);
157         }
158 #endif
159 }
160
161 String DaemonCommand::GetDescription() const
162 {
163         return "Starts Icinga 2.";
164 }
165
166 String DaemonCommand::GetShortDescription() const
167 {
168         return "starts Icinga 2";
169 }
170
171 void DaemonCommand::InitParameters(boost::program_options::options_description& visibleDesc,
172         boost::program_options::options_description& hiddenDesc) const
173 {
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)")
179 #ifndef _WIN32
180                 ("daemonize,d", "detach from the controlling terminal")
181                 ("close-stdio", "do not log to stdout (or stderr) after startup")
182 #endif /* _WIN32 */
183         ;
184
185 #ifndef _WIN32
186         hiddenDesc.add_options()
187                 ("reload-internal", po::value<int>(), "used internally to implement config reload: do not call manually, send SIGHUP instead");
188 #endif /* _WIN32 */
189 }
190
191 std::vector<String> DaemonCommand::GetArgumentSuggestions(const String& argument, const String& word) const
192 {
193         if (argument == "config" || argument == "errorlog")
194                 return GetBashCompletionSuggestions("file", word);
195         else
196                 return CLICommand::GetArgumentSuggestions(argument, word);
197 }
198
199 /**
200  * The entry point for the "daemon" CLI command.
201  *
202  * @returns An exit status.
203  */
204 int DaemonCommand::Run(const po::variables_map& vm, const std::vector<std::string>& ap) const
205 {
206         Logger::EnableTimestamp();
207
208         Log(LogInformation, "cli")
209                 << "Icinga application loader (version: " << Application::GetAppVersion()
210 #ifdef I2_DEBUG
211                 << "; debug"
212 #endif /* I2_DEBUG */
213                 << ")";
214
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;
220                         return EXIT_FAILURE;
221                 }
222         }
223
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");
231         }
232
233         Log(LogInformation, "cli", "Loading configuration file(s).");
234
235         std::vector<ConfigItem::Ptr> newItems;
236
237         if (!DaemonUtility::LoadConfigFiles(configs, newItems, Configuration::ObjectsPath, Configuration::VarsPath))
238                 return EXIT_FAILURE;
239
240         if (vm.count("validate")) {
241                 Log(LogInformation, "cli", "Finished validating the configuration file(s).");
242                 return EXIT_SUCCESS;
243         }
244
245 #ifndef _WIN32
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);
250                 if (rc) {
251                         Log(LogCritical, "cli")
252                                 << "Failed to send signal to \"" << vm["reload-internal"].as<int>() <<  "\" with " << strerror(errno);
253                         return EXIT_FAILURE;
254                 }
255
256                 double start = Utility::GetTime();
257                 while (kill(vm["reload-internal"].as<int>(), SIGCHLD) == 0)
258                         Utility::Sleep(0.2);
259
260                 Log(LogNotice, "cli")
261                         << "Waited for " << Utility::FormatDuration(Utility::GetTime() - start) << " on old process to exit.";
262         }
263 #endif /* _WIN32 */
264
265         if (vm.count("daemonize")) {
266                 if (!vm.count("reload-internal")) {
267                         // no additional fork neccessary on reload
268
269                         // this subroutine either succeeds, or logs an error
270                         // and terminates the process (does not return).
271                         Daemonize();
272                 }
273         }
274
275         /* restore the previous program state */
276         try {
277                 ConfigObject::RestoreObjects(Configuration::StatePath);
278         } catch (const std::exception& ex) {
279                 Log(LogCritical, "cli")
280                         << "Failed to restore state file: " << DiagnosticInformation(ex);
281                 return EXIT_FAILURE;
282         }
283
284         {
285                 WorkQueue upq(25000, Configuration::Concurrency);
286                 upq.SetName("DaemonCommand::Run");
287
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.");
291                         return EXIT_FAILURE;
292                 }
293         }
294
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.");
299
300                 String errorLog;
301                 if (vm.count("errorlog"))
302                         errorLog = vm["errorlog"].as<std::string>();
303
304                 CloseStdIO(errorLog);
305                 Logger::DisableConsoleLog();
306         }
307
308         /* Remove ignored Downtime/Comment objects. */
309         ConfigItem::RemoveIgnoredItems(ConfigObjectUtility::GetConfigDir());
310
311 #ifndef _WIN32
312         struct sigaction sa;
313         memset(&sa, 0, sizeof(sa));
314         sa.sa_handler = &SigHupHandler;
315         sigaction(SIGHUP, &sa, nullptr);
316 #endif /* _WIN32 */
317
318         ApiListener::UpdateObjectAuthority();
319
320         return Application::GetInstance()->Run();
321 }