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