X-Git-Url: https://git.octo.it/?a=blobdiff_plain;f=src%2Fconfigfile.c;h=0b7786f929605a311b4c8705a48e73a80ace68b4;hb=77f03c91b3074b5abd3e602002b7fc7ce28ea612;hp=3b6cb6b3a1b93505d994a91181bcf6e0e6d9b08b;hpb=f1a764777b71e54407293163aefb07586977b147;p=collectd.git diff --git a/src/configfile.c b/src/configfile.c index 3b6cb6b3..0b7786f9 100644 --- a/src/configfile.c +++ b/src/configfile.c @@ -1,6 +1,6 @@ /** * collectd - src/configfile.c - * Copyright (C) 2005-2008 Florian octo Forster + * Copyright (C) 2005-2009 Florian octo Forster * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the @@ -18,6 +18,7 @@ * * Authors: * Florian octo Forster + * Sebastian tokkee Harl **/ #include "collectd.h" @@ -29,6 +30,11 @@ #include "configfile.h" #include "types_list.h" #include "utils_threshold.h" +#include "filter_chain.h" + +#if HAVE_WORDEXP_H +# include +#endif /* HAVE_WORDEXP_H */ #define ESCAPE_NULL(str) ((str) == NULL ? "(null)" : (str)) @@ -69,7 +75,7 @@ typedef struct cf_global_option_s */ static int dispatch_value_typesdb (const oconfig_item_t *ci); static int dispatch_value_plugindir (const oconfig_item_t *ci); -static int dispatch_value_loadplugin (const oconfig_item_t *ci); +static int dispatch_loadplugin (const oconfig_item_t *ci); /* * Private variables @@ -81,7 +87,7 @@ static cf_value_map_t cf_value_map[] = { {"TypesDB", dispatch_value_typesdb}, {"PluginDir", dispatch_value_plugindir}, - {"LoadPlugin", dispatch_value_loadplugin} + {"LoadPlugin", dispatch_loadplugin} }; static int cf_value_map_num = STATIC_ARRAY_LEN (cf_value_map); @@ -92,7 +98,10 @@ static cf_global_option_t cf_global_options[] = {"Hostname", NULL, NULL}, {"FQDNLookup", NULL, "false"}, {"Interval", NULL, "10"}, - {"ReadThreads", NULL, "5"} + {"ReadThreads", NULL, "5"}, + {"Timeout", NULL, "2"}, + {"PreCacheChain", NULL, "PreCache"}, + {"PostCacheChain", NULL, "PostCache"} }; static int cf_global_options_num = STATIC_ARRAY_LEN (cf_global_options); @@ -150,7 +159,8 @@ static int cf_dispatch (const char *type, const char *orig_key, for (i = 0; i < cf_cb->keys_num; i++) { - if (strcasecmp (cf_cb->keys[i], key) == 0) + if ((cf_cb->keys[i] != NULL) + && (strcasecmp (cf_cb->keys[i], key) == 0)) { ret = (*cf_cb->callback) (key, value); break; @@ -163,7 +173,7 @@ static int cf_dispatch (const char *type, const char *orig_key, free (key); free (value); - DEBUG ("return (%i)", ret); + DEBUG ("cf_dispatch: return (%i)", ret); return (ret); } /* int cf_dispatch */ @@ -177,8 +187,7 @@ static int dispatch_global_option (const oconfig_item_t *ci) else if (ci->values[0].type == OCONFIG_TYPE_NUMBER) { char tmp[128]; - snprintf (tmp, sizeof (tmp), "%lf", ci->values[0].value.number); - tmp[127] = '\0'; + 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) @@ -200,13 +209,18 @@ static int dispatch_value_typesdb (const oconfig_item_t *ci) cf_default_typesdb = 0; - if (ci->values_num < 1) + 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) + 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); } @@ -226,8 +240,10 @@ static int dispatch_value_plugindir (const oconfig_item_t *ci) return (0); } -static int dispatch_value_loadplugin (const oconfig_item_t *ci) +static int dispatch_loadplugin (const oconfig_item_t *ci) { + int i; + uint32_t flags = 0; assert (strcasecmp (ci->key, "LoadPlugin") == 0); if (ci->values_num != 1) @@ -235,7 +251,19 @@ static int dispatch_value_loadplugin (const oconfig_item_t *ci) if (ci->values[0].type != OCONFIG_TYPE_STRING) return (-1); - return (plugin_load (ci->values[0].value.string)); + for (i = 0; i < ci->children_num; ++i) { + if (ci->children[i].values_num != 1 || + ci->children[i].values[0].type != OCONFIG_TYPE_BOOLEAN) { + WARNING("Ignoring unknown LoadPlugin option %s for plugin %s", ci->children[i].key, ci->values[0].value.string); + continue; + } + if (strcasecmp(ci->children[i].key, "globals") == 0) { + flags |= PLUGIN_FLAGS_GLOBAL; + } else { + WARNING("Ignoring unknown LoadPlugin option %s for plugin %s", ci->children[i].key, ci->values[0].value.string); + } + } + return (plugin_load (ci->values[0].value.string, flags)); } /* int dispatch_value_loadplugin */ static int dispatch_value_plugin (const char *plugin, oconfig_item_t *ci) @@ -253,13 +281,13 @@ static int dispatch_value_plugin (const char *plugin, oconfig_item_t *ci) int status = -1; if (ci->values[i].type == OCONFIG_TYPE_STRING) - status = snprintf (buffer_ptr, buffer_free, " %s", + status = ssnprintf (buffer_ptr, buffer_free, " %s", ci->values[i].value.string); else if (ci->values[i].type == OCONFIG_TYPE_NUMBER) - status = snprintf (buffer_ptr, buffer_free, " %lf", + status = ssnprintf (buffer_ptr, buffer_free, " %lf", ci->values[i].value.number); else if (ci->values[i].type == OCONFIG_TYPE_BOOLEAN) - status = snprintf (buffer_ptr, buffer_free, " %s", + status = ssnprintf (buffer_ptr, buffer_free, " %s", ci->values[i].value.boolean ? "true" : "false"); @@ -272,7 +300,7 @@ static int dispatch_value_plugin (const char *plugin, oconfig_item_t *ci) buffer_ptr = buffer + 1; return (cf_dispatch (plugin, ci->key, buffer_ptr)); -} /* int plugin_conf_dispatch */ +} /* int dispatch_value_plugin */ static int dispatch_value (const oconfig_item_t *ci) { @@ -323,7 +351,15 @@ static int dispatch_block_plugin (oconfig_item_t *ci) if (ci->children[i].children == NULL) dispatch_value_plugin (name, ci->children + i); else - {DEBUG ("No nested config blocks allow for this plugin.");} + { + 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); @@ -332,10 +368,14 @@ static int dispatch_block_plugin (oconfig_item_t *ci) static int dispatch_block (oconfig_item_t *ci) { - if (strcasecmp (ci->key, "Plugin") == 0) + 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, "Threshold") == 0) return (ut_config (ci)); + else if (strcasecmp (ci->key, "Chain") == 0) + return (fc_configure (ci)); return (0); } @@ -349,18 +389,6 @@ static int cf_ci_replace_child (oconfig_item_t *dst, oconfig_item_t *src, assert (offset >= 0); assert (dst->children_num > offset); - /* Resize the memory containing the children to be big enough to hold - * all children. */ - 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; - /* Free the memory used by the replaced child. Usually that's the * `Include "blah"' statement. */ temp = dst->children + offset; @@ -374,9 +402,34 @@ static int cf_ci_replace_child (oconfig_item_t *dst, oconfig_item_t *src, sfree (temp->values); temp = NULL; - /* If there are children behind the include statement, move them to the - * end of the list, so that the new children have room before them. */ - if ((dst->children_num - (offset + 1)) > 0) + /* 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. */ + 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; @@ -387,7 +440,7 @@ static int cf_ci_replace_child (oconfig_item_t *dst, oconfig_item_t *src, sizeof (oconfig_item_t) * nmemb); } - /* Last but not least: If there are new childrem, copy them to the + /* Last but not least: If there are new children, copy them to the * memory reserved for them. */ if (src->children_num > 0) { @@ -406,6 +459,9 @@ 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)); @@ -460,6 +516,9 @@ static int cf_include_all (oconfig_item_t *root, int depth) /* Now replace the i'th child in `root' with `new'. */ cf_ci_replace_child (root, new, i); + /* ... and go back to the new i'th child. */ + --i; + sfree (new->values); sfree (new); } /* for (i = 0; i < root->children_num; i++) */ @@ -485,13 +544,20 @@ static oconfig_item_t *cf_read_file (const char *file, int depth) 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, int depth) { oconfig_item_t *root = NULL; DIR *dh; struct dirent *de; - char name[1024]; + char **filenames = NULL; + int filenames_num = 0; int status; + int i; assert (depth < CF_MAX_DEPTH); @@ -510,34 +576,70 @@ static oconfig_item_t *cf_read_dir (const char *dir, int depth) ERROR ("configfile: malloc failed."); return (NULL); } - memset (root, '\0', sizeof (oconfig_item_t)); + memset (root, 0, sizeof (oconfig_item_t)); while ((de = readdir (dh)) != NULL) { - oconfig_item_t *temp; + char name[1024]; + char **tmp; - if ((de->d_name[0] == '.') || (de->d_name[0] == '\0')) + if ((de->d_name[0] == '.') || (de->d_name[0] == 0)) continue; - status = snprintf (name, sizeof (name), "%s/%s", + status = ssnprintf (name, sizeof (name), "%s/%s", dir, de->d_name); - if (status >= sizeof (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); - continue; + 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."); + 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); + } + + 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, 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); } + free(filenames); return (root); } /* oconfig_item_t *cf_read_dir */ @@ -546,11 +648,20 @@ static oconfig_item_t *cf_read_dir (const char *dir, int depth) * * 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, int depth) { - struct stat statbuf; + oconfig_item_t *root = NULL; int status; + const char *path_ptr; + wordexp_t we; + size_t i; if (depth >= CF_MAX_DEPTH) { @@ -559,7 +670,88 @@ static oconfig_item_t *cf_read_generic (const char *path, int depth) return (NULL); } - fprintf (stderr, "cf_read_generic (path = %s, depth = %i);", path, depth); + 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, depth); + else if (S_ISDIR (statbuf.st_mode)) + temp = cf_read_dir (path_ptr, 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); + + if (root->children == NULL) + { + oconfig_free (root); + return (NULL); + } + + 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, 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) @@ -579,6 +771,7 @@ static oconfig_item_t *cf_read_generic (const char *path, int 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 @@ -736,7 +929,107 @@ int cf_read (char *filename) 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 (PLUGINDIR"/types.db"); /* FIXME: Configure path */ + 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_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 ? true : false; + + return (0); +} /* }}} int cf_util_get_boolean */ + +/* Assures that the config option is a string. 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) /* {{{ */ +{ + if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_STRING)) + { + ERROR ("cf_util_get_port_number: The %s option requires " + "exactly one string argument.", ci->key); + return (-1); + } + + return (service_name_to_port_number (ci->values[0].value.string)); +} /* }}} int cf_util_get_port_number */