1 /*
2 * SysDB - src/core/store.c
3 * Copyright (C) 2012-2013 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/plugin.h"
31 #include "utils/error.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 sdb_store_base {
59 sdb_object_t super;
61 /* object type */
62 int type;
64 /* common meta information */
65 sdb_time_t last_update;
66 sdb_store_base_t *parent;
67 };
68 #define STORE_BASE(obj) ((sdb_store_base_t *)(obj))
69 #define STORE_CONST_BASE(obj) ((const sdb_store_base_t *)(obj))
71 typedef struct {
72 sdb_store_base_t super;
74 char *value;
75 } sdb_attribute_t;
76 #define SDB_ATTR(obj) ((sdb_attribute_t *)(obj))
77 #define SDB_CONST_ATTR(obj) ((const sdb_attribute_t *)(obj))
79 typedef struct {
80 sdb_store_base_t super;
82 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_base_init(sdb_object_t *obj, va_list ap)
104 {
105 sdb_store_base_t *sobj = STORE_BASE(obj);
107 sobj->type = va_arg(ap, int);
109 sobj->last_update = va_arg(ap, sdb_time_t);
110 sobj->parent = NULL;
111 return 0;
112 } /* store_base_init */
114 static void
115 store_base_destroy(sdb_object_t *obj)
116 {
117 const sdb_store_base_t *sobj = STORE_CONST_BASE(obj);
119 if (sobj->parent)
120 sdb_object_deref(SDB_OBJ(sobj->parent));
121 } /* store_base_destroy */
123 static int
124 sdb_store_obj_init(sdb_object_t *obj, va_list ap)
125 {
126 sdb_store_obj_t *sobj = SDB_STORE_OBJ(obj);
127 int ret;
129 /* this will consume the first argument (type) of ap */
130 ret = store_base_init(obj, ap);
131 if (ret)
132 return ret;
134 sobj->children = sdb_llist_create();
135 if (! sobj->children)
136 return -1;
137 sobj->attributes = sdb_llist_create();
138 if (! sobj->attributes)
139 return -1;
140 return 0;
141 } /* sdb_store_obj_init */
143 static void
144 sdb_store_obj_destroy(sdb_object_t *obj)
145 {
146 sdb_store_obj_t *sobj = SDB_STORE_OBJ(obj);
148 assert(obj);
150 store_base_destroy(obj);
152 if (sobj->children)
153 sdb_llist_destroy(sobj->children);
154 if (sobj->attributes)
155 sdb_llist_destroy(sobj->attributes);
156 } /* sdb_store_obj_destroy */
158 static int
159 sdb_attr_init(sdb_object_t *obj, va_list ap)
160 {
161 const char *value;
162 int ret;
164 /* this will consume the first argument (type) of ap */
165 ret = store_base_init(obj, ap);
166 if (ret)
167 return ret;
168 value = va_arg(ap, const char *);
170 if (value) {
171 SDB_ATTR(obj)->value = strdup(value);
172 if (! SDB_ATTR(obj)->value)
173 return -1;
174 }
175 return 0;
176 } /* sdb_attr_init */
178 static void
179 sdb_attr_destroy(sdb_object_t *obj)
180 {
181 assert(obj);
183 store_base_destroy(obj);
185 if (SDB_ATTR(obj)->value)
186 free(SDB_ATTR(obj)->value);
187 } /* sdb_attr_destroy */
189 static sdb_type_t sdb_store_obj_type = {
190 sizeof(sdb_store_obj_t),
192 sdb_store_obj_init,
193 sdb_store_obj_destroy
194 };
196 static sdb_type_t sdb_attribute_type = {
197 sizeof(sdb_attribute_t),
199 sdb_attr_init,
200 sdb_attr_destroy
201 };
203 /*
204 * private helper functions
205 */
207 static sdb_store_obj_t *
208 sdb_store_lookup_in_list(sdb_llist_t *l, int type, const char *name)
209 {
210 sdb_llist_iter_t *iter;
212 if (! l)
213 return NULL;
215 iter = sdb_llist_get_iter(l);
216 if (! iter)
217 return NULL;
219 while (sdb_llist_iter_has_next(iter)) {
220 sdb_store_obj_t *sobj = SDB_STORE_OBJ(sdb_llist_iter_get_next(iter));
221 assert(sobj);
223 if ((STORE_BASE(sobj)->type == type)
224 && (! strcasecmp(SDB_OBJ(sobj)->name, name))) {
225 sdb_llist_iter_destroy(iter);
226 return sobj;
227 }
229 /* don't lookups non-host types from hierarchical hosts */
230 if ((type != SDB_HOST) && (STORE_BASE(sobj)->type == SDB_HOST))
231 continue;
233 sobj = sdb_store_lookup_in_list(sobj->children, type, name);
234 if (sobj) {
235 sdb_llist_iter_destroy(iter);
236 return sobj;
237 }
238 }
239 sdb_llist_iter_destroy(iter);
240 return NULL;
241 } /* sdb_store_lookup_in_list */
243 static sdb_store_obj_t *
244 sdb_store_lookup(int type, const char *name)
245 {
246 return sdb_store_lookup_in_list(obj_list, type, name);
247 } /* sdb_store_lookup */
249 /* The obj_lock has to be acquired before calling this function. */
250 static int
251 store_obj(int parent_type, const char *parent_name,
252 int type, const char *name, sdb_time_t last_update,
253 sdb_store_base_t **updated_obj)
254 {
255 char *parent_cname = NULL, *cname = NULL;
257 sdb_llist_t *parent_list;
258 sdb_store_base_t *old;
259 int status = 0;
261 if (last_update <= 0)
262 last_update = sdb_gettime();
264 assert((parent_type == 0)
265 || (parent_type == SDB_HOST)
266 || (parent_type == SDB_SERVICE));
267 assert((type == 0)
268 || (type == SDB_HOST)
269 || (type == SDB_SERVICE)
270 || (type == SDB_ATTRIBUTE));
272 if (parent_type == SDB_HOST) {
273 parent_cname = sdb_plugin_cname(strdup(parent_name));
274 if (! parent_cname) {
275 sdb_log(SDB_LOG_ERR, "store: strdup failed");
276 return -1;
277 }
278 parent_name = parent_cname;
279 }
280 if (type == SDB_HOST) {
281 cname = sdb_plugin_cname(strdup(name));
282 if (! cname) {
283 sdb_log(SDB_LOG_ERR, "store: strdup failed");
284 return -1;
285 }
286 name = cname;
287 }
289 if (! obj_list) {
290 if (! (obj_list = sdb_llist_create())) {
291 free(parent_cname);
292 free(cname);
293 return -1;
294 }
295 }
296 parent_list = obj_list;
298 if (parent_type && parent_name) {
299 sdb_store_obj_t *parent;
301 parent = sdb_store_lookup(parent_type, parent_name);
302 if (! parent) {
303 sdb_log(SDB_LOG_ERR, "store: Failed to store %s '%s' - "
304 "parent %s '%s' not found", TYPE_TO_NAME(type), name,
305 TYPE_TO_NAME(parent_type), parent_name);
306 free(parent_cname);
307 free(cname);
308 return -1;
309 }
311 if (type == SDB_ATTRIBUTE)
312 parent_list = parent->attributes;
313 else
314 parent_list = parent->children;
315 }
317 if (type == SDB_HOST)
318 /* make sure that each host is unique */
319 old = STORE_BASE(sdb_store_lookup_in_list(obj_list, type, name));
320 else if (type == SDB_ATTRIBUTE)
321 /* look into attributes of this host */
322 old = STORE_BASE(sdb_llist_search_by_name(parent_list, name));
323 else
324 /* look into services assigned to this host (sdb_store_lookup_in_list
325 * does not look up services from hierarchical hosts) */
326 old = STORE_BASE(sdb_store_lookup_in_list(parent_list, type, name));
328 if (old) {
329 if (old->last_update > last_update) {
330 sdb_log(SDB_LOG_DEBUG, "store: Cannot update %s '%s' - "
331 "value too old (%"PRIscTIME" < %"PRIscTIME")",
332 TYPE_TO_NAME(type), name, last_update, old->last_update);
333 /* don't report an error; the object may be updated by multiple
334 * backends */
335 status = 1;
336 }
337 else {
338 old->last_update = last_update;
339 }
341 if (updated_obj)
342 *updated_obj = old;
343 }
344 else {
345 sdb_store_base_t *new;
347 if (type == SDB_ATTRIBUTE)
348 /* the value will be updated by the caller */
349 new = STORE_BASE(sdb_object_create(name, sdb_attribute_type,
350 type, last_update, NULL));
351 else
352 new = STORE_BASE(sdb_object_create(name, sdb_store_obj_type,
353 type, last_update));
355 if (! new) {
356 char errbuf[1024];
357 sdb_log(SDB_LOG_ERR, "store: Failed to create %s '%s': %s",
358 TYPE_TO_NAME(type), name,
359 sdb_strerror(errno, errbuf, sizeof(errbuf)));
360 free(parent_cname);
361 free(cname);
362 return -1;
363 }
365 /* TODO: insert type-aware; the current version works as long as we
366 * don't support to store hierarchical data */
367 status = sdb_llist_insert_sorted(parent_list, SDB_OBJ(new),
368 sdb_object_cmp_by_name);
370 /* pass control to the list or destroy in case of an error */
371 sdb_object_deref(SDB_OBJ(new));
373 if (updated_obj)
374 *updated_obj = new;
375 }
376 free(parent_cname);
377 free(cname);
378 return status;
379 } /* sdb_store_obj */
381 /*
382 * public API
383 */
385 int
386 sdb_store_host(const char *name, sdb_time_t last_update)
387 {
388 int status;
390 if (! name)
391 return -1;
393 pthread_rwlock_wrlock(&obj_lock);
394 status = store_obj(/* parent = */ 0, NULL,
395 /* stored object = */ SDB_HOST, name, last_update,
396 /* updated_obj = */ NULL);
397 pthread_rwlock_unlock(&obj_lock);
398 return status;
399 } /* sdb_store_host */
401 _Bool
402 sdb_store_has_host(const char *name)
403 {
404 sdb_store_obj_t *host;
406 if (! name)
407 return NULL;
409 host = sdb_store_lookup(SDB_HOST, name);
410 return host != NULL;
411 } /* sdb_store_has_host */
413 int
414 sdb_store_attribute(const char *hostname, const char *key, const char *value,
415 sdb_time_t last_update)
416 {
417 int status;
419 sdb_store_base_t *updated_attr = NULL;
421 if ((! hostname) || (! key))
422 return -1;
424 pthread_rwlock_wrlock(&obj_lock);
425 status = store_obj(/* parent = */ SDB_HOST, hostname,
426 /* stored object = */ SDB_ATTRIBUTE, key, last_update,
427 &updated_attr);
429 if (status >= 0) {
430 assert(updated_attr);
431 SDB_ATTR(updated_attr)->value = strdup(value);
432 if (! SDB_ATTR(updated_attr)->value) {
433 sdb_object_deref(SDB_OBJ(updated_attr));
434 status = -1;
435 }
436 }
438 pthread_rwlock_unlock(&obj_lock);
439 return status;
440 } /* sdb_store_attribute */
442 int
443 sdb_store_service(const char *hostname, const char *name,
444 sdb_time_t last_update)
445 {
446 int status;
448 if ((! hostname) || (! name))
449 return -1;
451 pthread_rwlock_wrlock(&obj_lock);
452 status = store_obj(/* parent = */ SDB_HOST, hostname,
453 /* stored object = */ SDB_SERVICE, name, last_update,
454 /* updated obj = */ NULL);
455 pthread_rwlock_unlock(&obj_lock);
456 return status;
457 } /* sdb_store_service */
459 /* TODO: actually support hierarchical data */
460 int
461 sdb_store_tojson(sdb_strbuf_t *buf)
462 {
463 sdb_llist_iter_t *host_iter;
465 if (! buf)
466 return -1;
468 pthread_rwlock_rdlock(&obj_lock);
470 host_iter = sdb_llist_get_iter(obj_list);
471 if (! host_iter) {
472 pthread_rwlock_unlock(&obj_lock);
473 return -1;
474 }
476 sdb_strbuf_append(buf, "{\"hosts\":[");
478 while (sdb_llist_iter_has_next(host_iter)) {
479 sdb_store_obj_t *host = SDB_STORE_OBJ(sdb_llist_iter_get_next(host_iter));
480 sdb_llist_iter_t *svc_iter;
481 sdb_llist_iter_t *attr_iter;
483 char time_str[64];
485 assert(host);
487 if (! sdb_strftime(time_str, sizeof(time_str),
488 "%F %T %z", host->_last_update))
489 snprintf(time_str, sizeof(time_str), "<error>");
490 time_str[sizeof(time_str) - 1] = '\0';
492 sdb_strbuf_append(buf, "{\"name\": \"%s\", "
493 "\"last_update\": \"%s\", "
494 "\"attributes\": [",
495 SDB_OBJ(host)->name, time_str);
497 attr_iter = sdb_llist_get_iter(host->attributes);
498 if (! attr_iter) {
499 char errbuf[1024];
500 sdb_log(SDB_LOG_ERR, "store: Failed to retrieve attributes: %s\n",
501 sdb_strerror(errno, errbuf, sizeof(errbuf)));
502 sdb_strbuf_append(buf, "{\"error\": \"failed to retrieve "
503 "attributes: %s\"}", errbuf);
504 }
506 /* has_next returns false if the iterator is NULL */
507 while (sdb_llist_iter_has_next(attr_iter)) {
508 sdb_attribute_t *attr = SDB_ATTR(sdb_llist_iter_get_next(attr_iter));
509 assert(attr);
511 if (! sdb_strftime(time_str, sizeof(time_str),
512 "%F %T %z", attr->_last_update))
513 snprintf(time_str, sizeof(time_str), "<error>");
514 time_str[sizeof(time_str) - 1] = '\0';
516 sdb_strbuf_append(buf, "{\"name\": \"%s\", "
517 "\"value\": \"%s\", \"last_update\": \"%s\"},",
518 SDB_OBJ(attr)->name, attr->value, time_str);
519 }
521 sdb_llist_iter_destroy(attr_iter);
522 sdb_strbuf_append(buf, "], \"services\": [");
524 svc_iter = sdb_llist_get_iter(host->children);
525 if (! svc_iter) {
526 char errbuf[1024];
527 sdb_log(SDB_LOG_ERR, "store: Failed to retrieve services: %s\n",
528 sdb_strerror(errno, errbuf, sizeof(errbuf)));
529 sdb_strbuf_append(buf, "{\"error\": \"failed to retrieve "
530 "services: %s\"}", errbuf);
531 }
533 while (sdb_llist_iter_has_next(svc_iter)) {
534 sdb_store_obj_t *svc = SDB_STORE_OBJ(sdb_llist_iter_get_next(svc_iter));
535 assert(svc);
537 if (! sdb_strftime(time_str, sizeof(time_str),
538 "%F %T %z", svc->_last_update))
539 snprintf(time_str, sizeof(time_str), "<error>");
540 time_str[sizeof(time_str) - 1] = '\0';
542 sdb_strbuf_append(buf, "{\"name\": \"%s\", "
543 "\"last_update\": \"%s\"},",
544 SDB_OBJ(svc)->name, time_str);
545 }
547 sdb_llist_iter_destroy(svc_iter);
548 sdb_strbuf_append(buf, "]},");
549 }
551 sdb_strbuf_append(buf, "]}");
553 sdb_llist_iter_destroy(host_iter);
554 pthread_rwlock_unlock(&obj_lock);
555 return 0;
556 } /* sdb_store_tojson */
558 /* vim: set tw=78 sw=4 ts=4 noexpandtab : */