Merge branch 'collectd-5.5'
[collectd.git] / src / collectdmon.c
1 /**
2  * collectd - src/collectdmon.c
3  * Copyright (C) 2007       Sebastian Harl
4  *
5  * Permission is hereby granted, free of charge, to any person obtaining a
6  * copy of this software and associated documentation files (the "Software"),
7  * to deal in the Software without restriction, including without limitation
8  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
9  * and/or sell copies of the Software, and to permit persons to whom the
10  * Software is furnished to do so, subject to the following conditions:
11  *
12  * The above copyright notice and this permission notice shall be included in
13  * all copies or substantial portions of the Software.
14  *
15  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
21  * DEALINGS IN THE SOFTWARE.
22  *
23  * Authors:
24  *   Sebastian Harl <sh at tokkee.org>
25  **/
26
27 #if !defined(__GNUC__) || !__GNUC__
28 # define __attribute__(x) /**/
29 #endif
30
31 #include "config.h"
32
33 #include <assert.h>
34
35 #include <errno.h>
36
37 #include <fcntl.h>
38
39 #include <signal.h>
40
41 #include <stdio.h>
42 #include <stdlib.h>
43
44 #include <string.h>
45
46 #include <syslog.h>
47
48 #include <sys/resource.h>
49 #include <sys/time.h>
50 #include <sys/types.h>
51 #include <sys/stat.h>
52 #include <sys/wait.h>
53
54 #include <time.h>
55
56 #include <unistd.h>
57
58 #ifndef COLLECTDMON_PIDFILE
59 # define COLLECTDMON_PIDFILE LOCALSTATEDIR"/run/collectdmon.pid"
60 #endif /* ! COLLECTDMON_PIDFILE */
61
62 #ifndef WCOREDUMP
63 # define WCOREDUMP(s) 0
64 #endif /* ! WCOREDUMP */
65
66 static int loop    = 0;
67 static int restart = 0;
68
69 static const char *pidfile      = NULL;
70 static pid_t       collectd_pid = 0;
71
72 __attribute__((noreturn))
73 static void exit_usage (const char *name)
74 {
75         printf ("Usage: %s <options> [-- <collectd options>]\n"
76
77                         "\nAvailable options:\n"
78                         "  -h         Display this help and exit.\n"
79                         "  -c <path>  Path to the collectd binary.\n"
80                         "  -P <file>  PID-file.\n"
81
82                         "\nFor <collectd options> see collectd.conf(5).\n"
83
84                         "\n"PACKAGE_NAME" "PACKAGE_VERSION", http://collectd.org/\n"
85                         "by Florian octo Forster <octo@collectd.org>\n"
86                         "for contributions see `AUTHORS'\n", name);
87         exit (0);
88 } /* exit_usage */
89
90 static int pidfile_create (void)
91 {
92         FILE *file = NULL;
93
94         if (NULL == pidfile)
95                 pidfile = COLLECTDMON_PIDFILE;
96
97         if (NULL == (file = fopen (pidfile, "w"))) {
98                 syslog (LOG_ERR, "Error: couldn't open PID-file (%s) for writing: %s",
99                                 pidfile, strerror (errno));
100                 return -1;
101         }
102
103         fprintf (file, "%i\n", (int)getpid ());
104         fclose (file);
105         return 0;
106 } /* pidfile_create */
107
108 static int pidfile_delete (void)
109 {
110         assert (NULL != pidfile);
111
112         if (0 != unlink (pidfile)) {
113                 syslog (LOG_ERR, "Error: couldn't delete PID-file (%s): %s",
114                                 pidfile, strerror (errno));
115                 return -1;
116         }
117         return 0;
118 } /* pidfile_remove */
119
120 static int daemonize (void)
121 {
122         struct rlimit rl;
123         int dev_null;
124
125         pid_t pid = 0;
126         int   i   = 0;
127
128         if (0 != chdir ("/")) {
129                 fprintf (stderr, "Error: chdir() failed: %s\n", strerror (errno));
130                 return -1;
131         }
132
133         if (0 != getrlimit (RLIMIT_NOFILE, &rl)) {
134                 fprintf (stderr, "Error: getrlimit() failed: %s\n", strerror (errno));
135                 return -1;
136         }
137
138         if (0 > (pid = fork ())) {
139                 fprintf (stderr, "Error: fork() failed: %s\n", strerror (errno));
140                 return -1;
141         }
142         else if (pid != 0) {
143                 exit (0);
144         }
145
146         if (0 != pidfile_create ())
147                 return -1;
148
149         setsid ();
150
151         if (RLIM_INFINITY == rl.rlim_max)
152                 rl.rlim_max = 1024;
153
154         for (i = 0; i < (int)rl.rlim_max; ++i)
155                 close (i);
156
157         dev_null = open ("/dev/null", O_RDWR);
158         if (dev_null == -1) {
159                 syslog (LOG_ERR, "Error: couldn't open /dev/null: %s", strerror (errno));
160                 return -1;
161         }
162
163         if (dup2 (dev_null, STDIN_FILENO) == -1) {
164                 close (dev_null);
165                 syslog (LOG_ERR, "Error: couldn't connect STDIN to /dev/null: %s", strerror (errno));
166                 return -1;
167         }
168
169         if (dup2 (dev_null, STDOUT_FILENO) == -1) {
170                 close (dev_null);
171                 syslog (LOG_ERR, "Error: couldn't connect STDOUT to /dev/null: %s", strerror (errno));
172                 return -1;
173         }
174
175         if (dup2 (dev_null, STDERR_FILENO) == -1) {
176                 close (dev_null);
177                 syslog (LOG_ERR, "Error: couldn't connect STDERR to /dev/null: %s", strerror (errno));
178                 return -1;
179         }
180
181         if ((dev_null != STDIN_FILENO) && (dev_null != STDOUT_FILENO) && (dev_null != STDERR_FILENO))
182                 close (dev_null);
183
184         return 0;
185 } /* daemonize */
186
187 static int collectd_start (char **argv)
188 {
189         pid_t pid = 0;
190
191         if (0 > (pid = fork ())) {
192                 syslog (LOG_ERR, "Error: fork() failed: %s", strerror (errno));
193                 return -1;
194         }
195         else if (pid != 0) {
196                 collectd_pid = pid;
197                 return 0;
198         }
199
200         execvp (argv[0], argv);
201         syslog (LOG_ERR, "Error: execvp(%s) failed: %s",
202                         argv[0], strerror (errno));
203         exit (-1);
204 } /* collectd_start */
205
206 static int collectd_stop (void)
207 {
208         if (0 == collectd_pid)
209                 return 0;
210
211         if (0 != kill (collectd_pid, SIGTERM)) {
212                 syslog (LOG_ERR, "Error: kill() failed: %s", strerror (errno));
213                 return -1;
214         }
215         return 0;
216 } /* collectd_stop */
217
218 static void sig_int_term_handler (int __attribute__((unused)) signo)
219 {
220         ++loop;
221         return;
222 } /* sig_int_term_handler */
223
224 static void sig_hup_handler (int __attribute__((unused)) signo)
225 {
226         ++restart;
227         return;
228 } /* sig_hup_handler */
229
230 static void log_status (int status)
231 {
232         if (WIFEXITED (status)) {
233                 if (0 == WEXITSTATUS (status))
234                         syslog (LOG_INFO, "Info: collectd terminated with exit status %i",
235                                         WEXITSTATUS (status));
236                 else
237                         syslog (LOG_WARNING,
238                                         "Warning: collectd terminated with exit status %i",
239                                         WEXITSTATUS (status));
240         }
241         else if (WIFSIGNALED (status)) {
242                 syslog (LOG_WARNING, "Warning: collectd was terminated by signal %i%s",
243                                 WTERMSIG (status), WCOREDUMP (status) ? " (core dumped)" : "");
244         }
245         return;
246 } /* log_status */
247
248 static void check_respawn (void)
249 {
250         time_t t = time (NULL);
251
252         static time_t timestamp = 0;
253         static int    counter   = 0;
254
255         if ((t - 120) < timestamp)
256                 ++counter;
257         else {
258                 timestamp = t;
259                 counter   = 0;
260         }
261
262         if (10 < counter) {
263                 unsigned int time_left = 300;
264
265                 syslog (LOG_ERR, "Error: collectd is respawning too fast - "
266                                 "disabled for %i seconds", time_left);
267
268                 while ((0 < (time_left = sleep (time_left))) && (0 == loop));
269         }
270         return;
271 } /* check_respawn */
272
273 int main (int argc, char **argv)
274 {
275         int    collectd_argc = 0;
276         char  *collectd      = NULL;
277         char **collectd_argv = NULL;
278
279         struct sigaction sa;
280
281         int i = 0;
282
283         /* parse command line options */
284         while (42) {
285                 int c = getopt (argc, argv, "hc:P:");
286
287                 if (-1 == c)
288                         break;
289
290                 switch (c) {
291                         case 'c':
292                                 collectd = optarg;
293                                 break;
294                         case 'P':
295                                 pidfile = optarg;
296                                 break;
297                         case 'h':
298                         default:
299                                 exit_usage (argv[0]);
300                 }
301         }
302
303         for (i = optind; i < argc; ++i)
304                 if (0 == strcmp (argv[i], "-f"))
305                         break;
306
307         /* i < argc => -f already present */
308         collectd_argc = 1 + argc - optind + ((i < argc) ? 0 : 1);
309         collectd_argv = (char **)calloc (collectd_argc + 1, sizeof (char *));
310
311         if (NULL == collectd_argv) {
312                 fprintf (stderr, "Out of memory.");
313                 return 3;
314         }
315
316         collectd_argv[0] = (NULL == collectd) ? "collectd" : collectd;
317
318         if (i == argc)
319                 collectd_argv[collectd_argc - 1] = "-f";
320
321         for (i = optind; i < argc; ++i)
322                 collectd_argv[i - optind + 1] = argv[i];
323
324         collectd_argv[collectd_argc] = NULL;
325
326         openlog ("collectdmon", LOG_CONS | LOG_PID, LOG_DAEMON);
327
328         if (-1 == daemonize ())
329         {
330                 free (collectd_argv);
331                 return 1;
332         }
333
334         sa.sa_handler = sig_int_term_handler;
335         sa.sa_flags   = 0;
336         sigemptyset (&sa.sa_mask);
337
338         if (0 != sigaction (SIGINT, &sa, NULL)) {
339                 syslog (LOG_ERR, "Error: sigaction() failed: %s", strerror (errno));
340                 free (collectd_argv);
341                 return 1;
342         }
343
344         if (0 != sigaction (SIGTERM, &sa, NULL)) {
345                 syslog (LOG_ERR, "Error: sigaction() failed: %s", strerror (errno));
346                 free (collectd_argv);
347                 return 1;
348         }
349
350         sa.sa_handler = sig_hup_handler;
351
352         if (0 != sigaction (SIGHUP, &sa, NULL)) {
353                 syslog (LOG_ERR, "Error: sigaction() failed: %s", strerror (errno));
354                 free (collectd_argv);
355                 return 1;
356         }
357
358         while (0 == loop) {
359                 int status = 0;
360
361                 if (0 != collectd_start (collectd_argv)) {
362                         syslog (LOG_ERR, "Error: failed to start collectd.");
363                         break;
364                 }
365
366                 assert (0 < collectd_pid);
367                 while ((collectd_pid != waitpid (collectd_pid, &status, 0))
368                                 && (EINTR == errno))
369                         if ((0 != loop) || (0 != restart))
370                                 collectd_stop ();
371
372                 collectd_pid = 0;
373
374                 log_status (status);
375                 check_respawn ();
376
377                 if (0 != restart) {
378                         syslog (LOG_INFO, "Info: restarting collectd");
379                         restart = 0;
380                 }
381                 else if (0 == loop)
382                         syslog (LOG_WARNING, "Warning: restarting collectd");
383         }
384
385         syslog (LOG_INFO, "Info: shutting down collectdmon");
386
387         pidfile_delete ();
388         closelog ();
389
390         free (collectd_argv);
391         return 0;
392 } /* main */
393
394 /* vim: set sw=4 ts=4 tw=78 noexpandtab : */
395