Tree wide: Reformat with clang-format.
[collectd.git] / src / target_replace.c
1 /**
2  * collectd - src/target_replace.c
3  * Copyright (C) 2008       Florian Forster
4  *
5  * Permission is hereby granted, free of charge, to any person obtaining a
6  * copy of this software and associated documentation files (the "Software"),
7  * to deal in the Software without restriction, including without limitation
8  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
9  * and/or sell copies of the Software, and to permit persons to whom the
10  * Software is furnished to do so, subject to the following conditions:
11  *
12  * The above copyright notice and this permission notice shall be included in
13  * all copies or substantial portions of the Software.
14  *
15  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
21  * DEALINGS IN THE SOFTWARE.
22  *
23  * Authors:
24  *   Florian Forster <octo at collectd.org>
25  **/
26
27 #include "collectd.h"
28
29 #include "common.h"
30 #include "filter_chain.h"
31 #include "utils_subst.h"
32
33 #include <regex.h>
34
35 struct tr_action_s;
36 typedef struct tr_action_s tr_action_t;
37 struct tr_action_s {
38   regex_t re;
39   char *replacement;
40   int may_be_empty;
41
42   tr_action_t *next;
43 };
44
45 struct tr_data_s {
46   tr_action_t *host;
47   tr_action_t *plugin;
48   tr_action_t *plugin_instance;
49   /* tr_action_t *type; */
50   tr_action_t *type_instance;
51 };
52 typedef struct tr_data_s tr_data_t;
53
54 static char *tr_strdup(const char *orig) /* {{{ */
55 {
56   size_t sz;
57   char *dest;
58
59   if (orig == NULL)
60     return (NULL);
61
62   sz = strlen(orig) + 1;
63   dest = malloc(sz);
64   if (dest == NULL)
65     return (NULL);
66
67   memcpy(dest, orig, sz);
68
69   return (dest);
70 } /* }}} char *tr_strdup */
71
72 static void tr_action_destroy(tr_action_t *act) /* {{{ */
73 {
74   if (act == NULL)
75     return;
76
77   regfree(&act->re);
78   sfree(act->replacement);
79
80   if (act->next != NULL)
81     tr_action_destroy(act->next);
82
83   sfree(act);
84 } /* }}} void tr_action_destroy */
85
86 static int tr_config_add_action(tr_action_t **dest, /* {{{ */
87                                 const oconfig_item_t *ci, int may_be_empty) {
88   tr_action_t *act;
89   int status;
90
91   if (dest == NULL)
92     return (-EINVAL);
93
94   if ((ci->values_num != 2) || (ci->values[0].type != OCONFIG_TYPE_STRING) ||
95       (ci->values[1].type != OCONFIG_TYPE_STRING)) {
96     ERROR("Target `replace': The `%s' option requires exactly two string "
97           "arguments.",
98           ci->key);
99     return (-1);
100   }
101
102   act = calloc(1, sizeof(*act));
103   if (act == NULL) {
104     ERROR("tr_config_add_action: calloc failed.");
105     return (-ENOMEM);
106   }
107
108   act->replacement = NULL;
109   act->may_be_empty = may_be_empty;
110
111   status = regcomp(&act->re, ci->values[0].value.string, REG_EXTENDED);
112   if (status != 0) {
113     char errbuf[1024] = "";
114
115     /* regerror assures null termination. */
116     regerror(status, &act->re, errbuf, sizeof(errbuf));
117     ERROR("Target `replace': Compiling the regular expression `%s' "
118           "failed: %s.",
119           ci->values[0].value.string, errbuf);
120     sfree(act);
121     return (-EINVAL);
122   }
123
124   act->replacement = tr_strdup(ci->values[1].value.string);
125   if (act->replacement == NULL) {
126     ERROR("tr_config_add_action: tr_strdup failed.");
127     regfree(&act->re);
128     sfree(act);
129     return (-ENOMEM);
130   }
131
132   /* Insert action at end of list. */
133   if (*dest == NULL)
134     *dest = act;
135   else {
136     tr_action_t *prev;
137
138     prev = *dest;
139     while (prev->next != NULL)
140       prev = prev->next;
141
142     prev->next = act;
143   }
144
145   return (0);
146 } /* }}} int tr_config_add_action */
147
148 static int tr_action_invoke(tr_action_t *act_head, /* {{{ */
149                             char *buffer_in, size_t buffer_in_size,
150                             int may_be_empty) {
151   int status;
152   char buffer[DATA_MAX_NAME_LEN];
153   regmatch_t matches[8] = {[0] = {0}};
154
155   if (act_head == NULL)
156     return (-EINVAL);
157
158   sstrncpy(buffer, buffer_in, sizeof(buffer));
159
160   DEBUG("target_replace plugin: tr_action_invoke: <- buffer = %s;", buffer);
161
162   for (tr_action_t *act = act_head; act != NULL; act = act->next) {
163     char temp[DATA_MAX_NAME_LEN];
164     char *subst_status;
165
166     status = regexec(&act->re, buffer, STATIC_ARRAY_SIZE(matches), matches,
167                      /* flags = */ 0);
168     if (status == REG_NOMATCH)
169       continue;
170     else if (status != 0) {
171       char errbuf[1024] = "";
172
173       regerror(status, &act->re, errbuf, sizeof(errbuf));
174       ERROR("Target `replace': Executing a regular expression failed: %s.",
175             errbuf);
176       continue;
177     }
178
179     subst_status = subst(temp, sizeof(temp), buffer, (size_t)matches[0].rm_so,
180                          (size_t)matches[0].rm_eo, act->replacement);
181     if (subst_status == NULL) {
182       ERROR("Target `replace': subst (buffer = %s, start = %zu, end = %zu, "
183             "replacement = %s) failed.",
184             buffer, (size_t)matches[0].rm_so, (size_t)matches[0].rm_eo,
185             act->replacement);
186       continue;
187     }
188     sstrncpy(buffer, temp, sizeof(buffer));
189
190     DEBUG("target_replace plugin: tr_action_invoke: -- buffer = %s;", buffer);
191   } /* for (act = act_head; act != NULL; act = act->next) */
192
193   if ((may_be_empty == 0) && (buffer[0] == 0)) {
194     WARNING("Target `replace': Replacement resulted in an empty string, "
195             "which is not allowed for this buffer (`host' or `plugin').");
196     return (0);
197   }
198
199   DEBUG("target_replace plugin: tr_action_invoke: -> buffer = %s;", buffer);
200   sstrncpy(buffer_in, buffer, buffer_in_size);
201
202   return (0);
203 } /* }}} int tr_action_invoke */
204
205 static int tr_destroy(void **user_data) /* {{{ */
206 {
207   tr_data_t *data;
208
209   if (user_data == NULL)
210     return (-EINVAL);
211
212   data = *user_data;
213   if (data == NULL)
214     return (0);
215
216   tr_action_destroy(data->host);
217   tr_action_destroy(data->plugin);
218   tr_action_destroy(data->plugin_instance);
219   /* tr_action_destroy (data->type); */
220   tr_action_destroy(data->type_instance);
221   sfree(data);
222
223   return (0);
224 } /* }}} int tr_destroy */
225
226 static int tr_create(const oconfig_item_t *ci, void **user_data) /* {{{ */
227 {
228   tr_data_t *data;
229   int status;
230
231   data = calloc(1, sizeof(*data));
232   if (data == NULL) {
233     ERROR("tr_create: calloc failed.");
234     return (-ENOMEM);
235   }
236
237   data->host = NULL;
238   data->plugin = NULL;
239   data->plugin_instance = NULL;
240   /* data->type = NULL; */
241   data->type_instance = NULL;
242
243   status = 0;
244   for (int i = 0; i < ci->children_num; i++) {
245     oconfig_item_t *child = ci->children + i;
246
247     if ((strcasecmp("Host", child->key) == 0) ||
248         (strcasecmp("Hostname", child->key) == 0))
249       status = tr_config_add_action(&data->host, child,
250                                     /* may be empty = */ 0);
251     else if (strcasecmp("Plugin", child->key) == 0)
252       status = tr_config_add_action(&data->plugin, child,
253                                     /* may be empty = */ 0);
254     else if (strcasecmp("PluginInstance", child->key) == 0)
255       status = tr_config_add_action(&data->plugin_instance, child,
256                                     /* may be empty = */ 1);
257 #if 0
258     else if (strcasecmp ("Type", child->key) == 0)
259       status = tr_config_add_action (&data->type, child,
260           /* may be empty = */ 0);
261 #endif
262     else if (strcasecmp("TypeInstance", child->key) == 0)
263       status = tr_config_add_action(&data->type_instance, child,
264                                     /* may be empty = */ 1);
265     else {
266       ERROR("Target `replace': The `%s' configuration option is not understood "
267             "and will be ignored.",
268             child->key);
269       status = 0;
270     }
271
272     if (status != 0)
273       break;
274   }
275
276   /* Additional sanity-checking */
277   while (status == 0) {
278     if ((data->host == NULL) && (data->plugin == NULL) &&
279         (data->plugin_instance == NULL)
280         /* && (data->type == NULL) */
281         && (data->type_instance == NULL)) {
282       ERROR("Target `replace': You need to set at least one of `Host', "
283             "`Plugin', `PluginInstance' or `TypeInstance'.");
284       status = -1;
285     }
286
287     break;
288   }
289
290   if (status != 0) {
291     tr_destroy((void *)&data);
292     return (status);
293   }
294
295   *user_data = data;
296   return (0);
297 } /* }}} int tr_create */
298
299 static int tr_invoke(const data_set_t *ds, value_list_t *vl, /* {{{ */
300                      notification_meta_t __attribute__((unused)) * *meta,
301                      void **user_data) {
302   tr_data_t *data;
303
304   if ((ds == NULL) || (vl == NULL) || (user_data == NULL))
305     return (-EINVAL);
306
307   data = *user_data;
308   if (data == NULL) {
309     ERROR("Target `replace': Invoke: `data' is NULL.");
310     return (-EINVAL);
311   }
312
313 #define HANDLE_FIELD(f, e)                                                     \
314   if (data->f != NULL)                                                         \
315   tr_action_invoke(data->f, vl->f, sizeof(vl->f), e)
316   HANDLE_FIELD(host, 0);
317   HANDLE_FIELD(plugin, 0);
318   HANDLE_FIELD(plugin_instance, 1);
319   /* HANDLE_FIELD (type); */
320   HANDLE_FIELD(type_instance, 1);
321
322   return (FC_TARGET_CONTINUE);
323 } /* }}} int tr_invoke */
324
325 void module_register(void) {
326   target_proc_t tproc = {0};
327
328   tproc.create = tr_create;
329   tproc.destroy = tr_destroy;
330   tproc.invoke = tr_invoke;
331   fc_register_target("replace", tproc);
332 } /* module_register */
333
334 /* vim: set sw=2 sts=2 tw=78 et fdm=marker : */