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 #if HAVE_CONFIG_H
29 # include "config.h"
30 #endif /* HAVE_CONFIG_H */
32 #include "sysdb.h"
33 #include "core/plugin.h"
34 #include "core/time.h"
35 #include "utils/error.h"
36 #include "utils/llist.h"
37 #include "utils/strbuf.h"
39 #include <assert.h>
41 #include <errno.h>
43 #include <stdarg.h>
44 #include <stdio.h>
45 #include <string.h>
46 #include <strings.h>
47 #include <unistd.h>
49 #include <ltdl.h>
51 #include <pthread.h>
53 /* helper to access info attributes */
54 #define INFO_GET(i, attr) \
55 ((i)->attr ? (i)->attr : #attr" not set")
57 /*
58 * private data types
59 */
61 typedef struct {
62 sdb_object_t super;
63 sdb_plugin_ctx_t public;
65 sdb_plugin_info_t info;
66 lt_dlhandle handle;
68 /* The usage count differs from the object's ref count
69 * in that it provides higher level information about how
70 * the plugin is in use. */
71 size_t use_cnt;
72 } ctx_t;
73 #define CTX_INIT { SDB_OBJECT_INIT, \
74 SDB_PLUGIN_CTX_INIT, SDB_PLUGIN_INFO_INIT, NULL, 0 }
76 #define CTX(obj) ((ctx_t *)(obj))
78 typedef struct {
79 sdb_object_t super;
80 void *cb_callback;
81 sdb_object_t *cb_user_data;
82 ctx_t *cb_ctx;
83 } sdb_plugin_cb_t;
84 #define SDB_PLUGIN_CB_INIT { SDB_OBJECT_INIT, \
85 /* callback = */ NULL, /* user_data = */ NULL, \
86 SDB_PLUGIN_CTX_INIT }
88 typedef struct {
89 sdb_plugin_cb_t super;
90 #define ccb_callback super.cb_callback
91 #define ccb_user_data super.cb_user_data
92 #define ccb_ctx super.cb_ctx
93 sdb_time_t ccb_interval;
94 sdb_time_t ccb_next_update;
95 } sdb_plugin_collector_cb_t;
97 #define SDB_PLUGIN_CB(obj) ((sdb_plugin_cb_t *)(obj))
98 #define SDB_CONST_PLUGIN_CB(obj) ((const sdb_plugin_cb_t *)(obj))
99 #define SDB_PLUGIN_CCB(obj) ((sdb_plugin_collector_cb_t *)(obj))
100 #define SDB_CONST_PLUGIN_CCB(obj) ((const sdb_plugin_collector_cb_t *)(obj))
102 /*
103 * private variables
104 */
106 static sdb_plugin_ctx_t plugin_default_ctx = SDB_PLUGIN_CTX_INIT;
107 static sdb_plugin_info_t plugin_default_info = SDB_PLUGIN_INFO_INIT;
109 static pthread_key_t plugin_ctx_key;
110 static bool plugin_ctx_key_initialized = 0;
112 /* a list of the plugin contexts of all registered plugins */
113 static sdb_llist_t *all_plugins = NULL;
115 static sdb_llist_t *config_list = NULL;
116 static sdb_llist_t *init_list = NULL;
117 static sdb_llist_t *collector_list = NULL;
118 static sdb_llist_t *cname_list = NULL;
119 static sdb_llist_t *shutdown_list = NULL;
120 static sdb_llist_t *log_list = NULL;
121 static sdb_llist_t *ts_fetcher_list = NULL;
123 static struct {
124 const char *type;
125 sdb_llist_t **list;
126 } all_lists[] = {
127 { "config", &config_list },
128 { "init", &init_list },
129 { "collector", &collector_list },
130 { "cname", &cname_list },
131 { "shutdown", &shutdown_list },
132 { "log", &log_list },
133 { "timeseries fetcher", &ts_fetcher_list },
134 };
136 /*
137 * private helper functions
138 */
140 static void
141 plugin_info_clear(sdb_plugin_info_t *info)
142 {
143 sdb_plugin_info_t empty_info = SDB_PLUGIN_INFO_INIT;
144 if (! info)
145 return;
147 if (info->plugin_name)
148 free(info->plugin_name);
149 if (info->filename)
150 free(info->filename);
152 if (info->description)
153 free(info->description);
154 if (info->copyright)
155 free(info->copyright);
156 if (info->license)
157 free(info->license);
159 *info = empty_info;
160 } /* plugin_info_clear */
162 static void
163 ctx_key_init(void)
164 {
165 if (plugin_ctx_key_initialized)
166 return;
168 pthread_key_create(&plugin_ctx_key, /* destructor */ NULL);
169 plugin_ctx_key_initialized = 1;
170 } /* ctx_key_init */
172 static int
173 plugin_cmp_next_update(const sdb_object_t *a, const sdb_object_t *b)
174 {
175 const sdb_plugin_collector_cb_t *ccb1
176 = (const sdb_plugin_collector_cb_t *)a;
177 const sdb_plugin_collector_cb_t *ccb2
178 = (const sdb_plugin_collector_cb_t *)b;
180 assert(ccb1 && ccb2);
182 return (ccb1->ccb_next_update > ccb2->ccb_next_update)
183 ? 1 : (ccb1->ccb_next_update < ccb2->ccb_next_update)
184 ? -1 : 0;
185 } /* plugin_cmp_next_update */
187 static int
188 plugin_lookup_by_name(const sdb_object_t *obj, const void *id)
189 {
190 const sdb_plugin_cb_t *cb = SDB_CONST_PLUGIN_CB(obj);
191 const char *name = id;
193 assert(cb && id);
195 /* when a plugin was registered from outside a plugin (e.g. the core),
196 * we don't have a plugin context */
197 if (! cb->cb_ctx)
198 return 1;
200 if (!strcasecmp(cb->cb_ctx->info.plugin_name, name))
201 return 0;
202 return 1;
203 } /* plugin_lookup_by_name */
205 /* since this function is called from sdb_plugin_reconfigure_finish()
206 * when iterating through all_plugins, we may not do any additional
207 * modifications to all_plugins except for the optional removal */
208 static void
209 plugin_unregister_by_name(const char *plugin_name)
210 {
211 sdb_object_t *obj;
212 size_t i;
214 for (i = 0; i < SDB_STATIC_ARRAY_LEN(all_lists); ++i) {
215 const char *type = all_lists[i].type;
216 sdb_llist_t *list = *all_lists[i].list;
218 while (1) {
219 sdb_plugin_cb_t *cb;
221 cb = SDB_PLUGIN_CB(sdb_llist_remove(list,
222 plugin_lookup_by_name, plugin_name));
223 if (! cb)
224 break;
226 assert(cb->cb_ctx);
228 sdb_log(SDB_LOG_INFO, "core: Unregistering "
229 "%s callback '%s' (module %s)", type, cb->super.name,
230 cb->cb_ctx->info.plugin_name);
231 sdb_object_deref(SDB_OBJ(cb));
232 }
233 }
235 obj = sdb_llist_search_by_name(all_plugins, plugin_name);
236 /* when called from sdb_plugin_reconfigure_finish, the object has already
237 * been removed from the list */
238 if (obj && (obj->ref_cnt <= 1)) {
239 sdb_llist_remove_by_name(all_plugins, plugin_name);
240 sdb_object_deref(obj);
241 }
242 /* else: other callbacks still reference it */
243 } /* plugin_unregister_by_name */
245 static void
246 plugin_unregister_all(void)
247 {
248 size_t i;
250 for (i = 0; i < SDB_STATIC_ARRAY_LEN(all_lists); ++i) {
251 const char *type = all_lists[i].type;
252 sdb_llist_t *list = *all_lists[i].list;
254 size_t len = sdb_llist_len(list);
256 if (! len)
257 continue;
259 sdb_llist_clear(list);
260 sdb_log(SDB_LOG_INFO, "core: Unregistered %zu %s callback%s",
261 len, type, len == 1 ? "" : "s");
262 }
263 } /* plugin_unregister_all */
265 /*
266 * private types
267 */
269 static int
270 ctx_init(sdb_object_t *obj, va_list __attribute__((unused)) ap)
271 {
272 ctx_t *ctx = CTX(obj);
274 assert(ctx);
276 ctx->public = plugin_default_ctx;
277 ctx->info = plugin_default_info;
278 ctx->handle = NULL;
279 ctx->use_cnt = 1;
280 return 0;
281 } /* ctx_init */
283 static void
284 ctx_destroy(sdb_object_t *obj)
285 {
286 ctx_t *ctx = CTX(obj);
288 if (ctx->handle) {
289 const char *err;
291 sdb_log(SDB_LOG_INFO, "core: Unloading module %s",
292 ctx->info.plugin_name);
294 lt_dlerror();
295 lt_dlclose(ctx->handle);
296 if ((err = lt_dlerror()))
297 sdb_log(SDB_LOG_WARNING, "core: Failed to unload module %s: %s",
298 ctx->info.plugin_name, err);
299 }
301 plugin_info_clear(&ctx->info);
302 } /* ctx_destroy */
304 static sdb_type_t ctx_type = {
305 sizeof(ctx_t),
307 ctx_init,
308 ctx_destroy
309 };
311 static ctx_t *
312 ctx_get(void)
313 {
314 if (! plugin_ctx_key_initialized)
315 ctx_key_init();
316 return pthread_getspecific(plugin_ctx_key);
317 } /* ctx_get */
319 static ctx_t *
320 ctx_set(ctx_t *new)
321 {
322 ctx_t *old;
324 if (! plugin_ctx_key_initialized)
325 ctx_key_init();
327 old = pthread_getspecific(plugin_ctx_key);
328 if (old)
329 sdb_object_deref(SDB_OBJ(old));
330 if (new)
331 sdb_object_ref(SDB_OBJ(new));
332 pthread_setspecific(plugin_ctx_key, new);
333 return old;
334 } /* ctx_set */
336 static ctx_t *
337 ctx_create(const char *name)
338 {
339 ctx_t *ctx;
341 ctx = CTX(sdb_object_create(name, ctx_type));
342 if (! ctx)
343 return NULL;
345 if (! plugin_ctx_key_initialized)
346 ctx_key_init();
347 ctx_set(ctx);
348 return ctx;
349 } /* ctx_create */
351 static int
352 plugin_cb_init(sdb_object_t *obj, va_list ap)
353 {
354 sdb_llist_t **list = va_arg(ap, sdb_llist_t **);
355 const char *type = va_arg(ap, const char *);
356 void *callback = va_arg(ap, void *);
357 sdb_object_t *ud = va_arg(ap, sdb_object_t *);
359 assert(list);
360 assert(type);
361 assert(obj);
363 if (sdb_llist_search_by_name(*list, obj->name)) {
364 sdb_log(SDB_LOG_WARNING, "core: %s callback '%s' "
365 "has already been registered. Ignoring newly "
366 "registered version.", type, obj->name);
367 return -1;
368 }
370 /* cb_ctx may be NULL if the plugin was not registered by a plugin */
372 SDB_PLUGIN_CB(obj)->cb_callback = callback;
373 SDB_PLUGIN_CB(obj)->cb_ctx = ctx_get();
374 sdb_object_ref(SDB_OBJ(SDB_PLUGIN_CB(obj)->cb_ctx));
376 sdb_object_ref(ud);
377 SDB_PLUGIN_CB(obj)->cb_user_data = ud;
378 return 0;
379 } /* plugin_cb_init */
381 static void
382 plugin_cb_destroy(sdb_object_t *obj)
383 {
384 assert(obj);
385 sdb_object_deref(SDB_PLUGIN_CB(obj)->cb_user_data);
386 sdb_object_deref(SDB_OBJ(SDB_PLUGIN_CB(obj)->cb_ctx));
387 } /* plugin_cb_destroy */
389 static sdb_type_t sdb_plugin_cb_type = {
390 sizeof(sdb_plugin_cb_t),
392 plugin_cb_init,
393 plugin_cb_destroy
394 };
396 static sdb_type_t sdb_plugin_collector_cb_type = {
397 sizeof(sdb_plugin_collector_cb_t),
399 plugin_cb_init,
400 plugin_cb_destroy
401 };
403 static int
404 module_init(const char *name, lt_dlhandle lh, sdb_plugin_info_t *info)
405 {
406 int (*mod_init)(sdb_plugin_info_t *);
407 int status;
409 mod_init = (int (*)(sdb_plugin_info_t *))lt_dlsym(lh, "sdb_module_init");
410 if (! mod_init) {
411 sdb_log(SDB_LOG_ERR, "core: Failed to load plugin '%s': "
412 "could not find symbol 'sdb_module_init'", name);
413 return -1;
414 }
416 status = mod_init(info);
417 if (status) {
418 sdb_log(SDB_LOG_ERR, "core: Failed to initialize "
419 "module '%s'", name);
420 plugin_unregister_by_name(name);
421 return -1;
422 }
423 return 0;
424 } /* module_init */
426 static int
427 module_load(const char *basedir, const char *name,
428 const sdb_plugin_ctx_t *plugin_ctx)
429 {
430 char base_name[name ? strlen(name) + 1 : 1];
431 const char *name_ptr;
432 char *tmp;
434 char filename[1024];
435 lt_dlhandle lh;
437 ctx_t *ctx;
439 int status;
441 assert(name);
443 base_name[0] = '\0';
444 name_ptr = name;
446 while ((tmp = strstr(name_ptr, "::"))) {
447 strncat(base_name, name_ptr, (size_t)(tmp - name_ptr));
448 strcat(base_name, "/");
449 name_ptr = tmp + strlen("::");
450 }
451 strcat(base_name, name_ptr);
453 if (! basedir)
454 basedir = PKGLIBDIR;
456 snprintf(filename, sizeof(filename), "%s/%s.so", basedir, base_name);
457 filename[sizeof(filename) - 1] = '\0';
459 if (access(filename, R_OK)) {
460 char errbuf[1024];
461 sdb_log(SDB_LOG_ERR, "core: Failed to load plugin '%s' (%s): %s",
462 name, filename, sdb_strerror(errno, errbuf, sizeof(errbuf)));
463 return -1;
464 }
466 lt_dlinit();
467 lt_dlerror();
469 lh = lt_dlopen(filename);
470 if (! lh) {
471 sdb_log(SDB_LOG_ERR, "core: Failed to load plugin '%s': %s"
472 "The most common cause for this problem are missing "
473 "dependencies.\n", name, lt_dlerror());
474 return -1;
475 }
477 if (ctx_get())
478 sdb_log(SDB_LOG_WARNING, "core: Discarding old plugin context");
480 ctx = ctx_create(name);
481 if (! ctx) {
482 sdb_log(SDB_LOG_ERR, "core: Failed to initialize plugin context");
483 return -1;
484 }
486 ctx->info.plugin_name = strdup(name);
487 ctx->info.filename = strdup(filename);
488 ctx->handle = lh;
490 if (plugin_ctx)
491 ctx->public = *plugin_ctx;
493 if ((status = module_init(name, lh, &ctx->info))) {
494 sdb_object_deref(SDB_OBJ(ctx));
495 return status;
496 }
498 /* compare minor version */
499 if ((ctx->info.version < 0)
500 || ((int)(ctx->info.version / 100) != (int)(SDB_VERSION / 100)))
501 sdb_log(SDB_LOG_WARNING, "core: WARNING: version of "
502 "plugin '%s' (%i.%i.%i) does not match our version "
503 "(%i.%i.%i); this might cause problems",
504 name, SDB_VERSION_DECODE(ctx->info.version),
505 SDB_VERSION_DECODE(SDB_VERSION));
507 sdb_llist_append(all_plugins, SDB_OBJ(ctx));
509 sdb_log(SDB_LOG_INFO, "core: Successfully loaded "
510 "plugin %s v%i (%s)", ctx->info.plugin_name,
511 ctx->info.plugin_version,
512 INFO_GET(&ctx->info, description));
513 sdb_log(SDB_LOG_INFO, "core: Plugin %s: %s, License: %s",
514 ctx->info.plugin_name,
515 INFO_GET(&ctx->info, copyright),
516 INFO_GET(&ctx->info, license));
518 /* any registered callbacks took ownership of the context */
519 sdb_object_deref(SDB_OBJ(ctx));
521 /* reset */
522 ctx_set(NULL);
523 return 0;
524 } /* module_load */
526 static char *
527 plugin_get_name(const char *name, char *buf, size_t bufsize)
528 {
529 ctx_t *ctx = ctx_get();
531 if (ctx)
532 snprintf(buf, bufsize, "%s::%s", ctx->info.plugin_name, name);
533 else
534 snprintf(buf, bufsize, "core::%s", name);
535 return buf;
536 } /* plugin_get_name */
538 static int
539 plugin_add_callback(sdb_llist_t **list, const char *type,
540 const char *name, void *callback, sdb_object_t *user_data)
541 {
542 sdb_object_t *obj;
544 if ((! name) || (! callback))
545 return -1;
547 assert(list);
549 if (! *list)
550 *list = sdb_llist_create();
551 if (! *list)
552 return -1;
554 obj = sdb_object_create(name, sdb_plugin_cb_type,
555 list, type, callback, user_data);
556 if (! obj)
557 return -1;
559 if (sdb_llist_append(*list, obj)) {
560 sdb_object_deref(obj);
561 return -1;
562 }
564 /* pass control to the list */
565 sdb_object_deref(obj);
567 sdb_log(SDB_LOG_INFO, "core: Registered %s callback '%s'.",
568 type, name);
569 return 0;
570 } /* plugin_add_callback */
572 /*
573 * public API
574 */
576 int
577 sdb_plugin_load(const char *basedir, const char *name,
578 const sdb_plugin_ctx_t *plugin_ctx)
579 {
580 ctx_t *ctx;
582 int status;
584 if ((! name) || (! *name))
585 return -1;
587 if (! all_plugins) {
588 if (! (all_plugins = sdb_llist_create())) {
589 sdb_log(SDB_LOG_ERR, "core: Failed to load plugin '%s': "
590 "internal error while creating linked list", name);
591 return -1;
592 }
593 }
595 ctx = CTX(sdb_llist_search_by_name(all_plugins, name));
596 if (ctx) {
597 /* plugin already loaded */
598 if (! ctx->use_cnt) {
599 /* reloading plugin */
600 ctx_t *old_ctx = ctx_set(ctx);
602 status = module_init(ctx->info.plugin_name, ctx->handle, NULL);
603 if (status)
604 return status;
606 sdb_log(SDB_LOG_INFO, "core: Successfully reloaded plugin "
607 "'%s' (%s)", ctx->info.plugin_name,
608 INFO_GET(&ctx->info, description));
609 ctx_set(old_ctx);
610 }
611 ++ctx->use_cnt;
612 return 0;
613 }
615 return module_load(basedir, name, plugin_ctx);
616 } /* sdb_plugin_load */
618 int
619 sdb_plugin_set_info(sdb_plugin_info_t *info, int type, ...)
620 {
621 va_list ap;
623 if (! info)
624 return -1;
626 va_start(ap, type);
628 switch (type) {
629 case SDB_PLUGIN_INFO_DESC:
630 {
631 char *desc = va_arg(ap, char *);
632 if (desc) {
633 if (info->description)
634 free(info->description);
635 info->description = strdup(desc);
636 }
637 }
638 break;
639 case SDB_PLUGIN_INFO_COPYRIGHT:
640 {
641 char *copyright = va_arg(ap, char *);
642 if (copyright)
643 info->copyright = strdup(copyright);
644 }
645 break;
646 case SDB_PLUGIN_INFO_LICENSE:
647 {
648 char *license = va_arg(ap, char *);
649 if (license) {
650 if (info->license)
651 free(info->license);
652 info->license = strdup(license);
653 }
654 }
655 break;
656 case SDB_PLUGIN_INFO_VERSION:
657 {
658 int version = va_arg(ap, int);
659 info->version = version;
660 }
661 break;
662 case SDB_PLUGIN_INFO_PLUGIN_VERSION:
663 {
664 int version = va_arg(ap, int);
665 info->plugin_version = version;
666 }
667 break;
668 default:
669 va_end(ap);
670 return -1;
671 }
673 va_end(ap);
674 return 0;
675 } /* sdb_plugin_set_info */
677 int
678 sdb_plugin_register_config(sdb_plugin_config_cb callback)
679 {
680 ctx_t *ctx = ctx_get();
682 if (! ctx) {
683 sdb_log(SDB_LOG_ERR, "core: Invalid attempt to register a "
684 "config callback from outside a plugin");
685 return -1;
686 }
687 return plugin_add_callback(&config_list, "init", ctx->info.plugin_name,
688 (void *)callback, NULL);
689 } /* sdb_plugin_register_config */
691 int
692 sdb_plugin_register_init(const char *name, sdb_plugin_init_cb callback,
693 sdb_object_t *user_data)
694 {
695 char cb_name[1024];
696 return plugin_add_callback(&init_list, "init",
697 plugin_get_name(name, cb_name, sizeof(cb_name)),
698 (void *)callback, user_data);
699 } /* sdb_plugin_register_init */
701 int
702 sdb_plugin_register_shutdown(const char *name, sdb_plugin_shutdown_cb callback,
703 sdb_object_t *user_data)
704 {
705 char cb_name[1024];
706 return plugin_add_callback(&shutdown_list, "shutdown",
707 plugin_get_name(name, cb_name, sizeof(cb_name)),
708 (void *)callback, user_data);
709 } /* sdb_plugin_register_shutdown */
711 int
712 sdb_plugin_register_log(const char *name, sdb_plugin_log_cb callback,
713 sdb_object_t *user_data)
714 {
715 char cb_name[1024];
716 return plugin_add_callback(&log_list, "log",
717 plugin_get_name(name, cb_name, sizeof(cb_name)),
718 callback, user_data);
719 } /* sdb_plugin_register_log */
721 int
722 sdb_plugin_register_cname(const char *name, sdb_plugin_cname_cb callback,
723 sdb_object_t *user_data)
724 {
725 char cb_name[1024];
726 return plugin_add_callback(&cname_list, "cname",
727 plugin_get_name(name, cb_name, sizeof(cb_name)),
728 callback, user_data);
729 } /* sdb_plugin_register_cname */
731 int
732 sdb_plugin_register_collector(const char *name, sdb_plugin_collector_cb callback,
733 const sdb_time_t *interval, sdb_object_t *user_data)
734 {
735 char cb_name[1024];
736 sdb_object_t *obj;
738 if ((! name) || (! callback))
739 return -1;
741 if (! collector_list)
742 collector_list = sdb_llist_create();
743 if (! collector_list)
744 return -1;
746 plugin_get_name(name, cb_name, sizeof(cb_name));
748 obj = sdb_object_create(cb_name, sdb_plugin_collector_cb_type,
749 &collector_list, "collector", callback, user_data);
750 if (! obj)
751 return -1;
753 if (interval)
754 SDB_PLUGIN_CCB(obj)->ccb_interval = *interval;
755 else {
756 ctx_t *ctx = ctx_get();
758 if (! ctx) {
759 sdb_log(SDB_LOG_ERR, "core: Cannot determine interval "
760 "for collector %s; none specified and no plugin "
761 "context found", cb_name);
762 return -1;
763 }
765 SDB_PLUGIN_CCB(obj)->ccb_interval = ctx->public.interval;
766 }
768 if (! (SDB_PLUGIN_CCB(obj)->ccb_next_update = sdb_gettime())) {
769 char errbuf[1024];
770 sdb_log(SDB_LOG_ERR, "core: Failed to determine current "
771 "time: %s", sdb_strerror(errno, errbuf, sizeof(errbuf)));
772 sdb_object_deref(obj);
773 return -1;
774 }
776 if (sdb_llist_insert_sorted(collector_list, obj,
777 plugin_cmp_next_update)) {
778 sdb_object_deref(obj);
779 return -1;
780 }
782 /* pass control to the list */
783 sdb_object_deref(obj);
785 sdb_log(SDB_LOG_INFO, "core: Registered collector callback '%s' "
786 "(interval = %.3fs).", cb_name,
787 SDB_TIME_TO_DOUBLE(SDB_PLUGIN_CCB(obj)->ccb_interval));
788 return 0;
789 } /* sdb_plugin_register_collector */
791 int
792 sdb_plugin_register_ts_fetcher(const char *name,
793 sdb_plugin_fetch_ts_cb callback, sdb_object_t *user_data)
794 {
795 return plugin_add_callback(&ts_fetcher_list, "time-series fetcher",
796 name, callback, user_data);
797 } /* sdb_plugin_register_ts_fetcher */
799 sdb_plugin_ctx_t
800 sdb_plugin_get_ctx(void)
801 {
802 ctx_t *c;
804 c = ctx_get();
805 if (! c) {
806 sdb_plugin_log(SDB_LOG_ERR, "core: Invalid read access to plugin "
807 "context outside a plugin");
808 return plugin_default_ctx;
809 }
810 return c->public;
811 } /* sdb_plugin_get_ctx */
813 int
814 sdb_plugin_set_ctx(sdb_plugin_ctx_t ctx, sdb_plugin_ctx_t *old)
815 {
816 ctx_t *c;
818 c = ctx_get();
819 if (! c) {
820 sdb_plugin_log(SDB_LOG_ERR, "core: Invalid write access to plugin "
821 "context outside a plugin");
822 return -1;
823 }
825 if (old)
826 *old = c->public;
827 c->public = ctx;
828 return 0;
829 } /* sdb_plugin_set_ctx */
831 const sdb_plugin_info_t *
832 sdb_plugin_current(void)
833 {
834 ctx_t *ctx = ctx_get();
836 if (! ctx)
837 return NULL;
838 return &ctx->info;
839 } /* sdb_plugin_current */
841 int
842 sdb_plugin_configure(const char *name, oconfig_item_t *ci)
843 {
844 sdb_plugin_cb_t *plugin;
845 sdb_plugin_config_cb callback;
847 ctx_t *old_ctx;
849 int status;
851 if ((! name) || (! ci))
852 return -1;
854 plugin = SDB_PLUGIN_CB(sdb_llist_search_by_name(config_list, name));
855 if (! plugin) {
856 ctx_t *ctx = CTX(sdb_llist_search_by_name(all_plugins, name));
857 if (! ctx)
858 sdb_log(SDB_LOG_ERR, "core: Cannot configure unknown "
859 "plugin '%s'. Missing 'LoadPlugin \"%s\"'?",
860 name, name);
861 else
862 sdb_log(SDB_LOG_ERR, "core: Plugin '%s' did not register "
863 "a config callback.", name);
864 errno = ENOENT;
865 return -1;
866 }
868 old_ctx = ctx_set(plugin->cb_ctx);
869 callback = (sdb_plugin_config_cb)plugin->cb_callback;
870 status = callback(ci);
871 ctx_set(old_ctx);
872 return status;
873 } /* sdb_plugin_configure */
875 int
876 sdb_plugin_reconfigure_init(void)
877 {
878 sdb_llist_iter_t *iter;
880 iter = sdb_llist_get_iter(config_list);
881 if (config_list && (! iter))
882 return -1;
884 /* deconfigure all plugins */
885 while (sdb_llist_iter_has_next(iter)) {
886 sdb_plugin_cb_t *plugin;
887 sdb_plugin_config_cb callback;
888 ctx_t *old_ctx;
890 plugin = SDB_PLUGIN_CB(sdb_llist_iter_get_next(iter));
891 old_ctx = ctx_set(plugin->cb_ctx);
892 callback = (sdb_plugin_config_cb)plugin->cb_callback;
893 callback(NULL);
894 ctx_set(old_ctx);
895 }
896 sdb_llist_iter_destroy(iter);
898 iter = sdb_llist_get_iter(all_plugins);
899 if (all_plugins && (! iter))
900 return -1;
902 /* record all plugins as being unused */
903 while (sdb_llist_iter_has_next(iter))
904 CTX(sdb_llist_iter_get_next(iter))->use_cnt = 0;
905 sdb_llist_iter_destroy(iter);
907 plugin_unregister_all();
908 return 0;
909 } /* sdb_plugin_reconfigure_init */
911 int
912 sdb_plugin_reconfigure_finish(void)
913 {
914 sdb_llist_iter_t *iter;
916 iter = sdb_llist_get_iter(all_plugins);
917 if (all_plugins && (! iter))
918 return -1;
920 while (sdb_llist_iter_has_next(iter)) {
921 ctx_t *ctx = CTX(sdb_llist_iter_get_next(iter));
922 if (ctx->use_cnt)
923 continue;
925 sdb_log(SDB_LOG_INFO, "core: Module %s no longer in use",
926 ctx->info.plugin_name);
927 sdb_llist_iter_remove_current(iter);
928 plugin_unregister_by_name(ctx->info.plugin_name);
929 sdb_object_deref(SDB_OBJ(ctx));
930 }
931 sdb_llist_iter_destroy(iter);
932 return 0;
933 } /* sdb_plugin_reconfigure_finish */
935 int
936 sdb_plugin_init_all(void)
937 {
938 sdb_llist_iter_t *iter;
939 int ret = 0;
941 iter = sdb_llist_get_iter(init_list);
942 while (sdb_llist_iter_has_next(iter)) {
943 sdb_plugin_cb_t *cb;
944 sdb_plugin_init_cb callback;
945 ctx_t *old_ctx;
947 sdb_object_t *obj = sdb_llist_iter_get_next(iter);
948 assert(obj);
949 cb = SDB_PLUGIN_CB(obj);
951 callback = (sdb_plugin_init_cb)cb->cb_callback;
953 old_ctx = ctx_set(cb->cb_ctx);
954 if (callback(cb->cb_user_data)) {
955 sdb_log(SDB_LOG_ERR, "core: Failed to initialize plugin "
956 "'%s'. Unregistering all callbacks.", obj->name);
957 ctx_set(old_ctx);
958 plugin_unregister_by_name(cb->cb_ctx->info.plugin_name);
959 ++ret;
960 }
961 else
962 ctx_set(old_ctx);
963 }
964 sdb_llist_iter_destroy(iter);
965 return ret;
966 } /* sdb_plugin_init_all */
968 int
969 sdb_plugin_shutdown_all(void)
970 {
971 sdb_llist_iter_t *iter;
972 int ret = 0;
974 iter = sdb_llist_get_iter(shutdown_list);
975 while (sdb_llist_iter_has_next(iter)) {
976 sdb_plugin_cb_t *cb;
977 sdb_plugin_shutdown_cb callback;
978 ctx_t *old_ctx;
980 sdb_object_t *obj = sdb_llist_iter_get_next(iter);
981 assert(obj);
982 cb = SDB_PLUGIN_CB(obj);
984 callback = (sdb_plugin_shutdown_cb)cb->cb_callback;
986 old_ctx = ctx_set(cb->cb_ctx);
987 if (callback(cb->cb_user_data)) {
988 sdb_log(SDB_LOG_ERR, "core: Failed to shutdown plugin '%s'.",
989 obj->name);
990 ++ret;
991 }
992 ctx_set(old_ctx);
993 }
994 sdb_llist_iter_destroy(iter);
995 return ret;
996 } /* sdb_plugin_shutdown_all */
998 int
999 sdb_plugin_collector_loop(sdb_plugin_loop_t *loop)
1000 {
1001 if (! collector_list) {
1002 sdb_log(SDB_LOG_WARNING, "core: No collectors registered. "
1003 "Quiting main loop.");
1004 return -1;
1005 }
1007 if (! loop)
1008 return -1;
1010 while (loop->do_loop) {
1011 sdb_plugin_collector_cb callback;
1012 ctx_t *old_ctx;
1014 sdb_time_t interval, now;
1016 sdb_object_t *obj = sdb_llist_shift(collector_list);
1017 if (! obj)
1018 return -1;
1020 callback = (sdb_plugin_collector_cb)SDB_PLUGIN_CCB(obj)->ccb_callback;
1022 if (! (now = sdb_gettime())) {
1023 char errbuf[1024];
1024 sdb_log(SDB_LOG_ERR, "core: Failed to determine current "
1025 "time in collector main loop: %s",
1026 sdb_strerror(errno, errbuf, sizeof(errbuf)));
1027 now = SDB_PLUGIN_CCB(obj)->ccb_next_update;
1028 }
1030 if (now < SDB_PLUGIN_CCB(obj)->ccb_next_update) {
1031 interval = SDB_PLUGIN_CCB(obj)->ccb_next_update - now;
1033 errno = 0;
1034 while (loop->do_loop && sdb_sleep(interval, &interval)) {
1035 if (errno != EINTR) {
1036 char errbuf[1024];
1037 sdb_log(SDB_LOG_ERR, "core: Failed to sleep "
1038 "in collector main loop: %s",
1039 sdb_strerror(errno, errbuf, sizeof(errbuf)));
1040 sdb_llist_insert_sorted(collector_list, obj,
1041 plugin_cmp_next_update);
1042 sdb_object_deref(obj);
1043 return -1;
1044 }
1045 errno = 0;
1046 }
1048 if (! loop->do_loop) {
1049 /* put back; don't worry about errors */
1050 sdb_llist_insert_sorted(collector_list, obj,
1051 plugin_cmp_next_update);
1052 sdb_object_deref(obj);
1053 return 0;
1054 }
1055 }
1057 old_ctx = ctx_set(SDB_PLUGIN_CCB(obj)->ccb_ctx);
1058 if (callback(SDB_PLUGIN_CCB(obj)->ccb_user_data)) {
1059 /* XXX */
1060 }
1061 ctx_set(old_ctx);
1063 interval = SDB_PLUGIN_CCB(obj)->ccb_interval;
1064 if (! interval)
1065 interval = loop->default_interval;
1066 if (! interval) {
1067 sdb_log(SDB_LOG_WARNING, "core: No interval configured "
1068 "for plugin '%s'; skipping any further "
1069 "iterations.", obj->name);
1070 sdb_object_deref(obj);
1071 continue;
1072 }
1074 SDB_PLUGIN_CCB(obj)->ccb_next_update += interval;
1076 if (! (now = sdb_gettime())) {
1077 char errbuf[1024];
1078 sdb_log(SDB_LOG_ERR, "core: Failed to determine current "
1079 "time in collector main loop: %s",
1080 sdb_strerror(errno, errbuf, sizeof(errbuf)));
1081 now = SDB_PLUGIN_CCB(obj)->ccb_next_update;
1082 }
1084 if (now > SDB_PLUGIN_CCB(obj)->ccb_next_update) {
1085 sdb_log(SDB_LOG_WARNING, "core: Plugin '%s' took too "
1086 "long; skipping iterations to keep up.",
1087 obj->name);
1088 SDB_PLUGIN_CCB(obj)->ccb_next_update = now;
1089 }
1091 if (sdb_llist_insert_sorted(collector_list, obj,
1092 plugin_cmp_next_update)) {
1093 sdb_log(SDB_LOG_ERR, "core: Failed to re-insert "
1094 "plugin '%s' into collector list. Unable to further "
1095 "use the plugin.",
1096 obj->name);
1097 sdb_object_deref(obj);
1098 return -1;
1099 }
1101 /* pass control back to the list */
1102 sdb_object_deref(obj);
1103 }
1104 return 0;
1105 } /* sdb_plugin_read_loop */
1107 char *
1108 sdb_plugin_cname(char *hostname)
1109 {
1110 sdb_llist_iter_t *iter;
1112 if (! hostname)
1113 return NULL;
1115 if (! cname_list)
1116 return hostname;
1118 iter = sdb_llist_get_iter(cname_list);
1119 while (sdb_llist_iter_has_next(iter)) {
1120 sdb_plugin_cname_cb callback;
1121 char *cname;
1123 sdb_object_t *obj = sdb_llist_iter_get_next(iter);
1124 assert(obj);
1126 callback = (sdb_plugin_cname_cb)SDB_PLUGIN_CB(obj)->cb_callback;
1127 cname = callback(hostname, SDB_PLUGIN_CB(obj)->cb_user_data);
1128 if (cname) {
1129 free(hostname);
1130 hostname = cname;
1131 }
1132 /* else: don't change hostname */
1133 }
1134 sdb_llist_iter_destroy(iter);
1135 return hostname;
1136 } /* sdb_plugin_cname */
1138 int
1139 sdb_plugin_log(int prio, const char *msg)
1140 {
1141 sdb_llist_iter_t *iter;
1142 int ret = -1;
1144 bool logged = 0;
1146 if (! msg)
1147 return 0;
1149 iter = sdb_llist_get_iter(log_list);
1150 while (sdb_llist_iter_has_next(iter)) {
1151 sdb_plugin_log_cb callback;
1152 int tmp;
1154 sdb_object_t *obj = sdb_llist_iter_get_next(iter);
1155 assert(obj);
1157 callback = (sdb_plugin_log_cb)SDB_PLUGIN_CB(obj)->cb_callback;
1158 tmp = callback(prio, msg, SDB_PLUGIN_CB(obj)->cb_user_data);
1159 if (tmp > ret)
1160 ret = tmp;
1162 if (SDB_PLUGIN_CB(obj)->cb_ctx)
1163 logged = 1;
1164 /* else: this is an internally registered callback */
1165 }
1166 sdb_llist_iter_destroy(iter);
1168 if (! logged)
1169 return fprintf(stderr, "[%s] %s\n", SDB_LOG_PRIO_TO_STRING(prio), msg);
1170 return ret;
1171 } /* sdb_plugin_log */
1173 int
1174 sdb_plugin_vlogf(int prio, const char *fmt, va_list ap)
1175 {
1176 sdb_strbuf_t *buf;
1177 int ret;
1179 if (! fmt)
1180 return 0;
1182 buf = sdb_strbuf_create(64);
1183 if (! buf) {
1184 ret = fprintf(stderr, "[%s] ", SDB_LOG_PRIO_TO_STRING(prio));
1185 ret += vfprintf(stderr, fmt, ap);
1186 return ret;
1187 }
1189 if (sdb_strbuf_vsprintf(buf, fmt, ap) < 0) {
1190 sdb_strbuf_destroy(buf);
1191 return -1;
1192 }
1194 ret = sdb_plugin_log(prio, sdb_strbuf_string(buf));
1195 sdb_strbuf_destroy(buf);
1196 return ret;
1197 } /* sdb_plugin_vlogf */
1199 int
1200 sdb_plugin_logf(int prio, const char *fmt, ...)
1201 {
1202 va_list ap;
1203 int ret;
1205 if (! fmt)
1206 return 0;
1208 va_start(ap, fmt);
1209 ret = sdb_plugin_vlogf(prio, fmt, ap);
1210 va_end(ap);
1211 return ret;
1212 } /* sdb_plugin_logf */
1214 sdb_timeseries_t *
1215 sdb_plugin_fetch_timeseries(const char *type, const char *id,
1216 sdb_timeseries_opts_t *opts)
1217 {
1218 sdb_plugin_cb_t *plugin;
1219 sdb_plugin_fetch_ts_cb callback;
1220 sdb_timeseries_t *ts;
1222 ctx_t *old_ctx;
1224 if ((! type) || (! id) || (! opts))
1225 return NULL;
1227 plugin = SDB_PLUGIN_CB(sdb_llist_search_by_name(ts_fetcher_list, type));
1228 if (! plugin) {
1229 sdb_log(SDB_LOG_ERR, "core: Cannot fetch time-series of type %s: "
1230 "no such plugin loaded", type);
1231 errno = ENOENT;
1232 return NULL;
1233 }
1235 old_ctx = ctx_set(plugin->cb_ctx);
1236 callback = (sdb_plugin_fetch_ts_cb)plugin->cb_callback;
1237 ts = callback(id, opts, plugin->cb_user_data);
1238 ctx_set(old_ctx);
1239 return ts;
1240 } /* sdb_plugin_fetch_timeseries */
1242 /* vim: set tw=78 sw=4 ts=4 noexpandtab : */