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 "utils/error.h"
31 #include "utils/llist.h"
32 #include "utils/time.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 char cb_name[64];
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, /* name = */ "", \
74 /* callback = */ NULL, /* user_data = */ NULL, \
75 SDB_PLUGIN_CTX_INIT }
77 typedef struct {
78 sdb_plugin_cb_t super;
79 #define ccb_name super.cb_name
80 #define ccb_callback super.cb_callback
81 #define ccb_user_data super.cb_user_data
82 #define ccb_ctx super.cb_ctx
83 sdb_time_t ccb_interval;
84 sdb_time_t ccb_next_update;
85 } sdb_plugin_collector_cb_t;
87 #define SDB_PLUGIN_CB(obj) ((sdb_plugin_cb_t *)(obj))
88 #define SDB_PLUGIN_CCB(obj) ((sdb_plugin_collector_cb_t *)(obj))
90 /*
91 * private variables
92 */
94 static sdb_plugin_ctx_t plugin_default_ctx = SDB_PLUGIN_CTX_INIT;
96 static pthread_key_t plugin_ctx_key;
97 static _Bool plugin_ctx_key_initialized = 0;
99 static sdb_llist_t *config_list = NULL;
100 static sdb_llist_t *init_list = NULL;
101 static sdb_llist_t *collector_list = NULL;
102 static sdb_llist_t *shutdown_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_name(const sdb_object_t *a, const sdb_object_t *b)
145 {
146 const sdb_plugin_cb_t *cb1 = (const sdb_plugin_cb_t *)a;
147 const sdb_plugin_cb_t *cb2 = (const sdb_plugin_cb_t *)b;
149 assert(cb1 && cb2);
150 return strcasecmp(cb1->cb_name, cb2->cb_name);
151 } /* sdb_plugin_cmp_name */
153 static int
154 sdb_plugin_cmp_next_update(const sdb_object_t *a, const sdb_object_t *b)
155 {
156 const sdb_plugin_collector_cb_t *ccb1
157 = (const sdb_plugin_collector_cb_t *)a;
158 const sdb_plugin_collector_cb_t *ccb2
159 = (const sdb_plugin_collector_cb_t *)b;
161 assert(ccb1 && ccb2);
163 return (ccb1->ccb_next_update > ccb2->ccb_next_update)
164 ? 1 : (ccb1->ccb_next_update < ccb2->ccb_next_update)
165 ? -1 : 0;
166 } /* sdb_plugin_cmp_next_update */
168 static sdb_plugin_cb_t *
169 sdb_plugin_find_by_name(sdb_llist_t *list, const char *name)
170 {
171 sdb_plugin_cb_t tmp = SDB_PLUGIN_CB_INIT;
173 sdb_object_t *obj;
174 assert(name);
176 if (! list)
177 return NULL;
179 snprintf(tmp.cb_name, sizeof(tmp.cb_name), "%s", name);
180 tmp.cb_name[sizeof(tmp.cb_name) - 1] = '\0';
181 obj = sdb_llist_search(list, SDB_OBJ(&tmp), sdb_plugin_cmp_name);
182 if (! obj)
183 return NULL;
184 return SDB_PLUGIN_CB(obj);
185 } /* sdb_plugin_find_by_name */
187 /*
188 * private types
189 */
191 static int
192 sdb_plugin_cb_init(sdb_object_t *obj, va_list ap)
193 {
194 sdb_llist_t **list = va_arg(ap, sdb_llist_t **);
195 const char *type = va_arg(ap, const char *);
196 const char *name = va_arg(ap, const char *);
197 void *callback = va_arg(ap, void *);
198 sdb_object_t *ud = va_arg(ap, sdb_object_t *);
200 assert(list);
201 assert(type);
202 assert(obj);
204 if (sdb_plugin_find_by_name(*list, name)) {
205 sdb_log(SDB_LOG_WARNING, "plugin: %s callback '%s' "
206 "has already been registered. Ignoring newly "
207 "registered version.", type, name);
208 return -1;
209 }
211 snprintf(SDB_PLUGIN_CB(obj)->cb_name,
212 sizeof(SDB_PLUGIN_CB(obj)->cb_name),
213 "%s", name);
214 SDB_PLUGIN_CB(obj)->cb_name[sizeof(SDB_PLUGIN_CB(obj)->cb_name) - 1] = '\0';
215 SDB_PLUGIN_CB(obj)->cb_callback = callback;
216 SDB_PLUGIN_CB(obj)->cb_ctx = sdb_plugin_get_ctx();
218 sdb_object_ref(ud);
219 SDB_PLUGIN_CB(obj)->cb_user_data = ud;
220 return 0;
221 } /* sdb_plugin_cb_init */
223 static void
224 sdb_plugin_cb_destroy(sdb_object_t *obj)
225 {
226 assert(obj);
227 sdb_object_deref(SDB_PLUGIN_CB(obj)->cb_user_data);
228 } /* sdb_plugin_cb_destroy */
230 static sdb_type_t sdb_plugin_cb_type = {
231 sizeof(sdb_plugin_cb_t),
233 sdb_plugin_cb_init,
234 sdb_plugin_cb_destroy,
235 /* clone = */ NULL
236 };
238 static sdb_type_t sdb_plugin_collector_cb_type = {
239 sizeof(sdb_plugin_collector_cb_t),
241 sdb_plugin_cb_init,
242 sdb_plugin_cb_destroy,
243 /* clone = */ NULL
244 };
246 static int
247 sdb_plugin_add_callback(sdb_llist_t **list, const char *type,
248 const char *name, void *callback, sdb_object_t *user_data)
249 {
250 sdb_object_t *obj;
252 if ((! name) || (! callback))
253 return -1;
255 assert(list);
257 if (! *list)
258 *list = sdb_llist_create();
259 if (! *list)
260 return -1;
262 obj = sdb_object_create(sdb_plugin_cb_type,
263 list, type, name, callback, user_data);
264 if (! obj)
265 return -1;
267 if (sdb_llist_append(*list, obj)) {
268 sdb_object_deref(obj);
269 return -1;
270 }
272 /* pass control to the list */
273 sdb_object_deref(obj);
275 sdb_log(SDB_LOG_INFO, "plugin: Registered %s callback '%s'.",
276 type, name);
277 return 0;
278 } /* sdb_plugin_add_callback */
280 /*
281 * public API
282 */
284 int
285 sdb_plugin_load(const char *name)
286 {
287 char real_name[strlen(name) > 0 ? strlen(name) : 1];
288 const char *name_ptr;
289 char *tmp;
291 char filename[1024];
293 lt_dlhandle lh;
295 int (*mod_init)(sdb_plugin_info_t *);
296 sdb_plugin_info_t plugin_info = SDB_PLUGIN_INFO_INIT;
298 int status;
300 if ((! name) || (! *name))
301 return -1;
303 real_name[0] = '\0';
304 name_ptr = name;
306 while ((tmp = strstr(name_ptr, "::"))) {
307 strncat(real_name, name_ptr, (size_t)(tmp - name_ptr));
308 strcat(real_name, "/");
309 name_ptr = tmp + strlen("::");
310 }
311 strcat(real_name, name_ptr);
313 snprintf(filename, sizeof(filename), "%s/%s.so",
314 PKGLIBDIR, real_name);
315 filename[sizeof(filename) - 1] = '\0';
317 if (access(filename, R_OK)) {
318 char errbuf[1024];
319 sdb_log(SDB_LOG_ERR, "plugin: Failed to load plugin '%s' (%s): %s",
320 name, filename, sdb_strerror(errno, errbuf, sizeof(errbuf)));
321 return -1;
322 }
324 lt_dlinit();
325 lt_dlerror();
327 lh = lt_dlopen(filename);
328 if (! lh) {
329 sdb_log(SDB_LOG_ERR, "plugin: Failed to load plugin '%s': %s"
330 "The most common cause for this problem are missing "
331 "dependencies.\n", name, lt_dlerror());
332 return -1;
333 }
335 mod_init = (int (*)(sdb_plugin_info_t *))lt_dlsym(lh, "sdb_module_init");
336 if (! mod_init) {
337 sdb_log(SDB_LOG_ERR, "plugin: Failed to load plugin '%s': "
338 "could not find symbol 'sdb_module_init'", name);
339 return -1;
340 }
342 status = mod_init(&plugin_info);
343 if (status) {
344 sdb_log(SDB_LOG_ERR, "plugin: Failed to initialize "
345 "plugin '%s'", name);
346 return -1;
347 }
349 /* compare minor version */
350 if ((plugin_info.version < 0)
351 || ((int)(plugin_info.version / 100) != (int)(SDB_VERSION / 100)))
352 sdb_log(SDB_LOG_WARNING, "plugin: WARNING: version of "
353 "plugin '%s' (%i.%i.%i) does not match our version "
354 "(%i.%i.%i); this might cause problems",
355 name, SDB_VERSION_DECODE(plugin_info.version),
356 SDB_VERSION_DECODE(SDB_VERSION));
358 sdb_log(SDB_LOG_INFO, "plugin: Successfully loaded "
359 "plugin '%s' v%i (%s)\n\t%s",
360 plugin_info.name, plugin_info.plugin_version,
361 plugin_info.description, plugin_info.copyright);
362 return 0;
363 } /* sdb_plugin_load */
365 int
366 sdb_plugin_set_info(sdb_plugin_info_t *info, int type, ...)
367 {
368 va_list ap;
370 if (! info)
371 return -1;
373 va_start(ap, type);
375 switch (type) {
376 case SDB_PLUGIN_INFO_NAME:
377 {
378 char *name = va_arg(ap, char *);
379 info->name = name;
380 }
381 break;
382 case SDB_PLUGIN_INFO_DESC:
383 {
384 char *desc = va_arg(ap, char *);
385 info->description = desc;
386 }
387 break;
388 case SDB_PLUGIN_INFO_COPYRIGHT:
389 {
390 char *copyright = va_arg(ap, char *);
391 info->copyright = copyright;
392 }
393 break;
394 case SDB_PLUGIN_INFO_LICENSE:
395 {
396 char *license = va_arg(ap, char *);
397 info->license = license;
398 }
399 break;
400 case SDB_PLUGIN_INFO_VERSION:
401 {
402 int version = va_arg(ap, int);
403 info->version = version;
404 }
405 break;
406 case SDB_PLUGIN_INFO_PLUGIN_VERSION:
407 {
408 int version = va_arg(ap, int);
409 info->plugin_version = version;
410 }
411 break;
412 default:
413 va_end(ap);
414 return -1;
415 }
417 va_end(ap);
418 return 0;
419 } /* sdb_plugin_set_info */
421 int
422 sdb_plugin_register_config(const char *name, sdb_plugin_config_cb callback)
423 {
424 return sdb_plugin_add_callback(&config_list, "init", name,
425 callback, NULL);
426 } /* sdb_plugin_register_config */
428 int
429 sdb_plugin_register_init(const char *name, sdb_plugin_init_cb callback,
430 sdb_object_t *user_data)
431 {
432 return sdb_plugin_add_callback(&init_list, "init", name,
433 callback, user_data);
434 } /* sdb_plugin_register_init */
436 int
437 sdb_plugin_register_shutdown(const char *name, sdb_plugin_shutdown_cb callback,
438 sdb_object_t *user_data)
439 {
440 return sdb_plugin_add_callback(&shutdown_list, "shutdown", name,
441 callback, user_data);
442 } /* sdb_plugin_register_shutdown */
444 int
445 sdb_plugin_register_collector(const char *name, sdb_plugin_collector_cb callback,
446 const sdb_time_t *interval, sdb_object_t *user_data)
447 {
448 sdb_object_t *obj;
450 if ((! name) || (! callback))
451 return -1;
453 if (! collector_list)
454 collector_list = sdb_llist_create();
455 if (! collector_list)
456 return -1;
458 obj = sdb_object_create(sdb_plugin_collector_cb_type,
459 &collector_list, "collector", name, callback, user_data);
460 if (! obj)
461 return -1;
463 if (interval)
464 SDB_PLUGIN_CCB(obj)->ccb_interval = *interval;
465 else {
466 sdb_time_t tmp = sdb_plugin_get_ctx().interval;
468 if (tmp > 0)
469 SDB_PLUGIN_CCB(obj)->ccb_interval = tmp;
470 else
471 SDB_PLUGIN_CCB(obj)->ccb_interval = 0;
472 }
474 if (! (SDB_PLUGIN_CCB(obj)->ccb_next_update = sdb_gettime())) {
475 char errbuf[1024];
476 sdb_log(SDB_LOG_ERR, "plugin: Failed to determine current "
477 "time: %s", sdb_strerror(errno, errbuf, sizeof(errbuf)));
478 sdb_object_deref(obj);
479 return -1;
480 }
482 if (sdb_llist_insert_sorted(collector_list, obj,
483 sdb_plugin_cmp_next_update)) {
484 sdb_object_deref(obj);
485 return -1;
486 }
488 /* pass control to the list */
489 sdb_object_deref(obj);
491 sdb_log(SDB_LOG_INFO, "plugin: Registered collector callback '%s' "
492 "(interval = %.3fs).", name,
493 SDB_TIME_TO_DOUBLE(SDB_PLUGIN_CCB(obj)->ccb_interval));
494 return 0;
495 } /* sdb_plugin_register_collector */
497 sdb_plugin_ctx_t
498 sdb_plugin_get_ctx(void)
499 {
500 sdb_plugin_ctx_t *ctx;
502 if (! plugin_ctx_key_initialized)
503 sdb_plugin_ctx_init();
504 ctx = pthread_getspecific(plugin_ctx_key);
506 if (! ctx)
507 ctx = sdb_plugin_ctx_create();
508 if (! ctx)
509 return plugin_default_ctx;
510 return *ctx;
511 } /* sdb_plugin_get_ctx */
513 sdb_plugin_ctx_t
514 sdb_plugin_set_ctx(sdb_plugin_ctx_t ctx)
515 {
516 sdb_plugin_ctx_t *tmp;
517 sdb_plugin_ctx_t old;
519 if (! plugin_ctx_key_initialized)
520 sdb_plugin_ctx_init();
521 tmp = pthread_getspecific(plugin_ctx_key);
523 if (! tmp)
524 tmp = sdb_plugin_ctx_create();
525 if (! tmp)
526 return plugin_default_ctx;
528 old = *tmp;
529 *tmp = ctx;
530 return old;
531 } /* sdb_plugin_set_ctx */
533 int
534 sdb_plugin_configure(const char *name, oconfig_item_t *ci)
535 {
536 sdb_plugin_cb_t *plugin;
537 sdb_plugin_config_cb callback;
539 sdb_plugin_ctx_t old_ctx;
541 int status;
543 if ((! name) || (! ci))
544 return -1;
546 plugin = sdb_plugin_find_by_name(config_list, name);
547 if (! plugin) {
548 /* XXX: check if any such plugin has been loaded */
549 sdb_log(SDB_LOG_ERR, "plugin: Plugin '%s' did not register "
550 "a config callback.", name);
551 errno = ENOENT;
552 return -1;
553 }
555 old_ctx = sdb_plugin_set_ctx(plugin->cb_ctx);
556 callback = plugin->cb_callback;
557 status = callback(ci);
558 sdb_plugin_set_ctx(old_ctx);
559 return status;
560 } /* sdb_plugin_configure */
562 int
563 sdb_plugin_init_all(void)
564 {
565 sdb_llist_iter_t *iter;
567 iter = sdb_llist_get_iter(init_list);
568 while (sdb_llist_iter_has_next(iter)) {
569 sdb_plugin_init_cb callback;
570 sdb_plugin_ctx_t old_ctx;
572 sdb_object_t *obj = sdb_llist_iter_get_next(iter);
573 assert(obj);
575 callback = SDB_PLUGIN_CB(obj)->cb_callback;
577 old_ctx = sdb_plugin_set_ctx(SDB_PLUGIN_CB(obj)->cb_ctx);
578 if (callback(SDB_PLUGIN_CB(obj)->cb_user_data)) {
579 /* XXX: unload plugin */
580 }
581 sdb_plugin_set_ctx(old_ctx);
582 }
583 return 0;
584 } /* sdb_plugin_init_all */
586 int
587 sdb_plugin_collector_loop(sdb_plugin_loop_t *loop)
588 {
589 if (! collector_list) {
590 sdb_log(SDB_LOG_WARNING, "plugin: No collectors registered. "
591 "Quiting main loop.");
592 return -1;
593 }
595 if (! loop)
596 return -1;
598 while (loop->do_loop) {
599 sdb_plugin_collector_cb callback;
600 sdb_plugin_ctx_t old_ctx;
602 sdb_time_t interval, now;
604 sdb_object_t *obj = sdb_llist_shift(collector_list);
605 if (! obj)
606 return -1;
608 callback = SDB_PLUGIN_CCB(obj)->ccb_callback;
610 if (! (now = sdb_gettime())) {
611 char errbuf[1024];
612 sdb_log(SDB_LOG_ERR, "plugin: Failed to determine current "
613 "time: %s", sdb_strerror(errno, errbuf, sizeof(errbuf)));
614 now = SDB_PLUGIN_CCB(obj)->ccb_next_update;
615 }
617 if (now < SDB_PLUGIN_CCB(obj)->ccb_next_update) {
618 interval = SDB_PLUGIN_CCB(obj)->ccb_next_update - now;
620 errno = 0;
621 while (loop->do_loop && sdb_sleep(interval, &interval)) {
622 if (errno != EINTR) {
623 char errbuf[1024];
624 sdb_log(SDB_LOG_ERR, "plugin: Failed to sleep: %s",
625 sdb_strerror(errno, errbuf, sizeof(errbuf)));
626 return -1;
627 }
628 errno = 0;
629 }
631 if (! loop->do_loop)
632 return 0;
633 }
635 old_ctx = sdb_plugin_set_ctx(SDB_PLUGIN_CCB(obj)->ccb_ctx);
636 if (callback(SDB_PLUGIN_CCB(obj)->ccb_user_data)) {
637 /* XXX */
638 }
639 sdb_plugin_set_ctx(old_ctx);
641 interval = SDB_PLUGIN_CCB(obj)->ccb_interval;
642 if (! interval)
643 interval = loop->default_interval;
644 if (! interval) {
645 sdb_log(SDB_LOG_WARNING, "plugin: No interval configured "
646 "for plugin '%s'; skipping any further "
647 "iterations.", SDB_PLUGIN_CCB(obj)->ccb_name);
648 sdb_object_deref(obj);
649 continue;
650 }
652 SDB_PLUGIN_CCB(obj)->ccb_next_update += interval;
654 if (! (now = sdb_gettime())) {
655 char errbuf[1024];
656 sdb_log(SDB_LOG_ERR, "plugin: Failed to determine current "
657 "time: %s", sdb_strerror(errno, errbuf, sizeof(errbuf)));
658 now = SDB_PLUGIN_CCB(obj)->ccb_next_update;
659 }
661 if (now > SDB_PLUGIN_CCB(obj)->ccb_next_update) {
662 sdb_log(SDB_LOG_WARNING, "plugin: Plugin '%s' took too "
663 "long; skipping iterations to keep up.",
664 SDB_PLUGIN_CCB(obj)->ccb_name);
665 SDB_PLUGIN_CCB(obj)->ccb_next_update = now;
666 }
668 if (sdb_llist_insert_sorted(collector_list, obj,
669 sdb_plugin_cmp_next_update)) {
670 sdb_log(SDB_LOG_ERR, "plugin: Failed to re-insert "
671 "plugin '%s' into collector list. Unable to further "
672 "use the plugin.",
673 SDB_PLUGIN_CCB(obj)->ccb_name);
674 sdb_object_deref(obj);
675 return -1;
676 }
678 /* pass control back to the list */
679 sdb_object_deref(obj);
680 }
681 return 0;
682 } /* sdb_plugin_read_loop */
684 /* vim: set tw=78 sw=4 ts=4 noexpandtab : */