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/string.h"
33 #include "utils/time.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 char cb_name[64];
70 void *cb_callback;
71 sdb_object_t *cb_user_data;
72 sdb_plugin_ctx_t cb_ctx;
73 } sdb_plugin_cb_t;
74 #define SDB_PLUGIN_CB_INIT { SDB_OBJECT_INIT, /* name = */ "", \
75 /* callback = */ NULL, /* user_data = */ NULL, \
76 SDB_PLUGIN_CTX_INIT }
78 typedef struct {
79 sdb_plugin_cb_t super;
80 #define ccb_name super.cb_name
81 #define ccb_callback super.cb_callback
82 #define ccb_user_data super.cb_user_data
83 #define ccb_ctx super.cb_ctx
84 sdb_time_t ccb_interval;
85 sdb_time_t ccb_next_update;
86 } sdb_plugin_collector_cb_t;
88 #define SDB_PLUGIN_CB(obj) ((sdb_plugin_cb_t *)(obj))
89 #define SDB_PLUGIN_CCB(obj) ((sdb_plugin_collector_cb_t *)(obj))
91 /*
92 * private variables
93 */
95 static sdb_plugin_ctx_t plugin_default_ctx = SDB_PLUGIN_CTX_INIT;
97 static pthread_key_t plugin_ctx_key;
98 static _Bool plugin_ctx_key_initialized = 0;
100 static sdb_llist_t *config_list = NULL;
101 static sdb_llist_t *init_list = NULL;
102 static sdb_llist_t *collector_list = NULL;
103 static sdb_llist_t *shutdown_list = NULL;
105 /*
106 * private helper functions
107 */
109 static void
110 sdb_plugin_ctx_destructor(void *ctx)
111 {
112 if (! ctx)
113 return;
114 free(ctx);
115 } /* sdb_plugin_ctx_destructor */
117 static void
118 sdb_plugin_ctx_init(void)
119 {
120 if (plugin_ctx_key_initialized)
121 return;
123 pthread_key_create(&plugin_ctx_key, sdb_plugin_ctx_destructor);
124 plugin_ctx_key_initialized = 1;
125 } /* sdb_plugin_ctx_init */
127 static sdb_plugin_ctx_t *
128 sdb_plugin_ctx_create(void)
129 {
130 sdb_plugin_ctx_t *ctx;
132 ctx = malloc(sizeof(*ctx));
133 if (! ctx)
134 return NULL;
136 *ctx = plugin_default_ctx;
138 if (! plugin_ctx_key_initialized)
139 sdb_plugin_ctx_init();
140 pthread_setspecific(plugin_ctx_key, ctx);
141 return ctx;
142 } /* sdb_plugin_ctx_create */
144 static int
145 sdb_plugin_cmp_name(const sdb_object_t *a, const sdb_object_t *b)
146 {
147 const sdb_plugin_cb_t *cb1 = (const sdb_plugin_cb_t *)a;
148 const sdb_plugin_cb_t *cb2 = (const sdb_plugin_cb_t *)b;
150 assert(cb1 && cb2);
151 return strcasecmp(cb1->cb_name, cb2->cb_name);
152 } /* sdb_plugin_cmp_name */
154 static int
155 sdb_plugin_cmp_next_update(const sdb_object_t *a, const sdb_object_t *b)
156 {
157 const sdb_plugin_collector_cb_t *ccb1
158 = (const sdb_plugin_collector_cb_t *)a;
159 const sdb_plugin_collector_cb_t *ccb2
160 = (const sdb_plugin_collector_cb_t *)b;
162 assert(ccb1 && ccb2);
164 return (ccb1->ccb_next_update > ccb2->ccb_next_update)
165 ? 1 : (ccb1->ccb_next_update < ccb2->ccb_next_update)
166 ? -1 : 0;
167 } /* sdb_plugin_cmp_next_update */
169 static sdb_plugin_cb_t *
170 sdb_plugin_find_by_name(sdb_llist_t *list, const char *name)
171 {
172 sdb_plugin_cb_t tmp = SDB_PLUGIN_CB_INIT;
174 sdb_object_t *obj;
175 assert(name);
177 if (! list)
178 return NULL;
180 snprintf(tmp.cb_name, sizeof(tmp.cb_name), "%s", name);
181 tmp.cb_name[sizeof(tmp.cb_name) - 1] = '\0';
182 obj = sdb_llist_search(list, SDB_OBJ(&tmp), sdb_plugin_cmp_name);
183 if (! obj)
184 return NULL;
185 return SDB_PLUGIN_CB(obj);
186 } /* sdb_plugin_find_by_name */
188 static int
189 sdb_plugin_cb_init(sdb_object_t *obj, va_list ap)
190 {
191 sdb_llist_t **list = va_arg(ap, sdb_llist_t **);
192 const char *type = va_arg(ap, const char *);
193 const char *name = va_arg(ap, const char *);
194 void *callback = va_arg(ap, void *);
195 sdb_object_t *ud = va_arg(ap, sdb_object_t *);
197 assert(list);
198 assert(type);
199 assert(obj);
201 if (sdb_plugin_find_by_name(*list, name)) {
202 sdb_log(SDB_LOG_WARNING, "plugin: %s callback '%s' "
203 "has already been registered. Ignoring newly "
204 "registered version.\n", type, name);
205 return -1;
206 }
208 snprintf(SDB_PLUGIN_CB(obj)->cb_name,
209 sizeof(SDB_PLUGIN_CB(obj)->cb_name),
210 "%s", name);
211 SDB_PLUGIN_CB(obj)->cb_name[sizeof(SDB_PLUGIN_CB(obj)->cb_name) - 1] = '\0';
212 SDB_PLUGIN_CB(obj)->cb_callback = callback;
213 SDB_PLUGIN_CB(obj)->cb_ctx = sdb_plugin_get_ctx();
215 sdb_object_ref(ud);
216 SDB_PLUGIN_CB(obj)->cb_user_data = ud;
217 return 0;
218 } /* sdb_plugin_cb_init */
220 static void
221 sdb_plugin_cb_destroy(sdb_object_t *obj)
222 {
223 assert(obj);
224 sdb_object_deref(SDB_PLUGIN_CB(obj)->cb_user_data);
225 } /* sdb_plugin_cb_destroy */
227 static int
228 sdb_plugin_add_callback(sdb_llist_t **list, const char *type,
229 const char *name, void *callback, sdb_object_t *user_data)
230 {
231 sdb_object_t *obj;
233 if ((! name) || (! callback))
234 return -1;
236 assert(list);
238 if (! *list)
239 *list = sdb_llist_create();
240 if (! *list)
241 return -1;
243 obj = sdb_object_create(sizeof(sdb_plugin_cb_t), sdb_plugin_cb_init,
244 sdb_plugin_cb_destroy, list, type, name, callback, user_data);
245 if (! obj)
246 return -1;
248 if (sdb_llist_append(*list, obj)) {
249 sdb_object_deref(obj);
250 return -1;
251 }
253 /* pass control to the list */
254 sdb_object_deref(obj);
256 sdb_log(SDB_LOG_INFO, "plugin: Registered %s callback '%s'.\n",
257 type, name);
258 return 0;
259 } /* sdb_plugin_add_callback */
261 /*
262 * public API
263 */
265 int
266 sdb_plugin_load(const char *name)
267 {
268 char filename[1024];
270 lt_dlhandle lh;
272 int (*mod_init)(sdb_plugin_info_t *);
273 sdb_plugin_info_t plugin_info = SDB_PLUGIN_INFO_INIT;
275 int status;
277 snprintf(filename, sizeof(filename), "%s/%s.so",
278 PKGLIBDIR, name);
279 filename[sizeof(filename) - 1] = '\0';
281 if (access(filename, R_OK)) {
282 char errbuf[1024];
283 sdb_log(SDB_LOG_ERR, "plugin: Failed to load plugin '%s': %s\n",
284 name, sdb_strerror(errno, errbuf, sizeof(errbuf)));
285 return -1;
286 }
288 lt_dlinit();
289 lt_dlerror();
291 lh = lt_dlopen(filename);
292 if (! lh) {
293 sdb_log(SDB_LOG_ERR, "plugin: Failed to load plugin '%s': %s\n"
294 "The most common cause for this problem are missing "
295 "dependencies.\n", name, lt_dlerror());
296 return -1;
297 }
299 mod_init = (int (*)(sdb_plugin_info_t *))lt_dlsym(lh, "sdb_module_init");
300 if (! mod_init) {
301 sdb_log(SDB_LOG_ERR, "plugin: Failed to load plugin '%s': "
302 "could not find symbol 'sdb_module_init'\n", name);
303 return -1;
304 }
306 status = mod_init(&plugin_info);
307 if (status) {
308 sdb_log(SDB_LOG_ERR, "plugin: Failed to initialize "
309 "plugin '%s'\n", name);
310 return -1;
311 }
313 /* compare minor version */
314 if ((plugin_info.version < 0)
315 || ((int)(plugin_info.version / 100) != (int)(SDB_VERSION / 100)))
316 sdb_log(SDB_LOG_WARNING, "plugin: WARNING: version of "
317 "plugin '%s' (%i.%i.%i) does not match our version "
318 "(%i.%i.%i); this might cause problems\n",
319 name, SDB_VERSION_DECODE(plugin_info.version),
320 SDB_VERSION_DECODE(SDB_VERSION));
322 sdb_log(SDB_LOG_INFO, "plugin: Successfully loaded "
323 "plugin '%s' v%i (%s)\n\t%s\n",
324 plugin_info.name, plugin_info.plugin_version,
325 plugin_info.description, plugin_info.copyright);
326 return 0;
327 } /* sdb_plugin_load */
329 int
330 sdb_plugin_set_info(sdb_plugin_info_t *info, int type, ...)
331 {
332 va_list ap;
334 if (! info)
335 return -1;
337 va_start(ap, type);
339 switch (type) {
340 case SDB_PLUGIN_INFO_NAME:
341 {
342 char *name = va_arg(ap, char *);
343 info->name = name;
344 }
345 break;
346 case SDB_PLUGIN_INFO_DESC:
347 {
348 char *desc = va_arg(ap, char *);
349 info->description = desc;
350 }
351 break;
352 case SDB_PLUGIN_INFO_COPYRIGHT:
353 {
354 char *copyright = va_arg(ap, char *);
355 info->copyright = copyright;
356 }
357 break;
358 case SDB_PLUGIN_INFO_LICENSE:
359 {
360 char *license = va_arg(ap, char *);
361 info->license = license;
362 }
363 break;
364 case SDB_PLUGIN_INFO_VERSION:
365 {
366 int version = va_arg(ap, int);
367 info->version = version;
368 }
369 break;
370 case SDB_PLUGIN_INFO_PLUGIN_VERSION:
371 {
372 int version = va_arg(ap, int);
373 info->plugin_version = version;
374 }
375 break;
376 default:
377 va_end(ap);
378 return -1;
379 }
381 va_end(ap);
382 return 0;
383 } /* sdb_plugin_set_info */
385 int
386 sdb_plugin_register_config(const char *name, sdb_plugin_config_cb callback)
387 {
388 return sdb_plugin_add_callback(&config_list, "init", name,
389 callback, NULL);
390 } /* sdb_plugin_register_config */
392 int
393 sdb_plugin_register_init(const char *name, sdb_plugin_init_cb callback,
394 sdb_object_t *user_data)
395 {
396 return sdb_plugin_add_callback(&init_list, "init", name,
397 callback, user_data);
398 } /* sdb_plugin_register_init */
400 int
401 sdb_plugin_register_shutdown(const char *name, sdb_plugin_shutdown_cb callback,
402 sdb_object_t *user_data)
403 {
404 return sdb_plugin_add_callback(&shutdown_list, "shutdown", name,
405 callback, user_data);
406 } /* sdb_plugin_register_shutdown */
408 int
409 sdb_plugin_register_collector(const char *name, sdb_plugin_collector_cb callback,
410 const sdb_time_t *interval, sdb_object_t *user_data)
411 {
412 sdb_object_t *obj;
414 if ((! name) || (! callback))
415 return -1;
417 if (! collector_list)
418 collector_list = sdb_llist_create();
419 if (! collector_list)
420 return -1;
422 obj = sdb_object_create(sizeof(sdb_plugin_collector_cb_t),
423 sdb_plugin_cb_init, sdb_plugin_cb_destroy,
424 &collector_list, "collector", name, callback, user_data);
425 if (! obj)
426 return -1;
428 if (interval)
429 SDB_PLUGIN_CCB(obj)->ccb_interval = *interval;
430 else {
431 sdb_time_t tmp = sdb_plugin_get_ctx().interval;
433 if (tmp > 0)
434 SDB_PLUGIN_CCB(obj)->ccb_interval = tmp;
435 else
436 SDB_PLUGIN_CCB(obj)->ccb_interval = 0;
437 }
439 if (! (SDB_PLUGIN_CCB(obj)->ccb_next_update = sdb_gettime())) {
440 char errbuf[1024];
441 sdb_log(SDB_LOG_ERR, "plugin: Failed to determine current "
442 "time: %s\n", sdb_strerror(errno, errbuf, sizeof(errbuf)));
443 sdb_object_deref(obj);
444 return -1;
445 }
447 if (sdb_llist_insert_sorted(collector_list, obj,
448 sdb_plugin_cmp_next_update)) {
449 sdb_object_deref(obj);
450 return -1;
451 }
453 /* pass control to the list */
454 sdb_object_deref(obj);
456 sdb_log(SDB_LOG_INFO, "plugin: Registered collector callback '%s' "
457 "(interval = %.3fs).\n", name,
458 SDB_TIME_TO_DOUBLE(SDB_PLUGIN_CCB(obj)->ccb_interval));
459 return 0;
460 } /* sdb_plugin_register_collector */
462 sdb_plugin_ctx_t
463 sdb_plugin_get_ctx(void)
464 {
465 sdb_plugin_ctx_t *ctx;
467 if (! plugin_ctx_key_initialized)
468 sdb_plugin_ctx_init();
469 ctx = pthread_getspecific(plugin_ctx_key);
471 if (! ctx)
472 ctx = sdb_plugin_ctx_create();
473 if (! ctx)
474 return plugin_default_ctx;
475 return *ctx;
476 } /* sdb_plugin_get_ctx */
478 sdb_plugin_ctx_t
479 sdb_plugin_set_ctx(sdb_plugin_ctx_t ctx)
480 {
481 sdb_plugin_ctx_t *tmp;
482 sdb_plugin_ctx_t old;
484 if (! plugin_ctx_key_initialized)
485 sdb_plugin_ctx_init();
486 tmp = pthread_getspecific(plugin_ctx_key);
488 if (! tmp)
489 tmp = sdb_plugin_ctx_create();
490 if (! tmp)
491 return plugin_default_ctx;
493 old = *tmp;
494 *tmp = ctx;
495 return old;
496 } /* sdb_plugin_set_ctx */
498 int
499 sdb_plugin_configure(const char *name, oconfig_item_t *ci)
500 {
501 sdb_plugin_cb_t *plugin;
502 sdb_plugin_config_cb callback;
504 sdb_plugin_ctx_t old_ctx;
506 int status;
508 if ((! name) || (! ci))
509 return -1;
511 plugin = sdb_plugin_find_by_name(config_list, name);
512 if (! plugin) {
513 /* XXX: check if any such plugin has been loaded */
514 sdb_log(SDB_LOG_ERR, "plugin: Plugin '%s' did not register "
515 "a config callback.\n", name);
516 errno = ENOENT;
517 return -1;
518 }
520 old_ctx = sdb_plugin_set_ctx(plugin->cb_ctx);
521 callback = plugin->cb_callback;
522 status = callback(ci);
523 sdb_plugin_set_ctx(old_ctx);
524 return status;
525 } /* sdb_plugin_configure */
527 int
528 sdb_plugin_init_all(void)
529 {
530 sdb_llist_iter_t *iter;
532 iter = sdb_llist_get_iter(init_list);
533 while (sdb_llist_iter_has_next(iter)) {
534 sdb_plugin_init_cb callback;
535 sdb_plugin_ctx_t old_ctx;
537 sdb_object_t *obj = sdb_llist_iter_get_next(iter);
538 assert(obj);
540 callback = SDB_PLUGIN_CB(obj)->cb_callback;
542 old_ctx = sdb_plugin_set_ctx(SDB_PLUGIN_CB(obj)->cb_ctx);
543 if (callback(SDB_PLUGIN_CB(obj)->cb_user_data)) {
544 /* XXX: unload plugin */
545 }
546 sdb_plugin_set_ctx(old_ctx);
547 }
548 return 0;
549 } /* sdb_plugin_init_all */
551 int
552 sdb_plugin_collector_loop(sdb_plugin_loop_t *loop)
553 {
554 if ((! collector_list) || (! loop))
555 return -1;
557 while (loop->do_loop) {
558 sdb_plugin_collector_cb callback;
559 sdb_plugin_ctx_t old_ctx;
561 sdb_time_t interval, now;
563 sdb_object_t *obj = sdb_llist_shift(collector_list);
564 if (! obj)
565 return -1;
567 callback = SDB_PLUGIN_CCB(obj)->ccb_callback;
569 if (! (now = sdb_gettime())) {
570 char errbuf[1024];
571 sdb_log(SDB_LOG_ERR, "plugin: Failed to determine current "
572 "time: %s\n", sdb_strerror(errno, errbuf, sizeof(errbuf)));
573 now = SDB_PLUGIN_CCB(obj)->ccb_next_update;
574 }
576 if (now < SDB_PLUGIN_CCB(obj)->ccb_next_update) {
577 interval = SDB_PLUGIN_CCB(obj)->ccb_next_update - now;
579 errno = 0;
580 while (loop->do_loop && sdb_sleep(interval, &interval)) {
581 if (errno != EINTR) {
582 char errbuf[1024];
583 sdb_log(SDB_LOG_ERR, "plugin: Failed to sleep: %s\n",
584 sdb_strerror(errno, errbuf, sizeof(errbuf)));
585 return -1;
586 }
587 errno = 0;
588 }
590 if (! loop->do_loop)
591 return 0;
592 }
594 old_ctx = sdb_plugin_set_ctx(SDB_PLUGIN_CCB(obj)->ccb_ctx);
595 if (callback(SDB_PLUGIN_CCB(obj)->ccb_user_data)) {
596 /* XXX */
597 }
598 sdb_plugin_set_ctx(old_ctx);
600 interval = SDB_PLUGIN_CCB(obj)->ccb_interval;
601 if (! interval)
602 interval = loop->default_interval;
603 if (! interval) {
604 sdb_log(SDB_LOG_WARNING, "plugin: No interval configured "
605 "for plugin '%s'; skipping any further "
606 "iterations.\n", SDB_PLUGIN_CCB(obj)->ccb_name);
607 sdb_object_deref(obj);
608 continue;
609 }
611 SDB_PLUGIN_CCB(obj)->ccb_next_update += interval;
613 if (! (now = sdb_gettime())) {
614 char errbuf[1024];
615 sdb_log(SDB_LOG_ERR, "plugin: Failed to determine current "
616 "time: %s\n", sdb_strerror(errno, errbuf, sizeof(errbuf)));
617 now = SDB_PLUGIN_CCB(obj)->ccb_next_update;
618 }
620 if (now > SDB_PLUGIN_CCB(obj)->ccb_next_update) {
621 sdb_log(SDB_LOG_WARNING, "plugin: Plugin '%s' took too "
622 "long; skipping iterations to keep up.\n",
623 SDB_PLUGIN_CCB(obj)->ccb_name);
624 SDB_PLUGIN_CCB(obj)->ccb_next_update = now;
625 }
627 if (sdb_llist_insert_sorted(collector_list, obj,
628 sdb_plugin_cmp_next_update)) {
629 sdb_log(SDB_LOG_ERR, "plugin: Failed to re-insert "
630 "plugin '%s' into collector list. Unable to further "
631 "use the plugin.\n",
632 SDB_PLUGIN_CCB(obj)->ccb_name);
633 sdb_object_deref(obj);
634 return -1;
635 }
637 /* pass control back to the list */
638 sdb_object_deref(obj);
639 }
640 return 0;
641 } /* sdb_plugin_read_loop */
643 /* vim: set tw=78 sw=4 ts=4 noexpandtab : */