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 #if HAVE_CONFIG_H
29 # include "config.h"
30 #endif /* HAVE_CONFIG_H */
32 #include "sysdb.h"
33 #include "core/store-private.h"
34 #include "core/plugin.h"
35 #include "utils/error.h"
36 #include "utils/llist.h"
38 #include <assert.h>
40 #include <errno.h>
42 #include <stdio.h>
43 #include <stdlib.h>
44 #include <string.h>
46 #include <pthread.h>
48 /*
49 * private variables
50 */
52 static sdb_llist_t *obj_list = NULL;
53 static pthread_rwlock_t obj_lock = PTHREAD_RWLOCK_INITIALIZER;
55 /*
56 * private types
57 */
59 static sdb_type_t sdb_store_obj_type;
60 static sdb_type_t sdb_attribute_type;
62 static int
63 store_base_init(sdb_object_t *obj, va_list ap)
64 {
65 sdb_store_base_t *sobj = STORE_BASE(obj);
67 sobj->type = va_arg(ap, int);
69 sobj->last_update = va_arg(ap, sdb_time_t);
70 sobj->parent = NULL;
71 return 0;
72 } /* store_base_init */
74 static void
75 store_base_destroy(sdb_object_t *obj)
76 {
77 const sdb_store_base_t *sobj = STORE_CONST_BASE(obj);
79 if (sobj->parent)
80 sdb_object_deref(SDB_OBJ(sobj->parent));
81 } /* store_base_destroy */
83 static int
84 sdb_store_obj_init(sdb_object_t *obj, va_list ap)
85 {
86 sdb_store_obj_t *sobj = SDB_STORE_OBJ(obj);
87 int ret;
89 /* this will consume the first argument (type) of ap */
90 ret = store_base_init(obj, ap);
91 if (ret)
92 return ret;
94 sobj->children = sdb_llist_create();
95 if (! sobj->children)
96 return -1;
97 sobj->attributes = sdb_llist_create();
98 if (! sobj->attributes)
99 return -1;
100 return 0;
101 } /* sdb_store_obj_init */
103 static void
104 sdb_store_obj_destroy(sdb_object_t *obj)
105 {
106 sdb_store_obj_t *sobj = SDB_STORE_OBJ(obj);
108 assert(obj);
110 store_base_destroy(obj);
112 if (sobj->children)
113 sdb_llist_destroy(sobj->children);
114 if (sobj->attributes)
115 sdb_llist_destroy(sobj->attributes);
116 } /* sdb_store_obj_destroy */
118 static int
119 sdb_attr_init(sdb_object_t *obj, va_list ap)
120 {
121 const sdb_data_t *value;
122 int ret;
124 /* this will consume the first two arguments
125 * (type and last_update) of ap */
126 ret = store_base_init(obj, ap);
127 if (ret)
128 return ret;
129 value = va_arg(ap, const sdb_data_t *);
131 if (value)
132 if (sdb_data_copy(&SDB_ATTR(obj)->value, value))
133 return -1;
134 return 0;
135 } /* sdb_attr_init */
137 static void
138 sdb_attr_destroy(sdb_object_t *obj)
139 {
140 assert(obj);
142 store_base_destroy(obj);
143 sdb_data_free_datum(&SDB_ATTR(obj)->value);
144 } /* sdb_attr_destroy */
146 static sdb_type_t sdb_store_obj_type = {
147 sizeof(sdb_store_obj_t),
149 sdb_store_obj_init,
150 sdb_store_obj_destroy
151 };
153 static sdb_type_t sdb_attribute_type = {
154 sizeof(sdb_attribute_t),
156 sdb_attr_init,
157 sdb_attr_destroy
158 };
160 /*
161 * private helper functions
162 */
164 static sdb_store_obj_t *
165 store_lookup_in_list(sdb_llist_t *l, int type, const char *name)
166 {
167 sdb_llist_iter_t *iter;
169 if (! l)
170 return NULL;
172 iter = sdb_llist_get_iter(l);
173 if (! iter)
174 return NULL;
176 while (sdb_llist_iter_has_next(iter)) {
177 sdb_store_obj_t *sobj = SDB_STORE_OBJ(sdb_llist_iter_get_next(iter));
178 assert(sobj);
180 if ((STORE_BASE(sobj)->type == type)
181 && (! strcasecmp(SDB_OBJ(sobj)->name, name))) {
182 sdb_llist_iter_destroy(iter);
183 return sobj;
184 }
186 /* don't lookups non-host types from hierarchical hosts */
187 if ((type != SDB_HOST) && (STORE_BASE(sobj)->type == SDB_HOST))
188 continue;
190 sobj = store_lookup_in_list(sobj->children, type, name);
191 if (sobj) {
192 sdb_llist_iter_destroy(iter);
193 return sobj;
194 }
195 }
196 sdb_llist_iter_destroy(iter);
197 return NULL;
198 } /* store_lookup_in_list */
200 static sdb_store_obj_t *
201 store_lookup(int type, const char *name)
202 {
203 return store_lookup_in_list(obj_list, type, name);
204 } /* store_lookup */
206 /* The obj_lock has to be acquired before calling this function. */
207 static int
208 store_obj(int parent_type, const char *parent_name,
209 int type, const char *name, sdb_time_t last_update,
210 sdb_store_base_t **updated_obj)
211 {
212 char *parent_cname = NULL, *cname = NULL;
214 sdb_llist_t *parent_list;
215 sdb_store_base_t *old;
216 int status = 0;
218 if (last_update <= 0)
219 last_update = sdb_gettime();
221 assert((parent_type == 0)
222 || (parent_type == SDB_HOST)
223 || (parent_type == SDB_SERVICE));
224 assert((type == 0)
225 || (type == SDB_HOST)
226 || (type == SDB_SERVICE)
227 || (type == SDB_ATTRIBUTE));
229 if (parent_type == SDB_HOST) {
230 parent_cname = sdb_plugin_cname(strdup(parent_name));
231 if (! parent_cname) {
232 sdb_log(SDB_LOG_ERR, "store: strdup failed");
233 return -1;
234 }
235 parent_name = parent_cname;
236 }
237 if (type == SDB_HOST) {
238 cname = sdb_plugin_cname(strdup(name));
239 if (! cname) {
240 sdb_log(SDB_LOG_ERR, "store: strdup failed");
241 return -1;
242 }
243 name = cname;
244 }
246 if (! obj_list) {
247 if (! (obj_list = sdb_llist_create())) {
248 free(parent_cname);
249 free(cname);
250 return -1;
251 }
252 }
253 parent_list = obj_list;
255 if (parent_type && parent_name) {
256 sdb_store_obj_t *parent;
258 parent = store_lookup(parent_type, parent_name);
259 if (! parent) {
260 sdb_log(SDB_LOG_ERR, "store: Failed to store %s '%s' - "
261 "parent %s '%s' not found", TYPE_TO_NAME(type), name,
262 TYPE_TO_NAME(parent_type), parent_name);
263 free(parent_cname);
264 free(cname);
265 return -1;
266 }
268 if (type == SDB_ATTRIBUTE)
269 parent_list = parent->attributes;
270 else
271 parent_list = parent->children;
272 }
274 if (type == SDB_HOST)
275 /* make sure that each host is unique */
276 old = STORE_BASE(store_lookup_in_list(obj_list, type, name));
277 else if (type == SDB_ATTRIBUTE)
278 /* look into attributes of this host */
279 old = STORE_BASE(sdb_llist_search_by_name(parent_list, name));
280 else
281 /* look into services assigned to this host (store_lookup_in_list
282 * does not look up services from hierarchical hosts) */
283 old = STORE_BASE(store_lookup_in_list(parent_list, type, name));
285 if (old) {
286 if (old->last_update > last_update) {
287 sdb_log(SDB_LOG_DEBUG, "store: Cannot update %s '%s' - "
288 "value too old (%"PRIscTIME" < %"PRIscTIME")",
289 TYPE_TO_NAME(type), name, last_update, old->last_update);
290 /* don't report an error; the object may be updated by multiple
291 * backends */
292 status = 1;
293 }
294 else {
295 old->last_update = last_update;
296 }
298 if (updated_obj)
299 *updated_obj = old;
300 }
301 else {
302 sdb_store_base_t *new;
304 if (type == SDB_ATTRIBUTE)
305 /* the value will be updated by the caller */
306 new = STORE_BASE(sdb_object_create(name, sdb_attribute_type,
307 type, last_update, NULL));
308 else
309 new = STORE_BASE(sdb_object_create(name, sdb_store_obj_type,
310 type, last_update));
312 if (! new) {
313 char errbuf[1024];
314 sdb_log(SDB_LOG_ERR, "store: Failed to create %s '%s': %s",
315 TYPE_TO_NAME(type), name,
316 sdb_strerror(errno, errbuf, sizeof(errbuf)));
317 free(parent_cname);
318 free(cname);
319 return -1;
320 }
322 /* TODO: insert type-aware; the current version works as long as we
323 * don't support to store hierarchical data */
324 status = sdb_llist_insert_sorted(parent_list, SDB_OBJ(new),
325 sdb_object_cmp_by_name);
327 /* pass control to the list or destroy in case of an error */
328 sdb_object_deref(SDB_OBJ(new));
330 if (updated_obj)
331 *updated_obj = new;
332 }
333 free(parent_cname);
334 free(cname);
335 return status;
336 } /* store_obj */
338 /*
339 * store_obj_tojson serializes attribute / service objects to JSON.
340 *
341 * The function never returns an error. Rather, an error message will be part
342 * of the serialized data.
343 */
344 static void
345 store_obj_tojson(sdb_llist_t *list, int type, sdb_strbuf_t *buf)
346 {
347 sdb_llist_iter_t *iter;
348 char time_str[64];
350 assert((type == SDB_ATTRIBUTE) || (type == SDB_SERVICE));
352 sdb_strbuf_append(buf, "[");
354 iter = sdb_llist_get_iter(list);
355 if (! iter) {
356 char errbuf[1024];
357 sdb_log(SDB_LOG_ERR, "store: Failed to retrieve %ss: %s\n",
358 TYPE_TO_NAME(type),
359 sdb_strerror(errno, errbuf, sizeof(errbuf)));
360 sdb_strbuf_append(buf, "{\"error\": \"failed to retrieve %ss: %s\"}",
361 TYPE_TO_NAME(type), errbuf);
362 }
364 /* has_next returns false if the iterator is NULL */
365 while (sdb_llist_iter_has_next(iter)) {
366 sdb_store_base_t *sobj = STORE_BASE(sdb_llist_iter_get_next(iter));
367 assert(sobj);
369 if (! sdb_strftime(time_str, sizeof(time_str),
370 "%F %T %z", sobj->last_update))
371 snprintf(time_str, sizeof(time_str), "<error>");
372 time_str[sizeof(time_str) - 1] = '\0';
374 sdb_strbuf_append(buf, "{\"name\": \"%s\", ", SDB_OBJ(sobj)->name);
375 if (type == SDB_ATTRIBUTE) {
376 char tmp[sdb_data_strlen(&SDB_ATTR(sobj)->value) + 1];
377 sdb_data_format(&SDB_ATTR(sobj)->value, tmp, sizeof(tmp),
378 SDB_DOUBLE_QUOTED);
379 sdb_strbuf_append(buf, "\"value\": %s, \"last_update\": \"%s\"}",
380 tmp, time_str);
381 }
382 else
383 sdb_strbuf_append(buf, "\"last_update\": \"%s\"}", time_str);
385 if (sdb_llist_iter_has_next(iter))
386 sdb_strbuf_append(buf, ",");
387 }
389 sdb_llist_iter_destroy(iter);
390 sdb_strbuf_append(buf, "]");
391 } /* store_obj_tojson */
393 /*
394 * public API
395 */
397 void
398 sdb_store_clear(void)
399 {
400 sdb_llist_destroy(obj_list);
401 obj_list = NULL;
402 } /* sdb_store_clear */
404 int
405 sdb_store_host(const char *name, sdb_time_t last_update)
406 {
407 int status;
409 if (! name)
410 return -1;
412 pthread_rwlock_wrlock(&obj_lock);
413 status = store_obj(/* parent = */ 0, NULL,
414 /* stored object = */ SDB_HOST, name, last_update,
415 /* updated_obj = */ NULL);
416 pthread_rwlock_unlock(&obj_lock);
417 return status;
418 } /* sdb_store_host */
420 _Bool
421 sdb_store_has_host(const char *name)
422 {
423 sdb_store_obj_t *host;
425 if (! name)
426 return NULL;
428 host = store_lookup(SDB_HOST, name);
429 return host != NULL;
430 } /* sdb_store_has_host */
432 sdb_store_base_t *
433 sdb_store_get_host(const char *name)
434 {
435 sdb_store_obj_t *host;
437 if (! name)
438 return NULL;
440 host = store_lookup(SDB_HOST, name);
441 if (! host)
442 return NULL;
444 sdb_object_ref(SDB_OBJ(host));
445 return STORE_BASE(host);
446 } /* sdb_store_get_host */
448 int
449 sdb_store_attribute(const char *hostname,
450 const char *key, const sdb_data_t *value,
451 sdb_time_t last_update)
452 {
453 int status;
455 sdb_store_base_t *updated_attr = NULL;
457 if ((! hostname) || (! key))
458 return -1;
460 pthread_rwlock_wrlock(&obj_lock);
461 status = store_obj(/* parent = */ SDB_HOST, hostname,
462 /* stored object = */ SDB_ATTRIBUTE, key, last_update,
463 &updated_attr);
465 if (status >= 0) {
466 assert(updated_attr);
467 if (sdb_data_copy(&SDB_ATTR(updated_attr)->value, value)) {
468 sdb_object_deref(SDB_OBJ(updated_attr));
469 status = -1;
470 }
471 }
473 pthread_rwlock_unlock(&obj_lock);
474 return status;
475 } /* sdb_store_attribute */
477 int
478 sdb_store_service(const char *hostname, const char *name,
479 sdb_time_t last_update)
480 {
481 int status;
483 if ((! hostname) || (! name))
484 return -1;
486 pthread_rwlock_wrlock(&obj_lock);
487 status = store_obj(/* parent = */ SDB_HOST, hostname,
488 /* stored object = */ SDB_SERVICE, name, last_update,
489 /* updated obj = */ NULL);
490 pthread_rwlock_unlock(&obj_lock);
491 return status;
492 } /* sdb_store_service */
494 int
495 sdb_store_host_tojson(sdb_store_base_t *h, sdb_strbuf_t *buf, int flags)
496 {
497 sdb_store_obj_t *host;
498 char time_str[64];
500 if ((! h) || (h->type != SDB_HOST) || (! buf))
501 return -1;
503 host = SDB_STORE_OBJ(h);
505 if (! sdb_strftime(time_str, sizeof(time_str),
506 "%F %T %z", host->_last_update))
507 snprintf(time_str, sizeof(time_str), "<error>");
508 time_str[sizeof(time_str) - 1] = '\0';
510 sdb_strbuf_append(buf, "{\"name\": \"%s\", "
511 "\"last_update\": \"%s\"",
512 SDB_OBJ(host)->name, time_str);
514 if (! (flags & SDB_SKIP_ATTRIBUTES)) {
515 sdb_strbuf_append(buf, ", \"attributes\": ");
516 store_obj_tojson(host->attributes, SDB_ATTRIBUTE, buf);
517 }
519 if (! (flags & SDB_SKIP_SERVICES)) {
520 sdb_strbuf_append(buf, ", \"services\": ");
521 store_obj_tojson(host->children, SDB_SERVICE, buf);
522 }
524 sdb_strbuf_append(buf, "}");
525 return 0;
526 } /* sdb_store_host_tojson */
528 /* TODO: actually support hierarchical data */
529 int
530 sdb_store_tojson(sdb_strbuf_t *buf, int flags)
531 {
532 sdb_llist_iter_t *host_iter;
534 if (! buf)
535 return -1;
537 pthread_rwlock_rdlock(&obj_lock);
539 host_iter = sdb_llist_get_iter(obj_list);
540 if (! host_iter) {
541 pthread_rwlock_unlock(&obj_lock);
542 return -1;
543 }
545 sdb_strbuf_append(buf, "{\"hosts\":[");
547 while (sdb_llist_iter_has_next(host_iter)) {
548 sdb_store_base_t *host = STORE_BASE(sdb_llist_iter_get_next(host_iter));
549 assert(host);
551 if (sdb_store_host_tojson(host, buf, flags))
552 return -1;
554 if (sdb_llist_iter_has_next(host_iter))
555 sdb_strbuf_append(buf, ",");
556 }
558 sdb_strbuf_append(buf, "]}");
560 sdb_llist_iter_destroy(host_iter);
561 pthread_rwlock_unlock(&obj_lock);
562 return 0;
563 } /* sdb_store_tojson */
565 /* vim: set tw=78 sw=4 ts=4 noexpandtab : */