collectdmon: Added a small daemon monitoring collectd.
[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
59 static char  *pidfile      = NULL;
60 static pid_t  collectd_pid = 0;
61
62 static void exit_usage (char *name)
63 {
64         printf ("Usage: %s <options> [-- <collectd options>]\n"
65
66                         "\nAvailable options:\n"
67                         "  -h         Display this help and exit.\n"
68                         "  -c <path>  Path to the collectd binary.\n"
69                         "  -P <file>  PID-file.\n"
70
71                         "\nFor <collectd options> see collectd.conf(5).\n"
72
73                         "\n"PACKAGE" "VERSION", http://collectd.org/\n"
74                         "by Florian octo Forster <octo@verplant.org>\n"
75                         "for contributions see `AUTHORS'\n", name);
76         exit (0);
77 } /* exit_usage */
78
79 static int pidfile_create (void)
80 {
81         FILE *file = NULL;
82
83         if (NULL == pidfile)
84                 pidfile = COLLECTDMON_PIDFILE;
85
86         if (NULL == (file = fopen (pidfile, "w"))) {
87                 syslog (LOG_ERR, "Error: couldn't open PID-file (%s) for writing: %s",
88                                 pidfile, strerror (errno));
89                 return -1;
90         }
91
92         fprintf (file, "%i\n", (int)getpid ());
93         fclose (file);
94         return 0;
95 } /* pidfile_create */
96
97 static int pidfile_delete (void)
98 {
99         assert (NULL != pidfile);
100
101         if (0 != unlink (pidfile)) {
102                 syslog (LOG_ERR, "Error: couldn't delete PID-file (%s): %s",
103                                 pidfile, strerror (errno));
104                 return -1;
105         }
106         return 0;
107 } /* pidfile_remove */
108
109 static int daemonize (void)
110 {
111         struct rlimit rl;
112
113         pid_t pid = 0;
114         int   i   = 0;
115
116         if (0 != chdir ("/")) {
117                 fprintf (stderr, "Error: chdir() failed: %s\n", strerror (errno));
118                 return -1;
119         }
120
121         if (0 != getrlimit (RLIMIT_NOFILE, &rl)) {
122                 fprintf (stderr, "Error: getrlimit() failed: %s\n", strerror (errno));
123                 return -1;
124         }
125
126         if (0 > (pid = fork ())) {
127                 fprintf (stderr, "Error: fork() failed: %s\n", strerror (errno));
128                 return -1;
129         }
130         else if (pid != 0) {
131                 exit (0);
132         }
133
134         if (0 != pidfile_create ())
135                 return -1;
136
137         setsid ();
138
139         if (RLIM_INFINITY == rl.rlim_max)
140                 rl.rlim_max = 1024;
141
142         for (i = 0; i < rl.rlim_max; ++i)
143                 close (i);
144
145         errno = 0;
146         if (open ("/dev/null", O_RDWR) != 0) {
147                 syslog (LOG_ERR, "Error: couldn't connect STDIN to /dev/null: %s",
148                                 strerror (errno));
149                 return -1;
150         }
151
152         errno = 0;
153         if (dup (0) != 1) {
154                 syslog (LOG_ERR, "Error: couldn't connect STDOUT to /dev/null: %s",
155                                 strerror (errno));
156                 return -1;
157         }
158
159         errno = 0;
160         if (dup (0) != 2) {
161                 syslog (LOG_ERR, "Error: couldn't connect STDERR to /dev/null: %s",
162                                 strerror (errno));
163                 return -1;
164         }
165         return 0;
166 } /* daemonize */
167
168 static int collectd_start (int argc, char **argv)
169 {
170         pid_t pid = 0;
171
172         if (0 > (pid = fork ())) {
173                 syslog (LOG_ERR, "Error: fork() failed: %s", strerror (errno));
174                 return -1;
175         }
176         else if (pid != 0) {
177                 collectd_pid = pid;
178                 return 0;
179         }
180
181         execvp (argv[0], argv);
182         syslog (LOG_ERR, "Error: execvp(%s) failed: %s",
183                         argv[0], strerror (errno));
184         exit (-1);
185 } /* collectd_start */
186
187 static int collectd_stop (void)
188 {
189         if (0 == collectd_pid)
190                 return 0;
191
192         if (0 != kill (collectd_pid, SIGTERM)) {
193                 syslog (LOG_ERR, "Error: kill() failed: %s", strerror (errno));
194                 return -1;
195         }
196         return 0;
197 } /* collectd_stop */
198
199 static void sig_int_term_handler (int signo)
200 {
201         ++loop;
202         return;
203 } /* sig_int_term_handler */
204
205 static void log_status (int status)
206 {
207         if (WIFEXITED (status)) {
208                 syslog (LOG_INFO, "Info: collectd terminated with exit status %i",
209                                 WEXITSTATUS (status));
210         }
211         else if (WIFSIGNALED (status)) {
212                 syslog (LOG_WARNING, "Warning: collectd was terminated by signal %i%s",
213                                 WTERMSIG (status), WCOREDUMP (status) ? " (core dumped)" : "");
214         }
215         return;
216 } /* log_status */
217
218 static void check_respawn (void)
219 {
220         time_t t = time (NULL);
221
222         static time_t timestamp = 0;
223         static int    counter   = 0;
224
225         if ((t - 120) < timestamp)
226                 ++counter;
227         else {
228                 timestamp = t;
229                 counter   = 0;
230         }
231
232         if (10 < counter) {
233                 unsigned int time_left = 300;
234
235                 syslog (LOG_ERR, "Error: collectd is respawning too fast - "
236                                 "disabled for %i seconds", time_left);
237
238                 while ((0 < (time_left = sleep (time_left))) && (0 == loop));
239         }
240         return;
241 } /* check_respawn */
242
243 int main (int argc, char **argv)
244 {
245         int    collectd_argc = 0;
246         char  *collectd      = NULL;
247         char **collectd_argv = NULL;
248
249         struct sigaction sa;
250
251         int i = 0;
252
253         /* parse command line options */
254         while (42) {
255                 int c = getopt (argc, argv, "hc:P:");
256
257                 if (-1 == c)
258                         break;
259
260                 switch (c) {
261                         case 'c':
262                                 collectd = optarg;
263                                 break;
264                         case 'P':
265                                 pidfile = optarg;
266                                 break;
267                         case 'h':
268                         default:
269                                 exit_usage (argv[0]);
270                 }
271         }
272
273         for (i = optind; i < argc; ++i)
274                 if (0 == strcmp (argv[i], "-f"))
275                         break;
276
277         /* i < argc => -f already present */
278         collectd_argc = 1 + argc - optind + ((i < argc) ? 0 : 1);
279         collectd_argv = (char **)calloc (collectd_argc + 1, sizeof (char *));
280
281         if (NULL == collectd_argv) {
282                 fprintf (stderr, "Out of memory.");
283                 return 3;
284         }
285
286         collectd_argv[0] = (NULL == collectd) ? "collectd" : collectd;
287
288         if (i == argc)
289                 collectd_argv[collectd_argc - 1] = "-f";
290
291         for (i = optind; i < argc; ++i)
292                 collectd_argv[i - optind + 1] = argv[i];
293
294         collectd_argv[collectd_argc] = NULL;
295
296         openlog ("collectdmon", LOG_CONS | LOG_PID, LOG_DAEMON);
297
298         if (-1 == daemonize ())
299                 return 1;
300
301         sa.sa_handler = sig_int_term_handler;
302         sa.sa_flags   = 0;
303         sigemptyset (&sa.sa_mask);
304
305         if (0 != sigaction (SIGINT, &sa, NULL)) {
306                 syslog (LOG_ERR, "Error: sigaction() failed: %s", strerror (errno));
307                 return 1;
308         }
309
310         if (0 != sigaction (SIGTERM, &sa, NULL)) {
311                 syslog (LOG_ERR, "Error: sigaction() failed: %s", strerror (errno));
312                 return 1;
313         }
314
315         sigaddset (&sa.sa_mask, SIGCHLD);
316         if (0 != sigprocmask (SIG_BLOCK, &sa.sa_mask, NULL)) {
317                 syslog (LOG_ERR, "Error: sigprocmask() failed: %s", strerror (errno));
318                 return 1;
319         }
320
321         while (0 == loop) {
322                 int status = 0;
323
324                 if (0 != collectd_start (collectd_argc, collectd_argv)) {
325                         syslog (LOG_ERR, "Error: failed to start collectd.");
326                         break;
327                 }
328
329                 assert (0 < collectd_pid);
330                 while ((collectd_pid != waitpid (collectd_pid, &status, 0))
331                                 && (EINTR == errno))
332                         if (0 != loop)
333                                 collectd_stop ();
334
335                 collectd_pid = 0;
336
337                 log_status (status);
338                 check_respawn ();
339
340                 if (0 == loop)
341                         syslog (LOG_WARNING, "Warning: restarting collectd");
342         }
343
344         syslog (LOG_INFO, "Info: shutting down collectdmon");
345
346         pidfile_delete ();
347         closelog ();
348
349         free (collectd_argv);
350         return 0;
351 } /* main */
352
353 /* vim: set sw=4 ts=4 tw=78 noexpandtab : */
354