Fix -O3 -Werror=unused-result build in dcache.c (#420)
[heimdal.git] / lib / base / db.c
blob9440916848825877734458221500694aa4ce31fd
1 /*
2 * Copyright (c) 2011, Secure Endpoints Inc.
3 * All rights reserved.
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
9 * - Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
12 * - Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in
14 * the documentation and/or other materials provided with the
15 * distribution.
17 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
20 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
21 * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
22 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
24 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
25 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
26 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
27 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
28 * OF THE POSSIBILITY OF SUCH DAMAGE.
32 * This is a pluggable simple DB abstraction, with a simple get/set/
33 * delete key/value pair interface.
35 * Plugins may provide any of the following optional features:
37 * - tables -- multiple attribute/value tables in one DB
38 * - locking
39 * - transactions (i.e., allow any heim_object_t as key or value)
40 * - transcoding of values
42 * Stackable plugins that provide missing optional features are
43 * possible.
45 * Any plugin that provides locking will also provide transactions, but
46 * those transactions will not be atomic in the face of failures (a
47 * memory-based rollback log is used).
50 #include <errno.h>
51 #include <stdio.h>
52 #include <stdlib.h>
53 #include <string.h>
54 #include <sys/types.h>
55 #include <sys/stat.h>
56 #ifdef WIN32
57 #include <io.h>
58 #else
59 #include <sys/file.h>
60 #endif
61 #ifdef HAVE_UNISTD_H
62 #include <unistd.h>
63 #endif
64 #include <fcntl.h>
66 #include "baselocl.h"
67 #include <base64.h>
69 #define HEIM_ENOMEM(ep) \
70 (((ep) && !*(ep)) ? \
71 heim_error_get_code((*(ep) = heim_error_create_enomem())) : ENOMEM)
73 #define HEIM_ERROR_HELPER(ep, ec, args) \
74 (((ep) && !*(ep)) ? \
75 heim_error_get_code((*(ep) = heim_error_create args)) : (ec))
77 #define HEIM_ERROR(ep, ec, args) \
78 (ec == ENOMEM) ? HEIM_ENOMEM(ep) : HEIM_ERROR_HELPER(ep, ec, args);
80 static heim_string_t to_base64(heim_data_t, heim_error_t *);
81 static heim_data_t from_base64(heim_string_t, heim_error_t *);
83 static int open_file(const char *, int , int, int *, heim_error_t *);
84 static int read_json(const char *, heim_object_t *, heim_error_t *);
85 static struct heim_db_type json_dbt;
87 static void db_dealloc(void *ptr);
89 struct heim_type_data db_object = {
90 HEIM_TID_DB,
91 "db-object",
92 NULL,
93 db_dealloc,
94 NULL,
95 NULL,
96 NULL,
97 NULL
101 static heim_base_once_t db_plugin_init_once = HEIM_BASE_ONCE_INIT;
103 static heim_dict_t db_plugins;
105 typedef struct db_plugin {
106 heim_string_t name;
107 heim_db_plug_open_f_t openf;
108 heim_db_plug_clone_f_t clonef;
109 heim_db_plug_close_f_t closef;
110 heim_db_plug_lock_f_t lockf;
111 heim_db_plug_unlock_f_t unlockf;
112 heim_db_plug_sync_f_t syncf;
113 heim_db_plug_begin_f_t beginf;
114 heim_db_plug_commit_f_t commitf;
115 heim_db_plug_rollback_f_t rollbackf;
116 heim_db_plug_copy_value_f_t copyf;
117 heim_db_plug_set_value_f_t setf;
118 heim_db_plug_del_key_f_t delf;
119 heim_db_plug_iter_f_t iterf;
120 void *data;
121 } db_plugin_desc, *db_plugin;
123 struct heim_db_data {
124 db_plugin plug;
125 heim_string_t dbtype;
126 heim_string_t dbname;
127 heim_dict_t options;
128 void *db_data;
129 heim_data_t to_release;
130 heim_error_t error;
131 int ret;
132 unsigned int in_transaction:1;
133 unsigned int ro:1;
134 unsigned int ro_tx:1;
135 heim_dict_t set_keys;
136 heim_dict_t del_keys;
137 heim_string_t current_table;
140 static int
141 db_do_log_actions(heim_db_t db, heim_error_t *error);
142 static int
143 db_replay_log(heim_db_t db, heim_error_t *error);
145 static HEIMDAL_MUTEX db_type_mutex = HEIMDAL_MUTEX_INITIALIZER;
147 static void
148 db_init_plugins_once(void *arg)
150 db_plugins = heim_retain(arg);
153 static void
154 plugin_dealloc(void *arg)
156 db_plugin plug = arg;
158 heim_release(plug->name);
161 /** heim_db_register
162 * @brief Registers a DB type for use with heim_db_create().
164 * @param dbtype Name of DB type
165 * @param data Private data argument to the dbtype's openf method
166 * @param plugin Structure with DB type methods (function pointers)
168 * Backends that provide begin/commit/rollback methods must provide ACID
169 * semantics.
171 * The registered DB type will have ACID semantics for backends that do
172 * not provide begin/commit/rollback methods but do provide lock/unlock
173 * and rdjournal/wrjournal methods (using a replay log journalling
174 * scheme).
176 * If the registered DB type does not natively provide read vs. write
177 * transaction isolation but does provide a lock method then the DB will
178 * provide read/write transaction isolation.
180 * @return ENOMEM on failure, else 0.
182 * @addtogroup heimbase
185 heim_db_register(const char *dbtype,
186 void *data,
187 struct heim_db_type *plugin)
189 heim_dict_t plugins;
190 heim_string_t s;
191 db_plugin plug, plug2;
192 int ret = 0;
194 if ((plugin->beginf != NULL && plugin->commitf == NULL) ||
195 (plugin->beginf != NULL && plugin->rollbackf == NULL) ||
196 (plugin->lockf != NULL && plugin->unlockf == NULL) ||
197 plugin->copyf == NULL)
198 heim_abort("Invalid DB plugin; make sure methods are paired");
200 /* Initialize */
201 plugins = heim_dict_create(11);
202 if (plugins == NULL)
203 return ENOMEM;
204 heim_base_once_f(&db_plugin_init_once, plugins, db_init_plugins_once);
205 heim_release(plugins);
206 heim_assert(db_plugins != NULL, "heim_db plugin table initialized");
208 s = heim_string_create(dbtype);
209 if (s == NULL)
210 return ENOMEM;
212 plug = heim_alloc(sizeof (*plug), "db_plug", plugin_dealloc);
213 if (plug == NULL) {
214 heim_release(s);
215 return ENOMEM;
218 plug->name = heim_retain(s);
219 plug->openf = plugin->openf;
220 plug->clonef = plugin->clonef;
221 plug->closef = plugin->closef;
222 plug->lockf = plugin->lockf;
223 plug->unlockf = plugin->unlockf;
224 plug->syncf = plugin->syncf;
225 plug->beginf = plugin->beginf;
226 plug->commitf = plugin->commitf;
227 plug->rollbackf = plugin->rollbackf;
228 plug->copyf = plugin->copyf;
229 plug->setf = plugin->setf;
230 plug->delf = plugin->delf;
231 plug->iterf = plugin->iterf;
232 plug->data = data;
234 HEIMDAL_MUTEX_lock(&db_type_mutex);
235 plug2 = heim_dict_get_value(db_plugins, s);
236 if (plug2 == NULL)
237 ret = heim_dict_set_value(db_plugins, s, plug);
238 HEIMDAL_MUTEX_unlock(&db_type_mutex);
239 heim_release(plug);
240 heim_release(s);
242 return ret;
245 static void
246 db_dealloc(void *arg)
248 heim_db_t db = arg;
249 heim_assert(!db->in_transaction,
250 "rollback or commit heim_db_t before releasing it");
251 if (db->db_data)
252 (void) db->plug->closef(db->db_data, NULL);
253 heim_release(db->to_release);
254 heim_release(db->dbtype);
255 heim_release(db->dbname);
256 heim_release(db->options);
257 heim_release(db->set_keys);
258 heim_release(db->del_keys);
259 heim_release(db->error);
262 struct dbtype_iter {
263 heim_db_t db;
264 const char *dbname;
265 heim_dict_t options;
266 heim_error_t *error;
270 * Helper to create a DB handle with the first registered DB type that
271 * can open the given DB. This is useful when the app doesn't know the
272 * DB type a priori. This assumes that DB types can "taste" DBs, either
273 * from the filename extension or from the actual file contents.
275 static void
276 dbtype_iter2create_f(heim_object_t dbtype, heim_object_t junk, void *arg)
278 struct dbtype_iter *iter_ctx = arg;
280 if (iter_ctx->db != NULL)
281 return;
282 iter_ctx->db = heim_db_create(heim_string_get_utf8(dbtype),
283 iter_ctx->dbname, iter_ctx->options,
284 iter_ctx->error);
288 * Open a database of the given dbtype.
290 * Database type names can be composed of one or more pseudo-DB types
291 * and one concrete DB type joined with a '+' between each. For
292 * example: "transaction+bdb" might be a Berkeley DB with a layer above
293 * that provides transactions.
295 * Options may be provided via a dict (an associative array). Existing
296 * options include:
298 * - "create", with any value (create if DB doesn't exist)
299 * - "exclusive", with any value (exclusive create)
300 * - "truncate", with any value (truncate the DB)
301 * - "read-only", with any value (disallow writes)
302 * - "sync", with any value (make transactions durable)
303 * - "journal-name", with a string value naming a journal file name
305 * @param dbtype Name of DB type
306 * @param dbname Name of DB (likely a file path)
307 * @param options Options dict
308 * @param db Output open DB handle
309 * @param error Output error object
311 * @return a DB handle
313 * @addtogroup heimbase
315 heim_db_t
316 heim_db_create(const char *dbtype, const char *dbname,
317 heim_dict_t options, heim_error_t *error)
319 heim_string_t s;
320 char *p;
321 db_plugin plug;
322 heim_db_t db;
323 int ret = 0;
325 if (options == NULL) {
326 options = heim_dict_create(11);
327 if (options == NULL) {
328 if (error)
329 *error = heim_error_create_enomem();
330 return NULL;
332 } else {
333 (void) heim_retain(options);
336 if (db_plugins == NULL) {
337 heim_release(options);
338 return NULL;
341 if (dbtype == NULL || *dbtype == '\0') {
342 struct dbtype_iter iter_ctx = { NULL, dbname, options, error};
344 /* Try all dbtypes */
345 heim_dict_iterate_f(db_plugins, &iter_ctx, dbtype_iter2create_f);
346 heim_release(options);
347 return iter_ctx.db;
348 } else if (strstr(dbtype, "json")) {
349 (void) heim_db_register(dbtype, NULL, &json_dbt);
353 * Allow for dbtypes that are composed from pseudo-dbtypes chained
354 * to a real DB type with '+'. For example a pseudo-dbtype might
355 * add locking, transactions, transcoding of values, ...
357 p = strchr(dbtype, '+');
358 if (p != NULL)
359 s = heim_string_create_with_bytes(dbtype, p - dbtype);
360 else
361 s = heim_string_create(dbtype);
362 if (s == NULL) {
363 heim_release(options);
364 return NULL;
367 HEIMDAL_MUTEX_lock(&db_type_mutex);
368 plug = heim_dict_get_value(db_plugins, s);
369 HEIMDAL_MUTEX_unlock(&db_type_mutex);
370 heim_release(s);
371 if (plug == NULL) {
372 if (error)
373 *error = heim_error_create(ENOENT,
374 N_("Heimdal DB plugin not found: %s", ""),
375 dbtype);
376 heim_release(options);
377 return NULL;
380 db = _heim_alloc_object(&db_object, sizeof(*db));
381 if (db == NULL) {
382 heim_release(options);
383 return NULL;
386 db->in_transaction = 0;
387 db->ro_tx = 0;
388 db->set_keys = NULL;
389 db->del_keys = NULL;
390 db->plug = plug;
391 db->options = options;
393 ret = plug->openf(plug->data, dbtype, dbname, options, &db->db_data, error);
394 if (ret) {
395 heim_release(db);
396 if (error && *error == NULL)
397 *error = heim_error_create(ENOENT,
398 N_("Heimdal DB could not be opened: %s", ""),
399 dbname);
400 return NULL;
403 ret = db_replay_log(db, error);
404 if (ret) {
405 heim_release(db);
406 return NULL;
409 if (plug->clonef == NULL) {
410 db->dbtype = heim_string_create(dbtype);
411 db->dbname = heim_string_create(dbname);
413 if (!db->dbtype || ! db->dbname) {
414 heim_release(db);
415 if (error)
416 *error = heim_error_create_enomem();
417 return NULL;
421 return db;
425 * Clone (duplicate) an open DB handle.
427 * This is useful for multi-threaded applications. Applications must
428 * synchronize access to any given DB handle.
430 * Returns EBUSY if there is an open transaction for the input db.
432 * @param db Open DB handle
433 * @param error Output error object
435 * @return a DB handle
437 * @addtogroup heimbase
439 heim_db_t
440 heim_db_clone(heim_db_t db, heim_error_t *error)
442 heim_db_t result;
443 int ret;
445 if (heim_get_tid(db) != HEIM_TID_DB)
446 heim_abort("Expected a database");
447 if (db->in_transaction)
448 heim_abort("DB handle is busy");
450 if (db->plug->clonef == NULL) {
451 return heim_db_create(heim_string_get_utf8(db->dbtype),
452 heim_string_get_utf8(db->dbname),
453 db->options, error);
456 result = _heim_alloc_object(&db_object, sizeof(*result));
457 if (result == NULL) {
458 if (error)
459 *error = heim_error_create_enomem();
460 return NULL;
463 result->set_keys = NULL;
464 result->del_keys = NULL;
465 ret = db->plug->clonef(db->db_data, &result->db_data, error);
466 if (ret) {
467 heim_release(result);
468 if (error && !*error)
469 *error = heim_error_create(ENOENT,
470 N_("Could not re-open DB while cloning", ""));
471 return NULL;
473 db->db_data = NULL;
474 return result;
478 * Open a transaction on the given db.
480 * @param db Open DB handle
481 * @param error Output error object
483 * @return 0 on success, system error otherwise
485 * @addtogroup heimbase
488 heim_db_begin(heim_db_t db, int read_only, heim_error_t *error)
490 int ret;
492 if (heim_get_tid(db) != HEIM_TID_DB)
493 return EINVAL;
495 if (db->in_transaction && (read_only || !db->ro_tx || (!read_only && !db->ro_tx)))
496 heim_abort("DB already in transaction");
498 if (db->plug->setf == NULL || db->plug->delf == NULL)
499 return EINVAL;
501 if (db->plug->beginf) {
502 ret = db->plug->beginf(db->db_data, read_only, error);
503 if (ret)
504 return ret;
505 } else if (!db->in_transaction) {
506 /* Try to emulate transactions */
508 if (db->plug->lockf == NULL)
509 return EINVAL; /* can't lock? -> no transactions */
511 /* Assume unlock provides sync/durability */
512 ret = db->plug->lockf(db->db_data, read_only, error);
513 if (ret)
514 return ret;
516 ret = db_replay_log(db, error);
517 if (ret) {
518 ret = db->plug->unlockf(db->db_data, error);
519 return ret;
522 db->set_keys = heim_dict_create(11);
523 if (db->set_keys == NULL)
524 return ENOMEM;
525 db->del_keys = heim_dict_create(11);
526 if (db->del_keys == NULL) {
527 heim_release(db->set_keys);
528 db->set_keys = NULL;
529 return ENOMEM;
531 } else {
532 heim_assert(read_only == 0, "Internal error");
533 ret = db->plug->lockf(db->db_data, 0, error);
534 if (ret)
535 return ret;
537 db->in_transaction = 1;
538 db->ro_tx = !!read_only;
539 return 0;
543 * Commit an open transaction on the given db.
545 * @param db Open DB handle
546 * @param error Output error object
548 * @return 0 on success, system error otherwise
550 * @addtogroup heimbase
553 heim_db_commit(heim_db_t db, heim_error_t *error)
555 int ret, ret2;
556 heim_string_t journal_fname = NULL;
558 if (heim_get_tid(db) != HEIM_TID_DB)
559 return EINVAL;
560 if (!db->in_transaction)
561 return 0;
562 if (db->plug->commitf == NULL && db->plug->lockf == NULL)
563 return EINVAL;
565 if (db->plug->commitf != NULL) {
566 ret = db->plug->commitf(db->db_data, error);
567 if (ret)
568 (void) db->plug->rollbackf(db->db_data, error);
570 db->in_transaction = 0;
571 db->ro_tx = 0;
572 return ret;
575 if (db->ro_tx) {
576 ret = 0;
577 goto done;
580 if (db->options == NULL)
581 journal_fname = heim_dict_get_value(db->options, HSTR("journal-filename"));
583 if (journal_fname != NULL) {
584 heim_array_t a;
585 heim_string_t journal_contents;
586 size_t len, bytes;
587 int save_errno;
589 /* Create contents for replay log */
590 ret = ENOMEM;
591 a = heim_array_create();
592 if (a == NULL)
593 goto err;
594 ret = heim_array_append_value(a, db->set_keys);
595 if (ret) {
596 heim_release(a);
597 goto err;
599 ret = heim_array_append_value(a, db->del_keys);
600 if (ret) {
601 heim_release(a);
602 goto err;
604 journal_contents = heim_json_copy_serialize(a, 0, error);
605 heim_release(a);
607 /* Write replay log */
608 if (journal_fname != NULL) {
609 int fd;
611 ret = open_file(heim_string_get_utf8(journal_fname), 1, 0, &fd, error);
612 if (ret) {
613 heim_release(journal_contents);
614 goto err;
616 len = strlen(heim_string_get_utf8(journal_contents));
617 bytes = write(fd, heim_string_get_utf8(journal_contents), len);
618 save_errno = errno;
619 heim_release(journal_contents);
620 ret = close(fd);
621 if (bytes != len) {
622 /* Truncate replay log */
623 (void) open_file(heim_string_get_utf8(journal_fname), 1, 0, NULL, error);
624 ret = save_errno;
625 goto err;
627 if (ret)
628 goto err;
632 /* Apply logged actions */
633 ret = db_do_log_actions(db, error);
634 if (ret)
635 return ret;
637 if (db->plug->syncf != NULL) {
638 /* fsync() or whatever */
639 ret = db->plug->syncf(db->db_data, error);
640 if (ret)
641 return ret;
644 /* Truncate replay log and we're done */
645 if (journal_fname != NULL) {
646 int fd;
648 ret2 = open_file(heim_string_get_utf8(journal_fname), 1, 0, &fd, error);
649 if (ret2 == 0)
650 (void) close(fd);
654 * Clean up; if we failed to remore the replay log that's OK, we'll
655 * handle that again in heim_db_commit()
657 done:
658 heim_release(db->set_keys);
659 heim_release(db->del_keys);
660 db->set_keys = NULL;
661 db->del_keys = NULL;
662 db->in_transaction = 0;
663 db->ro_tx = 0;
665 ret2 = db->plug->unlockf(db->db_data, error);
666 if (ret == 0)
667 ret = ret2;
669 return ret;
671 err:
672 return HEIM_ERROR(error, ret,
673 (ret, N_("Error while committing transaction: %s", ""),
674 strerror(ret)));
678 * Rollback an open transaction on the given db.
680 * @param db Open DB handle
681 * @param error Output error object
683 * @return 0 on success, system error otherwise
685 * @addtogroup heimbase
688 heim_db_rollback(heim_db_t db, heim_error_t *error)
690 int ret = 0;
692 if (heim_get_tid(db) != HEIM_TID_DB)
693 return EINVAL;
694 if (!db->in_transaction)
695 return 0;
697 if (db->plug->rollbackf != NULL)
698 ret = db->plug->rollbackf(db->db_data, error);
699 else if (db->plug->unlockf != NULL)
700 ret = db->plug->unlockf(db->db_data, error);
702 heim_release(db->set_keys);
703 heim_release(db->del_keys);
704 db->set_keys = NULL;
705 db->del_keys = NULL;
706 db->in_transaction = 0;
707 db->ro_tx = 0;
709 return ret;
713 * Get type ID of heim_db_t objects.
715 * @addtogroup heimbase
717 heim_tid_t
718 heim_db_get_type_id(void)
720 return HEIM_TID_DB;
723 heim_data_t
724 _heim_db_get_value(heim_db_t db, heim_string_t table, heim_data_t key,
725 heim_error_t *error)
727 heim_release(db->to_release);
728 db->to_release = heim_db_copy_value(db, table, key, error);
729 return db->to_release;
733 * Lookup a key's value in the DB.
735 * Returns 0 on success, -1 if the key does not exist in the DB, or a
736 * system error number on failure.
738 * @param db Open DB handle
739 * @param key Key
740 * @param error Output error object
742 * @return the value (retained), if there is one for the given key
744 * @addtogroup heimbase
746 heim_data_t
747 heim_db_copy_value(heim_db_t db, heim_string_t table, heim_data_t key,
748 heim_error_t *error)
750 heim_object_t v;
751 heim_data_t result;
753 if (heim_get_tid(db) != HEIM_TID_DB)
754 return NULL;
756 if (error != NULL)
757 *error = NULL;
759 if (table == NULL)
760 table = HSTR("");
762 if (db->in_transaction) {
763 heim_string_t key64;
765 key64 = to_base64(key, error);
766 if (key64 == NULL) {
767 if (error)
768 *error = heim_error_create_enomem();
769 return NULL;
772 v = heim_path_copy(db->set_keys, error, table, key64, NULL);
773 if (v != NULL) {
774 heim_release(key64);
775 return v;
777 v = heim_path_copy(db->del_keys, error, table, key64, NULL); /* can't be NULL */
778 heim_release(key64);
779 if (v != NULL)
780 return NULL;
783 result = db->plug->copyf(db->db_data, table, key, error);
785 return result;
789 * Set a key's value in the DB.
791 * @param db Open DB handle
792 * @param key Key
793 * @param value Value (if NULL the key will be deleted, but empty is OK)
794 * @param error Output error object
796 * @return 0 on success, system error otherwise
798 * @addtogroup heimbase
801 heim_db_set_value(heim_db_t db, heim_string_t table,
802 heim_data_t key, heim_data_t value, heim_error_t *error)
804 heim_string_t key64 = NULL;
805 int ret;
807 if (error != NULL)
808 *error = NULL;
810 if (table == NULL)
811 table = HSTR("");
813 if (value == NULL)
814 /* Use heim_null_t instead of NULL */
815 return heim_db_delete_key(db, table, key, error);
817 if (heim_get_tid(db) != HEIM_TID_DB)
818 return EINVAL;
820 if (heim_get_tid(key) != HEIM_TID_DATA)
821 return HEIM_ERROR(error, EINVAL,
822 (EINVAL, N_("DB keys must be data", "")));
824 if (db->plug->setf == NULL)
825 return EBADF;
827 if (!db->in_transaction) {
828 ret = heim_db_begin(db, 0, error);
829 if (ret)
830 goto err;
831 heim_assert(db->in_transaction, "Internal error");
832 ret = heim_db_set_value(db, table, key, value, error);
833 if (ret) {
834 (void) heim_db_rollback(db, NULL);
835 return ret;
837 return heim_db_commit(db, error);
840 /* Transaction emulation */
841 heim_assert(db->set_keys != NULL, "Internal error");
842 key64 = to_base64(key, error);
843 if (key64 == NULL)
844 return HEIM_ENOMEM(error);
846 if (db->ro_tx) {
847 ret = heim_db_begin(db, 0, error);
848 if (ret)
849 goto err;
851 ret = heim_path_create(db->set_keys, 29, value, error, table, key64, NULL);
852 if (ret)
853 goto err;
854 heim_path_delete(db->del_keys, error, table, key64, NULL);
855 heim_release(key64);
857 return 0;
859 err:
860 heim_release(key64);
861 return HEIM_ERROR(error, ret,
862 (ret, N_("Could not set a dict value while while "
863 "setting a DB value", "")));
867 * Delete a key and its value from the DB
870 * @param db Open DB handle
871 * @param key Key
872 * @param error Output error object
874 * @return 0 on success, system error otherwise
876 * @addtogroup heimbase
879 heim_db_delete_key(heim_db_t db, heim_string_t table, heim_data_t key,
880 heim_error_t *error)
882 heim_string_t key64 = NULL;
883 int ret;
885 if (error != NULL)
886 *error = NULL;
888 if (table == NULL)
889 table = HSTR("");
891 if (heim_get_tid(db) != HEIM_TID_DB)
892 return EINVAL;
894 if (db->plug->delf == NULL)
895 return EBADF;
897 if (!db->in_transaction) {
898 ret = heim_db_begin(db, 0, error);
899 if (ret)
900 goto err;
901 heim_assert(db->in_transaction, "Internal error");
902 ret = heim_db_delete_key(db, table, key, error);
903 if (ret) {
904 (void) heim_db_rollback(db, NULL);
905 return ret;
907 return heim_db_commit(db, error);
910 /* Transaction emulation */
911 heim_assert(db->set_keys != NULL, "Internal error");
912 key64 = to_base64(key, error);
913 if (key64 == NULL)
914 return HEIM_ENOMEM(error);
915 if (db->ro_tx) {
916 ret = heim_db_begin(db, 0, error);
917 if (ret)
918 goto err;
920 ret = heim_path_create(db->del_keys, 29, heim_number_create(1), error, table, key64, NULL);
921 if (ret)
922 goto err;
923 heim_path_delete(db->set_keys, error, table, key64, NULL);
924 heim_release(key64);
926 return 0;
928 err:
929 heim_release(key64);
930 return HEIM_ERROR(error, ret,
931 (ret, N_("Could not set a dict value while while "
932 "deleting a DB value", "")));
936 * Iterate a callback function over keys and values from a DB.
938 * @param db Open DB handle
939 * @param iter_data Callback function's private data
940 * @param iter_f Callback function, called once per-key/value pair
941 * @param error Output error object
943 * @addtogroup heimbase
945 void
946 heim_db_iterate_f(heim_db_t db, heim_string_t table, void *iter_data,
947 heim_db_iterator_f_t iter_f, heim_error_t *error)
949 if (error != NULL)
950 *error = NULL;
952 if (heim_get_tid(db) != HEIM_TID_DB)
953 return;
955 if (!db->in_transaction)
956 db->plug->iterf(db->db_data, table, iter_data, iter_f, error);
959 static void
960 db_replay_log_table_set_keys_iter(heim_object_t key, heim_object_t value,
961 void *arg)
963 heim_db_t db = arg;
964 heim_data_t k, v;
966 if (db->ret)
967 return;
969 k = from_base64((heim_string_t)key, &db->error);
970 if (k == NULL) {
971 db->ret = ENOMEM;
972 return;
974 v = (heim_data_t)value;
976 db->ret = db->plug->setf(db->db_data, db->current_table, k, v, &db->error);
977 heim_release(k);
980 static void
981 db_replay_log_table_del_keys_iter(heim_object_t key, heim_object_t value,
982 void *arg)
984 heim_db_t db = arg;
985 heim_data_t k;
987 if (db->ret) {
988 db->ret = ENOMEM;
989 return;
992 k = from_base64((heim_string_t)key, &db->error);
993 if (k == NULL)
994 return;
996 db->ret = db->plug->delf(db->db_data, db->current_table, k, &db->error);
997 heim_release(k);
1000 static void
1001 db_replay_log_set_keys_iter(heim_object_t table, heim_object_t table_dict,
1002 void *arg)
1004 heim_db_t db = arg;
1006 if (db->ret)
1007 return;
1009 db->current_table = table;
1010 heim_dict_iterate_f(table_dict, db, db_replay_log_table_set_keys_iter);
1013 static void
1014 db_replay_log_del_keys_iter(heim_object_t table, heim_object_t table_dict,
1015 void *arg)
1017 heim_db_t db = arg;
1019 if (db->ret)
1020 return;
1022 db->current_table = table;
1023 heim_dict_iterate_f(table_dict, db, db_replay_log_table_del_keys_iter);
1026 static int
1027 db_do_log_actions(heim_db_t db, heim_error_t *error)
1029 int ret;
1031 if (error)
1032 *error = NULL;
1034 db->ret = 0;
1035 db->error = NULL;
1036 if (db->set_keys != NULL)
1037 heim_dict_iterate_f(db->set_keys, db, db_replay_log_set_keys_iter);
1038 if (db->del_keys != NULL)
1039 heim_dict_iterate_f(db->del_keys, db, db_replay_log_del_keys_iter);
1041 ret = db->ret;
1042 db->ret = 0;
1043 if (error && db->error) {
1044 *error = db->error;
1045 db->error = NULL;
1046 } else {
1047 heim_release(db->error);
1048 db->error = NULL;
1050 return ret;
1053 static int
1054 db_replay_log(heim_db_t db, heim_error_t *error)
1056 int ret;
1057 heim_string_t journal_fname = NULL;
1058 heim_object_t journal;
1059 size_t len;
1061 heim_assert(!db->in_transaction, "DB transaction not open");
1062 heim_assert(db->set_keys == NULL && db->set_keys == NULL, "DB transaction not open");
1064 if (error)
1065 *error = NULL;
1067 if (db->options == NULL)
1068 return 0;
1070 journal_fname = heim_dict_get_value(db->options, HSTR("journal-filename"));
1071 if (journal_fname == NULL)
1072 return 0;
1074 ret = read_json(heim_string_get_utf8(journal_fname), &journal, error);
1075 if (ret == ENOENT) {
1076 heim_release(journal_fname);
1077 return 0;
1079 if (ret == 0 && journal == NULL) {
1080 heim_release(journal_fname);
1081 return 0;
1083 if (ret != 0) {
1084 heim_release(journal_fname);
1085 return ret;
1088 if (heim_get_tid(journal) != HEIM_TID_ARRAY) {
1089 heim_release(journal_fname);
1090 return HEIM_ERROR(error, EINVAL,
1091 (ret, N_("Invalid journal contents; delete journal",
1092 "")));
1095 len = heim_array_get_length(journal);
1097 if (len > 0)
1098 db->set_keys = heim_array_get_value(journal, 0);
1099 if (len > 1)
1100 db->del_keys = heim_array_get_value(journal, 1);
1101 ret = db_do_log_actions(db, error);
1102 if (ret) {
1103 heim_release(journal_fname);
1104 return ret;
1107 /* Truncate replay log and we're done */
1108 ret = open_file(heim_string_get_utf8(journal_fname), 1, 0, NULL, error);
1109 heim_release(journal_fname);
1110 if (ret)
1111 return ret;
1112 heim_release(db->set_keys);
1113 heim_release(db->del_keys);
1114 db->set_keys = NULL;
1115 db->del_keys = NULL;
1117 return 0;
1120 static
1121 heim_string_t to_base64(heim_data_t data, heim_error_t *error)
1123 char *b64 = NULL;
1124 heim_string_t s = NULL;
1125 const heim_octet_string *d;
1126 int ret;
1128 d = heim_data_get_data(data);
1129 ret = rk_base64_encode(d->data, d->length, &b64);
1130 if (ret < 0 || b64 == NULL)
1131 goto enomem;
1132 s = heim_string_ref_create(b64, free);
1133 if (s == NULL)
1134 goto enomem;
1135 return s;
1137 enomem:
1138 free(b64);
1139 if (error)
1140 *error = heim_error_create_enomem();
1141 return NULL;
1144 static
1145 heim_data_t from_base64(heim_string_t s, heim_error_t *error)
1147 void *buf;
1148 size_t len;
1149 heim_data_t d;
1151 buf = malloc(strlen(heim_string_get_utf8(s)));
1152 if (buf == NULL)
1153 goto enomem;
1155 len = rk_base64_decode(heim_string_get_utf8(s), buf);
1156 d = heim_data_ref_create(buf, len, free);
1157 if (d == NULL)
1158 goto enomem;
1159 return d;
1161 enomem:
1162 free(buf);
1163 if (error)
1164 *error = heim_error_create_enomem();
1165 return NULL;
1169 static int
1170 open_file(const char *dbname, int for_write, int excl, int *fd_out, heim_error_t *error)
1172 #ifdef WIN32
1173 HANDLE hFile;
1174 int ret = 0;
1176 if (fd_out)
1177 *fd_out = -1;
1179 if (for_write)
1180 hFile = CreateFile(dbname, GENERIC_WRITE | GENERIC_READ, 0,
1181 NULL, /* we'll close as soon as we read */
1182 CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
1183 else
1184 hFile = CreateFile(dbname, GENERIC_READ, FILE_SHARE_READ,
1185 NULL, /* we'll close as soon as we read */
1186 OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
1187 if (hFile == INVALID_HANDLE_VALUE) {
1188 ret = GetLastError();
1189 _set_errno(ret); /* CreateFile() does not set errno */
1190 goto err;
1192 if (fd_out == NULL) {
1193 (void) CloseHandle(hFile);
1194 return 0;
1197 *fd_out = _open_osfhandle((intptr_t) hFile, 0);
1198 if (*fd_out < 0) {
1199 ret = errno;
1200 (void) CloseHandle(hFile);
1201 goto err;
1204 /* No need to lock given share deny mode */
1205 return 0;
1207 err:
1208 if (error != NULL) {
1209 char *s = NULL;
1210 FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ALLOCATE_BUFFER,
1211 0, ret, 0, (LPTSTR) &s, 0, NULL);
1212 *error = heim_error_create(ret, N_("Could not open JSON file %s: %s", ""),
1213 dbname, s ? s : "<error formatting error>");
1214 LocalFree(s);
1216 return ret;
1217 #else
1218 int ret = 0;
1219 int fd;
1221 if (fd_out)
1222 *fd_out = -1;
1224 if (for_write && excl)
1225 fd = open(dbname, O_CREAT | O_EXCL | O_WRONLY, 0600);
1226 else if (for_write)
1227 fd = open(dbname, O_CREAT | O_TRUNC | O_WRONLY, 0600);
1228 else
1229 fd = open(dbname, O_RDONLY);
1230 if (fd < 0) {
1231 if (error != NULL)
1232 *error = heim_error_create(ret, N_("Could not open JSON file %s: %s", ""),
1233 dbname, strerror(errno));
1234 return errno;
1237 if (fd_out == NULL) {
1238 (void) close(fd);
1239 return 0;
1242 ret = flock(fd, for_write ? LOCK_EX : LOCK_SH);
1243 if (ret == -1) {
1244 /* Note that we if O_EXCL we're leaving the [lock] file around */
1245 (void) close(fd);
1246 return HEIM_ERROR(error, errno,
1247 (errno, N_("Could not lock JSON file %s: %s", ""),
1248 dbname, strerror(errno)));
1251 *fd_out = fd;
1253 return 0;
1254 #endif
1257 static int
1258 read_json(const char *dbname, heim_object_t *out, heim_error_t *error)
1260 struct stat st;
1261 char *str = NULL;
1262 int ret;
1263 int fd = -1;
1264 ssize_t bytes;
1266 *out = NULL;
1267 ret = open_file(dbname, 0, 0, &fd, error);
1268 if (ret)
1269 return ret;
1271 ret = fstat(fd, &st);
1272 if (ret == -1) {
1273 (void) close(fd);
1274 return HEIM_ERROR(error, errno,
1275 (ret, N_("Could not stat JSON DB %s: %s", ""),
1276 dbname, strerror(errno)));
1279 if (st.st_size == 0) {
1280 (void) close(fd);
1281 return 0;
1284 str = malloc(st.st_size + 1);
1285 if (str == NULL) {
1286 (void) close(fd);
1287 return HEIM_ENOMEM(error);
1290 bytes = read(fd, str, st.st_size);
1291 (void) close(fd);
1292 if (bytes != st.st_size) {
1293 free(str);
1294 if (bytes >= 0)
1295 errno = EINVAL; /* ?? */
1296 return HEIM_ERROR(error, errno,
1297 (ret, N_("Could not read JSON DB %s: %s", ""),
1298 dbname, strerror(errno)));
1300 str[st.st_size] = '\0';
1301 *out = heim_json_create(str, 10, 0, error);
1302 free(str);
1303 if (*out == NULL)
1304 return (error && *error) ? heim_error_get_code(*error) : EINVAL;
1305 return 0;
1308 typedef struct json_db {
1309 heim_dict_t dict;
1310 heim_string_t dbname;
1311 heim_string_t bkpname;
1312 int fd;
1313 time_t last_read_time;
1314 unsigned int read_only:1;
1315 unsigned int locked:1;
1316 unsigned int locked_needs_unlink:1;
1317 } *json_db_t;
1319 static int
1320 json_db_open(void *plug, const char *dbtype, const char *dbname,
1321 heim_dict_t options, void **db, heim_error_t *error)
1323 json_db_t jsondb;
1324 heim_dict_t contents = NULL;
1325 heim_string_t dbname_s = NULL;
1326 heim_string_t bkpname_s = NULL;
1328 if (error)
1329 *error = NULL;
1330 if (dbtype && *dbtype && strcmp(dbtype, "json"))
1331 return HEIM_ERROR(error, EINVAL, (EINVAL, N_("Wrong DB type", "")));
1332 if (dbname && *dbname && strcmp(dbname, "MEMORY") != 0) {
1333 char *ext = strrchr(dbname, '.');
1334 char *bkpname;
1335 size_t len;
1336 int ret;
1338 if (ext == NULL || strcmp(ext, ".json") != 0)
1339 return HEIM_ERROR(error, EINVAL,
1340 (EINVAL, N_("JSON DB files must end in .json",
1341 "")));
1343 if (options) {
1344 heim_object_t vc, ve, vt;
1346 vc = heim_dict_get_value(options, HSTR("create"));
1347 ve = heim_dict_get_value(options, HSTR("exclusive"));
1348 vt = heim_dict_get_value(options, HSTR("truncate"));
1349 if (vc && vt) {
1350 ret = open_file(dbname, 1, ve ? 1 : 0, NULL, error);
1351 if (ret)
1352 return ret;
1353 } else if (vc || ve || vt) {
1354 return HEIM_ERROR(error, EINVAL,
1355 (EINVAL, N_("Invalid JSON DB open options",
1356 "")));
1359 * We don't want cloned handles to truncate the DB, eh?
1361 * We should really just create a copy of the options dict
1362 * rather than modify the caller's! But for that it'd be
1363 * nicer to have copy utilities in heimbase, something like
1364 * this:
1366 * heim_object_t heim_copy(heim_object_t src, int depth,
1367 * heim_error_t *error);
1369 * so that options = heim_copy(options, 1); means copy the
1370 * dict but nothing else (whereas depth == 0 would mean
1371 * heim_retain(), and depth > 1 would be copy that many
1372 * levels).
1374 heim_dict_delete_key(options, HSTR("create"));
1375 heim_dict_delete_key(options, HSTR("exclusive"));
1376 heim_dict_delete_key(options, HSTR("truncate"));
1378 dbname_s = heim_string_create(dbname);
1379 if (dbname_s == NULL)
1380 return HEIM_ENOMEM(error);
1382 len = snprintf(NULL, 0, "%s~", dbname);
1383 bkpname = malloc(len + 2);
1384 if (bkpname == NULL) {
1385 heim_release(dbname_s);
1386 return HEIM_ENOMEM(error);
1388 (void) snprintf(bkpname, len + 1, "%s~", dbname);
1389 bkpname_s = heim_string_create(bkpname);
1390 free(bkpname);
1391 if (bkpname_s == NULL) {
1392 heim_release(dbname_s);
1393 return HEIM_ENOMEM(error);
1396 ret = read_json(dbname, (heim_object_t *)&contents, error);
1397 if (ret) {
1398 heim_release(bkpname_s);
1399 heim_release(dbname_s);
1400 return ret;
1403 if (contents != NULL && heim_get_tid(contents) != HEIM_TID_DICT) {
1404 heim_release(bkpname_s);
1405 heim_release(dbname_s);
1406 return HEIM_ERROR(error, EINVAL,
1407 (EINVAL, N_("JSON DB contents not valid JSON",
1408 "")));
1412 jsondb = heim_alloc(sizeof (*jsondb), "json_db", NULL);
1413 if (jsondb == NULL) {
1414 heim_release(contents);
1415 heim_release(dbname_s);
1416 heim_release(bkpname_s);
1417 return ENOMEM;
1420 jsondb->last_read_time = time(NULL);
1421 jsondb->fd = -1;
1422 jsondb->dbname = dbname_s;
1423 jsondb->bkpname = bkpname_s;
1424 jsondb->read_only = 0;
1426 if (contents != NULL)
1427 jsondb->dict = contents;
1428 else {
1429 jsondb->dict = heim_dict_create(29);
1430 if (jsondb->dict == NULL) {
1431 heim_release(jsondb);
1432 return ENOMEM;
1436 *db = jsondb;
1437 return 0;
1440 static int
1441 json_db_close(void *db, heim_error_t *error)
1443 json_db_t jsondb = db;
1445 if (error)
1446 *error = NULL;
1447 if (jsondb->fd > -1)
1448 (void) close(jsondb->fd);
1449 jsondb->fd = -1;
1450 heim_release(jsondb->dbname);
1451 heim_release(jsondb->bkpname);
1452 heim_release(jsondb->dict);
1453 heim_release(jsondb);
1454 return 0;
1457 static int
1458 json_db_lock(void *db, int read_only, heim_error_t *error)
1460 json_db_t jsondb = db;
1461 int ret;
1463 heim_assert(jsondb->fd == -1 || (jsondb->read_only && !read_only),
1464 "DB locks are not recursive");
1466 jsondb->read_only = read_only ? 1 : 0;
1467 if (jsondb->fd > -1)
1468 return 0;
1470 ret = open_file(heim_string_get_utf8(jsondb->bkpname), 1, 1, &jsondb->fd, error);
1471 if (ret == 0) {
1472 jsondb->locked_needs_unlink = 1;
1473 jsondb->locked = 1;
1475 return ret;
1478 static int
1479 json_db_unlock(void *db, heim_error_t *error)
1481 json_db_t jsondb = db;
1482 int ret = 0;
1484 heim_assert(jsondb->locked, "DB not locked when unlock attempted");
1485 if (jsondb->fd > -1)
1486 ret = close(jsondb->fd);
1487 jsondb->fd = -1;
1488 jsondb->read_only = 0;
1489 jsondb->locked = 0;
1490 if (jsondb->locked_needs_unlink)
1491 unlink(heim_string_get_utf8(jsondb->bkpname));
1492 jsondb->locked_needs_unlink = 0;
1493 return ret;
1496 static int
1497 json_db_sync(void *db, heim_error_t *error)
1499 json_db_t jsondb = db;
1500 size_t len, bytes;
1501 heim_error_t e;
1502 heim_string_t json;
1503 const char *json_text = NULL;
1504 int ret = 0;
1505 int fd = -1;
1506 #ifdef WIN32
1507 int tries = 3;
1508 #endif
1510 heim_assert(jsondb->fd > -1, "DB not locked when sync attempted");
1512 json = heim_json_copy_serialize(jsondb->dict, 0, &e);
1513 if (json == NULL) {
1514 if (error)
1515 *error = e;
1516 else
1517 heim_release(e);
1518 return heim_error_get_code(e);
1521 json_text = heim_string_get_utf8(json);
1522 len = strlen(json_text);
1523 errno = 0;
1525 #ifdef WIN32
1526 while (tries--) {
1527 ret = open_file(heim_string_get_utf8(jsondb->dbname), 1, 0, &fd, error);
1528 if (ret == 0)
1529 break;
1530 sleep(1);
1532 if (ret) {
1533 heim_release(json);
1534 return ret;
1536 #else
1537 fd = jsondb->fd;
1538 #endif /* WIN32 */
1540 bytes = write(fd, json_text, len);
1541 heim_release(json);
1542 if (bytes != len)
1543 return errno ? errno : EIO;
1544 ret = fsync(fd);
1545 if (ret)
1546 return ret;
1548 #ifdef WIN32
1549 ret = close(fd);
1550 if (ret)
1551 return GetLastError();
1552 #else
1553 ret = rename(heim_string_get_utf8(jsondb->bkpname), heim_string_get_utf8(jsondb->dbname));
1554 if (ret == 0) {
1555 jsondb->locked_needs_unlink = 0;
1556 return 0;
1558 #endif /* WIN32 */
1560 return errno;
1563 static heim_data_t
1564 json_db_copy_value(void *db, heim_string_t table, heim_data_t key,
1565 heim_error_t *error)
1567 json_db_t jsondb = db;
1568 heim_string_t key_string;
1569 const heim_octet_string *key_data = heim_data_get_data(key);
1570 struct stat st;
1571 heim_data_t result;
1573 if (error)
1574 *error = NULL;
1576 if (strnlen(key_data->data, key_data->length) != key_data->length) {
1577 HEIM_ERROR(error, EINVAL,
1578 (EINVAL, N_("JSON DB requires keys that are actually "
1579 "strings", "")));
1580 return NULL;
1583 if (stat(heim_string_get_utf8(jsondb->dbname), &st) == -1) {
1584 HEIM_ERROR(error, errno,
1585 (errno, N_("Could not stat JSON DB file", "")));
1586 return NULL;
1589 if (st.st_mtime > jsondb->last_read_time ||
1590 st.st_ctime > jsondb->last_read_time) {
1591 heim_dict_t contents = NULL;
1592 int ret;
1594 /* Ignore file is gone (ENOENT) */
1595 ret = read_json(heim_string_get_utf8(jsondb->dbname),
1596 (heim_object_t *)&contents, error);
1597 if (ret)
1598 return NULL;
1599 if (contents == NULL)
1600 contents = heim_dict_create(29);
1601 heim_release(jsondb->dict);
1602 jsondb->dict = contents;
1603 jsondb->last_read_time = time(NULL);
1606 key_string = heim_string_create_with_bytes(key_data->data,
1607 key_data->length);
1608 if (key_string == NULL) {
1609 (void) HEIM_ENOMEM(error);
1610 return NULL;
1613 result = heim_path_copy(jsondb->dict, error, table, key_string, NULL);
1614 heim_release(key_string);
1615 return result;
1618 static int
1619 json_db_set_value(void *db, heim_string_t table,
1620 heim_data_t key, heim_data_t value, heim_error_t *error)
1622 json_db_t jsondb = db;
1623 heim_string_t key_string;
1624 const heim_octet_string *key_data = heim_data_get_data(key);
1625 int ret;
1627 if (error)
1628 *error = NULL;
1630 if (strnlen(key_data->data, key_data->length) != key_data->length)
1631 return HEIM_ERROR(error, EINVAL,
1632 (EINVAL,
1633 N_("JSON DB requires keys that are actually strings",
1634 "")));
1636 key_string = heim_string_create_with_bytes(key_data->data,
1637 key_data->length);
1638 if (key_string == NULL)
1639 return HEIM_ENOMEM(error);
1641 if (table == NULL)
1642 table = HSTR("");
1644 ret = heim_path_create(jsondb->dict, 29, value, error, table, key_string, NULL);
1645 heim_release(key_string);
1646 return ret;
1649 static int
1650 json_db_del_key(void *db, heim_string_t table, heim_data_t key,
1651 heim_error_t *error)
1653 json_db_t jsondb = db;
1654 heim_string_t key_string;
1655 const heim_octet_string *key_data = heim_data_get_data(key);
1657 if (error)
1658 *error = NULL;
1660 if (strnlen(key_data->data, key_data->length) != key_data->length)
1661 return HEIM_ERROR(error, EINVAL,
1662 (EINVAL,
1663 N_("JSON DB requires keys that are actually strings",
1664 "")));
1666 key_string = heim_string_create_with_bytes(key_data->data,
1667 key_data->length);
1668 if (key_string == NULL)
1669 return HEIM_ENOMEM(error);
1671 if (table == NULL)
1672 table = HSTR("");
1674 heim_path_delete(jsondb->dict, error, table, key_string, NULL);
1675 heim_release(key_string);
1676 return 0;
1679 struct json_db_iter_ctx {
1680 heim_db_iterator_f_t iter_f;
1681 void *iter_ctx;
1684 static void json_db_iter_f(heim_object_t key, heim_object_t value, void *arg)
1686 struct json_db_iter_ctx *ctx = arg;
1687 const char *key_string;
1688 heim_data_t key_data;
1690 key_string = heim_string_get_utf8((heim_string_t)key);
1691 key_data = heim_data_ref_create(key_string, strlen(key_string), NULL);
1692 ctx->iter_f(key_data, (heim_object_t)value, ctx->iter_ctx);
1693 heim_release(key_data);
1696 static void
1697 json_db_iter(void *db, heim_string_t table, void *iter_data,
1698 heim_db_iterator_f_t iter_f, heim_error_t *error)
1700 json_db_t jsondb = db;
1701 struct json_db_iter_ctx ctx;
1702 heim_dict_t table_dict;
1704 if (error)
1705 *error = NULL;
1707 if (table == NULL)
1708 table = HSTR("");
1710 table_dict = heim_dict_get_value(jsondb->dict, table);
1711 if (table_dict == NULL)
1712 return;
1714 ctx.iter_ctx = iter_data;
1715 ctx.iter_f = iter_f;
1717 heim_dict_iterate_f(table_dict, &ctx, json_db_iter_f);
1720 static struct heim_db_type json_dbt = {
1721 1, json_db_open, NULL, json_db_close,
1722 json_db_lock, json_db_unlock, json_db_sync,
1723 NULL, NULL, NULL,
1724 json_db_copy_value, json_db_set_value,
1725 json_db_del_key, json_db_iter