Merge branch 'collectd-4.2'
[collectd.git] / src / collectdmon.c
1 /**
2  * collectd - src/collectdmon.c
3  * Copyright (C) 2007  Sebastian Harl
4  *
5  * This program is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License as published by the
7  * Free Software Foundation; only version 2 of the License is applicable.
8  *
9  * This program is distributed in the hope that it will be useful, but
10  * WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License along
15  * with this program; if not, write to the Free Software Foundation, Inc.,
16  * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
17  *
18  * Author:
19  *   Sebastian Harl <sh at tokkee.org>
20  **/
21
22 #include "config.h"
23
24 #include <assert.h>
25
26 #include <errno.h>
27
28 #include <fcntl.h>
29
30 #include <signal.h>
31
32 #include <stdio.h>
33 #include <stdlib.h>
34
35 #include <string.h>
36
37 #include <syslog.h>
38
39 #include <sys/resource.h>
40 #include <sys/time.h>
41 #include <sys/types.h>
42 #include <sys/stat.h>
43 #include <sys/wait.h>
44
45 #include <time.h>
46
47 #include <unistd.h>
48
49 #ifndef COLLECTDMON_PIDFILE
50 # define COLLECTDMON_PIDFILE LOCALSTATEDIR"/run/collectdmon.pid"
51 #endif /* ! COLLECTDMON_PIDFILE */
52
53 #ifndef WCOREDUMP
54 # define WCOREDUMP(s) 0
55 #endif /* ! WCOREDUMP */
56
57 static int loop    = 0;
58 static int restart = 0;
59
60 static char  *pidfile      = NULL;
61 static pid_t  collectd_pid = 0;
62
63 static void exit_usage (char *name)
64 {
65         printf ("Usage: %s <options> [-- <collectd options>]\n"
66
67                         "\nAvailable options:\n"
68                         "  -h         Display this help and exit.\n"
69                         "  -c <path>  Path to the collectd binary.\n"
70                         "  -P <file>  PID-file.\n"
71
72                         "\nFor <collectd options> see collectd.conf(5).\n"
73
74                         "\n"PACKAGE" "VERSION", http://collectd.org/\n"
75                         "by Florian octo Forster <octo@verplant.org>\n"
76                         "for contributions see `AUTHORS'\n", name);
77         exit (0);
78 } /* exit_usage */
79
80 static int pidfile_create (void)
81 {
82         FILE *file = NULL;
83
84         if (NULL == pidfile)
85                 pidfile = COLLECTDMON_PIDFILE;
86
87         if (NULL == (file = fopen (pidfile, "w"))) {
88                 syslog (LOG_ERR, "Error: couldn't open PID-file (%s) for writing: %s",
89                                 pidfile, strerror (errno));
90                 return -1;
91         }
92
93         fprintf (file, "%i\n", (int)getpid ());
94         fclose (file);
95         return 0;
96 } /* pidfile_create */
97
98 static int pidfile_delete (void)
99 {
100         assert (NULL != pidfile);
101
102         if (0 != unlink (pidfile)) {
103                 syslog (LOG_ERR, "Error: couldn't delete PID-file (%s): %s",
104                                 pidfile, strerror (errno));
105                 return -1;
106         }
107         return 0;
108 } /* pidfile_remove */
109
110 static int daemonize (void)
111 {
112         struct rlimit rl;
113
114         pid_t pid = 0;
115         int   i   = 0;
116
117         if (0 != chdir ("/")) {
118                 fprintf (stderr, "Error: chdir() failed: %s\n", strerror (errno));
119                 return -1;
120         }
121
122         if (0 != getrlimit (RLIMIT_NOFILE, &rl)) {
123                 fprintf (stderr, "Error: getrlimit() failed: %s\n", strerror (errno));
124                 return -1;
125         }
126
127         if (0 > (pid = fork ())) {
128                 fprintf (stderr, "Error: fork() failed: %s\n", strerror (errno));
129                 return -1;
130         }
131         else if (pid != 0) {
132                 exit (0);
133         }
134
135         if (0 != pidfile_create ())
136                 return -1;
137
138         setsid ();
139
140         if (RLIM_INFINITY == rl.rlim_max)
141                 rl.rlim_max = 1024;
142
143         for (i = 0; i < rl.rlim_max; ++i)
144                 close (i);
145
146         errno = 0;
147         if (open ("/dev/null", O_RDWR) != 0) {
148                 syslog (LOG_ERR, "Error: couldn't connect STDIN to /dev/null: %s",
149                                 strerror (errno));
150                 return -1;
151         }
152
153         errno = 0;
154         if (dup (0) != 1) {
155                 syslog (LOG_ERR, "Error: couldn't connect STDOUT to /dev/null: %s",
156                                 strerror (errno));
157                 return -1;
158         }
159
160         errno = 0;
161         if (dup (0) != 2) {
162                 syslog (LOG_ERR, "Error: couldn't connect STDERR to /dev/null: %s",
163                                 strerror (errno));
164                 return -1;
165         }
166         return 0;
167 } /* daemonize */
168
169 static int collectd_start (int argc, char **argv)
170 {
171         pid_t pid = 0;
172
173         if (0 > (pid = fork ())) {
174                 syslog (LOG_ERR, "Error: fork() failed: %s", strerror (errno));
175                 return -1;
176         }
177         else if (pid != 0) {
178                 collectd_pid = pid;
179                 return 0;
180         }
181
182         execvp (argv[0], argv);
183         syslog (LOG_ERR, "Error: execvp(%s) failed: %s",
184                         argv[0], strerror (errno));
185         exit (-1);
186 } /* collectd_start */
187
188 static int collectd_stop (void)
189 {
190         if (0 == collectd_pid)
191                 return 0;
192
193         if (0 != kill (collectd_pid, SIGTERM)) {
194                 syslog (LOG_ERR, "Error: kill() failed: %s", strerror (errno));
195                 return -1;
196         }
197         return 0;
198 } /* collectd_stop */
199
200 static void sig_int_term_handler (int signo)
201 {
202         ++loop;
203         return;
204 } /* sig_int_term_handler */
205
206 static void sig_hup_handler (int signo)
207 {
208         ++restart;
209         return;
210 } /* sig_hup_handler */
211
212 static void log_status (int status)
213 {
214         if (WIFEXITED (status)) {
215                 if (0 == WEXITSTATUS (status))
216                         syslog (LOG_INFO, "Info: collectd terminated with exit status %i",
217                                         WEXITSTATUS (status));
218                 else
219                         syslog (LOG_WARNING,
220                                         "Warning: collectd terminated with exit status %i",
221                                         WEXITSTATUS (status));
222         }
223         else if (WIFSIGNALED (status)) {
224                 syslog (LOG_WARNING, "Warning: collectd was terminated by signal %i%s",
225                                 WTERMSIG (status), WCOREDUMP (status) ? " (core dumped)" : "");
226         }
227         return;
228 } /* log_status */
229
230 static void check_respawn (void)
231 {
232         time_t t = time (NULL);
233
234         static time_t timestamp = 0;
235         static int    counter   = 0;
236
237         if ((t - 120) < timestamp)
238                 ++counter;
239         else {
240                 timestamp = t;
241                 counter   = 0;
242         }
243
244         if (10 < counter) {
245                 unsigned int time_left = 300;
246
247                 syslog (LOG_ERR, "Error: collectd is respawning too fast - "
248                                 "disabled for %i seconds", time_left);
249
250                 while ((0 < (time_left = sleep (time_left))) && (0 == loop));
251         }
252         return;
253 } /* check_respawn */
254
255 int main (int argc, char **argv)
256 {
257         int    collectd_argc = 0;
258         char  *collectd      = NULL;
259         char **collectd_argv = NULL;
260
261         struct sigaction sa;
262
263         int i = 0;
264
265         /* parse command line options */
266         while (42) {
267                 int c = getopt (argc, argv, "hc:P:");
268
269                 if (-1 == c)
270                         break;
271
272                 switch (c) {
273                         case 'c':
274                                 collectd = optarg;
275                                 break;
276                         case 'P':
277                                 pidfile = optarg;
278                                 break;
279                         case 'h':
280                         default:
281                                 exit_usage (argv[0]);
282                 }
283         }
284
285         for (i = optind; i < argc; ++i)
286                 if (0 == strcmp (argv[i], "-f"))
287                         break;
288
289         /* i < argc => -f already present */
290         collectd_argc = 1 + argc - optind + ((i < argc) ? 0 : 1);
291         collectd_argv = (char **)calloc (collectd_argc + 1, sizeof (char *));
292
293         if (NULL == collectd_argv) {
294                 fprintf (stderr, "Out of memory.");
295                 return 3;
296         }
297
298         collectd_argv[0] = (NULL == collectd) ? "collectd" : collectd;
299
300         if (i == argc)
301                 collectd_argv[collectd_argc - 1] = "-f";
302
303         for (i = optind; i < argc; ++i)
304                 collectd_argv[i - optind + 1] = argv[i];
305
306         collectd_argv[collectd_argc] = NULL;
307
308         openlog ("collectdmon", LOG_CONS | LOG_PID, LOG_DAEMON);
309
310         if (-1 == daemonize ())
311                 return 1;
312
313         sa.sa_handler = sig_int_term_handler;
314         sa.sa_flags   = 0;
315         sigemptyset (&sa.sa_mask);
316
317         if (0 != sigaction (SIGINT, &sa, NULL)) {
318                 syslog (LOG_ERR, "Error: sigaction() failed: %s", strerror (errno));
319                 return 1;
320         }
321
322         if (0 != sigaction (SIGTERM, &sa, NULL)) {
323                 syslog (LOG_ERR, "Error: sigaction() failed: %s", strerror (errno));
324                 return 1;
325         }
326
327         sa.sa_handler = sig_hup_handler;
328
329         if (0 != sigaction (SIGHUP, &sa, NULL)) {
330                 syslog (LOG_ERR, "Error: sigaction() failed: %s", strerror (errno));
331                 return 1;
332         }
333
334         sigaddset (&sa.sa_mask, SIGCHLD);
335         if (0 != sigprocmask (SIG_BLOCK, &sa.sa_mask, NULL)) {
336                 syslog (LOG_ERR, "Error: sigprocmask() failed: %s", strerror (errno));
337                 return 1;
338         }
339
340         while (0 == loop) {
341                 int status = 0;
342
343                 if (0 != collectd_start (collectd_argc, collectd_argv)) {
344                         syslog (LOG_ERR, "Error: failed to start collectd.");
345                         break;
346                 }
347
348                 assert (0 < collectd_pid);
349                 while ((collectd_pid != waitpid (collectd_pid, &status, 0))
350                                 && (EINTR == errno))
351                         if ((0 != loop) || (0 != restart))
352                                 collectd_stop ();
353
354                 collectd_pid = 0;
355
356                 log_status (status);
357                 check_respawn ();
358
359                 if (0 != restart) {
360                         syslog (LOG_INFO, "Info: restarting collectd");
361                         restart = 0;
362                 }
363                 else if (0 == loop)
364                         syslog (LOG_WARNING, "Warning: restarting collectd");
365         }
366
367         syslog (LOG_INFO, "Info: shutting down collectdmon");
368
369         pidfile_delete ();
370         closelog ();
371
372         free (collectd_argv);
373         return 0;
374 } /* main */
375
376 /* vim: set sw=4 ts=4 tw=78 noexpandtab : */
377