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 **/
22 /*
23 * This module allows to filter and rewrite value lists based on
24 * Perl-compatible regular expressions.
25 */
27 #include "collectd.h"
28 #include "configfile.h"
29 #include "plugin.h"
30 #include "common.h"
32 #include "utils_subst.h"
34 #include <pcre.h>
36 #define log_err(...) ERROR ("filter_pcre: " __VA_ARGS__)
37 #define log_warn(...) WARNING ("filter_pcre: " __VA_ARGS__)
39 /*
40 * private data types
41 */
43 typedef struct {
44 /* regular expression */
45 pcre *re;
46 const char *re_str;
48 /* extra information from studying the pattern */
49 pcre_extra *extra;
51 /* replacment text for string substitution */
52 const char *replacement;
53 } c_pcre_t;
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)
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)
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;
77 int action;
78 } regex_t;
80 typedef struct {
81 int vec[30];
82 int status;
83 } ovec_t;
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;
93 /*
94 * private variables
95 */
97 static regex_t *regexes = NULL;
98 static int regexes_num = 0;
100 /*
101 * internal helper functions
102 */
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;
110 if (NULL == string)
111 string = "";
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));
122 if (0 <= ovec->status)
123 return 1;
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 */
131 static int c_pcre_subst (c_pcre_t *re, char *string, size_t strlen,
132 ovec_t *ovec)
133 {
134 char buffer[strlen];
136 if ((NULL == re) || (NULL == re->replacement))
137 return 0;
139 assert (0 <= ovec->status);
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 }
149 sstrncpy (string, buffer, strlen);
150 return 0;
151 } /* c_pcre_subst */
153 static regex_t *regex_new (void)
154 {
155 regex_t *re;
156 regex_t *temp;
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++;
167 re = regexes + (regexes_num - 1);
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);
175 re->action = 0;
176 return re;
177 } /* regex_new */
179 static void regex_delete (regex_t *re)
180 {
181 if (NULL == re)
182 return;
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);
190 re->action = 0;
191 } /* regex_delete */
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;
198 if (NULL == re)
199 return 1;
201 if (c_pcre_match (&re->host, vl->host, &ovectors->host))
202 ++matches;
204 if (c_pcre_match (&re->plugin, vl->plugin, &ovectors->plugin))
205 ++matches;
207 if (c_pcre_match (&re->plugin_instance, vl->plugin_instance,
208 &ovectors->plugin_instance))
209 ++matches;
211 if (c_pcre_match (&re->type, vl->type, &ovectors->type))
212 ++matches;
214 if (c_pcre_match (&re->type_instance, vl->type_instance,
215 &ovectors->type_instance))
216 ++matches;
218 if (5 == matches)
219 return 1;
220 return 0;
221 } /* regex_match */
223 static int regex_subst (regex_t *re, value_list_t *vl, ovectors_t *ovectors)
224 {
225 if (NULL == re)
226 return 0;
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 */
241 /*
242 * interface to collectd
243 */
245 static int c_pcre_filter (const data_set_t *ds, value_list_t *vl)
246 {
247 int i;
249 ovectors_t ovectors;
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 */
259 static int c_pcre_shutdown (void)
260 {
261 int i;
263 plugin_unregister_filter ("filter_pcre");
264 plugin_unregister_shutdown ("filter_pcre");
266 for (i = 0; i < regexes_num; ++i)
267 regex_delete (regexes + i);
269 sfree (regexes);
270 regexes_num = 0;
271 return 0;
272 } /* c_pcre_shutdown */
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;
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 }
286 pattern = ci->values[0].value.string;
288 re->re = pcre_compile (pattern,
289 /* options = */ 0,
290 /* errptr = */ &errptr,
291 /* erroffset = */ &erroffset,
292 /* tableptr = */ NULL);
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 }
300 re->re_str = sstrdup (pattern);
302 re->extra = pcre_study (re->re,
303 /* options = */ 0,
304 /* errptr = */ &errptr);
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 */
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 }
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 }
328 re->replacement = sstrdup (ci->values[0].value.string);
329 return 0;
330 } /* config_set_replacement */
332 static int config_set_action (int *action, oconfig_item_t *ci)
333 {
334 const char *action_str;
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 }
342 action_str = ci->values[0].value.string;
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 */
355 static int c_pcre_config_regex (oconfig_item_t *ci)
356 {
357 regex_t *re;
358 int i;
360 if (0 != ci->values_num) {
361 log_err ("<RegEx> expects no arguments.");
362 return 1;
363 }
365 re = regex_new ();
366 if (NULL == re)
367 return -1;
369 for (i = 0; i < ci->children_num; ++i) {
370 oconfig_item_t *c = ci->children + i;
371 int status = 0;
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);
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 */
407 static int c_pcre_config (oconfig_item_t *ci)
408 {
409 int i;
411 for (i = 0; i < ci->children_num; ++i) {
412 oconfig_item_t *c = ci->children + i;
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 }
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 */
425 void module_register (void)
426 {
427 plugin_register_complex_config ("filter_pcre", c_pcre_config);
428 } /* module_register */
430 /* vim: set sw=4 ts=4 tw=78 noexpandtab : */