Merge branch 'collectd-5.4' into 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 = (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
396     if (strcasecmp ("Match", option->key) == 0)
397       status = fc_config_add_match (&rule->matches, option);
398     else if (strcasecmp ("Target", option->key) == 0)
399       status = fc_config_add_target (&rule->targets, option);
400     else
401     {
402       WARNING ("Filter subsystem: %s: Option `%s' not allowed "
403           "inside a <Rule> block.", rule_name, option->key);
404       status = -1;
405     }
406
407     if (status != 0)
408       break;
409   } /* for (ci->children) */
410
411   /* Additional sanity checking. */
412   while (status == 0)
413   {
414     if (rule->targets == NULL)
415     {
416       WARNING ("Filter subsystem: %s: No target has been specified.",
417           rule_name);
418       status = -1;
419       break;
420     }
421
422     break;
423   } /* while (status == 0) */
424
425   if (status != 0)
426   {
427     fc_free_rules (rule);
428     return (-1);
429   }
430
431   if (chain->rules != NULL)
432   {
433     fc_rule_t *ptr;
434
435     ptr = chain->rules;
436     while (ptr->next != NULL)
437       ptr = ptr->next;
438
439     ptr->next = rule;
440   }
441   else
442   {
443     chain->rules = rule;
444   }
445
446   return (0);
447 } /* }}} int fc_config_add_rule */
448
449 static int fc_config_add_chain (const oconfig_item_t *ci) /* {{{ */
450 {
451   fc_chain_t *chain = NULL;
452   int status = 0;
453   int i;
454   int new_chain = 1;
455
456   if ((ci->values_num != 1)
457       || (ci->values[0].type != OCONFIG_TYPE_STRING))
458   {
459     WARNING ("Filter subsystem: <Chain> blocks require exactly one "
460         "string argument.");
461     return (-1);
462   }
463
464   if (chain_list_head != NULL)
465   {
466     if ((chain = fc_chain_get_by_name (ci->values[0].value.string)) != NULL)
467       new_chain = 0;
468   }
469
470   if (chain == NULL)
471   {
472     chain = (fc_chain_t *) malloc (sizeof (*chain));
473     if (chain == NULL)
474     {
475       ERROR ("fc_config_add_chain: malloc failed.");
476       return (-1);
477     }
478     memset (chain, 0, sizeof (*chain));
479     sstrncpy (chain->name, ci->values[0].value.string, sizeof (chain->name));
480     chain->rules = NULL;
481     chain->targets = NULL;
482     chain->next = NULL;
483   }
484
485   for (i = 0; i < ci->children_num; i++)
486   {
487     oconfig_item_t *option = ci->children + i;
488
489     if (strcasecmp ("Rule", option->key) == 0)
490       status = fc_config_add_rule (chain, option);
491     else if (strcasecmp ("Target", option->key) == 0)
492       status = fc_config_add_target (&chain->targets, option);
493     else
494     {
495       WARNING ("Filter subsystem: Chain %s: Option `%s' not allowed "
496           "inside a <Chain> block.", chain->name, option->key);
497       status = -1;
498     }
499
500     if (status != 0)
501       break;
502   } /* for (ci->children) */
503
504   if (status != 0)
505   {
506     fc_free_chains (chain);
507     return (-1);
508   }
509
510   if (chain_list_head != NULL)
511   {
512     if (!new_chain)
513       return (0);
514
515     fc_chain_t *ptr;
516
517     ptr = chain_list_head;
518     while (ptr->next != NULL)
519       ptr = ptr->next;
520
521     ptr->next = chain;
522   }
523   else
524   {
525     chain_list_head = chain;
526   }
527
528   return (0);
529 } /* }}} int fc_config_add_chain */
530
531 /*
532  * Built-in target "jump"
533  *
534  * Prefix `bit' like `_b_uilt-_i_n _t_arget'
535  */
536 static int fc_bit_jump_create (const oconfig_item_t *ci, /* {{{ */
537     void **user_data)
538 {
539   oconfig_item_t *ci_chain;
540
541   if (ci->children_num != 1)
542   {
543     ERROR ("Filter subsystem: The built-in target `jump' needs exactly "
544         "one `Chain' argument!");
545     return (-1);
546   }
547
548   ci_chain = ci->children;
549   if (strcasecmp ("Chain", ci_chain->key) != 0)
550   {
551     ERROR ("Filter subsystem: The built-in target `jump' does not "
552         "support the configuration option `%s'.",
553         ci_chain->key);
554     return (-1);
555   }
556
557   if ((ci_chain->values_num != 1)
558       || (ci_chain->values[0].type != OCONFIG_TYPE_STRING))
559   {
560     ERROR ("Filter subsystem: Built-in target `jump': The `Chain' option "
561         "needs exactly one string argument.");
562     return (-1);
563   }
564
565   *user_data = fc_strdup (ci_chain->values[0].value.string);
566   if (*user_data == NULL)
567   {
568     ERROR ("fc_bit_jump_create: fc_strdup failed.");
569     return (-1);
570   }
571
572   return (0);
573 } /* }}} int fc_bit_jump_create */
574
575 static int fc_bit_jump_destroy (void **user_data) /* {{{ */
576 {
577   if (user_data != NULL)
578   {
579     free (*user_data);
580     *user_data = NULL;
581   }
582
583   return (0);
584 } /* }}} int fc_bit_jump_destroy */
585
586 static int fc_bit_jump_invoke (const data_set_t *ds, /* {{{ */
587     value_list_t *vl, notification_meta_t __attribute__((unused)) **meta,
588     void **user_data)
589 {
590   char *chain_name;
591   fc_chain_t *chain;
592   int status;
593
594   chain_name = *user_data;
595
596   for (chain = chain_list_head; chain != NULL; chain = chain->next)
597     if (strcasecmp (chain_name, chain->name) == 0)
598       break;
599
600   if (chain == NULL)
601   {
602     ERROR ("Filter subsystem: Built-in target `jump': There is no chain "
603         "named `%s'.", chain_name);
604     return (-1);
605   }
606
607   status = fc_process_chain (ds, vl, chain);
608   if (status < 0)
609     return (status);
610   else if (status == FC_TARGET_STOP)
611     return (FC_TARGET_STOP);
612   else
613     return (FC_TARGET_CONTINUE);
614 } /* }}} int fc_bit_jump_invoke */
615
616 static int fc_bit_stop_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_STOP);
622 } /* }}} int fc_bit_stop_invoke */
623
624 static int fc_bit_return_invoke (const data_set_t __attribute__((unused)) *ds, /* {{{ */
625     value_list_t __attribute__((unused)) *vl,
626     notification_meta_t __attribute__((unused)) **meta,
627     void __attribute__((unused)) **user_data)
628 {
629   return (FC_TARGET_RETURN);
630 } /* }}} int fc_bit_return_invoke */
631
632 static int fc_bit_write_create (const oconfig_item_t *ci, /* {{{ */
633     void **user_data)
634 {
635   int i;
636
637   fc_writer_t *plugin_list = NULL;
638   size_t plugin_list_len = 0;
639
640   for (i = 0; i < ci->children_num; i++)
641   {
642     oconfig_item_t *child = ci->children + i;
643     fc_writer_t *temp;
644     int j;
645
646     if (strcasecmp ("Plugin", child->key) != 0)
647     {
648       ERROR ("Filter subsystem: The built-in target `write' does not "
649           "support the configuration option `%s'.",
650           child->key);
651       continue;
652     }
653
654     for (j = 0; j < child->values_num; j++)
655     {
656       char *plugin;
657
658       if (child->values[j].type != OCONFIG_TYPE_STRING)
659       {
660         ERROR ("Filter subsystem: Built-in target `write': "
661             "The `Plugin' option accepts only string arguments.");
662         continue;
663       }
664       plugin = child->values[j].value.string;
665
666       temp = (fc_writer_t *) realloc (plugin_list, (plugin_list_len + 2)
667           * (sizeof (*plugin_list)));
668       if (temp == NULL)
669       {
670         ERROR ("fc_bit_write_create: realloc failed.");
671         continue;
672       }
673       plugin_list = temp;
674
675       plugin_list[plugin_list_len].plugin = fc_strdup (plugin);
676       if (plugin_list[plugin_list_len].plugin == NULL)
677       {
678         ERROR ("fc_bit_write_create: fc_strdup failed.");
679         continue;
680       }
681       C_COMPLAIN_INIT (&plugin_list[plugin_list_len].complaint);
682       plugin_list_len++;
683       plugin_list[plugin_list_len].plugin = NULL;
684     } /* for (j = 0; j < child->values_num; j++) */
685   } /* for (i = 0; i < ci->children_num; i++) */
686
687   *user_data = plugin_list;
688
689   return (0);
690 } /* }}} int fc_bit_write_create */
691
692 static int fc_bit_write_destroy (void **user_data) /* {{{ */
693 {
694   fc_writer_t *plugin_list;
695   size_t i;
696
697   if ((user_data == NULL) || (*user_data == NULL))
698     return (0);
699
700   plugin_list = *user_data;
701
702   for (i = 0; plugin_list[i].plugin != NULL; i++)
703     free (plugin_list[i].plugin);
704   free (plugin_list);
705
706   return (0);
707 } /* }}} int fc_bit_write_destroy */
708
709 static int fc_bit_write_invoke (const data_set_t *ds, /* {{{ */
710     value_list_t *vl, notification_meta_t __attribute__((unused)) **meta,
711     void **user_data)
712 {
713   fc_writer_t *plugin_list;
714   int status;
715
716   plugin_list = NULL;
717   if (user_data != NULL)
718     plugin_list = *user_data;
719
720   if ((plugin_list == NULL) || (plugin_list[0].plugin == NULL))
721   {
722     static c_complain_t write_complaint = C_COMPLAIN_INIT_STATIC;
723
724     status = plugin_write (/* plugin = */ NULL, ds, vl);
725     if (status == ENOENT)
726     {
727       /* in most cases this is a permanent error, so use the complain
728        * mechanism rather than spamming the logs */
729       c_complain (LOG_INFO, &write_complaint,
730           "Filter subsystem: Built-in target `write': Dispatching value to "
731           "all write plugins failed with status %i (ENOENT). "
732           "Most likely this means you didn't load any write plugins.",
733           status);
734
735       plugin_log_available_writers ();
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         plugin_log_available_writers ();
768       }
769       else
770       {
771         c_release (LOG_INFO, &plugin_list[i].complaint,
772             "Filter subsystem: Built-in target `write': Plugin `%s' is back "
773             "to normal operation. `write' succeeded.", plugin_list[i].plugin);
774       }
775     } /* for (i = 0; plugin_list[i] != NULL; i++) */
776   }
777
778   return (FC_TARGET_CONTINUE);
779 } /* }}} int fc_bit_write_invoke */
780
781 static int fc_init_once (void) /* {{{ */
782 {
783   static int done = 0;
784   target_proc_t tproc;
785
786   if (done != 0)
787     return (0);
788
789   memset (&tproc, 0, sizeof (tproc));
790   tproc.create  = fc_bit_jump_create;
791   tproc.destroy = fc_bit_jump_destroy;
792   tproc.invoke  = fc_bit_jump_invoke;
793   fc_register_target ("jump", tproc);
794
795   memset (&tproc, 0, sizeof (tproc));
796   tproc.create  = NULL;
797   tproc.destroy = NULL;
798   tproc.invoke  = fc_bit_stop_invoke;
799   fc_register_target ("stop", tproc);
800
801   memset (&tproc, 0, sizeof (tproc));
802   tproc.create  = NULL;
803   tproc.destroy = NULL;
804   tproc.invoke  = fc_bit_return_invoke;
805   fc_register_target ("return", tproc);
806
807   memset (&tproc, 0, sizeof (tproc));
808   tproc.create  = fc_bit_write_create;
809   tproc.destroy = fc_bit_write_destroy;
810   tproc.invoke  = fc_bit_write_invoke;
811   fc_register_target ("write", tproc);
812
813   done++;
814   return (0);
815 } /* }}} int fc_init_once */
816
817 /*
818  * Public functions
819  */
820 /* Add a match to list of available matches. */
821 int fc_register_match (const char *name, match_proc_t proc) /* {{{ */
822 {
823   fc_match_t *m;
824
825   DEBUG ("fc_register_match (%s);", name);
826
827   m = (fc_match_t *) malloc (sizeof (*m));
828   if (m == NULL)
829     return (-ENOMEM);
830   memset (m, 0, sizeof (*m));
831
832   sstrncpy (m->name, name, sizeof (m->name));
833   memcpy (&m->proc, &proc, sizeof (m->proc));
834   m->next = NULL;
835
836   if (match_list_head == NULL)
837   {
838     match_list_head = m;
839   }
840   else
841   {
842     fc_match_t *ptr;
843
844     ptr = match_list_head;
845     while (ptr->next != NULL)
846       ptr = ptr->next;
847
848     ptr->next = m;
849   }
850
851   return (0);
852 } /* }}} int fc_register_match */
853
854 /* Add a target to list of available targets. */
855 int fc_register_target (const char *name, target_proc_t proc) /* {{{ */
856 {
857   fc_target_t *t;
858
859   DEBUG ("fc_register_target (%s);", name);
860
861   t = (fc_target_t *) malloc (sizeof (*t));
862   if (t == NULL)
863     return (-ENOMEM);
864   memset (t, 0, sizeof (*t));
865
866   sstrncpy (t->name, name, sizeof (t->name));
867   memcpy (&t->proc, &proc, sizeof (t->proc));
868   t->next = NULL;
869
870   if (target_list_head == NULL)
871   {
872     target_list_head = t;
873   }
874   else
875   {
876     fc_target_t *ptr;
877
878     ptr = target_list_head;
879     while (ptr->next != NULL)
880       ptr = ptr->next;
881
882     ptr->next = t;
883   }
884
885   return (0);
886 } /* }}} int fc_register_target */
887
888 fc_chain_t *fc_chain_get_by_name (const char *chain_name) /* {{{ */
889 {
890   fc_chain_t *chain;
891
892   if (chain_name == NULL)
893     return (NULL);
894
895   for (chain = chain_list_head; chain != NULL; chain = chain->next)
896     if (strcasecmp (chain_name, chain->name) == 0)
897       return (chain);
898
899   return (NULL);
900 } /* }}} int fc_chain_get_by_name */
901
902 int fc_process_chain (const data_set_t *ds, value_list_t *vl, /* {{{ */
903     fc_chain_t *chain)
904 {
905   fc_rule_t *rule;
906   fc_target_t *target;
907   int status = FC_TARGET_CONTINUE;
908
909   if (chain == NULL)
910     return (-1);
911
912   DEBUG ("fc_process_chain (chain = %s);", chain->name);
913
914   for (rule = chain->rules; rule != NULL; rule = rule->next)
915   {
916     fc_match_t *match;
917     status = FC_TARGET_CONTINUE;
918
919     if (rule->name[0] != 0)
920     {
921       DEBUG ("fc_process_chain (%s): Testing the `%s' rule.",
922           chain->name, rule->name);
923     }
924
925     /* N. B.: rule->matches may be NULL. */
926     for (match = rule->matches; match != NULL; match = match->next)
927     {
928       /* FIXME: Pass the meta-data to match targets here (when implemented). */
929       status = (*match->proc.match) (ds, vl, /* meta = */ NULL,
930           &match->user_data);
931       if (status < 0)
932       {
933         WARNING ("fc_process_chain (%s): A match failed.", chain->name);
934         break;
935       }
936       else if (status != FC_MATCH_MATCHES)
937         break;
938     }
939
940     /* for-loop has been aborted: Either error or no match. */
941     if (match != NULL)
942     {
943       status = FC_TARGET_CONTINUE;
944       continue;
945     }
946
947     if (rule->name[0] != 0)
948     {
949       DEBUG ("fc_process_chain (%s): Rule `%s' matches.",
950           chain->name, rule->name);
951     }
952
953     for (target = rule->targets; target != NULL; target = target->next)
954     {
955       /* If we get here, all matches have matched the value. Execute the
956        * target. */
957       /* FIXME: Pass the meta-data to match targets here (when implemented). */
958       status = (*target->proc.invoke) (ds, vl, /* meta = */ NULL,
959           &target->user_data);
960       if (status < 0)
961       {
962         WARNING ("fc_process_chain (%s): A target failed.", chain->name);
963         continue;
964       }
965       else if (status == FC_TARGET_CONTINUE)
966         continue;
967       else if (status == FC_TARGET_STOP)
968         break;
969       else if (status == FC_TARGET_RETURN)
970         break;
971       else
972       {
973         WARNING ("fc_process_chain (%s): Unknown return value "
974             "from target `%s': %i",
975             chain->name, target->name, status);
976       }
977     }
978
979     if ((status == FC_TARGET_STOP) || (status == FC_TARGET_RETURN))
980     {
981       if (rule->name[0] != 0)
982       {
983         DEBUG ("fc_process_chain (%s): Rule `%s' signaled "
984             "the %s condition.",
985             chain->name, rule->name,
986             (status == FC_TARGET_STOP) ? "stop" : "return");
987       }
988       break;
989     }
990   } /* for (rule) */
991
992   if ((status == FC_TARGET_STOP) || (status == FC_TARGET_RETURN))
993     return (status);
994
995   DEBUG ("fc_process_chain (%s): Executing the default targets.",
996       chain->name);
997
998   status = FC_TARGET_CONTINUE;
999   for (target = chain->targets; target != NULL; target = target->next)
1000   {
1001     /* If we get here, all matches have matched the value. Execute the
1002      * target. */
1003     /* FIXME: Pass the meta-data to match targets here (when implemented). */
1004     status = (*target->proc.invoke) (ds, vl, /* meta = */ NULL,
1005         &target->user_data);
1006     if (status < 0)
1007     {
1008       WARNING ("fc_process_chain (%s): The default target failed.",
1009           chain->name);
1010     }
1011     else if (status == FC_TARGET_CONTINUE)
1012       continue;
1013     else if (status == FC_TARGET_STOP)
1014       break;
1015     else if (status == FC_TARGET_RETURN)
1016       break;
1017     else
1018     {
1019       WARNING ("fc_process_chain (%s): Unknown return value "
1020           "from target `%s': %i",
1021           chain->name, target->name, status);
1022     }
1023   }
1024
1025   if ((status == FC_TARGET_STOP)
1026       || (status == FC_TARGET_RETURN))
1027   {
1028     assert (target != NULL);
1029     DEBUG ("fc_process_chain (%s): Default target `%s' signaled "
1030         "the %s condition.",
1031         chain->name, target->name,
1032         (status == FC_TARGET_STOP) ? "stop" : "return");
1033     if (status == FC_TARGET_STOP)
1034       return (FC_TARGET_STOP);
1035     else
1036       return (FC_TARGET_CONTINUE);
1037   }
1038
1039   DEBUG ("fc_process_chain (%s): Signaling `continue' at end of chain.",
1040       chain->name);
1041
1042   return (FC_TARGET_CONTINUE);
1043 } /* }}} int fc_process_chain */
1044
1045 /* Iterate over all rules in the chain and execute all targets for which all
1046  * matches match. */
1047 int fc_default_action (const data_set_t *ds, value_list_t *vl) /* {{{ */
1048 {
1049   /* FIXME: Pass the meta-data to match targets here (when implemented). */
1050   return (fc_bit_write_invoke (ds, vl,
1051         /* meta = */ NULL, /* user_data = */ NULL));
1052 } /* }}} int fc_default_action */
1053
1054 int fc_configure (const oconfig_item_t *ci) /* {{{ */
1055 {
1056   fc_init_once ();
1057
1058   if (ci == NULL)
1059     return (-EINVAL);
1060
1061   if (strcasecmp ("Chain", ci->key) == 0)
1062     return (fc_config_add_chain (ci));
1063
1064   WARNING ("Filter subsystem: Unknown top level config option `%s'.",
1065       ci->key);
1066
1067   return (-1);
1068 } /* }}} int fc_configure */
1069
1070 /* vim: set sw=2 sts=2 et fdm=marker : */