Merge branch 'collectd-5.6'
[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 <sys/types.h>
42 #include <regex.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 {
55         regex_t re;
56         char *re_str;
57
58         mr_regex_t *next;
59 };
60
61 struct mr_match_s;
62 typedef struct mr_match_s mr_match_t;
63 struct mr_match_s
64 {
65         mr_regex_t *host;
66         mr_regex_t *plugin;
67         mr_regex_t *plugin_instance;
68         mr_regex_t *type;
69         mr_regex_t *type_instance;
70         llist_t *meta;  /* Maps each meta key into mr_regex_t* */
71         _Bool invert;
72 };
73
74 /*
75  * internal helper functions
76  */
77 static void mr_free_regex (mr_regex_t *r) /* {{{ */
78 {
79         if (r == NULL)
80                 return;
81
82         regfree (&r->re);
83         memset (&r->re, 0, sizeof (r->re));
84         sfree (r->re_str);
85
86         if (r->next != NULL)
87                 mr_free_regex (r->next);
88 } /* }}} void mr_free_regex */
89
90 static void mr_free_match (mr_match_t *m) /* {{{ */
91 {
92         if (m == NULL)
93                 return;
94
95         mr_free_regex (m->host);
96         mr_free_regex (m->plugin);
97         mr_free_regex (m->plugin_instance);
98         mr_free_regex (m->type);
99         mr_free_regex (m->type_instance);
100         for (llentry_t *e = llist_head(m->meta); e != NULL; e = e->next)
101         {
102                 sfree (e->key);
103                 mr_free_regex ((mr_regex_t *) e->value);
104         }
105         llist_destroy (m->meta);
106
107         sfree (m);
108 } /* }}} void mr_free_match */
109
110 static int mr_match_regexen (mr_regex_t *re_head, /* {{{ */
111                 const char *string)
112 {
113         if (re_head == NULL)
114                 return (FC_MATCH_MATCHES);
115
116         for (mr_regex_t *re = re_head; re != NULL; re = re->next)
117         {
118                 int status;
119
120                 status = regexec (&re->re, string,
121                                 /* nmatch = */ 0, /* pmatch = */ NULL,
122                                 /* eflags = */ 0);
123                 if (status == 0)
124                 {
125                         DEBUG ("regex match: Regular expression `%s' matches `%s'.",
126                                         re->re_str, string);
127                 }
128                 else
129                 {
130                         DEBUG ("regex match: Regular expression `%s' does not match `%s'.",
131                                         re->re_str, string);
132                         return (FC_MATCH_NO_MATCH);
133                 }
134
135         }
136
137         return (FC_MATCH_MATCHES);
138 } /* }}} int mr_match_regexen */
139
140 static int mr_add_regex (mr_regex_t **re_head, const char *re_str, /* {{{ */
141                 const char *option)
142 {
143         mr_regex_t *re;
144         int status;
145
146         re = calloc (1, sizeof (*re));
147         if (re == NULL)
148         {
149                 log_err ("mr_add_regex: calloc failed.");
150                 return (-1);
151         }
152         re->next = NULL;
153
154         re->re_str = strdup (re_str);
155         if (re->re_str == NULL)
156         {
157                 sfree (re);
158                 log_err ("mr_add_regex: strdup failed.");
159                 return (-1);
160         }
161
162         status = regcomp (&re->re, re->re_str, REG_EXTENDED | REG_NOSUB);
163         if (status != 0)
164         {
165                 char errmsg[1024];
166                 regerror (status, &re->re, errmsg, sizeof (errmsg));
167                 errmsg[sizeof (errmsg) - 1] = 0;
168                 log_err ("Compiling regex `%s' for `%s' failed: %s.",
169                                 re->re_str, option, errmsg);
170                 sfree (re->re_str);
171                 sfree (re);
172                 return (-1);
173         }
174
175         if (*re_head == NULL)
176         {
177                 *re_head = re;
178         }
179         else
180         {
181                 mr_regex_t *ptr;
182
183                 ptr = *re_head;
184                 while (ptr->next != NULL)
185                         ptr = ptr->next;
186
187                 ptr->next = re;
188         }
189
190         return (0);
191 } /* }}} int mr_add_regex */
192
193 static int mr_config_add_regex (mr_regex_t **re_head, /* {{{ */
194                 oconfig_item_t *ci)
195 {
196         if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_STRING))
197         {
198                 log_warn ("`%s' needs exactly one string argument.", ci->key);
199                 return (-1);
200         }
201
202         return mr_add_regex (re_head, ci->values[0].value.string, ci->key);
203 } /* }}} int mr_config_add_regex */
204
205 static int mr_config_add_meta_regex (llist_t **meta, /* {{{ */
206                 oconfig_item_t *ci)
207 {
208         char *meta_key;
209         llentry_t *entry;
210         mr_regex_t *re_head;
211         int status;
212         char buffer[1024];
213
214         if ((ci->values_num != 2)
215                 || (ci->values[0].type != OCONFIG_TYPE_STRING)
216                 || (ci->values[1].type != OCONFIG_TYPE_STRING))
217         {
218                 log_warn ("`%s' needs exactly two string arguments.", ci->key);
219                 return (-1);
220         }
221
222         if (*meta == NULL)
223         {
224                 *meta = llist_create();
225                 if (*meta == NULL)
226                 {
227                         log_err ("mr_config_add_meta_regex: llist_create failed.");
228                         return (-1);
229                 }
230         }
231
232         meta_key = ci->values[0].value.string;
233         entry = llist_search (*meta, meta_key);
234         if (entry == NULL)
235         {
236                 meta_key = strdup (meta_key);
237                 if (meta_key == NULL)
238                 {
239                         log_err ("mr_config_add_meta_regex: strdup failed.");
240                         return (-1);
241                 }
242                 entry = llentry_create (meta_key, NULL);
243                 if (entry == NULL)
244                 {
245                         log_err ("mr_config_add_meta_regex: llentry_create failed.");
246                         sfree (meta_key);
247                         return (-1);
248                 }
249                 /* meta_key and entry will now be freed by mr_free_match(). */
250                 llist_append (*meta, entry);
251         }
252
253         ssnprintf (buffer, sizeof (buffer), "%s `%s'", ci->key, meta_key);
254         /* Can't pass &entry->value into mr_add_regex, so copy in/out. */
255         re_head = entry->value;
256         status = mr_add_regex (&re_head, ci->values[1].value.string, buffer);
257         if (status == 0) {
258                 entry->value = re_head;
259         }
260         return status;
261 } /* }}} int mr_config_add_meta_regex */
262
263 static int mr_create (const oconfig_item_t *ci, void **user_data) /* {{{ */
264 {
265         mr_match_t *m;
266         int status;
267
268         m = calloc (1, sizeof (*m));
269         if (m == NULL)
270         {
271                 log_err ("mr_create: calloc failed.");
272                 return (-ENOMEM);
273         }
274
275         m->invert = 0;
276
277         status = 0;
278         for (int i = 0; i < ci->children_num; i++)
279         {
280                 oconfig_item_t *child = ci->children + i;
281
282                 if ((strcasecmp ("Host", child->key) == 0)
283                                 || (strcasecmp ("Hostname", child->key) == 0))
284                         status = mr_config_add_regex (&m->host, child);
285                 else if (strcasecmp ("Plugin", child->key) == 0)
286                         status = mr_config_add_regex (&m->plugin, child);
287                 else if (strcasecmp ("PluginInstance", child->key) == 0)
288                         status = mr_config_add_regex (&m->plugin_instance, child);
289                 else if (strcasecmp ("Type", child->key) == 0)
290                         status = mr_config_add_regex (&m->type, child);
291                 else if (strcasecmp ("TypeInstance", child->key) == 0)
292                         status = mr_config_add_regex (&m->type_instance, child);
293                 else if (strcasecmp ("MetaData", child->key) == 0)
294                         status = mr_config_add_meta_regex (&m->meta, child);
295                 else if (strcasecmp ("Invert", child->key) == 0)
296                         status = cf_util_get_boolean(child, &m->invert);
297                 else
298                 {
299                         log_err ("The `%s' configuration option is not understood and "
300                                         "will be ignored.", child->key);
301                         status = 0;
302                 }
303
304                 if (status != 0)
305                         break;
306         }
307
308         /* Additional sanity-checking */
309         while (status == 0)
310         {
311                 if ((m->host == NULL)
312                                 && (m->plugin == NULL)
313                                 && (m->plugin_instance == NULL)
314                                 && (m->type == NULL)
315                                 && (m->type_instance == NULL)
316                                 && (m->meta == NULL))
317                 {
318                         log_err ("No (valid) regular expressions have been configured. "
319                                         "This match will be ignored.");
320                         status = -1;
321                 }
322
323                 break;
324         }
325
326         if (status != 0)
327         {
328                 mr_free_match (m);
329                 return (status);
330         }
331
332         *user_data = m;
333         return (0);
334 } /* }}} int mr_create */
335
336 static int mr_destroy (void **user_data) /* {{{ */
337 {
338         if ((user_data != NULL) && (*user_data != NULL))
339                 mr_free_match (*user_data);
340         return (0);
341 } /* }}} int mr_destroy */
342
343 static int mr_match (const data_set_t __attribute__((unused)) *ds, /* {{{ */
344                 const value_list_t *vl,
345                 notification_meta_t __attribute__((unused)) **meta,
346                 void **user_data)
347 {
348         mr_match_t *m;
349         int match_value = FC_MATCH_MATCHES;
350         int nomatch_value = FC_MATCH_NO_MATCH;
351
352         if ((user_data == NULL) || (*user_data == NULL))
353                 return (-1);
354
355         m = *user_data;
356
357         if (m->invert)
358         {
359                 match_value = FC_MATCH_NO_MATCH;
360                 nomatch_value = FC_MATCH_MATCHES;
361         }
362
363         if (mr_match_regexen (m->host, vl->host) == FC_MATCH_NO_MATCH)
364                 return (nomatch_value);
365         if (mr_match_regexen (m->plugin, vl->plugin) == FC_MATCH_NO_MATCH)
366                 return (nomatch_value);
367         if (mr_match_regexen (m->plugin_instance,
368                                 vl->plugin_instance) == FC_MATCH_NO_MATCH)
369                 return (nomatch_value);
370         if (mr_match_regexen (m->type, vl->type) == FC_MATCH_NO_MATCH)
371                 return (nomatch_value);
372         if (mr_match_regexen (m->type_instance,
373                                 vl->type_instance) == FC_MATCH_NO_MATCH)
374                 return (nomatch_value);
375         if (vl->meta != NULL)
376         {
377                 for (llentry_t *e = llist_head(m->meta); e != NULL; e = e->next)
378                 {
379                         mr_regex_t *meta_re = (mr_regex_t *) e->value;
380                         char *value;
381                         int status = meta_data_get_string (vl->meta, e->key, &value);
382                         if (status == (-ENOENT))  /* key is not present */
383                                 return (nomatch_value);
384                         if (status != 0)  /* some other problem */
385                                 continue;  /* error will have already been printed. */
386                         if (mr_match_regexen (meta_re, value) == FC_MATCH_NO_MATCH)
387                         {
388                                 sfree (value);
389                                 return (nomatch_value);
390                         }
391                         sfree (value);
392                 }
393         }
394
395         return (match_value);
396 } /* }}} int mr_match */
397
398 void module_register (void)
399 {
400         match_proc_t mproc = { 0 };
401
402         mproc.create  = mr_create;
403         mproc.destroy = mr_destroy;
404         mproc.match   = mr_match;
405         fc_register_match ("regex", mproc);
406 } /* module_register */
407
408 /* vim: set sw=4 ts=4 tw=78 noexpandtab fdm=marker : */
409