{GPL, other}: Relicense to MIT license.
[collectd.git] / src / configfile.c
index 4fe50cc..6a83072 100644 (file)
@@ -2,19 +2,23 @@
  * collectd - src/configfile.c
  * Copyright (C) 2005-2011  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.
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
  *
- * 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.
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
  *
- * 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
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
  *
  * Authors:
  *   Florian octo Forster <octo at collectd.org>
 # 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))
 
 /*
@@ -46,6 +58,7 @@ typedef struct cf_callback
        int  (*callback) (const char *, const char *);
        const char **keys;
        int    keys_num;
+       plugin_ctx_t ctx;
        struct cf_callback *next;
 } cf_callback_t;
 
@@ -53,6 +66,7 @@ typedef struct cf_complex_callback_s
 {
        char *type;
        int (*callback) (oconfig_item_t *);
+       plugin_ctx_t ctx;
        struct cf_complex_callback_s *next;
 } cf_complex_callback_t;
 
@@ -88,7 +102,7 @@ static cf_value_map_t cf_value_map[] =
        {"PluginDir",  dispatch_value_plugindir},
        {"LoadPlugin", dispatch_loadplugin}
 };
-static int cf_value_map_num = STATIC_ARRAY_LEN (cf_value_map);
+static int cf_value_map_num = STATIC_ARRAY_SIZE (cf_value_map);
 
 static cf_global_option_t cf_global_options[] =
 {
@@ -96,13 +110,17 @@ static cf_global_option_t cf_global_options[] =
        {"PIDFile",     NULL, PIDFILE},
        {"Hostname",    NULL, NULL},
        {"FQDNLookup",  NULL, "true"},
-       {"Interval",    NULL, "10"},
+       {"Interval",    NULL, NULL},
        {"ReadThreads", NULL, "5"},
+       {"WriteThreads", NULL, "5"},
+       {"WriteQueueLimitHigh", NULL, NULL},
+       {"WriteQueueLimitLow", NULL, NULL},
        {"Timeout",     NULL, "2"},
+       {"AutoLoadPlugin", NULL, "false"},
        {"PreCacheChain",  NULL, "PreCache"},
        {"PostCacheChain", NULL, "PostCache"}
 };
-static int cf_global_options_num = STATIC_ARRAY_LEN (cf_global_options);
+static int cf_global_options_num = STATIC_ARRAY_SIZE (cf_global_options);
 
 static int cf_default_typesdb = 1;
 
@@ -128,6 +146,7 @@ static int cf_dispatch (const char *type, const char *orig_key,
                const char *orig_value)
 {
        cf_callback_t *cf_cb;
+       plugin_ctx_t old_ctx;
        char *key;
        char *value;
        int ret;
@@ -156,6 +175,8 @@ static int cf_dispatch (const char *type, const char *orig_key,
 
        ret = -1;
 
+       old_ctx = plugin_set_ctx (cf_cb->ctx);
+
        for (i = 0; i < cf_cb->keys_num; i++)
        {
                if ((cf_cb->keys[i] != NULL)
@@ -166,6 +187,8 @@ static int cf_dispatch (const char *type, const char *orig_key,
                }
        }
 
+       plugin_set_ctx (old_ctx);
+
        if (i >= cf_cb->keys_num)
                WARNING ("Plugin `%s' did not register for value `%s'.", type, key);
 
@@ -244,6 +267,10 @@ static int dispatch_loadplugin (const oconfig_item_t *ci)
        int i;
        const char *name;
        unsigned int flags = 0;
+       plugin_ctx_t ctx;
+       plugin_ctx_t old_ctx;
+       int ret_val;
+
        assert (strcasecmp (ci->key, "LoadPlugin") == 0);
 
        if (ci->values_num != 1)
@@ -253,24 +280,23 @@ static int dispatch_loadplugin (const oconfig_item_t *ci)
 
        name = ci->values[0].value.string;
 
-       /*
-        * XXX: Magic at work:
-        *
-        * Some of the language bindings, for example the Python and Perl
-        * plugins, need to be able to export symbols to the scripts they run.
-        * For this to happen, the "Globals" flag needs to be set.
-        * Unfortunately, this technical detail is hard to explain to the
-        * average user and she shouldn't have to worry about this, ideally.
-        * So in order to save everyone's sanity use a different default for a
-        * handful of special plugins. --octo
-        */
-       if ((strcasecmp ("Perl", name) == 0)
-                       || (strcasecmp ("Python", name) == 0))
-               flags |= PLUGIN_FLAGS_GLOBAL;
+       /* default to the global interval set before loading this plugin */
+       memset (&ctx, 0, sizeof (ctx));
+       ctx.interval = cf_get_default_interval ();
 
        for (i = 0; i < ci->children_num; ++i) {
                if (strcasecmp("Globals", ci->children[i].key) == 0)
                        cf_util_get_flag (ci->children + i, &flags, PLUGIN_FLAGS_GLOBAL);
+               else if (strcasecmp ("Interval", ci->children[i].key) == 0) {
+                       double interval = 0.0;
+
+                       if (cf_util_get_double (ci->children + i, &interval) != 0) {
+                               /* cf_util_get_double will log an error */
+                               continue;
+                       }
+
+                       ctx.interval = DOUBLE_TO_CDTIME_T (interval);
+               }
                else {
                        WARNING("Ignoring unknown LoadPlugin option \"%s\" "
                                        "for plugin \"%s\"",
@@ -278,7 +304,12 @@ static int dispatch_loadplugin (const oconfig_item_t *ci)
                }
        }
 
-       return (plugin_load (name, (uint32_t) flags));
+       old_ctx = plugin_set_ctx (ctx);
+       ret_val = plugin_load (name, (uint32_t) flags);
+       /* reset to the "global" context */
+       plugin_set_ctx (old_ctx);
+
+       return (ret_val);
 } /* int dispatch_value_loadplugin */
 
 static int dispatch_value_plugin (const char *plugin, oconfig_item_t *ci)
