Merge branch 'collectd-5.5' into 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 "filter_chain.h"
37
38 #include <regex.h>
39 #include <sys/types.h>
40
41 #define log_err(...) ERROR("`regex' match: " __VA_ARGS__)
42 #define log_warn(...) WARNING("`regex' match: " __VA_ARGS__)
43
44 /*
45  * private data types
46  */
47
48 struct mr_regex_s;
49 typedef struct mr_regex_s mr_regex_t;
50 struct mr_regex_s {
51   regex_t re;
52   char *re_str;
53
54   mr_regex_t *next;
55 };
56
57 struct mr_match_s;
58 typedef struct mr_match_s mr_match_t;
59 struct mr_match_s {
60   mr_regex_t *host;
61   mr_regex_t *plugin;
62   mr_regex_t *plugin_instance;
63   mr_regex_t *type;
64   mr_regex_t *type_instance;
65   _Bool invert;
66 };
67
68 /*
69  * internal helper functions
70  */
71 static void mr_free_regex(mr_regex_t *r) /* {{{ */
72 {
73   if (r == NULL)
74     return;
75
76   regfree(&r->re);
77   memset(&r->re, 0, sizeof(r->re));
78   free(r->re_str);
79
80   if (r->next != NULL)
81     mr_free_regex(r->next);
82 } /* }}} void mr_free_regex */
83
84 static void mr_free_match(mr_match_t *m) /* {{{ */
85 {
86   if (m == NULL)
87     return;
88
89   mr_free_regex(m->host);
90   mr_free_regex(m->plugin);
91   mr_free_regex(m->plugin_instance);
92   mr_free_regex(m->type);
93   mr_free_regex(m->type_instance);
94
95   free(m);
96 } /* }}} void mr_free_match */
97
98 static int mr_match_regexen(mr_regex_t *re_head, /* {{{ */
99                             const char *string) {
100   if (re_head == NULL)
101     return (FC_MATCH_MATCHES);
102
103   for (mr_regex_t *re = re_head; re != NULL; re = re->next) {
104     int status;
105
106     status = regexec(&re->re, string,
107                      /* nmatch = */ 0, /* pmatch = */ NULL,
108                      /* eflags = */ 0);
109     if (status == 0) {
110       DEBUG("regex match: Regular expression `%s' matches `%s'.", re->re_str,
111             string);
112     } else {
113       DEBUG("regex match: Regular expression `%s' does not match `%s'.",
114             re->re_str, string);
115       return (FC_MATCH_NO_MATCH);
116     }
117   }
118
119   return (FC_MATCH_MATCHES);
120 } /* }}} int mr_match_regexen */
121
122 static int mr_config_add_regex(mr_regex_t **re_head, /* {{{ */
123                                oconfig_item_t *ci) {
124   mr_regex_t *re;
125   int status;
126
127   if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_STRING)) {
128     log_warn("`%s' needs exactly one string argument.", ci->key);
129     return (-1);
130   }
131
132   re = calloc(1, sizeof(*re));
133   if (re == NULL) {
134     log_err("mr_config_add_regex: calloc failed.");
135     return (-1);
136   }
137   re->next = NULL;
138
139   re->re_str = strdup(ci->values[0].value.string);
140   if (re->re_str == NULL) {
141     free(re);
142     log_err("mr_config_add_regex: strdup failed.");
143     return (-1);
144   }
145
146   status = regcomp(&re->re, re->re_str, REG_EXTENDED | REG_NOSUB);
147   if (status != 0) {
148     char errmsg[1024];
149     regerror(status, &re->re, errmsg, sizeof(errmsg));
150     errmsg[sizeof(errmsg) - 1] = 0;
151     log_err("Compiling regex `%s' for `%s' failed: %s.", re->re_str, ci->key,
152             errmsg);
153     free(re->re_str);
154     free(re);
155     return (-1);
156   }
157
158   if (*re_head == NULL) {
159     *re_head = re;
160   } else {
161     mr_regex_t *ptr;
162
163     ptr = *re_head;
164     while (ptr->next != NULL)
165       ptr = ptr->next;
166
167     ptr->next = re;
168   }
169
170   return (0);
171 } /* }}} int mr_config_add_regex */
172
173 static int mr_create(const oconfig_item_t *ci, void **user_data) /* {{{ */
174 {
175   mr_match_t *m;
176   int status;
177
178   m = calloc(1, sizeof(*m));
179   if (m == NULL) {
180     log_err("mr_create: calloc failed.");
181     return (-ENOMEM);
182   }
183
184   m->invert = 0;
185
186   status = 0;
187   for (int i = 0; i < ci->children_num; i++) {
188     oconfig_item_t *child = ci->children + i;
189
190     if ((strcasecmp("Host", child->key) == 0) ||
191         (strcasecmp("Hostname", child->key) == 0))
192       status = mr_config_add_regex(&m->host, child);
193     else if (strcasecmp("Plugin", child->key) == 0)
194       status = mr_config_add_regex(&m->plugin, child);
195     else if (strcasecmp("PluginInstance", child->key) == 0)
196       status = mr_config_add_regex(&m->plugin_instance, child);
197     else if (strcasecmp("Type", child->key) == 0)
198       status = mr_config_add_regex(&m->type, child);
199     else if (strcasecmp("TypeInstance", child->key) == 0)
200       status = mr_config_add_regex(&m->type_instance, child);
201     else if (strcasecmp("Invert", child->key) == 0)
202       status = cf_util_get_boolean(child, &m->invert);
203     else {
204       log_err("The `%s' configuration option is not understood and "
205               "will be ignored.",
206               child->key);
207       status = 0;
208     }
209
210     if (status != 0)
211       break;
212   }
213
214   /* Additional sanity-checking */
215   while (status == 0) {
216     if ((m->host == NULL) && (m->plugin == NULL) &&
217         (m->plugin_instance == NULL) && (m->type == NULL) &&
218         (m->type_instance == NULL)) {
219       log_err("No (valid) regular expressions have been configured. "
220               "This match will be ignored.");
221       status = -1;
222     }
223
224     break;
225   }
226
227   if (status != 0) {
228     mr_free_match(m);
229     return (status);
230   }
231
232   *user_data = m;
233   return (0);
234 } /* }}} int mr_create */
235
236 static int mr_destroy(void **user_data) /* {{{ */
237 {
238   if ((user_data != NULL) && (*user_data != NULL))
239     mr_free_match(*user_data);
240   return (0);
241 } /* }}} int mr_destroy */
242
243 static int mr_match(const data_set_t __attribute__((unused)) * ds, /* {{{ */
244                     const value_list_t *vl,
245                     notification_meta_t __attribute__((unused)) * *meta,
246                     void **user_data) {
247   mr_match_t *m;
248   int match_value = FC_MATCH_MATCHES;
249   int nomatch_value = FC_MATCH_NO_MATCH;
250
251   if ((user_data == NULL) || (*user_data == NULL))
252     return (-1);
253
254   m = *user_data;
255
256   if (m->invert) {
257     match_value = FC_MATCH_NO_MATCH;
258     nomatch_value = FC_MATCH_MATCHES;
259   }
260
261   if (mr_match_regexen(m->host, vl->host) == FC_MATCH_NO_MATCH)
262     return (nomatch_value);
263   if (mr_match_regexen(m->plugin, vl->plugin) == FC_MATCH_NO_MATCH)
264     return (nomatch_value);
265   if (mr_match_regexen(m->plugin_instance, vl->plugin_instance) ==
266       FC_MATCH_NO_MATCH)
267     return (nomatch_value);
268   if (mr_match_regexen(m->type, vl->type) == FC_MATCH_NO_MATCH)
269     return (nomatch_value);
270   if (mr_match_regexen(m->type_instance, vl->type_instance) ==
271       FC_MATCH_NO_MATCH)
272     return (nomatch_value);
273
274   return (match_value);
275 } /* }}} int mr_match */
276
277 void module_register(void) {
278   match_proc_t mproc = {0};
279
280   mproc.create = mr_create;
281   mproc.destroy = mr_destroy;
282   mproc.match = mr_match;
283   fc_register_match("regex", mproc);
284 } /* module_register */
285
286 /* vim: set sw=4 ts=4 tw=78 noexpandtab fdm=marker : */