/** * collectd - src/configfile.c * 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 * Free Software Foundation; either version 2 of the License, or (at your * option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * * 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 "utils_threshold.h" #include "filter_chain.h" #if HAVE_WORDEXP_H # include #endif /* HAVE_WORDEXP_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; struct cf_callback *next; } cf_callback_t; typedef struct cf_complex_callback_s { char *type; int (*callback) (oconfig_item_t *); struct cf_complex_callback_s *next; } cf_complex_callback_t; typedef struct cf_value_map_s { char *key; int (*func) (const 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 (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); /* * 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_value_loadplugin} }; static int cf_value_map_num = STATIC_ARRAY_LEN (cf_value_map); static cf_global_option_t cf_global_options[] = { {"BaseDir", NULL, PKGLOCALSTATEDIR}, {"PIDFile", NULL, PIDFILE}, {"Hostname", NULL, NULL}, {"FQDNLookup", NULL, "false"}, {"Interval", NULL, "10"}, {"ReadThreads", NULL, "5"}, {"PreCacheChain", NULL, "PreCache"}, {"PostCacheChain", NULL, "PostCache"} }; static int cf_global_options_num = STATIC_ARRAY_LEN (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; char *key; char *value; int ret; int i; DEBUG ("type = %s, key = %s, value = %s", ESCAPE_NULL(type), ESCAPE_NULL(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; 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; } } if (i >= cf_cb->keys_num) WARNING ("Plugin `%s' did not register for value `%s'.", type, key); free (key); free (value); DEBUG ("cf_dispatch: return (%i)", ret); 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 (const 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 (const 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_value_loadplugin (const oconfig_item_t *ci) { assert (strcasecmp (ci->key, "LoadPlugin") == 0); if (ci->values_num != 1) return (-1); if (ci->values[0].type != OCONFIG_TYPE_STRING) return (-1); return (plugin_load (ci->values[0].value.string)); } /* 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 (const 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; /* Check for a complex callback first */ for (cb = complex_callback_head; cb != NULL; cb = cb->next) if (strcasecmp (name, cb->type) == 0) return (cb->callback (ci)); /* 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 {DEBUG ("No nested config blocks allowed for this plugin.");} } return (0); } static int dispatch_block (oconfig_item_t *ci) { 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); } 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 < (src->children_num - 1)), then * we need to move the trailing elements before resizing the array. */ if ((src->children_num == 0) && (offset < (src->children_num - 1))) { int nmemb = src->children_num - (offset + 1); memmove (src->children + offset, src->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; 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 childrem, 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, 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; /* Ignore all blocks, including `Include' blocks. */ if (root->children[i].children_num != 0) continue; 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; } new = cf_read_generic (old->values[0].value.string, depth + 1); if (new == NULL) continue; /* Now replace the i'th child in `root' with `new'. */ cf_ci_replace_child (root, new, 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, int depth) { oconfig_item_t *root; assert (depth < CF_MAX_DEPTH); root = oconfig_parse_file (file); if (root == NULL) { ERROR ("configfile: Cannot read file `%s'.", file); return (NULL); } cf_include_all (root, 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 **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."); 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); 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) { int j; for (j = i; j < filenames_num; ++j) free (filenames[j]); free (filenames); oconfig_free (root); return (NULL); } cf_ci_append_children (root, temp); sfree (temp->children); sfree (temp); free (name); } 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, 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]; ERROR ("configfile: stat (%s) failed: %s", path_ptr, sstrerror (errno, errbuf, sizeof (errbuf))); oconfig_free (root); return (NULL); } 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 { ERROR ("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, 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, depth)); else if (S_ISDIR (statbuf.st_mode)) return (cf_read_dir (path, 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); 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 */ 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->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; 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, 0 /* depth */); if (conf == NULL) { ERROR ("Unable to read config file %s.", filename); 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 */