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