@@ -355,10 +386,33 @@ static int dispatch_block_plugin (oconfig_item_t *ci)
 
        name = ci->values[0].value.string;
 
+       if (IS_TRUE (global_option_get ("AutoLoadPlugin")))
+       {
+               int status;
+
+               status = plugin_load (name, /* flags = */ 0);
+               if (status != 0)
+               {
+                       ERROR ("Automatically loading plugin \"%s\" failed "
+                                       "with status %i.", name, status);
+                       return (status);
+               }
+       }
+
        /* 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));
+               {
+                       plugin_ctx_t old_ctx;
+                       int ret_val;
+
+                       old_ctx = plugin_set_ctx (cb->ctx);
+                       ret_val = (cb->callback (ci));
+                       plugin_set_ctx (old_ctx);
+                       return (ret_val);
+               }
+       }
 
        /* Hm, no complex plugin found. Dispatch the values one by one */
        for (i = 0; i < ci->children_num; i++)
@@ -495,7 +549,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)
 {
@@ -506,9 +561,9 @@ static int cf_include_all (oconfig_item_t *root, int depth)
                oconfig_item_t *new;
                oconfig_item_t *old;
 
-               /* Ignore all blocks, including `Include' blocks. */
-               if (root->children[i].children_num != 0)
-                       continue;
+               char *pattern = NULL;
+
+               int j;
 
                if (strcasecmp (root->children[i].key, "Include") != 0)
                        continue;
@@ -522,9 +577,22 @@ static int cf_include_all (oconfig_item_t *root, int depth)
                        continue;
                }
 
-               new = cf_read_generic (old->values[0].value.string, depth + 1);
+               for (j = 0; j < old->children_num; ++j)
+               {
+                       oconfig_item_t *child = old->children + j;
+
+                       if (strcasecmp (child->key, "Filter") == 0)
+                               cf_util_get_string (child, &pattern);
+                       else
+                               ERROR ("configfile: Option `%s' not allowed in <Include> block.",
+                                               child->key);
+               }
+
+               new = cf_read_generic (old->values[0].value.string, pattern, depth + 1);
+               sfree (pattern);
+
                if (new == NULL)
-                       continue;
+                       return (-1);
 
                /* Now replace the i'th child in `root' with `new'. */
                cf_ci_replace_child (root, new, i);
