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