Use the complain mechanism to report write failures.
[collectd.git] / src / filter_chain.c
1 /**
2  * collectd - src/filter_chain.h
3  * Copyright (C) 2008-2010  Florian octo Forster
4  *
5  * This program is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License as published by the
7  * Free Software Foundation; only version 2 of the License is applicable.
8  *
9  * This program is distributed in the hope that it will be useful, but
10  * WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License along
15  * with this program; if not, write to the Free Software Foundation, Inc.,
16  * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
17  *
18  * Authors:
19  *   Florian octo Forster <octo at verplant.org>
20  **/
21
22 #include "collectd.h"
23 #include "configfile.h"
24 #include "plugin.h"
25 #include "utils_complain.h"
26 #include "common.h"
27 #include "filter_chain.h"
28
29 /*
30  * Data types
31  */
32 /* List of matches, used in fc_rule_t and for the global `match_list_head'
33  * variable. */
34 struct fc_match_s;
35 typedef struct fc_match_s fc_match_t; /* {{{ */
36 struct fc_match_s
37 {
38   char name[DATA_MAX_NAME_LEN];
39   match_proc_t proc;
40   void *user_data;
41   fc_match_t *next;
42 }; /* }}} */
43
44 /* List of targets, used in fc_rule_t and for the global `target_list_head'
45  * variable. */
46 struct fc_target_s;
47 typedef struct fc_target_s fc_target_t; /* {{{ */
48 struct fc_target_s
49 {
50   char name[DATA_MAX_NAME_LEN];
51   void *user_data;
52   target_proc_t proc;
53   fc_target_t *next;
54 }; /* }}} */
55
56 /* List of rules, used in fc_chain_t */
57 struct fc_rule_s;
58 typedef struct fc_rule_s fc_rule_t; /* {{{ */
59 struct fc_rule_s
60 {
61   char name[DATA_MAX_NAME_LEN];
62   fc_match_t  *matches;
63   fc_target_t *targets;
64   fc_rule_t *next;
65 }; /* }}} */
66
67 /* List of chains, used for `chain_list_head' */
68 struct fc_chain_s /* {{{ */
69 {
70   char name[DATA_MAX_NAME_LEN];
71   fc_rule_t   *rules;
72   fc_target_t *targets;
73   fc_chain_t  *next;
74 }; /* }}} */
75
76 /* Writer configuration. */
77 struct fc_writer_s;
78 typedef struct fc_writer_s fc_writer_t; /* {{{ */
79 struct fc_writer_s
80 {
81   char *plugin;
82   c_complain_t complaint;
83 }; /* }}} */
84
85 /*
86  * Global variables
87  */
88 static fc_match_t  *match_list_head;
89 static fc_target_t *target_list_head;
90 static fc_chain_t  *chain_list_head;
91
92 /*
93  * Private functions
94  */
95 static void fc_free_matches (fc_match_t *m) /* {{{ */
96 {
97   if (m == NULL)
98     return;
99
100   if (m->proc.destroy != NULL)
101     (*m->proc.destroy) (&m->user_data);
102   else if (m->user_data != NULL)
103   {
104     ERROR ("Filter subsystem: fc_free_matches: There is user data, but no "
105         "destroy functions has been specified. "
106         "Memory will probably be lost!");
107   }
108
109   if (m->next != NULL)
110     fc_free_matches (m->next);
111
112   free (m);
113 } /* }}} void fc_free_matches */
114
115 static void fc_free_targets (fc_target_t *t) /* {{{ */
116 {
117   if (t == NULL)
118     return;
119
120   if (t->proc.destroy != NULL)
121     (*t->proc.destroy) (&t->user_data);
122   else if (t->user_data != NULL)
123   {
124     ERROR ("Filter subsystem: fc_free_targets: There is user data, but no "
125         "destroy functions has been specified. "
126         "Memory will probably be lost!");
127   }
128
129   if (t->next != NULL)
130     fc_free_targets (t->next);
131
132   free (t);
133 } /* }}} void fc_free_targets */
134
135 static void fc_free_rules (fc_rule_t *r) /* {{{ */
136 {
137   if (r == NULL)
138     return;
139
140   fc_free_matches (r->matches);
141   fc_free_targets (r->targets);
142
143   if (r->next != NULL)
144     fc_free_rules (r->next);
145
146   free (r);
147 } /* }}} void fc_free_rules */
148
149 static void fc_free_chains (fc_chain_t *c) /* {{{ */
150 {
151   if (c == NULL)
152     return;
153
154   fc_free_rules (c->rules);
155   fc_free_targets (c->targets);
156
157   if (c->next != NULL)
158     fc_free_chains (c->next);
159
160   free (c);
161 } /* }}} void fc_free_chains */
162
163 static char *fc_strdup (const char *orig) /* {{{ */
164 {
165   size_t sz;
166   char *dest;
167
168   if (orig == NULL)
169     return (NULL);
170
171   sz = strlen (orig) + 1;
172   dest = (char *) malloc (sz);
173   if (dest == NULL)
174     return (NULL);
175
176   memcpy (dest, orig, sz);
177
178   return (dest);
179 } /* }}} char *fc_strdup */
180
181 /*
182  * Configuration.
183  *
184  * The configuration looks somewhat like this:
185  *
186  *  <Chain "PreCache">
187  *    <Rule>
188  *      <Match "regex">
189  *        Plugin "^mysql$"
190  *        Type "^mysql_command$"
191  *        TypeInstance "^show_"
192  *      </Match>
193  *      <Target "drop">
194  *      </Target>
195  *    </Rule>
196  *
197  *    <Target "write">
198  *      Plugin "rrdtool"
199  *    </Target>
200  *  </Chain>
201  */
202 static int fc_config_add_match (fc_match_t **matches_head, /* {{{ */
203     oconfig_item_t *ci)
204 {
205   fc_match_t *m;
206   fc_match_t *ptr;
207   int status;
208
209   if ((ci->values_num != 1)
210       || (ci->values[0].type != OCONFIG_TYPE_STRING))
211   {
212     WARNING ("Filter subsystem: `Match' blocks require "
213         "exactly one string argument.");
214     return (-1);
215   }
216
217   ptr = match_list_head;
218   while (ptr != NULL)
219   {
220     if (strcasecmp (ptr->name, ci->values[0].value.string) == 0)
221       break;
222     ptr = ptr->next;
223   }
224
225   if (ptr == NULL)
226   {
227     WARNING ("Filter subsystem: Cannot find a \"%s\" match. "
228         "Did you load the appropriate plugin?",
229         ci->values[0].value.string);
230     return (-1);
231   }
232
233   m = (fc_match_t *) malloc (sizeof (*m));
234   if (m == NULL)
235   {
236     ERROR ("fc_config_add_match: malloc failed.");
237     return (-1);
238   }
239   memset (m, 0, sizeof (*m));
240
241   sstrncpy (m->name, ptr->name, sizeof (m->name));
242   memcpy (&m->proc, &ptr->proc, sizeof (m->proc));
243   m->user_data = NULL;
244   m->next = NULL;
245
246   if (m->proc.create != NULL)
247   {
248     status = (*m->proc.create) (ci, &m->user_data);
249     if (status != 0)
250     {
251       WARNING ("Filter subsystem: Failed to create a %s match.",
252           m->name);
253       fc_free_matches (m);
254       return (-1);
255     }
256   }
257
258   if (*matches_head != NULL)
259   {
260     ptr = *matches_head;
261     while (ptr->next != NULL)
262       ptr = ptr->next;
263
264     ptr->next = m;
265   }
266   else
267   {
268     *matches_head = m;
269   }
270
271   return (0);
272 } /* }}} int fc_config_add_match */
273
274 static int fc_config_add_target (fc_target_t **targets_head, /* {{{ */
275     oconfig_item_t *ci)
276 {
277   fc_target_t *t;
278   fc_target_t *ptr;
279   int status;
280
281   if ((ci->values_num != 1)
282       || (ci->values[0].type != OCONFIG_TYPE_STRING))
283   {
284     WARNING ("Filter subsystem: `Target' blocks require "
285         "exactly one string argument.");
286     return (-1);
287   }
288
289   ptr = target_list_head;
290   while (ptr != NULL)
291   {
292     if (strcasecmp (ptr->name, ci->values[0].value.string) == 0)
293       break;
294     ptr = ptr->next;
295   }
296
297   if (ptr == NULL)
298   {
299     WARNING ("Filter subsystem: Cannot find a \"%s\" target. "
300         "Did you load the appropriate plugin?",
301         ci->values[0].value.string);
302     return (-1);
303   }
304
305   t = (fc_target_t *) malloc (sizeof (*t));
306   if (t == NULL)
307   {
308     ERROR ("fc_config_add_target: malloc failed.");
309     return (-1);
310   }
311   memset (t, 0, sizeof (*t));
312
313   sstrncpy (t->name, ptr->name, sizeof (t->name));
314   memcpy (&t->proc, &ptr->proc, sizeof (t->proc));
315   t->user_data = NULL;
316   t->next = NULL;
317
318   if (t->proc.create != NULL)
319   {
320     status = (*t->proc.create) (ci, &t->user_data);
321     if (status != 0)
322     {
323       WARNING ("Filter subsystem: Failed to create a %s target.",
324           t->name);
325       fc_free_targets (t);
326       return (-1);
327     }
328   }
329   else
330   {
331     t->user_data = NULL;
332   }
333   
334   if (*targets_head != NULL)
335   {
336     ptr = *targets_head;
337     while (ptr->next != NULL)
338       ptr = ptr->next;
339
340     ptr->next = t;
341   }
342   else
343   {
344     *targets_head = t;
345   }
346
347   return (0);
348 } /* }}} int fc_config_add_target */
349
350 static int fc_config_add_rule (fc_chain_t *chain, /* {{{ */
351     oconfig_item_t *ci)
352 {
353   fc_rule_t *rule;
354   char rule_name[2*DATA_MAX_NAME_LEN] = "Unnamed rule";
355   int status = 0;
356   int i;
357
358   if (ci->values_num > 1)
359   {
360     WARNING ("Filter subsystem: `Rule' blocks have at most one argument.");
361     return (-1);
362   }
363   else if ((ci->values_num == 1)
364       && (ci->values[0].type != OCONFIG_TYPE_STRING))
365   {
366     WARNING ("Filter subsystem: `Rule' blocks expect one string argument "
367         "or no argument at all.");
368     return (-1);
369   }
370
371   rule = (fc_rule_t *) malloc (sizeof (*rule));
372   if (rule == NULL)
373   {
374     ERROR ("fc_config_add_rule: malloc failed.");
375     return (-1);
376   }
377   memset (rule, 0, sizeof (*rule));
378   rule->next = NULL;
379
380   if (ci->values_num == 1)
381   {
382     sstrncpy (rule->name, ci->values[0].value.string, sizeof (rule->name));
383     ssnprintf (rule_name, sizeof (rule_name), "Rule \"%s\"",
384         ci->values[0].value.string);
385   }
386
387   for (i = 0; i < ci->children_num; i++)
388   {
389     oconfig_item_t *option = ci->children + i;
390     status = 0;
391
392     if (strcasecmp ("Match", option->key) == 0)
393       status = fc_config_add_match (&rule->matches, option);
394     else if (strcasecmp ("Target", option->key) == 0)
395       status = fc_config_add_target (&rule->targets, option);
396     else
397     {
398       WARNING ("Filter subsystem: %s: Option `%s' not allowed "
399           "inside a <Rule> block.", rule_name, option->key);
400       status = -1;
401     }
402
403     if (status != 0)
404       break;
405   } /* for (ci->children) */
406
407   /* Additional sanity checking. */
408   while (status == 0)
409   {
410     if (rule->targets == NULL)
411     {
412       WARNING ("Filter subsystem: %s: No target has been specified.",
413           rule_name);
414       status = -1;
415       break;
416     }
417
418     break;
419   } /* while (status == 0) */
420
421   if (status != 0)
422   {
423     fc_free_rules (rule);
424     return (-1);
425   }
426
427   if (chain->rules != NULL)
428   {
429     fc_rule_t *ptr;
430
431     ptr = chain->rules;
432     while (ptr->next != NULL)
433       ptr = ptr->next;
434
435     ptr->next = rule;
436   }
437   else
438   {
439     chain->rules = rule;
440   }
441
442   return (0);
443 } /* }}} int fc_config_add_rule */
444
445 static int fc_config_add_chain (const oconfig_item_t *ci) /* {{{ */
446 {
447   fc_chain_t *chain;
448   int status = 0;
449   int i;
450
451   if ((ci->values_num != 1)
452       || (ci->values[0].type != OCONFIG_TYPE_STRING))
453   {
454     WARNING ("Filter subsystem: <Chain> blocks require exactly one "
455         "string argument.");
456     return (-1);
457   }
458
459   chain = (fc_chain_t *) malloc (sizeof (*chain));
460   if (chain == NULL)
461   {
462     ERROR ("fc_config_add_chain: malloc failed.");
463     return (-1);
464   }
465   memset (chain, 0, sizeof (*chain));
466   sstrncpy (chain->name, ci->values[0].value.string, sizeof (chain->name));
467   chain->rules = NULL;
468   chain->targets = NULL;
469   chain->next = NULL;
470
471   for (i = 0; i < ci->children_num; i++)
472   {
473     oconfig_item_t *option = ci->children + i;
474     status = 0;
475
476     if (strcasecmp ("Rule", option->key) == 0)
477       status = fc_config_add_rule (chain, option);
478     else if (strcasecmp ("Target", option->key) == 0)
479       status = fc_config_add_target (&chain->targets, option);
480     else
481     {
482       WARNING ("Filter subsystem: Chain %s: Option `%s' not allowed "
483           "inside a <Chain> block.", chain->name, option->key);
484       status = -1;
485     }
486
487     if (status != 0)
488       break;
489   } /* for (ci->children) */
490
491   if (status != 0)
492   {
493     fc_free_chains (chain);
494     return (-1);
495   }
496
497   if (chain_list_head != NULL)
498   {
499     fc_chain_t *ptr;
500
501     ptr = chain_list_head;
502     while (ptr->next != NULL)
503       ptr = ptr->next;
504
505     ptr->next = chain;
506   }
507   else
508   {
509     chain_list_head = chain;
510   }
511
512   return (0);
513 } /* }}} int fc_config_add_chain */
514
515 /*
516  * Built-in target "jump"
517  *
518  * Prefix `bit' like `_b_uilt-_i_n _t_arget'
519  */
520 static int fc_bit_jump_create (const oconfig_item_t *ci, /* {{{ */
521     void **user_data)
522 {
523   oconfig_item_t *ci_chain;
524
525   if (ci->children_num != 1)
526   {
527     ERROR ("Filter subsystem: The built-in target `jump' needs exactly "
528         "one `Chain' argument!");
529     return (-1);
530   }
531
532   ci_chain = ci->children;
533   if (strcasecmp ("Chain", ci_chain->key) != 0)
534   {
535     ERROR ("Filter subsystem: The built-in target `jump' does not "
536         "support the configuration option `%s'.",
537         ci_chain->key);
538     return (-1);
539   }
540
541   if ((ci_chain->values_num != 1)
542       || (ci_chain->values[0].type != OCONFIG_TYPE_STRING))
543   {
544     ERROR ("Filter subsystem: Built-in target `jump': The `Chain' option "
545         "needs exactly one string argument.");
546     return (-1);
547   }
548
549   *user_data = fc_strdup (ci_chain->values[0].value.string);
550   if (*user_data == NULL)
551   {
552     ERROR ("fc_bit_jump_create: fc_strdup failed.");
553     return (-1);
554   }
555
556   return (0);
557 } /* }}} int fc_bit_jump_create */
558
559 static int fc_bit_jump_destroy (void **user_data) /* {{{ */
560 {
561   if (user_data != NULL)
562   {
563     free (*user_data);
564     *user_data = NULL;
565   }
566
567   return (0);
568 } /* }}} int fc_bit_jump_destroy */
569
570 static int fc_bit_jump_invoke (const data_set_t *ds, /* {{{ */
571     value_list_t *vl, notification_meta_t __attribute__((unused)) **meta,
572     void **user_data)
573 {
574   char *chain_name;
575   fc_chain_t *chain;
576   int status;
577
578   chain_name = *user_data;
579
580   for (chain = chain_list_head; chain != NULL; chain = chain->next)
581     if (strcasecmp (chain_name, chain->name) == 0)
582       break;
583
584   if (chain == NULL)
585   {
586     ERROR ("Filter subsystem: Built-in target `jump': There is no chain "
587         "named `%s'.", chain_name);
588     return (-1);
589   }
590
591   status = fc_process_chain (ds, vl, chain);
592   if (status < 0)
593     return (status);
594   else if (status == FC_TARGET_STOP)
595     return (FC_TARGET_STOP);
596   else
597     return (FC_TARGET_CONTINUE);
598 } /* }}} int fc_bit_jump_invoke */
599
600 static int fc_bit_stop_invoke (const data_set_t __attribute__((unused)) *ds, /* {{{ */
601     value_list_t __attribute__((unused)) *vl,
602     notification_meta_t __attribute__((unused)) **meta,
603     void __attribute__((unused)) **user_data)
604 {
605   return (FC_TARGET_STOP);
606 } /* }}} int fc_bit_stop_invoke */
607
608 static int fc_bit_return_invoke (const data_set_t __attribute__((unused)) *ds, /* {{{ */
609     value_list_t __attribute__((unused)) *vl,
610     notification_meta_t __attribute__((unused)) **meta,
611     void __attribute__((unused)) **user_data)
612 {
613   return (FC_TARGET_RETURN);
614 } /* }}} int fc_bit_return_invoke */
615
616 static int fc_bit_write_create (const oconfig_item_t *ci, /* {{{ */
617     void **user_data)
618 {
619   int i;
620
621   fc_writer_t *plugin_list = NULL;
622   size_t plugin_list_len = 0;
623
624   for (i = 0; i < ci->children_num; i++)
625   {
626     oconfig_item_t *child = ci->children + i;
627     fc_writer_t *temp;
628     int j;
629
630     if (strcasecmp ("Plugin", child->key) != 0)
631     {
632       ERROR ("Filter subsystem: The built-in target `write' does not "
633           "support the configuration option `%s'.",
634           child->key);
635       continue;
636     }
637
638     for (j = 0; j < child->values_num; j++)
639     {
640       char *plugin;
641
642       if (child->values[j].type != OCONFIG_TYPE_STRING)
643       {
644         ERROR ("Filter subsystem: Built-in target `write': "
645             "The `Plugin' option accepts only string arguments.");
646         continue;
647       }
648       plugin = child->values[j].value.string;
649
650       temp = (fc_writer_t *) realloc (plugin_list, (plugin_list_len + 2)
651           * (sizeof (*plugin_list)));
652       if (temp == NULL)
653       {
654         ERROR ("fc_bit_write_create: realloc failed.");
655         continue;
656       }
657       plugin_list = temp;
658
659       plugin_list[plugin_list_len].plugin = fc_strdup (plugin);
660       if (plugin_list[plugin_list_len].plugin == NULL)
661       {
662         ERROR ("fc_bit_write_create: fc_strdup failed.");
663         continue;
664       }
665       C_COMPLAIN_INIT (&plugin_list[plugin_list_len].complaint);
666       plugin_list_len++;
667       plugin_list[plugin_list_len].plugin = NULL;
668     } /* for (j = 0; j < child->values_num; j++) */
669   } /* for (i = 0; i < ci->children_num; i++) */
670
671   *user_data = plugin_list;
672
673   return (0);
674 } /* }}} int fc_bit_write_create */
675
676 static int fc_bit_write_destroy (void **user_data) /* {{{ */
677 {
678   fc_writer_t *plugin_list;
679   size_t i;
680
681   if ((user_data == NULL) || (*user_data == NULL))
682     return (0);
683
684   plugin_list = *user_data;
685
686   for (i = 0; plugin_list[i].plugin != NULL; i++)
687     free (plugin_list[i].plugin);
688   free (plugin_list);
689
690   return (0);
691 } /* }}} int fc_bit_write_destroy */
692
693 static int fc_bit_write_invoke (const data_set_t *ds, /* {{{ */
694     value_list_t *vl, notification_meta_t __attribute__((unused)) **meta,
695     void **user_data)
696 {
697   fc_writer_t *plugin_list;
698   int status;
699
700   plugin_list = NULL;
701   if (user_data != NULL)
702     plugin_list = *user_data;
703
704   if ((plugin_list == NULL) || (plugin_list[0].plugin == NULL))
705   {
706     static c_complain_t write_complaint = C_COMPLAIN_INIT_STATIC;
707
708     status = plugin_write (/* plugin = */ NULL, ds, vl);
709     if (status == ENOENT)
710     {
711       /* in most cases this is a permanent error, so use the complain
712        * mechanism rather than spamming the logs */
713       c_complain (LOG_INFO, &write_complaint,
714           "Filter subsystem: Built-in target `write': Dispatching value to "
715           "all write plugins failed with status %i (ENOENT). "
716           "Most likely this means you didn't load any write plugins.",
717           status);
718     }
719     else if (status != 0)
720     {
721       /* often, this is a permanent error (e.g. target system unavailable),
722        * so use the complain mechanism rather than spamming the logs */
723       c_complain (LOG_INFO, &write_complaint,
724           "Filter subsystem: Built-in target `write': Dispatching value to "
725           "all write plugins failed with status %i.", status);
726     }
727     else
728     {
729       assert (status == 0);
730       c_release (LOG_INFO, &write_complaint, "Filter subsystem: "
731           "Built-in target `write': Some write plugin is back to normal "
732           "operation. `write' succeeded.");
733     }
734   }
735   else
736   {
737     size_t i;
738
739     for (i = 0; plugin_list[i].plugin != NULL; i++)
740     {
741       status = plugin_write (plugin_list[i].plugin, ds, vl);
742       if (status != 0)
743       {
744         c_complain (LOG_INFO, &plugin_list[i].complaint,
745             "Filter subsystem: Built-in target `write': Dispatching value to "
746             "the `%s' plugin failed with status %i.",
747             plugin_list[i].plugin, status);
748       }
749       else
750       {
751         c_release (LOG_INFO, &plugin_list[i].complaint,
752             "Filter subsystem: Built-in target `write': Plugin `%s' is back "
753             "to normal operation. `write' succeeded.", plugin_list[i].plugin);
754       }
755     } /* for (i = 0; plugin_list[i] != NULL; i++) */
756   }
757
758   return (FC_TARGET_CONTINUE);
759 } /* }}} int fc_bit_write_invoke */
760
761 static int fc_init_once (void) /* {{{ */
762 {
763   static int done = 0;
764   target_proc_t tproc;
765
766   if (done != 0)
767     return (0);
768
769   memset (&tproc, 0, sizeof (tproc));
770   tproc.create  = fc_bit_jump_create;
771   tproc.destroy = fc_bit_jump_destroy;
772   tproc.invoke  = fc_bit_jump_invoke;
773   fc_register_target ("jump", tproc);
774
775   memset (&tproc, 0, sizeof (tproc));
776   tproc.create  = NULL;
777   tproc.destroy = NULL;
778   tproc.invoke  = fc_bit_stop_invoke;
779   fc_register_target ("stop", tproc);
780
781   memset (&tproc, 0, sizeof (tproc));
782   tproc.create  = NULL;
783   tproc.destroy = NULL;
784   tproc.invoke  = fc_bit_return_invoke;
785   fc_register_target ("return", tproc);
786
787   memset (&tproc, 0, sizeof (tproc));
788   tproc.create  = fc_bit_write_create;
789   tproc.destroy = fc_bit_write_destroy;
790   tproc.invoke  = fc_bit_write_invoke;
791   fc_register_target ("write", tproc);
792
793   done++;
794   return (0);
795 } /* }}} int fc_init_once */
796
797 /*
798  * Public functions
799  */
800 /* Add a match to list of available matches. */
801 int fc_register_match (const char *name, match_proc_t proc) /* {{{ */
802 {
803   fc_match_t *m;
804
805   DEBUG ("fc_register_match (%s);", name);
806
807   m = (fc_match_t *) malloc (sizeof (*m));
808   if (m == NULL)
809     return (-ENOMEM);
810   memset (m, 0, sizeof (*m));
811
812   sstrncpy (m->name, name, sizeof (m->name));
813   memcpy (&m->proc, &proc, sizeof (m->proc));
814   m->next = NULL;
815
816   if (match_list_head == NULL)
817   {
818     match_list_head = m;
819   }
820   else
821   {
822     fc_match_t *ptr;
823
824     ptr = match_list_head;
825     while (ptr->next != NULL)
826       ptr = ptr->next;
827
828     ptr->next = m;
829   }
830
831   return (0);
832 } /* }}} int fc_register_match */
833
834 /* Add a target to list of available targets. */
835 int fc_register_target (const char *name, target_proc_t proc) /* {{{ */
836 {
837   fc_target_t *t;
838
839   DEBUG ("fc_register_target (%s);", name);
840
841   t = (fc_target_t *) malloc (sizeof (*t));
842   if (t == NULL)
843     return (-ENOMEM);
844   memset (t, 0, sizeof (*t));
845
846   sstrncpy (t->name, name, sizeof (t->name));
847   memcpy (&t->proc, &proc, sizeof (t->proc));
848   t->next = NULL;
849
850   if (target_list_head == NULL)
851   {
852     target_list_head = t;
853   }
854   else
855   {
856     fc_target_t *ptr;
857
858     ptr = target_list_head;
859     while (ptr->next != NULL)
860       ptr = ptr->next;
861
862     ptr->next = t;
863   }
864
865   return (0);
866 } /* }}} int fc_register_target */
867
868 fc_chain_t *fc_chain_get_by_name (const char *chain_name) /* {{{ */
869 {
870   fc_chain_t *chain;
871
872   if (chain_name == NULL)
873     return (NULL);
874
875   for (chain = chain_list_head; chain != NULL; chain = chain->next)
876     if (strcasecmp (chain_name, chain->name) == 0)
877       return (chain);
878
879   return (NULL);
880 } /* }}} int fc_chain_get_by_name */
881
882 int fc_process_chain (const data_set_t *ds, value_list_t *vl, /* {{{ */
883     fc_chain_t *chain)
884 {
885   fc_rule_t *rule;
886   fc_target_t *target;
887   int status;
888
889   if (chain == NULL)
890     return (-1);
891
892   DEBUG ("fc_process_chain (chain = %s);", chain->name);
893
894   status = FC_TARGET_CONTINUE;
895   for (rule = chain->rules; rule != NULL; rule = rule->next)
896   {
897     fc_match_t *match;
898
899     if (rule->name[0] != 0)
900     {
901       DEBUG ("fc_process_chain (%s): Testing the `%s' rule.",
902           chain->name, rule->name);
903     }
904
905     /* N. B.: rule->matches may be NULL. */
906     for (match = rule->matches; match != NULL; match = match->next)
907     {
908       /* FIXME: Pass the meta-data to match targets here (when implemented). */
909       status = (*match->proc.match) (ds, vl, /* meta = */ NULL,
910           &match->user_data);
911       if (status < 0)
912       {
913         WARNING ("fc_process_chain (%s): A match failed.", chain->name);
914         break;
915       }
916       else if (status != FC_MATCH_MATCHES)
917         break;
918     }
919
920     /* for-loop has been aborted: Either error or no match. */
921     if (match != NULL)
922     {
923       status = FC_TARGET_CONTINUE;
924       continue;
925     }
926
927     if (rule->name[0] != 0)
928     {
929       DEBUG ("fc_process_chain (%s): Rule `%s' matches.",
930           chain->name, rule->name);
931     }
932
933     for (target = rule->targets; target != NULL; target = target->next)
934     {
935       /* If we get here, all matches have matched the value. Execute the
936        * target. */
937       /* FIXME: Pass the meta-data to match targets here (when implemented). */
938       status = (*target->proc.invoke) (ds, vl, /* meta = */ NULL,
939           &target->user_data);
940       if (status < 0)
941       {
942         WARNING ("fc_process_chain (%s): A target failed.", chain->name);
943         continue;
944       }
945       else if (status == FC_TARGET_CONTINUE)
946         continue;
947       else if (status == FC_TARGET_STOP)
948         break;
949       else if (status == FC_TARGET_RETURN)
950         break;
951       else
952       {
953         WARNING ("fc_process_chain (%s): Unknown return value "
954             "from target `%s': %i",
955             chain->name, target->name, status);
956       }
957     }
958
959     if ((status == FC_TARGET_STOP)
960         || (status == FC_TARGET_RETURN))
961     {
962       if (rule->name[0] != 0)
963       {
964         DEBUG ("fc_process_chain (%s): Rule `%s' signaled "
965             "the %s condition.",
966             chain->name, rule->name,
967             (status == FC_TARGET_STOP) ? "stop" : "return");
968       }
969       break;
970     }
971     else
972     {
973       status = FC_TARGET_CONTINUE;
974     }
975   } /* for (rule) */
976
977   if (status == FC_TARGET_STOP)
978     return (FC_TARGET_STOP);
979   else if (status == FC_TARGET_RETURN)
980     return (FC_TARGET_CONTINUE);
981
982   /* for-loop has been aborted: A target returned `FC_TARGET_STOP' */
983   if (rule != NULL)
984     return (FC_TARGET_CONTINUE);
985
986   DEBUG ("fc_process_chain (%s): Executing the default targets.",
987       chain->name);
988
989   status = FC_TARGET_CONTINUE;
990   for (target = chain->targets; target != NULL; target = target->next)
991   {
992     /* If we get here, all matches have matched the value. Execute the
993      * target. */
994     /* FIXME: Pass the meta-data to match targets here (when implemented). */
995     status = (*target->proc.invoke) (ds, vl, /* meta = */ NULL,
996         &target->user_data);
997     if (status < 0)
998     {
999       WARNING ("fc_process_chain (%s): The default target failed.",
1000           chain->name);
1001     }
1002     else if (status == FC_TARGET_CONTINUE)
1003       continue;
1004     else if (status == FC_TARGET_STOP)
1005       break;
1006     else if (status == FC_TARGET_RETURN)
1007       break;
1008     else
1009     {
1010       WARNING ("fc_process_chain (%s): Unknown return value "
1011           "from target `%s': %i",
1012           chain->name, target->name, status);
1013     }
1014   }
1015
1016   if ((status == FC_TARGET_STOP)
1017       || (status == FC_TARGET_RETURN))
1018   {
1019     assert (target != NULL);
1020     DEBUG ("fc_process_chain (%s): Default target `%s' signaled "
1021         "the %s condition.",
1022         chain->name, target->name,
1023         (status == FC_TARGET_STOP) ? "stop" : "return");
1024     if (status == FC_TARGET_STOP)
1025       return (FC_TARGET_STOP);
1026     else
1027       return (FC_TARGET_CONTINUE);
1028   }
1029
1030   DEBUG ("fc_process_chain (%s): Signaling `continue' at end of chain.",
1031       chain->name);
1032
1033   return (FC_TARGET_CONTINUE);
1034 } /* }}} int fc_process_chain */
1035
1036 /* Iterate over all rules in the chain and execute all targets for which all
1037  * matches match. */
1038 int fc_default_action (const data_set_t *ds, value_list_t *vl) /* {{{ */
1039 {
1040   /* FIXME: Pass the meta-data to match targets here (when implemented). */
1041   return (fc_bit_write_invoke (ds, vl,
1042         /* meta = */ NULL, /* user_data = */ NULL));
1043 } /* }}} int fc_default_action */
1044
1045 int fc_configure (const oconfig_item_t *ci) /* {{{ */
1046 {
1047   fc_init_once ();
1048
1049   if (ci == NULL)
1050     return (-EINVAL);
1051
1052   if (strcasecmp ("Chain", ci->key) == 0)
1053     return (fc_config_add_chain (ci));
1054
1055   WARNING ("Filter subsystem: Unknown top level config option `%s'.",
1056       ci->key);
1057
1058   return (-1);
1059 } /* }}} int fc_configure */
1060
1061 /* vim: set sw=2 sts=2 et fdm=marker : */