Code

store: Fixed handling of metric store values.
[sysdb.git] / src / core / plugin.c
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)
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)
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)
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)
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)
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)
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)
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)
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)
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)
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)
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)
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)
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)
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)
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)
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)
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)
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)
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, ...)
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)
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)
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)
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)
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)
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)
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)
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)
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)
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)
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)
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)
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)
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)
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)
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)
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)
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)
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)
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)
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)
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, ...)
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)
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)
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                 writer = SDB_PLUGIN_WRITER(sdb_llist_iter_get_next(iter));
1348                 assert(writer);
1349                 if (writer->impl.store_host(name, last_update, writer->w_user_data))
1350                         status = -1;
1351         }
1352         sdb_llist_iter_destroy(iter);
1353         return status;
1354 } /* sdb_plugin_store_host */
1356 int
1357 sdb_plugin_store_service(const char *hostname, const char *name,
1358                 sdb_time_t last_update)
1360         sdb_llist_iter_t *iter;
1361         int status = 0;
1363         if ((! hostname) || (! name))
1364                 return -1;
1366         iter = sdb_llist_get_iter(writer_list);
1367         while (sdb_llist_iter_has_next(iter)) {
1368                 sdb_plugin_writer_t *writer;
1369                 writer = SDB_PLUGIN_WRITER(sdb_llist_iter_get_next(iter));
1370                 assert(writer);
1371                 if (writer->impl.store_service(hostname, name, last_update,
1372                                         writer->w_user_data))
1373                         status = -1;
1374         }
1375         sdb_llist_iter_destroy(iter);
1376         return status;
1377 } /* sdb_plugin_store_service */
1379 int
1380 sdb_plugin_store_metric(const char *hostname, const char *name,
1381                 sdb_metric_store_t *store, sdb_time_t last_update)
1383         sdb_llist_iter_t *iter;
1384         int status = 0;
1386         if ((! hostname) || (! name))
1387                 return -1;
1389         if (store && ((! store->type) || (! store->id)))
1390                 store = NULL;
1392         iter = sdb_llist_get_iter(writer_list);
1393         while (sdb_llist_iter_has_next(iter)) {
1394                 sdb_plugin_writer_t *writer;
1395                 writer = SDB_PLUGIN_WRITER(sdb_llist_iter_get_next(iter));
1396                 assert(writer);
1397                 if (writer->impl.store_metric(hostname, name, store, last_update,
1398                                         writer->w_user_data))
1399                         status = -1;
1400         }
1401         sdb_llist_iter_destroy(iter);
1402         return status;
1403 } /* sdb_plugin_store_metric */
1405 int
1406 sdb_plugin_store_attribute(const char *hostname, const char *key,
1407                 const sdb_data_t *value, sdb_time_t last_update)
1409         sdb_llist_iter_t *iter;
1410         int status = 0;
1412         if ((! hostname) || (! key) || (! value))
1413                 return -1;
1415         iter = sdb_llist_get_iter(writer_list);
1416         while (sdb_llist_iter_has_next(iter)) {
1417                 sdb_plugin_writer_t *writer;
1418                 writer = SDB_PLUGIN_WRITER(sdb_llist_iter_get_next(iter));
1419                 assert(writer);
1420                 if (writer->impl.store_attribute(hostname, key, value, last_update,
1421                                         writer->w_user_data))
1422                         status = -1;
1423         }
1424         sdb_llist_iter_destroy(iter);
1425         return status;
1426 } /* sdb_plugin_store_attribute */
1428 int
1429 sdb_plugin_store_service_attribute(const char *hostname, const char *service,
1430                 const char *key, const sdb_data_t *value, sdb_time_t last_update)
1432         sdb_llist_iter_t *iter;
1433         int status = 0;
1435         if ((! hostname) || (! service) || (! key) || (! value))
1436                 return -1;
1438         iter = sdb_llist_get_iter(writer_list);
1439         while (sdb_llist_iter_has_next(iter)) {
1440                 sdb_plugin_writer_t *writer;
1441                 writer = SDB_PLUGIN_WRITER(sdb_llist_iter_get_next(iter));
1442                 assert(writer);
1443                 if (writer->impl.store_service_attr(hostname, service,
1444                                         key, value, last_update, writer->w_user_data))
1445                         status = -1;
1446         }
1447         sdb_llist_iter_destroy(iter);
1448         return status;
1449 } /* sdb_plugin_store_service_attribute */
1451 int
1452 sdb_plugin_store_metric_attribute(const char *hostname, const char *metric,
1453                 const char *key, const sdb_data_t *value, sdb_time_t last_update)
1455         sdb_llist_iter_t *iter;
1456         int status = 0;
1458         if ((! hostname) || (! metric) || (! key) || (! value))
1459                 return -1;
1461         iter = sdb_llist_get_iter(writer_list);
1462         while (sdb_llist_iter_has_next(iter)) {
1463                 sdb_plugin_writer_t *writer;
1464                 writer = SDB_PLUGIN_WRITER(sdb_llist_iter_get_next(iter));
1465                 assert(writer);
1466                 if (writer->impl.store_metric_attr(hostname, metric,
1467                                         key, value, last_update, writer->w_user_data))
1468                         status = -1;
1469         }
1470         sdb_llist_iter_destroy(iter);
1471         return status;
1472 } /* sdb_plugin_store_metric_attribute */
1474 /* vim: set tw=78 sw=4 ts=4 noexpandtab : */