From: Florian Forster Date: Sat, 5 Dec 2015 07:56:17 +0000 (+0100) Subject: Merge branch 'collectd-5.4' into collectd-5.5 X-Git-Tag: collectd-5.5.1~31 X-Git-Url: https://git.tokkee.org/?a=commitdiff_plain;h=9b7dee8838f56233cea2854a7ddbcad2790609ce;p=collectd.git Merge branch 'collectd-5.4' into collectd-5.5 --- 9b7dee8838f56233cea2854a7ddbcad2790609ce diff --cc src/daemon/configfile.c index ae3e7989,00000000..16e07247 mode 100644,000000..100644 --- a/src/daemon/configfile.c +++ b/src/daemon/configfile.c @@@ -1,1372 -1,0 +1,1379 @@@ +/** + * collectd - src/configfile.c + * Copyright (C) 2005-2011 Florian octo Forster + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + * + * Authors: + * Florian octo Forster + * Sebastian tokkee Harl + **/ + +#include "collectd.h" + +#include "liboconfig/oconfig.h" + +#include "common.h" +#include "plugin.h" +#include "configfile.h" +#include "types_list.h" +#include "filter_chain.h" + +#if HAVE_WORDEXP_H +# include +#endif /* HAVE_WORDEXP_H */ + +#if HAVE_FNMATCH_H +# include +#endif /* HAVE_FNMATCH_H */ + +#if HAVE_LIBGEN_H +# include +#endif /* HAVE_LIBGEN_H */ + +#define ESCAPE_NULL(str) ((str) == NULL ? "(null)" : (str)) + +/* + * Private types + */ +typedef struct cf_callback +{ + const char *type; + int (*callback) (const char *, const char *); + const char **keys; + int keys_num; + plugin_ctx_t ctx; + struct cf_callback *next; +} cf_callback_t; + +typedef struct cf_complex_callback_s +{ + char *type; + int (*callback) (oconfig_item_t *); + plugin_ctx_t ctx; + struct cf_complex_callback_s *next; +} cf_complex_callback_t; + +typedef struct cf_value_map_s +{ + char *key; + int (*func) (oconfig_item_t *); +} cf_value_map_t; + +typedef struct cf_global_option_s +{ + char *key; + char *value; + char *def; +} cf_global_option_t; + +/* + * Prototypes of callback functions + */ +static int dispatch_value_typesdb (oconfig_item_t *ci); +static int dispatch_value_plugindir (oconfig_item_t *ci); +static int dispatch_loadplugin (oconfig_item_t *ci); +static int dispatch_block_plugin (oconfig_item_t *ci); + +/* + * Private variables + */ +static cf_callback_t *first_callback = NULL; +static cf_complex_callback_t *complex_callback_head = NULL; + +static cf_value_map_t cf_value_map[] = +{ + {"TypesDB", dispatch_value_typesdb}, + {"PluginDir", dispatch_value_plugindir}, + {"LoadPlugin", dispatch_loadplugin}, + {"Plugin", dispatch_block_plugin} +}; +static int cf_value_map_num = STATIC_ARRAY_SIZE (cf_value_map); + +static cf_global_option_t cf_global_options[] = +{ + {"BaseDir", NULL, PKGLOCALSTATEDIR}, + {"PIDFile", NULL, PIDFILE}, + {"Hostname", NULL, NULL}, + {"FQDNLookup", NULL, "true"}, + {"Interval", NULL, NULL}, + {"ReadThreads", NULL, "5"}, + {"WriteThreads", NULL, "5"}, + {"WriteQueueLimitHigh", NULL, NULL}, + {"WriteQueueLimitLow", NULL, NULL}, + {"Timeout", NULL, "2"}, + {"AutoLoadPlugin", NULL, "false"}, + {"CollectInternalStats", NULL, "false"}, + {"PreCacheChain", NULL, "PreCache"}, + {"PostCacheChain", NULL, "PostCache"}, + {"MaxReadInterval", NULL, "86400"} +}; +static int cf_global_options_num = STATIC_ARRAY_SIZE (cf_global_options); + +static int cf_default_typesdb = 1; + +/* + * Functions to handle register/unregister, search, and other plugin related + * stuff + */ +static cf_callback_t *cf_search (const char *type) +{ + cf_callback_t *cf_cb; + + if (type == NULL) + return (NULL); + + for (cf_cb = first_callback; cf_cb != NULL; cf_cb = cf_cb->next) + if (strcasecmp (cf_cb->type, type) == 0) + break; + + return (cf_cb); +} + +static int cf_dispatch (const char *type, const char *orig_key, + const char *orig_value) +{ + cf_callback_t *cf_cb; + plugin_ctx_t old_ctx; + char *key; + char *value; + int ret; + int i; + + if (orig_key == NULL) + return (EINVAL); + + DEBUG ("type = %s, key = %s, value = %s", + ESCAPE_NULL(type), + orig_key, + ESCAPE_NULL(orig_value)); + + if ((cf_cb = cf_search (type)) == NULL) + { + WARNING ("Found a configuration for the `%s' plugin, but " + "the plugin isn't loaded or didn't register " + "a configuration callback.", type); + return (-1); + } + + if ((key = strdup (orig_key)) == NULL) + return (1); + if ((value = strdup (orig_value)) == NULL) + { + free (key); + return (2); + } + + ret = -1; + + old_ctx = plugin_set_ctx (cf_cb->ctx); + + for (i = 0; i < cf_cb->keys_num; i++) + { + if ((cf_cb->keys[i] != NULL) + && (strcasecmp (cf_cb->keys[i], key) == 0)) + { + ret = (*cf_cb->callback) (key, value); + break; + } + } + + plugin_set_ctx (old_ctx); + + if (i >= cf_cb->keys_num) + WARNING ("Plugin `%s' did not register for value `%s'.", type, key); + + free (key); + free (value); + + return (ret); +} /* int cf_dispatch */ + +static int dispatch_global_option (const oconfig_item_t *ci) +{ + if (ci->values_num != 1) + return (-1); + if (ci->values[0].type == OCONFIG_TYPE_STRING) + return (global_option_set (ci->key, ci->values[0].value.string)); + else if (ci->values[0].type == OCONFIG_TYPE_NUMBER) + { + char tmp[128]; + ssnprintf (tmp, sizeof (tmp), "%lf", ci->values[0].value.number); + return (global_option_set (ci->key, tmp)); + } + else if (ci->values[0].type == OCONFIG_TYPE_BOOLEAN) + { + if (ci->values[0].value.boolean) + return (global_option_set (ci->key, "true")); + else + return (global_option_set (ci->key, "false")); + } + + return (-1); +} /* int dispatch_global_option */ + +static int dispatch_value_typesdb (oconfig_item_t *ci) +{ + int i = 0; + + assert (strcasecmp (ci->key, "TypesDB") == 0); + + cf_default_typesdb = 0; + + if (ci->values_num < 1) { + ERROR ("configfile: `TypesDB' needs at least one argument."); + return (-1); + } + + for (i = 0; i < ci->values_num; ++i) + { + if (OCONFIG_TYPE_STRING != ci->values[i].type) { + WARNING ("configfile: TypesDB: Skipping %i. argument which " + "is not a string.", i + 1); + continue; + } + + read_types_list (ci->values[i].value.string); + } + return (0); +} /* int dispatch_value_typesdb */ + +static int dispatch_value_plugindir (oconfig_item_t *ci) +{ + assert (strcasecmp (ci->key, "PluginDir") == 0); + + if (ci->values_num != 1) + return (-1); + if (ci->values[0].type != OCONFIG_TYPE_STRING) + return (-1); + + plugin_set_dir (ci->values[0].value.string); + return (0); +} + +static int dispatch_loadplugin (oconfig_item_t *ci) +{ + int i; + const char *name; + unsigned int flags = 0; + plugin_ctx_t ctx; + plugin_ctx_t old_ctx; + int ret_val; + + assert (strcasecmp (ci->key, "LoadPlugin") == 0); + + if (ci->values_num != 1) + return (-1); + if (ci->values[0].type != OCONFIG_TYPE_STRING) + return (-1); + + name = ci->values[0].value.string; + if (strcmp ("libvirt", name) == 0) + name = "virt"; + + /* default to the global interval set before loading this plugin */ + memset (&ctx, 0, sizeof (ctx)); + ctx.interval = cf_get_default_interval (); + + for (i = 0; i < ci->children_num; ++i) { + if (strcasecmp("Globals", ci->children[i].key) == 0) + cf_util_get_flag (ci->children + i, &flags, PLUGIN_FLAGS_GLOBAL); + else if (strcasecmp ("Interval", ci->children[i].key) == 0) { + if (cf_util_get_cdtime (ci->children + i, &ctx.interval) != 0) { + /* cf_util_get_cdtime will log an error */ + continue; + } + } + else { + WARNING("Ignoring unknown LoadPlugin option \"%s\" " + "for plugin \"%s\"", + ci->children[i].key, ci->values[0].value.string); + } + } + + old_ctx = plugin_set_ctx (ctx); + ret_val = plugin_load (name, (uint32_t) flags); + /* reset to the "global" context */ + plugin_set_ctx (old_ctx); + + return (ret_val); +} /* int dispatch_value_loadplugin */ + +static int dispatch_value_plugin (const char *plugin, oconfig_item_t *ci) +{ + char buffer[4096]; + char *buffer_ptr; + int buffer_free; + int i; + + buffer_ptr = buffer; + buffer_free = sizeof (buffer); + + for (i = 0; i < ci->values_num; i++) + { + int status = -1; + + if (ci->values[i].type == OCONFIG_TYPE_STRING) + status = ssnprintf (buffer_ptr, buffer_free, " %s", + ci->values[i].value.string); + else if (ci->values[i].type == OCONFIG_TYPE_NUMBER) + status = ssnprintf (buffer_ptr, buffer_free, " %lf", + ci->values[i].value.number); + else if (ci->values[i].type == OCONFIG_TYPE_BOOLEAN) + status = ssnprintf (buffer_ptr, buffer_free, " %s", + ci->values[i].value.boolean + ? "true" : "false"); + + if ((status < 0) || (status >= buffer_free)) + return (-1); + buffer_free -= status; + buffer_ptr += status; + } + /* skip the initial space */ + buffer_ptr = buffer + 1; + + return (cf_dispatch (plugin, ci->key, buffer_ptr)); +} /* int dispatch_value_plugin */ + +static int dispatch_value (oconfig_item_t *ci) +{ + int ret = -2; + int i; + + for (i = 0; i < cf_value_map_num; i++) + if (strcasecmp (cf_value_map[i].key, ci->key) == 0) + { + ret = cf_value_map[i].func (ci); + break; + } + + for (i = 0; i < cf_global_options_num; i++) + if (strcasecmp (cf_global_options[i].key, ci->key) == 0) + { + ret = dispatch_global_option (ci); + break; + } + + return (ret); +} /* int dispatch_value */ + +static int dispatch_block_plugin (oconfig_item_t *ci) +{ + int i; + char *name; + + cf_complex_callback_t *cb; + + if (strcasecmp (ci->key, "Plugin") != 0) + return (-1); + if (ci->values_num < 1) + return (-1); + if (ci->values[0].type != OCONFIG_TYPE_STRING) + return (-1); + + name = ci->values[0].value.string; + if (strcmp ("libvirt", name) == 0) + { + /* TODO(octo): Remove this legacy. */ + WARNING ("The \"libvirt\" plugin has been renamed to \"virt\" to avoid problems with the build system. " + "Your configuration is still using the old name. " + "Please change it to use \"virt\" as soon as possible. " + "This compatibility code will go away eventually."); + name = "virt"; + } + + if (IS_TRUE (global_option_get ("AutoLoadPlugin"))) + { + plugin_ctx_t ctx; + plugin_ctx_t old_ctx; + int status; + + /* default to the global interval set before loading this plugin */ + memset (&ctx, 0, sizeof (ctx)); + ctx.interval = cf_get_default_interval (); + + old_ctx = plugin_set_ctx (ctx); + status = plugin_load (name, /* flags = */ 0); + /* reset to the "global" context */ + plugin_set_ctx (old_ctx); + + if (status != 0) + { + ERROR ("Automatically loading plugin \"%s\" failed " + "with status %i.", name, status); + return (status); + } + } + + /* Check for a complex callback first */ + for (cb = complex_callback_head; cb != NULL; cb = cb->next) + { + if (strcasecmp (name, cb->type) == 0) + { + plugin_ctx_t old_ctx; + int ret_val; + + old_ctx = plugin_set_ctx (cb->ctx); + ret_val = (cb->callback (ci)); + plugin_set_ctx (old_ctx); + return (ret_val); + } + } + + /* Hm, no complex plugin found. Dispatch the values one by one */ + for (i = 0; i < ci->children_num; i++) + { + if (ci->children[i].children == NULL) + dispatch_value_plugin (name, ci->children + i); + else + { + WARNING ("There is a `%s' block within the " + "configuration for the %s plugin. " + "The plugin either only expects " + "\"simple\" configuration statements " + "or wasn't loaded using `LoadPlugin'." + " Please check your configuration.", + ci->children[i].key, name); + } + } + + return (0); +} + + +static int dispatch_block (oconfig_item_t *ci) +{ + if (strcasecmp (ci->key, "LoadPlugin") == 0) + return (dispatch_loadplugin (ci)); + else if (strcasecmp (ci->key, "Plugin") == 0) + return (dispatch_block_plugin (ci)); + else if (strcasecmp (ci->key, "Chain") == 0) + return (fc_configure (ci)); + + return (0); +} + +static int cf_ci_replace_child (oconfig_item_t *dst, oconfig_item_t *src, + int offset) +{ + oconfig_item_t *temp; + int i; + + assert (offset >= 0); + assert (dst->children_num > offset); + + /* Free the memory used by the replaced child. Usually that's the + * `Include "blah"' statement. */ + temp = dst->children + offset; + for (i = 0; i < temp->values_num; i++) + { + if (temp->values[i].type == OCONFIG_TYPE_STRING) + { + sfree (temp->values[i].value.string); + } + } + sfree (temp->values); + temp = NULL; + + /* If (src->children_num == 0) the array size is decreased. If offset + * is _not_ the last element, (offset < (dst->children_num - 1)), then + * we need to move the trailing elements before resizing the array. */ + if ((src->children_num == 0) && (offset < (dst->children_num - 1))) + { + int nmemb = dst->children_num - (offset + 1); + memmove (dst->children + offset, dst->children + offset + 1, + sizeof (oconfig_item_t) * nmemb); + } + + /* Resize the memory containing the children to be big enough to hold + * all children. */ + if (dst->children_num + src->children_num - 1 == 0) + { + dst->children_num = 0; + return (0); + } + + temp = (oconfig_item_t *) realloc (dst->children, + sizeof (oconfig_item_t) + * (dst->children_num + src->children_num - 1)); + if (temp == NULL) + { + ERROR ("configfile: realloc failed."); + return (-1); + } + dst->children = temp; + + /* If there are children behind the include statement, and they have + * not yet been moved because (src->children_num == 0), then move them + * to the end of the list, so that the new children have room before + * them. */ + if ((src->children_num > 0) + && ((dst->children_num - (offset + 1)) > 0)) + { + int nmemb = dst->children_num - (offset + 1); + int old_offset = offset + 1; + int new_offset = offset + src->children_num; + + memmove (dst->children + new_offset, + dst->children + old_offset, + sizeof (oconfig_item_t) * nmemb); + } + + /* Last but not least: If there are new children, copy them to the + * memory reserved for them. */ + if (src->children_num > 0) + { + memcpy (dst->children + offset, + src->children, + sizeof (oconfig_item_t) * src->children_num); + } + + /* Update the number of children. */ + dst->children_num += (src->children_num - 1); + + return (0); +} /* int cf_ci_replace_child */ + +static int cf_ci_append_children (oconfig_item_t *dst, oconfig_item_t *src) +{ + oconfig_item_t *temp; + + if ((src == NULL) || (src->children_num == 0)) + return (0); + + temp = (oconfig_item_t *) realloc (dst->children, + sizeof (oconfig_item_t) + * (dst->children_num + src->children_num)); + if (temp == NULL) + { + ERROR ("configfile: realloc failed."); + return (-1); + } + dst->children = temp; + + memcpy (dst->children + dst->children_num, + src->children, + sizeof (oconfig_item_t) + * src->children_num); + dst->children_num += src->children_num; + + return (0); +} /* int cf_ci_append_children */ + +#define CF_MAX_DEPTH 8 +static oconfig_item_t *cf_read_generic (const char *path, + const char *pattern, int depth); + +static int cf_include_all (oconfig_item_t *root, int depth) +{ + int i; + + for (i = 0; i < root->children_num; i++) + { + oconfig_item_t *new; + oconfig_item_t *old; + + char *pattern = NULL; + + int j; + + if (strcasecmp (root->children[i].key, "Include") != 0) + continue; + + old = root->children + i; + + if ((old->values_num != 1) + || (old->values[0].type != OCONFIG_TYPE_STRING)) + { + ERROR ("configfile: `Include' needs exactly one string argument."); + continue; + } + + for (j = 0; j < old->children_num; ++j) + { + oconfig_item_t *child = old->children + j; + + if (strcasecmp (child->key, "Filter") == 0) + cf_util_get_string (child, &pattern); + else + ERROR ("configfile: Option `%s' not allowed in block.", + child->key); + } + + new = cf_read_generic (old->values[0].value.string, pattern, depth + 1); + sfree (pattern); + + if (new == NULL) + return (-1); + + /* Now replace the i'th child in `root' with `new'. */ + if (cf_ci_replace_child (root, new, i) < 0) { + sfree (new->values); + sfree (new); + return (-1); + } + + /* ... and go back to the new i'th child. */ + --i; + + sfree (new->values); + sfree (new); + } /* for (i = 0; i < root->children_num; i++) */ + + return (0); +} /* int cf_include_all */ + +static oconfig_item_t *cf_read_file (const char *file, + const char *pattern, int depth) +{ + oconfig_item_t *root; + int status; + + assert (depth < CF_MAX_DEPTH); + + if (pattern != NULL) { +#if HAVE_FNMATCH_H && HAVE_LIBGEN_H + char *tmp = sstrdup (file); + char *filename = basename (tmp); + + if ((filename != NULL) && (fnmatch (pattern, filename, 0) != 0)) { + DEBUG ("configfile: Not including `%s' because it " + "does not match pattern `%s'.", + filename, pattern); + free (tmp); + return (NULL); + } + + free (tmp); +#else + ERROR ("configfile: Cannot apply pattern filter '%s' " + "to file '%s': functions basename() and / or " + "fnmatch() not available.", pattern, file); +#endif /* HAVE_FNMATCH_H && HAVE_LIBGEN_H */ + } + + root = oconfig_parse_file (file); + if (root == NULL) + { + ERROR ("configfile: Cannot read file `%s'.", file); + return (NULL); + } + + status = cf_include_all (root, depth); + if (status != 0) + { + oconfig_free (root); + return (NULL); + } + + return (root); +} /* oconfig_item_t *cf_read_file */ + +static int cf_compare_string (const void *p1, const void *p2) +{ + return strcmp (*(const char **) p1, *(const char **) p2); +} + +static oconfig_item_t *cf_read_dir (const char *dir, + const char *pattern, int depth) +{ + oconfig_item_t *root = NULL; + DIR *dh; + struct dirent *de; + char **filenames = NULL; + int filenames_num = 0; + int status; + int i; + + assert (depth < CF_MAX_DEPTH); + + dh = opendir (dir); + if (dh == NULL) + { + char errbuf[1024]; + ERROR ("configfile: opendir failed: %s", + sstrerror (errno, errbuf, sizeof (errbuf))); + return (NULL); + } + + root = (oconfig_item_t *) malloc (sizeof (oconfig_item_t)); + if (root == NULL) + { + ERROR ("configfile: malloc failed."); ++ closedir (dh); + return (NULL); + } + memset (root, 0, sizeof (oconfig_item_t)); + + while ((de = readdir (dh)) != NULL) + { + char name[1024]; + char **tmp; + + if ((de->d_name[0] == '.') || (de->d_name[0] == 0)) + continue; + + status = ssnprintf (name, sizeof (name), "%s/%s", + dir, de->d_name); + if ((status < 0) || ((size_t) status >= sizeof (name))) + { + ERROR ("configfile: Not including `%s/%s' because its" + " name is too long.", + dir, de->d_name); ++ closedir (dh); + for (i = 0; i < filenames_num; ++i) + free (filenames[i]); + free (filenames); + free (root); + return (NULL); + } + + ++filenames_num; + tmp = (char **) realloc (filenames, + filenames_num * sizeof (*filenames)); + if (tmp == NULL) { + ERROR ("configfile: realloc failed."); ++ closedir (dh); + for (i = 0; i < filenames_num - 1; ++i) + free (filenames[i]); + free (filenames); + free (root); + return (NULL); + } + filenames = tmp; + + filenames[filenames_num - 1] = sstrdup (name); + } + + if (filenames == NULL) ++ { ++ closedir (dh); + return (root); ++ } + + qsort ((void *) filenames, filenames_num, sizeof (*filenames), + cf_compare_string); + + for (i = 0; i < filenames_num; ++i) + { + oconfig_item_t *temp; + char *name = filenames[i]; + + temp = cf_read_generic (name, pattern, depth); + if (temp == NULL) + { + /* An error should already have been reported. */ + sfree (name); + continue; + } + + cf_ci_append_children (root, temp); + sfree (temp->children); + sfree (temp); + + free (name); + } + ++ closedir (dh); + free(filenames); + return (root); +} /* oconfig_item_t *cf_read_dir */ + - /* ++/* + * cf_read_generic + * + * Path is stat'ed and either cf_read_file or cf_read_dir is called + * accordingly. + * + * There are two versions of this function: If `wordexp' exists shell wildcards + * will be expanded and the function will include all matches found. If + * `wordexp' (or, more precisely, it's header file) is not available the + * simpler function is used which does not do any such expansion. + */ +#if HAVE_WORDEXP_H +static oconfig_item_t *cf_read_generic (const char *path, + const char *pattern, int depth) +{ + oconfig_item_t *root = NULL; + int status; + const char *path_ptr; + wordexp_t we; + size_t i; + + if (depth >= CF_MAX_DEPTH) + { + ERROR ("configfile: Not including `%s' because the maximum " + "nesting depth has been reached.", path); + return (NULL); + } + + status = wordexp (path, &we, WRDE_NOCMD); + if (status != 0) + { + ERROR ("configfile: wordexp (%s) failed.", path); + return (NULL); + } + + root = (oconfig_item_t *) malloc (sizeof (oconfig_item_t)); + if (root == NULL) + { + ERROR ("configfile: malloc failed."); + return (NULL); + } + memset (root, '\0', sizeof (oconfig_item_t)); + + /* wordexp() might return a sorted list already. That's not + * documented though, so let's make sure we get what we want. */ + qsort ((void *) we.we_wordv, we.we_wordc, sizeof (*we.we_wordv), + cf_compare_string); + + for (i = 0; i < we.we_wordc; i++) + { + oconfig_item_t *temp; + struct stat statbuf; + + path_ptr = we.we_wordv[i]; + + status = stat (path_ptr, &statbuf); + if (status != 0) + { + char errbuf[1024]; + WARNING ("configfile: stat (%s) failed: %s", + path_ptr, + sstrerror (errno, errbuf, sizeof (errbuf))); + continue; + } + + if (S_ISREG (statbuf.st_mode)) + temp = cf_read_file (path_ptr, pattern, depth); + else if (S_ISDIR (statbuf.st_mode)) + temp = cf_read_dir (path_ptr, pattern, depth); + else + { + WARNING ("configfile: %s is neither a file nor a " + "directory.", path); + continue; + } + + if (temp == NULL) { + oconfig_free (root); + return (NULL); + } + + cf_ci_append_children (root, temp); + sfree (temp->children); + sfree (temp); + } + + wordfree (&we); + + return (root); +} /* oconfig_item_t *cf_read_generic */ +/* #endif HAVE_WORDEXP_H */ + +#else /* if !HAVE_WORDEXP_H */ +static oconfig_item_t *cf_read_generic (const char *path, + const char *pattern, int depth) +{ + struct stat statbuf; + int status; + + if (depth >= CF_MAX_DEPTH) + { + ERROR ("configfile: Not including `%s' because the maximum " + "nesting depth has been reached.", path); + return (NULL); + } + + status = stat (path, &statbuf); + if (status != 0) + { + char errbuf[1024]; + ERROR ("configfile: stat (%s) failed: %s", + path, + sstrerror (errno, errbuf, sizeof (errbuf))); + return (NULL); + } + + if (S_ISREG (statbuf.st_mode)) + return (cf_read_file (path, pattern, depth)); + else if (S_ISDIR (statbuf.st_mode)) + return (cf_read_dir (path, pattern, depth)); + + ERROR ("configfile: %s is neither a file nor a directory.", path); + return (NULL); +} /* oconfig_item_t *cf_read_generic */ +#endif /* !HAVE_WORDEXP_H */ + +/* + * Public functions + */ +int global_option_set (const char *option, const char *value) +{ + int i; + + DEBUG ("option = %s; value = %s;", option, value); + + for (i = 0; i < cf_global_options_num; i++) + if (strcasecmp (cf_global_options[i].key, option) == 0) + break; + + if (i >= cf_global_options_num) + return (-1); + + if (strcasecmp (option, "PIDFile") == 0 && pidfile_from_cli == 1) + { + DEBUG ("Configfile: Ignoring `PIDFILE' option because " + "command-line option `-P' take precedence."); + return (0); + } + + sfree (cf_global_options[i].value); + + if (value != NULL) + cf_global_options[i].value = strdup (value); + else + cf_global_options[i].value = NULL; + + return (0); +} + +const char *global_option_get (const char *option) +{ + int i; + + for (i = 0; i < cf_global_options_num; i++) + if (strcasecmp (cf_global_options[i].key, option) == 0) + break; + + if (i >= cf_global_options_num) + return (NULL); + + return ((cf_global_options[i].value != NULL) + ? cf_global_options[i].value + : cf_global_options[i].def); +} /* char *global_option_get */ + +long global_option_get_long (const char *option, long default_value) +{ + const char *str; + long value; + + str = global_option_get (option); + if (NULL == str) + return (default_value); + + errno = 0; + value = strtol (str, /* endptr = */ NULL, /* base = */ 0); + if (errno != 0) + return (default_value); + + return (value); +} /* char *global_option_get_long */ + +cdtime_t global_option_get_time (const char *name, cdtime_t def) /* {{{ */ +{ + char const *optstr; + char *endptr = NULL; + double v; + + optstr = global_option_get (name); + if (optstr == NULL) + return (def); + + errno = 0; + v = strtod (optstr, &endptr); + if ((endptr == NULL) || (*endptr != 0) || (errno != 0)) + return (def); + else if (v <= 0.0) + return (def); + + return (DOUBLE_TO_CDTIME_T (v)); +} /* }}} cdtime_t global_option_get_time */ + +cdtime_t cf_get_default_interval (void) +{ + return (global_option_get_time ("Interval", + DOUBLE_TO_CDTIME_T (COLLECTD_DEFAULT_INTERVAL))); +} + +void cf_unregister (const char *type) +{ + cf_callback_t *this, *prev; + + for (prev = NULL, this = first_callback; + this != NULL; + prev = this, this = this->next) + if (strcasecmp (this->type, type) == 0) + { + if (prev == NULL) + first_callback = this->next; + else + prev->next = this->next; + + free (this); + break; + } +} /* void cf_unregister */ + +void cf_unregister_complex (const char *type) +{ + cf_complex_callback_t *this, *prev; + + for (prev = NULL, this = complex_callback_head; + this != NULL; + prev = this, this = this->next) + if (strcasecmp (this->type, type) == 0) + { + if (prev == NULL) + complex_callback_head = this->next; + else + prev->next = this->next; + + sfree (this->type); + sfree (this); + break; + } +} /* void cf_unregister */ + +void cf_register (const char *type, + int (*callback) (const char *, const char *), + const char **keys, int keys_num) +{ + cf_callback_t *cf_cb; + + /* Remove this module from the list, if it already exists */ + cf_unregister (type); + + /* This pointer will be free'd in `cf_unregister' */ + if ((cf_cb = (cf_callback_t *) malloc (sizeof (cf_callback_t))) == NULL) + return; + + cf_cb->type = type; + cf_cb->callback = callback; + cf_cb->keys = keys; + cf_cb->keys_num = keys_num; + cf_cb->ctx = plugin_get_ctx (); + + cf_cb->next = first_callback; + first_callback = cf_cb; +} /* void cf_register */ + +int cf_register_complex (const char *type, int (*callback) (oconfig_item_t *)) +{ + cf_complex_callback_t *new; + + new = (cf_complex_callback_t *) malloc (sizeof (cf_complex_callback_t)); + if (new == NULL) + return (-1); + + new->type = strdup (type); + if (new->type == NULL) + { + sfree (new); + return (-1); + } + + new->callback = callback; + new->next = NULL; + + new->ctx = plugin_get_ctx (); + + if (complex_callback_head == NULL) + { + complex_callback_head = new; + } + else + { + cf_complex_callback_t *last = complex_callback_head; + while (last->next != NULL) + last = last->next; + last->next = new; + } + + return (0); +} /* int cf_register_complex */ + +int cf_read (char *filename) +{ + oconfig_item_t *conf; + int i; + + conf = cf_read_generic (filename, /* pattern = */ NULL, /* depth = */ 0); + if (conf == NULL) + { + ERROR ("Unable to read config file %s.", filename); + return (-1); + } + else if (conf->children_num == 0) + { + ERROR ("Configuration file %s is empty.", filename); + oconfig_free (conf); + return (-1); + } + + for (i = 0; i < conf->children_num; i++) + { + if (conf->children[i].children == NULL) + dispatch_value (conf->children + i); + else + dispatch_block (conf->children + i); + } + + oconfig_free (conf); + + /* Read the default types.db if no `TypesDB' option was given. */ + if (cf_default_typesdb) + read_types_list (PKGDATADIR"/types.db"); + + return (0); +} /* int cf_read */ + +/* Assures the config option is a string, duplicates it and returns the copy in + * "ret_string". If necessary "*ret_string" is freed first. Returns zero upon + * success. */ +int cf_util_get_string (const oconfig_item_t *ci, char **ret_string) /* {{{ */ +{ + char *string; + + if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_STRING)) + { + ERROR ("cf_util_get_string: The %s option requires " + "exactly one string argument.", ci->key); + return (-1); + } + + string = strdup (ci->values[0].value.string); + if (string == NULL) + return (-1); + + if (*ret_string != NULL) + sfree (*ret_string); + *ret_string = string; + + return (0); +} /* }}} int cf_util_get_string */ + +/* Assures the config option is a string and copies it to the provided buffer. + * Assures null-termination. */ +int cf_util_get_string_buffer (const oconfig_item_t *ci, char *buffer, /* {{{ */ + size_t buffer_size) +{ + if ((ci == NULL) || (buffer == NULL) || (buffer_size < 1)) + return (EINVAL); + + if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_STRING)) + { + ERROR ("cf_util_get_string_buffer: The %s option requires " + "exactly one string argument.", ci->key); + return (-1); + } + + strncpy (buffer, ci->values[0].value.string, buffer_size); + buffer[buffer_size - 1] = 0; + + return (0); +} /* }}} int cf_util_get_string_buffer */ + +/* Assures the config option is a number and returns it as an int. */ +int cf_util_get_int (const oconfig_item_t *ci, int *ret_value) /* {{{ */ +{ + if ((ci == NULL) || (ret_value == NULL)) + return (EINVAL); + + if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_NUMBER)) + { + ERROR ("cf_util_get_int: The %s option requires " + "exactly one numeric argument.", ci->key); + return (-1); + } + + *ret_value = (int) ci->values[0].value.number; + + return (0); +} /* }}} int cf_util_get_int */ + +int cf_util_get_double (const oconfig_item_t *ci, double *ret_value) /* {{{ */ +{ + if ((ci == NULL) || (ret_value == NULL)) + return (EINVAL); + + if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_NUMBER)) + { + ERROR ("cf_util_get_double: The %s option requires " + "exactly one numeric argument.", ci->key); + return (-1); + } + + *ret_value = ci->values[0].value.number; + + return (0); +} /* }}} int cf_util_get_double */ + +int cf_util_get_boolean (const oconfig_item_t *ci, _Bool *ret_bool) /* {{{ */ +{ + if ((ci == NULL) || (ret_bool == NULL)) + return (EINVAL); + + if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_BOOLEAN)) + { + ERROR ("cf_util_get_boolean: The %s option requires " + "exactly one boolean argument.", ci->key); + return (-1); + } + + *ret_bool = ci->values[0].value.boolean ? 1 : 0; + + return (0); +} /* }}} int cf_util_get_boolean */ + +int cf_util_get_flag (const oconfig_item_t *ci, /* {{{ */ + unsigned int *ret_value, unsigned int flag) +{ + int status; + _Bool b; + + if (ret_value == NULL) + return (EINVAL); + + b = 0; + status = cf_util_get_boolean (ci, &b); + if (status != 0) + return (status); + + if (b) + { + *ret_value |= flag; + } + else + { + *ret_value &= ~flag; + } + + return (0); +} /* }}} int cf_util_get_flag */ + +/* Assures that the config option is a string or a number if the correct range + * of 1-65535. The string is then converted to a port number using + * `service_name_to_port_number' and returned. + * Returns the port number in the range [1-65535] or less than zero upon + * failure. */ +int cf_util_get_port_number (const oconfig_item_t *ci) /* {{{ */ +{ + int tmp; + + if ((ci->values_num != 1) + || ((ci->values[0].type != OCONFIG_TYPE_STRING) + && (ci->values[0].type != OCONFIG_TYPE_NUMBER))) + { + ERROR ("cf_util_get_port_number: The \"%s\" option requires " + "exactly one string argument.", ci->key); + return (-1); + } + + if (ci->values[0].type == OCONFIG_TYPE_STRING) + return (service_name_to_port_number (ci->values[0].value.string)); + + assert (ci->values[0].type == OCONFIG_TYPE_NUMBER); + tmp = (int) (ci->values[0].value.number + 0.5); + if ((tmp < 1) || (tmp > 65535)) + { + ERROR ("cf_util_get_port_number: The \"%s\" option requires " + "a service name or a port number. The number " + "you specified, %i, is not in the valid " + "range of 1-65535.", + ci->key, tmp); + return (-1); + } + + return (tmp); +} /* }}} int cf_util_get_port_number */ + +int cf_util_get_service (const oconfig_item_t *ci, char **ret_string) /* {{{ */ +{ + int port; + char *service; + int status; + + if (ci->values_num != 1) + { + ERROR ("cf_util_get_service: The %s option requires exactly " + "one argument.", ci->key); + return (-1); + } + + if (ci->values[0].type == OCONFIG_TYPE_STRING) + return (cf_util_get_string (ci, ret_string)); + if (ci->values[0].type != OCONFIG_TYPE_NUMBER) + { + ERROR ("cf_util_get_service: The %s option requires " + "exactly one string or numeric argument.", + ci->key); + } + + port = 0; + status = cf_util_get_int (ci, &port); + if (status != 0) + return (status); + else if ((port < 1) || (port > 65535)) + { + ERROR ("cf_util_get_service: The port number given " + "for the %s option is out of " + "range (%i).", ci->key, port); + return (-1); + } + + service = malloc (6); + if (service == NULL) + { + ERROR ("cf_util_get_service: Out of memory."); + return (-1); + } + ssnprintf (service, 6, "%i", port); + + sfree (*ret_string); + *ret_string = service; + + return (0); +} /* }}} int cf_util_get_service */ + +int cf_util_get_cdtime (const oconfig_item_t *ci, cdtime_t *ret_value) /* {{{ */ +{ + if ((ci == NULL) || (ret_value == NULL)) + return (EINVAL); + + if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_NUMBER)) + { + ERROR ("cf_util_get_cdtime: The %s option requires " + "exactly one numeric argument.", ci->key); + return (-1); + } + + if (ci->values[0].value.number < 0.0) + { + ERROR ("cf_util_get_cdtime: The numeric argument of the %s " + "option must not be negative.", ci->key); + return (-1); + } + + *ret_value = DOUBLE_TO_CDTIME_T (ci->values[0].value.number); + + return (0); +} /* }}} int cf_util_get_cdtime */ +