Code

Let objects be named.
[sysdb.git] / src / core / plugin.c
1 /*
2  * SysDB - src/core/plugin.c
3  * Copyright (C) 2012 Sebastian 'tokkee' Harl <sh@tokkee.org>
4  * All rights reserved.
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  * 1. Redistributions of source code must retain the above copyright
10  *    notice, this list of conditions and the following disclaimer.
11  * 2. Redistributions in binary form must reproduce the above copyright
12  *    notice, this list of conditions and the following disclaimer in the
13  *    documentation and/or other materials provided with the distribution.
14  *
15  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
16  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
17  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
18  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR
19  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
20  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
21  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
22  * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
23  * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
24  * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
25  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26  */
28 #include "sysdb.h"
29 #include "core/plugin.h"
30 #include "core/error.h"
31 #include "core/time.h"
32 #include "utils/llist.h"
34 #include <assert.h>
36 #include <errno.h>
38 #include <stdarg.h>
39 #include <stdio.h>
40 #include <string.h>
41 #include <strings.h>
42 #include <unistd.h>
44 #include <ltdl.h>
46 #include <pthread.h>
48 /*
49  * private data types
50  */
52 struct sdb_plugin_info {
53         char *name;
55         char *description;
56         char *copyright;
57         char *license;
59         int   version;
60         int   plugin_version;
61 };
62 #define SDB_PLUGIN_INFO_INIT { "no name set", "no description set", \
63         /* copyright */ "", /* license */ "", \
64         /* version */ -1, /* plugin_version */ -1 }
66 typedef struct {
67         sdb_object_t super;
68         void *cb_callback;
69         sdb_object_t *cb_user_data;
70         sdb_plugin_ctx_t cb_ctx;
71 } sdb_plugin_cb_t;
72 #define SDB_PLUGIN_CB_INIT { SDB_OBJECT_INIT, \
73         /* callback = */ NULL, /* user_data = */ NULL, \
74         SDB_PLUGIN_CTX_INIT }
76 typedef struct {
77         sdb_plugin_cb_t super;
78 #define ccb_callback super.cb_callback
79 #define ccb_user_data super.cb_user_data
80 #define ccb_ctx super.cb_ctx
81         sdb_time_t ccb_interval;
82         sdb_time_t ccb_next_update;
83 } sdb_plugin_collector_cb_t;
85 #define SDB_PLUGIN_CB(obj) ((sdb_plugin_cb_t *)(obj))
86 #define SDB_PLUGIN_CCB(obj) ((sdb_plugin_collector_cb_t *)(obj))
88 /*
89  * private variables
90  */
92 static sdb_plugin_ctx_t  plugin_default_ctx = SDB_PLUGIN_CTX_INIT;
94 static pthread_key_t    plugin_ctx_key;
95 static _Bool            plugin_ctx_key_initialized = 0;
97 static sdb_llist_t      *config_list = NULL;
98 static sdb_llist_t      *init_list = NULL;
99 static sdb_llist_t      *collector_list = NULL;
100 static sdb_llist_t      *shutdown_list = NULL;
101 static sdb_llist_t      *log_list = NULL;
103 /*
104  * private helper functions
105  */
107 static void
108 sdb_plugin_ctx_destructor(void *ctx)
110         if (! ctx)
111                 return;
112         free(ctx);
113 } /* sdb_plugin_ctx_destructor */
115 static void
116 sdb_plugin_ctx_init(void)
118         if (plugin_ctx_key_initialized)
119                 return;
121         pthread_key_create(&plugin_ctx_key, sdb_plugin_ctx_destructor);
122         plugin_ctx_key_initialized = 1;
123 } /* sdb_plugin_ctx_init */
125 static sdb_plugin_ctx_t *
126 sdb_plugin_ctx_create(void)
128         sdb_plugin_ctx_t *ctx;
130         ctx = malloc(sizeof(*ctx));
131         if (! ctx)
132                 return NULL;
134         *ctx = plugin_default_ctx;
136         if (! plugin_ctx_key_initialized)
137                 sdb_plugin_ctx_init();
138         pthread_setspecific(plugin_ctx_key, ctx);
139         return ctx;
140 } /* sdb_plugin_ctx_create */
142 static int
143 sdb_plugin_cmp_next_update(const sdb_object_t *a, const sdb_object_t *b)
145         const sdb_plugin_collector_cb_t *ccb1
146                 = (const sdb_plugin_collector_cb_t *)a;
147         const sdb_plugin_collector_cb_t *ccb2
148                 = (const sdb_plugin_collector_cb_t *)b;
150         assert(ccb1 && ccb2);
152         return (ccb1->ccb_next_update > ccb2->ccb_next_update)
153                 ? 1 : (ccb1->ccb_next_update < ccb2->ccb_next_update)
154                 ? -1 : 0;
155 } /* sdb_plugin_cmp_next_update */
157 static sdb_plugin_cb_t *
158 sdb_plugin_find_by_name(sdb_llist_t *list, const char *name)
160         sdb_object_t tmp = SDB_OBJECT_INIT;
162         sdb_object_t *obj;
163         assert(name);
165         if (! list)
166                 return NULL;
168         tmp.name = strdup(name);
169         if (! tmp.name)
170                 return NULL;
171         obj = sdb_llist_search(list, &tmp, sdb_object_cmp_by_name);
172         free(tmp.name);
173         if (! obj)
174                 return NULL;
175         return SDB_PLUGIN_CB(obj);
176 } /* sdb_plugin_find_by_name */
178 /*
179  * private types
180  */
182 static int
183 sdb_plugin_cb_init(sdb_object_t *obj, va_list ap)
185         sdb_llist_t **list = va_arg(ap, sdb_llist_t **);
186         const char   *type = va_arg(ap, const char *);
187         void     *callback = va_arg(ap, void *);
188         sdb_object_t   *ud = va_arg(ap, sdb_object_t *);
190         assert(list);
191         assert(type);
192         assert(obj);
194         if (sdb_plugin_find_by_name(*list, obj->name)) {
195                 sdb_log(SDB_LOG_WARNING, "plugin: %s callback '%s' "
196                                 "has already been registered. Ignoring newly "
197                                 "registered version.", type, obj->name);
198                 return -1;
199         }
201         SDB_PLUGIN_CB(obj)->cb_callback = callback;
202         SDB_PLUGIN_CB(obj)->cb_ctx      = sdb_plugin_get_ctx();
204         sdb_object_ref(ud);
205         SDB_PLUGIN_CB(obj)->cb_user_data = ud;
206         return 0;
207 } /* sdb_plugin_cb_init */
209 static void
210 sdb_plugin_cb_destroy(sdb_object_t *obj)
212         assert(obj);
213         sdb_object_deref(SDB_PLUGIN_CB(obj)->cb_user_data);
214 } /* sdb_plugin_cb_destroy */
216 static sdb_type_t sdb_plugin_cb_type = {
217         sizeof(sdb_plugin_cb_t),
219         sdb_plugin_cb_init,
220         sdb_plugin_cb_destroy,
221         /* clone = */ NULL
222 };
224 static sdb_type_t sdb_plugin_collector_cb_type = {
225         sizeof(sdb_plugin_collector_cb_t),
227         sdb_plugin_cb_init,
228         sdb_plugin_cb_destroy,
229         /* clone = */ NULL
230 };
232 static int
233 sdb_plugin_add_callback(sdb_llist_t **list, const char *type,
234                 const char *name, void *callback, sdb_object_t *user_data)
236         sdb_object_t *obj;
238         if ((! name) || (! callback))
239                 return -1;
241         assert(list);
243         if (! *list)
244                 *list = sdb_llist_create();
245         if (! *list)
246                 return -1;
248         obj = sdb_object_create(name, sdb_plugin_cb_type,
249                         list, type, callback, user_data);
250         if (! obj)
251                 return -1;
253         if (sdb_llist_append(*list, obj)) {
254                 sdb_object_deref(obj);
255                 return -1;
256         }
258         /* pass control to the list */
259         sdb_object_deref(obj);
261         sdb_log(SDB_LOG_INFO, "plugin: Registered %s callback '%s'.",
262                         type, name);
263         return 0;
264 } /* sdb_plugin_add_callback */
266 /*
267  * public API
268  */
270 int
271 sdb_plugin_load(const char *name)
273         char  real_name[strlen(name) > 0 ? strlen(name) : 1];
274         const char *name_ptr;
275         char *tmp;
277         char filename[1024];
279         lt_dlhandle lh;
281         int (*mod_init)(sdb_plugin_info_t *);
282         sdb_plugin_info_t plugin_info = SDB_PLUGIN_INFO_INIT;
284         int status;
286         if ((! name) || (! *name))
287                 return -1;
289         real_name[0] = '\0';
290         name_ptr = name;
292         while ((tmp = strstr(name_ptr, "::"))) {
293                 strncat(real_name, name_ptr, (size_t)(tmp - name_ptr));
294                 strcat(real_name, "/");
295                 name_ptr = tmp + strlen("::");
296         }
297         strcat(real_name, name_ptr);
299         snprintf(filename, sizeof(filename), "%s/%s.so",
300                         PKGLIBDIR, real_name);
301         filename[sizeof(filename) - 1] = '\0';
303         if (access(filename, R_OK)) {
304                 char errbuf[1024];
305                 sdb_log(SDB_LOG_ERR, "plugin: Failed to load plugin '%s' (%s): %s",
306                                 name, filename, sdb_strerror(errno, errbuf, sizeof(errbuf)));
307                 return -1;
308         }
310         lt_dlinit();
311         lt_dlerror();
313         lh = lt_dlopen(filename);
314         if (! lh) {
315                 sdb_log(SDB_LOG_ERR, "plugin: Failed to load plugin '%s': %s"
316                                 "The most common cause for this problem are missing "
317                                 "dependencies.\n", name, lt_dlerror());
318                 return -1;
319         }
321         mod_init = (int (*)(sdb_plugin_info_t *))lt_dlsym(lh, "sdb_module_init");
322         if (! mod_init) {
323                 sdb_log(SDB_LOG_ERR, "plugin: Failed to load plugin '%s': "
324                                 "could not find symbol 'sdb_module_init'", name);
325                 return -1;
326         }
328         status = mod_init(&plugin_info);
329         if (status) {
330                 sdb_log(SDB_LOG_ERR, "plugin: Failed to initialize "
331                                 "plugin '%s'", name);
332                 return -1;
333         }
335         /* compare minor version */
336         if ((plugin_info.version < 0)
337                         || ((int)(plugin_info.version / 100) != (int)(SDB_VERSION / 100)))
338                 sdb_log(SDB_LOG_WARNING, "plugin: WARNING: version of "
339                                 "plugin '%s' (%i.%i.%i) does not match our version "
340                                 "(%i.%i.%i); this might cause problems",
341                                 name, SDB_VERSION_DECODE(plugin_info.version),
342                                 SDB_VERSION_DECODE(SDB_VERSION));
344         sdb_log(SDB_LOG_INFO, "plugin: Successfully loaded "
345                         "plugin '%s' v%i (%s)\n\t%s",
346                         plugin_info.name, plugin_info.plugin_version,
347                         plugin_info.description, plugin_info.copyright);
348         return 0;
349 } /* sdb_plugin_load */
351 int
352 sdb_plugin_set_info(sdb_plugin_info_t *info, int type, ...)
354         va_list ap;
356         if (! info)
357                 return -1;
359         va_start(ap, type);
361         switch (type) {
362                 case SDB_PLUGIN_INFO_NAME:
363                         {
364                                 char *name = va_arg(ap, char *);
365                                 info->name = name;
366                         }
367                         break;
368                 case SDB_PLUGIN_INFO_DESC:
369                         {
370                                 char *desc = va_arg(ap, char *);
371                                 info->description = desc;
372                         }
373                         break;
374                 case SDB_PLUGIN_INFO_COPYRIGHT:
375                         {
376                                 char *copyright = va_arg(ap, char *);
377                                 info->copyright = copyright;
378                         }
379                         break;
380                 case SDB_PLUGIN_INFO_LICENSE:
381                         {
382                                 char *license = va_arg(ap, char *);
383                                 info->license = license;
384                         }
385                         break;
386                 case SDB_PLUGIN_INFO_VERSION:
387                         {
388                                 int version = va_arg(ap, int);
389                                 info->version = version;
390                         }
391                         break;
392                 case SDB_PLUGIN_INFO_PLUGIN_VERSION:
393                         {
394                                 int version = va_arg(ap, int);
395                                 info->plugin_version = version;
396                         }
397                         break;
398                 default:
399                         va_end(ap);
400                         return -1;
401         }
403         va_end(ap);
404         return 0;
405 } /* sdb_plugin_set_info */
407 int
408 sdb_plugin_register_config(const char *name, sdb_plugin_config_cb callback)
410         return sdb_plugin_add_callback(&config_list, "init", name,
411                         callback, NULL);
412 } /* sdb_plugin_register_config */
414 int
415 sdb_plugin_register_init(const char *name, sdb_plugin_init_cb callback,
416                 sdb_object_t *user_data)
418         return sdb_plugin_add_callback(&init_list, "init", name,
419                         callback, user_data);
420 } /* sdb_plugin_register_init */
422 int
423 sdb_plugin_register_shutdown(const char *name, sdb_plugin_shutdown_cb callback,
424                 sdb_object_t *user_data)
426         return sdb_plugin_add_callback(&shutdown_list, "shutdown", name,
427                         callback, user_data);
428 } /* sdb_plugin_register_shutdown */
430 int
431 sdb_plugin_register_log(const char *name, sdb_plugin_log_cb callback,
432                 sdb_object_t *user_data)
434         return sdb_plugin_add_callback(&log_list, "log", name, callback,
435                         user_data);
436 } /* sdb_plugin_register_log */
438 int
439 sdb_plugin_register_collector(const char *name, sdb_plugin_collector_cb callback,
440                 const sdb_time_t *interval, sdb_object_t *user_data)
442         sdb_object_t *obj;
444         if ((! name) || (! callback))
445                 return -1;
447         if (! collector_list)
448                 collector_list = sdb_llist_create();
449         if (! collector_list)
450                 return -1;
452         obj = sdb_object_create(name, sdb_plugin_collector_cb_type,
453                         &collector_list, "collector", callback, user_data);
454         if (! obj)
455                 return -1;
457         if (interval)
458                 SDB_PLUGIN_CCB(obj)->ccb_interval = *interval;
459         else {
460                 sdb_time_t tmp = sdb_plugin_get_ctx().interval;
462                 if (tmp > 0)
463                         SDB_PLUGIN_CCB(obj)->ccb_interval = tmp;
464                 else
465                         SDB_PLUGIN_CCB(obj)->ccb_interval = 0;
466         }
468         if (! (SDB_PLUGIN_CCB(obj)->ccb_next_update = sdb_gettime())) {
469                 char errbuf[1024];
470                 sdb_log(SDB_LOG_ERR, "plugin: Failed to determine current "
471                                 "time: %s", sdb_strerror(errno, errbuf, sizeof(errbuf)));
472                 sdb_object_deref(obj);
473                 return -1;
474         }
476         if (sdb_llist_insert_sorted(collector_list, obj,
477                                 sdb_plugin_cmp_next_update)) {
478                 sdb_object_deref(obj);
479                 return -1;
480         }
482         /* pass control to the list */
483         sdb_object_deref(obj);
485         sdb_log(SDB_LOG_INFO, "plugin: Registered collector callback '%s' "
486                         "(interval = %.3fs).", name,
487                         SDB_TIME_TO_DOUBLE(SDB_PLUGIN_CCB(obj)->ccb_interval));
488         return 0;
489 } /* sdb_plugin_register_collector */
491 sdb_plugin_ctx_t
492 sdb_plugin_get_ctx(void)
494         sdb_plugin_ctx_t *ctx;
496         if (! plugin_ctx_key_initialized)
497                 sdb_plugin_ctx_init();
498         ctx = pthread_getspecific(plugin_ctx_key);
500         if (! ctx)
501                 ctx = sdb_plugin_ctx_create();
502         if (! ctx)
503                 return plugin_default_ctx;
504         return *ctx;
505 } /* sdb_plugin_get_ctx */
507 sdb_plugin_ctx_t
508 sdb_plugin_set_ctx(sdb_plugin_ctx_t ctx)
510         sdb_plugin_ctx_t *tmp;
511         sdb_plugin_ctx_t old;
513         if (! plugin_ctx_key_initialized)
514                 sdb_plugin_ctx_init();
515         tmp = pthread_getspecific(plugin_ctx_key);
517         if (! tmp)
518                 tmp = sdb_plugin_ctx_create();
519         if (! tmp)
520                 return plugin_default_ctx;
522         old = *tmp;
523         *tmp = ctx;
524         return old;
525 } /* sdb_plugin_set_ctx */
527 int
528 sdb_plugin_configure(const char *name, oconfig_item_t *ci)
530         sdb_plugin_cb_t *plugin;
531         sdb_plugin_config_cb callback;
533         sdb_plugin_ctx_t old_ctx;
535         int status;
537         if ((! name) || (! ci))
538                 return -1;
540         plugin = sdb_plugin_find_by_name(config_list, name);
541         if (! plugin) {
542                 /* XXX: check if any such plugin has been loaded */
543                 sdb_log(SDB_LOG_ERR, "plugin: Plugin '%s' did not register "
544                                 "a config callback.", name);
545                 errno = ENOENT;
546                 return -1;
547         }
549         old_ctx = sdb_plugin_set_ctx(plugin->cb_ctx);
550         callback = plugin->cb_callback;
551         status = callback(ci);
552         sdb_plugin_set_ctx(old_ctx);
553         return status;
554 } /* sdb_plugin_configure */
556 int
557 sdb_plugin_init_all(void)
559         sdb_llist_iter_t *iter;
561         iter = sdb_llist_get_iter(init_list);
562         while (sdb_llist_iter_has_next(iter)) {
563                 sdb_plugin_init_cb callback;
564                 sdb_plugin_ctx_t old_ctx;
566                 sdb_object_t *obj = sdb_llist_iter_get_next(iter);
567                 assert(obj);
569                 callback = SDB_PLUGIN_CB(obj)->cb_callback;
571                 old_ctx = sdb_plugin_set_ctx(SDB_PLUGIN_CB(obj)->cb_ctx);
572                 if (callback(SDB_PLUGIN_CB(obj)->cb_user_data)) {
573                         /* XXX: unload plugin */
574                 }
575                 sdb_plugin_set_ctx(old_ctx);
576         }
577         sdb_llist_iter_destroy(iter);
578         return 0;
579 } /* sdb_plugin_init_all */
581 int
582 sdb_plugin_collector_loop(sdb_plugin_loop_t *loop)
584         if (! collector_list) {
585                 sdb_log(SDB_LOG_WARNING, "plugin: No collectors registered. "
586                                 "Quiting main loop.");
587                 return -1;
588         }
590         if (! loop)
591                 return -1;
593         while (loop->do_loop) {
594                 sdb_plugin_collector_cb callback;
595                 sdb_plugin_ctx_t old_ctx;
597                 sdb_time_t interval, now;
599                 sdb_object_t *obj = sdb_llist_shift(collector_list);
600                 if (! obj)
601                         return -1;
603                 callback = SDB_PLUGIN_CCB(obj)->ccb_callback;
605                 if (! (now = sdb_gettime())) {
606                         char errbuf[1024];
607                         sdb_log(SDB_LOG_ERR, "plugin: Failed to determine current "
608                                         "time: %s", sdb_strerror(errno, errbuf, sizeof(errbuf)));
609                         now = SDB_PLUGIN_CCB(obj)->ccb_next_update;
610                 }
612                 if (now < SDB_PLUGIN_CCB(obj)->ccb_next_update) {
613                         interval = SDB_PLUGIN_CCB(obj)->ccb_next_update - now;
615                         errno = 0;
616                         while (loop->do_loop && sdb_sleep(interval, &interval)) {
617                                 if (errno != EINTR) {
618                                         char errbuf[1024];
619                                         sdb_log(SDB_LOG_ERR, "plugin: Failed to sleep: %s",
620                                                         sdb_strerror(errno, errbuf, sizeof(errbuf)));
621                                         return -1;
622                                 }
623                                 errno = 0;
624                         }
626                         if (! loop->do_loop)
627                                 return 0;
628                 }
630                 old_ctx = sdb_plugin_set_ctx(SDB_PLUGIN_CCB(obj)->ccb_ctx);
631                 if (callback(SDB_PLUGIN_CCB(obj)->ccb_user_data)) {
632                         /* XXX */
633                 }
634                 sdb_plugin_set_ctx(old_ctx);
636                 interval = SDB_PLUGIN_CCB(obj)->ccb_interval;
637                 if (! interval)
638                         interval = loop->default_interval;
639                 if (! interval) {
640                         sdb_log(SDB_LOG_WARNING, "plugin: No interval configured "
641                                         "for plugin '%s'; skipping any further "
642                                         "iterations.", obj->name);
643                         sdb_object_deref(obj);
644                         continue;
645                 }
647                 SDB_PLUGIN_CCB(obj)->ccb_next_update += interval;
649                 if (! (now = sdb_gettime())) {
650                         char errbuf[1024];
651                         sdb_log(SDB_LOG_ERR, "plugin: Failed to determine current "
652                                         "time: %s", sdb_strerror(errno, errbuf, sizeof(errbuf)));
653                         now = SDB_PLUGIN_CCB(obj)->ccb_next_update;
654                 }
656                 if (now > SDB_PLUGIN_CCB(obj)->ccb_next_update) {
657                         sdb_log(SDB_LOG_WARNING, "plugin: Plugin '%s' took too "
658                                         "long; skipping iterations to keep up.",
659                                         obj->name);
660                         SDB_PLUGIN_CCB(obj)->ccb_next_update = now;
661                 }
663                 if (sdb_llist_insert_sorted(collector_list, obj,
664                                         sdb_plugin_cmp_next_update)) {
665                         sdb_log(SDB_LOG_ERR, "plugin: Failed to re-insert "
666                                         "plugin '%s' into collector list. Unable to further "
667                                         "use the plugin.",
668                                         obj->name);
669                         sdb_object_deref(obj);
670                         return -1;
671                 }
673                 /* pass control back to the list */
674                 sdb_object_deref(obj);
675         }
676         return 0;
677 } /* sdb_plugin_read_loop */
679 int
680 sdb_plugin_log(int prio, const char *msg)
682         sdb_llist_iter_t *iter;
683         int ret = -1;
685         if (! log_list)
686                 return fprintf(stderr, "[%s] %s\n", SDB_LOG_PRIO_TO_STRING(prio), msg);
688         iter = sdb_llist_get_iter(log_list);
689         while (sdb_llist_iter_has_next(iter)) {
690                 sdb_plugin_log_cb callback;
691                 int tmp;
693                 sdb_object_t *obj = sdb_llist_iter_get_next(iter);
694                 assert(obj);
696                 callback = SDB_PLUGIN_CB(obj)->cb_callback;
697                 tmp = callback(prio, msg, SDB_PLUGIN_CB(obj)->cb_user_data);
698                 if (tmp > ret)
699                         ret = tmp;
700         }
701         sdb_llist_iter_destroy(iter);
702         return ret;
703 } /* sdb_plugin_log */
705 /* vim: set tw=78 sw=4 ts=4 noexpandtab : */