1 /**
2 * collectd - src/utils_vl_lookup.c
3 * Copyright (C) 2012 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 **/
27 #include "collectd.h"
29 #include <regex.h>
31 #include "common.h"
32 #include "utils_vl_lookup.h"
33 #include "utils_avltree.h"
35 #if BUILD_TEST
36 # define sstrncpy strncpy
37 # define plugin_log(s, ...) do { \
38 printf ("[severity %i] ", s); \
39 printf (__VA_ARGS__); \
40 printf ("\n"); \
41 } while (0)
42 #endif
44 /*
45 * Types
46 */
47 struct part_match_s
48 {
49 char str[DATA_MAX_NAME_LEN];
50 regex_t regex;
51 _Bool is_regex;
52 };
53 typedef struct part_match_s part_match_t;
55 struct identifier_match_s
56 {
57 part_match_t host;
58 part_match_t plugin;
59 part_match_t plugin_instance;
60 part_match_t type;
61 part_match_t type_instance;
63 unsigned int group_by;
64 };
65 typedef struct identifier_match_s identifier_match_t;
67 struct lookup_s
68 {
69 c_avl_tree_t *by_type_tree;
71 lookup_class_callback_t cb_user_class;
72 lookup_obj_callback_t cb_user_obj;
73 lookup_free_class_callback_t cb_free_class;
74 lookup_free_obj_callback_t cb_free_obj;
75 };
77 struct user_obj_s;
78 typedef struct user_obj_s user_obj_t;
79 struct user_obj_s
80 {
81 void *user_obj;
82 identifier_t ident;
84 user_obj_t *next;
85 };
87 struct user_class_s
88 {
89 void *user_class;
90 identifier_match_t match;
91 user_obj_t *user_obj_list; /* list of user_obj */
92 };
93 typedef struct user_class_s user_class_t;
95 struct user_class_list_s;
96 typedef struct user_class_list_s user_class_list_t;
97 struct user_class_list_s
98 {
99 user_class_t entry;
100 user_class_list_t *next;
101 };
103 struct by_type_entry_s
104 {
105 c_avl_tree_t *by_plugin_tree; /* plugin -> user_class_list_t */
106 user_class_list_t *wildcard_plugin_list;
107 };
108 typedef struct by_type_entry_s by_type_entry_t;
110 /*
111 * Private functions
112 */
113 static _Bool lu_part_matches (part_match_t const *match, /* {{{ */
114 char const *str)
115 {
116 if (match->is_regex)
117 {
118 /* Short cut popular catch-all regex. */
119 if (strcmp (".*", match->str) == 0)
120 return (1);
122 int status = regexec (&match->regex, str,
123 /* nmatch = */ 0, /* pmatch = */ NULL,
124 /* flags = */ 0);
125 if (status == 0)
126 return (1);
127 else
128 return (0);
129 }
130 else if (strcmp (match->str, str) == 0)
131 return (1);
132 else
133 return (0);
134 } /* }}} _Bool lu_part_matches */
136 static int lu_copy_ident_to_match_part (part_match_t *match_part, /* {{{ */
137 char const *ident_part)
138 {
139 size_t len = strlen (ident_part);
140 int status;
142 if ((len < 3) || (ident_part[0] != '/') || (ident_part[len - 1] != '/'))
143 {
144 sstrncpy (match_part->str, ident_part, sizeof (match_part->str));
145 match_part->is_regex = 0;
146 return (0);
147 }
149 /* Copy string without the leading slash. */
150 sstrncpy (match_part->str, ident_part + 1, sizeof (match_part->str));
151 assert (sizeof (match_part->str) > len);
152 /* strip trailing slash */
153 match_part->str[len - 2] = 0;
155 status = regcomp (&match_part->regex, match_part->str,
156 /* flags = */ REG_EXTENDED);
157 if (status != 0)
158 {
159 char errbuf[1024];
160 regerror (status, &match_part->regex, errbuf, sizeof (errbuf));
161 ERROR ("utils_vl_lookup: Compiling regular expression \"%s\" failed: %s",
162 match_part->str, errbuf);
163 return (EINVAL);
164 }
165 match_part->is_regex = 1;
167 return (0);
168 } /* }}} int lu_copy_ident_to_match_part */
170 static int lu_copy_ident_to_match (identifier_match_t *match, /* {{{ */
171 identifier_t const *ident, unsigned int group_by)
172 {
173 memset (match, 0, sizeof (*match));
175 match->group_by = group_by;
177 #define COPY_FIELD(field) do { \
178 int status = lu_copy_ident_to_match_part (&match->field, ident->field); \
179 if (status != 0) \
180 return (status); \
181 } while (0)
183 COPY_FIELD (host);
184 COPY_FIELD (plugin);
185 COPY_FIELD (plugin_instance);
186 COPY_FIELD (type);
187 COPY_FIELD (type_instance);
189 #undef COPY_FIELD
191 return (0);
192 } /* }}} int lu_copy_ident_to_match */
194 static void *lu_create_user_obj (lookup_t *obj, /* {{{ */
195 data_set_t const *ds, value_list_t const *vl,
196 user_class_t *user_class)
197 {
198 user_obj_t *user_obj;
200 user_obj = malloc (sizeof (*user_obj));
201 if (user_obj == NULL)
202 {
203 ERROR ("utils_vl_lookup: malloc failed.");
204 return (NULL);
205 }
206 memset (user_obj, 0, sizeof (*user_obj));
207 user_obj->next = NULL;
209 user_obj->user_obj = obj->cb_user_class (ds, vl, user_class->user_class);
210 if (user_obj->user_obj == NULL)
211 {
212 sfree (user_obj);
213 WARNING("utils_vl_lookup: User-provided constructor failed.");
214 return (NULL);
215 }
217 #define COPY_FIELD(field, group_mask) do { \
218 if (user_class->match.field.is_regex \
219 && ((user_class->match.group_by & group_mask) == 0)) \
220 sstrncpy (user_obj->ident.field, "/.*/", sizeof (user_obj->ident.field)); \
221 else \
222 sstrncpy (user_obj->ident.field, vl->field, sizeof (user_obj->ident.field)); \
223 } while (0)
225 COPY_FIELD (host, LU_GROUP_BY_HOST);
226 COPY_FIELD (plugin, LU_GROUP_BY_PLUGIN);
227 COPY_FIELD (plugin_instance, LU_GROUP_BY_PLUGIN_INSTANCE);
228 COPY_FIELD (type, 0);
229 COPY_FIELD (type_instance, LU_GROUP_BY_TYPE_INSTANCE);
231 #undef COPY_FIELD
233 if (user_class->user_obj_list == NULL)
234 {
235 user_class->user_obj_list = user_obj;
236 }
237 else
238 {
239 user_obj_t *last = user_class->user_obj_list;
240 while (last->next != NULL)
241 last = last->next;
242 last->next = user_obj;
243 }
245 return (user_obj);
246 } /* }}} void *lu_create_user_obj */
248 static user_obj_t *lu_find_user_obj (user_class_t *user_class, /* {{{ */
249 value_list_t const *vl)
250 {
251 user_obj_t *ptr;
253 for (ptr = user_class->user_obj_list;
254 ptr != NULL;
255 ptr = ptr->next)
256 {
257 if (user_class->match.host.is_regex
258 && (user_class->match.group_by & LU_GROUP_BY_HOST)
259 && (strcmp (vl->host, ptr->ident.host) != 0))
260 continue;
261 if (user_class->match.plugin.is_regex
262 && (user_class->match.group_by & LU_GROUP_BY_PLUGIN)
263 && (strcmp (vl->plugin, ptr->ident.plugin) != 0))
264 continue;
265 if (user_class->match.plugin_instance.is_regex
266 && (user_class->match.group_by & LU_GROUP_BY_PLUGIN_INSTANCE)
267 && (strcmp (vl->plugin_instance, ptr->ident.plugin_instance) != 0))
268 continue;
269 if (user_class->match.type_instance.is_regex
270 && (user_class->match.group_by & LU_GROUP_BY_TYPE_INSTANCE)
271 && (strcmp (vl->type_instance, ptr->ident.type_instance) != 0))
272 continue;
274 return (ptr);
275 }
277 return (NULL);
278 } /* }}} user_obj_t *lu_find_user_obj */
280 static int lu_handle_user_class (lookup_t *obj, /* {{{ */
281 data_set_t const *ds, value_list_t const *vl,
282 user_class_t *user_class)
283 {
284 user_obj_t *user_obj;
285 int status;
287 assert (strcmp (vl->type, user_class->match.type.str) == 0);
288 assert (user_class->match.plugin.is_regex
289 || (strcmp (vl->plugin, user_class->match.plugin.str)) == 0);
291 if (!lu_part_matches (&user_class->match.type_instance, vl->type_instance)
292 || !lu_part_matches (&user_class->match.plugin_instance, vl->plugin_instance)
293 || !lu_part_matches (&user_class->match.plugin, vl->plugin)
294 || !lu_part_matches (&user_class->match.host, vl->host))
295 return (1);
297 user_obj = lu_find_user_obj (user_class, vl);
298 if (user_obj == NULL)
299 {
300 /* call lookup_class_callback_t() and insert into the list of user objects. */
301 user_obj = lu_create_user_obj (obj, ds, vl, user_class);
302 if (user_obj == NULL)
303 return (-1);
304 }
306 status = obj->cb_user_obj (ds, vl,
307 user_class->user_class, user_obj->user_obj);
308 if (status != 0)
309 {
310 ERROR ("utils_vl_lookup: The user object callback failed with status %i.",
311 status);
312 /* Returning a negative value means: abort! */
313 if (status < 0)
314 return (status);
315 else
316 return (1);
317 }
319 return (0);
320 } /* }}} int lu_handle_user_class */
322 static int lu_handle_user_class_list (lookup_t *obj, /* {{{ */
323 data_set_t const *ds, value_list_t const *vl,
324 user_class_list_t *user_class_list)
325 {
326 user_class_list_t *ptr;
327 int retval = 0;
329 for (ptr = user_class_list; ptr != NULL; ptr = ptr->next)
330 {
331 int status;
333 status = lu_handle_user_class (obj, ds, vl, &ptr->entry);
334 if (status < 0)
335 return (status);
336 else if (status == 0)
337 retval++;
338 }
340 return (retval);
341 } /* }}} int lu_handle_user_class_list */
343 static by_type_entry_t *lu_search_by_type (lookup_t *obj, /* {{{ */
344 char const *type, _Bool allocate_if_missing)
345 {
346 by_type_entry_t *by_type;
347 char *type_copy;
348 int status;
350 status = c_avl_get (obj->by_type_tree, type, (void *) &by_type);
351 if (status == 0)
352 return (by_type);
354 if (!allocate_if_missing)
355 return (NULL);
357 type_copy = strdup (type);
358 if (type_copy == NULL)
359 {
360 ERROR ("utils_vl_lookup: strdup failed.");
361 return (NULL);
362 }
364 by_type = malloc (sizeof (*by_type));
365 if (by_type == NULL)
366 {
367 ERROR ("utils_vl_lookup: malloc failed.");
368 sfree (type_copy);
369 return (NULL);
370 }
371 memset (by_type, 0, sizeof (*by_type));
372 by_type->wildcard_plugin_list = NULL;
374 by_type->by_plugin_tree = c_avl_create ((void *) strcmp);
375 if (by_type->by_plugin_tree == NULL)
376 {
377 ERROR ("utils_vl_lookup: c_avl_create failed.");
378 sfree (by_type);
379 sfree (type_copy);
380 return (NULL);
381 }
383 status = c_avl_insert (obj->by_type_tree,
384 /* key = */ type_copy, /* value = */ by_type);
385 assert (status <= 0); /* >0 => entry exists => race condition. */
386 if (status != 0)
387 {
388 ERROR ("utils_vl_lookup: c_avl_insert failed.");
389 c_avl_destroy (by_type->by_plugin_tree);
390 sfree (by_type);
391 sfree (type_copy);
392 return (NULL);
393 }
395 return (by_type);
396 } /* }}} by_type_entry_t *lu_search_by_type */
398 static int lu_add_by_plugin (by_type_entry_t *by_type, /* {{{ */
399 user_class_list_t *user_class_list)
400 {
401 user_class_list_t *ptr = NULL;
402 identifier_match_t const *match = &user_class_list->entry.match;
404 /* Lookup user_class_list from the per-plugin structure. If this is the first
405 * user_class to be added, the blocks return immediately. Otherwise they will
406 * set "ptr" to non-NULL. */
407 if (match->plugin.is_regex)
408 {
409 if (by_type->wildcard_plugin_list == NULL)
410 {
411 by_type->wildcard_plugin_list = user_class_list;
412 return (0);
413 }
415 ptr = by_type->wildcard_plugin_list;
416 } /* if (plugin is wildcard) */
417 else /* (plugin is not wildcard) */
418 {
419 int status;
421 status = c_avl_get (by_type->by_plugin_tree,
422 match->plugin.str, (void *) &ptr);
424 if (status != 0) /* plugin not yet in tree */
425 {
426 char *plugin_copy = strdup (match->plugin.str);
428 if (plugin_copy == NULL)
429 {
430 ERROR ("utils_vl_lookup: strdup failed.");
431 sfree (user_class_list);
432 return (ENOMEM);
433 }
435 status = c_avl_insert (by_type->by_plugin_tree,
436 plugin_copy, user_class_list);
437 if (status != 0)
438 {
439 ERROR ("utils_vl_lookup: c_avl_insert(\"%s\") failed with status %i.",
440 plugin_copy, status);
441 sfree (plugin_copy);
442 sfree (user_class_list);
443 return (status);
444 }
445 else
446 {
447 return (0);
448 }
449 } /* if (plugin not yet in tree) */
450 } /* if (plugin is not wildcard) */
452 assert (ptr != NULL);
454 while (ptr->next != NULL)
455 ptr = ptr->next;
456 ptr->next = user_class_list;
458 return (0);
459 } /* }}} int lu_add_by_plugin */
461 static void lu_destroy_user_obj (lookup_t *obj, /* {{{ */
462 user_obj_t *user_obj)
463 {
464 while (user_obj != NULL)
465 {
466 user_obj_t *next = user_obj->next;
468 if (obj->cb_free_obj != NULL)
469 obj->cb_free_obj (user_obj->user_obj);
470 user_obj->user_obj = NULL;
472 sfree (user_obj);
473 user_obj = next;
474 }
475 } /* }}} void lu_destroy_user_obj */
477 static void lu_destroy_user_class_list (lookup_t *obj, /* {{{ */
478 user_class_list_t *user_class_list)
479 {
480 while (user_class_list != NULL)
481 {
482 user_class_list_t *next = user_class_list->next;
484 if (obj->cb_free_class != NULL)
485 obj->cb_free_class (user_class_list->entry.user_class);
486 user_class_list->entry.user_class = NULL;
488 lu_destroy_user_obj (obj, user_class_list->entry.user_obj_list);
489 user_class_list->entry.user_obj_list = NULL;
491 sfree (user_class_list);
492 user_class_list = next;
493 }
494 } /* }}} void lu_destroy_user_class_list */
496 static void lu_destroy_by_type (lookup_t *obj, /* {{{ */
497 by_type_entry_t *by_type)
498 {
500 while (42)
501 {
502 char *plugin = NULL;
503 user_class_list_t *user_class_list = NULL;
504 int status;
506 status = c_avl_pick (by_type->by_plugin_tree,
507 (void *) &plugin, (void *) &user_class_list);
508 if (status != 0)
509 break;
511 DEBUG ("utils_vl_lookup: lu_destroy_by_type: Destroying plugin \"%s\".",
512 plugin);
513 sfree (plugin);
514 lu_destroy_user_class_list (obj, user_class_list);
515 }
517 c_avl_destroy (by_type->by_plugin_tree);
518 by_type->by_plugin_tree = NULL;
520 lu_destroy_user_class_list (obj, by_type->wildcard_plugin_list);
521 by_type->wildcard_plugin_list = NULL;
523 sfree (by_type);
524 } /* }}} int lu_destroy_by_type */
526 /*
527 * Public functions
528 */
529 lookup_t *lookup_create (lookup_class_callback_t cb_user_class, /* {{{ */
530 lookup_obj_callback_t cb_user_obj,
531 lookup_free_class_callback_t cb_free_class,
532 lookup_free_obj_callback_t cb_free_obj)
533 {
534 lookup_t *obj = malloc (sizeof (*obj));
535 if (obj == NULL)
536 {
537 ERROR ("utils_vl_lookup: malloc failed.");
538 return (NULL);
539 }
540 memset (obj, 0, sizeof (*obj));
542 obj->by_type_tree = c_avl_create ((void *) strcmp);
543 if (obj->by_type_tree == NULL)
544 {
545 ERROR ("utils_vl_lookup: c_avl_create failed.");
546 sfree (obj);
547 return (NULL);
548 }
550 obj->cb_user_class = cb_user_class;
551 obj->cb_user_obj = cb_user_obj;
552 obj->cb_free_class = cb_free_class;
553 obj->cb_free_obj = cb_free_obj;
555 return (obj);
556 } /* }}} lookup_t *lookup_create */
558 void lookup_destroy (lookup_t *obj) /* {{{ */
559 {
560 int status;
562 if (obj == NULL)
563 return;
565 while (42)
566 {
567 char *type = NULL;
568 by_type_entry_t *by_type = NULL;
570 status = c_avl_pick (obj->by_type_tree, (void *) &type, (void *) &by_type);
571 if (status != 0)
572 break;
574 DEBUG ("utils_vl_lookup: lookup_destroy: Destroying type \"%s\".", type);
575 sfree (type);
576 lu_destroy_by_type (obj, by_type);
577 }
579 c_avl_destroy (obj->by_type_tree);
580 obj->by_type_tree = NULL;
582 sfree (obj);
583 } /* }}} void lookup_destroy */
585 int lookup_add (lookup_t *obj, /* {{{ */
586 identifier_t const *ident, unsigned int group_by, void *user_class)
587 {
588 by_type_entry_t *by_type = NULL;
589 user_class_list_t *user_class_obj;
591 by_type = lu_search_by_type (obj, ident->type, /* allocate = */ 1);
592 if (by_type == NULL)
593 return (-1);
595 user_class_obj = malloc (sizeof (*user_class_obj));
596 if (user_class_obj == NULL)
597 {
598 ERROR ("utils_vl_lookup: malloc failed.");
599 return (ENOMEM);
600 }
601 memset (user_class_obj, 0, sizeof (*user_class_obj));
602 user_class_obj->entry.user_class = user_class;
603 lu_copy_ident_to_match (&user_class_obj->entry.match, ident, group_by);
604 user_class_obj->entry.user_obj_list = NULL;
605 user_class_obj->next = NULL;
607 return (lu_add_by_plugin (by_type, user_class_obj));
608 } /* }}} int lookup_add */
610 /* returns the number of successful calls to the callback function */
611 int lookup_search (lookup_t *obj, /* {{{ */
612 data_set_t const *ds, value_list_t const *vl)
613 {
614 by_type_entry_t *by_type = NULL;
615 user_class_list_t *user_class_list = NULL;
616 int retval = 0;
617 int status;
619 if ((obj == NULL) || (ds == NULL) || (vl == NULL))
620 return (-EINVAL);
622 by_type = lu_search_by_type (obj, vl->type, /* allocate = */ 0);
623 if (by_type == NULL)
624 return (0);
626 status = c_avl_get (by_type->by_plugin_tree,
627 vl->plugin, (void *) &user_class_list);
628 if (status == 0)
629 {
630 status = lu_handle_user_class_list (obj, ds, vl, user_class_list);
631 if (status < 0)
632 return (status);
633 retval += status;
634 }
636 if (by_type->wildcard_plugin_list != NULL)
637 {
638 status = lu_handle_user_class_list (obj, ds, vl,
639 by_type->wildcard_plugin_list);
640 if (status < 0)
641 return (status);
642 retval += status;
643 }
645 return (retval);
646 } /* }}} lookup_search */