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