src/configfile.c: Fix a minor memory leak.
[collectd.git] / src / configfile.c
index 3b6cb6b..8b526f2 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 "types_list.h"
 #include "utils_threshold.h"
 
+#if HAVE_WORDEXP_H
+# include <wordexp.h>
+#endif /* HAVE_WORDEXP_H */
+
 #define ESCAPE_NULL(str) ((str) == NULL ? "(null)" : (str))
 
 /*
@@ -323,7 +328,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);
@@ -349,18 +354,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 +367,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 +424,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 +506,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,7 +542,8 @@ 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;
@@ -526,18 +555,55 @@ static oconfig_item_t *cf_read_dir (const char *dir, int depth)
                        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 +612,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 +634,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 +730,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 +888,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 (PLUGINDIR"/types.db");
+
        return (0);
 } /* int cf_read */