Tidy up formatting with clang-format.
[collectd.git] / src / daemon / collectd.c
1 /**
2  * collectd - src/collectd.c
3  * Copyright (C) 2005-2007  Florian octo Forster
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  *   Florian octo Forster <octo at collectd.org>
25  *   Alvaro Barcellos <alvaro.barcellos at gmail.com>
26  **/
27
28 #include "collectd.h"
29
30 #include "common.h"
31 #include "configfile.h"
32 #include "plugin.h"
33
34 #include <netdb.h>
35 #include <sys/types.h>
36 #include <sys/un.h>
37
38 #if HAVE_LOCALE_H
39 #include <locale.h>
40 #endif
41
42 #if HAVE_STATGRAB_H
43 #include <statgrab.h>
44 #endif
45
46 #ifndef COLLECTD_LOCALE
47 #define COLLECTD_LOCALE "C"
48 #endif
49
50 static int loop = 0;
51
52 static void *do_flush(void __attribute__((unused)) * arg) {
53   INFO("Flushing all data.");
54   plugin_flush(/* plugin = */ NULL,
55                /* timeout = */ 0,
56                /* ident = */ NULL);
57   INFO("Finished flushing all data.");
58   pthread_exit(NULL);
59   return NULL;
60 }
61
62 static void sig_int_handler(int __attribute__((unused)) signal) { loop++; }
63
64 static void sig_term_handler(int __attribute__((unused)) signal) { loop++; }
65
66 static void sig_usr1_handler(int __attribute__((unused)) signal) {
67   pthread_t thread;
68   pthread_attr_t attr;
69
70   /* flushing the data might take a while,
71    * so it should be done asynchronously */
72   pthread_attr_init(&attr);
73   pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
74   pthread_create(&thread, &attr, do_flush, NULL);
75   pthread_attr_destroy(&attr);
76 }
77
78 static int init_hostname(void) {
79   const char *str;
80
81   struct addrinfo *ai_list;
82   int status;
83
84   long hostname_len = sysconf(_SC_HOST_NAME_MAX);
85   if (hostname_len == -1) {
86     hostname_len = NI_MAXHOST;
87   }
88   char hostname[hostname_len];
89   hostname_set(hostname);
90
91   str = global_option_get("Hostname");
92   if ((str != NULL) && (str[0] != 0)) {
93     hostname_set(str);
94     return 0;
95   }
96
97   if (gethostname(hostname, hostname_len) != 0) {
98     fprintf(stderr, "`gethostname' failed and no "
99                     "hostname was configured.\n");
100     return -1;
101   }
102
103   str = global_option_get("FQDNLookup");
104   if (IS_FALSE(str))
105     return 0;
106
107   struct addrinfo ai_hints = {.ai_flags = AI_CANONNAME};
108
109   status = getaddrinfo(hostname, NULL, &ai_hints, &ai_list);
110   if (status != 0) {
111     ERROR("Looking up \"%s\" failed. You have set the "
112           "\"FQDNLookup\" option, but I cannot resolve "
113           "my hostname to a fully qualified domain "
114           "name. Please fix the network "
115           "configuration.",
116           hostname);
117     return -1;
118   }
119
120   for (struct addrinfo *ai_ptr = ai_list; ai_ptr != NULL;
121        ai_ptr = ai_ptr->ai_next) {
122     if (ai_ptr->ai_canonname == NULL)
123       continue;
124
125     hostname_set(ai_ptr->ai_canonname);
126     break;
127   }
128
129   freeaddrinfo(ai_list);
130   return 0;
131 } /* int init_hostname */
132
133 static int init_global_variables(void) {
134   char const *str;
135
136   interval_g = cf_get_default_interval();
137   assert(interval_g > 0);
138   DEBUG("interval_g = %.3f;", CDTIME_T_TO_DOUBLE(interval_g));
139
140   str = global_option_get("Timeout");
141   if (str == NULL)
142     str = "2";
143   timeout_g = atoi(str);
144   if (timeout_g <= 1) {
145     fprintf(stderr, "Cannot set the timeout to a correct value.\n"
146                     "Please check your settings.\n");
147     return -1;
148   }
149   DEBUG("timeout_g = %i;", timeout_g);
150
151   if (init_hostname() != 0)
152     return -1;
153   DEBUG("hostname_g = %s;", hostname_g);
154
155   return 0;
156 } /* int init_global_variables */
157
158 static int change_basedir(const char *orig_dir, _Bool create) {
159   char *dir;
160   size_t dirlen;
161   int status;
162
163   dir = strdup(orig_dir);
164   if (dir == NULL) {
165     char errbuf[1024];
166     ERROR("strdup failed: %s", sstrerror(errno, errbuf, sizeof(errbuf)));
167     return -1;
168   }
169
170   dirlen = strlen(dir);
171   while ((dirlen > 0) && (dir[dirlen - 1] == '/'))
172     dir[--dirlen] = '\0';
173
174   if (dirlen == 0) {
175     free(dir);
176     return -1;
177   }
178
179   status = chdir(dir);
180   if (status == 0) {
181     free(dir);
182     return 0;
183   } else if (!create || (errno != ENOENT)) {
184     char errbuf[1024];
185     ERROR("change_basedir: chdir (%s): %s", dir,
186           sstrerror(errno, errbuf, sizeof(errbuf)));
187     free(dir);
188     return -1;
189   }
190
191   status = mkdir(dir, S_IRWXU | S_IRWXG | S_IRWXO);
192   if (status != 0) {
193     char errbuf[1024];
194     ERROR("change_basedir: mkdir (%s): %s", dir,
195           sstrerror(errno, errbuf, sizeof(errbuf)));
196     free(dir);
197     return -1;
198   }
199
200   status = chdir(dir);
201   if (status != 0) {
202     char errbuf[1024];
203     ERROR("change_basedir: chdir (%s): %s", dir,
204           sstrerror(errno, errbuf, sizeof(errbuf)));
205     free(dir);
206     return -1;
207   }
208
209   free(dir);
210   return 0;
211 } /* static int change_basedir (char *dir) */
212
213 #if HAVE_LIBKSTAT
214 static void update_kstat(void) {
215   if (kc == NULL) {
216     if ((kc = kstat_open()) == NULL)
217       ERROR("Unable to open kstat control structure");
218   } else {
219     kid_t kid;
220     kid = kstat_chain_update(kc);
221     if (kid > 0) {
222       INFO("kstat chain has been updated");
223       plugin_init_all();
224     } else if (kid < 0)
225       ERROR("kstat chain update failed");
226     /* else: everything works as expected */
227   }
228
229   return;
230 } /* static void update_kstat (void) */
231 #endif /* HAVE_LIBKSTAT */
232
233 /* TODO
234  * Remove all settings but `-f' and `-C'
235  */
236 __attribute__((noreturn)) static void exit_usage(int status) {
237   printf("Usage: " PACKAGE_NAME " [OPTIONS]\n\n"
238
239          "Available options:\n"
240          "  General:\n"
241          "    -C <file>       Configuration file.\n"
242          "                    Default: " CONFIGFILE "\n"
243          "    -t              Test config and exit.\n"
244          "    -T              Test plugin read and exit.\n"
245          "    -P <file>       PID-file.\n"
246          "                    Default: " PIDFILE "\n"
247 #if COLLECT_DAEMON
248          "    -f              Don't fork to the background.\n"
249 #endif
250          "    -B              Don't create the BaseDir\n"
251          "    -h              Display help (this message)\n"
252          "\nBuiltin defaults:\n"
253          "  Config file       " CONFIGFILE "\n"
254          "  PID file          " PIDFILE "\n"
255          "  Plugin directory  " PLUGINDIR "\n"
256          "  Data directory    " PKGLOCALSTATEDIR "\n"
257          "\n" PACKAGE_NAME " " PACKAGE_VERSION ", http://collectd.org/\n"
258          "by Florian octo Forster <octo@collectd.org>\n"
259          "for contributions see `AUTHORS'\n");
260   exit(status);
261 } /* static void exit_usage (int status) */
262
263 static int do_init(void) {
264 #if HAVE_SETLOCALE
265   if (setlocale(LC_NUMERIC, COLLECTD_LOCALE) == NULL)
266     WARNING("setlocale (\"%s\") failed.", COLLECTD_LOCALE);
267
268   /* Update the environment, so that libraries that are calling
269    * setlocale(LC_NUMERIC, "") don't accidentally revert these changes. */
270   unsetenv("LC_ALL");
271   setenv("LC_NUMERIC", COLLECTD_LOCALE, /* overwrite = */ 1);
272 #endif
273
274 #if HAVE_LIBKSTAT
275   kc = NULL;
276   update_kstat();
277 #endif
278
279 #if HAVE_LIBSTATGRAB
280   if (sg_init(
281 #if HAVE_LIBSTATGRAB_0_90
282           0
283 #endif
284           )) {
285     ERROR("sg_init: %s", sg_str_error(sg_get_error()));
286     return -1;
287   }
288
289   if (sg_drop_privileges()) {
290     ERROR("sg_drop_privileges: %s", sg_str_error(sg_get_error()));
291     return -1;
292   }
293 #endif
294
295   return plugin_init_all();
296 } /* int do_init () */
297
298 static int do_loop(void) {
299   cdtime_t interval = cf_get_default_interval();
300   cdtime_t wait_until;
301
302   wait_until = cdtime() + interval;
303
304   while (loop == 0) {
305     cdtime_t now;
306
307 #if HAVE_LIBKSTAT
308     update_kstat();
309 #endif
310
311     /* Issue all plugins */
312     plugin_read_all();
313
314     now = cdtime();
315     if (now >= wait_until) {
316       WARNING("Not sleeping because the next interval is "
317               "%.3f seconds in the past!",
318               CDTIME_T_TO_DOUBLE(now - wait_until));
319       wait_until = now + interval;
320       continue;
321     }
322
323     struct timespec ts_wait = CDTIME_T_TO_TIMESPEC(wait_until - now);
324     wait_until = wait_until + interval;
325
326     while ((loop == 0) && (nanosleep(&ts_wait, &ts_wait) != 0)) {
327       if (errno != EINTR) {
328         char errbuf[1024];
329         ERROR("nanosleep failed: %s", sstrerror(errno, errbuf, sizeof(errbuf)));
330         return -1;
331       }
332     }
333   } /* while (loop == 0) */
334
335   return 0;
336 } /* int do_loop */
337
338 static int do_shutdown(void) {
339   return plugin_shutdown_all();
340 } /* int do_shutdown */
341
342 #if COLLECT_DAEMON
343 static int pidfile_create(void) {
344   FILE *fh;
345   const char *file = global_option_get("PIDFile");
346
347   if ((fh = fopen(file, "w")) == NULL) {
348     char errbuf[1024];
349     ERROR("fopen (%s): %s", file, sstrerror(errno, errbuf, sizeof(errbuf)));
350     return 1;
351   }
352
353   fprintf(fh, "%i\n", (int)getpid());
354   fclose(fh);
355
356   return 0;
357 } /* static int pidfile_create (const char *file) */
358
359 static int pidfile_remove(void) {
360   const char *file = global_option_get("PIDFile");
361   if (file == NULL)
362     return 0;
363
364   return unlink(file);
365 } /* static int pidfile_remove (const char *file) */
366 #endif /* COLLECT_DAEMON */
367
368 #ifdef KERNEL_LINUX
369 static int notify_upstart(void) {
370   char const *upstart_job = getenv("UPSTART_JOB");
371
372   if (upstart_job == NULL)
373     return 0;
374
375   if (strcmp(upstart_job, "collectd") != 0) {
376     WARNING("Environment specifies unexpected UPSTART_JOB=\"%s\", expected "
377             "\"collectd\". Ignoring the variable.",
378             upstart_job);
379     return 0;
380   }
381
382   NOTICE("Upstart detected, stopping now to signal readyness.");
383   raise(SIGSTOP);
384   unsetenv("UPSTART_JOB");
385
386   return 1;
387 }
388
389 static int notify_systemd(void) {
390   int fd;
391   const char *notifysocket;
392   struct sockaddr_un su = {0};
393   size_t su_size;
394   char buffer[] = "READY=1\n";
395
396   notifysocket = getenv("NOTIFY_SOCKET");
397   if (notifysocket == NULL)
398     return 0;
399
400   if ((strlen(notifysocket) < 2) ||
401       ((notifysocket[0] != '@') && (notifysocket[0] != '/'))) {
402     ERROR("invalid notification socket NOTIFY_SOCKET=\"%s\": path must be "
403           "absolute",
404           notifysocket);
405     return 0;
406   }
407   NOTICE("Systemd detected, trying to signal readyness.");
408
409   unsetenv("NOTIFY_SOCKET");
410
411 #if defined(SOCK_CLOEXEC)
412   fd = socket(AF_UNIX, SOCK_DGRAM | SOCK_CLOEXEC, /* protocol = */ 0);
413 #else
414   fd = socket(AF_UNIX, SOCK_DGRAM, /* protocol = */ 0);
415 #endif
416   if (fd < 0) {
417     char errbuf[1024];
418     ERROR("creating UNIX socket failed: %s",
419           sstrerror(errno, errbuf, sizeof(errbuf)));
420     return 0;
421   }
422
423   su.sun_family = AF_UNIX;
424   if (notifysocket[0] != '@') {
425     /* regular UNIX socket */
426     sstrncpy(su.sun_path, notifysocket, sizeof(su.sun_path));
427     su_size = sizeof(su);
428   } else {
429     /* Linux abstract namespace socket: specify address as "\0foo", i.e.
430      * start with a null byte. Since null bytes have no special meaning in
431      * that case, we have to set su_size correctly to cover only the bytes
432      * that are part of the address. */
433     sstrncpy(su.sun_path, notifysocket, sizeof(su.sun_path));
434     su.sun_path[0] = 0;
435     su_size = sizeof(sa_family_t) + strlen(notifysocket);
436     if (su_size > sizeof(su))
437       su_size = sizeof(su);
438   }
439
440   if (sendto(fd, buffer, strlen(buffer), MSG_NOSIGNAL, (void *)&su,
441              (socklen_t)su_size) < 0) {
442     char errbuf[1024];
443     ERROR("sendto(\"%s\") failed: %s", notifysocket,
444           sstrerror(errno, errbuf, sizeof(errbuf)));
445     close(fd);
446     return 0;
447   }
448
449   unsetenv("NOTIFY_SOCKET");
450   close(fd);
451   return 1;
452 }
453 #endif /* KERNEL_LINUX */
454
455 struct cmdline_config {
456   _Bool test_config;
457   _Bool test_readall;
458   _Bool create_basedir;
459   const char *configfile;
460   _Bool daemonize;
461 };
462
463 void read_cmdline(int argc, char **argv, struct cmdline_config *config) {
464   /* read options */
465   while (1) {
466     int c;
467     c = getopt(argc, argv,
468                "htTC:"
469 #if COLLECT_DAEMON
470                "fP:"
471 #endif
472     );
473
474     if (c == -1)
475       break;
476
477     switch (c) {
478     case 'B':
479       config->create_basedir = 0;
480       break;
481     case 'C':
482       config->configfile = optarg;
483       break;
484     case 't':
485       config->test_config = 1;
486       break;
487     case 'T':
488       config->test_readall = 1;
489       global_option_set("ReadThreads", "-1", 1);
490 #if COLLECT_DAEMON
491       config->daemonize = 0;
492 #endif /* COLLECT_DAEMON */
493       break;
494 #if COLLECT_DAEMON
495     case 'P':
496       global_option_set("PIDFile", optarg, 1);
497       break;
498     case 'f':
499       config->daemonize = 0;
500       break;
501 #endif /* COLLECT_DAEMON */
502     case 'h':
503       exit_usage(0);
504       break;
505     default:
506       exit_usage(1);
507     } /* switch (c) */
508   }   /* while (1) */
509 }
510
511 int configure_collectd(struct cmdline_config *config) {
512   const char *basedir;
513   /*
514    * Read options from the config file, the environment and the command
515    * line (in that order, with later options overwriting previous ones in
516    * general).
517    * Also, this will automatically load modules.
518    */
519   if (cf_read(config->configfile)) {
520     fprintf(stderr, "Error: Reading the config file failed!\n"
521                     "Read the logs for details.\n");
522     return 1;
523   }
524
525   /*
526    * Change directory. We do this _after_ reading the config and loading
527    * modules to relative paths work as expected.
528    */
529   if ((basedir = global_option_get("BaseDir")) == NULL) {
530     fprintf(stderr,
531             "Don't have a basedir to use. This should not happen. Ever.");
532     return 1;
533   } else if (change_basedir(basedir, config->create_basedir)) {
534     fprintf(stderr, "Error: Unable to change to directory `%s'.\n", basedir);
535     return 1;
536   }
537
538   /*
539    * Set global variables or, if that fails, exit. We cannot run with
540    * them being uninitialized. If nothing is configured, then defaults
541    * are being used. So this means that the user has actually done
542    * something wrong.
543    */
544   if (init_global_variables() != 0)
545     return 1;
546
547   return 0;
548 }
549
550 int main(int argc, char **argv) {
551 #if COLLECT_DAEMON
552   pid_t pid;
553 #endif
554   int exit_status = 0;
555
556   struct cmdline_config config = {
557       .daemonize = 1,
558       .create_basedir = 1,
559       .configfile = CONFIGFILE,
560   };
561
562   read_cmdline(argc, argv, &config);
563
564   if (config.test_config)
565     return 0;
566
567   if (optind < argc)
568     exit_usage(1);
569
570   plugin_init_ctx();
571
572   int status;
573   if ((status = configure_collectd(&config)) != 0)
574     exit(EXIT_FAILURE);
575
576 #if COLLECT_DAEMON
577   /*
578    * fork off child
579    */
580   struct sigaction sig_chld_action = {.sa_handler = SIG_IGN};
581
582   sigaction(SIGCHLD, &sig_chld_action, NULL);
583
584   /*
585    * Only daemonize if we're not being supervised
586    * by upstart or systemd (when using Linux).
587    */
588   if (config.daemonize
589 #ifdef KERNEL_LINUX
590       && notify_upstart() == 0 && notify_systemd() == 0
591 #endif
592   ) {
593     int status;
594
595     if ((pid = fork()) == -1) {
596       /* error */
597       char errbuf[1024];
598       fprintf(stderr, "fork: %s", sstrerror(errno, errbuf, sizeof(errbuf)));
599       return 1;
600     } else if (pid != 0) {
601       /* parent */
602       /* printf ("Running (PID %i)\n", pid); */
603       return 0;
604     }
605
606     /* Detach from session */
607     setsid();
608
609     /* Write pidfile */
610     if (pidfile_create())
611       exit(2);
612
613     /* close standard descriptors */
614     close(2);
615     close(1);
616     close(0);
617
618     status = open("/dev/null", O_RDWR);
619     if (status != 0) {
620       ERROR("Error: Could not connect `STDIN' to `/dev/null' (status %d)",
621             status);
622       return 1;
623     }
624
625     status = dup(0);
626     if (status != 1) {
627       ERROR("Error: Could not connect `STDOUT' to `/dev/null' (status %d)",
628             status);
629       return 1;
630     }
631
632     status = dup(0);
633     if (status != 2) {
634       ERROR("Error: Could not connect `STDERR' to `/dev/null', (status %d)",
635             status);
636       return 1;
637     }
638   }    /* if (config.daemonize) */
639 #endif /* COLLECT_DAEMON */
640
641   struct sigaction sig_pipe_action = {.sa_handler = SIG_IGN};
642
643   sigaction(SIGPIPE, &sig_pipe_action, NULL);
644
645   /*
646    * install signal handlers
647    */
648   struct sigaction sig_int_action = {.sa_handler = sig_int_handler};
649
650   if (0 != sigaction(SIGINT, &sig_int_action, NULL)) {
651     char errbuf[1024];
652     ERROR("Error: Failed to install a signal handler for signal INT: %s",
653           sstrerror(errno, errbuf, sizeof(errbuf)));
654     return 1;
655   }
656
657   struct sigaction sig_term_action = {.sa_handler = sig_term_handler};
658
659   if (0 != sigaction(SIGTERM, &sig_term_action, NULL)) {
660     char errbuf[1024];
661     ERROR("Error: Failed to install a signal handler for signal TERM: %s",
662           sstrerror(errno, errbuf, sizeof(errbuf)));
663     return 1;
664   }
665
666   struct sigaction sig_usr1_action = {.sa_handler = sig_usr1_handler};
667
668   if (0 != sigaction(SIGUSR1, &sig_usr1_action, NULL)) {
669     char errbuf[1024];
670     ERROR("Error: Failed to install a signal handler for signal USR1: %s",
671           sstrerror(errno, errbuf, sizeof(errbuf)));
672     return 1;
673   }
674
675   /*
676    * run the actual loops
677    */
678   if (do_init() != 0) {
679     ERROR("Error: one or more plugin init callbacks failed.");
680     exit_status = 1;
681   }
682
683   if (config.test_readall) {
684     if (plugin_read_all_once() != 0) {
685       ERROR("Error: one or more plugin read callbacks failed.");
686       exit_status = 1;
687     }
688   } else {
689     INFO("Initialization complete, entering read-loop.");
690     do_loop();
691   }
692
693   /* close syslog */
694   INFO("Exiting normally.");
695
696   if (do_shutdown() != 0) {
697     ERROR("Error: one or more plugin shutdown callbacks failed.");
698     exit_status = 1;
699   }
700
701 #if COLLECT_DAEMON
702   if (config.daemonize)
703     pidfile_remove();
704 #endif /* COLLECT_DAEMON */
705
706   return exit_status;
707 } /* int main */