src/filter_chain.[ch]: Implement an advanced filtering framework.
authorFlorian Forster <octo@huhu.verplant.org>
Fri, 21 Nov 2008 21:09:53 +0000 (22:09 +0100)
committerFlorian Forster <octo@huhu.verplant.org>
Fri, 21 Nov 2008 21:09:53 +0000 (22:09 +0100)
The concept separates `filter' plugins into `match' and `target' plugins. Those
two parts can be combined in wild variation, allowing *very* flexible
mechanisms.

The concept is explained in more detail in the `FILTER CONFIGURATION' section
of collectd.conf(5), also added with this commit.

src/Makefile.am
src/collectd.conf.pod
src/filter_chain.c [new file with mode: 0644]
src/filter_chain.h [new file with mode: 0644]

index b6d1a08..960dfe4 100644 (file)
@@ -29,6 +29,7 @@ bin_PROGRAMS = collectd-nagios
 collectd_SOURCES = collectd.c collectd.h \
                   common.c common.h \
                   configfile.c configfile.h \
+                  filter_chain.c filter_chain.h \
                   plugin.c plugin.h \
                   utils_avltree.c utils_avltree.h \
                   utils_cache.c utils_cache.h \
index 9889b61..9ce2ae9 100644 (file)
@@ -2451,6 +2451,251 @@ only one such notification is generated until the value appears again.
 
 =back
 
+=head1 FILTER CONFIGURATION
+
+TODO: Update this entire section once development is done.
+
+Starting with collectd 4.6 there is a powerful filtering infrastructure
+implemented in the daemon. The concept has mostly been copied from I<iptables>,
+the packet filter infrastructure for Linux. We'll use a similar terminology, so
+that users that are familiar with iptables feel right at home.
+
+=head2 Terminology
+
+The most important terms are:
+
+=over 4
+
+=item B<Match>
+
+A I<match> is a criteria to select specific values. Examples are, of course, the
+name of the value or it's current value.
+
+=item B<Target>
+
+A I<target> is some action that is to be performed with data. Such actions
+could, for example, be to change part of the value's identifier or to ignore
+the value completely. Built-in functions are B<write> and B<stop>, see below.
+
+Some targets, for example the built-in B<stop> target, signal that processing
+of a value should be stopped. In that case processing of the current chain will
+be aborted.
+
+=item B<Rule>
+
+The combination of any number of matches and at least one target is called a
+I<rule>. The target actions will be performed for all values for which B<all>
+matches apply. If the rule does not have any matches associated with it, the
+target action will be performed for all values.
+
+If any target returns the stop condition, the processing will stop right away.
+This means that any targets following the current one will not be called after
+the stop condition has been returned.
+
+=item B<Chain>
+
+A I<chain> is a list of rules and possibly default targets. The rules are tried
+in order and if one matches, the associated target will be called. If a value
+is handled by a rule, it depends on the target whether or not any subsequent
+rules are considered or if traversal of the chain is aborted. After all rules
+have been checked and no target returned the stop condition, the default
+targets will be executed.
+
+=back
+
+=head2 General structure
+
+The following shows the resulting structure:
+
+ +---------+
+ ! Chain   !
+ +---------+
+      !
+      V
+ +---------+  +---------+  +---------+  +---------+
+ ! Rule    !->! Match   !->! Match   !->! Target  !
+ +---------+  +---------+  +---------+  +---------+
+      !
+      V
+ +---------+  +---------+  +---------+
+ ! Rule    !->! Target  !->! Target  !
+ +---------+  +---------+  +---------+
+      !
+      V
+      :
+      :
+      !
+      V
+ +---------+  +---------+  +---------+
+ ! Rule    !->! Match   !->! Target  !
+ +---------+  +---------+  +---------+
+      !
+      V
+ +---------+
+ ! Default !
+ ! Target  !
+ +---------+
+
+=head2 Synopsis
+
+The configuration reflects this structure directly:
+
+ <Chain "main">
+   <Rule "ignore_mysql_show">
+     <Match "regex">
+       Plugin "^mysql$"
+       Type "^mysql_command$"
+       TypeInstance "^show_"
+     </Match>
+     <Target "stop">
+     </Target>
+   </Rule>
+   <Target "write">
+     Plugin "rrdtool"
+   </Target>
+ </Chain>
+
+The above configuration example will ignore all values where the plugin field
+is "mysql", the type is "mysql_command" and the type instance begins with
+"show_". All other values will be sent to the "rrdtool" write plugin via the
+default target of the chain.
+
+=head2 List of configuration options
+
+=over 4
+
+=item B<Chain> I<Name>
+
+Adds a new chain with a certain name. This name can be used to refer to a
+specific chain, for example to jump to it.
+
+Within the B<Chain> block, there can be B<Rule> blocks and B<Target> blocks.
+
+=item B<Rule> [I<Name>]
+
+Adds a new rule to the current chain. The name of the rule is optional and
+currently has no meaning for the daemon.
+
+Within the B<Rule> block, there may be any number of B<Match> blocks and there
+must be at least one B<Target> block.
+
+=item B<Match> I<Name>
+
+Adds a match to a B<Rule> block. The name specifies what kind of match should
+be performed. Available matches depend on the plugins that have been loaded.
+
+The arguments inside the B<Match> block are passed to the plugin implementing
+the match, so which arguments are valid here depends on the plugin being used.
+If you do not need any to pass any arguments to a match, you can use the
+shorter syntax:
+
+ Match "foobar"
+
+Which is equivalent to:
+
+ <Match "foobar">
+ </Match>
+
+=item B<Target> I<Name>
+
+Add a target to a rule or a default target to a chain. The name specifies what
+kind of target is to be added. Which targets are available depends on the
+plugins being loaded.
+
+The arguments inside the B<Target> block are passed to the plugin implementing
+the target, so which arguments are valid here depends on the plugin being used.
+If you do not need any to pass any arguments to a target, you can use the
+shorter syntax:
+
+ Target "stop"
+
+This is the same as writing:
+
+ <Target "stop">
+ </Target>
+
+=back
+
+=head2 Built-in targets 
+
+The following targets are built into the core daemon and therefore need no
+plugins to be loaded:
+
+=over 4
+
+=item B<stop>
+
+Does nothing except returning with the stop condition, causing processing of
+the current chain to be aborted.
+
+=item B<write>
+
+Sends the value to write plugins.
+
+Available options:
+
+=over 4
+
+=item B<Plugin> I<Name>
+
+Name of the write plugin to which the data should be sent. This option may be
+given multiple times to send the data to more than one write plugin.
+
+Example:
+
+ Target "stop"
+
+=back
+
+If no plugin is explicitly specified, the values will be sent to all available
+write plugins.
+
+Example:
+
+ <Target "write">
+   Plugin "rrdtool"
+ </Target>
+
+=item B<jump>
+
+Starts processing the rules of another chain. If the end of that chain is
+reached, or a stop condition is encountered, processing will continue right
+after the B<jump> target, i.E<nbsp>e. with the next target or the next rule.
+
+Available options:
+
+=over 4
+
+=item B<Chain> I<Name>
+
+Jumps to the chain I<Name>. This argument is required and may appear only once.
+
+=back
+
+Example:
+
+ <Target "jump">
+   Chain "foobar"
+ </Target>
+
+=back
+
+=head2 Backwards compatibility
+
+If you use collectd with an old configuration, i.E<nbsp>e. one without a
+B<Chain> block, it will behave as it used to. This is equivalent to the
+following configuration:
+
+ <Chain "main">
+   Target "write"
+ </Chain>
+
+If you specify a B<Chain> block anywhere, the B<write> target will not be added
+anywhere and you will have to make sure that it is called where appropriate. We
+suggest to add the above snippet as default target to your main chain.
+
+TODO: Notifications will be implemented using chains, too. Describe that here!
+
 =head1 SEE ALSO
 
 L<collectd(1)>,
diff --git a/src/filter_chain.c b/src/filter_chain.c
new file mode 100644 (file)
index 0000000..9a15661
--- /dev/null
@@ -0,0 +1,958 @@
+/**
+ * collectd - src/filter_chain.h
+ * Copyright (C) 2008  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.
+ *
+ * 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.
+ *
+ * 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
+ *
+ * Authors:
+ *   Florian octo Forster <octo at verplant.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 "common.h"
+#include "filter_chain.h"
+
+/*
+ * Data types
+ */
+/* List of matches, used in fc_rule_t and for the global `match_list_head'
+ * variable. */
+struct fc_match_s;
+typedef struct fc_match_s fc_match_t; /* {{{ */
+struct fc_match_s
+{
+  char name[DATA_MAX_NAME_LEN];
+  match_proc_t proc;
+  void *user_data;
+  fc_match_t *next;
+}; /* }}} */
+
+/* List of targets, used in fc_rule_t and for the global `target_list_head'
+ * variable. */
+struct fc_target_s;
+typedef struct fc_target_s fc_target_t; /* {{{ */
+struct fc_target_s
+{
+  char name[DATA_MAX_NAME_LEN];
+  void *user_data;
+  target_proc_t proc;
+  fc_target_t *next;
+}; /* }}} */
+
+/* List of rules, used in fc_chain_t */
+struct fc_fule_s;
+typedef struct fc_fule_s fc_rule_t; /* {{{ */
+struct fc_fule_s
+{
+  char name[DATA_MAX_NAME_LEN];
+  fc_match_t  *matches;
+  fc_target_t *targets;
+  fc_rule_t *next;
+}; /* }}} */
+
+/* List of chains, used for `chain_list_head' */
+struct fc_chain_s;
+typedef struct fc_chain_s fc_chain_t; /* {{{ */
+struct fc_chain_s
+{
+  char name[DATA_MAX_NAME_LEN];
+  fc_rule_t   *rules;
+  fc_target_t *targets;
+  fc_chain_t  *next;
+}; /* }}} */
+
+/*
+ * Global variables
+ */
+static fc_match_t  *match_list_head;
+static fc_target_t *target_list_head;
+static fc_chain_t  *chain_list_head;
+
+/*
+ * Private functions
+ */
+static void fc_free_matches (fc_match_t *m) /* {{{ */
+{
+  if (m == NULL)
+    return;
+
+  if (m->proc.destroy != NULL)
+    (*m->proc.destroy) (&m->user_data);
+  else if (m->user_data != NULL)
+  {
+    ERROR ("Filter sybsystem: fc_free_matches: There is user data, but no "
+        "destroy functions has been specified. "
+        "Memory will probably be lost!");
+  }
+
+  if (m->next != NULL)
+    fc_free_matches (m->next);
+
+  free (m);
+} /* }}} void fc_free_matches */
+
+static void fc_free_targets (fc_target_t *t) /* {{{ */
+{
+  if (t == NULL)
+    return;
+
+  if (t->proc.destroy != NULL)
+    (*t->proc.destroy) (&t->user_data);
+  else if (t->user_data != NULL)
+  {
+    ERROR ("Filter sybsystem: fc_free_targets: There is user data, but no "
+        "destroy functions has been specified. "
+        "Memory will probably be lost!");
+  }
+
+  if (t->next != NULL)
+    fc_free_targets (t->next);
+
+  free (t);
+} /* }}} void fc_free_targets */
+
+static void fc_free_rules (fc_rule_t *r) /* {{{ */
+{
+  if (r == NULL)
+    return;
+
+  fc_free_matches (r->matches);
+  fc_free_targets (r->targets);
+
+  if (r->next != NULL)
+    fc_free_rules (r->next);
+
+  free (r);
+} /* }}} void fc_free_rules */
+
+static void fc_free_chains (fc_chain_t *c) /* {{{ */
+{
+  if (c == NULL)
+    return;
+
+  fc_free_rules (c->rules);
+  fc_free_targets (c->targets);
+
+  if (c->next != NULL)
+    fc_free_chains (c->next);
+
+  free (c);
+} /* }}} void fc_free_chains */
+
+static char *fc_strdup (const char *orig) /* {{{ */
+{
+  size_t sz;
+  char *dest;
+
+  if (orig == NULL)
+    return (NULL);
+
+  sz = strlen (orig) + 1;
+  dest = (char *) malloc (sz);
+  if (dest == NULL)
+    return (NULL);
+
+  memcpy (dest, orig, sz);
+
+  return (dest);
+} /* }}} char *fc_strdup */
+
+/*
+ * Configuration.
+ *
+ * The configuration looks somewhat like this:
+ *
+ *  <Chain "main">
+ *    <Rule>
+ *      <Match "regex">
+ *        Plugin "^mysql$"
+ *        Type "^mysql_command$"
+ *        TypeInstance "^show_"
+ *      </Match>
+ *      <Target "drop">
+ *      </Target>
+ *    </Rule>
+ *
+ *    <Target "write">
+ *      Plugin "rrdtool"
+ *    </Target>
+ *  </Chain>
+ */
+static int fc_config_add_match (fc_match_t **matches_head, /* {{{ */
+    oconfig_item_t *ci)
+{
+  fc_match_t *m;
+  fc_match_t *ptr;
+  int status;
+
+  if ((ci->values_num != 1)
+      || (ci->values[0].type != OCONFIG_TYPE_STRING))
+  {
+    WARNING ("Filter subsystem: `Match' blocks require "
+        "exactly one string argument.");
+    return (-1);
+  }
+
+  ptr = match_list_head;
+  while (ptr != NULL)
+  {
+    if (strcasecmp (ptr->name, ci->values[0].value.string) == 0)
+      break;
+    ptr = ptr->next;
+  }
+
+  if (ptr == NULL)
+  {
+    WARNING ("Filter subsystem: Cannot find a \"%s\" match. "
+        "Did you load the appropriate plugin?",
+        ci->values[0].value.string);
+    return (-1);
+  }
+
+  m = (fc_match_t *) malloc (sizeof (*m));
+  if (m == NULL)
+  {
+    ERROR ("fc_config_add_match: malloc failed.");
+    return (-1);
+  }
+  memset (m, 0, sizeof (*m));
+
+  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)
+  {
+    WARNING ("Filter subsystem: Failed to create a %s match.",
+        m->name);
+    fc_free_matches (m);
+    return (-1);
+  }
+  
+  if (*matches_head != NULL)
+  {
+    ptr = *matches_head;
+    while (ptr->next != NULL)
+      ptr = ptr->next;
+
+    ptr->next = m;
+  }
+  else
+  {
+    *matches_head = m;
+  }
+
+  return (0);
+} /* }}} int fc_config_add_match */
+
+static int fc_config_add_target (fc_target_t **targets_head, /* {{{ */
+    oconfig_item_t *ci)
+{
+  fc_target_t *t;
+  fc_target_t *ptr;
+  int status;
+
+  if ((ci->values_num != 1)
+      || (ci->values[0].type != OCONFIG_TYPE_STRING))
+  {
+    WARNING ("Filter subsystem: `Target' blocks require "
+        "exactly one string argument.");
+    return (-1);
+  }
+
+  ptr = target_list_head;
+  while (ptr != NULL)
+  {
+    if (strcasecmp (ptr->name, ci->values[0].value.string) == 0)
+      break;
+    ptr = ptr->next;
+  }
+
+  if (ptr == NULL)
+  {
+    WARNING ("Filter subsystem: Cannot find a \"%s\" target. "
+        "Did you load the appropriate plugin?",
+        ci->values[0].value.string);
+    return (-1);
+  }
+
+  t = (fc_target_t *) malloc (sizeof (*t));
+  if (t == NULL)
+  {
+    ERROR ("fc_config_add_match: malloc failed.");
+    return (-1);
+  }
+  memset (t, 0, sizeof (*t));
+
+  sstrncpy (t->name, ptr->name, sizeof (t->name));
+  memcpy (&t->proc, &ptr->proc, sizeof (t->proc));
+  assert (t->proc.create != NULL);
+  t->user_data = NULL;
+  t->next = NULL;
+
+  status = (*t->proc.create) (ci, &t->user_data);
+  if (status != 0)
+  {
+    WARNING ("Filter subsystem: Failed to create a %s match.",
+        t->name);
+    fc_free_targets (t);
+    return (-1);
+  }
+  
+  if (*targets_head != NULL)
+  {
+    ptr = *targets_head;
+    while (ptr->next != NULL)
+      ptr = ptr->next;
+
+    ptr->next = t;
+  }
+  else
+  {
+    *targets_head = t;
+  }
+
+  return (0);
+} /* }}} int fc_config_add_target */
+
+static int fc_config_add_rule (fc_chain_t *chain, /* {{{ */
+    oconfig_item_t *ci)
+{
+  fc_rule_t *rule;
+  char rule_name[2*DATA_MAX_NAME_LEN] = "Unnamed rule";
+  int status = 0;
+  int i;
+
+  if (ci->values_num > 1)
+  {
+    WARNING ("Filter subsystem: `Rule' blocks have at most one argument.");
+    return (-1);
+  }
+  else if ((ci->values_num == 1)
+      && (ci->values[0].type != OCONFIG_TYPE_STRING))
+  {
+    WARNING ("Filter subsystem: `Rule' blocks expect one string argument "
+        "or no argument at all.");
+    return (-1);
+  }
+
+  rule = (fc_rule_t *) malloc (sizeof (*rule));
+  if (rule == NULL)
+  {
+    ERROR ("fc_config_add_rule: malloc failed.");
+    return (-1);
+  }
+  memset (rule, 0, sizeof (*rule));
+  rule->next = NULL;
+
+  if (ci->values_num == 1)
+  {
+    sstrncpy (rule->name, ci->values[0].value.string, sizeof (rule->name));
+    ssnprintf (rule_name, sizeof (rule_name), "Rule \"%s\"",
+        ci->values[0].value.string);
+  }
+
+  for (i = 0; i < ci->children_num; i++)
+  {
+    oconfig_item_t *option = ci->children + i;
+    status = 0;
+
+    if (strcasecmp ("Match", option->key) == 0)
+      status = fc_config_add_match (&rule->matches, option);
+    else if (strcasecmp ("Target", option->key) == 0)
+      status = fc_config_add_target (&rule->targets, option);
+    else
+    {
+      WARNING ("Filter subsystem: %s: Option `%s' not allowed "
+          "inside a <Rule> block.", rule_name, option->key);
+      status = -1;
+    }
+
+    if (status != 0)
+      break;
+  } /* for (ci->children) */
+
+  /* Additional sanity checking. */
+  while (status == 0)
+  {
+    if (rule->targets == NULL)
+    {
+      WARNING ("Filter subsystem: %s: No target has been specified.",
+          rule_name);
+      status = -1;
+      break;
+    }
+
+    break;
+  } /* while (status == 0) */
+
+  if (status != 0)
+  {
+    fc_free_rules (rule);
+    return (-1);
+  }
+
+  if (chain->rules != NULL)
+  {
+    fc_rule_t *ptr;
+
+    ptr = chain->rules;
+    while (ptr->next != NULL)
+      ptr = ptr->next;
+
+    ptr->next = rule;
+  }
+  else
+  {
+    chain->rules = rule;
+  }
+
+  return (0);
+} /* }}} int fc_config_add_rule */
+
+static int fc_config_add_chain (const oconfig_item_t *ci) /* {{{ */
+{
+  fc_chain_t *chain;
+  int status = 0;
+  int i;
+
+  if ((ci->values_num != 1)
+      || (ci->values[0].type != OCONFIG_TYPE_STRING))
+  {
+    WARNING ("Filter subsystem: <Chain> blocks require exactly one "
+        "string argument.");
+    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;
+
+  for (i = 0; i < ci->children_num; i++)
+  {
+    oconfig_item_t *option = ci->children + i;
+    status = 0;
+
+    if (strcasecmp ("Rule", option->key) == 0)
+      status = fc_config_add_rule (chain, option);
+    else
+    {
+      WARNING ("Filter subsystem: Chain %s: Option `%s' not allowed "
+          "inside a <Chain> block.", chain->name, option->key);
+      status = -1;
+    }
+
+    if (status != 0)
+      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);
+    return (-1);
+  }
+
+  if (chain_list_head != NULL)
+  {
+    fc_chain_t *ptr;
+
+    ptr = chain_list_head;
+    while (ptr->next != NULL)
+      ptr = ptr->next;
+
+    ptr->next = chain;
+  }
+  else
+  {
+    chain_list_head = chain;
+  }
+
+  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);
+
+  status = FC_ACTION_CONTINUE;
+
+  for (rule = chain->rules; rule != NULL; rule = rule->next)
+  {
+    fc_match_t *match;
+
+    /* 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: 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;
+
+    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: A target failed.");
+        continue;
+      }
+      else if (status == FC_ACTION_CONTINUE)
+        continue;
+      else if (status == FC_ACTION_STOP)
+        break;
+      else
+      {
+        WARNING ("fc_process: Unknown target return value: %i", status);
+      }
+    }
+
+    if (status == FC_ACTION_STOP)
+      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: The default target failed.");
+    }
+  }
+
+  return (0);
+} /* }}} int fc_process_chain */
+
+/*
+ * Built-in target "jump"
+ *
+ * Prefix `bit' like `_b_uilt-_i_n _t_arget'
+ */
+static int fc_bit_jump_create (const oconfig_item_t *ci, /* {{{ */
+    void **user_data)
+{
+  oconfig_item_t *ci_chain;
+
+  if (ci->children_num != 1)
+  {
+    ERROR ("Filter subsystem: The built-in target `jump' needs exactly "
+        "one `Chain' argument!");
+    return (-1);
+  }
+
+  ci_chain = ci->children;
+  if (strcasecmp ("Chain", ci_chain->key) != 0)
+  {
+    ERROR ("Filter subsystem: The built-in target `jump' does not "
+        "support the configuration option `%s'.",
+        ci_chain->key);
+    return (-1);
+  }
+
+  if ((ci_chain->values_num != 1)
+      || (ci_chain->values[0].type != OCONFIG_TYPE_STRING))
+  {
+    ERROR ("Filter subsystem: Built-in target `jump': The `Chain' option "
+        "needs exactly one string argument.");
+    return (-1);
+  }
+
+  *user_data = fc_strdup (ci_chain->values[0].value.string);
+  if (*user_data == NULL)
+  {
+    ERROR ("fc_bit_jump_create: fc_strdup failed.");
+    return (-1);
+  }
+
+  return (0);
+} /* }}} int fc_bit_jump_create */
+
+static int fc_bit_jump_destroy (void **user_data) /* {{{ */
+{
+  if (user_data != NULL)
+  {
+    free (*user_data);
+    *user_data = NULL;
+  }
+
+  return (0);
+} /* }}} 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)
+{
+  char *chain_name;
+  fc_chain_t *chain;
+  int status;
+
+  chain_name = *user_data;
+
+  for (chain = chain_list_head; chain != NULL; chain = chain->next)
+    if (strcasecmp (chain_name, chain->name) == 0)
+      break;
+
+  if (chain == NULL)
+  {
+    ERROR ("Filter subsystem: Built-in target `jump': There is no chain "
+        "named `%s'.", chain_name);
+    return (-1);
+  }
+
+  status = fc_process_chain (ds, vl, chain);
+  if (status < 0)
+    return (status);
+
+  return (FC_ACTION_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)
+{
+  return (FC_ACTION_STOP);
+} /* }}} int fc_bit_stop_invoke */
+
+static int fc_bit_write_create (const oconfig_item_t *ci, /* {{{ */
+    void **user_data)
+{
+  int i;
+
+  char **plugin_list;
+  size_t plugin_list_len;
+
+  plugin_list = NULL;
+  plugin_list_len = 0;
+
+  for (i = 0; i < ci->children_num; i++)
+  {
+    oconfig_item_t *child = ci->children + i;
+    char **temp;
+    int j;
+
+    if (strcasecmp ("Plugin", child->key) != 0)
+    {
+      ERROR ("Filter subsystem: The built-in target `write' does not "
+          "support the configuration option `%s'.",
+          child->key);
+      continue;
+    }
+
+    for (j = 0; j < child->values_num; j++)
+    {
+      if (child->values[j].type != OCONFIG_TYPE_STRING)
+      {
+        ERROR ("Filter subsystem: Built-in target `write': "
+            "The `Plugin' option accepts only string arguments.");
+        continue;
+      }
+
+      temp = (char **) realloc (plugin_list, (plugin_list_len + 2)
+          * (sizeof (*plugin_list)));
+      if (temp == NULL)
+      {
+        ERROR ("fc_bit_write_create: realloc failed.");
+        continue;
+      }
+      plugin_list = temp;
+
+      plugin_list[plugin_list_len] = fc_strdup (child->values[j].value.string);
+      if (plugin_list[plugin_list_len] == NULL)
+      {
+        ERROR ("fc_bit_write_create: fc_strdup failed.");
+        continue;
+      }
+      plugin_list_len++;
+      plugin_list[plugin_list_len] = NULL;
+    } /* for (j = 0; j < child->values_num; j++) */
+  } /* for (i = 0; i < ci->children_num; i++) */
+
+  *user_data = plugin_list;
+
+  return (0);
+} /* }}} int fc_bit_write_create */
+
+static int fc_bit_write_destroy (void **user_data) /* {{{ */
+{
+  char **plugin_list;
+  size_t i;
+
+  if ((user_data == NULL) || (*user_data == NULL))
+    return (0);
+
+  plugin_list = *user_data;
+
+  for (i = 0; plugin_list[i] != NULL; i++)
+    free (plugin_list[i]);
+  free (plugin_list);
+
+  return (0);
+} /* }}} 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)
+{
+  char **plugin_list;
+  int status;
+
+  plugin_list = NULL;
+  if (user_data != NULL)
+    plugin_list = *user_data;
+
+  if ((plugin_list == NULL) || (plugin_list[0] == NULL))
+  {
+    status = plugin_write (/* plugin = */ NULL, ds, vl);
+    if (status != 0)
+    {
+      INFO ("Filter subsystem: Built-in target `write': Dispatching value to "
+          "all write plugins failed with status %i.", status);
+    }
+  }
+  else
+  {
+    size_t i;
+
+    for (i = 0; plugin_list[i] != NULL; i++)
+    {
+      status = plugin_write (plugin_list[i], ds, vl);
+      if (status != 0)
+      {
+        INFO ("Filter subsystem: Built-in target `write': Dispatching value to "
+            "the `%s' plugin failed with status %i.", plugin_list[i], status);
+      }
+    } /* for (i = 0; plugin_list[i] != NULL; i++) */
+  }
+
+  return (FC_ACTION_CONTINUE);
+} /* }}} int fc_bit_write_invoke */
+
+static int fc_init_once (void) /* {{{ */
+{
+  static int done = 0;
+  target_proc_t tproc;
+
+  if (done != 0)
+    return (0);
+
+  memset (&tproc, 0, sizeof (tproc));
+  tproc.create  = fc_bit_jump_create;
+  tproc.destroy = fc_bit_jump_destroy;
+  tproc.invoke  = fc_bit_jump_invoke;
+  fc_register_target ("jump", tproc);
+
+  memset (&tproc, 0, sizeof (tproc));
+  tproc.create  = NULL;
+  tproc.destroy = NULL;
+  tproc.invoke  = fc_bit_stop_invoke;
+  fc_register_target ("stop", tproc);
+
+  memset (&tproc, 0, sizeof (tproc));
+  tproc.create  = fc_bit_write_create;
+  tproc.destroy = fc_bit_write_destroy;
+  tproc.invoke  = fc_bit_write_invoke;
+  fc_register_target ("write", tproc);
+
+  done++;
+  return (0);
+} /* }}} int fc_init_once */
+
+/*
+ * Public functions
+ */
+/* Add a match to list of available matches. */
+int fc_register_match (const char *name, match_proc_t proc) /* {{{ */
+{
+  fc_match_t *m;
+
+  m = (fc_match_t *) malloc (sizeof (*m));
+  if (m == NULL)
+    return (-ENOMEM);
+  memset (m, 0, sizeof (*m));
+
+  sstrncpy (m->name, name, sizeof (m->name));
+  memcpy (&m->proc, &proc, sizeof (m->proc));
+  m->next = NULL;
+
+  if (match_list_head == NULL)
+  {
+    match_list_head = m;
+  }
+  else
+  {
+    fc_match_t *ptr;
+
+    ptr = match_list_head;
+    while (ptr->next != NULL)
+      ptr = ptr->next;
+
+    ptr->next = m;
+  }
+
+  return (0);
+} /* }}} int fc_register_match */
+
+/* Add a target to list of available targets. */
+int fc_register_target (const char *name, target_proc_t proc) /* {{{ */
+{
+  fc_target_t *t;
+
+  t = (fc_target_t *) malloc (sizeof (*t));
+  if (t == NULL)
+    return (-ENOMEM);
+  memset (t, 0, sizeof (*t));
+
+  sstrncpy (t->name, name, sizeof (t->name));
+  memcpy (&t->proc, &proc, sizeof (t->proc));
+  t->next = NULL;
+
+  if (target_list_head == NULL)
+  {
+    target_list_head = t;
+  }
+  else
+  {
+    fc_target_t *ptr;
+
+    ptr = target_list_head;
+    while (ptr->next != NULL)
+      ptr = ptr->next;
+
+    ptr->next = t;
+  }
+
+  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 *chain;
+
+  for (chain = chain_list_head; chain != NULL; chain = chain->next)
+    if (strcasecmp ("Main", chain->name) == 0)
+      break;
+
+  if (chain != NULL)
+    return (fc_process_chain (ds, vl, chain));
+
+  ERROR ("fc_process: TODO: Implement default behavior!");
+
+  return (0);
+} /* }}} int fc_process */
+
+int fc_configure (const oconfig_item_t *ci) /* {{{ */
+{
+  fc_init_once ();
+
+  if (ci == NULL)
+    return (-EINVAL);
+
+  if (strcasecmp ("Chain", ci->key) == 0)
+    return (fc_config_add_chain (ci));
+
+  WARNING ("Filter subsystem: Unknown top level config option `%s'.",
+      ci->key);
+
+  return (-1);
+} /* }}} int fc_configure */
+
+/* vim: set sw=2 sts=2 et fdm=marker : */
diff --git a/src/filter_chain.h b/src/filter_chain.h
new file mode 100644 (file)
index 0000000..f2e22af
--- /dev/null
@@ -0,0 +1,92 @@
+/**
+ * collectd - src/filter_chain.h
+ * Copyright (C) 2008  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.
+ *
+ * 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.
+ *
+ * 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
+ *
+ * Authors:
+ *   Florian octo Forster <octo at verplant.org>
+ **/
+
+#ifndef FILTER_CHAIN_H
+#define FILTER_CHAIN_H 1
+
+#include "collectd.h"
+#include "plugin.h"
+
+#define FC_MATCH_NO_MATCH  0
+#define FC_MATCH_MATCHES   1
+
+#define FC_ACTION_CONTINUE 0
+#define FC_ACTION_STOP     1
+
+/*
+ * Match functions
+ */
+struct match_proc_s
+{
+  int (*create) (const oconfig_item_t *ci, void **user_data);
+  int (*destroy) (void **user_data);
+  int (*match) (const data_set_t *ds, const value_list_t *vl,
+      notification_meta_t **meta, void **user_data);
+};
+typedef struct match_proc_s match_proc_t;
+
+int fc_register_match (const char *name, match_proc_t proc);
+
+/*
+ * Target functions
+ */
+struct target_proc_s
+{
+  int (*create) (const oconfig_item_t *ci, void **user_data);
+  int (*destroy) (void **user_data);
+  int (*invoke) (const data_set_t *ds, value_list_t *vl,
+      notification_meta_t **meta, void **user_data);
+};
+typedef struct target_proc_s target_proc_t;
+
+int fc_register_target (const char *name, target_proc_t proc);
+
+/*
+ * TODO: Chain management
+ */
+#if 0
+int fc_chain_add (const char *chain_name,
+    const char *target_name, int target_argc, char **target_argv);
+int fc_chain_delete (const char *chain_name);
+#endif
+
+/*
+ * TODO: Rule management
+ */
+#if 0
+int fc_rule_add (const char *chain_name, int position,
+    int match_num, const char **match_name, int *match_argc, char ***match_argv,
+    const char *target_name, int target_argc, char **target_argv);
+int fc_rule_delete (const char *chain_name, int position);
+#endif
+
+/*
+ * Processing function
+ */
+int fc_process (const data_set_t *ds, value_list_t *vl);
+
+/* 
+ * Shortcut for global configuration
+ */
+int fc_configure (const oconfig_item_t *ci);
+
+#endif /* FILTER_CHAIN_H */
+/* vim: set sw=2 sts=2 et : */