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"
30 #include <pthread.h>
31 #include <regex.h>
33 #include "common.h"
34 #include "utils_vl_lookup.h"
35 #include "utils_avltree.h"
37 #if HAVE_LIBKSTAT
38 kstat_ctl_t *kc;
39 #endif /* HAVE_LIBKSTAT */
41 #if BUILD_TEST
42 # define sstrncpy strncpy
43 # define plugin_log(s, ...) do { \
44 printf ("[severity %i] ", s); \
45 printf (__VA_ARGS__); \
46 printf ("\n"); \
47 } while (0)
48 #endif
50 /*
51 * Types
52 */
53 struct part_match_s
54 {
55 char str[DATA_MAX_NAME_LEN];
56 regex_t regex;
57 _Bool is_regex;
58 };
59 typedef struct part_match_s part_match_t;
61 struct identifier_match_s
62 {
63 part_match_t host;
64 part_match_t plugin;
65 part_match_t plugin_instance;
66 part_match_t type;
67 part_match_t type_instance;
69 unsigned int group_by;
70 };
71 typedef struct identifier_match_s identifier_match_t;
73 struct lookup_s
74 {
75 c_avl_tree_t *by_type_tree;
77 lookup_class_callback_t cb_user_class;
78 lookup_obj_callback_t cb_user_obj;
79 lookup_free_class_callback_t cb_free_class;
80 lookup_free_obj_callback_t cb_free_obj;
81 };
83 struct user_obj_s;
84 typedef struct user_obj_s user_obj_t;
85 struct user_obj_s
86 {
87 void *user_obj;
88 identifier_t ident;
90 user_obj_t *next;
91 };
93 struct user_class_s
94 {
95 pthread_mutex_t lock;
96 void *user_class;
97 identifier_match_t match;
98 user_obj_t *user_obj_list; /* list of user_obj */
99 };
100 typedef struct user_class_s user_class_t;
102 struct user_class_list_s;
103 typedef struct user_class_list_s user_class_list_t;
104 struct user_class_list_s
105 {
106 user_class_t entry;
107 user_class_list_t *next;
108 };
110 struct by_type_entry_s
111 {
112 c_avl_tree_t *by_plugin_tree; /* plugin -> user_class_list_t */
113 user_class_list_t *wildcard_plugin_list;
114 };
115 typedef struct by_type_entry_s by_type_entry_t;
117 /*
118 * Private functions
119 */
120 static _Bool lu_part_matches (part_match_t const *match, /* {{{ */
121 char const *str)
122 {
123 if (match->is_regex)
124 {
125 /* Short cut popular catch-all regex. */
126 if (strcmp (".*", match->str) == 0)
127 return (1);
129 int status = regexec (&match->regex, str,
130 /* nmatch = */ 0, /* pmatch = */ NULL,
131 /* flags = */ 0);
132 if (status == 0)
133 return (1);
134 else
135 return (0);
136 }
137 else if (strcmp (match->str, str) == 0)
138 return (1);
139 else
140 return (0);
141 } /* }}} _Bool lu_part_matches */
143 static int lu_copy_ident_to_match_part (part_match_t *match_part, /* {{{ */
144 char const *ident_part)
145 {
146 size_t len = strlen (ident_part);
147 int status;
149 if ((len < 3) || (ident_part[0] != '/') || (ident_part[len - 1] != '/'))
150 {
151 sstrncpy (match_part->str, ident_part, sizeof (match_part->str));
152 match_part->is_regex = 0;
153 return (0);
154 }
156 /* Copy string without the leading slash. */
157 sstrncpy (match_part->str, ident_part + 1, sizeof (match_part->str));
158 assert (sizeof (match_part->str) > len);
159 /* strip trailing slash */
160 match_part->str[len - 2] = 0;
162 status = regcomp (&match_part->regex, match_part->str,
163 /* flags = */ REG_EXTENDED);
164 if (status != 0)
165 {
166 char errbuf[1024];
167 regerror (status, &match_part->regex, errbuf, sizeof (errbuf));
168 ERROR ("utils_vl_lookup: Compiling regular expression \"%s\" failed: %s",
169 match_part->str, errbuf);
170 return (EINVAL);
171 }
172 match_part->is_regex = 1;
174 return (0);
175 } /* }}} int lu_copy_ident_to_match_part */
177 static int lu_copy_ident_to_match (identifier_match_t *match, /* {{{ */
178 identifier_t const *ident, unsigned int group_by)
179 {
180 memset (match, 0, sizeof (*match));
182 match->group_by = group_by;
184 #define COPY_FIELD(field) do { \
185 int status = lu_copy_ident_to_match_part (&match->field, ident->field); \
186 if (status != 0) \
187 return (status); \
188 } while (0)
190 COPY_FIELD (host);
191 COPY_FIELD (plugin);
192 COPY_FIELD (plugin_instance);
193 COPY_FIELD (type);
194 COPY_FIELD (type_instance);
196 #undef COPY_FIELD
198 return (0);
199 } /* }}} int lu_copy_ident_to_match */
201 /* user_class->lock must be held when calling this function */
202 static void *lu_create_user_obj (lookup_t *obj, /* {{{ */
203 data_set_t const *ds, value_list_t const *vl,
204 user_class_t *user_class)
205 {
206 user_obj_t *user_obj;
208 user_obj = calloc (1, sizeof (*user_obj));
209 if (user_obj == NULL)
210 {
211 ERROR ("utils_vl_lookup: calloc failed.");
212 return (NULL);
213 }
214 user_obj->next = NULL;
216 user_obj->user_obj = obj->cb_user_class (ds, vl, user_class->user_class);
217 if (user_obj->user_obj == NULL)
218 {
219 sfree (user_obj);
220 WARNING("utils_vl_lookup: User-provided constructor failed.");
221 return (NULL);
222 }
224 #define COPY_FIELD(field, group_mask) do { \
225 if (user_class->match.field.is_regex \
226 && ((user_class->match.group_by & group_mask) == 0)) \
227 sstrncpy (user_obj->ident.field, "/.*/", sizeof (user_obj->ident.field)); \
228 else \
229 sstrncpy (user_obj->ident.field, vl->field, sizeof (user_obj->ident.field)); \
230 } while (0)
232 COPY_FIELD (host, LU_GROUP_BY_HOST);
233 COPY_FIELD (plugin, LU_GROUP_BY_PLUGIN);
234 COPY_FIELD (plugin_instance, LU_GROUP_BY_PLUGIN_INSTANCE);
235 COPY_FIELD (type, 0);
236 COPY_FIELD (type_instance, LU_GROUP_BY_TYPE_INSTANCE);
238 #undef COPY_FIELD
240 if (user_class->user_obj_list == NULL)
241 {
242 user_class->user_obj_list = user_obj;
243 }
244 else
245 {
246 user_obj_t *last = user_class->user_obj_list;
247 while (last->next != NULL)
248 last = last->next;
249 last->next = user_obj;
250 }
252 return (user_obj);
253 } /* }}} void *lu_create_user_obj */
255 /* user_class->lock must be held when calling this function */
256 static user_obj_t *lu_find_user_obj (user_class_t *user_class, /* {{{ */
257 value_list_t const *vl)
258 {
259 user_obj_t *ptr;
261 for (ptr = user_class->user_obj_list;
262 ptr != NULL;
263 ptr = ptr->next)
264 {
265 if (user_class->match.host.is_regex
266 && (user_class->match.group_by & LU_GROUP_BY_HOST)
267 && (strcmp (vl->host, ptr->ident.host) != 0))
268 continue;
269 if (user_class->match.plugin.is_regex
270 && (user_class->match.group_by & LU_GROUP_BY_PLUGIN)
271 && (strcmp (vl->plugin, ptr->ident.plugin) != 0))
272 continue;
273 if (user_class->match.plugin_instance.is_regex
274 && (user_class->match.group_by & LU_GROUP_BY_PLUGIN_INSTANCE)
275 && (strcmp (vl->plugin_instance, ptr->ident.plugin_instance) != 0))
276 continue;
277 if (user_class->match.type_instance.is_regex
278 && (user_class->match.group_by & LU_GROUP_BY_TYPE_INSTANCE)
279 && (strcmp (vl->type_instance, ptr->ident.type_instance) != 0))
280 continue;
282 return (ptr);
283 }
285 return (NULL);
286 } /* }}} user_obj_t *lu_find_user_obj */
288 static int lu_handle_user_class (lookup_t *obj, /* {{{ */
289 data_set_t const *ds, value_list_t const *vl,
290 user_class_t *user_class)
291 {
292 user_obj_t *user_obj;
293 int status;
295 assert (strcmp (vl->type, user_class->match.type.str) == 0);
296 assert (user_class->match.plugin.is_regex
297 || (strcmp (vl->plugin, user_class->match.plugin.str)) == 0);
299 if (!lu_part_matches (&user_class->match.type_instance, vl->type_instance)
300 || !lu_part_matches (&user_class->match.plugin_instance, vl->plugin_instance)
301 || !lu_part_matches (&user_class->match.plugin, vl->plugin)
302 || !lu_part_matches (&user_class->match.host, vl->host))
303 return (1);
305 pthread_mutex_lock (&user_class->lock);
306 user_obj = lu_find_user_obj (user_class, vl);
307 if (user_obj == NULL)
308 {
309 /* call lookup_class_callback_t() and insert into the list of user objects. */
310 user_obj = lu_create_user_obj (obj, ds, vl, user_class);
311 if (user_obj == NULL) {
312 pthread_mutex_unlock (&user_class->lock);
313 return (-1);
314 }
315 }
316 pthread_mutex_unlock (&user_class->lock);
318 status = obj->cb_user_obj (ds, vl,
319 user_class->user_class, user_obj->user_obj);
320 if (status != 0)
321 {
322 ERROR ("utils_vl_lookup: The user object callback failed with status %i.",
323 status);
324 /* Returning a negative value means: abort! */
325 if (status < 0)
326 return (status);
327 else
328 return (1);
329 }
331 return (0);
332 } /* }}} int lu_handle_user_class */
334 static int lu_handle_user_class_list (lookup_t *obj, /* {{{ */
335 data_set_t const *ds, value_list_t const *vl,
336 user_class_list_t *user_class_list)
337 {
338 user_class_list_t *ptr;
339 int retval = 0;
341 for (ptr = user_class_list; ptr != NULL; ptr = ptr->next)
342 {
343 int status;
345 status = lu_handle_user_class (obj, ds, vl, &ptr->entry);
346 if (status < 0)
347 return (status);
348 else if (status == 0)
349 retval++;
350 }
352 return (retval);
353 } /* }}} int lu_handle_user_class_list */
355 static by_type_entry_t *lu_search_by_type (lookup_t *obj, /* {{{ */
356 char const *type, _Bool allocate_if_missing)
357 {
358 by_type_entry_t *by_type;
359 char *type_copy;
360 int status;
362 status = c_avl_get (obj->by_type_tree, type, (void *) &by_type);
363 if (status == 0)
364 return (by_type);
366 if (!allocate_if_missing)
367 return (NULL);
369 type_copy = strdup (type);
370 if (type_copy == NULL)
371 {
372 ERROR ("utils_vl_lookup: strdup failed.");
373 return (NULL);
374 }
376 by_type = calloc (1, sizeof (*by_type));
377 if (by_type == NULL)
378 {
379 ERROR ("utils_vl_lookup: calloc failed.");
380 sfree (type_copy);
381 return (NULL);
382 }
383 by_type->wildcard_plugin_list = NULL;
385 by_type->by_plugin_tree = c_avl_create ((int (*) (const void *, const void *)) strcmp);
386 if (by_type->by_plugin_tree == NULL)
387 {
388 ERROR ("utils_vl_lookup: c_avl_create failed.");
389 sfree (by_type);
390 sfree (type_copy);
391 return (NULL);
392 }
394 status = c_avl_insert (obj->by_type_tree,
395 /* key = */ type_copy, /* value = */ by_type);
396 assert (status <= 0); /* >0 => entry exists => race condition. */
397 if (status != 0)
398 {
399 ERROR ("utils_vl_lookup: c_avl_insert failed.");
400 c_avl_destroy (by_type->by_plugin_tree);
401 sfree (by_type);
402 sfree (type_copy);
403 return (NULL);
404 }
406 return (by_type);
407 } /* }}} by_type_entry_t *lu_search_by_type */
409 static int lu_add_by_plugin (by_type_entry_t *by_type, /* {{{ */
410 user_class_list_t *user_class_list)
411 {
412 user_class_list_t *ptr = NULL;
413 identifier_match_t const *match = &user_class_list->entry.match;
415 /* Lookup user_class_list from the per-plugin structure. If this is the first
416 * user_class to be added, the block returns immediately. Otherwise they will
417 * set "ptr" to non-NULL. */
418 if (match->plugin.is_regex)
419 {
420 if (by_type->wildcard_plugin_list == NULL)
421 {
422 by_type->wildcard_plugin_list = user_class_list;
423 return (0);
424 }
426 ptr = by_type->wildcard_plugin_list;
427 } /* if (plugin is wildcard) */
428 else /* (plugin is not wildcard) */
429 {
430 int status;
432 status = c_avl_get (by_type->by_plugin_tree,
433 match->plugin.str, (void *) &ptr);
435 if (status != 0) /* plugin not yet in tree */
436 {
437 char *plugin_copy = strdup (match->plugin.str);
439 if (plugin_copy == NULL)
440 {
441 ERROR ("utils_vl_lookup: strdup failed.");
442 sfree (user_class_list);
443 return (ENOMEM);
444 }
446 status = c_avl_insert (by_type->by_plugin_tree,
447 plugin_copy, user_class_list);
448 if (status != 0)
449 {
450 ERROR ("utils_vl_lookup: c_avl_insert(\"%s\") failed with status %i.",
451 plugin_copy, status);
452 sfree (plugin_copy);
453 sfree (user_class_list);
454 return (status);
455 }
456 else
457 {
458 return (0);
459 }
460 } /* if (plugin not yet in tree) */
461 } /* if (plugin is not wildcard) */
463 assert (ptr != NULL);
465 while (ptr->next != NULL)
466 ptr = ptr->next;
467 ptr->next = user_class_list;
469 return (0);
470 } /* }}} int lu_add_by_plugin */
472 static void lu_destroy_user_obj (lookup_t *obj, /* {{{ */
473 user_obj_t *user_obj)
474 {
475 while (user_obj != NULL)
476 {
477 user_obj_t *next = user_obj->next;
479 if (obj->cb_free_obj != NULL)
480 obj->cb_free_obj (user_obj->user_obj);
481 user_obj->user_obj = NULL;
483 sfree (user_obj);
484 user_obj = next;
485 }
486 } /* }}} void lu_destroy_user_obj */
488 static void lu_destroy_user_class_list (lookup_t *obj, /* {{{ */
489 user_class_list_t *user_class_list)
490 {
491 while (user_class_list != NULL)
492 {
493 user_class_list_t *next = user_class_list->next;
495 if (obj->cb_free_class != NULL)
496 obj->cb_free_class (user_class_list->entry.user_class);
497 user_class_list->entry.user_class = NULL;
499 #define CLEAR_FIELD(field) do { \
500 if (user_class_list->entry.match.field.is_regex) { \
501 regfree (&user_class_list->entry.match.field.regex); \
502 user_class_list->entry.match.field.is_regex = 0; \
503 } \
504 } while (0)
506 CLEAR_FIELD (host);
507 CLEAR_FIELD (plugin);
508 CLEAR_FIELD (plugin_instance);
509 CLEAR_FIELD (type);
510 CLEAR_FIELD (type_instance);
512 #undef CLEAR_FIELD
514 lu_destroy_user_obj (obj, user_class_list->entry.user_obj_list);
515 user_class_list->entry.user_obj_list = NULL;
516 pthread_mutex_destroy (&user_class_list->entry.lock);
518 sfree (user_class_list);
519 user_class_list = next;
520 }
521 } /* }}} void lu_destroy_user_class_list */
523 static void lu_destroy_by_type (lookup_t *obj, /* {{{ */
524 by_type_entry_t *by_type)
525 {
527 while (42)
528 {
529 char *plugin = NULL;
530 user_class_list_t *user_class_list = NULL;
531 int status;
533 status = c_avl_pick (by_type->by_plugin_tree,
534 (void *) &plugin, (void *) &user_class_list);
535 if (status != 0)
536 break;
538 DEBUG ("utils_vl_lookup: lu_destroy_by_type: Destroying plugin \"%s\".",
539 plugin);
540 sfree (plugin);
541 lu_destroy_user_class_list (obj, user_class_list);
542 }
544 c_avl_destroy (by_type->by_plugin_tree);
545 by_type->by_plugin_tree = NULL;
547 lu_destroy_user_class_list (obj, by_type->wildcard_plugin_list);
548 by_type->wildcard_plugin_list = NULL;
550 sfree (by_type);
551 } /* }}} int lu_destroy_by_type */
553 /*
554 * Public functions
555 */
556 lookup_t *lookup_create (lookup_class_callback_t cb_user_class, /* {{{ */
557 lookup_obj_callback_t cb_user_obj,
558 lookup_free_class_callback_t cb_free_class,
559 lookup_free_obj_callback_t cb_free_obj)
560 {
561 lookup_t *obj = calloc (1, sizeof (*obj));
562 if (obj == NULL)
563 {
564 ERROR ("utils_vl_lookup: calloc failed.");
565 return (NULL);
566 }
568 obj->by_type_tree = c_avl_create ((int (*) (const void *, const void *)) strcmp);
569 if (obj->by_type_tree == NULL)
570 {
571 ERROR ("utils_vl_lookup: c_avl_create failed.");
572 sfree (obj);
573 return (NULL);
574 }
576 obj->cb_user_class = cb_user_class;
577 obj->cb_user_obj = cb_user_obj;
578 obj->cb_free_class = cb_free_class;
579 obj->cb_free_obj = cb_free_obj;
581 return (obj);
582 } /* }}} lookup_t *lookup_create */
584 void lookup_destroy (lookup_t *obj) /* {{{ */
585 {
586 int status;
588 if (obj == NULL)
589 return;
591 while (42)
592 {
593 char *type = NULL;
594 by_type_entry_t *by_type = NULL;
596 status = c_avl_pick (obj->by_type_tree, (void *) &type, (void *) &by_type);
597 if (status != 0)
598 break;
600 DEBUG ("utils_vl_lookup: lookup_destroy: Destroying type \"%s\".", type);
601 sfree (type);
602 lu_destroy_by_type (obj, by_type);
603 }
605 c_avl_destroy (obj->by_type_tree);
606 obj->by_type_tree = NULL;
608 sfree (obj);
609 } /* }}} void lookup_destroy */
611 int lookup_add (lookup_t *obj, /* {{{ */
612 identifier_t const *ident, unsigned int group_by, void *user_class)
613 {
614 by_type_entry_t *by_type = NULL;
615 user_class_list_t *user_class_obj;
617 by_type = lu_search_by_type (obj, ident->type, /* allocate = */ 1);
618 if (by_type == NULL)
619 return (-1);
621 user_class_obj = calloc (1, sizeof (*user_class_obj));
622 if (user_class_obj == NULL)
623 {
624 ERROR ("utils_vl_lookup: calloc failed.");
625 return (ENOMEM);
626 }
627 pthread_mutex_init (&user_class_obj->entry.lock, /* attr = */ NULL);
628 user_class_obj->entry.user_class = user_class;
629 lu_copy_ident_to_match (&user_class_obj->entry.match, ident, group_by);
630 user_class_obj->entry.user_obj_list = NULL;
631 user_class_obj->next = NULL;
633 return (lu_add_by_plugin (by_type, user_class_obj));
634 } /* }}} int lookup_add */
636 /* returns the number of successful calls to the callback function */
637 int lookup_search (lookup_t *obj, /* {{{ */
638 data_set_t const *ds, value_list_t const *vl)
639 {
640 by_type_entry_t *by_type = NULL;
641 user_class_list_t *user_class_list = NULL;
642 int retval = 0;
643 int status;
645 if ((obj == NULL) || (ds == NULL) || (vl == NULL))
646 return (-EINVAL);
648 by_type = lu_search_by_type (obj, vl->type, /* allocate = */ 0);
649 if (by_type == NULL)
650 return (0);
652 status = c_avl_get (by_type->by_plugin_tree,
653 vl->plugin, (void *) &user_class_list);
654 if (status == 0)
655 {
656 status = lu_handle_user_class_list (obj, ds, vl, user_class_list);
657 if (status < 0)
658 return (status);
659 retval += status;
660 }
662 if (by_type->wildcard_plugin_list != NULL)
663 {
664 status = lu_handle_user_class_list (obj, ds, vl,
665 by_type->wildcard_plugin_list);
666 if (status < 0)
667 return (status);
668 retval += status;
669 }
671 return (retval);
672 } /* }}} lookup_search */