config: Added support for specifying include filter patterns.
authorSebastian Harl <sh@tokkee.org>
Sat, 24 Nov 2012 09:26:20 +0000 (10:26 +0100)
committerSebastian Harl <sh@tokkee.org>
Sat, 24 Nov 2012 09:26:20 +0000 (10:26 +0100)
An optional second argument may now be passed to the "Include" configuration
option. If specified (and if the fnmatch() function was available at build
time), only files matching this pattern will be included. For example, the
following will include all files matching "*.conf" in any subdirectory of
/etc/collectd.d/:

  Include "/etc/collectd.d" "*.conf"

This is useful, e.g. for distributions in order to include a possibly empty
directory in the default configuration including all subdirectories but also
making it possible to ship further documents like README files.

configure.in
src/collectd.conf.pod
src/configfile.c

index 98395ed..f0f9cfb 100644 (file)
@@ -128,7 +128,7 @@ AC_HEADER_SYS_WAIT
 AC_HEADER_DIRENT
 AC_HEADER_STDBOOL
 
-AC_CHECK_HEADERS(stdio.h errno.h math.h stdarg.h syslog.h fcntl.h signal.h assert.h sys/types.h sys/socket.h sys/select.h poll.h netdb.h arpa/inet.h sys/resource.h sys/param.h kstat.h regex.h sys/ioctl.h endian.h sys/isa_defs.h)
+AC_CHECK_HEADERS(stdio.h errno.h math.h stdarg.h syslog.h fcntl.h signal.h assert.h sys/types.h sys/socket.h sys/select.h poll.h netdb.h arpa/inet.h sys/resource.h sys/param.h kstat.h regex.h sys/ioctl.h endian.h sys/isa_defs.h fnmatch.h libgen.h)
 
 # For ping library
 AC_CHECK_HEADERS(netinet/in_systm.h, [], [],
@@ -298,7 +298,7 @@ else
 fi
 
 # For hddtemp module
-AC_CHECK_HEADERS(linux/major.h libgen.h)
+AC_CHECK_HEADERS(linux/major.h)
 
 # For md module (Linux only)
 if test "x$ac_system" = "xLinux"
index 1c8b7a4..80fd31b 100644 (file)
@@ -109,7 +109,7 @@ interval, that setting will take precedence.
 
 =back
 
-=item B<Include> I<Path>
+=item B<Include> I<Path> [I<pattern>]
 
 If I<Path> points to a file, includes that file. If I<Path> points to a
 directory, recursively includes all files within that directory and its
@@ -119,6 +119,15 @@ use statements like the following:
 
   Include "/etc/collectd.d/*.conf"
 
+If the C<fnmatch> function is available on your system, a shell-like wildcard
+I<pattern> may be specified to filter which files to include. This may be used
+in combination with recursively including a directory to easily be able to
+arbitrarily mix configuration files and other documents (e.g. README files).
+The following statement is similar to the example above but includes all files
+matching C<*.conf> in any subdirectory of C</etc/collectd.d>:
+
+  Include "/etc/collectd.d" "*.conf"
+
 If more than one files are included by a single B<Include> option, the files
 will be included in lexicographical order (as defined by the C<strcmp>
 function). Thus, you can e.E<nbsp>g. use numbered prefixes to specify the
index 5920c53..1a9e28a 100644 (file)
 # include <wordexp.h>
 #endif /* HAVE_WORDEXP_H */
 
+#if HAVE_FNMATCH_H
+# include <fnmatch.h>
+#endif /* HAVE_FNMATCH_H */
+
+#if HAVE_LIBGEN_H
+# include <libgen.h>
+#endif /* HAVE_LIBGEN_H */
+
 #define ESCAPE_NULL(str) ((str) == NULL ? "(null)" : (str))
 
 /*
@@ -535,7 +543,8 @@ static int cf_ci_append_children (oconfig_item_t *dst, oconfig_item_t *src)
 } /* int cf_ci_append_children */
 
 #define CF_MAX_DEPTH 8
-static oconfig_item_t *cf_read_generic (const char *path, int depth);
+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)
 {
@@ -555,14 +564,18 @@ static int cf_include_all (oconfig_item_t *root, int depth)
 
                old = root->children + i;
 
-               if ((old->values_num != 1)
-                               || (old->values[0].type != OCONFIG_TYPE_STRING))
+               if ((old->values_num < 1) || (old->values_num > 2)
+                               || (old->values[0].type != OCONFIG_TYPE_STRING)
+                               || ((old->values_num == 2)
+                                       && (old->values[1].type != OCONFIG_TYPE_STRING)))
                {
-                       ERROR ("configfile: `Include' needs exactly one string argument.");
+                       ERROR ("configfile: `Include' needs exactly one or two string argument.");
                        continue;
                }
 
-               new = cf_read_generic (old->values[0].value.string, depth + 1);
+               new = cf_read_generic (old->values[0].value.string,
+                               (old->values_num == 2) ? old->values[1].value.string : NULL,
+                               depth + 1);
                if (new == NULL)
                        continue;
 
@@ -579,12 +592,31 @@ static int cf_include_all (oconfig_item_t *root, int depth)
        return (0);
 } /* int cf_include_all */
 
