3b1afbf5fbf37f455fed258887114058f93194df
[collectd.git] / src / filter_pcre.c
1 /**
2  * collectd - src/filter_pcre.c
3  * Copyright (C) 2008  Sebastian Harl
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  * Author:
19  *   Sebastian Harl <sh at tokkee.org>
20  **/
21
22 /*
23  * This module allows to filter and rewrite value lists based on
24  * Perl-compatible regular expressions.
25  */
26
27 #include "collectd.h"
28 #include "configfile.h"
29 #include "plugin.h"
30 #include "common.h"
31
32 #include "utils_subst.h"
33
34 #include <pcre.h>
35
36 #define log_err(...) ERROR ("filter_pcre: " __VA_ARGS__)
37 #define log_warn(...) WARNING ("filter_pcre: " __VA_ARGS__)
38
39 /*
40  * private data types
41  */
42
43 typedef struct {
44         /* regular expression */
45         pcre       *re;
46         const char *re_str;
47
48         /* extra information from studying the pattern */
49         pcre_extra *extra;
50
51         /* replacment text for string substitution */
52         const char *replacement;
53 } c_pcre_t;
54
55 #define C_PCRE_INIT(regex) do { \
56                 (regex).re     = NULL; \
57                 (regex).re_str = NULL; \
58                 (regex).extra  = NULL; \
59                 (regex).replacement = NULL; \
60         } while (0)
61
62 #define C_PCRE_FREE(regex) do { \
63                 pcre_free ((regex).re); \
64                 free ((void *)(regex).re_str); \
65                 pcre_free ((regex).extra); \
66                 free ((void *)(regex).replacement); \
67                 C_PCRE_INIT (regex); \
68         } while (0)
69
70 typedef struct {
71         c_pcre_t host;
72         c_pcre_t plugin;
73         c_pcre_t plugin_instance;
74         c_pcre_t type;
75         c_pcre_t type_instance;
76
77         int action;
78 } regex_t;
79
80 typedef struct {
81         int vec[30];
82         int status;
83 } ovec_t;
84
85 typedef struct {
86         ovec_t host;
87         ovec_t plugin;
88         ovec_t plugin_instance;
89         ovec_t type;
90         ovec_t type_instance;
91 } ovectors_t;
92
93 /*
94  * private variables
95  */
96
97 static regex_t *regexes     = NULL;
98 static int      regexes_num = 0;
99
100 /*
101  * internal helper functions
102  */
103
104 /* returns true if string matches the regular expression */
105 static int c_pcre_match (c_pcre_t *re, const char *string, ovec_t *ovec)
106 {
107         if ((NULL == re) || (NULL == re->re))
108                 return 1;
109
110         if (NULL == string)
111                 string = "";
112
113         ovec->status = pcre_exec (re->re,
114                         /* extra       = */ re->extra,
115                         /* subject     = */ string,
116                         /* length      = */ strlen (string),
117                         /* startoffset = */ 0,
118                         /* options     = */ 0,
119                         /* ovector     = */ ovec->vec,
120                         /* ovecsize    = */ STATIC_ARRAY_SIZE (ovec->vec));
121
122         if (0 <= ovec->status)
123                 return 1;
124
125         if (PCRE_ERROR_NOMATCH != ovec->status)
126                 log_err ("PCRE matching of string \"%s\" failed with status %d",
127                                 string, ovec->status);
128         return 0;
129 } /* c_pcre_match */
130
131 static int c_pcre_subst (c_pcre_t *re, char *string, size_t strlen,
132                 ovec_t *ovec)
133 {
134         char buffer[strlen];
135
136         if ((NULL == re) || (NULL == re->replacement))
137                 return 0;
138
139         assert (0 <= ovec->status);
140
141         if (NULL == subst (buffer, sizeof (buffer), string,
142                                 ovec->vec[0], ovec->vec[1], re->replacement)) {
143                 log_err ("Substitution in string \"%s\" (using regex \"%s\" and "
144                                 "replacement string \"%s\") failed.",
145                                 string, re->re_str, re->replacement);
146                 return -1;
147         }
148
149         sstrncpy (string, buffer, strlen);
150         return 0;
151 } /* c_pcre_subst */
152
153 static regex_t *regex_new (void)
154 {
155         regex_t *re;
156         regex_t *temp;
157
158         temp = (regex_t *) realloc (regexes, (regexes_num + 1)
159                         * sizeof (*regexes));
160         if (NULL == temp) {
161                 log_err ("Out of memory.");
162                 return NULL;
163         }
164         regexes = temp;
165         regexes_num++;
166
167         re = regexes + (regexes_num - 1);
168
169         C_PCRE_INIT (re->host);
170         C_PCRE_INIT (re->plugin);
171         C_PCRE_INIT (re->plugin_instance);
172         C_PCRE_INIT (re->type);
173         C_PCRE_INIT (re->type_instance);
174
175         re->action = 0;
176         return re;
177 } /* regex_new */
178
179 static void regex_delete (regex_t *re)
180 {
181         if (NULL == re)
182                 return;
183
184         C_PCRE_FREE (re->host);
185         C_PCRE_FREE (re->plugin);
186         C_PCRE_FREE (re->plugin_instance);
187         C_PCRE_FREE (re->type);
188         C_PCRE_FREE (re->type_instance);
189
190         re->action = 0;
191 } /* regex_delete */
192
193 /* returns true if the value list matches the regular expression */
194 static int regex_match (regex_t *re, value_list_t *vl, ovectors_t *ovectors)
195 {
196         int matches = 0;
197
198         if (NULL == re)
199                 return 1;
200
201         if (c_pcre_match (&re->host, vl->host, &ovectors->host))
202                 ++matches;
203
204         if (c_pcre_match (&re->plugin, vl->plugin, &ovectors->plugin))
205                 ++matches;
206
207         if (c_pcre_match (&re->plugin_instance, vl->plugin_instance,
208                                 &ovectors->plugin_instance))
209                 ++matches;
210
211         if (c_pcre_match (&re->type, vl->type, &ovectors->type))
212                 ++matches;
213
214         if (c_pcre_match (&re->type_instance, vl->type_instance,
215                                 &ovectors->type_instance))
216                 ++matches;
217
218         if (5 == matches)
219                 return 1;
220         return 0;
221 } /* regex_match */
222
223 static int regex_subst (regex_t *re, value_list_t *vl, ovectors_t *ovectors)
224 {
225         if (NULL == re)
226                 return 0;
227
228         c_pcre_subst (&re->host, vl->host, sizeof (vl->host),
229                         &ovectors->host);
230         c_pcre_subst (&re->plugin, vl->plugin, sizeof (vl->plugin),
231                         &ovectors->plugin);
232         c_pcre_subst (&re->plugin_instance, vl->plugin_instance,
233                         sizeof (vl->plugin_instance), &ovectors->plugin_instance);
234         c_pcre_subst (&re->type, vl->type, sizeof (vl->type),
235                         &ovectors->type);
236         c_pcre_subst (&re->type_instance, vl->type_instance,
237                         sizeof (vl->type_instance), &ovectors->type_instance);
238         return 0;
239 } /* regex_subst */
240
241 /*
242  * interface to collectd
243  */
244
245 static int c_pcre_filter (const data_set_t *ds, value_list_t *vl)
246 {
247         int i;
248
249         ovectors_t ovectors;
250
251         for (i = 0; i < regexes_num; ++i)
252                 if (regex_match (regexes + i, vl, &ovectors)) {
253                         regex_subst (regexes + i, vl, &ovectors);
254                         return regexes[i].action;
255                 }
256         return 0;
257 } /* c_pcre_filter */
258
259 static int c_pcre_shutdown (void)
260 {
261         int i;
262
263         plugin_unregister_filter ("filter_pcre");
264         plugin_unregister_shutdown ("filter_pcre");
265
266         for (i = 0; i < regexes_num; ++i)
267                 regex_delete (regexes + i);
268
269         sfree (regexes);
270         regexes_num = 0;
271         return 0;
272 } /* c_pcre_shutdown */
273
274 static int config_set_regex (c_pcre_t *re, oconfig_item_t *ci)
275 {
276         const char *pattern;
277         const char *errptr;
278         int erroffset;
279
280         if ((0 != ci->children_num) || (1 != ci->values_num)
281                         || (OCONFIG_TYPE_STRING != ci->values[0].type)) {
282                 log_err ("<RegEx>: %s expects a single string argument.", ci->key);
283                 return 1;
284         }
285
286         pattern = ci->values[0].value.string;
287
288         re->re = pcre_compile (pattern,
289                         /* options   = */ 0,
290                         /* errptr    = */ &errptr,
291                         /* erroffset = */ &erroffset,
292                         /* tableptr  = */ NULL);
293
294         if (NULL == re->re) {
295                 log_err ("<RegEx>: PCRE compilation of pattern \"%s\" failed "
296                                 "at offset %d: %s", pattern, erroffset, errptr);
297                 return 1;
298         }
299
300         re->re_str = sstrdup (pattern);
301
302         re->extra = pcre_study (re->re,
303                         /* options = */ 0,
304                         /* errptr  = */ &errptr);
305
306         if (NULL != errptr) {
307                 log_err ("<RegEx>: PCRE studying of pattern \"%s\" failed: %s",
308                                 pattern, errptr);
309                 return 1;
310         }
311         return 0;
312 } /* config_set_regex */
313
314 static int config_set_replacement (c_pcre_t *re, oconfig_item_t *ci)
315 {
316         if ((0 != ci->children_num) || (1 != ci->values_num)
317                         || (OCONFIG_TYPE_STRING != ci->values[0].type)) {
318                 log_err ("<RegEx>: %s expects a single string argument.", ci->key);
319                 return 1;
320         }
321
322         if (NULL == re->re) {
323                 log_err ("<RegEx>: %s without an appropriate regex (%s) "
324                                 "is not allowed.", ci->key, ci->key + strlen ("Substitute"));
325                 return 1;
326         }
327
328         re->replacement = sstrdup (ci->values[0].value.string);
329         return 0;
330 } /* config_set_replacement */
331
332 static int config_set_action (int *action, oconfig_item_t *ci)
333 {
334         const char *action_str;
335
336         if ((0 != ci->children_num) || (1 != ci->values_num)
337                         || (OCONFIG_TYPE_STRING != ci->values[0].type)) {
338                 log_err ("<RegEx>: Action expects a single string argument.");
339                 return 1;
340         }
341
342         action_str = ci->values[0].value.string;
343
344         if (0 == strcasecmp (action_str, "NoWrite"))
345                 *action |= FILTER_NOWRITE;
346         else if (0 == strcasecmp (action_str, "NoThresholdCheck"))
347                 *action |= FILTER_NOTHRESHOLD_CHECK;
348         else if (0 == strcasecmp (action_str, "Ignore"))
349                 *action |= FILTER_IGNORE;
350         else
351                 log_warn ("<Regex>: Ignoring unknown action \"%s\".", action_str);
352         return 0;
353 } /* config_set_action */
354
355 static int c_pcre_config_regex (oconfig_item_t *ci)
356 {
357         regex_t *re;
358         int i;
359
360         if (0 != ci->values_num) {
361                 log_err ("<RegEx> expects no arguments.");
362                 return 1;
363         }
364
365         re = regex_new ();
366         if (NULL == re)
367                 return -1;
368
369         for (i = 0; i < ci->children_num; ++i) {
370                 oconfig_item_t *c = ci->children + i;
371                 int status = 0;
372
373                 if (0 == strcasecmp (c->key, "Host"))
374                         status = config_set_regex (&re->host, c);
375                 else if (0 == strcasecmp (c->key, "Plugin"))
376                         status = config_set_regex (&re->plugin, c);
377                 else if (0 == strcasecmp (c->key, "PluginInstance"))
378                         status = config_set_regex (&re->plugin_instance, c);
379                 else if (0 == strcasecmp (c->key, "Type"))
380                         status = config_set_regex (&re->type, c);
381                 else if (0 == strcasecmp (c->key, "TypeInstance"))
382                         status = config_set_regex (&re->type_instance, c);
383                 else if (0 == strcasecmp (c->key, "Action"))
384                         status = config_set_action (&re->action, c);
385                 else if (0 == strcasecmp (c->key, "SubstituteHost"))
386                         status = config_set_replacement (&re->host, c);
387                 else if (0 == strcasecmp (c->key, "SubstitutePlugin"))
388                         status = config_set_replacement (&re->plugin, c);
389                 else if (0 == strcasecmp (c->key, "SubstitutePluginInstance"))
390                         status = config_set_replacement (&re->plugin_instance, c);
391                 else if (0 == strcasecmp (c->key, "SubstituteType"))
392                         status = config_set_replacement (&re->type, c);
393                 else if (0 == strcasecmp (c->key, "SubstituteTypeInstance"))
394                         status = config_set_replacement (&re->type_instance, c);
395                 else
396                         log_warn ("<RegEx>: Ignoring unknown config key \"%s\".", c->key);
397
398                 if (0 != status) {
399                         log_err ("Ignoring regular expression definition.");
400                         regex_delete (re);
401                         --regexes_num;
402                 }
403         }
404         return 0;
405 } /* c_pcre_config_regex */
406
407 static int c_pcre_config (oconfig_item_t *ci)
408 {
409         int i;
410
411         for (i = 0; i < ci->children_num; ++i) {
412                 oconfig_item_t *c = ci->children + i;
413
414                 if (0 == strcasecmp (c->key, "RegEx"))
415                         c_pcre_config_regex (c);
416                 else
417                         log_warn ("Ignoring unknown config key \"%s\".", c->key);
418         }
419
420         plugin_register_filter ("filter_pcre", c_pcre_filter);
421         plugin_register_shutdown ("filter_pcre", c_pcre_shutdown);
422         return 0;
423 } /* c_pcre_config */
424
425 void module_register (void)
426 {
427         plugin_register_complex_config ("filter_pcre", c_pcre_config);
428 } /* module_register */
429
430 /* vim: set sw=4 ts=4 tw=78 noexpandtab : */
431