tizen 2.3 release
[external/buxton.git] / src / db / gdbm.c
1 /*
2  * This file is part of buxton.
3  *
4  * Copyright (C) 2013 Intel Corporation
5  *
6  * buxton is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU Lesser General Public License as
8  * published by the Free Software Foundation; either version 2.1
9  * of the License, or (at your option) any later version.
10  */
11
12 #ifdef HAVE_CONFIG_H
13         #include "config.h"
14 #endif
15
16 #include <assert.h>
17 #include <errno.h>
18 #include <gdbm.h>
19 #include <stdlib.h>
20 #include <string.h>
21
22 #include "log.h"
23 #include "hashmap.h"
24 #include "serialize.h"
25 #include "util.h"
26
27 /**
28  * GDBM Database Module
29  */
30
31
32 static Hashmap *_resources = NULL;
33
34 static char *key_get_name(BuxtonString *key)
35 {
36         char *c;
37
38         c = strchr(key->value, 0);
39         if (!c) {
40                 return NULL;
41         }
42         if (c - (key->value + (key->length - 1)) >= 0) {
43                 return NULL;
44         }
45         c++;
46
47         return c;
48 }
49
50 static GDBM_FILE try_open_database(char *path, const int oflag)
51 {
52         GDBM_FILE db = gdbm_open(path, 0, oflag, S_IRUSR | S_IWUSR, NULL);
53         /* handle open under write mode failing by falling back to
54            reader mode */
55         if (!db && (gdbm_errno == GDBM_FILE_OPEN_ERROR)) {
56                 db = gdbm_open(path, 0, GDBM_READER, S_IRUSR | S_IWUSR, NULL);
57                 buxton_debug("Attempting to fallback to opening db as read-only\n");
58                 errno = EROFS;
59         } else {
60                 if (!db) {
61                         abort();
62                 }
63                 /* Must do this as gdbm_open messes with errno */
64                 errno = 0;
65         }
66         return db;
67 }
68
69 /* Open or create databases on the fly */
70 static GDBM_FILE db_for_resource(BuxtonLayer *layer)
71 {
72         GDBM_FILE db;
73         _cleanup_free_ char *path = NULL;
74         char *name = NULL;
75         int r;
76         int oflag = layer->readonly ? GDBM_READER : GDBM_WRCREAT;
77         int save_errno = 0;
78
79         assert(layer);
80         assert(_resources);
81
82         if (layer->type == LAYER_USER) {
83                 r = asprintf(&name, "%s-%d", layer->name.value, layer->uid);
84         } else {
85                 r = asprintf(&name, "%s", layer->name.value);
86         }
87         if (r == -1) {
88                 abort();
89         }
90
91         db = hashmap_get(_resources, name);
92         if (!db) {
93                 path = get_layer_path(layer);
94                 if (!path) {
95                         abort();
96                 }
97
98                 db = try_open_database(path, oflag);
99                 save_errno = errno;
100                 if (!db) {
101                         free(name);
102                         buxton_log("Couldn't create db for path: %s\n", path);
103                         return 0;
104                 }
105                 r = hashmap_put(_resources, name, db);
106                 if (r != 1) {
107                         abort();
108                 }
109         } else {
110                 db = (GDBM_FILE) hashmap_get(_resources, name);
111                 free(name);
112         }
113
114         errno = save_errno;
115         return db;
116 }
117
118 static int set_value(BuxtonLayer *layer, _BuxtonKey *key, BuxtonData *data,
119                       BuxtonString *label)
120 {
121         GDBM_FILE db;
122         int ret = -1;
123         datum key_data;
124         datum cvalue = {0};
125         datum value;
126         _cleanup_free_ uint8_t *data_store = NULL;
127         size_t size;
128         uint32_t sz;
129         BuxtonData cdata = {0};
130         BuxtonString clabel;
131
132         assert(layer);
133         assert(key);
134         assert(label);
135
136         if (key->name.value) {
137                 sz = key->group.length + key->name.length;
138                 key_data.dptr = malloc(sz);
139                 if (!key_data.dptr) {
140                         abort();
141                 }
142
143                 /* size is string\0string\0 so just write, bonus for
144                    nil seperator being added without extra work */
145                 key_data.dsize = (int)sz;
146                 memcpy(key_data.dptr, key->group.value, key->group.length);
147                 memcpy(key_data.dptr + key->group.length, key->name.value,
148                        key->name.length);
149         } else {
150                 key_data.dptr = malloc(key->group.length);
151                 if (!key_data.dptr) {
152                         abort();
153                 }
154
155                 memcpy(key_data.dptr, key->group.value, key->group.length);
156                 key_data.dsize = (int)key->group.length;
157         }
158
159         db = db_for_resource(layer);
160         if (!db || errno) {
161                 ret = errno;
162                 goto end;
163         }
164
165         /* set_label will pass a NULL for data */
166         if (!data) {
167                 cvalue = gdbm_fetch(db, key_data);
168                 if (cvalue.dsize < 0 || cvalue.dptr == NULL) {
169                         ret = ENOENT;
170                         goto end;
171                 }
172
173                 data_store = (uint8_t*)cvalue.dptr;
174                 buxton_deserialize(data_store, &cdata, &clabel);
175                 free(clabel.value);
176                 data = &cdata;
177                 data_store = NULL;
178         }
179
180         size = buxton_serialize(data, label, &data_store);
181
182         value.dptr = (char *)data_store;
183         value.dsize = (int)size;
184         ret = gdbm_store(db, key_data, value, GDBM_REPLACE);
185         if (ret && gdbm_errno == GDBM_READER_CANT_STORE) {
186                 ret = EROFS;
187         }
188         assert(ret == 0);
189
190 end:
191         if (cdata.type == STRING) {
192                 free(cdata.store.d_string.value);
193         }
194         free(key_data.dptr);
195         free(cvalue.dptr);
196
197         return ret;
198 }
199
200 static int get_value(BuxtonLayer *layer, _BuxtonKey *key, BuxtonData *data,
201                       BuxtonString *label)
202 {
203         GDBM_FILE db;
204         datum key_data;
205         datum value;
206         uint8_t *data_store = NULL;
207         int ret;
208         uint32_t sz;
209
210         assert(layer);
211
212         if (key->name.value) {
213                 sz = key->group.length + key->name.length;
214                 key_data.dptr = malloc(sz);
215                 if (!key_data.dptr) {
216                         abort();
217                 }
218
219                 /* size is string\0string\0 so just write, bonus for
220                    nil seperator being added without extra work */
221                 key_data.dsize = (int)sz;
222                 memcpy(key_data.dptr, key->group.value, key->group.length);
223                 memcpy(key_data.dptr + key->group.length, key->name.value,
224                        key->name.length);
225         } else {
226                 key_data.dptr = malloc(key->group.length);
227                 if (!key_data.dptr) {
228                         abort();
229                 }
230
231                 memcpy(key_data.dptr, key->group.value, key->group.length);
232                 key_data.dsize = (int)key->group.length;
233         }
234
235         memzero(&value, sizeof(datum));
236         db = db_for_resource(layer);
237         if (!db) {
238                 /*
239                  * Set negative here to indicate layer not found
240                  * rather than key not found, optimization for
241                  * set value
242                  */
243                 ret = -ENOENT;
244                 goto end;
245         }
246
247         value = gdbm_fetch(db, key_data);
248         if (value.dsize < 0 || value.dptr == NULL) {
249                 ret = ENOENT;
250                 goto end;
251         }
252
253         data_store = (uint8_t*)value.dptr;
254         buxton_deserialize(data_store, data, label);
255
256         if (data->type != key->type) {
257                 free(label->value);
258                 label->value = NULL;
259                 if (data->type == STRING) {
260                         free(data->store.d_string.value);
261                         data->store.d_string.value = NULL;
262                 }
263                 ret = EINVAL;
264                 goto end;
265         }
266         ret = 0;
267
268 end:
269         free(key_data.dptr);
270         free(value.dptr);
271         data_store = NULL;
272
273         return ret;
274 }
275
276 static int unset_value(BuxtonLayer *layer,
277                         _BuxtonKey *key,
278                         __attribute__((unused)) BuxtonData *data,
279                         __attribute__((unused)) BuxtonString *label)
280 {
281         GDBM_FILE db;
282         datum key_data;
283         int ret;
284         uint32_t sz;
285
286         assert(layer);
287         assert(key);
288
289         if (key->name.value) {
290                 sz = key->group.length + key->name.length;
291                 key_data.dptr = malloc(sz);
292                 if (!key_data.dptr) {
293                         abort();
294                 }
295
296                 /* size is string\0string\0 so just write, bonus for
297                    nil seperator being added without extra work */
298                 key_data.dsize = (int)sz;
299                 memcpy(key_data.dptr, key->group.value, key->group.length);
300                 memcpy(key_data.dptr + key->group.length, key->name.value,
301                        key->name.length);
302         } else {
303                 key_data.dptr = malloc(key->group.length);
304                 if (!key_data.dptr) {
305                         abort();
306                 }
307
308                 memcpy(key_data.dptr, key->group.value, key->group.length);
309                 key_data.dsize = (int)key->group.length;
310         }
311
312         errno = 0;
313         db = db_for_resource(layer);
314         if (!db || gdbm_errno) {
315                 ret = EROFS;
316                 goto end;
317         }
318
319         ret = gdbm_delete(db, key_data);
320         if (ret) {
321                 if (gdbm_errno == GDBM_READER_CANT_DELETE) {
322                         ret = EROFS;
323                 } else if (gdbm_errno == GDBM_ITEM_NOT_FOUND) {
324                         ret = ENOENT;
325                 } else {
326                         abort();
327                 }
328         }
329
330 end:
331         free(key_data.dptr);
332
333         return ret;
334 }
335
336 static bool list_keys(BuxtonLayer *layer,
337                       BuxtonArray **list)
338 {
339         GDBM_FILE db;
340         datum key, nextkey;
341         BuxtonArray *k_list = NULL;
342         BuxtonData *current = NULL;
343         BuxtonString in_key;
344         char *name;
345         bool ret = false;
346
347         assert(layer);
348
349         db = db_for_resource(layer);
350         if (!db) {
351                 goto end;
352         }
353
354         k_list = buxton_array_new();
355         key = gdbm_firstkey(db);
356         /* Iterate through all of the keys */
357         while (key.dptr) {
358                 /* Split the key name from the rest of the key */
359                 in_key.value = (char*)key.dptr;
360                 in_key.length = (uint32_t)key.dsize;
361                 name = key_get_name(&in_key);
362                 if (!name) {
363                         continue;
364                 }
365
366                 current = malloc0(sizeof(BuxtonData));
367                 if (!current) {
368                         abort();
369                 }
370                 current->type = STRING;
371                 current->store.d_string.value = strdup(name);
372                 if (!current->store.d_string.value) {
373                         abort();
374                 }
375                 current->store.d_string.length = (uint32_t)strlen(name) + 1;
376                 if (!buxton_array_add(k_list, current)) {
377                         abort();
378                 }
379
380                 /* Visit the next key */
381                 nextkey = gdbm_nextkey(db, key);
382                 free(key.dptr);
383                 key = nextkey;
384         }
385
386         /* Pass ownership of the array to the caller */
387         *list = k_list;
388         ret = true;
389
390 end:
391         if (!ret && k_list) {
392                 for (uint16_t i = 0; i < k_list->len; i++) {
393                         current = buxton_array_get(k_list, i);
394                         if (!current) {
395                                 break;
396                         }
397                         free(current->store.d_string.value);
398                         free(current);
399                 }
400                 buxton_array_free(&k_list, NULL);
401         }
402         return ret;
403 }
404
405 _bx_export_ void buxton_module_destroy(void)
406 {
407         const char *key;
408         Iterator iterator;
409         GDBM_FILE db;
410
411         /* close all gdbm handles */
412         HASHMAP_FOREACH_KEY(db, key, _resources, iterator) {
413                 hashmap_remove(_resources, key);
414                 gdbm_close(db);
415                 free((void *)key);
416         }
417         hashmap_free(_resources);
418         _resources = NULL;
419 }
420
421 _bx_export_ bool buxton_module_init(BuxtonBackend *backend)
422 {
423
424         assert(backend);
425
426         /* Point the struct methods back to our own */
427         backend->set_value = &set_value;
428         backend->get_value = &get_value;
429         backend->list_keys = &list_keys;
430         backend->unset_value = &unset_value;
431         backend->create_db = (module_db_init_func) &db_for_resource;
432
433         _resources = hashmap_new(string_hash_func, string_compare_func);
434         if (!_resources) {
435                 abort();
436         }
437
438         return true;
439 }
440
441 /*
442  * Editor modelines  -  http://www.wireshark.org/tools/modelines.html
443  *
444  * Local variables:
445  * c-basic-offset: 8
446  * tab-width: 8
447  * indent-tabs-mode: t
448  * End:
449  *
450  * vi: set shiftwidth=8 tabstop=8 noexpandtab:
451  * :indentSize=8:tabSize=8:noTabs=false:
452  */