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 **/
29 /*
30 * This module allows to filter and rewrite value lists based on
31 * Perl-compatible regular expressions.
32 */
34 #include "collectd.h"
36 #include "filter_chain.h"
37 #include "meta_data.h"
38 #include "utils_llist.h"
40 #include <sys/types.h>
41 #include <regex.h>
43 #define log_err(...) ERROR ("`regex' match: " __VA_ARGS__)
44 #define log_warn(...) WARNING ("`regex' match: " __VA_ARGS__)
46 /*
47 * private data types
48 */
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;
57 mr_regex_t *next;
58 };
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 };
73 /*
74 * internal helper functions
75 */
76 static void mr_free_regex (mr_regex_t *r) /* {{{ */
77 {
78 if (r == NULL)
79 return;
81 regfree (&r->re);
82 memset (&r->re, 0, sizeof (r->re));
83 free (r->re_str);
85 if (r->next != NULL)
86 mr_free_regex (r->next);
87 } /* }}} void mr_free_regex */
89 static void mr_free_match (mr_match_t *m) /* {{{ */
90 {
91 if (m == NULL)
92 return;
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);
106 free (m);
107 } /* }}} void mr_free_match */
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);
115 for (mr_regex_t *re = re_head; re != NULL; re = re->next)
116 {
117 int status;
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 }
134 }
136 return (FC_MATCH_MATCHES);
137 } /* }}} int mr_match_regexen */
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;
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;
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 }
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 }
174 if (*re_head == NULL)
175 {
176 *re_head = re;
177 }
178 else
179 {
180 mr_regex_t *ptr;
182 ptr = *re_head;
183 while (ptr->next != NULL)
184 ptr = ptr->next;
186 ptr->next = re;
187 }
189 return (0);
190 } /* }}} int mr_add_regex */
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 }
201 return mr_add_regex (re_head, ci->values[0].value.string, ci->key);
202 } /* }}} int mr_config_add_regex */
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];
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 }
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 }
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 }
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 */
262 static int mr_create (const oconfig_item_t *ci, void **user_data) /* {{{ */
263 {
264 mr_match_t *m;
265 int status;
267 m = calloc (1, sizeof (*m));
268 if (m == NULL)
269 {
270 log_err ("mr_create: calloc failed.");
271 return (-ENOMEM);
272 }
274 m->invert = 0;
276 status = 0;
277 for (int i = 0; i < ci->children_num; i++)
278 {
279 oconfig_item_t *child = ci->children + i;
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 }
303 if (status != 0)
304 break;
305 }
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 }
322 break;
323 }
325 if (status != 0)
326 {
327 mr_free_match (m);
328 return (status);
329 }
331 *user_data = m;
332 return (0);
333 } /* }}} int mr_create */
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 */
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;
351 if ((user_data == NULL) || (*user_data == NULL))
352 return (-1);
354 m = *user_data;
356 if (m->invert)
357 {
358 match_value = FC_MATCH_NO_MATCH;
359 nomatch_value = FC_MATCH_MATCHES;
360 }
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 }
393 return (match_value);
394 } /* }}} int mr_match */
396 void module_register (void)
397 {
398 match_proc_t mproc = { 0 };
400 mproc.create = mr_create;
401 mproc.destroy = mr_destroy;
402 mproc.match = mr_match;
403 fc_register_match ("regex", mproc);
404 } /* module_register */
406 /* vim: set sw=4 ts=4 tw=78 noexpandtab fdm=marker : */