Merge branch 'collectd-4.5'
[collectd.git] / src / configfile.c
index 3b6cb6b..f9ade29 100644 (file)
@@ -18,6 +18,7 @@
  *
  * Authors:
  *   Florian octo Forster <octo at verplant.org>
+ *   Sebastian tokkee Harl <sh at tokkee.org>
  **/
 
 #include "collectd.h"
 #include "configfile.h"
 #include "types_list.h"
 #include "utils_threshold.h"
+#include "filter_chain.h"
+
+#if HAVE_WORDEXP_H
+# include <wordexp.h>
+#endif /* HAVE_WORDEXP_H */
 
 #define ESCAPE_NULL(str) ((str) == NULL ? "(null)" : (str))
 
@@ -150,7 +156,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;
@@ -177,8 +184,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)
@@ -253,13 +259,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 +278,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 +329,7 @@ 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.");}
+                       {DEBUG ("No nested config blocks allowed for this plugin.");}
        }
 
        return (0);
@@ -336,6 +342,8 @@ static int dispatch_block (oconfig_item_t *ci)
                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 +357,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 +370,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 < (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;
@@ -406,6 +427,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));
@@ -485,13 +509,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);
 
@@ -514,30 +545,68 @@ static oconfig_item_t *cf_read_dir (const char *dir, int depth)
 
        while ((de = readdir (dh)) != NULL)
        {
-               oconfig_item_t *temp;
+               char   name[1024];
+               char **tmp;
 
                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))
                {
                        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)
-                       continue;
+               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 */
 
@@ -546,11 +615,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;
+       int i;
 
        if (depth >= CF_MAX_DEPTH)
        {
@@ -559,7 +637,83 @@ 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];
+                       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)
@@ -579,6 +733,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 +891,11 @@ 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 */