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