Merge pull request #3159 from rpv-tomsk/collectd-usercache
[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,
311                    "BhtTC:"
312 #if COLLECT_DAEMON
313                    "fP:"
314 #endif
315     );
316
317     if (c == -1)
318       break;
319
320     switch (c) {
321     case 'B':
322       config->create_basedir = false;
323       break;
324     case 'C':
325       config->configfile = optarg;
326       break;
327     case 't':
328       config->test_config = true;
329       break;
330     case 'T':
331       config->test_readall = true;
332       global_option_set("ReadThreads", "-1", 1);
333 #if COLLECT_DAEMON
334       config->daemonize = false;
335 #endif /* COLLECT_DAEMON */
336       break;
337 #if COLLECT_DAEMON
338     case 'P':
339       global_option_set("PIDFile", optarg, 1);
340       break;
341     case 'f':
342       config->daemonize = false;
343       break;
344 #endif /* COLLECT_DAEMON */
345     case 'h':
346       exit_usage(0);
347     default:
348       exit_usage(1);
349     } /* switch (c) */
350   }   /* while (1) */
351 }
352
353 static int configure_collectd(struct cmdline_config *config) {
354   /*
355    * Read options from the config file, the environment and the command
356    * line (in that order, with later options overwriting previous ones in
357    * general).
358    * Also, this will automatically load modules.
359    */
360   if (cf_read(config->configfile)) {
361     fprintf(stderr, "Error: Parsing the config file failed!\n");
362     return 1;
363   }
364
365   /*
366    * Change directory. We do this _after_ reading the config and loading
367    * modules to relative paths work as expected.
368    */
369   const char *basedir;
370   if ((basedir = global_option_get("BaseDir")) == NULL) {
371     fprintf(stderr,
372             "Don't have a basedir to use. This should not happen. Ever.");
373     return 1;
374   } else if (change_basedir(basedir, config->create_basedir)) {
375     fprintf(stderr, "Error: Unable to change to directory `%s'.\n", basedir);
376     return 1;
377   }
378
379   /*
380    * Set global variables or, if that fails, exit. We cannot run with
381    * them being uninitialized. If nothing is configured, then defaults
382    * are being used. So this means that the user has actually done
383    * something wrong.
384    */
385   if (init_global_variables() != 0)
386     return 1;
387
388   return 0;
389 }
390
391 void stop_collectd(void) { loop++; }
392
393 struct cmdline_config init_config(int argc, char **argv) {
394   struct cmdline_config config = {
395       .daemonize = true,
396       .create_basedir = true,
397       .configfile = CONFIGFILE,
398   };
399
400   read_cmdline(argc, argv, &config);
401
402   if (config.test_config)
403     exit(EXIT_SUCCESS);
404
405   if (optind < argc)
406     exit_usage(1);
407
408   plugin_init_ctx();
409
410   if (configure_collectd(&config) != 0)
411     exit(EXIT_FAILURE);
412
413   return config;
414 }
415
416 int run_loop(bool test_readall) {
417   int exit_status = 0;
418
419   if (do_init() != 0) {
420     ERROR("Error: one or more plugin init callbacks failed.");
421     exit_status = 1;
422   }
423
424   if (test_readall) {
425     if (plugin_read_all_once() != 0) {
426       ERROR("Error: one or more plugin read callbacks failed.");
427       exit_status = 1;
428     }
429   } else {
430     INFO("Initialization complete, entering read-loop.");
431     do_loop();
432   }
433
434   /* close syslog */
435   INFO("Exiting normally.");
436
437   if (do_shutdown() != 0) {
438     ERROR("Error: one or more plugin shutdown callbacks failed.");
439     exit_status = 1;
440   }
441
442   return exit_status;
443 } /* int run_loop */