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