{GPL, other}: Relicense to MIT license.
[collectd.git] / src / filter_chain.c
index e51e07e..c87b877 100644 (file)
@@ -1,65 +1,33 @@
 /**
- * collectd - src/filter_chain.h
- * Copyright (C) 2008  Florian octo Forster
+ * collectd - src/filter_chain.c
+ * Copyright (C) 2008-2010  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; only version 2 of the License is applicable.
+ * 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 verplant.org>
+ *   Florian octo Forster <octo at collectd.org>
  **/
 
-/*
- * First tell the compiler to stick to the C99 and POSIX standards as close as
- * possible.
- */
-#ifndef __STRICT_ANSI__ /* {{{ */
-# define __STRICT_ANSI__
-#endif
-
-#ifndef _ISOC99_SOURCE
-# define _ISOC99_SOURCE
-#endif
-
-#ifdef _POSIX_C_SOURCE
-# undef _POSIX_C_SOURCE
-#endif
-#define _POSIX_C_SOURCE 200112L
-
-#if 0
-/* Single UNIX needed for strdup. */
-#ifdef _XOPEN_SOURCE
-# undef _XOPEN_SOURCE
-#endif
-#define _XOPEN_SOURCE 500
-#endif
-
-#ifndef _REENTRANT
-# define _REENTRANT
-#endif
-
-#ifndef _THREAD_SAFE
-# define _THREAD_SAFE
-#endif
-
-#ifdef _GNU_SOURCE
-# undef _GNU_SOURCE
-#endif
-/* }}} */
-
 #include "collectd.h"
 #include "configfile.h"
 #include "plugin.h"
+#include "utils_complain.h"
 #include "common.h"
 #include "filter_chain.h"
 
@@ -91,9 +59,9 @@ struct fc_target_s
 }; /* }}} */
 
 /* List of rules, used in fc_chain_t */
-struct fc_fule_s;
-typedef struct fc_fule_s fc_rule_t; /* {{{ */
-struct fc_fule_s
+struct fc_rule_s;
+typedef struct fc_rule_s fc_rule_t; /* {{{ */
+struct fc_rule_s
 {
   char name[DATA_MAX_NAME_LEN];
   fc_match_t  *matches;
@@ -102,9 +70,7 @@ struct fc_fule_s
 }; /* }}} */
 
 /* List of chains, used for `chain_list_head' */
