1 /*
2 * SysDB - src/core/store.c
3 * Copyright (C) 2012 Sebastian 'tokkee' Harl <sh@tokkee.org>
4 * All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 * 1. Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
14 *
15 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
16 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
17 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
18 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR
19 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
20 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
21 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
22 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
23 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
24 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
25 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 */
28 #include "sysdb.h"
29 #include "core/store.h"
30 #include "core/error.h"
31 #include "core/plugin.h"
32 #include "utils/llist.h"
34 #include <assert.h>
36 #include <errno.h>
38 #include <stdio.h>
39 #include <stdlib.h>
40 #include <string.h>
42 #include <pthread.h>
44 /*
45 * private variables
46 */
48 static sdb_llist_t *obj_list = NULL;
49 static pthread_rwlock_t obj_lock = PTHREAD_RWLOCK_INITIALIZER;
51 /*
52 * private types
53 */
55 static sdb_type_t sdb_store_obj_type;
56 static sdb_type_t sdb_attribute_type;
58 struct store_obj;
59 typedef struct store_obj store_obj_t;
61 struct store_obj {
62 sdb_object_t super;
63 sdb_time_t last_update;
64 store_obj_t *parent;
65 };
66 #define STORE_OBJ(obj) ((store_obj_t *)(obj))
67 #define STORE_CONST_OBJ(obj) ((const store_obj_t *)(obj))
69 typedef struct {
70 store_obj_t super;
72 char *value;
73 } sdb_attribute_t;
74 #define SDB_ATTR(obj) ((sdb_attribute_t *)(obj))
75 #define SDB_CONST_ATTR(obj) ((const sdb_attribute_t *)(obj))
77 typedef struct {
78 store_obj_t super;
80 int type;
81 sdb_llist_t *children;
83 sdb_llist_t *attributes;
84 } sdb_store_obj_t;
85 #define SDB_STORE_OBJ(obj) ((sdb_store_obj_t *)(obj))
86 #define SDB_CONST_STORE_OBJ(obj) ((const sdb_store_obj_t *)(obj))
88 enum {
89 SDB_HOST = 1,
90 SDB_SERVICE,
91 SDB_ATTRIBUTE,
92 };
93 #define TYPE_TO_NAME(t) \
94 (((t) == SDB_HOST) ? "host" \
95 : ((t) == SDB_SERVICE) ? "service" \
96 : ((t) == SDB_ATTRIBUTE) ? "attribute" : "unknown")
98 /* shortcuts for accessing the sdb_store_obj_t attributes
99 * of inheriting objects */
100 #define _last_update super.last_update
102 static int
103 store_obj_init(sdb_object_t *obj, va_list ap)
104 {
105 store_obj_t *sobj = STORE_OBJ(obj);
106 sobj->last_update = va_arg(ap, sdb_time_t);
108 sobj->parent = NULL;
109 return 0;
110 } /* store_obj_init */
112 static void
113 store_obj_destroy(sdb_object_t *obj)
114 {
115 const store_obj_t *sobj = STORE_OBJ(obj);
117 if (sobj->parent)
118 sdb_object_deref(SDB_OBJ(sobj->parent));
119 } /* store_obj_destroy */
121 /* this may not be used as a type on its own but only as helper functions for
122 * the derived types; the function accepts a variadic list of arguments to
123 * pass to the sub-type's init function */
124 static sdb_object_t *
125 store_obj_clone(const sdb_object_t *obj, ...)
126 {
127 const store_obj_t *sobj = STORE_CONST_OBJ(obj);
128 store_obj_t *new;
130 va_list ap;
132 va_start(ap, obj);
134 new = STORE_OBJ(sdb_object_vcreate(obj->name, obj->type, ap));
135 va_end(ap);
136 if (! new)
137 return NULL;
139 new->last_update = sobj->last_update;
140 sdb_object_ref(SDB_OBJ(sobj->parent));
141 new->parent = sobj->parent;
142 return SDB_OBJ(new);
143 } /* store_obj_clone */
145 static int
146 sdb_store_obj_init(sdb_object_t *obj, va_list ap)
147 {
148 sdb_store_obj_t *sobj = SDB_STORE_OBJ(obj);
149 int ret;
151 ret = store_obj_init(obj, ap);
152 if (ret)
153 return ret;
155 sobj->type = va_arg(ap, int);
157 sobj->children = sdb_llist_create();
158 if (! sobj->children)
159 return -1;
160 sobj->attributes = sdb_llist_create();
161 if (! sobj->attributes)
162 return -1;
163 return 0;
164 } /* sdb_store_obj_init */
166 static void
167 sdb_store_obj_destroy(sdb_object_t *obj)
168 {
169 sdb_store_obj_t *sobj = SDB_STORE_OBJ(obj);
171 assert(obj);
173 store_obj_destroy(obj);
175 if (sobj->children)
176 sdb_llist_destroy(sobj->children);
177 if (sobj->attributes)
178 sdb_llist_destroy(sobj->attributes);
179 } /* sdb_store_obj_destroy */
181 static sdb_object_t *
182 sdb_store_obj_clone(const sdb_object_t *obj)
183 {
184 const sdb_store_obj_t *sobj = SDB_CONST_STORE_OBJ(obj);
185 sdb_store_obj_t *new;
187 new = SDB_STORE_OBJ(store_obj_clone(obj, sobj->_last_update, sobj->type));
188 if (! new)
189 return NULL;
191 if (sobj->children) {
192 sdb_llist_destroy(new->children);
193 new->children = sdb_llist_clone(sobj->children);
194 if (! new->children) {
195 sdb_object_deref(SDB_OBJ(new));
196 return NULL;
197 }
198 }
199 if (sobj->attributes) {
200 sdb_llist_destroy(new->attributes);
201 new->attributes = sdb_llist_clone(sobj->attributes);
202 if (! new->attributes) {
203 sdb_object_deref(SDB_OBJ(new));
204 return NULL;
205 }
206 }
208 return SDB_OBJ(new);
209 } /* sdb_store_obj_clone */
211 static int
212 sdb_attr_init(sdb_object_t *obj, va_list ap)
213 {
214 const char *value;
215 int ret;
217 ret = store_obj_init(obj, ap);
218 if (ret)
219 return ret;
220 value = va_arg(ap, const char *);
222 if (value) {
223 SDB_ATTR(obj)->value = strdup(value);
224 if (! SDB_ATTR(obj)->value)
225 return -1;
226 }
227 return 0;
228 } /* sdb_attr_init */
230 static void
231 sdb_attr_destroy(sdb_object_t *obj)
232 {
233 assert(obj);
235 store_obj_destroy(obj);
237 if (SDB_ATTR(obj)->value)
238 free(SDB_ATTR(obj)->value);
239 } /* sdb_attr_destroy */
241 static sdb_object_t *
242 sdb_attr_clone(const sdb_object_t *obj)
243 {
244 const sdb_attribute_t *attr = (const sdb_attribute_t *)obj;
245 sdb_attribute_t *new;
247 new = SDB_ATTR(store_obj_clone(obj, attr->_last_update, attr->value));
248 if (! new)
249 return NULL;
250 return SDB_OBJ(new);
251 } /* sdb_attr_clone */
253 static sdb_type_t sdb_store_obj_type = {
254 sizeof(sdb_store_obj_t),
256 sdb_store_obj_init,
257 sdb_store_obj_destroy,
258 sdb_store_obj_clone
259 };
261 static sdb_type_t sdb_attribute_type = {
262 sizeof(sdb_attribute_t),
264 sdb_attr_init,
265 sdb_attr_destroy,
266 sdb_attr_clone
267 };
269 /*
270 * private helper functions
271 */
273 static sdb_store_obj_t *
274 sdb_store_lookup_in_list(sdb_llist_t *l, int type, const char *name)
275 {
276 sdb_llist_iter_t *iter;
278 if (! l)
279 return NULL;
281 iter = sdb_llist_get_iter(l);
282 if (! iter)
283 return NULL;
285 while (sdb_llist_iter_has_next(iter)) {
286 sdb_store_obj_t *sobj = SDB_STORE_OBJ(sdb_llist_iter_get_next(iter));
287 assert(sobj);
289 if ((sobj->type == type)
290 && (! strcasecmp(SDB_OBJ(sobj)->name, name))) {
291 sdb_llist_iter_destroy(iter);
292 return sobj;
293 }
295 sobj = sdb_store_lookup_in_list(sobj->children, type, name);
296 if (sobj) {
297 sdb_llist_iter_destroy(iter);
298 return sobj;
299 }
300 }
301 sdb_llist_iter_destroy(iter);
302 return NULL;
303 } /* sdb_store_lookup_in_list */
305 static sdb_store_obj_t *
306 sdb_store_lookup(int type, const char *name)
307 {
308 return sdb_store_lookup_in_list(obj_list, type, name);
309 } /* sdb_store_lookup */
311 /* The obj_lock has to be acquired before calling this function. */
312 static int
313 store_obj(int parent_type, const char *parent_name,
314 int type, const char *name, sdb_time_t last_update,
315 store_obj_t **updated_obj)
316 {
317 char *parent_cname = NULL, *cname = NULL;
319 sdb_llist_t *parent_list;
320 store_obj_t *old;
321 int status = 0;
323 if (last_update <= 0)
324 last_update = sdb_gettime();
326 assert((parent_type == 0)
327 || (parent_type == SDB_HOST)
328 || (parent_type == SDB_SERVICE));
329 assert((type == 0)
330 || (type == SDB_HOST)
331 || (type == SDB_SERVICE)
332 || (type == SDB_ATTRIBUTE));
334 if (parent_type == SDB_HOST) {
335 parent_cname = sdb_plugin_cname(strdup(parent_name));
336 if (! parent_cname) {
337 sdb_log(SDB_LOG_ERR, "store: strdup failed");
338 return -1;
339 }
340 parent_name = parent_cname;
341 }
342 if (type == SDB_HOST) {
343 cname = sdb_plugin_cname(strdup(name));
344 if (! cname) {
345 sdb_log(SDB_LOG_ERR, "store: strdup failed");
346 return -1;
347 }
348 name = cname;
349 }
351 if (! obj_list) {
352 if (! (obj_list = sdb_llist_create())) {
353 free(parent_cname);
354 free(cname);
355 return -1;
356 }
357 }
358 parent_list = obj_list;
360 if (parent_type && parent_name) {
361 sdb_store_obj_t *parent;
363 parent = sdb_store_lookup(parent_type, parent_name);
364 if (! parent) {
365 sdb_log(SDB_LOG_ERR, "store: Failed to store %s '%s' - "
366 "parent %s '%s' not found", TYPE_TO_NAME(type), name,
367 TYPE_TO_NAME(parent_type), parent_name);
368 free(parent_cname);
369 free(cname);
370 return -1;
371 }
373 if (type == SDB_ATTRIBUTE)
374 parent_list = parent->attributes;
375 else
376 parent_list = parent->children;
377 }
379 /* TODO: only look into direct children? */
380 if (type == SDB_ATTRIBUTE)
381 old = STORE_OBJ(sdb_llist_search_by_name(parent_list, name));
382 else
383 old = STORE_OBJ(sdb_store_lookup_in_list(parent_list, type, name));
385 if (old) {
386 if (old->last_update > last_update) {
387 sdb_log(SDB_LOG_DEBUG, "store: Cannot update %s '%s' - "
388 "value too old (%"PRIscTIME" < %"PRIscTIME")",
389 TYPE_TO_NAME(type), name, last_update, old->last_update);
390 /* don't report an error; the object may be updated by multiple
391 * backends */
392 status = 1;
393 }
394 else {
395 old->last_update = last_update;
396 }
398 if (updated_obj)
399 *updated_obj = old;
400 }
401 else {
402 store_obj_t *new;
404 if (type == SDB_ATTRIBUTE)
405 /* the value will be updated by the caller */
406 new = STORE_OBJ(sdb_object_create(name, sdb_attribute_type,
407 last_update, NULL));
408 else
409 new = STORE_OBJ(sdb_object_create(name, sdb_store_obj_type,
410 last_update, type));
412 if (! new) {
413 char errbuf[1024];
414 sdb_log(SDB_LOG_ERR, "store: Failed to create %s '%s': %s",
415 TYPE_TO_NAME(type), name,
416 sdb_strerror(errno, errbuf, sizeof(errbuf)));
417 free(parent_cname);
418 free(cname);
419 return -1;
420 }
422 /* TODO: insert type-aware; the current version works as long as we
423 * don't support to store hierarchical data */
424 status = sdb_llist_insert_sorted(parent_list, SDB_OBJ(new),
425 sdb_object_cmp_by_name);
427 /* pass control to the list or destroy in case of an error */
428 sdb_object_deref(SDB_OBJ(new));
430 if (updated_obj)
431 *updated_obj = new;
432 }
433 free(parent_cname);
434 free(cname);
435 return status;
436 } /* sdb_store_obj */
438 /*
439 * public API
440 */
442 int
443 sdb_store_host(const char *name, sdb_time_t last_update)
444 {
445 int status;
447 if (! name)
448 return -1;
450 pthread_rwlock_wrlock(&obj_lock);
451 status = store_obj(/* parent = */ 0, NULL,
452 /* stored object = */ SDB_HOST, name, last_update,
453 /* updated_obj = */ NULL);
454 pthread_rwlock_unlock(&obj_lock);
455 return status;
456 } /* sdb_store_host */
458 _Bool
459 sdb_store_has_host(const char *name)
460 {
461 sdb_store_obj_t *host;
463 if (! name)
464 return NULL;
466 host = sdb_store_lookup(SDB_HOST, name);
467 return host != NULL;
468 } /* sdb_store_has_host */
470 int
471 sdb_store_attribute(const char *hostname, const char *key, const char *value,
472 sdb_time_t last_update)
473 {
474 int status;
476 store_obj_t *updated_attr = NULL;
478 if ((! hostname) || (! key))
479 return -1;
481 pthread_rwlock_wrlock(&obj_lock);
482 status = store_obj(/* parent = */ SDB_HOST, hostname,
483 /* stored object = */ SDB_ATTRIBUTE, key, last_update,
484 &updated_attr);
486 SDB_ATTR(updated_attr)->value = strdup(value);
487 if (! SDB_ATTR(updated_attr)->value) {
488 sdb_object_deref(SDB_OBJ(updated_attr));
489 status = -1;
490 }
491 pthread_rwlock_unlock(&obj_lock);
492 return status;
493 } /* sdb_store_attribute */
495 int
496 sdb_store_service(const char *hostname, const char *name,
497 sdb_time_t last_update)
498 {
499 int status;
501 if ((! hostname) || (! name))
502 return -1;
504 pthread_rwlock_wrlock(&obj_lock);
505 status = store_obj(/* parent = */ SDB_HOST, hostname,
506 /* stored object = */ SDB_SERVICE, name, last_update,
507 /* updated obj = */ NULL);
508 pthread_rwlock_unlock(&obj_lock);
509 return status;
510 } /* sdb_store_service */
512 /* TODO: actually support hierarchical data */
513 int
514 sdb_store_dump(FILE *fh)
515 {
516 sdb_llist_iter_t *host_iter;
518 if (! fh)
519 return -1;
521 pthread_rwlock_rdlock(&obj_lock);
523 host_iter = sdb_llist_get_iter(obj_list);
524 if (! host_iter) {
525 pthread_rwlock_unlock(&obj_lock);
526 return -1;
527 }
529 while (sdb_llist_iter_has_next(host_iter)) {
530 sdb_store_obj_t *host = SDB_STORE_OBJ(sdb_llist_iter_get_next(host_iter));
531 sdb_llist_iter_t *svc_iter;
532 sdb_llist_iter_t *attr_iter;
534 char time_str[64];
536 assert(host);
538 if (! sdb_strftime(time_str, sizeof(time_str),
539 "%F %T %z", host->_last_update))
540 snprintf(time_str, sizeof(time_str), "<error>");
541 time_str[sizeof(time_str) - 1] = '\0';
543 fprintf(fh, "Host '%s' (last updated: %s):\n",
544 SDB_OBJ(host)->name, time_str);
546 attr_iter = sdb_llist_get_iter(host->attributes);
547 if (! attr_iter) {
548 char errbuf[1024];
549 fprintf(fh, "Failed to retrieve attributes: %s\n",
550 sdb_strerror(errno, errbuf, sizeof(errbuf)));
551 continue;
552 }
554 while (sdb_llist_iter_has_next(attr_iter)) {
555 sdb_attribute_t *attr = SDB_ATTR(sdb_llist_iter_get_next(attr_iter));
556 assert(attr);
558 if (! sdb_strftime(time_str, sizeof(time_str),
559 "%F %T %z", attr->_last_update))
560 snprintf(time_str, sizeof(time_str), "<error>");
561 time_str[sizeof(time_str) - 1] = '\0';
563 fprintf(fh, "\tAttribute '%s' -> '%s' (last updated: %s)\n",
564 SDB_OBJ(attr)->name, attr->value, time_str);
565 }
567 sdb_llist_iter_destroy(attr_iter);
569 svc_iter = sdb_llist_get_iter(host->children);
570 if (! svc_iter) {
571 char errbuf[1024];
572 fprintf(fh, "Failed to retrieve services: %s\n",
573 sdb_strerror(errno, errbuf, sizeof(errbuf)));
574 continue;
575 }
577 while (sdb_llist_iter_has_next(svc_iter)) {
578 sdb_store_obj_t *svc = SDB_STORE_OBJ(sdb_llist_iter_get_next(svc_iter));
579 assert(svc);
581 if (! sdb_strftime(time_str, sizeof(time_str),
582 "%F %T %z", svc->_last_update))
583 snprintf(time_str, sizeof(time_str), "<error>");
584 time_str[sizeof(time_str) - 1] = '\0';
586 fprintf(fh, "\tService '%s' (last updated: %s)\n",
587 SDB_OBJ(svc)->name, time_str);
588 }
590 sdb_llist_iter_destroy(svc_iter);
591 }
593 sdb_llist_iter_destroy(host_iter);
594 pthread_rwlock_unlock(&obj_lock);
595 return 0;
596 } /* sdb_store_dump */
598 /* vim: set tw=78 sw=4 ts=4 noexpandtab : */