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