Merge branch 'collectd-5.7' into collectd-5.8
[collectd.git] / src / match_regex.c
1 /**
2  * collectd - src/match_regex.c
3  * Copyright (C) 2008       Sebastian Harl
4  * Copyright (C) 2008       Florian Forster
5  *
6  * Permission is hereby granted, free of charge, to any person obtaining a
7  * copy of this software and associated documentation files (the "Software"),
8  * to deal in the Software without restriction, including without limitation
9  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
10  * and/or sell copies of the Software, and to permit persons to whom the
11  * Software is furnished to do so, subject to the following conditions:
12  *
13  * The above copyright notice and this permission notice shall be included in
14  * all copies or substantial portions of the Software.
15  *
16  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
22  * DEALINGS IN THE SOFTWARE.
23  *
24  * Authors:
25  *   Sebastian Harl <sh at tokkee.org>
26  *   Florian Forster <octo at collectd.org>
27  **/
28
29 /*
30  * This module allows to filter and rewrite value lists based on
31  * Perl-compatible regular expressions.
32  */
33
34 #include "collectd.h"
35
36 #include "common.h"
37 #include "filter_chain.h"
38 #include "meta_data.h"
39 #include "utils_llist.h"
40
41 #include <regex.h>
42 #include <sys/types.h>
43
44 #define log_err(...) ERROR("`regex' match: " __VA_ARGS__)
45 #define log_warn(...) WARNING("`regex' match: " __VA_ARGS__)
46
47 /*
48  * private data types
49  */
50
51 struct mr_regex_s;
52 typedef struct mr_regex_s mr_regex_t;
53 struct mr_regex_s {
54   regex_t re;
55   char *re_str;
56
57   mr_regex_t *next;
58 };
59
60 struct mr_match_s;
61 typedef struct mr_match_s mr_match_t;
62 struct mr_match_s {
63   mr_regex_t *host;
64   mr_regex_t *plugin;
65   mr_regex_t *plugin_instance;
66   mr_regex_t *type;
67   mr_regex_t *type_instance;
68   llist_t *meta; /* Maps each meta key into mr_regex_t* */
69   _Bool invert;
70 };
71
72 /*
73  * internal helper functions
74  */
75 static void mr_free_regex(mr_regex_t *r) /* {{{ */
76 {
77   if (r == NULL)
78     return;
79
80   regfree(&r->re);
81   memset(&r->re, 0, sizeof(r->re));
82   sfree(r->re_str);
83
84   if (r->next != NULL)
85     mr_free_regex(r->next);
86 } /* }}} void mr_free_regex */
87
88 static void mr_free_match(mr_match_t *m) /* {{{ */
89 {
90   if (m == NULL)
91     return;
92
93   mr_free_regex(m->host);
94   mr_free_regex(m->plugin);
95   mr_free_regex(m->plugin_instance);
96   mr_free_regex(m->type);
97   mr_free_regex(m->type_instance);
98   for (llentry_t *e = llist_head(m->meta); e != NULL; e = e->next) {
99     sfree(e->key);
100     mr_free_regex((mr_regex_t *)e->value);
101   }
102   llist_destroy(m->meta);
103
104   sfree(m);
105 } /* }}} void mr_free_match */
106
107 static int mr_match_regexen(mr_regex_t *re_head, /* {{{ */
108                             const char *string) {
109   if (re_head == NULL)
110     return FC_MATCH_MATCHES;
111
112   for (mr_regex_t *re = re_head; re != NULL; re = re->next) {
113     int status;
114
115     status = regexec(&re->re, string,
116                      /* nmatch = */ 0, /* pmatch = */ NULL,
117                      /* eflags = */ 0);
118     if (status == 0) {
119       DEBUG("regex match: Regular expression `%s' matches `%s'.", re->re_str,
120             string);
121     } else {
122       DEBUG("regex match: Regular expression `%s' does not match `%s'.",
123             re->re_str, string);
124       return FC_MATCH_NO_MATCH;
125     }
126   }
127
128   return FC_MATCH_MATCHES;
129 } /* }}} int mr_match_regexen */
130
131 static int mr_add_regex(mr_regex_t **re_head, const char *re_str, /* {{{ */
132                         const char *option) {
133   mr_regex_t *re;
134   int status;
135
136   re = calloc(1, sizeof(*re));
137   if (re == NULL) {
138     log_err("mr_add_regex: calloc failed.");
139     return -1;
140   }
141   re->next = NULL;
142
143   re->re_str = strdup(re_str);
144   if (re->re_str == NULL) {
145     sfree(re);
146     log_err("mr_add_regex: strdup failed.");
147     return -1;
148   }
149
150   status = regcomp(&re->re, re->re_str, REG_EXTENDED | REG_NOSUB);
151   if (status != 0) {
152     char errmsg[1024];
153     regerror(status, &re->re, errmsg, sizeof(errmsg));
154     errmsg[sizeof(errmsg) - 1] = 0;
155     log_err("Compiling regex `%s' for `%s' failed: %s.", re->re_str, option,
156             errmsg);
157     sfree(re->re_str);
158     sfree(re);
159     return -1;
160   }
161
162   if (*re_head == NULL) {
163     *re_head = re;
164   } else {
165     mr_regex_t *ptr;
166
167     ptr = *re_head;
168     while (ptr->next != NULL)
169       ptr = ptr->next;
170
171     ptr->next = re;
172   }
173
174   return 0;
175 } /* }}} int mr_add_regex */
176
177 static int mr_config_add_regex(mr_regex_t **re_head, /* {{{ */
178                                oconfig_item_t *ci) {
179   if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_STRING)) {
180     log_warn("`%s' needs exactly one string argument.", ci->key);
181     return -1;
182   }
183
184   return mr_add_regex(re_head, ci->values[0].value.string, ci->key);
185 } /* }}} int mr_config_add_regex */
186
187 static int mr_config_add_meta_regex(llist_t **meta, /* {{{ */
188                                     oconfig_item_t *ci) {
189   char *meta_key;
190   llentry_t *entry;
191   mr_regex_t *re_head;
192   int status;
193   char buffer[1024];
194
195   if ((ci->values_num != 2) || (ci->values[0].type != OCONFIG_TYPE_STRING) ||
196       (ci->values[1].type != OCONFIG_TYPE_STRING)) {
197     log_warn("`%s' needs exactly two string arguments.", ci->key);
198     return -1;
199   }
200
201   if (*meta == NULL) {
202     *meta = llist_create();
203     if (*meta == NULL) {
204       log_err("mr_config_add_meta_regex: llist_create failed.");
205       return -1;
206     }
207   }
208
209   meta_key = ci->values[0].value.string;
210   entry = llist_search(*meta, meta_key);
211   if (entry == NULL) {
212     meta_key = strdup(meta_key);
213     if (meta_key == NULL) {
214       log_err("mr_config_add_meta_regex: strdup failed.");
215       return -1;
216     }
217     entry = llentry_create(meta_key, NULL);
218     if (entry == NULL) {
219       log_err("mr_config_add_meta_regex: llentry_create failed.");
220       sfree(meta_key);
221       return -1;
222     }
223     /* meta_key and entry will now be freed by mr_free_match(). */
224     llist_append(*meta, entry);
225   }
226
227   snprintf(buffer, sizeof(buffer), "%s `%s'", ci->key, meta_key);
228   /* Can't pass &entry->value into mr_add_regex, so copy in/out. */
229   re_head = entry->value;
230   status = mr_add_regex(&re_head, ci->values[1].value.string, buffer);
231   if (status == 0) {
232     entry->value = re_head;
233   }
234   return status;
235 } /* }}} int mr_config_add_meta_regex */
236
237 static int mr_create(const oconfig_item_t *ci, void **user_data) /* {{{ */
238 {
239   mr_match_t *m;
240   int status;
241
242   m = calloc(1, sizeof(*m));
243   if (m == NULL) {
244     log_err("mr_create: calloc failed.");
245     return -ENOMEM;
246   }
247
248   m->invert = 0;
249
250   status = 0;
251   for (int i = 0; i < ci->children_num; i++) {
252     oconfig_item_t *child = ci->children + i;
253
254     if ((strcasecmp("Host", child->key) == 0) ||
255         (strcasecmp("Hostname", child->key) == 0))
256       status = mr_config_add_regex(&m->host, child);
257     else if (strcasecmp("Plugin", child->key) == 0)
258       status = mr_config_add_regex(&m->plugin, child);
259     else if (strcasecmp("PluginInstance", child->key) == 0)
260       status = mr_config_add_regex(&m->plugin_instance, child);
261     else if (strcasecmp("Type", child->key) == 0)
262       status = mr_config_add_regex(&m->type, child);
263     else if (strcasecmp("TypeInstance", child->key) == 0)
264       status = mr_config_add_regex(&m->type_instance, child);
265     else if (strcasecmp("MetaData", child->key) == 0)
266       status = mr_config_add_meta_regex(&m->meta, child);
267     else if (strcasecmp("Invert", child->key) == 0)
268       status = cf_util_get_boolean(child, &m->invert);
269     else {
270       log_err("The `%s' configuration option is not understood and "
271               "will be ignored.",
272               child->key);
273       status = 0;
274     }
275
276     if (status != 0)
277       break;
278   }
279
280   /* Additional sanity-checking */
281   while (status == 0) {
282     if ((m->host == NULL) && (m->plugin == NULL) &&
283         (m->plugin_instance == NULL) && (m->type == NULL) &&
284         (m->type_instance == NULL) && (m->meta == NULL)) {
285       log_err("No (valid) regular expressions have been configured. "
286               "This match will be ignored.");
287       status = -1;
288     }
289
290     break;
291   }
292
293   if (status != 0) {
294     mr_free_match(m);
295     return status;
296   }
297
298   *user_data = m;
299   return 0;
300 } /* }}} int mr_create */
301
302 static int mr_destroy(void **user_data) /* {{{ */
303 {
304   if ((user_data != NULL) && (*user_data != NULL))
305     mr_free_match(*user_data);
306   return 0;
307 } /* }}} int mr_destroy */
308
309 static int mr_match(const data_set_t __attribute__((unused)) * ds, /* {{{ */
310                     const value_list_t *vl,
311                     notification_meta_t __attribute__((unused)) * *meta,
312                     void **user_data) {
313   mr_match_t *m;
314   int match_value = FC_MATCH_MATCHES;
315   int nomatch_value = FC_MATCH_NO_MATCH;
316
317   if ((user_data == NULL) || (*user_data == NULL))
318     return -1;
319
320   m = *user_data;
321
322   if (m->invert) {
323     match_value = FC_MATCH_NO_MATCH;
324     nomatch_value = FC_MATCH_MATCHES;
325   }
326
327   if (mr_match_regexen(m->host, vl->host) == FC_MATCH_NO_MATCH)
328     return nomatch_value;
329   if (mr_match_regexen(m->plugin, vl->plugin) == FC_MATCH_NO_MATCH)
330     return nomatch_value;
331   if (mr_match_regexen(m->plugin_instance, vl->plugin_instance) ==
332       FC_MATCH_NO_MATCH)
333     return nomatch_value;
334   if (mr_match_regexen(m->type, vl->type) == FC_MATCH_NO_MATCH)
335     return nomatch_value;
336   if (mr_match_regexen(m->type_instance, vl->type_instance) ==
337       FC_MATCH_NO_MATCH)
338     return nomatch_value;
339   if (vl->meta != NULL) {
340     for (llentry_t *e = llist_head(m->meta); e != NULL; e = e->next) {
341       mr_regex_t *meta_re = (mr_regex_t *)e->value;
342       char *value;
343       int status = meta_data_get_string(vl->meta, e->key, &value);
344       if (status == (-ENOENT)) /* key is not present */
345         return nomatch_value;
346       if (status != 0) /* some other problem */
347         continue;      /* error will have already been printed. */
348       if (mr_match_regexen(meta_re, value) == FC_MATCH_NO_MATCH) {
349         sfree(value);
350         return nomatch_value;
351       }
352       sfree(value);
353     }
354   }
355
356   return match_value;
357 } /* }}} int mr_match */
358
359 void module_register(void) {
360   match_proc_t mproc = {0};
361
362   mproc.create = mr_create;
363   mproc.destroy = mr_destroy;
364   mproc.match = mr_match;
365   fc_register_match("regex", mproc);
366 } /* module_register */