-static oconfig_item_t *cf_read_file (const char *file, int depth)
+static oconfig_item_t *cf_read_file (const char *file,
+               const char *pattern, int depth)
 {
        oconfig_item_t *root;
 
        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)) {
+                       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)
        {
@@ -602,7 +634,8 @@ 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)
+static oconfig_item_t *cf_read_dir (const char *dir,
+               const char *pattern, int depth)
 {
        oconfig_item_t *root = NULL;
        DIR *dh;
@@ -677,7 +710,7 @@ static oconfig_item_t *cf_read_dir (const char *dir, int depth)
                oconfig_item_t *temp;
                char *name = filenames[i];
 
-               temp = cf_read_generic (name, depth);
+               temp = cf_read_generic (name, pattern, depth);
                if (temp == NULL)
                {
                        /* An error should already have been reported. */
@@ -708,7 +741,8 @@ static oconfig_item_t *cf_read_dir (const char *dir, int depth)
  * 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)
+static oconfig_item_t *cf_read_generic (const char *path,
+               const char *pattern, int depth)
 {
        oconfig_item_t *root = NULL;
        int status;
@@ -761,9 +795,9 @@ static oconfig_item_t *cf_read_generic (const char *path, int depth)
                }
 
                if (S_ISREG (statbuf.st_mode))
-                       temp = cf_read_file (path_ptr, depth);
+                       temp = cf_read_file (path_ptr, pattern, depth);
                else if (S_ISDIR (statbuf.st_mode))
-                       temp = cf_read_dir (path_ptr, depth);
+                       temp = cf_read_dir (path_ptr, pattern, depth);
                else
                {
                        WARNING ("configfile: %s is neither a file nor a "
@@ -794,7 +828,8 @@ static oconfig_item_t *cf_read_generic (const char *path, int depth)
 /* #endif HAVE_WORDEXP_H */
 
 #else /* if !HAVE_WORDEXP_H */
-static oconfig_item_t *cf_read_generic (const char *path, int depth)
+static oconfig_item_t *cf_read_generic (const char *path,
+               const char *pattern, int depth)
 {
        struct stat statbuf;
        int status;
@@ -817,9 +852,9 @@ static oconfig_item_t *cf_read_generic (const char *path, int depth)
        }
 
        if (S_ISREG (statbuf.st_mode))
-               return (cf_read_file (path, depth));
+               return (cf_read_file (path, pattern, depth));
        else if (S_ISDIR (statbuf.st_mode))
-               return (cf_read_dir (path, depth));
+               return (cf_read_dir (path, pattern, depth));
 
        ERROR ("configfile: %s is neither a file nor a directory.", path);
        return (NULL);
@@ -993,7 +1028,7 @@ int cf_read (char *filename)
        oconfig_item_t *conf;
        int i;
 
-       conf = cf_read_generic (filename, 0 /* depth */);
+       conf = cf_read_generic (filename, /* pattern = */ NULL, /* depth = */ 0);
        if (conf == NULL)
        {
                ERROR ("Unable to read config file %s.", filename);