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