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