Merge pull request #2837 from abays/fix-collectd-tg-dtime
[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 "cmd.h"
29 #include "collectd.h"
30
31 #include "common.h"
32 #include "configfile.h"
33 #include "plugin.h"
34
35 #include <netdb.h>
36 #include <sys/types.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 #if HAVE_KSTAT_H
47 #include <kstat.h>
48 #endif
49
50 #ifndef COLLECTD_LOCALE
51 #define COLLECTD_LOCALE "C"
52 #endif
53
54 static int loop;
55
56 static int init_hostname(void) {
57   const char *str = global_option_get("Hostname");
58   if (str && str[0] != '\0') {
59     hostname_set(str);
60     return 0;
61   }
62
63   long hostname_len = sysconf(_SC_HOST_NAME_MAX);
64   if (hostname_len == -1) {
65     hostname_len = NI_MAXHOST;
66   }
67   char hostname[hostname_len];
68
69   if (gethostname(hostname, hostname_len) != 0) {
70     fprintf(stderr, "`gethostname' failed and no "
71                     "hostname was configured.\n");
72     return -1;
73   }
74
75   hostname_set(hostname);
76
77   str = global_option_get("FQDNLookup");
78   if (IS_FALSE(str))
79     return 0;
80
81   struct addrinfo *ai_list;
82   struct addrinfo ai_hints = {.ai_flags = AI_CANONNAME};
83
84   int status = getaddrinfo(hostname, NULL, &ai_hints, &ai_list);
85   if (status != 0) {
86     ERROR("Looking up \"%s\" failed. You have set the "
87           "\"FQDNLookup\" option, but I cannot resolve "
88           "my hostname to a fully qualified domain "
89           "name. Please fix the network "
90           "configuration.",
91           hostname);
92     return -1;
93   }
94
95   for (struct addrinfo *ai_ptr = ai_list; ai_ptr; ai_ptr = ai_ptr->ai_next) {
96     if (ai_ptr->ai_canonname == NULL)
97       continue;
98
99     hostname_set(ai_ptr->ai_canonname);
100     break;
101   }
102
103   freeaddrinfo(ai_list);
104   return 0;
105 } /* int init_hostname */
106
107 static int init_global_variables(void) {
108   interval_g = cf_get_default_interval();
109   assert(interval_g > 0);
110   DEBUG("interval_g = %.3f;", CDTIME_T_TO_DOUBLE(interval_g));
111
112   const char *str = global_option_get("Timeout");
113   if (str == NULL)
114     str = "2";
115   timeout_g = atoi(str);
116   if (timeout_g <= 1) {
117     fprintf(stderr, "Cannot set the timeout to a correct value.\n"
118                     "Please check your settings.\n");
119     return -1;
120   }
121   DEBUG("timeout_g = %i;", timeout_g);
122
123   if (init_hostname() != 0)
124     return -1;
125   DEBUG("hostname_g = %s;", hostname_g);
126
127   return 0;
128 } /* int init_global_variables */
129
130 static int change_basedir(const char *orig_dir, bool create) {
131   char *dir = strdup(orig_dir);
132   if (dir == NULL) {
133     ERROR("strdup failed: %s", STRERRNO);
134     return -1;
135   }
136
137   size_t dirlen = strlen(dir);
138   while ((dirlen > 0) && (dir[dirlen - 1] == '/'))
139     dir[--dirlen] = '\0';
140
141   if (dirlen == 0) {
142     free(dir);
143     return -1;
144   }
145
146   int status = chdir(dir);
147   if (status == 0) {
148     free(dir);
149     return 0;
150   } else if (!create || (errno != ENOENT)) {
151     ERROR("change_basedir: chdir (%s): %s", dir, STRERRNO);
152     free(dir);
153     return -1;
154   }
155
156   status = mkdir(dir, S_IRWXU | S_IRWXG | S_IRWXO);
157   if (status != 0) {
158     ERROR("change_basedir: mkdir (%s): %s", dir, STRERRNO);
159     free(dir);
160     return -1;
161   }
162
163   status = chdir(dir);
164   if (status != 0) {
165     ERROR("change_basedir: chdir (%s): %s", dir, STRERRNO);
166     free(dir);
167     return -1;
168   }
169
170   free(dir);
171   return 0;
172 } /* static int change_basedir (char *dir) */
173
174 #if HAVE_LIBKSTAT
175 extern kstat_ctl_t *kc;
176 static void update_kstat(void) {
177   if (kc == NULL) {
178     if ((kc = kstat_open()) == NULL)
179       ERROR("Unable to open kstat control structure");
180   } else {
181     kid_t kid = kstat_chain_update(kc);
182     if (kid > 0) {
183       INFO("kstat chain has been updated");
184       plugin_init_all();
185     } else if (kid < 0)
186       ERROR("kstat chain update failed");
187     /* else: everything works as expected */
188   }
189
190   return;
191 } /* static void update_kstat (void) */
192 #endif /* HAVE_LIBKSTAT */
193
194 __attribute__((noreturn)) static void exit_usage(int status) {
195   printf("Usage: " PACKAGE_NAME " [OPTIONS]\n\n"
196
197          "Available options:\n"
198          "  General:\n"
199          "    -C <file>       Configuration file.\n"
200          "                    Default: " CONFIGFILE "\n"
201          "    -t              Test config and exit.\n"
202          "    -T              Test plugin read and exit.\n"
203          "    -P <file>       PID-file.\n"
204          "                    Default: " PIDFILE "\n"
205 #if COLLECT_DAEMON
206          "    -f              Don't fork to the background.\n"
207 #endif
208          "    -B              Don't create the BaseDir\n"
209          "    -h              Display help (this message)\n"
210          "\nBuiltin defaults:\n"
211          "  Config file       " CONFIGFILE "\n"
212          "  PID file          " PIDFILE "\n"
213          "  Plugin directory  " PLUGINDIR "\n"
214          "  Data directory    " PKGLOCALSTATEDIR "\n"
215          "\n" PACKAGE_NAME " " PACKAGE_VERSION ", http://collectd.org/\n"
216          "by Florian octo Forster <octo@collectd.org>\n"
217          "for contributions see `AUTHORS'\n");
218   exit(status);
219 } /* static void exit_usage (int status) */
220
221 static int do_init(void) {
222 #if HAVE_SETLOCALE
223   if (setlocale(LC_NUMERIC, COLLECTD_LOCALE) == NULL)
224     WARNING("setlocale (\"%s\") failed.", COLLECTD_LOCALE);
225
226   /* Update the environment, so that libraries that are calling
227    * setlocale(LC_NUMERIC, "") don't accidentally revert these changes. */
228   unsetenv("LC_ALL");
229   setenv("LC_NUMERIC", COLLECTD_LOCALE, /* overwrite = */ 1);
230 #endif
231
232 #if HAVE_LIBKSTAT
233   kc = NULL;
234   update_kstat();
235 #endif
236
237 #if HAVE_LIBSTATGRAB
238   if (sg_init(
239 #if HAVE_LIBSTATGRAB_0_90
240           0
241 #endif
242           )) {
243     ERROR("sg_init: %s", sg_str_error(sg_get_error()));
244     return -1;
245   }
246
247   if (sg_drop_privileges()) {
248     ERROR("sg_drop_privileges: %s", sg_str_error(sg_get_error()));
249     return -1;
250   }
251 #endif
252
253   return plugin_init_all();
254 } /* int do_init () */
255
256 static int do_loop(void) {
257   cdtime_t interval = cf_get_default_interval();
258   cdtime_t wait_until = cdtime() + interval;
259
260   while (loop == 0) {
261 #if HAVE_LIBKSTAT
262     update_kstat();
263 #endif
264
265     /* Issue all plugins */
266     plugin_read_all();
267
268     cdtime_t now = cdtime();
269     if (now >= wait_until) {
270       WARNING("Not sleeping because the next interval is "
271               "%.3f seconds in the past!",
272               CDTIME_T_TO_DOUBLE(now - wait_until));
273       wait_until = now + interval;
274       continue;
275     }
276
277     struct timespec ts_wait = CDTIME_T_TO_TIMESPEC(wait_until - now);
278     wait_until = wait_until + interval;
279
280     while ((loop == 0) && (nanosleep(&ts_wait, &ts_wait) != 0)) {
281       if (errno != EINTR) {
282         ERROR("nanosleep failed: %s", STRERRNO);
283         return -1;
284       }
285     }
286   } /* while (loop == 0) */
287
288   return 0;
289 } /* int do_loop */
290
291 static int do_shutdown(void) {
292   return plugin_shutdown_all();
293 } /* int do_shutdown */
294
295 static void read_cmdline(int argc, char **argv, struct cmdline_config *config) {
296   /* read options */
297   while (1) {
298     int c = getopt(argc, argv, "htTC:"
299 #if COLLECT_DAEMON
300                                "fP:"
301 #endif
302                    );
303
304     if (c == -1)
305       break;
306
307     switch (c) {
308     case 'B':
309       config->create_basedir = false;
310       break;
311     case 'C':
312       config->configfile = optarg;
313       break;
314     case 't':
315       config->test_config = true;
316       break;
317     case 'T':
318       config->test_readall = true;
319       global_option_set("ReadThreads", "-1", 1);
320 #if COLLECT_DAEMON
321       config->daemonize = false;
322 #endif /* COLLECT_DAEMON */
323       break;
324 #if COLLECT_DAEMON
325     case 'P':
326       global_option_set("PIDFile", optarg, 1);
327       break;
328     case 'f':
329       config->daemonize = false;
330       break;
331 #endif /* COLLECT_DAEMON */
332     case 'h':
333       exit_usage(0);
334     default:
335       exit_usage(1);
336     } /* switch (c) */
337   }   /* while (1) */
338 }
339
340 static int configure_collectd(struct cmdline_config *config) {
341   /*
342    * Read options from the config file, the environment and the command
343    * line (in that order, with later options overwriting previous ones in
344    * general).
345    * Also, this will automatically load modules.
346    */
347   if (cf_read(config->configfile)) {
348     fprintf(stderr, "Error: Reading the config file failed!\n"
349                     "Read the logs for details.\n");
350     return 1;
351   }
352
353   /*
354    * Change directory. We do this _after_ reading the config and loading
355    * modules to relative paths work as expected.
356    */
357   const char *basedir;
358   if ((basedir = global_option_get("BaseDir")) == NULL) {
359     fprintf(stderr,
360             "Don't have a basedir to use. This should not happen. Ever.");
361     return 1;
362   } else if (change_basedir(basedir, config->create_basedir)) {
363     fprintf(stderr, "Error: Unable to change to directory `%s'.\n", basedir);
364     return 1;
365   }
366
367   /*
368    * Set global variables or, if that fails, exit. We cannot run with
369    * them being uninitialized. If nothing is configured, then defaults
370    * are being used. So this means that the user has actually done
371    * something wrong.
372    */
373   if (init_global_variables() != 0)
374     return 1;
375
376   return 0;
377 }
378
379 void stop_collectd(void) { loop++; }
380
381 struct cmdline_config init_config(int argc, char **argv) {
382   struct cmdline_config config = {
383       .daemonize = true, .create_basedir = true, .configfile = CONFIGFILE,
384   };
385
386   read_cmdline(argc, argv, &config);
387
388   if (config.test_config)
389     exit(EXIT_SUCCESS);
390
391   if (optind < argc)
392     exit_usage(1);
393
394   plugin_init_ctx();
395
396   if (configure_collectd(&config) != 0)
397     exit(EXIT_FAILURE);
398
399   return config;
400 }
401
402 int run_loop(bool test_readall) {
403   int exit_status = 0;
404
405   if (do_init() != 0) {
406     ERROR("Error: one or more plugin init callbacks failed.");
407     exit_status = 1;
408   }
409
410   if (test_readall) {
411     if (plugin_read_all_once() != 0) {
412       ERROR("Error: one or more plugin read callbacks failed.");
413       exit_status = 1;
414     }
415   } else {
416     INFO("Initialization complete, entering read-loop.");
417     do_loop();
418   }
419
420   /* close syslog */
421   INFO("Exiting normally.");
422
423   if (do_shutdown() != 0) {
424     ERROR("Error: one or more plugin shutdown callbacks failed.");
425     exit_status = 1;
426   }
427
428   return exit_status;
429 } /* int run_loop */