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