plugin.h: Introduce the identifier_t type with pointer fields.
[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
30 #include <pthread.h>
31 #include <regex.h>
32
33 #include "common.h"
34 #include "utils_vl_lookup.h"
35 #include "utils_avltree.h"
36
37 #if HAVE_LIBKSTAT
38 kstat_ctl_t *kc;
39 #endif /* HAVE_LIBKSTAT */
40
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
49
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;
60
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;
68
69   unsigned int group_by;
70 };
71 typedef struct identifier_match_s identifier_match_t;
72
73 struct lookup_s
74 {
75   c_avl_tree_t *by_type_tree;
76
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 };
82
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   lookup_identifier_t ident;
89
90   user_obj_t *next;
91 };
92
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;
101
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 };
109
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;
116
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);
128
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 */
142
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;
148
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   }
155
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;
161
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;
173
174   return (0);
175 } /* }}} int lu_copy_ident_to_match_part */
176
177 static int lu_copy_ident_to_match (identifier_match_t *match, /* {{{ */
178     lookup_identifier_t const *ident, unsigned int group_by)
179 {
180   memset (match, 0, sizeof (*match));
181
182   match->group_by = group_by;
183
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)
189
190   COPY_FIELD (host);
191   COPY_FIELD (plugin);
192   COPY_FIELD (plugin_instance);
193   COPY_FIELD (type);
194   COPY_FIELD (type_instance);
195
196 #undef COPY_FIELD
197
198   return (0);
199 } /* }}} int lu_copy_ident_to_match */
200
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;
207
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;
215
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   }
223
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)
231
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);
237
238 #undef COPY_FIELD
239
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   }
251
252   return (user_obj);
253 } /* }}} void *lu_create_user_obj */
254
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;
260
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;
281
282     return (ptr);
283   }
284
285   return (NULL);
286 } /* }}} user_obj_t *lu_find_user_obj */
287
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;
294
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);
298
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);
304
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);
317
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   }
330
331   return (0);
332 } /* }}} int lu_handle_user_class */
333
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;
340
341   for (ptr = user_class_list; ptr != NULL; ptr = ptr->next)
342   {
343     int status;
344
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   }
351
352   return (retval);
353 } /* }}} int lu_handle_user_class_list */
354
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;
361
362   status = c_avl_get (obj->by_type_tree, type, (void *) &by_type);
363   if (status == 0)
364     return (by_type);
365
366   if (!allocate_if_missing)
367     return (NULL);
368
369   type_copy = strdup (type);
370   if (type_copy == NULL)
371   {
372     ERROR ("utils_vl_lookup: strdup failed.");
373     return (NULL);
374   }
375
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;
384
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   }
393
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   }
405
406   return (by_type);
407 } /* }}} by_type_entry_t *lu_search_by_type */
408
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;
414
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     }
425
426     ptr = by_type->wildcard_plugin_list;
427   } /* if (plugin is wildcard) */
428   else /* (plugin is not wildcard) */
429   {
430     int status;
431
432     status = c_avl_get (by_type->by_plugin_tree,
433         match->plugin.str, (void *) &ptr);
434
435     if (status != 0) /* plugin not yet in tree */
436     {
437       char *plugin_copy = strdup (match->plugin.str);
438
439       if (plugin_copy == NULL)
440       {
441         ERROR ("utils_vl_lookup: strdup failed.");
442         sfree (user_class_list);
443         return (ENOMEM);
444       }
445
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) */
462
463   assert (ptr != NULL);
464
465   while (ptr->next != NULL)
466     ptr = ptr->next;
467   ptr->next = user_class_list;
468
469   return (0);
470 } /* }}} int lu_add_by_plugin */
471
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;
478
479     if (obj->cb_free_obj != NULL)
480       obj->cb_free_obj (user_obj->user_obj);
481     user_obj->user_obj = NULL;
482
483     sfree (user_obj);
484     user_obj = next;
485   }
486 } /* }}} void lu_destroy_user_obj */
487
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;
494
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;
498
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)
505
506     CLEAR_FIELD (host);
507     CLEAR_FIELD (plugin);
508     CLEAR_FIELD (plugin_instance);
509     CLEAR_FIELD (type);
510     CLEAR_FIELD (type_instance);
511
512 #undef CLEAR_FIELD
513
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);
517
518     sfree (user_class_list);
519     user_class_list = next;
520   }
521 } /* }}} void lu_destroy_user_class_list */
522
523 static void lu_destroy_by_type (lookup_t *obj, /* {{{ */
524     by_type_entry_t *by_type)
525 {
526
527   while (42)
528   {
529     char *plugin = NULL;
530     user_class_list_t *user_class_list = NULL;
531     int status;
532
533     status = c_avl_pick (by_type->by_plugin_tree,
534         (void *) &plugin, (void *) &user_class_list);
535     if (status != 0)
536       break;
537
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   }
543
544   c_avl_destroy (by_type->by_plugin_tree);
545   by_type->by_plugin_tree = NULL;
546
547   lu_destroy_user_class_list (obj, by_type->wildcard_plugin_list);
548   by_type->wildcard_plugin_list = NULL;
549
550   sfree (by_type);
551 } /* }}} int lu_destroy_by_type */
552
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   }
567
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   }
575
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;
580
581   return (obj);
582 } /* }}} lookup_t *lookup_create */
583
584 void lookup_destroy (lookup_t *obj) /* {{{ */
585 {
586   int status;
587
588   if (obj == NULL)
589     return;
590
591   while (42)
592   {
593     char *type = NULL;
594     by_type_entry_t *by_type = NULL;
595
596     status = c_avl_pick (obj->by_type_tree, (void *) &type, (void *) &by_type);
597     if (status != 0)
598       break;
599
600     DEBUG ("utils_vl_lookup: lookup_destroy: Destroying type \"%s\".", type);
601     sfree (type);
602     lu_destroy_by_type (obj, by_type);
603   }
604
605   c_avl_destroy (obj->by_type_tree);
606   obj->by_type_tree = NULL;
607
608   sfree (obj);
609 } /* }}} void lookup_destroy */
610
611 int lookup_add (lookup_t *obj, /* {{{ */
612     lookup_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;
616
617   by_type = lu_search_by_type (obj, ident->type, /* allocate = */ 1);
618   if (by_type == NULL)
619     return (-1);
620
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;
632
633   return (lu_add_by_plugin (by_type, user_class_obj));
634 } /* }}} int lookup_add */
635
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;
644
645   if ((obj == NULL) || (ds == NULL) || (vl == NULL))
646     return (-EINVAL);
647
648   by_type = lu_search_by_type (obj, vl->type, /* allocate = */ 0);
649   if (by_type == NULL)
650     return (0);
651
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   }
661
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   }
670
671   return (retval);
672 } /* }}} lookup_search */