Merge branch 'collectd-5.5'
[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 = 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 = calloc (1, sizeof (*m));
239   if (m == NULL)
240   {
241     ERROR ("fc_config_add_match: calloc failed.");
242     return (-1);
243   }
244
245   sstrncpy (m->name, ptr->name, sizeof (m->name));
246   memcpy (&m->proc, &ptr->proc, sizeof (m->proc));
247   m->user_data = NULL;
248   m->next = NULL;
249
250   if (m->proc.create != NULL)
251   {
252     status = (*m->proc.create) (ci, &m->user_data);
253     if (status != 0)
254     {
255       WARNING ("Filter subsystem: Failed to create a %s match.",
256           m->name);
257       fc_free_matches (m);
258       return (-1);
259     }
260   }
261
262   if (*matches_head != NULL)
263   {
264     ptr = *matches_head;
265     while (ptr->next != NULL)
266       ptr = ptr->next;
267
268     ptr->next = m;
269   }
270   else
271   {
272     *matches_head = m;
273   }
274
275   return (0);
276 } /* }}} int fc_config_add_match */
277
278 static int fc_config_add_target (fc_target_t **targets_head, /* {{{ */
279     oconfig_item_t *ci)
280 {
281   fc_target_t *t;
282   fc_target_t *ptr;
283   int status;
284
285   if ((ci->values_num != 1)
286       || (ci->values[0].type != OCONFIG_TYPE_STRING))
287   {
288     WARNING ("Filter subsystem: `Target' blocks require "
289         "exactly one string argument.");
290     return (-1);
291   }
292
293   ptr = target_list_head;
294   while (ptr != NULL)
295   {
296     if (strcasecmp (ptr->name, ci->values[0].value.string) == 0)
297       break;
298     ptr = ptr->next;
299   }
300
301   if (ptr == NULL)
302   {
303     WARNING ("Filter subsystem: Cannot find a \"%s\" target. "
304         "Did you load the appropriate plugin?",
305         ci->values[0].value.string);
306     return (-1);
307   }
308
309   t = calloc (1, sizeof (*t));
310   if (t == NULL)
311   {
312     ERROR ("fc_config_add_target: calloc failed.");
313     return (-1);
314   }
315
316   sstrncpy (t->name, ptr->name, sizeof (t->name));
317   memcpy (&t->proc, &ptr->proc, sizeof (t->proc));
318   t->user_data = NULL;
319   t->next = NULL;
320
321   if (t->proc.create != NULL)
322   {
323     status = (*t->proc.create) (ci, &t->user_data);
324     if (status != 0)
325     {
326       WARNING ("Filter subsystem: Failed to create a %s target.",
327           t->name);
328       fc_free_targets (t);
329       return (-1);
330     }
331   }
332   else
333   {
334     t->user_data = NULL;
335   }
336
337   if (*targets_head != NULL)
338   {
339     ptr = *targets_head;
340     while (ptr->next != NULL)
341       ptr = ptr->next;
342
343     ptr->next = t;
344   }
345   else
346   {
347     *targets_head = t;
348   }
349
350   return (0);
351 } /* }}} int fc_config_add_target */
352
353 static int fc_config_add_rule (fc_chain_t *chain, /* {{{ */
354     oconfig_item_t *ci)
355 {
356   fc_rule_t *rule;
357   char rule_name[2*DATA_MAX_NAME_LEN] = "Unnamed rule";
358   int status = 0;
359   int i;
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 (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 i;
450   int new_chain = 1;
451
452   if ((ci->values_num != 1)
453       || (ci->values[0].type != OCONFIG_TYPE_STRING))
454   {
455     WARNING ("Filter subsystem: <Chain> blocks require exactly one "
456         "string argument.");
457     return (-1);
458   }
459
460   if (chain_list_head != NULL)
461   {
462     if ((chain = fc_chain_get_by_name (ci->values[0].value.string)) != NULL)
463       new_chain = 0;
464   }
465
466   if (chain == NULL)
467   {
468     chain = calloc (1, sizeof (*chain));
469     if (chain == NULL)
470     {
471       ERROR ("fc_config_add_chain: calloc failed.");
472       return (-1);
473     }
474     sstrncpy (chain->name, ci->values[0].value.string, sizeof (chain->name));
475   }
476
477   for (i = 0; i < ci->children_num; i++)
478   {
479     oconfig_item_t *option = ci->children + i;
480
481     if (strcasecmp ("Rule", option->key) == 0)
482       status = fc_config_add_rule (chain, option);
483     else if (strcasecmp ("Target", option->key) == 0)
484       status = fc_config_add_target (&chain->targets, option);
485     else
486     {
487       WARNING ("Filter subsystem: Chain %s: Option `%s' not allowed "
488           "inside a <Chain> block.", chain->name, option->key);
489       status = -1;
490     }
491
492     if (status != 0)
493       break;
494   } /* for (ci->children) */
495
496   if (status != 0)
497   {
498     fc_free_chains (chain);
499     return (-1);
500   }
501
502   if (chain_list_head != NULL)
503   {
504     if (!new_chain)
505       return (0);
506
507     fc_chain_t *ptr;
508
509     ptr = chain_list_head;
510     while (ptr->next != NULL)
511       ptr = ptr->next;
512
513     ptr->next = chain;
514   }
515   else
516   {
517     chain_list_head = chain;
518   }
519
520   return (0);
521 } /* }}} int fc_config_add_chain */
522
523 /*
524  * Built-in target "jump"
525  *
526  * Prefix `bit' like `_b_uilt-_i_n _t_arget'
527  */
528 static int fc_bit_jump_create (const oconfig_item_t *ci, /* {{{ */
529     void **user_data)
530 {
531   oconfig_item_t *ci_chain;
532
533   if (ci->children_num != 1)
534   {
535     ERROR ("Filter subsystem: The built-in target `jump' needs exactly "
536         "one `Chain' argument!");
537     return (-1);
538   }
539
540   ci_chain = ci->children;
541   if (strcasecmp ("Chain", ci_chain->key) != 0)
542   {
543     ERROR ("Filter subsystem: The built-in target `jump' does not "
544         "support the configuration option `%s'.",
545         ci_chain->key);
546     return (-1);
547   }
548
549   if ((ci_chain->values_num != 1)
550       || (ci_chain->values[0].type != OCONFIG_TYPE_STRING))
551   {
552     ERROR ("Filter subsystem: Built-in target `jump': The `Chain' option "
553         "needs exactly one string argument.");
554     return (-1);
555   }
556
557   *user_data = fc_strdup (ci_chain->values[0].value.string);
558   if (*user_data == NULL)
559   {
560     ERROR ("fc_bit_jump_create: fc_strdup failed.");
561     return (-1);
562   }
563
564   return (0);
565 } /* }}} int fc_bit_jump_create */
566
567 static int fc_bit_jump_destroy (void **user_data) /* {{{ */
568 {
569   if (user_data != NULL)
570   {
571     free (*user_data);
572     *user_data = NULL;
573   }
574
575   return (0);
576 } /* }}} int fc_bit_jump_destroy */
577
578 static int fc_bit_jump_invoke (const data_set_t *ds, /* {{{ */
579     value_list_t *vl, notification_meta_t __attribute__((unused)) **meta,
580     void **user_data)
581 {
582   char *chain_name;
583   fc_chain_t *chain;
584   int status;
585
586   chain_name = *user_data;
587
588   for (chain = chain_list_head; chain != NULL; chain = chain->next)
589     if (strcasecmp (chain_name, chain->name) == 0)
590       break;
591
592   if (chain == NULL)
593   {
594     ERROR ("Filter subsystem: Built-in target `jump': There is no chain "
595         "named `%s'.", chain_name);
596     return (-1);
597   }
598
599   status = fc_process_chain (ds, vl, chain);
600   if (status < 0)
601     return (status);
602   else if (status == FC_TARGET_STOP)
603     return (FC_TARGET_STOP);
604   else
605     return (FC_TARGET_CONTINUE);
606 } /* }}} int fc_bit_jump_invoke */
607
608 static int fc_bit_stop_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_STOP);
614 } /* }}} int fc_bit_stop_invoke */
615
616 static int fc_bit_return_invoke (const data_set_t __attribute__((unused)) *ds, /* {{{ */
617     value_list_t __attribute__((unused)) *vl,
618     notification_meta_t __attribute__((unused)) **meta,
619     void __attribute__((unused)) **user_data)
620 {
621   return (FC_TARGET_RETURN);
622 } /* }}} int fc_bit_return_invoke */
623
624 static int fc_bit_write_create (const oconfig_item_t *ci, /* {{{ */
625     void **user_data)
626 {
627   int i;
628
629   fc_writer_t *plugin_list = NULL;
630   size_t plugin_list_len = 0;
631
632   for (i = 0; i < ci->children_num; i++)
633   {
634     oconfig_item_t *child = ci->children + i;
635     fc_writer_t *temp;
636     int j;
637
638     if (strcasecmp ("Plugin", child->key) != 0)
639     {
640       ERROR ("Filter subsystem: The built-in target `write' does not "
641           "support the configuration option `%s'.",
642           child->key);
643       continue;
644     }
645
646     for (j = 0; j < child->values_num; j++)
647     {
648       char *plugin;
649
650       if (child->values[j].type != OCONFIG_TYPE_STRING)
651       {
652         ERROR ("Filter subsystem: Built-in target `write': "
653             "The `Plugin' option accepts only string arguments.");
654         continue;
655       }
656       plugin = child->values[j].value.string;
657
658       temp = realloc (plugin_list, (plugin_list_len + 2)
659           * (sizeof (*plugin_list)));
660       if (temp == NULL)
661       {
662         ERROR ("fc_bit_write_create: realloc failed.");
663         continue;
664       }
665       plugin_list = temp;
666
667       plugin_list[plugin_list_len].plugin = fc_strdup (plugin);
668       if (plugin_list[plugin_list_len].plugin == NULL)
669       {
670         ERROR ("fc_bit_write_create: fc_strdup failed.");
671         continue;
672       }
673       C_COMPLAIN_INIT (&plugin_list[plugin_list_len].complaint);
674       plugin_list_len++;
675       plugin_list[plugin_list_len].plugin = 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   fc_writer_t *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].plugin != NULL; i++)
695     free (plugin_list[i].plugin);
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   fc_writer_t *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].plugin == NULL))
713   {
714     static c_complain_t write_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, &write_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       plugin_log_available_writers ();
728     }
729     else if (status != 0)
730     {
731       /* often, this is a permanent error (e.g. target system unavailable),
732        * so use the complain mechanism rather than spamming the logs */
733       c_complain (LOG_INFO, &write_complaint,
734           "Filter subsystem: Built-in target `write': Dispatching value to "
735           "all write plugins failed with status %i.", status);
736     }
737     else
738     {
739       assert (status == 0);
740       c_release (LOG_INFO, &write_complaint, "Filter subsystem: "
741           "Built-in target `write': Some write plugin is back to normal "
742           "operation. `write' succeeded.");
743     }
744   }
745   else
746   {
747     size_t i;
748
749     for (i = 0; plugin_list[i].plugin != NULL; i++)
750     {
751       status = plugin_write (plugin_list[i].plugin, ds, vl);
752       if (status != 0)
753       {
754         c_complain (LOG_INFO, &plugin_list[i].complaint,
755             "Filter subsystem: Built-in target `write': Dispatching value to "
756             "the `%s' plugin failed with status %i.",
757             plugin_list[i].plugin, status);
758
759         plugin_log_available_writers ();
760       }
761       else
762       {
763         c_release (LOG_INFO, &plugin_list[i].complaint,
764             "Filter subsystem: Built-in target `write': Plugin `%s' is back "
765             "to normal operation. `write' succeeded.", plugin_list[i].plugin);
766       }
767     } /* for (i = 0; plugin_list[i] != NULL; i++) */
768   }
769
770   return (FC_TARGET_CONTINUE);
771 } /* }}} int fc_bit_write_invoke */
772
773 static int fc_init_once (void) /* {{{ */
774 {
775   static int done = 0;
776   target_proc_t tproc;
777
778   if (done != 0)
779     return (0);
780
781   memset (&tproc, 0, sizeof (tproc));
782   tproc.create  = fc_bit_jump_create;
783   tproc.destroy = fc_bit_jump_destroy;
784   tproc.invoke  = fc_bit_jump_invoke;
785   fc_register_target ("jump", tproc);
786
787   memset (&tproc, 0, sizeof (tproc));
788   tproc.create  = NULL;
789   tproc.destroy = NULL;
790   tproc.invoke  = fc_bit_stop_invoke;
791   fc_register_target ("stop", tproc);
792
793   memset (&tproc, 0, sizeof (tproc));
794   tproc.create  = NULL;
795   tproc.destroy = NULL;
796   tproc.invoke  = fc_bit_return_invoke;
797   fc_register_target ("return", tproc);
798
799   memset (&tproc, 0, sizeof (tproc));
800   tproc.create  = fc_bit_write_create;
801   tproc.destroy = fc_bit_write_destroy;
802   tproc.invoke  = fc_bit_write_invoke;
803   fc_register_target ("write", tproc);
804
805   done++;
806   return (0);
807 } /* }}} int fc_init_once */
808
809 /*
810  * Public functions
811  */
812 /* Add a match to list of available matches. */
813 int fc_register_match (const char *name, match_proc_t proc) /* {{{ */
814 {
815   fc_match_t *m;
816
817   DEBUG ("fc_register_match (%s);", name);
818
819   m = calloc (1, sizeof (*m));
820   if (m == NULL)
821     return (-ENOMEM);
822
823   sstrncpy (m->name, name, sizeof (m->name));
824   memcpy (&m->proc, &proc, sizeof (m->proc));
825
826   if (match_list_head == NULL)
827   {
828     match_list_head = m;
829   }
830   else
831   {
832     fc_match_t *ptr;
833
834     ptr = match_list_head;
835     while (ptr->next != NULL)
836       ptr = ptr->next;
837
838     ptr->next = m;
839   }
840
841   return (0);
842 } /* }}} int fc_register_match */
843
844 /* Add a target to list of available targets. */
845 int fc_register_target (const char *name, target_proc_t proc) /* {{{ */
846 {
847   fc_target_t *t;
848
849   DEBUG ("fc_register_target (%s);", name);
850
851   t = calloc (1, sizeof (*t));
852   if (t == NULL)
853     return (-ENOMEM);
854
855   sstrncpy (t->name, name, sizeof (t->name));
856   memcpy (&t->proc, &proc, sizeof (t->proc));
857
858   if (target_list_head == NULL)
859   {
860     target_list_head = t;
861   }
862   else
863   {
864     fc_target_t *ptr;
865
866     ptr = target_list_head;
867     while (ptr->next != NULL)
868       ptr = ptr->next;
869
870     ptr->next = t;
871   }
872
873   return (0);
874 } /* }}} int fc_register_target */
875
876 fc_chain_t *fc_chain_get_by_name (const char *chain_name) /* {{{ */
877 {
878   fc_chain_t *chain;
879
880   if (chain_name == NULL)
881     return (NULL);
882
883   for (chain = chain_list_head; chain != NULL; chain = chain->next)
884     if (strcasecmp (chain_name, chain->name) == 0)
885       return (chain);
886
887   return (NULL);
888 } /* }}} int fc_chain_get_by_name */
889
890 int fc_process_chain (const data_set_t *ds, value_list_t *vl, /* {{{ */
891     fc_chain_t *chain)
892 {
893   fc_rule_t *rule;
894   fc_target_t *target;
895   int status = FC_TARGET_CONTINUE;
896
897   if (chain == NULL)
898     return (-1);
899
900   DEBUG ("fc_process_chain (chain = %s);", chain->name);
901
902   for (rule = chain->rules; rule != NULL; rule = rule->next)
903   {
904     fc_match_t *match;
905     status = FC_TARGET_CONTINUE;
906
907     if (rule->name[0] != 0)
908     {
909       DEBUG ("fc_process_chain (%s): Testing the `%s' rule.",
910           chain->name, rule->name);
911     }
912
913     /* N. B.: rule->matches may be NULL. */
914     for (match = rule->matches; match != NULL; match = match->next)
915     {
916       /* FIXME: Pass the meta-data to match targets here (when implemented). */
917       status = (*match->proc.match) (ds, vl, /* meta = */ NULL,
918           &match->user_data);
919       if (status < 0)
920       {
921         WARNING ("fc_process_chain (%s): A match failed.", chain->name);
922         break;
923       }
924       else if (status != FC_MATCH_MATCHES)
925         break;
926     }
927
928     /* for-loop has been aborted: Either error or no match. */
929     if (match != NULL)
930     {
931       status = FC_TARGET_CONTINUE;
932       continue;
933     }
934
935     if (rule->name[0] != 0)
936     {
937       DEBUG ("fc_process_chain (%s): Rule `%s' matches.",
938           chain->name, rule->name);
939     }
940
941     for (target = rule->targets; target != NULL; target = target->next)
942     {
943       /* If we get here, all matches have matched the value. Execute the
944        * target. */
945       /* FIXME: Pass the meta-data to match targets here (when implemented). */
946       status = (*target->proc.invoke) (ds, vl, /* meta = */ NULL,
947           &target->user_data);
948       if (status < 0)
949       {
950         WARNING ("fc_process_chain (%s): A target failed.", chain->name);
951         continue;
952       }
953       else if (status == FC_TARGET_CONTINUE)
954         continue;
955       else if (status == FC_TARGET_STOP)
956         break;
957       else if (status == FC_TARGET_RETURN)
958         break;
959       else
960       {
961         WARNING ("fc_process_chain (%s): Unknown return value "
962             "from target `%s': %i",
963             chain->name, target->name, status);
964       }
965     }
966
967     if ((status == FC_TARGET_STOP) || (status == FC_TARGET_RETURN))
968     {
969       if (rule->name[0] != 0)
970       {
971         DEBUG ("fc_process_chain (%s): Rule `%s' signaled "
972             "the %s condition.",
973             chain->name, rule->name,
974             (status == FC_TARGET_STOP) ? "stop" : "return");
975       }
976       break;
977     }
978   } /* for (rule) */
979
980   if ((status == FC_TARGET_STOP) || (status == FC_TARGET_RETURN))
981     return (status);
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 : */