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"
33 #include "utils/strbuf.h"
35 #include <assert.h>
37 #include <errno.h>
39 #include <stdarg.h>
40 #include <stdio.h>
41 #include <string.h>
42 #include <strings.h>
43 #include <unistd.h>
45 #include <ltdl.h>
47 #include <pthread.h>
49 /*
50 * private data types
51 */
53 struct sdb_plugin_info {
54 char *name;
56 char *description;
57 char *copyright;
58 char *license;
60 int version;
61 int plugin_version;
62 };
63 #define SDB_PLUGIN_INFO_INIT { "no name set", "no description set", \
64 /* copyright */ "", /* license */ "", \
65 /* version */ -1, /* plugin_version */ -1 }
67 typedef struct {
68 sdb_object_t super;
69 void *cb_callback;
70 sdb_object_t *cb_user_data;
71 sdb_plugin_ctx_t cb_ctx;
72 } sdb_plugin_cb_t;
73 #define SDB_PLUGIN_CB_INIT { SDB_OBJECT_INIT, \
74 /* callback = */ NULL, /* user_data = */ NULL, \
75 SDB_PLUGIN_CTX_INIT }
77 typedef struct {
78 sdb_plugin_cb_t super;
79 #define ccb_callback super.cb_callback
80 #define ccb_user_data super.cb_user_data
81 #define ccb_ctx super.cb_ctx
82 sdb_time_t ccb_interval;
83 sdb_time_t ccb_next_update;
84 } sdb_plugin_collector_cb_t;
86 #define SDB_PLUGIN_CB(obj) ((sdb_plugin_cb_t *)(obj))
87 #define SDB_PLUGIN_CCB(obj) ((sdb_plugin_collector_cb_t *)(obj))
89 /*
90 * private variables
91 */
93 static sdb_plugin_ctx_t plugin_default_ctx = SDB_PLUGIN_CTX_INIT;
95 static pthread_key_t plugin_ctx_key;
96 static _Bool plugin_ctx_key_initialized = 0;
98 static sdb_llist_t *config_list = NULL;
99 static sdb_llist_t *init_list = NULL;
100 static sdb_llist_t *collector_list = NULL;
101 static sdb_llist_t *shutdown_list = NULL;
102 static sdb_llist_t *log_list = NULL;
104 /*
105 * private helper functions
106 */
108 static void
109 sdb_plugin_ctx_destructor(void *ctx)
110 {
111 if (! ctx)
112 return;
113 free(ctx);
114 } /* sdb_plugin_ctx_destructor */
116 static void
117 sdb_plugin_ctx_init(void)
118 {
119 if (plugin_ctx_key_initialized)
120 return;
122 pthread_key_create(&plugin_ctx_key, sdb_plugin_ctx_destructor);
123 plugin_ctx_key_initialized = 1;
124 } /* sdb_plugin_ctx_init */
126 static sdb_plugin_ctx_t *
127 sdb_plugin_ctx_create(void)
128 {
129 sdb_plugin_ctx_t *ctx;
131 ctx = malloc(sizeof(*ctx));
132 if (! ctx)
133 return NULL;
135 *ctx = plugin_default_ctx;
137 if (! plugin_ctx_key_initialized)
138 sdb_plugin_ctx_init();
139 pthread_setspecific(plugin_ctx_key, ctx);
140 return ctx;
141 } /* sdb_plugin_ctx_create */
143 static int
144 sdb_plugin_cmp_next_update(const sdb_object_t *a, const sdb_object_t *b)
145 {
146 const sdb_plugin_collector_cb_t *ccb1
147 = (const sdb_plugin_collector_cb_t *)a;
148 const sdb_plugin_collector_cb_t *ccb2
149 = (const sdb_plugin_collector_cb_t *)b;
151 assert(ccb1 && ccb2);
153 return (ccb1->ccb_next_update > ccb2->ccb_next_update)
154 ? 1 : (ccb1->ccb_next_update < ccb2->ccb_next_update)
155 ? -1 : 0;
156 } /* sdb_plugin_cmp_next_update */
158 /*
159 * private types
160 */
162 static int
163 sdb_plugin_cb_init(sdb_object_t *obj, va_list ap)
164 {
165 sdb_llist_t **list = va_arg(ap, sdb_llist_t **);
166 const char *type = va_arg(ap, const char *);
167 void *callback = va_arg(ap, void *);
168 sdb_object_t *ud = va_arg(ap, sdb_object_t *);
170 assert(list);
171 assert(type);
172 assert(obj);
174 if (sdb_llist_search_by_name(*list, obj->name)) {
175 sdb_log(SDB_LOG_WARNING, "plugin: %s callback '%s' "
176 "has already been registered. Ignoring newly "
177 "registered version.", type, obj->name);
178 return -1;
179 }
181 SDB_PLUGIN_CB(obj)->cb_callback = callback;
182 SDB_PLUGIN_CB(obj)->cb_ctx = sdb_plugin_get_ctx();
184 sdb_object_ref(ud);
185 SDB_PLUGIN_CB(obj)->cb_user_data = ud;
186 return 0;
187 } /* sdb_plugin_cb_init */
189 static void
190 sdb_plugin_cb_destroy(sdb_object_t *obj)
191 {
192 assert(obj);
193 sdb_object_deref(SDB_PLUGIN_CB(obj)->cb_user_data);
194 } /* sdb_plugin_cb_destroy */
196 static sdb_type_t sdb_plugin_cb_type = {
197 sizeof(sdb_plugin_cb_t),
199 sdb_plugin_cb_init,
200 sdb_plugin_cb_destroy,
201 /* clone = */ NULL
202 };
204 static sdb_type_t sdb_plugin_collector_cb_type = {
205 sizeof(sdb_plugin_collector_cb_t),
207 sdb_plugin_cb_init,
208 sdb_plugin_cb_destroy,
209 /* clone = */ NULL
210 };
212 static int
213 sdb_plugin_add_callback(sdb_llist_t **list, const char *type,
214 const char *name, void *callback, sdb_object_t *user_data)
215 {
216 sdb_object_t *obj;
218 if ((! name) || (! callback))
219 return -1;
221 assert(list);
223 if (! *list)
224 *list = sdb_llist_create();
225 if (! *list)
226 return -1;
228 obj = sdb_object_create(name, sdb_plugin_cb_type,
229 list, type, callback, user_data);
230 if (! obj)
231 return -1;
233 if (sdb_llist_append(*list, obj)) {
234 sdb_object_deref(obj);
235 return -1;
236 }
238 /* pass control to the list */
239 sdb_object_deref(obj);
241 sdb_log(SDB_LOG_INFO, "plugin: Registered %s callback '%s'.",
242 type, name);
243 return 0;
244 } /* sdb_plugin_add_callback */
246 /*
247 * public API
248 */
250 int
251 sdb_plugin_load(const char *name)
252 {
253 char real_name[strlen(name) > 0 ? strlen(name) : 1];
254 const char *name_ptr;
255 char *tmp;
257 char filename[1024];
259 lt_dlhandle lh;
261 int (*mod_init)(sdb_plugin_info_t *);
262 sdb_plugin_info_t plugin_info = SDB_PLUGIN_INFO_INIT;
264 int status;
266 if ((! name) || (! *name))
267 return -1;
269 real_name[0] = '\0';
270 name_ptr = name;
272 while ((tmp = strstr(name_ptr, "::"))) {
273 strncat(real_name, name_ptr, (size_t)(tmp - name_ptr));
274 strcat(real_name, "/");
275 name_ptr = tmp + strlen("::");
276 }
277 strcat(real_name, name_ptr);
279 snprintf(filename, sizeof(filename), "%s/%s.so",
280 PKGLIBDIR, real_name);
281 filename[sizeof(filename) - 1] = '\0';
283 if (access(filename, R_OK)) {
284 char errbuf[1024];
285 sdb_log(SDB_LOG_ERR, "plugin: Failed to load plugin '%s' (%s): %s",
286 name, filename, sdb_strerror(errno, errbuf, sizeof(errbuf)));
287 return -1;
288 }
290 lt_dlinit();
291 lt_dlerror();
293 lh = lt_dlopen(filename);
294 if (! lh) {
295 sdb_log(SDB_LOG_ERR, "plugin: Failed to load plugin '%s': %s"
296 "The most common cause for this problem are missing "
297 "dependencies.\n", name, lt_dlerror());
298 return -1;
299 }
301 mod_init = (int (*)(sdb_plugin_info_t *))lt_dlsym(lh, "sdb_module_init");
302 if (! mod_init) {
303 sdb_log(SDB_LOG_ERR, "plugin: Failed to load plugin '%s': "
304 "could not find symbol 'sdb_module_init'", name);
305 return -1;
306 }
308 status = mod_init(&plugin_info);
309 if (status) {
310 sdb_log(SDB_LOG_ERR, "plugin: Failed to initialize "
311 "plugin '%s'", name);
312 return -1;
313 }
315 /* compare minor version */
316 if ((plugin_info.version < 0)
317 || ((int)(plugin_info.version / 100) != (int)(SDB_VERSION / 100)))
318 sdb_log(SDB_LOG_WARNING, "plugin: WARNING: version of "
319 "plugin '%s' (%i.%i.%i) does not match our version "
320 "(%i.%i.%i); this might cause problems",
321 name, SDB_VERSION_DECODE(plugin_info.version),
322 SDB_VERSION_DECODE(SDB_VERSION));
324 sdb_log(SDB_LOG_INFO, "plugin: Successfully loaded "
325 "plugin '%s' v%i (%s)\n\t%s",
326 plugin_info.name, plugin_info.plugin_version,
327 plugin_info.description, plugin_info.copyright);
328 return 0;
329 } /* sdb_plugin_load */
331 int
332 sdb_plugin_set_info(sdb_plugin_info_t *info, int type, ...)
333 {
334 va_list ap;
336 if (! info)
337 return -1;
339 va_start(ap, type);
341 switch (type) {
342 case SDB_PLUGIN_INFO_NAME:
343 {
344 char *name = va_arg(ap, char *);
345 info->name = name;
346 }
347 break;
348 case SDB_PLUGIN_INFO_DESC:
349 {
350 char *desc = va_arg(ap, char *);
351 info->description = desc;
352 }
353 break;
354 case SDB_PLUGIN_INFO_COPYRIGHT:
355 {
356 char *copyright = va_arg(ap, char *);
357 info->copyright = copyright;
358 }
359 break;
360 case SDB_PLUGIN_INFO_LICENSE:
361 {
362 char *license = va_arg(ap, char *);
363 info->license = license;
364 }
365 break;
366 case SDB_PLUGIN_INFO_VERSION:
367 {
368 int version = va_arg(ap, int);
369 info->version = version;
370 }
371 break;
372 case SDB_PLUGIN_INFO_PLUGIN_VERSION:
373 {
374 int version = va_arg(ap, int);
375 info->plugin_version = version;
376 }
377 break;
378 default:
379 va_end(ap);
380 return -1;
381 }
383 va_end(ap);
384 return 0;
385 } /* sdb_plugin_set_info */
387 int
388 sdb_plugin_register_config(const char *name, sdb_plugin_config_cb callback)
389 {
390 return sdb_plugin_add_callback(&config_list, "init", name,
391 callback, NULL);
392 } /* sdb_plugin_register_config */
394 int
395 sdb_plugin_register_init(const char *name, sdb_plugin_init_cb callback,
396 sdb_object_t *user_data)
397 {
398 return sdb_plugin_add_callback(&init_list, "init", name,
399 callback, user_data);
400 } /* sdb_plugin_register_init */
402 int
403 sdb_plugin_register_shutdown(const char *name, sdb_plugin_shutdown_cb callback,
404 sdb_object_t *user_data)
405 {
406 return sdb_plugin_add_callback(&shutdown_list, "shutdown", name,
407 callback, user_data);
408 } /* sdb_plugin_register_shutdown */
410 int
411 sdb_plugin_register_log(const char *name, sdb_plugin_log_cb callback,
412 sdb_object_t *user_data)
413 {
414 return sdb_plugin_add_callback(&log_list, "log", name, callback,
415 user_data);
416 } /* sdb_plugin_register_log */
418 int
419 sdb_plugin_register_collector(const char *name, sdb_plugin_collector_cb callback,
420 const sdb_time_t *interval, sdb_object_t *user_data)
421 {
422 sdb_object_t *obj;
424 if ((! name) || (! callback))
425 return -1;
427 if (! collector_list)
428 collector_list = sdb_llist_create();
429 if (! collector_list)
430 return -1;
432 obj = sdb_object_create(name, sdb_plugin_collector_cb_type,
433 &collector_list, "collector", callback, user_data);
434 if (! obj)
435 return -1;
437 if (interval)
438 SDB_PLUGIN_CCB(obj)->ccb_interval = *interval;
439 else {
440 sdb_time_t tmp = sdb_plugin_get_ctx().interval;
442 if (tmp > 0)
443 SDB_PLUGIN_CCB(obj)->ccb_interval = tmp;
444 else
445 SDB_PLUGIN_CCB(obj)->ccb_interval = 0;
446 }
448 if (! (SDB_PLUGIN_CCB(obj)->ccb_next_update = sdb_gettime())) {
449 char errbuf[1024];
450 sdb_log(SDB_LOG_ERR, "plugin: Failed to determine current "
451 "time: %s", sdb_strerror(errno, errbuf, sizeof(errbuf)));
452 sdb_object_deref(obj);
453 return -1;
454 }
456 if (sdb_llist_insert_sorted(collector_list, obj,
457 sdb_plugin_cmp_next_update)) {
458 sdb_object_deref(obj);
459 return -1;
460 }
462 /* pass control to the list */
463 sdb_object_deref(obj);
465 sdb_log(SDB_LOG_INFO, "plugin: Registered collector callback '%s' "
466 "(interval = %.3fs).", name,
467 SDB_TIME_TO_DOUBLE(SDB_PLUGIN_CCB(obj)->ccb_interval));
468 return 0;
469 } /* sdb_plugin_register_collector */
471 sdb_plugin_ctx_t
472 sdb_plugin_get_ctx(void)
473 {
474 sdb_plugin_ctx_t *ctx;
476 if (! plugin_ctx_key_initialized)
477 sdb_plugin_ctx_init();
478 ctx = pthread_getspecific(plugin_ctx_key);
480 if (! ctx)
481 ctx = sdb_plugin_ctx_create();
482 if (! ctx)
483 return plugin_default_ctx;
484 return *ctx;
485 } /* sdb_plugin_get_ctx */
487 sdb_plugin_ctx_t
488 sdb_plugin_set_ctx(sdb_plugin_ctx_t ctx)
489 {
490 sdb_plugin_ctx_t *tmp;
491 sdb_plugin_ctx_t old;
493 if (! plugin_ctx_key_initialized)
494 sdb_plugin_ctx_init();
495 tmp = pthread_getspecific(plugin_ctx_key);
497 if (! tmp)
498 tmp = sdb_plugin_ctx_create();
499 if (! tmp)
500 return plugin_default_ctx;
502 old = *tmp;
503 *tmp = ctx;
504 return old;
505 } /* sdb_plugin_set_ctx */
507 int
508 sdb_plugin_configure(const char *name, oconfig_item_t *ci)
509 {
510 sdb_plugin_cb_t *plugin;
511 sdb_plugin_config_cb callback;
513 sdb_plugin_ctx_t old_ctx;
515 int status;
517 if ((! name) || (! ci))
518 return -1;
520 plugin = SDB_PLUGIN_CB(sdb_llist_search_by_name(config_list, name));
521 if (! plugin) {
522 /* XXX: check if any such plugin has been loaded */
523 sdb_log(SDB_LOG_ERR, "plugin: Plugin '%s' did not register "
524 "a config callback.", name);
525 errno = ENOENT;
526 return -1;
527 }
529 old_ctx = sdb_plugin_set_ctx(plugin->cb_ctx);
530 callback = plugin->cb_callback;
531 status = callback(ci);
532 sdb_plugin_set_ctx(old_ctx);
533 return status;
534 } /* sdb_plugin_configure */
536 int
537 sdb_plugin_init_all(void)
538 {
539 sdb_llist_iter_t *iter;
541 iter = sdb_llist_get_iter(init_list);
542 while (sdb_llist_iter_has_next(iter)) {
543 sdb_plugin_init_cb callback;
544 sdb_plugin_ctx_t old_ctx;
546 sdb_object_t *obj = sdb_llist_iter_get_next(iter);
547 assert(obj);
549 callback = SDB_PLUGIN_CB(obj)->cb_callback;
551 old_ctx = sdb_plugin_set_ctx(SDB_PLUGIN_CB(obj)->cb_ctx);
552 if (callback(SDB_PLUGIN_CB(obj)->cb_user_data)) {
553 /* XXX: unload plugin */
554 }
555 sdb_plugin_set_ctx(old_ctx);
556 }
557 sdb_llist_iter_destroy(iter);
558 return 0;
559 } /* sdb_plugin_init_all */
561 int
562 sdb_plugin_collector_loop(sdb_plugin_loop_t *loop)
563 {
564 if (! collector_list) {
565 sdb_log(SDB_LOG_WARNING, "plugin: No collectors registered. "
566 "Quiting main loop.");
567 return -1;
568 }
570 if (! loop)
571 return -1;
573 while (loop->do_loop) {
574 sdb_plugin_collector_cb callback;
575 sdb_plugin_ctx_t old_ctx;
577 sdb_time_t interval, now;
579 sdb_object_t *obj = sdb_llist_shift(collector_list);
580 if (! obj)
581 return -1;
583 callback = SDB_PLUGIN_CCB(obj)->ccb_callback;
585 if (! (now = sdb_gettime())) {
586 char errbuf[1024];
587 sdb_log(SDB_LOG_ERR, "plugin: Failed to determine current "
588 "time: %s", sdb_strerror(errno, errbuf, sizeof(errbuf)));
589 now = SDB_PLUGIN_CCB(obj)->ccb_next_update;
590 }
592 if (now < SDB_PLUGIN_CCB(obj)->ccb_next_update) {
593 interval = SDB_PLUGIN_CCB(obj)->ccb_next_update - now;
595 errno = 0;
596 while (loop->do_loop && sdb_sleep(interval, &interval)) {
597 if (errno != EINTR) {
598 char errbuf[1024];
599 sdb_log(SDB_LOG_ERR, "plugin: Failed to sleep: %s",
600 sdb_strerror(errno, errbuf, sizeof(errbuf)));
601 return -1;
602 }
603 errno = 0;
604 }
606 if (! loop->do_loop)
607 return 0;
608 }
610 old_ctx = sdb_plugin_set_ctx(SDB_PLUGIN_CCB(obj)->ccb_ctx);
611 if (callback(SDB_PLUGIN_CCB(obj)->ccb_user_data)) {
612 /* XXX */
613 }
614 sdb_plugin_set_ctx(old_ctx);
616 interval = SDB_PLUGIN_CCB(obj)->ccb_interval;
617 if (! interval)
618 interval = loop->default_interval;
619 if (! interval) {
620 sdb_log(SDB_LOG_WARNING, "plugin: No interval configured "
621 "for plugin '%s'; skipping any further "
622 "iterations.", obj->name);
623 sdb_object_deref(obj);
624 continue;
625 }
627 SDB_PLUGIN_CCB(obj)->ccb_next_update += interval;
629 if (! (now = sdb_gettime())) {
630 char errbuf[1024];
631 sdb_log(SDB_LOG_ERR, "plugin: Failed to determine current "
632 "time: %s", sdb_strerror(errno, errbuf, sizeof(errbuf)));
633 now = SDB_PLUGIN_CCB(obj)->ccb_next_update;
634 }
636 if (now > SDB_PLUGIN_CCB(obj)->ccb_next_update) {
637 sdb_log(SDB_LOG_WARNING, "plugin: Plugin '%s' took too "
638 "long; skipping iterations to keep up.",
639 obj->name);
640 SDB_PLUGIN_CCB(obj)->ccb_next_update = now;
641 }
643 if (sdb_llist_insert_sorted(collector_list, obj,
644 sdb_plugin_cmp_next_update)) {
645 sdb_log(SDB_LOG_ERR, "plugin: Failed to re-insert "
646 "plugin '%s' into collector list. Unable to further "
647 "use the plugin.",
648 obj->name);
649 sdb_object_deref(obj);
650 return -1;
651 }
653 /* pass control back to the list */
654 sdb_object_deref(obj);
655 }
656 return 0;
657 } /* sdb_plugin_read_loop */
659 int
660 sdb_plugin_log(int prio, const char *msg)
661 {
662 sdb_llist_iter_t *iter;
663 int ret = -1;
665 if (! msg)
666 return 0;
668 if (! log_list)
669 return fprintf(stderr, "[%s] %s\n", SDB_LOG_PRIO_TO_STRING(prio), msg);
671 iter = sdb_llist_get_iter(log_list);
672 while (sdb_llist_iter_has_next(iter)) {
673 sdb_plugin_log_cb callback;
674 int tmp;
676 sdb_object_t *obj = sdb_llist_iter_get_next(iter);
677 assert(obj);
679 callback = SDB_PLUGIN_CB(obj)->cb_callback;
680 tmp = callback(prio, msg, SDB_PLUGIN_CB(obj)->cb_user_data);
681 if (tmp > ret)
682 ret = tmp;
683 }
684 sdb_llist_iter_destroy(iter);
685 return ret;
686 } /* sdb_plugin_log */
688 int
689 sdb_plugin_vlogf(int prio, const char *fmt, va_list ap)
690 {
691 sdb_strbuf_t *buf;
692 int ret;
694 if (! fmt)
695 return 0;
697 buf = sdb_strbuf_create(64);
698 if (! buf) {
699 ret = fprintf(stderr, "[%s] ", SDB_LOG_PRIO_TO_STRING(prio));
700 ret += vfprintf(stderr, fmt, ap);
701 return ret;
702 }
704 if (sdb_strbuf_vsprintf(buf, fmt, ap) < 0) {
705 sdb_strbuf_destroy(buf);
706 return -1;
707 }
709 ret = sdb_plugin_log(prio, sdb_strbuf_string(buf));
710 sdb_strbuf_destroy(buf);
711 return ret;
712 } /* sdb_plugin_vlogf */
714 int
715 sdb_plugin_logf(int prio, const char *fmt, ...)
716 {
717 va_list ap;
718 int ret;
720 if (! fmt)
721 return 0;
723 va_start(ap, fmt);
724 ret = sdb_plugin_vlogf(prio, fmt, ap);
725 va_end(ap);
726 return ret;
727 } /* sdb_plugin_logf */
729 /* vim: set tw=78 sw=4 ts=4 noexpandtab : */