-struct fc_chain_s;
-typedef struct fc_chain_s fc_chain_t; /* {{{ */
-struct fc_chain_s
+struct fc_chain_s /* {{{ */
 {
   char name[DATA_MAX_NAME_LEN];
   fc_rule_t   *rules;
@@ -131,7 +97,7 @@ static void fc_free_matches (fc_match_t *m) /* {{{ */
     (*m->proc.destroy) (&m->user_data);
   else if (m->user_data != NULL)
   {
-    ERROR ("Filter sybsystem: fc_free_matches: There is user data, but no "
+    ERROR ("Filter subsystem: fc_free_matches: There is user data, but no "
         "destroy functions has been specified. "
         "Memory will probably be lost!");
   }
@@ -151,7 +117,7 @@ static void fc_free_targets (fc_target_t *t) /* {{{ */
     (*t->proc.destroy) (&t->user_data);
   else if (t->user_data != NULL)
   {
-    ERROR ("Filter sybsystem: fc_free_targets: There is user data, but no "
+    ERROR ("Filter subsystem: fc_free_targets: There is user data, but no "
         "destroy functions has been specified. "
         "Memory will probably be lost!");
   }
@@ -213,7 +179,7 @@ static char *fc_strdup (const char *orig) /* {{{ */
  *
  * The configuration looks somewhat like this:
  *
- *  <Chain "main">
+ *  <Chain "PreCache">
  *    <Rule>
  *      <Match "regex">
  *        Plugin "^mysql$"
@@ -270,19 +236,21 @@ static int fc_config_add_match (fc_match_t **matches_head, /* {{{ */
 
   sstrncpy (m->name, ptr->name, sizeof (m->name));
   memcpy (&m->proc, &ptr->proc, sizeof (m->proc));
-  assert (m->proc.create != NULL);
   m->user_data = NULL;
   m->next = NULL;
 
-  status = (*m->proc.create) (ci, &m->user_data);
-  if (status != 0)
+  if (m->proc.create != NULL)
   {
-    WARNING ("Filter subsystem: Failed to create a %s match.",
-        m->name);
-    fc_free_matches (m);
-    return (-1);
+    status = (*m->proc.create) (ci, &m->user_data);
+    if (status != 0)
+    {
+      WARNING ("Filter subsystem: Failed to create a %s match.",
+          m->name);
+      fc_free_matches (m);
+      return (-1);
+    }
   }
-  
+
   if (*matches_head != NULL)
   {
     ptr = *matches_head;
@@ -333,7 +301,7 @@ static int fc_config_add_target (fc_target_t **targets_head, /* {{{ */
   t = (fc_target_t *) malloc (sizeof (*t));
   if (t == NULL)
   {
-    ERROR ("fc_config_add_match: malloc failed.");
+    ERROR ("fc_config_add_target: malloc failed.");
     return (-1);
   }
   memset (t, 0, sizeof (*t));
@@ -348,7 +316,7 @@ static int fc_config_add_target (fc_target_t **targets_head, /* {{{ */
     status = (*t->proc.create) (ci, &t->user_data);
     if (status != 0)
     {
-      WARNING ("Filter subsystem: Failed to create a %s match.",
+      WARNING ("Filter subsystem: Failed to create a %s target.",
           t->name);
       fc_free_targets (t);
       return (-1);
@@ -472,9 +440,10 @@ static int fc_config_add_rule (fc_chain_t *chain, /* {{{ */
 
 static int fc_config_add_chain (const oconfig_item_t *ci) /* {{{ */
 {
-  fc_chain_t *chain;
+  fc_chain_t *chain = NULL;
   int status = 0;
   int i;
+  int new_chain = 1;
 
   if ((ci->values_num != 1)
       || (ci->values[0].type != OCONFIG_TYPE_STRING))
@@ -484,17 +453,26 @@ static int fc_config_add_chain (const oconfig_item_t *ci) /* {{{ */
     return (-1);
   }
 
-  chain = (fc_chain_t *) malloc (sizeof (*chain));
+  if (chain_list_head != NULL)
+  {
+    if ((chain = fc_chain_get_by_name (ci->values[0].value.string)) != NULL)
+      new_chain = 0;
+  }
+
   if (chain == NULL)
   {
-    ERROR ("fc_config_add_chain: malloc failed.");
-    return (-1);
+    chain = (fc_chain_t *) malloc (sizeof (*chain));
+    if (chain == NULL)
+    {
+      ERROR ("fc_config_add_chain: malloc failed.");
+      return (-1);
+    }
+    memset (chain, 0, sizeof (*chain));
+    sstrncpy (chain->name, ci->values[0].value.string, sizeof (chain->name));
+    chain->rules = NULL;
+    chain->targets = NULL;
+    chain->next = NULL;
   }
-  memset (chain, 0, sizeof (*chain));
-  sstrncpy (chain->name, ci->values[0].value.string, sizeof (chain->name));
-  chain->rules = NULL;
-  chain->targets = NULL;
-  chain->next = NULL;
 
   for (i = 0; i < ci->children_num; i++)
   {
@@ -516,21 +494,6 @@ static int fc_config_add_chain (const oconfig_item_t *ci) /* {{{ */
       break;
   } /* for (ci->children) */
 
-  /* Additional sanity checking. */
-  while (status == 0)
-  {
-    if (chain->targets == NULL)
-    {
-      WARNING ("Filter subsystem: Chain %s: No default target has been "
-          "specified. Please make sure that there is a <Target> block within "
-          "the <Chain> block!", chain->name);
-      status = -1;
-      break;
-    }
-
-    break;
-  } /* while (status == 0) */
-
   if (status != 0)
   {
     fc_free_chains (chain);
@@ -539,6 +502,9 @@ static int fc_config_add_chain (const oconfig_item_t *ci) /* {{{ */
 
   if (chain_list_head != NULL)
   {
+    if (!new_chain)
+      return (0);
+
     fc_chain_t *ptr;
 
     ptr = chain_list_head;
@@ -555,103 +521,6 @@ static int fc_config_add_chain (const oconfig_item_t *ci) /* {{{ */
   return (0);
 } /* }}} int fc_config_add_chain */
 
-int fc_process_chain (const data_set_t *ds, value_list_t *vl, /* {{{ */
-    fc_chain_t *chain)
-{
-  fc_rule_t *rule;
-  fc_target_t *target;
-  int status;
-
-  if (chain == NULL)
-    return (-1);
-
-  DEBUG ("fc_process_chain (chain = %s);", chain->name);
-
-  status = FC_ACTION_CONTINUE;
-
-  for (rule = chain->rules; rule != NULL; rule = rule->next)
-  {
-    fc_match_t *match;
-
-    if (rule->name[0] != 0)
-    {
-      DEBUG ("fc_process_chain: Testing the `%s' rule.", rule->name);
-    }
-
-    /* N. B.: rule->matches may be NULL. */
-    for (match = rule->matches; match != NULL; match = match->next)
-    {
-      status = (*match->proc.match) (ds, vl, /* meta = */ NULL,
-          &match->user_data);
-      if (status < 0)
-      {
-        WARNING ("fc_process_chain: A match failed.");
-        break;
-      }
-      else if (status != FC_MATCH_MATCHES)
-        break;
-    }
-
-    /* for-loop has been aborted: Either error or no match. */
-    if (match != NULL)
-      continue;
-
-    if (rule->name[0] != 0)
-    {
-      DEBUG ("fc_process_chain: Rule `%s' matches.", rule->name);
-    }
-
-    for (target = rule->targets; target != NULL; target = target->next)
-    {
-      /* If we get here, all matches have matched the value. Execute the
-       * target. */
-      status = (*target->proc.invoke) (ds, vl, /* meta = */ NULL,
-          &target->user_data);
-      if (status < 0)
-      {
-        WARNING ("fc_process_chain: A target failed.");
-        continue;
-      }
-      else if (status == FC_ACTION_CONTINUE)
-        continue;
-      else if (status == FC_ACTION_STOP)
-        break;
-      else
-      {
-        WARNING ("fc_process_chain: Unknown target return value: %i", status);
-      }
-    }
-
-    if (status == FC_ACTION_STOP)
-    {
-      if (rule->name[0] != 0)
-      {
-        DEBUG ("fc_process_chain: Rule `%s' signaled the stop condition.",
-            rule->name);
-      }
-      break;
-    }
-  } /* for (rule) */
-
-  /* for-loop has been aborted: A target returned `FC_ACTION_STOP' */
-  if (rule != NULL)
-    return (0);
-
-  for (target = chain->targets; target != NULL; target = target->next)
-  {
-    /* If we get here, all matches have matched the value. Execute the
-     * target. */
-    status = (*target->proc.invoke) (ds, vl, /* meta = */ NULL,
-        &target->user_data);
-    if (status < 0)
-    {
-      WARNING ("fc_process_chain: The default target failed.");
-    }
-  }
-
-  return (0);
-} /* }}} int fc_process_chain */
-
 /*
  * Built-in target "jump"
  *
@@ -708,7 +577,8 @@ static int fc_bit_jump_destroy (void **user_data) /* {{{ */
 } /* }}} int fc_bit_jump_destroy */
 
 static int fc_bit_jump_invoke (const data_set_t *ds, /* {{{ */
-    value_list_t *vl, notification_meta_t **meta, void **user_data)
+    value_list_t *vl, notification_meta_t __attribute__((unused)) **meta,
+    void **user_data)
 {
   char *chain_name;
   fc_chain_t *chain;
@@ -730,16 +600,28 @@ static int fc_bit_jump_invoke (const data_set_t *ds, /* {{{ */
   status = fc_process_chain (ds, vl, chain);
   if (status < 0)
     return (status);
-
-  return (FC_ACTION_CONTINUE);
+  else if (status == FC_TARGET_STOP)
+    return (FC_TARGET_STOP);
+  else
+    return (FC_TARGET_CONTINUE);
 } /* }}} int fc_bit_jump_invoke */
 
-static int fc_bit_stop_invoke (const data_set_t *ds, /* {{{ */
-    value_list_t *vl, notification_meta_t **meta, void **user_data)
+static int fc_bit_stop_invoke (const data_set_t __attribute__((unused)) *ds, /* {{{ */
+    value_list_t __attribute__((unused)) *vl,
+    notification_meta_t __attribute__((unused)) **meta,
+    void __attribute__((unused)) **user_data)
 {
-  return (FC_ACTION_STOP);
+  return (FC_TARGET_STOP);
 } /* }}} int fc_bit_stop_invoke */
 
+static int fc_bit_return_invoke (const data_set_t __attribute__((unused)) *ds, /* {{{ */
+    value_list_t __attribute__((unused)) *vl,
+    notification_meta_t __attribute__((unused)) **meta,
+    void __attribute__((unused)) **user_data)
+{
+  return (FC_TARGET_RETURN);
+} /* }}} int fc_bit_return_invoke */
+
 static int fc_bit_write_create (const oconfig_item_t *ci, /* {{{ */
     void **user_data)
 {
@@ -817,7 +699,8 @@ static int fc_bit_write_destroy (void **user_data) /* {{{ */
 } /* }}} int fc_bit_write_destroy */
 
 static int fc_bit_write_invoke (const data_set_t *ds, /* {{{ */
-    value_list_t *vl, notification_meta_t **meta, void **user_data)
+    value_list_t *vl, notification_meta_t __attribute__((unused)) **meta,
+    void **user_data)
 {
   char **plugin_list;
   int status;
@@ -828,12 +711,31 @@ static int fc_bit_write_invoke (const data_set_t *ds, /* {{{ */
 
   if ((plugin_list == NULL) || (plugin_list[0] == NULL))
   {
+    static c_complain_t enoent_complaint = C_COMPLAIN_INIT_STATIC;
+
     status = plugin_write (/* plugin = */ NULL, ds, vl);
-    if (status != 0)
+    if (status == ENOENT)
+    {
+      /* in most cases this is a permanent error, so use the complain
+       * mechanism rather than spamming the logs */
+      c_complain (LOG_INFO, &enoent_complaint,
+          "Filter subsystem: Built-in target `write': Dispatching value to "
+          "all write plugins failed with status %i (ENOENT). "
+          "Most likely this means you didn't load any write plugins.",
+          status);
+    }
+    else if (status != 0)
     {
       INFO ("Filter subsystem: Built-in target `write': Dispatching value to "
           "all write plugins failed with status %i.", status);
     }
+    else
+    {
+      assert (status == 0);
+      c_release (LOG_INFO, &enoent_complaint, "Filter subsystem: "
+          "Built-in target `write': Some write plugin is back to normal "
+          "operation. `write' succeeded.");
+    }
   }
   else
   {
@@ -850,7 +752,7 @@ static int fc_bit_write_invoke (const data_set_t *ds, /* {{{ */
     } /* for (i = 0; plugin_list[i] != NULL; i++) */
   }
 
-  return (FC_ACTION_CONTINUE);
+  return (FC_TARGET_CONTINUE);
 } /* }}} int fc_bit_write_invoke */
 
 static int fc_init_once (void) /* {{{ */
@@ -874,6 +776,12 @@ static int fc_init_once (void) /* {{{ */
   fc_register_target ("stop", tproc);
 
   memset (&tproc, 0, sizeof (tproc));
+  tproc.create  = NULL;
+  tproc.destroy = NULL;
+  tproc.invoke  = fc_bit_return_invoke;
+  fc_register_target ("return", tproc);
+
+  memset (&tproc, 0, sizeof (tproc));
   tproc.create  = fc_bit_write_create;
   tproc.destroy = fc_bit_write_destroy;
   tproc.invoke  = fc_bit_write_invoke;
@@ -954,23 +862,182 @@ int fc_register_target (const char *name, target_proc_t proc) /* {{{ */
   return (0);
 } /* }}} int fc_register_target */
 
-/* Iterate over all rules in the chain and execute all targets for which all
- * matches match. */
-int fc_process (const data_set_t *ds, value_list_t *vl) /* {{{ */
+fc_chain_t *fc_chain_get_by_name (const char *chain_name) /* {{{ */
 {
   fc_chain_t *chain;
 
+  if (chain_name == NULL)
+    return (NULL);
+
   for (chain = chain_list_head; chain != NULL; chain = chain->next)
-    if (strcasecmp ("Main", chain->name) == 0)
+    if (strcasecmp (chain_name, chain->name) == 0)
+      return (chain);
+
+  return (NULL);
+} /* }}} int fc_chain_get_by_name */
+
+int fc_process_chain (const data_set_t *ds, value_list_t *vl, /* {{{ */
+    fc_chain_t *chain)
+{
+  fc_rule_t *rule;
+  fc_target_t *target;
+  int status;
+
+  if (chain == NULL)
+    return (-1);
+
+  DEBUG ("fc_process_chain (chain = %s);", chain->name);
+
+  status = FC_TARGET_CONTINUE;
+  for (rule = chain->rules; rule != NULL; rule = rule->next)
+  {
+    fc_match_t *match;
+
+    if (rule->name[0] != 0)
+    {
+      DEBUG ("fc_process_chain (%s): Testing the `%s' rule.",
+          chain->name, rule->name);
+    }
+
+    /* N. B.: rule->matches may be NULL. */
+    for (match = rule->matches; match != NULL; match = match->next)
+    {
+      /* FIXME: Pass the meta-data to match targets here (when implemented). */
+      status = (*match->proc.match) (ds, vl, /* meta = */ NULL,
+          &match->user_data);
+      if (status < 0)
+      {
+        WARNING ("fc_process_chain (%s): A match failed.", chain->name);
+        break;
+      }
+      else if (status != FC_MATCH_MATCHES)
+        break;
+    }
+
+    /* for-loop has been aborted: Either error or no match. */
+    if (match != NULL)
+    {
+      status = FC_TARGET_CONTINUE;
+      continue;
+    }
+
+    if (rule->name[0] != 0)
+    {
+      DEBUG ("fc_process_chain (%s): Rule `%s' matches.",
+          chain->name, rule->name);
+    }
+
+    for (target = rule->targets; target != NULL; target = target->next)
+    {
+      /* If we get here, all matches have matched the value. Execute the
+       * target. */
+      /* FIXME: Pass the meta-data to match targets here (when implemented). */
+      status = (*target->proc.invoke) (ds, vl, /* meta = */ NULL,
+          &target->user_data);
+      if (status < 0)
+      {
+        WARNING ("fc_process_chain (%s): A target failed.", chain->name);
+        continue;
+      }
+      else if (status == FC_TARGET_CONTINUE)
+        continue;
+      else if (status == FC_TARGET_STOP)
+        break;
+      else if (status == FC_TARGET_RETURN)
+        break;
+      else
+      {
+        WARNING ("fc_process_chain (%s): Unknown return value "
+            "from target `%s': %i",
+            chain->name, target->name, status);
+      }
+    }
+
+    if ((status == FC_TARGET_STOP)
+        || (status == FC_TARGET_RETURN))
+    {
+      if (rule->name[0] != 0)
+      {
+        DEBUG ("fc_process_chain (%s): Rule `%s' signaled "
+            "the %s condition.",
+            chain->name, rule->name,
+            (status == FC_TARGET_STOP) ? "stop" : "return");
+      }
+      break;
+    }
+    else
+    {
+      status = FC_TARGET_CONTINUE;
+    }
+  } /* for (rule) */
+
+  if (status == FC_TARGET_STOP)
+    return (FC_TARGET_STOP);
+  else if (status == FC_TARGET_RETURN)
+    return (FC_TARGET_CONTINUE);
+
+  /* for-loop has been aborted: A target returned `FC_TARGET_STOP' */
+  if (rule != NULL)
+    return (FC_TARGET_CONTINUE);
+
+  DEBUG ("fc_process_chain (%s): Executing the default targets.",
+      chain->name);
+
+  status = FC_TARGET_CONTINUE;
+  for (target = chain->targets; target != NULL; target = target->next)
+  {
+    /* If we get here, all matches have matched the value. Execute the
+     * target. */
+    /* FIXME: Pass the meta-data to match targets here (when implemented). */
+    status = (*target->proc.invoke) (ds, vl, /* meta = */ NULL,
+        &target->user_data);
+    if (status < 0)
+    {
+      WARNING ("fc_process_chain (%s): The default target failed.",
+          chain->name);
+    }
+    else if (status == FC_TARGET_CONTINUE)
+      continue;
+    else if (status == FC_TARGET_STOP)
+      break;
+    else if (status == FC_TARGET_RETURN)
       break;
+    else
+    {
+      WARNING ("fc_process_chain (%s): Unknown return value "
+          "from target `%s': %i",
+          chain->name, target->name, status);
+    }
+  }
 
-  if (chain != NULL)
-    return (fc_process_chain (ds, vl, chain));
+  if ((status == FC_TARGET_STOP)
+      || (status == FC_TARGET_RETURN))
+  {
+    assert (target != NULL);
+    DEBUG ("fc_process_chain (%s): Default target `%s' signaled "
+        "the %s condition.",
+        chain->name, target->name,
+        (status == FC_TARGET_STOP) ? "stop" : "return");
+    if (status == FC_TARGET_STOP)
+      return (FC_TARGET_STOP);
+    else
+      return (FC_TARGET_CONTINUE);
+  }
 
-  ERROR ("fc_process: TODO: Implement default behavior!");
+  DEBUG ("fc_process_chain (%s): Signaling `continue' at end of chain.",
+      chain->name);
 
-  return (0);
-} /* }}} int fc_process */
+  return (FC_TARGET_CONTINUE);
+} /* }}} int fc_process_chain */
+
+/* Iterate over all rules in the chain and execute all targets for which all
+ * matches match. */
+int fc_default_action (const data_set_t *ds, value_list_t *vl) /* {{{ */
+{
+  /* FIXME: Pass the meta-data to match targets here (when implemented). */
+  return (fc_bit_write_invoke (ds, vl,
+        /* meta = */ NULL, /* user_data = */ NULL));
+} /* }}} int fc_default_action */
 
 int fc_configure (const oconfig_item_t *ci) /* {{{ */
 {