Code

store: Pass on all stored objects to store writer plugins.
[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_object_t super;
103         sdb_store_writer_t impl;
104         sdb_object_t *user_data;
105         ctx_t *ctx;
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 static void
255 plugin_unregister_all(void)
257         size_t i;
259         for (i = 0; i < SDB_STATIC_ARRAY_LEN(all_lists); ++i) {
260                 const char  *type =  all_lists[i].type;
261                 sdb_llist_t *list = *all_lists[i].list;
263                 size_t len = sdb_llist_len(list);
265                 if (! len)
266                         continue;
268                 sdb_llist_clear(list);
269                 sdb_log(SDB_LOG_INFO, "core: Unregistered %zu %s callback%s",
270                                 len, type, len == 1 ? "" : "s");
271         }
272 } /* plugin_unregister_all */
274 /*
275  * private types
276  */
278 static int
279 ctx_init(sdb_object_t *obj, va_list __attribute__((unused)) ap)
281         ctx_t *ctx = CTX(obj);
283         assert(ctx);
285         ctx->public = plugin_default_ctx;
286         ctx->info = plugin_default_info;
287         ctx->handle = NULL;
288         ctx->use_cnt = 1;
289         return 0;
290 } /* ctx_init */
292 static void
293 ctx_destroy(sdb_object_t *obj)
295         ctx_t *ctx = CTX(obj);
297         if (ctx->handle) {
298                 const char *err;
300                 sdb_log(SDB_LOG_INFO, "core: Unloading module %s",
301                                 ctx->info.plugin_name);
303                 lt_dlerror();
304                 lt_dlclose(ctx->handle);
305                 if ((err = lt_dlerror()))
306                         sdb_log(SDB_LOG_WARNING, "core: Failed to unload module %s: %s",
307                                         ctx->info.plugin_name, err);
308         }
310         plugin_info_clear(&ctx->info);
311 } /* ctx_destroy */
313 static sdb_type_t ctx_type = {
314         sizeof(ctx_t),
316         ctx_init,
317         ctx_destroy
318 };
320 static ctx_t *
321 ctx_get(void)
323         if (! plugin_ctx_key_initialized)
324                 ctx_key_init();
325         return pthread_getspecific(plugin_ctx_key);
326 } /* ctx_get */
328 static ctx_t *
329 ctx_set(ctx_t *new)
331         ctx_t *old;
333         if (! plugin_ctx_key_initialized)
334                 ctx_key_init();
336         old = pthread_getspecific(plugin_ctx_key);
337         if (old)
338                 sdb_object_deref(SDB_OBJ(old));
339         if (new)
340                 sdb_object_ref(SDB_OBJ(new));
341         pthread_setspecific(plugin_ctx_key, new);
342         return old;
343 } /* ctx_set */
345 static ctx_t *
346 ctx_create(const char *name)
348         ctx_t *ctx;
350         ctx = CTX(sdb_object_create(name, ctx_type));
351         if (! ctx)
352                 return NULL;
354         if (! plugin_ctx_key_initialized)
355                 ctx_key_init();
356         ctx_set(ctx);
357         return ctx;
358 } /* ctx_create */
360 static int
361 plugin_cb_init(sdb_object_t *obj, va_list ap)
363         sdb_llist_t **list = va_arg(ap, sdb_llist_t **);
364         const char   *type = va_arg(ap, const char *);
365         void     *callback = va_arg(ap, void *);
366         sdb_object_t   *ud = va_arg(ap, sdb_object_t *);
368         assert(list);
369         assert(type);
370         assert(obj);
372         if (sdb_llist_search_by_name(*list, obj->name)) {
373                 sdb_log(SDB_LOG_WARNING, "core: %s callback '%s' "
374                                 "has already been registered. Ignoring newly "
375                                 "registered version.", type, obj->name);
376                 return -1;
377         }
379         /* cb_ctx may be NULL if the plugin was not registered by a plugin */
381         SDB_PLUGIN_CB(obj)->cb_callback = callback;
382         SDB_PLUGIN_CB(obj)->cb_ctx      = ctx_get();
383         sdb_object_ref(SDB_OBJ(SDB_PLUGIN_CB(obj)->cb_ctx));
385         sdb_object_ref(ud);
386         SDB_PLUGIN_CB(obj)->cb_user_data = ud;
387         return 0;
388 } /* plugin_cb_init */
390 static void
391 plugin_cb_destroy(sdb_object_t *obj)
393         assert(obj);
394         sdb_object_deref(SDB_PLUGIN_CB(obj)->cb_user_data);
395         sdb_object_deref(SDB_OBJ(SDB_PLUGIN_CB(obj)->cb_ctx));
396 } /* plugin_cb_destroy */
398 static sdb_type_t sdb_plugin_cb_type = {
399         sizeof(sdb_plugin_cb_t),
401         plugin_cb_init,
402         plugin_cb_destroy
403 };
405 static sdb_type_t sdb_plugin_collector_cb_type = {
406         sizeof(sdb_plugin_collector_cb_t),
408         plugin_cb_init,
409         plugin_cb_destroy
410 };
412 static int
413 plugin_writer_init(sdb_object_t *obj, va_list ap)
415         sdb_store_writer_t *impl = va_arg(ap, sdb_store_writer_t *);
416         sdb_object_t       *ud   = va_arg(ap, sdb_object_t *);
418         assert(impl);
420         if ((! impl->store_host) || (! impl->store_service)
421                         || (! impl->store_metric) || (! impl->store_attribute)
422                         || (! impl->store_service_attr) || (! impl->store_metric_attr)) {
423                 sdb_log(SDB_LOG_ERR, "core: store writer callback '%s' "
424                                 "does not fully implement the writer interface.",
425                                 obj->name);
426                 return -1;
427         }
428         if (sdb_llist_search_by_name(writer_list, obj->name)) {
429                 sdb_log(SDB_LOG_WARNING, "core: store writer callback '%s' "
430                                 "has already been registered. Ignoring newly "
431                                 "registered version.", obj->name);
432                 return -1;
433         }
435         /* ctx may be NULL if the plugin was not registered by a plugin */
437         SDB_PLUGIN_WRITER(obj)->impl = *impl;
438         SDB_PLUGIN_WRITER(obj)->ctx  = ctx_get();
439         sdb_object_ref(SDB_OBJ(SDB_PLUGIN_WRITER(obj)->ctx));
441         sdb_object_ref(ud);
442         SDB_PLUGIN_WRITER(obj)->user_data = ud;
443         return 0;
444 } /* plugin_writer_init */
446 static void
447 plugin_writer_destroy(sdb_object_t *obj)
449         assert(obj);
450         sdb_object_deref(SDB_PLUGIN_WRITER(obj)->user_data);
451         sdb_object_deref(SDB_OBJ(SDB_PLUGIN_WRITER(obj)->ctx));
452 } /* plugin_writer_destroy */
454 static sdb_type_t sdb_plugin_writer_type = {
455         sizeof(sdb_plugin_writer_t),
457         plugin_writer_init,
458         plugin_writer_destroy
459 };
461 static int
462 module_init(const char *name, lt_dlhandle lh, sdb_plugin_info_t *info)
464         int (*mod_init)(sdb_plugin_info_t *);
465         int status;
467         mod_init = (int (*)(sdb_plugin_info_t *))lt_dlsym(lh, "sdb_module_init");
468         if (! mod_init) {
469                 sdb_log(SDB_LOG_ERR, "core: Failed to load plugin '%s': "
470                                 "could not find symbol 'sdb_module_init'", name);
471                 return -1;
472         }
474         status = mod_init(info);
475         if (status) {
476                 sdb_log(SDB_LOG_ERR, "core: Failed to initialize "
477                                 "module '%s'", name);
478                 plugin_unregister_by_name(name);
479                 return -1;
480         }
481         return 0;
482 } /* module_init */
484 static int
485 module_load(const char *basedir, const char *name,
486                 const sdb_plugin_ctx_t *plugin_ctx)
488         char  base_name[name ? strlen(name) + 1 : 1];
489         const char *name_ptr;
490         char *tmp;
492         char filename[1024];
493         lt_dlhandle lh;
495         ctx_t *ctx;
497         int status;
499         assert(name);
501         base_name[0] = '\0';
502         name_ptr = name;
504         while ((tmp = strstr(name_ptr, "::"))) {
505                 strncat(base_name, name_ptr, (size_t)(tmp - name_ptr));
506                 strcat(base_name, "/");
507                 name_ptr = tmp + strlen("::");
508         }
509         strcat(base_name, name_ptr);
511         if (! basedir)
512                 basedir = PKGLIBDIR;
514         snprintf(filename, sizeof(filename), "%s/%s.so", basedir, base_name);
515         filename[sizeof(filename) - 1] = '\0';
517         if (access(filename, R_OK)) {
518                 char errbuf[1024];
519                 sdb_log(SDB_LOG_ERR, "core: Failed to load plugin '%s' (%s): %s",
520                                 name, filename, sdb_strerror(errno, errbuf, sizeof(errbuf)));
521                 return -1;
522         }
524         lt_dlinit();
525         lt_dlerror();
527         lh = lt_dlopen(filename);
528         if (! lh) {
529                 sdb_log(SDB_LOG_ERR, "core: Failed to load plugin '%s': %s"
530                                 "The most common cause for this problem are missing "
531                                 "dependencies.\n", name, lt_dlerror());
532                 return -1;
533         }
535         if (ctx_get())
536                 sdb_log(SDB_LOG_WARNING, "core: Discarding old plugin context");
538         ctx = ctx_create(name);
539         if (! ctx) {
540                 sdb_log(SDB_LOG_ERR, "core: Failed to initialize plugin context");
541                 return -1;
542         }
544         ctx->info.plugin_name = strdup(name);
545         ctx->info.filename = strdup(filename);
546         ctx->handle = lh;
548         if (plugin_ctx)
549                 ctx->public = *plugin_ctx;
551         if ((status = module_init(name, lh, &ctx->info))) {
552                 sdb_object_deref(SDB_OBJ(ctx));
553                 return status;
554         }
556         /* compare minor version */
557         if ((ctx->info.version < 0)
558                         || ((int)(ctx->info.version / 100) != (int)(SDB_VERSION / 100)))
559                 sdb_log(SDB_LOG_WARNING, "core: WARNING: version of "
560                                 "plugin '%s' (%i.%i.%i) does not match our version "
561                                 "(%i.%i.%i); this might cause problems",
562                                 name, SDB_VERSION_DECODE(ctx->info.version),
563                                 SDB_VERSION_DECODE(SDB_VERSION));
565         sdb_llist_append(all_plugins, SDB_OBJ(ctx));
567         sdb_log(SDB_LOG_INFO, "core: Successfully loaded "
568                         "plugin %s v%i (%s)", ctx->info.plugin_name,
569                         ctx->info.plugin_version,
570                         INFO_GET(&ctx->info, description));
571         sdb_log(SDB_LOG_INFO, "core: Plugin %s: %s, License: %s",
572                         ctx->info.plugin_name,
573                         INFO_GET(&ctx->info, copyright),
574                         INFO_GET(&ctx->info, license));
576         /* any registered callbacks took ownership of the context */
577         sdb_object_deref(SDB_OBJ(ctx));
579         /* reset */
580         ctx_set(NULL);
581         return 0;
582 } /* module_load */
584 static char *
585 plugin_get_name(const char *name, char *buf, size_t bufsize)
587         ctx_t *ctx = ctx_get();
589         if (ctx)
590                 snprintf(buf, bufsize, "%s::%s", ctx->info.plugin_name, name);
591         else
592                 snprintf(buf, bufsize, "core::%s", name);
593         return buf;
594 } /* plugin_get_name */
596 static int
597 plugin_add_callback(sdb_llist_t **list, const char *type,
598                 const char *name, void *callback, sdb_object_t *user_data)
600         sdb_object_t *obj;
602         if ((! name) || (! callback))
603                 return -1;
605         assert(list);
607         if (! *list)
608                 *list = sdb_llist_create();
609         if (! *list)
610                 return -1;
612         obj = sdb_object_create(name, sdb_plugin_cb_type,
613                         list, type, callback, user_data);
614         if (! obj)
615                 return -1;
617         if (sdb_llist_append(*list, obj)) {
618                 sdb_object_deref(obj);
619                 return -1;
620         }
622         /* pass control to the list */
623         sdb_object_deref(obj);
625         sdb_log(SDB_LOG_INFO, "core: Registered %s callback '%s'.",
626                         type, name);
627         return 0;
628 } /* plugin_add_callback */
630 /*
631  * public API
632  */
634 int
635 sdb_plugin_load(const char *basedir, const char *name,
636                 const sdb_plugin_ctx_t *plugin_ctx)
638         ctx_t *ctx;
640         int status;
642         if ((! name) || (! *name))
643                 return -1;
645         if (! all_plugins) {
646                 if (! (all_plugins = sdb_llist_create())) {
647                         sdb_log(SDB_LOG_ERR, "core: Failed to load plugin '%s': "
648                                         "internal error while creating linked list", name);
649                         return -1;
650                 }
651         }
653         ctx = CTX(sdb_llist_search_by_name(all_plugins, name));
654         if (ctx) {
655                 /* plugin already loaded */
656                 if (! ctx->use_cnt) {
657                         /* reloading plugin */
658                         ctx_t *old_ctx = ctx_set(ctx);
660                         status = module_init(ctx->info.plugin_name, ctx->handle, NULL);
661                         if (status)
662                                 return status;
664                         sdb_log(SDB_LOG_INFO, "core: Successfully reloaded plugin "
665                                         "'%s' (%s)", ctx->info.plugin_name,
666                                         INFO_GET(&ctx->info, description));
667                         ctx_set(old_ctx);
668                 }
669                 ++ctx->use_cnt;
670                 return 0;
671         }
673         return module_load(basedir, name, plugin_ctx);
674 } /* sdb_plugin_load */
676 int
677 sdb_plugin_set_info(sdb_plugin_info_t *info, int type, ...)
679         va_list ap;
681         if (! info)
682                 return -1;
684         va_start(ap, type);
686         switch (type) {
687                 case SDB_PLUGIN_INFO_DESC:
688                         {
689                                 char *desc = va_arg(ap, char *);
690                                 if (desc) {
691                                         if (info->description)
692                                                 free(info->description);
693                                         info->description = strdup(desc);
694                                 }
695                         }
696                         break;
697                 case SDB_PLUGIN_INFO_COPYRIGHT:
698                         {
699                                 char *copyright = va_arg(ap, char *);
700                                 if (copyright)
701                                         info->copyright = strdup(copyright);
702                         }
703                         break;
704                 case SDB_PLUGIN_INFO_LICENSE:
705                         {
706                                 char *license = va_arg(ap, char *);
707                                 if (license) {
708                                         if (info->license)
709                                                 free(info->license);
710                                         info->license = strdup(license);
711                                 }
712                         }
713                         break;
714                 case SDB_PLUGIN_INFO_VERSION:
715                         {
716                                 int version = va_arg(ap, int);
717                                 info->version = version;
718                         }
719                         break;
720                 case SDB_PLUGIN_INFO_PLUGIN_VERSION:
721                         {
722                                 int version = va_arg(ap, int);
723                                 info->plugin_version = version;
724                         }
725                         break;
726                 default:
727                         va_end(ap);
728                         return -1;
729         }
731         va_end(ap);
732         return 0;
733 } /* sdb_plugin_set_info */
735 int
736 sdb_plugin_register_config(sdb_plugin_config_cb callback)
738         ctx_t *ctx = ctx_get();
740         if (! ctx) {
741                 sdb_log(SDB_LOG_ERR, "core: Invalid attempt to register a "
742                                 "config callback from outside a plugin");
743                 return -1;
744         }
745         return plugin_add_callback(&config_list, "init", ctx->info.plugin_name,
746                         (void *)callback, NULL);
747 } /* sdb_plugin_register_config */
749 int
750 sdb_plugin_register_init(const char *name, sdb_plugin_init_cb callback,
751                 sdb_object_t *user_data)
753         char cb_name[1024];
754         return plugin_add_callback(&init_list, "init",
755                         plugin_get_name(name, cb_name, sizeof(cb_name)),
756                         (void *)callback, user_data);
757 } /* sdb_plugin_register_init */
759 int
760 sdb_plugin_register_shutdown(const char *name, sdb_plugin_shutdown_cb callback,
761                 sdb_object_t *user_data)
763         char cb_name[1024];
764         return plugin_add_callback(&shutdown_list, "shutdown",
765                         plugin_get_name(name, cb_name, sizeof(cb_name)),
766                         (void *)callback, user_data);
767 } /* sdb_plugin_register_shutdown */
769 int
770 sdb_plugin_register_log(const char *name, sdb_plugin_log_cb callback,
771                 sdb_object_t *user_data)
773         char cb_name[1024];
774         return plugin_add_callback(&log_list, "log",
775                         plugin_get_name(name, cb_name, sizeof(cb_name)),
776                         callback, user_data);
777 } /* sdb_plugin_register_log */
779 int
780 sdb_plugin_register_cname(const char *name, sdb_plugin_cname_cb callback,
781                 sdb_object_t *user_data)
783         char cb_name[1024];
784         return plugin_add_callback(&cname_list, "cname",
785                         plugin_get_name(name, cb_name, sizeof(cb_name)),
786                         callback, user_data);
787 } /* sdb_plugin_register_cname */
789 int
790 sdb_plugin_register_collector(const char *name, sdb_plugin_collector_cb callback,
791                 const sdb_time_t *interval, sdb_object_t *user_data)
793         char cb_name[1024];
794         sdb_object_t *obj;
796         if ((! name) || (! callback))
797                 return -1;
799         if (! collector_list)
800                 collector_list = sdb_llist_create();
801         if (! collector_list)
802                 return -1;
804         plugin_get_name(name, cb_name, sizeof(cb_name));
806         obj = sdb_object_create(cb_name, sdb_plugin_collector_cb_type,
807                         &collector_list, "collector", callback, user_data);
808         if (! obj)
809                 return -1;
811         if (interval)
812                 SDB_PLUGIN_CCB(obj)->ccb_interval = *interval;
813         else {
814                 ctx_t *ctx = ctx_get();
816                 if (! ctx) {
817                         sdb_log(SDB_LOG_ERR, "core: Cannot determine interval "
818                                         "for collector %s; none specified and no plugin "
819                                         "context found", cb_name);
820                         return -1;
821                 }
823                 SDB_PLUGIN_CCB(obj)->ccb_interval = ctx->public.interval;
824         }
826         if (! (SDB_PLUGIN_CCB(obj)->ccb_next_update = sdb_gettime())) {
827                 char errbuf[1024];
828                 sdb_log(SDB_LOG_ERR, "core: Failed to determine current "
829                                 "time: %s", sdb_strerror(errno, errbuf, sizeof(errbuf)));
830                 sdb_object_deref(obj);
831                 return -1;
832         }
834         if (sdb_llist_insert_sorted(collector_list, obj,
835                                 plugin_cmp_next_update)) {
836                 sdb_object_deref(obj);
837                 return -1;
838         }
840         /* pass control to the list */
841         sdb_object_deref(obj);
843         sdb_log(SDB_LOG_INFO, "core: Registered collector callback '%s' "
844                         "(interval = %.3fs).", cb_name,
845                         SDB_TIME_TO_DOUBLE(SDB_PLUGIN_CCB(obj)->ccb_interval));
846         return 0;
847 } /* sdb_plugin_register_collector */
849 int
850 sdb_plugin_register_ts_fetcher(const char *name,
851                 sdb_plugin_fetch_ts_cb callback, sdb_object_t *user_data)
853         return plugin_add_callback(&ts_fetcher_list, "time-series fetcher",
854                         name, callback, user_data);
855 } /* sdb_plugin_register_ts_fetcher */
857 int
858 sdb_plugin_register_writer(const char *name,
859                 sdb_store_writer_t *writer, sdb_object_t *user_data)
861         char cb_name[1024];
862         sdb_object_t *obj;
864         if ((! name) || (! writer))
865                 return -1;
867         if (! writer_list)
868                 writer_list = sdb_llist_create();
869         if (! writer_list)
870                 return -1;
872         plugin_get_name(name, cb_name, sizeof(cb_name));
874         obj = sdb_object_create(cb_name, sdb_plugin_writer_type,
875                         writer, user_data);
876         if (! obj)
877                 return -1;
879         if (sdb_llist_append(writer_list, obj)) {
880                 sdb_object_deref(obj);
881                 return -1;
882         }
884         /* pass control to the list */
885         sdb_object_deref(obj);
887         sdb_log(SDB_LOG_INFO, "core: Registered store writer callback '%s'.",
888                         cb_name);
889         return 0;
890 } /* sdb_store_register_writer */
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         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->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->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->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->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->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->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->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 : */