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