@@ -539,12 +607,35 @@ 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;
+       int status;
 
        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)) {
+                       DEBUG ("configfile: Not including `%s' because it "
+                                       "does not match pattern `%s'.",
+                                       filename, pattern);
+                       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)
        {
@@ -552,7 +643,12 @@ static oconfig_item_t *cf_read_file (const char *file, int depth)
                return (NULL);
        }
 
-       cf_include_all (root, depth);
+       status = cf_include_all (root, depth);
+       if (status != 0)
+       {
+               oconfig_free (root);
+               return (NULL);
+       }
 
        return (root);
 } /* oconfig_item_t *cf_read_file */
@@ -562,7 +658,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;
@@ -637,7 +734,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. */
@@ -668,7 +765,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;
@@ -721,9 +819,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 "
@@ -743,18 +841,13 @@ static oconfig_item_t *cf_read_generic (const char *path, int depth)
 
        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)
+static oconfig_item_t *cf_read_generic (const char *path,
+               const char *pattern, int depth)
 {
        struct stat statbuf;
        int status;
@@ -777,9 +870,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);
@@ -828,6 +921,46 @@ const char *global_option_get (const char *option)
                        : cf_global_options[i].def);
 } /* char *global_option_get */
 
+long global_option_get_long (const char *option, long default_value)
+{
+               const char *str;
+               long value;
+
+               str = global_option_get (option);
+               if (NULL == str)
+                       return (default_value);
+
+               errno = 0;
+               value = strtol (str, /* endptr = */ NULL, /* base = */ 0);
+               if (errno != 0)
+                       return (default_value);
+
+               return (value);
+} /* char *global_option_get_long */
+
+cdtime_t cf_get_default_interval (void)
+{
+  char const *str = global_option_get ("Interval");
+  double interval_double = COLLECTD_DEFAULT_INTERVAL;
+
+  if (str != NULL)
+  {
+    char *endptr = NULL;
+    double tmp = strtod (str, &endptr);
+
+    if ((endptr == NULL) || (endptr == str) || (*endptr != 0))
+      ERROR ("cf_get_default_interval: Unable to parse string \"%s\" "
+          "as number.", str);
+    else if (tmp <= 0.0)
+      ERROR ("cf_get_default_interval: Interval must be a positive number. "
+          "The current number is %g.", tmp);
+    else
+      interval_double = tmp;
+  }
+
+  return (DOUBLE_TO_CDTIME_T (interval_double));
+} /* }}} cdtime_t cf_get_default_interval */
+
 void cf_unregister (const char *type)
 {
        cf_callback_t *this, *prev;
@@ -884,6 +1017,7 @@ void cf_register (const char *type,
        cf_cb->callback = callback;
        cf_cb->keys     = keys;
        cf_cb->keys_num = keys_num;
+       cf_cb->ctx      = plugin_get_ctx ();
 
        cf_cb->next = first_callback;
        first_callback = cf_cb;
@@ -907,6 +1041,8 @@ int cf_register_complex (const char *type, int (*callback) (oconfig_item_t *))
        new->callback = callback;
        new->next = NULL;
 
+       new->ctx = plugin_get_ctx ();
+
        if (complex_callback_head == NULL)
        {
                complex_callback_head = new;
@@ -927,12 +1063,18 @@ 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);
                return (-1);
        }
+       else if (conf->children_num == 0)
+       {
+               ERROR ("Configuration file %s is empty.", filename);
+               oconfig_free (conf);
+               return (-1);
+       }
 
        for (i = 0; i < conf->children_num; i++)
        {
@@ -1015,6 +1157,23 @@ int cf_util_get_int (const oconfig_item_t *ci, int *ret_value) /* {{{ */
        return (0);
 } /* }}} int cf_util_get_int */
 
+int cf_util_get_double (const oconfig_item_t *ci, double *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_double: The %s option requires "
+                               "exactly one numeric argument.", ci->key);
+               return (-1);
+       }
+
+       *ret_value = ci->values[0].value.number;
+
+       return (0);
+} /* }}} int cf_util_get_double */
+
 int cf_util_get_boolean (const oconfig_item_t *ci, _Bool *ret_bool) /* {{{ */
 {
        if ((ci == NULL) || (ret_bool == NULL))