Fixed issues with initializing hostname.
[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, "htTC:"
468 #if COLLECT_DAEMON
469                            "fP:"
470 #endif
471                );
472
473     if (c == -1)
474       break;
475
476     switch (c) {
477     case 'B':
478       config->create_basedir = 0;
479       break;
480     case 'C':
481       config->configfile = optarg;
482       break;
483     case 't':
484       config->test_config = 1;
485       break;
486     case 'T':
487       config->test_readall = 1;
488       global_option_set("ReadThreads", "-1", 1);
489 #if COLLECT_DAEMON
490       config->daemonize = 0;
491 #endif /* COLLECT_DAEMON */
492       break;
493 #if COLLECT_DAEMON
494     case 'P':
495       global_option_set("PIDFile", optarg, 1);
496       break;
497     case 'f':
498       config->daemonize = 0;
499       break;
500 #endif /* COLLECT_DAEMON */
501     case 'h':
502       exit_usage(0);
503       break;
504     default:
505       exit_usage(1);
506     } /* switch (c) */
507   }   /* while (1) */
508 }
509
510 int configure_collectd(struct cmdline_config *config) {
511   const char *basedir;
512   /*
513    * Read options from the config file, the environment and the command
514    * line (in that order, with later options overwriting previous ones in
515    * general).
516    * Also, this will automatically load modules.
517    */
518   if (cf_read(config->configfile)) {
519     fprintf(stderr, "Error: Reading the config file failed!\n"
520                     "Read the logs for details.\n");
521     return 1;
522   }
523
524   /*
525    * Change directory. We do this _after_ reading the config and loading
526    * modules to relative paths work as expected.
527    */
528   if ((basedir = global_option_get("BaseDir")) == NULL) {
529     fprintf(stderr,
530             "Don't have a basedir to use. This should not happen. Ever.");
531     return 1;
532   } else if (change_basedir(basedir, config->create_basedir)) {
533     fprintf(stderr, "Error: Unable to change to directory `%s'.\n", basedir);
534     return 1;
535   }
536
537   /*
538    * Set global variables or, if that fails, exit. We cannot run with
539    * them being uninitialized. If nothing is configured, then defaults
540    * are being used. So this means that the user has actually done
541    * something wrong.
542    */
543   if (init_global_variables() != 0)
544     return 1;
545
546   return 0;
547 }
548
549 int main(int argc, char **argv) {
550 #if COLLECT_DAEMON
551   pid_t pid;
552 #endif
553   int exit_status = 0;
554
555   struct cmdline_config config = {
556       .daemonize = 1, .create_basedir = 1, .configfile = CONFIGFILE,
557   };
558
559   read_cmdline(argc, argv, &config);
560
561   if (config.test_config)
562     return 0;
563
564   if (optind < argc)
565     exit_usage(1);
566
567   plugin_init_ctx();
568
569   int status;
570   if ((status = configure_collectd(&config)) != 0)
571     exit(EXIT_FAILURE);
572
573 #if COLLECT_DAEMON
574   /*
575    * fork off child
576    */
577   struct sigaction sig_chld_action = {.sa_handler = SIG_IGN};
578
579   sigaction(SIGCHLD, &sig_chld_action, NULL);
580
581   /*
582    * Only daemonize if we're not being supervised
583    * by upstart or systemd (when using Linux).
584    */
585   if (config.daemonize
586 #ifdef KERNEL_LINUX
587       && notify_upstart() == 0 && notify_systemd() == 0
588 #endif
589       ) {
590     int status;
591
592     if ((pid = fork()) == -1) {
593       /* error */
594       char errbuf[1024];
595       fprintf(stderr, "fork: %s", sstrerror(errno, errbuf, sizeof(errbuf)));
596       return 1;
597     } else if (pid != 0) {
598       /* parent */
599       /* printf ("Running (PID %i)\n", pid); */
600       return 0;
601     }
602
603     /* Detach from session */
604     setsid();
605
606     /* Write pidfile */
607     if (pidfile_create())
608       exit(2);
609
610     /* close standard descriptors */
611     close(2);
612     close(1);
613     close(0);
614
615     status = open("/dev/null", O_RDWR);
616     if (status != 0) {
617       ERROR("Error: Could not connect `STDIN' to `/dev/null' (status %d)",
618             status);
619       return 1;
620     }
621
622     status = dup(0);
623     if (status != 1) {
624       ERROR("Error: Could not connect `STDOUT' to `/dev/null' (status %d)",
625             status);
626       return 1;
627     }
628
629     status = dup(0);
630     if (status != 2) {
631       ERROR("Error: Could not connect `STDERR' to `/dev/null', (status %d)",
632             status);
633       return 1;
634     }
635   }    /* if (config.daemonize) */
636 #endif /* COLLECT_DAEMON */
637
638   struct sigaction sig_pipe_action = {.sa_handler = SIG_IGN};
639
640   sigaction(SIGPIPE, &sig_pipe_action, NULL);
641
642   /*
643    * install signal handlers
644    */
645   struct sigaction sig_int_action = {.sa_handler = sig_int_handler};
646
647   if (0 != sigaction(SIGINT, &sig_int_action, NULL)) {
648     char errbuf[1024];
649     ERROR("Error: Failed to install a signal handler for signal INT: %s",
650           sstrerror(errno, errbuf, sizeof(errbuf)));
651     return 1;
652   }
653
654   struct sigaction sig_term_action = {.sa_handler = sig_term_handler};
655
656   if (0 != sigaction(SIGTERM, &sig_term_action, NULL)) {
657     char errbuf[1024];
658     ERROR("Error: Failed to install a signal handler for signal TERM: %s",
659           sstrerror(errno, errbuf, sizeof(errbuf)));
660     return 1;
661   }
662
663   struct sigaction sig_usr1_action = {.sa_handler = sig_usr1_handler};
664
665   if (0 != sigaction(SIGUSR1, &sig_usr1_action, NULL)) {
666     char errbuf[1024];
667     ERROR("Error: Failed to install a signal handler for signal USR1: %s",
668           sstrerror(errno, errbuf, sizeof(errbuf)));
669     return 1;
670   }
671
672   /*
673    * run the actual loops
674    */
675   if (do_init() != 0) {
676     ERROR("Error: one or more plugin init callbacks failed.");
677     exit_status = 1;
678   }
679
680   if (config.test_readall) {
681     if (plugin_read_all_once() != 0) {
682       ERROR("Error: one or more plugin read callbacks failed.");
683       exit_status = 1;
684     }
685   } else {
686     INFO("Initialization complete, entering read-loop.");
687     do_loop();
688   }
689
690   /* close syslog */
691   INFO("Exiting normally.");
692
693   if (do_shutdown() != 0) {
694     ERROR("Error: one or more plugin shutdown callbacks failed.");
695     exit_status = 1;
696   }
697
698 #if COLLECT_DAEMON
699   if (config.daemonize)
700     pidfile_remove();
701 #endif /* COLLECT_DAEMON */
702
703   return exit_status;
704 } /* int main */