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 <pthread.h>
30 #include <regex.h>
32 #include "common.h"
33 #include "utils_vl_lookup.h"
34 #include "utils_avltree.h"
36 #if BUILD_TEST
37 # define sstrncpy strncpy
38 # define plugin_log(s, ...) do { \
39 printf ("[severity %i] ", s); \
40 printf (__VA_ARGS__); \
41 printf ("\n"); \
42 } while (0)
43 #endif
45 /*
46 * Types
47 */
48 struct part_match_s
49 {
50 char str[DATA_MAX_NAME_LEN];
51 regex_t regex;
52 _Bool is_regex;
53 };
54 typedef struct part_match_s part_match_t;
56 struct identifier_match_s
57 {
58 part_match_t host;
59 part_match_t plugin;
60 part_match_t plugin_instance;
61 part_match_t type;
62 part_match_t type_instance;
64 unsigned int group_by;
65 };
66 typedef struct identifier_match_s identifier_match_t;
68 struct lookup_s
69 {
70 c_avl_tree_t *by_type_tree;
72 lookup_class_callback_t cb_user_class;
73 lookup_obj_callback_t cb_user_obj;
74 lookup_free_class_callback_t cb_free_class;
75 lookup_free_obj_callback_t cb_free_obj;
76 };
78 struct user_obj_s;
79 typedef struct user_obj_s user_obj_t;
80 struct user_obj_s
81 {
82 void *user_obj;
83 identifier_t ident;
85 user_obj_t *next;
86 };
88 struct user_class_s
89 {
90 pthread_mutex_t lock;
91 void *user_class;
92 identifier_match_t match;
93 user_obj_t *user_obj_list; /* list of user_obj */
94 };
95 typedef struct user_class_s user_class_t;
97 struct user_class_list_s;
98 typedef struct user_class_list_s user_class_list_t;
99 struct user_class_list_s
100 {
101 user_class_t entry;
102 user_class_list_t *next;
103 };
105 struct by_type_entry_s
106 {
107 c_avl_tree_t *by_plugin_tree; /* plugin -> user_class_list_t */
108 user_class_list_t *wildcard_plugin_list;
109 };
110 typedef struct by_type_entry_s by_type_entry_t;
112 /*
113 * Private functions
114 */
115 static _Bool lu_part_matches (part_match_t const *match, /* {{{ */
116 char const *str)
117 {
118 if (match->is_regex)
119 {
120 /* Short cut popular catch-all regex. */
121 if (strcmp (".*", match->str) == 0)
122 return (1);
124 int status = regexec (&match->regex, str,
125 /* nmatch = */ 0, /* pmatch = */ NULL,
126 /* flags = */ 0);
127 if (status == 0)
128 return (1);
129 else
130 return (0);
131 }
132 else if (strcmp (match->str, str) == 0)
133 return (1);
134 else
135 return (0);
136 } /* }}} _Bool lu_part_matches */
138 static int lu_copy_ident_to_match_part (part_match_t *match_part, /* {{{ */
139 char const *ident_part)
140 {
141 size_t len = strlen (ident_part);
142 int status;
144 if ((len < 3) || (ident_part[0] != '/') || (ident_part[len - 1] != '/'))
145 {
146 sstrncpy (match_part->str, ident_part, sizeof (match_part->str));
147 match_part->is_regex = 0;
148 return (0);
149 }
151 /* Copy string without the leading slash. */
152 sstrncpy (match_part->str, ident_part + 1, sizeof (match_part->str));
153 assert (sizeof (match_part->str) > len);
154 /* strip trailing slash */
155 match_part->str[len - 2] = 0;
157 status = regcomp (&match_part->regex, match_part->str,
158 /* flags = */ REG_EXTENDED);
159 if (status != 0)
160 {
161 char errbuf[1024];
162 regerror (status, &match_part->regex, errbuf, sizeof (errbuf));
163 ERROR ("utils_vl_lookup: Compiling regular expression \"%s\" failed: %s",
164 match_part->str, errbuf);
165 return (EINVAL);
166 }
167 match_part->is_regex = 1;
169 return (0);
170 } /* }}} int lu_copy_ident_to_match_part */
172 static int lu_copy_ident_to_match (identifier_match_t *match, /* {{{ */
173 identifier_t const *ident, unsigned int group_by)
174 {
175 memset (match, 0, sizeof (*match));
177 match->group_by = group_by;
179 #define COPY_FIELD(field) do { \
180 int status = lu_copy_ident_to_match_part (&match->field, ident->field); \
181 if (status != 0) \
182 return (status); \
183 } while (0)
185 COPY_FIELD (host);
186 COPY_FIELD (plugin);
187 COPY_FIELD (plugin_instance);
188 COPY_FIELD (type);
189 COPY_FIELD (type_instance);
191 #undef COPY_FIELD
193 return (0);
194 } /* }}} int lu_copy_ident_to_match */
196 /* user_class->lock must be held when calling this function */
197 static void *lu_create_user_obj (lookup_t *obj, /* {{{ */
198 data_set_t const *ds, value_list_t const *vl,
199 user_class_t *user_class)
200 {
201 user_obj_t *user_obj;
203 user_obj = malloc (sizeof (*user_obj));
204 if (user_obj == NULL)
205 {
206 ERROR ("utils_vl_lookup: malloc failed.");
207 return (NULL);
208 }
209 memset (user_obj, 0, sizeof (*user_obj));
210 user_obj->next = NULL;
212 user_obj->user_obj = obj->cb_user_class (ds, vl, user_class->user_class);
213 if (user_obj->user_obj == NULL)
214 {
215 sfree (user_obj);
216 WARNING("utils_vl_lookup: User-provided constructor failed.");
217 return (NULL);
218 }
220 #define COPY_FIELD(field, group_mask) do { \
221 if (user_class->match.field.is_regex \
222 && ((user_class->match.group_by & group_mask) == 0)) \
223 sstrncpy (user_obj->ident.field, "/.*/", sizeof (user_obj->ident.field)); \
224 else \
225 sstrncpy (user_obj->ident.field, vl->field, sizeof (user_obj->ident.field)); \
226 } while (0)
228 COPY_FIELD (host, LU_GROUP_BY_HOST);
229 COPY_FIELD (plugin, LU_GROUP_BY_PLUGIN);
230 COPY_FIELD (plugin_instance, LU_GROUP_BY_PLUGIN_INSTANCE);
231 COPY_FIELD (type, 0);
232 COPY_FIELD (type_instance, LU_GROUP_BY_TYPE_INSTANCE);
234 #undef COPY_FIELD
236 if (user_class->user_obj_list == NULL)
237 {
238 user_class->user_obj_list = user_obj;
239 }
240 else
241 {
242 user_obj_t *last = user_class->user_obj_list;
243 while (last->next != NULL)
244 last = last->next;
245 last->next = user_obj;
246 }
248 return (user_obj);
249 } /* }}} void *lu_create_user_obj */
251 /* user_class->lock must be held when calling this function */
252 static user_obj_t *lu_find_user_obj (user_class_t *user_class, /* {{{ */
253 value_list_t const *vl)
254 {
255 user_obj_t *ptr;
257 for (ptr = user_class->user_obj_list;
258 ptr != NULL;
259 ptr = ptr->next)
260 {
261 if (user_class->match.host.is_regex
262 && (user_class->match.group_by & LU_GROUP_BY_HOST)
263 && (strcmp (vl->host, ptr->ident.host) != 0))
264 continue;
265 if (user_class->match.plugin.is_regex
266 && (user_class->match.group_by & LU_GROUP_BY_PLUGIN)
267 && (strcmp (vl->plugin, ptr->ident.plugin) != 0))
268 continue;
269 if (user_class->match.plugin_instance.is_regex
270 && (user_class->match.group_by & LU_GROUP_BY_PLUGIN_INSTANCE)
271 && (strcmp (vl->plugin_instance, ptr->ident.plugin_instance) != 0))
272 continue;
273 if (user_class->match.type_instance.is_regex
274 && (user_class->match.group_by & LU_GROUP_BY_TYPE_INSTANCE)
275 && (strcmp (vl->type_instance, ptr->ident.type_instance) != 0))
276 continue;
278 return (ptr);
279 }
281 return (NULL);
282 } /* }}} user_obj_t *lu_find_user_obj */
284 static int lu_handle_user_class (lookup_t *obj, /* {{{ */
285 data_set_t const *ds, value_list_t const *vl,
286 user_class_t *user_class)
287 {
288 user_obj_t *user_obj;
289 int status;
291 assert (strcmp (vl->type, user_class->match.type.str) == 0);
292 assert (user_class->match.plugin.is_regex
293 || (strcmp (vl->plugin, user_class->match.plugin.str)) == 0);
295 if (!lu_part_matches (&user_class->match.type_instance, vl->type_instance)
296 || !lu_part_matches (&user_class->match.plugin_instance, vl->plugin_instance)
297 || !lu_part_matches (&user_class->match.plugin, vl->plugin)
298 || !lu_part_matches (&user_class->match.host, vl->host))
299 return (1);
301 pthread_mutex_lock (&user_class->lock);
302 user_obj = lu_find_user_obj (user_class, vl);
303 if (user_obj == NULL)
304 {
305 /* call lookup_class_callback_t() and insert into the list of user objects. */
306 user_obj = lu_create_user_obj (obj, ds, vl, user_class);
307 if (user_obj == NULL) {
308 pthread_mutex_unlock (&user_class->lock);
309 return (-1);
310 }
311 }
312 pthread_mutex_unlock (&user_class->lock);
314 status = obj->cb_user_obj (ds, vl,
315 user_class->user_class, user_obj->user_obj);
316 if (status != 0)
317 {
318 ERROR ("utils_vl_lookup: The user object callback failed with status %i.",
319 status);
320 /* Returning a negative value means: abort! */
321 if (status < 0)
322 return (status);
323 else
324 return (1);
325 }
327 return (0);
328 } /* }}} int lu_handle_user_class */
330 static int lu_handle_user_class_list (lookup_t *obj, /* {{{ */
331 data_set_t const *ds, value_list_t const *vl,
332 user_class_list_t *user_class_list)
333 {
334 user_class_list_t *ptr;
335 int retval = 0;
337 for (ptr = user_class_list; ptr != NULL; ptr = ptr->next)
338 {
339 int status;
341 status = lu_handle_user_class (obj, ds, vl, &ptr->entry);
342 if (status < 0)
343 return (status);
344 else if (status == 0)
345 retval++;
346 }
348 return (retval);
349 } /* }}} int lu_handle_user_class_list */
351 static by_type_entry_t *lu_search_by_type (lookup_t *obj, /* {{{ */
352 char const *type, _Bool allocate_if_missing)
353 {
354 by_type_entry_t *by_type;
355 char *type_copy;
356 int status;
358 status = c_avl_get (obj->by_type_tree, type, (void *) &by_type);
359 if (status == 0)
360 return (by_type);
362 if (!allocate_if_missing)
363 return (NULL);
365 type_copy = strdup (type);
366 if (type_copy == NULL)
367 {
368 ERROR ("utils_vl_lookup: strdup failed.");
369 return (NULL);
370 }
372 by_type = malloc (sizeof (*by_type));
373 if (by_type == NULL)
374 {
375 ERROR ("utils_vl_lookup: malloc failed.");
376 sfree (type_copy);
377 return (NULL);
378 }
379 memset (by_type, 0, sizeof (*by_type));
380 by_type->wildcard_plugin_list = NULL;
382 by_type->by_plugin_tree = c_avl_create ((void *) strcmp);
383 if (by_type->by_plugin_tree == NULL)
384 {
385 ERROR ("utils_vl_lookup: c_avl_create failed.");
386 sfree (by_type);
387 sfree (type_copy);
388 return (NULL);
389 }
391 status = c_avl_insert (obj->by_type_tree,
392 /* key = */ type_copy, /* value = */ by_type);
393 assert (status <= 0); /* >0 => entry exists => race condition. */
394 if (status != 0)
395 {
396 ERROR ("utils_vl_lookup: c_avl_insert failed.");
397 c_avl_destroy (by_type->by_plugin_tree);
398 sfree (by_type);
399 sfree (type_copy);
400 return (NULL);
401 }
403 return (by_type);
404 } /* }}} by_type_entry_t *lu_search_by_type */
406 static int lu_add_by_plugin (by_type_entry_t *by_type, /* {{{ */
407 user_class_list_t *user_class_list)
408 {
409 user_class_list_t *ptr = NULL;
410 identifier_match_t const *match = &user_class_list->entry.match;
412 /* Lookup user_class_list from the per-plugin structure. If this is the first
413 * user_class to be added, the block returns immediately. Otherwise they will
414 * set "ptr" to non-NULL. */
415 if (match->plugin.is_regex)
416 {
417 if (by_type->wildcard_plugin_list == NULL)
418 {
419 by_type->wildcard_plugin_list = user_class_list;
420 return (0);
421 }
423 ptr = by_type->wildcard_plugin_list;
424 } /* if (plugin is wildcard) */
425 else /* (plugin is not wildcard) */
426 {
427 int status;
429 status = c_avl_get (by_type->by_plugin_tree,
430 match->plugin.str, (void *) &ptr);
432 if (status != 0) /* plugin not yet in tree */
433 {
434 char *plugin_copy = strdup (match->plugin.str);
436 if (plugin_copy == NULL)
437 {
438 ERROR ("utils_vl_lookup: strdup failed.");
439 sfree (user_class_list);
440 return (ENOMEM);
441 }
443 status = c_avl_insert (by_type->by_plugin_tree,
444 plugin_copy, user_class_list);
445 if (status != 0)
446 {
447 ERROR ("utils_vl_lookup: c_avl_insert(\"%s\") failed with status %i.",
448 plugin_copy, status);
449 sfree (plugin_copy);
450 sfree (user_class_list);
451 return (status);
452 }
453 else
454 {
455 return (0);
456 }
457 } /* if (plugin not yet in tree) */
458 } /* if (plugin is not wildcard) */
460 assert (ptr != NULL);
462 while (ptr->next != NULL)
463 ptr = ptr->next;
464 ptr->next = user_class_list;
466 return (0);
467 } /* }}} int lu_add_by_plugin */
469 static void lu_destroy_user_obj (lookup_t *obj, /* {{{ */
470 user_obj_t *user_obj)
471 {
472 while (user_obj != NULL)
473 {
474 user_obj_t *next = user_obj->next;
476 if (obj->cb_free_obj != NULL)
477 obj->cb_free_obj (user_obj->user_obj);
478 user_obj->user_obj = NULL;
480 sfree (user_obj);
481 user_obj = next;
482 }
483 } /* }}} void lu_destroy_user_obj */
485 static void lu_destroy_user_class_list (lookup_t *obj, /* {{{ */
486 user_class_list_t *user_class_list)
487 {
488 while (user_class_list != NULL)
489 {
490 user_class_list_t *next = user_class_list->next;
492 if (obj->cb_free_class != NULL)
493 obj->cb_free_class (user_class_list->entry.user_class);
494 user_class_list->entry.user_class = NULL;
496 lu_destroy_user_obj (obj, user_class_list->entry.user_obj_list);
497 user_class_list->entry.user_obj_list = NULL;
498 pthread_mutex_destroy (&user_class_list->entry.lock);
500 sfree (user_class_list);
501 user_class_list = next;
502 }
503 } /* }}} void lu_destroy_user_class_list */
505 static void lu_destroy_by_type (lookup_t *obj, /* {{{ */
506 by_type_entry_t *by_type)
507 {
509 while (42)
510 {
511 char *plugin = NULL;
512 user_class_list_t *user_class_list = NULL;
513 int status;
515 status = c_avl_pick (by_type->by_plugin_tree,
516 (void *) &plugin, (void *) &user_class_list);
517 if (status != 0)
518 break;
520 DEBUG ("utils_vl_lookup: lu_destroy_by_type: Destroying plugin \"%s\".",
521 plugin);
522 sfree (plugin);
523 lu_destroy_user_class_list (obj, user_class_list);
524 }
526 c_avl_destroy (by_type->by_plugin_tree);
527 by_type->by_plugin_tree = NULL;
529 lu_destroy_user_class_list (obj, by_type->wildcard_plugin_list);
530 by_type->wildcard_plugin_list = NULL;
532 sfree (by_type);
533 } /* }}} int lu_destroy_by_type */
535 /*
536 * Public functions
537 */
538 lookup_t *lookup_create (lookup_class_callback_t cb_user_class, /* {{{ */
539 lookup_obj_callback_t cb_user_obj,
540 lookup_free_class_callback_t cb_free_class,
541 lookup_free_obj_callback_t cb_free_obj)
542 {
543 lookup_t *obj = malloc (sizeof (*obj));
544 if (obj == NULL)
545 {
546 ERROR ("utils_vl_lookup: malloc failed.");
547 return (NULL);
548 }
549 memset (obj, 0, sizeof (*obj));
551 obj->by_type_tree = c_avl_create ((void *) strcmp);
552 if (obj->by_type_tree == NULL)
553 {
554 ERROR ("utils_vl_lookup: c_avl_create failed.");
555 sfree (obj);
556 return (NULL);
557 }
559 obj->cb_user_class = cb_user_class;
560 obj->cb_user_obj = cb_user_obj;
561 obj->cb_free_class = cb_free_class;
562 obj->cb_free_obj = cb_free_obj;
564 return (obj);
565 } /* }}} lookup_t *lookup_create */
567 void lookup_destroy (lookup_t *obj) /* {{{ */
568 {
569 int status;
571 if (obj == NULL)
572 return;
574 while (42)
575 {
576 char *type = NULL;
577 by_type_entry_t *by_type = NULL;
579 status = c_avl_pick (obj->by_type_tree, (void *) &type, (void *) &by_type);
580 if (status != 0)
581 break;
583 DEBUG ("utils_vl_lookup: lookup_destroy: Destroying type \"%s\".", type);
584 sfree (type);
585 lu_destroy_by_type (obj, by_type);
586 }
588 c_avl_destroy (obj->by_type_tree);
589 obj->by_type_tree = NULL;
591 sfree (obj);
592 } /* }}} void lookup_destroy */
594 int lookup_add (lookup_t *obj, /* {{{ */
595 identifier_t const *ident, unsigned int group_by, void *user_class)
596 {
597 by_type_entry_t *by_type = NULL;
598 user_class_list_t *user_class_obj;
600 by_type = lu_search_by_type (obj, ident->type, /* allocate = */ 1);
601 if (by_type == NULL)
602 return (-1);
604 user_class_obj = malloc (sizeof (*user_class_obj));
605 if (user_class_obj == NULL)
606 {
607 ERROR ("utils_vl_lookup: malloc failed.");
608 return (ENOMEM);
609 }
610 memset (user_class_obj, 0, sizeof (*user_class_obj));
611 pthread_mutex_init (&user_class_obj->entry.lock, /* attr = */ NULL);
612 user_class_obj->entry.user_class = user_class;
613 lu_copy_ident_to_match (&user_class_obj->entry.match, ident, group_by);
614 user_class_obj->entry.user_obj_list = NULL;
615 user_class_obj->next = NULL;
617 return (lu_add_by_plugin (by_type, user_class_obj));
618 } /* }}} int lookup_add */
620 /* returns the number of successful calls to the callback function */
621 int lookup_search (lookup_t *obj, /* {{{ */
622 data_set_t const *ds, value_list_t const *vl)
623 {
624 by_type_entry_t *by_type = NULL;
625 user_class_list_t *user_class_list = NULL;
626 int retval = 0;
627 int status;
629 if ((obj == NULL) || (ds == NULL) || (vl == NULL))
630 return (-EINVAL);
632 by_type = lu_search_by_type (obj, vl->type, /* allocate = */ 0);
633 if (by_type == NULL)
634 return (0);
636 status = c_avl_get (by_type->by_plugin_tree,
637 vl->plugin, (void *) &user_class_list);
638 if (status == 0)
639 {
640 status = lu_handle_user_class_list (obj, ds, vl, user_class_list);
641 if (status < 0)
642 return (status);
643 retval += status;
644 }
646 if (by_type->wildcard_plugin_list != NULL)
647 {
648 status = lu_handle_user_class_list (obj, ds, vl,
649 by_type->wildcard_plugin_list);
650 if (status < 0)
651 return (status);
652 retval += status;
653 }
655 return (retval);
656 } /* }}} lookup_search */