Merge branch 'collectd-5.4' into collectd-5.5
[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_vl_lookup.h"
34 #include "utils_avltree.h"
35
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
44
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;
55
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;
63
64   unsigned int group_by;
65 };
66 typedef struct identifier_match_s identifier_match_t;
67
68 struct lookup_s
69 {
70   c_avl_tree_t *by_type_tree;
71
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 };
77
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;
84
85   user_obj_t *next;
86 };
87
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;
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 {
101   user_class_t entry;
102   user_class_list_t *next;
103 };
104
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;
111
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);
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   }
132   else if (strcmp (match->str, str) == 0)
133     return (1);
134   else
135     return (0);
136 } /* }}} _Bool lu_part_matches */
137
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;
143
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   }
150
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;
156   
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;
168   
169   return (0);
170 } /* }}} int lu_copy_ident_to_match_part */
171
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));
176
177   match->group_by = group_by;
178
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)
184
185   COPY_FIELD (host);
186   COPY_FIELD (plugin);
187   COPY_FIELD (plugin_instance);
188   COPY_FIELD (type);
189   COPY_FIELD (type_instance);
190
191 #undef COPY_FIELD
192
193   return (0);
194 } /* }}} int lu_copy_ident_to_match */
195
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;
202
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;
211
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   }
219
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)
227
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);
233
234 #undef COPY_FIELD
235
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   }
247
248   return (user_obj);
249 } /* }}} void *lu_create_user_obj */
250
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;
256
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;
277
278     return (ptr);
279   }
280
281   return (NULL);
282 } /* }}} user_obj_t *lu_find_user_obj */
283
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;
290
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);
294
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);
300
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);
313
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   }
326
327   return (0);
328 } /* }}} int lu_handle_user_class */
329
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;
336   
337   for (ptr = user_class_list; ptr != NULL; ptr = ptr->next)
338   {
339     int status;
340
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   }
347
348   return (retval);
349 } /* }}} int lu_handle_user_class_list */
350
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;
357
358   status = c_avl_get (obj->by_type_tree, type, (void *) &by_type);
359   if (status == 0)
360     return (by_type);
361
362   if (!allocate_if_missing)
363     return (NULL);
364
365   type_copy = strdup (type);
366   if (type_copy == NULL)
367   {
368     ERROR ("utils_vl_lookup: strdup failed.");
369     return (NULL);
370   }
371
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;
381   
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   }
390
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   }
402   
403   return (by_type);
404 } /* }}} by_type_entry_t *lu_search_by_type */
405
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;
411
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     }
422
423     ptr = by_type->wildcard_plugin_list;
424   } /* if (plugin is wildcard) */
425   else /* (plugin is not wildcard) */
426   {
427     int status;
428
429     status = c_avl_get (by_type->by_plugin_tree,
430         match->plugin.str, (void *) &ptr);
431
432     if (status != 0) /* plugin not yet in tree */
433     {
434       char *plugin_copy = strdup (match->plugin.str);
435
436       if (plugin_copy == NULL)
437       {
438         ERROR ("utils_vl_lookup: strdup failed.");
439         sfree (user_class_list);
440         return (ENOMEM);
441       }
442
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) */
459
460   assert (ptr != NULL);
461
462   while (ptr->next != NULL)
463     ptr = ptr->next;
464   ptr->next = user_class_list;
465
466   return (0);
467 } /* }}} int lu_add_by_plugin */
468
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;
475
476     if (obj->cb_free_obj != NULL)
477       obj->cb_free_obj (user_obj->user_obj);
478     user_obj->user_obj = NULL;
479
480     sfree (user_obj);
481     user_obj = next;
482   }
483 } /* }}} void lu_destroy_user_obj */
484
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;
491
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;
495
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);
499
500     sfree (user_class_list);
501     user_class_list = next;
502   }
503 } /* }}} void lu_destroy_user_class_list */
504
505 static void lu_destroy_by_type (lookup_t *obj, /* {{{ */
506     by_type_entry_t *by_type)
507 {
508   
509   while (42)
510   {
511     char *plugin = NULL;
512     user_class_list_t *user_class_list = NULL;
513     int status;
514
515     status = c_avl_pick (by_type->by_plugin_tree,
516         (void *) &plugin, (void *) &user_class_list);
517     if (status != 0)
518       break;
519
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   }
525
526   c_avl_destroy (by_type->by_plugin_tree);
527   by_type->by_plugin_tree = NULL;
528
529   lu_destroy_user_class_list (obj, by_type->wildcard_plugin_list);
530   by_type->wildcard_plugin_list = NULL;
531
532   sfree (by_type);
533 } /* }}} int lu_destroy_by_type */
534
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));
550
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   }
558
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;
563
564   return (obj);
565 } /* }}} lookup_t *lookup_create */
566
567 void lookup_destroy (lookup_t *obj) /* {{{ */
568 {
569   int status;
570
571   if (obj == NULL)
572     return;
573
574   while (42)
575   {
576     char *type = NULL;
577     by_type_entry_t *by_type = NULL;
578
579     status = c_avl_pick (obj->by_type_tree, (void *) &type, (void *) &by_type);
580     if (status != 0)
581       break;
582
583     DEBUG ("utils_vl_lookup: lookup_destroy: Destroying type \"%s\".", type);
584     sfree (type);
585     lu_destroy_by_type (obj, by_type);
586   }
587
588   c_avl_destroy (obj->by_type_tree);
589   obj->by_type_tree = NULL;
590
591   sfree (obj);
592 } /* }}} void lookup_destroy */
593
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;
599
600   by_type = lu_search_by_type (obj, ident->type, /* allocate = */ 1);
601   if (by_type == NULL)
602     return (-1);
603
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;
616
617   return (lu_add_by_plugin (by_type, user_class_obj));
618 } /* }}} int lookup_add */
619
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;
628
629   if ((obj == NULL) || (ds == NULL) || (vl == NULL))
630     return (-EINVAL);
631
632   by_type = lu_search_by_type (obj, vl->type, /* allocate = */ 0);
633   if (by_type == NULL)
634     return (0);
635
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   }
645
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   }
654     
655   return (retval);
656 } /* }}} lookup_search */