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