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 }
87 #define SDB_PLUGIN_CB(obj) ((sdb_plugin_cb_t *)(obj))
88 #define SDB_CONST_PLUGIN_CB(obj) ((const sdb_plugin_cb_t *)(obj))
90 typedef struct {
91 sdb_plugin_cb_t super;
92 #define ccb_callback super.cb_callback
93 #define ccb_user_data super.cb_user_data
94 #define ccb_ctx super.cb_ctx
95 sdb_time_t ccb_interval;
96 sdb_time_t ccb_next_update;
97 } sdb_plugin_collector_cb_t;
98 #define SDB_PLUGIN_CCB(obj) ((sdb_plugin_collector_cb_t *)(obj))
99 #define SDB_CONST_PLUGIN_CCB(obj) ((const sdb_plugin_collector_cb_t *)(obj))
101 typedef struct {
102 sdb_plugin_cb_t super; /* cb_callback will always be NULL */
103 #define w_user_data super.cb_user_data
104 #define w_ctx super.cb_ctx
105 sdb_store_writer_t impl;
106 } sdb_plugin_writer_t;
107 #define SDB_PLUGIN_WRITER(obj) ((sdb_plugin_writer_t *)(obj))
109 /*
110 * private variables
111 */
113 static sdb_plugin_ctx_t plugin_default_ctx = SDB_PLUGIN_CTX_INIT;
114 static sdb_plugin_info_t plugin_default_info = SDB_PLUGIN_INFO_INIT;
116 static pthread_key_t plugin_ctx_key;
117 static bool plugin_ctx_key_initialized = 0;
119 /* a list of the plugin contexts of all registered plugins */
120 static sdb_llist_t *all_plugins = NULL;
122 static sdb_llist_t *config_list = NULL;
123 static sdb_llist_t *init_list = NULL;
124 static sdb_llist_t *collector_list = NULL;
125 static sdb_llist_t *cname_list = NULL;
126 static sdb_llist_t *shutdown_list = NULL;
127 static sdb_llist_t *log_list = NULL;
128 static sdb_llist_t *ts_fetcher_list = NULL;
129 static sdb_llist_t *writer_list = NULL;
131 static struct {
132 const char *type;
133 sdb_llist_t **list;
134 } all_lists[] = {
135 { "config", &config_list },
136 { "init", &init_list },
137 { "collector", &collector_list },
138 { "cname", &cname_list },
139 { "shutdown", &shutdown_list },
140 { "log", &log_list },
141 { "timeseries fetcher", &ts_fetcher_list },
142 { "store writer", &writer_list },
143 };
145 /*
146 * private helper functions
147 */
149 static void
150 plugin_info_clear(sdb_plugin_info_t *info)
151 {
152 sdb_plugin_info_t empty_info = SDB_PLUGIN_INFO_INIT;
153 if (! info)
154 return;
156 if (info->plugin_name)
157 free(info->plugin_name);
158 if (info->filename)
159 free(info->filename);
161 if (info->description)
162 free(info->description);
163 if (info->copyright)
164 free(info->copyright);
165 if (info->license)
166 free(info->license);
168 *info = empty_info;
169 } /* plugin_info_clear */
171 static void
172 ctx_key_init(void)
173 {
174 if (plugin_ctx_key_initialized)
175 return;
177 pthread_key_create(&plugin_ctx_key, /* destructor */ NULL);
178 plugin_ctx_key_initialized = 1;
179 } /* ctx_key_init */
181 static int
182 plugin_cmp_next_update(const sdb_object_t *a, const sdb_object_t *b)
183 {
184 const sdb_plugin_collector_cb_t *ccb1
185 = (const sdb_plugin_collector_cb_t *)a;
186 const sdb_plugin_collector_cb_t *ccb2
187 = (const sdb_plugin_collector_cb_t *)b;
189 assert(ccb1 && ccb2);
191 return (ccb1->ccb_next_update > ccb2->ccb_next_update)
192 ? 1 : (ccb1->ccb_next_update < ccb2->ccb_next_update)
193 ? -1 : 0;
194 } /* plugin_cmp_next_update */
196 static int
197 plugin_lookup_by_name(const sdb_object_t *obj, const void *id)
198 {
199 const sdb_plugin_cb_t *cb = SDB_CONST_PLUGIN_CB(obj);
200 const char *name = id;
202 assert(cb && id);
204 /* when a plugin was registered from outside a plugin (e.g. the core),
205 * we don't have a plugin context */
206 if (! cb->cb_ctx)
207 return 1;
209 if (!strcasecmp(cb->cb_ctx->info.plugin_name, name))
210 return 0;
211 return 1;
212 } /* plugin_lookup_by_name */
214 /* since this function is called from sdb_plugin_reconfigure_finish()
215 * when iterating through all_plugins, we may not do any additional
216 * modifications to all_plugins except for the optional removal */
217 static void
218 plugin_unregister_by_name(const char *plugin_name)
219 {
220 sdb_object_t *obj;
221 size_t i;
223 for (i = 0; i < SDB_STATIC_ARRAY_LEN(all_lists); ++i) {
224 const char *type = all_lists[i].type;
225 sdb_llist_t *list = *all_lists[i].list;
227 while (1) {
228 sdb_plugin_cb_t *cb;
230 cb = SDB_PLUGIN_CB(sdb_llist_remove(list,
231 plugin_lookup_by_name, plugin_name));
232 if (! cb)
233 break;
235 assert(cb->cb_ctx);
237 sdb_log(SDB_LOG_INFO, "core: Unregistering "
238 "%s callback '%s' (module %s)", type, cb->super.name,
239 cb->cb_ctx->info.plugin_name);
240 sdb_object_deref(SDB_OBJ(cb));
241 }
242 }
244 obj = sdb_llist_search_by_name(all_plugins, plugin_name);
245 /* when called from sdb_plugin_reconfigure_finish, the object has already
246 * been removed from the list */
247 if (obj && (obj->ref_cnt <= 1)) {
248 sdb_llist_remove_by_name(all_plugins, plugin_name);
249 sdb_object_deref(obj);
250 }
251 /* else: other callbacks still reference it */
252 } /* plugin_unregister_by_name */
254 /*
255 * private types
256 */
258 static int
259 ctx_init(sdb_object_t *obj, va_list __attribute__((unused)) ap)
260 {
261 ctx_t *ctx = CTX(obj);
263 assert(ctx);
265 ctx->public = plugin_default_ctx;
266 ctx->info = plugin_default_info;
267 ctx->handle = NULL;
268 ctx->use_cnt = 1;
269 return 0;
270 } /* ctx_init */
272 static void
273 ctx_destroy(sdb_object_t *obj)
274 {
275 ctx_t *ctx = CTX(obj);
277 if (ctx->handle) {
278 const char *err;
280 sdb_log(SDB_LOG_INFO, "core: Unloading module %s",
281 ctx->info.plugin_name);
283 lt_dlerror();
284 lt_dlclose(ctx->handle);
285 if ((err = lt_dlerror()))
286 sdb_log(SDB_LOG_WARNING, "core: Failed to unload module %s: %s",
287 ctx->info.plugin_name, err);
288 }
290 plugin_info_clear(&ctx->info);
291 } /* ctx_destroy */
293 static sdb_type_t ctx_type = {
294 sizeof(ctx_t),
296 ctx_init,
297 ctx_destroy
298 };
300 static ctx_t *
301 ctx_get(void)
302 {
303 if (! plugin_ctx_key_initialized)
304 ctx_key_init();
305 return pthread_getspecific(plugin_ctx_key);
306 } /* ctx_get */
308 static ctx_t *
309 ctx_set(ctx_t *new)
310 {
311 ctx_t *old;
313 if (! plugin_ctx_key_initialized)
314 ctx_key_init();
316 old = pthread_getspecific(plugin_ctx_key);
317 if (old)
318 sdb_object_deref(SDB_OBJ(old));
319 if (new)
320 sdb_object_ref(SDB_OBJ(new));
321 pthread_setspecific(plugin_ctx_key, new);
322 return old;
323 } /* ctx_set */
325 static ctx_t *
326 ctx_create(const char *name)
327 {
328 ctx_t *ctx;
330 ctx = CTX(sdb_object_create(name, ctx_type));
331 if (! ctx)
332 return NULL;
334 if (! plugin_ctx_key_initialized)
335 ctx_key_init();
336 ctx_set(ctx);
337 return ctx;
338 } /* ctx_create */
340 static int
341 plugin_cb_init(sdb_object_t *obj, va_list ap)
342 {
343 sdb_llist_t **list = va_arg(ap, sdb_llist_t **);
344 const char *type = va_arg(ap, const char *);
345 void *callback = va_arg(ap, void *);
346 sdb_object_t *ud = va_arg(ap, sdb_object_t *);
348 assert(list);
349 assert(type);
350 assert(obj);
352 if (sdb_llist_search_by_name(*list, obj->name)) {
353 sdb_log(SDB_LOG_WARNING, "core: %s callback '%s' "
354 "has already been registered. Ignoring newly "
355 "registered version.", type, obj->name);
356 return -1;
357 }
359 /* cb_ctx may be NULL if the plugin was not registered by a plugin */
361 SDB_PLUGIN_CB(obj)->cb_callback = callback;
362 SDB_PLUGIN_CB(obj)->cb_ctx = ctx_get();
363 sdb_object_ref(SDB_OBJ(SDB_PLUGIN_CB(obj)->cb_ctx));
365 sdb_object_ref(ud);
366 SDB_PLUGIN_CB(obj)->cb_user_data = ud;
367 return 0;
368 } /* plugin_cb_init */
370 static void
371 plugin_cb_destroy(sdb_object_t *obj)
372 {
373 assert(obj);
374 sdb_object_deref(SDB_PLUGIN_CB(obj)->cb_user_data);
375 sdb_object_deref(SDB_OBJ(SDB_PLUGIN_CB(obj)->cb_ctx));
376 } /* plugin_cb_destroy */
378 static sdb_type_t sdb_plugin_cb_type = {
379 sizeof(sdb_plugin_cb_t),
381 plugin_cb_init,
382 plugin_cb_destroy
383 };
385 static sdb_type_t sdb_plugin_collector_cb_type = {
386 sizeof(sdb_plugin_collector_cb_t),
388 plugin_cb_init,
389 plugin_cb_destroy
390 };
392 static int
393 plugin_writer_init(sdb_object_t *obj, va_list ap)
394 {
395 sdb_store_writer_t *impl = va_arg(ap, sdb_store_writer_t *);
396 sdb_object_t *ud = va_arg(ap, sdb_object_t *);
398 assert(impl);
400 if ((! impl->store_host) || (! impl->store_service)
401 || (! impl->store_metric) || (! impl->store_attribute)
402 || (! impl->store_service_attr) || (! impl->store_metric_attr)) {
403 sdb_log(SDB_LOG_ERR, "core: store writer callback '%s' "
404 "does not fully implement the writer interface.",
405 obj->name);
406 return -1;
407 }
408 if (sdb_llist_search_by_name(writer_list, obj->name)) {
409 sdb_log(SDB_LOG_WARNING, "core: store writer callback '%s' "
410 "has already been registered. Ignoring newly "
411 "registered version.", obj->name);
412 return -1;
413 }
415 /* ctx may be NULL if the plugin was not registered by a plugin */
417 SDB_PLUGIN_WRITER(obj)->impl = *impl;
418 SDB_PLUGIN_WRITER(obj)->w_ctx = ctx_get();
419 sdb_object_ref(SDB_OBJ(SDB_PLUGIN_WRITER(obj)->w_ctx));
421 sdb_object_ref(ud);
422 SDB_PLUGIN_WRITER(obj)->w_user_data = ud;
423 return 0;
424 } /* plugin_writer_init */
426 static void
427 plugin_writer_destroy(sdb_object_t *obj)
428 {
429 assert(obj);
430 sdb_object_deref(SDB_PLUGIN_WRITER(obj)->w_user_data);
431 sdb_object_deref(SDB_OBJ(SDB_PLUGIN_WRITER(obj)->w_ctx));
432 } /* plugin_writer_destroy */
434 static sdb_type_t sdb_plugin_writer_type = {
435 sizeof(sdb_plugin_writer_t),
437 plugin_writer_init,
438 plugin_writer_destroy
439 };
441 static int
442 module_init(const char *name, lt_dlhandle lh, sdb_plugin_info_t *info)
443 {
444 int (*mod_init)(sdb_plugin_info_t *);
445 int status;
447 mod_init = (int (*)(sdb_plugin_info_t *))lt_dlsym(lh, "sdb_module_init");
448 if (! mod_init) {
449 sdb_log(SDB_LOG_ERR, "core: Failed to load plugin '%s': "
450 "could not find symbol 'sdb_module_init'", name);
451 return -1;
452 }
454 status = mod_init(info);
455 if (status) {
456 sdb_log(SDB_LOG_ERR, "core: Failed to initialize "
457 "module '%s'", name);
458 plugin_unregister_by_name(name);
459 return -1;
460 }
461 return 0;
462 } /* module_init */
464 static int
465 module_load(const char *basedir, const char *name,
466 const sdb_plugin_ctx_t *plugin_ctx)
467 {
468 char base_name[name ? strlen(name) + 1 : 1];
469 const char *name_ptr;
470 char *tmp;
472 char filename[1024];
473 lt_dlhandle lh;
475 ctx_t *ctx;
477 int status;
479 assert(name);
481 base_name[0] = '\0';
482 name_ptr = name;
484 while ((tmp = strstr(name_ptr, "::"))) {
485 strncat(base_name, name_ptr, (size_t)(tmp - name_ptr));
486 strcat(base_name, "/");
487 name_ptr = tmp + strlen("::");
488 }
489 strcat(base_name, name_ptr);
491 if (! basedir)
492 basedir = PKGLIBDIR;
494 snprintf(filename, sizeof(filename), "%s/%s.so", basedir, base_name);
495 filename[sizeof(filename) - 1] = '\0';
497 if (access(filename, R_OK)) {
498 char errbuf[1024];
499 sdb_log(SDB_LOG_ERR, "core: Failed to load plugin '%s' (%s): %s",
500 name, filename, sdb_strerror(errno, errbuf, sizeof(errbuf)));
501 return -1;
502 }
504 lt_dlinit();
505 lt_dlerror();
507 lh = lt_dlopen(filename);
508 if (! lh) {
509 sdb_log(SDB_LOG_ERR, "core: Failed to load plugin '%s': %s"
510 "The most common cause for this problem are missing "
511 "dependencies.\n", name, lt_dlerror());
512 return -1;
513 }
515 if (ctx_get())
516 sdb_log(SDB_LOG_WARNING, "core: Discarding old plugin context");
518 ctx = ctx_create(name);
519 if (! ctx) {
520 sdb_log(SDB_LOG_ERR, "core: Failed to initialize plugin context");
521 return -1;
522 }
524 ctx->info.plugin_name = strdup(name);
525 ctx->info.filename = strdup(filename);
526 ctx->handle = lh;
528 if (plugin_ctx)
529 ctx->public = *plugin_ctx;
531 if ((status = module_init(name, lh, &ctx->info))) {
532 sdb_object_deref(SDB_OBJ(ctx));
533 return status;
534 }
536 /* compare minor version */
537 if ((ctx->info.version < 0)
538 || ((int)(ctx->info.version / 100) != (int)(SDB_VERSION / 100)))
539 sdb_log(SDB_LOG_WARNING, "core: WARNING: version of "
540 "plugin '%s' (%i.%i.%i) does not match our version "
541 "(%i.%i.%i); this might cause problems",
542 name, SDB_VERSION_DECODE(ctx->info.version),
543 SDB_VERSION_DECODE(SDB_VERSION));
545 sdb_llist_append(all_plugins, SDB_OBJ(ctx));
547 sdb_log(SDB_LOG_INFO, "core: Successfully loaded "
548 "plugin %s v%i (%s)", ctx->info.plugin_name,
549 ctx->info.plugin_version,
550 INFO_GET(&ctx->info, description));
551 sdb_log(SDB_LOG_INFO, "core: Plugin %s: %s, License: %s",
552 ctx->info.plugin_name,
553 INFO_GET(&ctx->info, copyright),
554 INFO_GET(&ctx->info, license));
556 /* any registered callbacks took ownership of the context */
557 sdb_object_deref(SDB_OBJ(ctx));
559 /* reset */
560 ctx_set(NULL);
561 return 0;
562 } /* module_load */
564 static char *
565 plugin_get_name(const char *name, char *buf, size_t bufsize)
566 {
567 ctx_t *ctx = ctx_get();
569 if (ctx)
570 snprintf(buf, bufsize, "%s::%s", ctx->info.plugin_name, name);
571 else
572 snprintf(buf, bufsize, "core::%s", name);
573 return buf;
574 } /* plugin_get_name */
576 static int
577 plugin_add_callback(sdb_llist_t **list, const char *type,
578 const char *name, void *callback, sdb_object_t *user_data)
579 {
580 sdb_object_t *obj;
582 if ((! name) || (! callback))
583 return -1;
585 assert(list);
587 if (! *list)
588 *list = sdb_llist_create();
589 if (! *list)
590 return -1;
592 obj = sdb_object_create(name, sdb_plugin_cb_type,
593 list, type, callback, user_data);
594 if (! obj)
595 return -1;
597 if (sdb_llist_append(*list, obj)) {
598 sdb_object_deref(obj);
599 return -1;
600 }
602 /* pass control to the list */
603 sdb_object_deref(obj);
605 sdb_log(SDB_LOG_INFO, "core: Registered %s callback '%s'.",
606 type, name);
607 return 0;
608 } /* plugin_add_callback */
610 /*
611 * public API
612 */
614 int
615 sdb_plugin_load(const char *basedir, const char *name,
616 const sdb_plugin_ctx_t *plugin_ctx)
617 {
618 ctx_t *ctx;
620 int status;
622 if ((! name) || (! *name))
623 return -1;
625 if (! all_plugins) {
626 if (! (all_plugins = sdb_llist_create())) {
627 sdb_log(SDB_LOG_ERR, "core: Failed to load plugin '%s': "
628 "internal error while creating linked list", name);
629 return -1;
630 }
631 }
633 ctx = CTX(sdb_llist_search_by_name(all_plugins, name));
634 if (ctx) {
635 /* plugin already loaded */
636 if (! ctx->use_cnt) {
637 /* reloading plugin */
638 ctx_t *old_ctx = ctx_set(ctx);
640 status = module_init(ctx->info.plugin_name, ctx->handle, NULL);
641 if (status)
642 return status;
644 sdb_log(SDB_LOG_INFO, "core: Successfully reloaded plugin "
645 "'%s' (%s)", ctx->info.plugin_name,
646 INFO_GET(&ctx->info, description));
647 ctx_set(old_ctx);
648 }
649 ++ctx->use_cnt;
650 return 0;
651 }
653 return module_load(basedir, name, plugin_ctx);
654 } /* sdb_plugin_load */
656 int
657 sdb_plugin_set_info(sdb_plugin_info_t *info, int type, ...)
658 {
659 va_list ap;
661 if (! info)
662 return -1;
664 va_start(ap, type);
666 switch (type) {
667 case SDB_PLUGIN_INFO_DESC:
668 {
669 char *desc = va_arg(ap, char *);
670 if (desc) {
671 if (info->description)
672 free(info->description);
673 info->description = strdup(desc);
674 }
675 }
676 break;
677 case SDB_PLUGIN_INFO_COPYRIGHT:
678 {
679 char *copyright = va_arg(ap, char *);
680 if (copyright)
681 info->copyright = strdup(copyright);
682 }
683 break;
684 case SDB_PLUGIN_INFO_LICENSE:
685 {
686 char *license = va_arg(ap, char *);
687 if (license) {
688 if (info->license)
689 free(info->license);
690 info->license = strdup(license);
691 }
692 }
693 break;
694 case SDB_PLUGIN_INFO_VERSION:
695 {
696 int version = va_arg(ap, int);
697 info->version = version;
698 }
699 break;
700 case SDB_PLUGIN_INFO_PLUGIN_VERSION:
701 {
702 int version = va_arg(ap, int);
703 info->plugin_version = version;
704 }
705 break;
706 default:
707 va_end(ap);
708 return -1;
709 }
711 va_end(ap);
712 return 0;
713 } /* sdb_plugin_set_info */
715 int
716 sdb_plugin_register_config(sdb_plugin_config_cb callback)
717 {
718 ctx_t *ctx = ctx_get();
720 if (! ctx) {
721 sdb_log(SDB_LOG_ERR, "core: Invalid attempt to register a "
722 "config callback from outside a plugin");
723 return -1;
724 }
725 return plugin_add_callback(&config_list, "config", ctx->info.plugin_name,
726 (void *)callback, NULL);
727 } /* sdb_plugin_register_config */
729 int
730 sdb_plugin_register_init(const char *name, sdb_plugin_init_cb callback,
731 sdb_object_t *user_data)
732 {
733 char cb_name[1024];
734 return plugin_add_callback(&init_list, "init",
735 plugin_get_name(name, cb_name, sizeof(cb_name)),
736 (void *)callback, user_data);
737 } /* sdb_plugin_register_init */
739 int
740 sdb_plugin_register_shutdown(const char *name, sdb_plugin_shutdown_cb callback,
741 sdb_object_t *user_data)
742 {
743 char cb_name[1024];
744 return plugin_add_callback(&shutdown_list, "shutdown",
745 plugin_get_name(name, cb_name, sizeof(cb_name)),
746 (void *)callback, user_data);
747 } /* sdb_plugin_register_shutdown */
749 int
750 sdb_plugin_register_log(const char *name, sdb_plugin_log_cb callback,
751 sdb_object_t *user_data)
752 {
753 char cb_name[1024];
754 return plugin_add_callback(&log_list, "log",
755 plugin_get_name(name, cb_name, sizeof(cb_name)),
756 callback, user_data);
757 } /* sdb_plugin_register_log */
759 int
760 sdb_plugin_register_cname(const char *name, sdb_plugin_cname_cb callback,
761 sdb_object_t *user_data)
762 {
763 char cb_name[1024];
764 return plugin_add_callback(&cname_list, "cname",
765 plugin_get_name(name, cb_name, sizeof(cb_name)),
766 callback, user_data);
767 } /* sdb_plugin_register_cname */
769 int
770 sdb_plugin_register_collector(const char *name, sdb_plugin_collector_cb callback,
771 const sdb_time_t *interval, sdb_object_t *user_data)
772 {
773 char cb_name[1024];
774 sdb_object_t *obj;
776 if ((! name) || (! callback))
777 return -1;
779 if (! collector_list)
780 collector_list = sdb_llist_create();
781 if (! collector_list)
782 return -1;
784 plugin_get_name(name, cb_name, sizeof(cb_name));
786 obj = sdb_object_create(cb_name, sdb_plugin_collector_cb_type,
787 &collector_list, "collector", callback, user_data);
788 if (! obj)
789 return -1;
791 if (interval)
792 SDB_PLUGIN_CCB(obj)->ccb_interval = *interval;
793 else {
794 ctx_t *ctx = ctx_get();
796 if (! ctx) {
797 sdb_log(SDB_LOG_ERR, "core: Cannot determine interval "
798 "for collector %s; none specified and no plugin "
799 "context found", cb_name);
800 return -1;
801 }
803 SDB_PLUGIN_CCB(obj)->ccb_interval = ctx->public.interval;
804 }
806 if (! (SDB_PLUGIN_CCB(obj)->ccb_next_update = sdb_gettime())) {
807 char errbuf[1024];
808 sdb_log(SDB_LOG_ERR, "core: Failed to determine current "
809 "time: %s", sdb_strerror(errno, errbuf, sizeof(errbuf)));
810 sdb_object_deref(obj);
811 return -1;
812 }
814 if (sdb_llist_insert_sorted(collector_list, obj,
815 plugin_cmp_next_update)) {
816 sdb_object_deref(obj);
817 return -1;
818 }
820 /* pass control to the list */
821 sdb_object_deref(obj);
823 sdb_log(SDB_LOG_INFO, "core: Registered collector callback '%s' "
824 "(interval = %.3fs).", cb_name,
825 SDB_TIME_TO_DOUBLE(SDB_PLUGIN_CCB(obj)->ccb_interval));
826 return 0;
827 } /* sdb_plugin_register_collector */
829 int
830 sdb_plugin_register_ts_fetcher(const char *name,
831 sdb_plugin_fetch_ts_cb callback, sdb_object_t *user_data)
832 {
833 return plugin_add_callback(&ts_fetcher_list, "time-series fetcher",
834 name, callback, user_data);
835 } /* sdb_plugin_register_ts_fetcher */
837 int
838 sdb_plugin_register_writer(const char *name,
839 sdb_store_writer_t *writer, sdb_object_t *user_data)
840 {
841 char cb_name[1024];
842 sdb_object_t *obj;
844 if ((! name) || (! writer))
845 return -1;
847 if (! writer_list)
848 writer_list = sdb_llist_create();
849 if (! writer_list)
850 return -1;
852 plugin_get_name(name, cb_name, sizeof(cb_name));
854 obj = sdb_object_create(cb_name, sdb_plugin_writer_type,
855 writer, user_data);
856 if (! obj)
857 return -1;
859 if (sdb_llist_append(writer_list, obj)) {
860 sdb_object_deref(obj);
861 return -1;
862 }
864 /* pass control to the list */
865 sdb_object_deref(obj);
867 sdb_log(SDB_LOG_INFO, "core: Registered store writer callback '%s'.",
868 cb_name);
869 return 0;
870 } /* sdb_store_register_writer */
872 void
873 sdb_plugin_unregister_all(void)
874 {
875 size_t i;
877 for (i = 0; i < SDB_STATIC_ARRAY_LEN(all_lists); ++i) {
878 const char *type = all_lists[i].type;
879 sdb_llist_t *list = *all_lists[i].list;
881 size_t len = sdb_llist_len(list);
883 if (! len)
884 continue;
886 sdb_llist_clear(list);
887 sdb_log(SDB_LOG_INFO, "core: Unregistered %zu %s callback%s",
888 len, type, len == 1 ? "" : "s");
889 }
890 } /* sdb_plugin_unregister_all */
892 sdb_plugin_ctx_t
893 sdb_plugin_get_ctx(void)
894 {
895 ctx_t *c;
897 c = ctx_get();
898 if (! c) {
899 sdb_plugin_log(SDB_LOG_ERR, "core: Invalid read access to plugin "
900 "context outside a plugin");
901 return plugin_default_ctx;
902 }
903 return c->public;
904 } /* sdb_plugin_get_ctx */
906 int
907 sdb_plugin_set_ctx(sdb_plugin_ctx_t ctx, sdb_plugin_ctx_t *old)
908 {
909 ctx_t *c;
911 c = ctx_get();
912 if (! c) {
913 sdb_plugin_log(SDB_LOG_ERR, "core: Invalid write access to plugin "
914 "context outside a plugin");
915 return -1;
916 }
918 if (old)
919 *old = c->public;
920 c->public = ctx;
921 return 0;
922 } /* sdb_plugin_set_ctx */
924 const sdb_plugin_info_t *
925 sdb_plugin_current(void)
926 {
927 ctx_t *ctx = ctx_get();
929 if (! ctx)
930 return NULL;
931 return &ctx->info;
932 } /* sdb_plugin_current */
934 int
935 sdb_plugin_configure(const char *name, oconfig_item_t *ci)
936 {
937 sdb_plugin_cb_t *plugin;
938 sdb_plugin_config_cb callback;
940 ctx_t *old_ctx;
942 int status;
944 if ((! name) || (! ci))
945 return -1;
947 plugin = SDB_PLUGIN_CB(sdb_llist_search_by_name(config_list, name));
948 if (! plugin) {
949 ctx_t *ctx = CTX(sdb_llist_search_by_name(all_plugins, name));
950 if (! ctx)
951 sdb_log(SDB_LOG_ERR, "core: Cannot configure unknown "
952 "plugin '%s'. Missing 'LoadPlugin \"%s\"'?",
953 name, name);
954 else
955 sdb_log(SDB_LOG_ERR, "core: Plugin '%s' did not register "
956 "a config callback.", name);
957 errno = ENOENT;
958 return -1;
959 }
961 old_ctx = ctx_set(plugin->cb_ctx);
962 callback = (sdb_plugin_config_cb)plugin->cb_callback;
963 status = callback(ci);
964 ctx_set(old_ctx);
965 return status;
966 } /* sdb_plugin_configure */
968 int
969 sdb_plugin_reconfigure_init(void)
970 {
971 sdb_llist_iter_t *iter;
973 iter = sdb_llist_get_iter(config_list);
974 if (config_list && (! iter))
975 return -1;
977 /* deconfigure all plugins */
978 while (sdb_llist_iter_has_next(iter)) {
979 sdb_plugin_cb_t *plugin;
980 sdb_plugin_config_cb callback;
981 ctx_t *old_ctx;
983 plugin = SDB_PLUGIN_CB(sdb_llist_iter_get_next(iter));
984 old_ctx = ctx_set(plugin->cb_ctx);
985 callback = (sdb_plugin_config_cb)plugin->cb_callback;
986 callback(NULL);
987 ctx_set(old_ctx);
988 }
989 sdb_llist_iter_destroy(iter);
991 iter = sdb_llist_get_iter(all_plugins);
992 if (all_plugins && (! iter))
993 return -1;
995 /* record all plugins as being unused */
996 while (sdb_llist_iter_has_next(iter))
997 CTX(sdb_llist_iter_get_next(iter))->use_cnt = 0;
998 sdb_llist_iter_destroy(iter);
1000 sdb_plugin_unregister_all();
1001 return 0;
1002 } /* sdb_plugin_reconfigure_init */
1004 int
1005 sdb_plugin_reconfigure_finish(void)
1006 {
1007 sdb_llist_iter_t *iter;
1009 iter = sdb_llist_get_iter(all_plugins);
1010 if (all_plugins && (! iter))
1011 return -1;
1013 while (sdb_llist_iter_has_next(iter)) {
1014 ctx_t *ctx = CTX(sdb_llist_iter_get_next(iter));
1015 if (ctx->use_cnt)
1016 continue;
1018 sdb_log(SDB_LOG_INFO, "core: Module %s no longer in use",
1019 ctx->info.plugin_name);
1020 sdb_llist_iter_remove_current(iter);
1021 plugin_unregister_by_name(ctx->info.plugin_name);
1022 sdb_object_deref(SDB_OBJ(ctx));
1023 }
1024 sdb_llist_iter_destroy(iter);
1025 return 0;
1026 } /* sdb_plugin_reconfigure_finish */
1028 int
1029 sdb_plugin_init_all(void)
1030 {
1031 sdb_llist_iter_t *iter;
1032 int ret = 0;
1034 iter = sdb_llist_get_iter(init_list);
1035 while (sdb_llist_iter_has_next(iter)) {
1036 sdb_plugin_cb_t *cb;
1037 sdb_plugin_init_cb callback;
1038 ctx_t *old_ctx;
1040 sdb_object_t *obj = sdb_llist_iter_get_next(iter);
1041 assert(obj);
1042 cb = SDB_PLUGIN_CB(obj);
1044 callback = (sdb_plugin_init_cb)cb->cb_callback;
1046 old_ctx = ctx_set(cb->cb_ctx);
1047 if (callback(cb->cb_user_data)) {
1048 sdb_log(SDB_LOG_ERR, "core: Failed to initialize plugin "
1049 "'%s'. Unregistering all callbacks.", obj->name);
1050 ctx_set(old_ctx);
1051 plugin_unregister_by_name(cb->cb_ctx->info.plugin_name);
1052 ++ret;
1053 }
1054 else
1055 ctx_set(old_ctx);
1056 }
1057 sdb_llist_iter_destroy(iter);
1058 return ret;
1059 } /* sdb_plugin_init_all */
1061 int
1062 sdb_plugin_shutdown_all(void)
1063 {
1064 sdb_llist_iter_t *iter;
1065 int ret = 0;
1067 iter = sdb_llist_get_iter(shutdown_list);
1068 while (sdb_llist_iter_has_next(iter)) {
1069 sdb_plugin_cb_t *cb;
1070 sdb_plugin_shutdown_cb callback;
1071 ctx_t *old_ctx;
1073 sdb_object_t *obj = sdb_llist_iter_get_next(iter);
1074 assert(obj);
1075 cb = SDB_PLUGIN_CB(obj);
1077 callback = (sdb_plugin_shutdown_cb)cb->cb_callback;
1079 old_ctx = ctx_set(cb->cb_ctx);
1080 if (callback(cb->cb_user_data)) {
1081 sdb_log(SDB_LOG_ERR, "core: Failed to shutdown plugin '%s'.",
1082 obj->name);
1083 ++ret;
1084 }
1085 ctx_set(old_ctx);
1086 }
1087 sdb_llist_iter_destroy(iter);
1088 return ret;
1089 } /* sdb_plugin_shutdown_all */
1091 int
1092 sdb_plugin_collector_loop(sdb_plugin_loop_t *loop)
1093 {
1094 if (! collector_list) {
1095 sdb_log(SDB_LOG_WARNING, "core: No collectors registered. "
1096 "Quiting main loop.");
1097 return -1;
1098 }
1100 if (! loop)
1101 return -1;
1103 while (loop->do_loop) {
1104 sdb_plugin_collector_cb callback;
1105 ctx_t *old_ctx;
1107 sdb_time_t interval, now;
1109 sdb_object_t *obj = sdb_llist_shift(collector_list);
1110 if (! obj)
1111 return -1;
1113 callback = (sdb_plugin_collector_cb)SDB_PLUGIN_CCB(obj)->ccb_callback;
1115 if (! (now = sdb_gettime())) {
1116 char errbuf[1024];
1117 sdb_log(SDB_LOG_ERR, "core: Failed to determine current "
1118 "time in collector main loop: %s",
1119 sdb_strerror(errno, errbuf, sizeof(errbuf)));
1120 now = SDB_PLUGIN_CCB(obj)->ccb_next_update;
1121 }
1123 if (now < SDB_PLUGIN_CCB(obj)->ccb_next_update) {
1124 interval = SDB_PLUGIN_CCB(obj)->ccb_next_update - now;
1126 errno = 0;
1127 while (loop->do_loop && sdb_sleep(interval, &interval)) {
1128 if (errno != EINTR) {
1129 char errbuf[1024];
1130 sdb_log(SDB_LOG_ERR, "core: Failed to sleep "
1131 "in collector main loop: %s",
1132 sdb_strerror(errno, errbuf, sizeof(errbuf)));
1133 sdb_llist_insert_sorted(collector_list, obj,
1134 plugin_cmp_next_update);
1135 sdb_object_deref(obj);
1136 return -1;
1137 }
1138 errno = 0;
1139 }
1141 if (! loop->do_loop) {
1142 /* put back; don't worry about errors */
1143 sdb_llist_insert_sorted(collector_list, obj,
1144 plugin_cmp_next_update);
1145 sdb_object_deref(obj);
1146 return 0;
1147 }
1148 }
1150 old_ctx = ctx_set(SDB_PLUGIN_CCB(obj)->ccb_ctx);
1151 if (callback(SDB_PLUGIN_CCB(obj)->ccb_user_data)) {
1152 /* XXX */
1153 }
1154 ctx_set(old_ctx);
1156 interval = SDB_PLUGIN_CCB(obj)->ccb_interval;
1157 if (! interval)
1158 interval = loop->default_interval;
1159 if (! interval) {
1160 sdb_log(SDB_LOG_WARNING, "core: No interval configured "
1161 "for plugin '%s'; skipping any further "
1162 "iterations.", obj->name);
1163 sdb_object_deref(obj);
1164 continue;
1165 }
1167 SDB_PLUGIN_CCB(obj)->ccb_next_update += interval;
1169 if (! (now = sdb_gettime())) {
1170 char errbuf[1024];
1171 sdb_log(SDB_LOG_ERR, "core: Failed to determine current "
1172 "time in collector main loop: %s",
1173 sdb_strerror(errno, errbuf, sizeof(errbuf)));
1174 now = SDB_PLUGIN_CCB(obj)->ccb_next_update;
1175 }
1177 if (now > SDB_PLUGIN_CCB(obj)->ccb_next_update) {
1178 sdb_log(SDB_LOG_WARNING, "core: Plugin '%s' took too "
1179 "long; skipping iterations to keep up.",
1180 obj->name);
1181 SDB_PLUGIN_CCB(obj)->ccb_next_update = now;
1182 }
1184 if (sdb_llist_insert_sorted(collector_list, obj,
1185 plugin_cmp_next_update)) {
1186 sdb_log(SDB_LOG_ERR, "core: Failed to re-insert "
1187 "plugin '%s' into collector list. Unable to further "
1188 "use the plugin.",
1189 obj->name);
1190 sdb_object_deref(obj);
1191 return -1;
1192 }
1194 /* pass control back to the list */
1195 sdb_object_deref(obj);
1196 }
1197 return 0;
1198 } /* sdb_plugin_read_loop */
1200 char *
1201 sdb_plugin_cname(char *hostname)
1202 {
1203 sdb_llist_iter_t *iter;
1205 if (! hostname)
1206 return NULL;
1208 if (! cname_list)
1209 return hostname;
1211 iter = sdb_llist_get_iter(cname_list);
1212 while (sdb_llist_iter_has_next(iter)) {
1213 sdb_plugin_cname_cb callback;
1214 char *cname;
1216 sdb_object_t *obj = sdb_llist_iter_get_next(iter);
1217 assert(obj);
1219 callback = (sdb_plugin_cname_cb)SDB_PLUGIN_CB(obj)->cb_callback;
1220 cname = callback(hostname, SDB_PLUGIN_CB(obj)->cb_user_data);
1221 if (cname) {
1222 free(hostname);
1223 hostname = cname;
1224 }
1225 /* else: don't change hostname */
1226 }
1227 sdb_llist_iter_destroy(iter);
1228 return hostname;
1229 } /* sdb_plugin_cname */
1231 int
1232 sdb_plugin_log(int prio, const char *msg)
1233 {
1234 sdb_llist_iter_t *iter;
1235 int ret = -1;
1237 bool logged = 0;
1239 if (! msg)
1240 return 0;
1242 iter = sdb_llist_get_iter(log_list);
1243 while (sdb_llist_iter_has_next(iter)) {
1244 sdb_plugin_log_cb callback;
1245 int tmp;
1247 sdb_object_t *obj = sdb_llist_iter_get_next(iter);
1248 assert(obj);
1250 callback = (sdb_plugin_log_cb)SDB_PLUGIN_CB(obj)->cb_callback;
1251 tmp = callback(prio, msg, SDB_PLUGIN_CB(obj)->cb_user_data);
1252 if (tmp > ret)
1253 ret = tmp;
1255 if (SDB_PLUGIN_CB(obj)->cb_ctx)
1256 logged = 1;
1257 /* else: this is an internally registered callback */
1258 }
1259 sdb_llist_iter_destroy(iter);
1261 if (! logged)
1262 return fprintf(stderr, "[%s] %s\n", SDB_LOG_PRIO_TO_STRING(prio), msg);
1263 return ret;
1264 } /* sdb_plugin_log */
1266 int
1267 sdb_plugin_vlogf(int prio, const char *fmt, va_list ap)
1268 {
1269 sdb_strbuf_t *buf;
1270 int ret;
1272 if (! fmt)
1273 return 0;
1275 buf = sdb_strbuf_create(64);
1276 if (! buf) {
1277 ret = fprintf(stderr, "[%s] ", SDB_LOG_PRIO_TO_STRING(prio));
1278 ret += vfprintf(stderr, fmt, ap);
1279 return ret;
1280 }
1282 if (sdb_strbuf_vsprintf(buf, fmt, ap) < 0) {
1283 sdb_strbuf_destroy(buf);
1284 return -1;
1285 }
1287 ret = sdb_plugin_log(prio, sdb_strbuf_string(buf));
1288 sdb_strbuf_destroy(buf);
1289 return ret;
1290 } /* sdb_plugin_vlogf */
1292 int
1293 sdb_plugin_logf(int prio, const char *fmt, ...)
1294 {
1295 va_list ap;
1296 int ret;
1298 if (! fmt)
1299 return 0;
1301 va_start(ap, fmt);
1302 ret = sdb_plugin_vlogf(prio, fmt, ap);
1303 va_end(ap);
1304 return ret;
1305 } /* sdb_plugin_logf */
1307 sdb_timeseries_t *
1308 sdb_plugin_fetch_timeseries(const char *type, const char *id,
1309 sdb_timeseries_opts_t *opts)
1310 {
1311 sdb_plugin_cb_t *plugin;
1312 sdb_plugin_fetch_ts_cb callback;
1313 sdb_timeseries_t *ts;
1315 ctx_t *old_ctx;
1317 if ((! type) || (! id) || (! opts))
1318 return NULL;
1320 plugin = SDB_PLUGIN_CB(sdb_llist_search_by_name(ts_fetcher_list, type));
1321 if (! plugin) {
1322 sdb_log(SDB_LOG_ERR, "core: Cannot fetch time-series of type %s: "
1323 "no such plugin loaded", type);
1324 errno = ENOENT;
1325 return NULL;
1326 }
1328 old_ctx = ctx_set(plugin->cb_ctx);
1329 callback = (sdb_plugin_fetch_ts_cb)plugin->cb_callback;
1330 ts = callback(id, opts, plugin->cb_user_data);
1331 ctx_set(old_ctx);
1332 return ts;
1333 } /* sdb_plugin_fetch_timeseries */
1335 int
1336 sdb_plugin_store_host(const char *name, sdb_time_t last_update)
1337 {
1338 sdb_llist_iter_t *iter;
1339 int status = 0;
1341 if (! name)
1342 return -1;
1344 iter = sdb_llist_get_iter(writer_list);
1345 while (sdb_llist_iter_has_next(iter)) {
1346 sdb_plugin_writer_t *writer;
1347 int s;
1348 writer = SDB_PLUGIN_WRITER(sdb_llist_iter_get_next(iter));
1349 assert(writer);
1350 s = writer->impl.store_host(name, last_update, writer->w_user_data);
1351 if (((s > 0) && (status >= 0)) || (s < 0))
1352 status = s;
1353 }
1354 sdb_llist_iter_destroy(iter);
1355 return status;
1356 } /* sdb_plugin_store_host */
1358 int
1359 sdb_plugin_store_service(const char *hostname, const char *name,
1360 sdb_time_t last_update)
1361 {
1362 sdb_llist_iter_t *iter;
1363 int status = 0;
1365 if ((! hostname) || (! name))
1366 return -1;
1368 iter = sdb_llist_get_iter(writer_list);
1369 while (sdb_llist_iter_has_next(iter)) {
1370 sdb_plugin_writer_t *writer;
1371 int s;
1372 writer = SDB_PLUGIN_WRITER(sdb_llist_iter_get_next(iter));
1373 assert(writer);
1374 s = writer->impl.store_service(hostname, name, last_update,
1375 writer->w_user_data);
1376 if (((s > 0) && (status >= 0)) || (s < 0))
1377 status = s;
1378 }
1379 sdb_llist_iter_destroy(iter);
1380 return status;
1381 } /* sdb_plugin_store_service */
1383 int
1384 sdb_plugin_store_metric(const char *hostname, const char *name,
1385 sdb_metric_store_t *store, sdb_time_t last_update)
1386 {
1387 sdb_llist_iter_t *iter;
1388 int status = 0;
1390 if ((! hostname) || (! name))
1391 return -1;
1393 if (store && ((! store->type) || (! store->id)))
1394 store = NULL;
1396 iter = sdb_llist_get_iter(writer_list);
1397 while (sdb_llist_iter_has_next(iter)) {
1398 sdb_plugin_writer_t *writer;
1399 int s;
1400 writer = SDB_PLUGIN_WRITER(sdb_llist_iter_get_next(iter));
1401 assert(writer);
1402 s = writer->impl.store_metric(hostname, name, store, last_update,
1403 writer->w_user_data);
1404 if (((s > 0) && (status >= 0)) || (s < 0))
1405 status = s;
1406 }
1407 sdb_llist_iter_destroy(iter);
1408 return status;
1409 } /* sdb_plugin_store_metric */
1411 int
1412 sdb_plugin_store_attribute(const char *hostname, const char *key,
1413 const sdb_data_t *value, sdb_time_t last_update)
1414 {
1415 sdb_llist_iter_t *iter;
1416 int status = 0;
1418 if ((! hostname) || (! key) || (! value))
1419 return -1;
1421 iter = sdb_llist_get_iter(writer_list);
1422 while (sdb_llist_iter_has_next(iter)) {
1423 sdb_plugin_writer_t *writer;
1424 int s;
1425 writer = SDB_PLUGIN_WRITER(sdb_llist_iter_get_next(iter));
1426 assert(writer);
1427 s = writer->impl.store_attribute(hostname, key, value, last_update,
1428 writer->w_user_data);
1429 if (((s > 0) && (status >= 0)) || (s < 0))
1430 status = s;
1431 }
1432 sdb_llist_iter_destroy(iter);
1433 return status;
1434 } /* sdb_plugin_store_attribute */
1436 int
1437 sdb_plugin_store_service_attribute(const char *hostname, const char *service,
1438 const char *key, const sdb_data_t *value, sdb_time_t last_update)
1439 {
1440 sdb_llist_iter_t *iter;
1441 int status = 0;
1443 if ((! hostname) || (! service) || (! key) || (! value))
1444 return -1;
1446 iter = sdb_llist_get_iter(writer_list);
1447 while (sdb_llist_iter_has_next(iter)) {
1448 sdb_plugin_writer_t *writer;
1449 int s;
1450 writer = SDB_PLUGIN_WRITER(sdb_llist_iter_get_next(iter));
1451 assert(writer);
1452 s = writer->impl.store_service_attr(hostname, service,
1453 key, value, last_update, writer->w_user_data);
1454 if (((s > 0) && (status >= 0)) || (s < 0))
1455 status = s;
1456 }
1457 sdb_llist_iter_destroy(iter);
1458 return status;
1459 } /* sdb_plugin_store_service_attribute */
1461 int
1462 sdb_plugin_store_metric_attribute(const char *hostname, const char *metric,
1463 const char *key, const sdb_data_t *value, sdb_time_t last_update)
1464 {
1465 sdb_llist_iter_t *iter;
1466 int status = 0;
1468 if ((! hostname) || (! metric) || (! key) || (! value))
1469 return -1;
1471 iter = sdb_llist_get_iter(writer_list);
1472 while (sdb_llist_iter_has_next(iter)) {
1473 sdb_plugin_writer_t *writer;
1474 int s;
1475 writer = SDB_PLUGIN_WRITER(sdb_llist_iter_get_next(iter));
1476 assert(writer);
1477 s = writer->impl.store_metric_attr(hostname, metric,
1478 key, value, last_update, writer->w_user_data);
1479 if (((s > 0) && (status >= 0)) || (s < 0))
1480 status = s;
1481 }
1482 sdb_llist_iter_destroy(iter);
1483 return status;
1484 } /* sdb_plugin_store_metric_attribute */
1486 /* vim: set tw=78 sw=4 ts=4 noexpandtab : */