2 * Copyright (c) 2011, Secure Endpoints Inc.
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
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
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
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
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).
56 #include <sys/types.h>
71 #define HEIM_ENOMEM(ep) \
73 heim_error_get_code((*(ep) = heim_error_create_enomem())) : ENOMEM)
75 #define HEIM_ERROR_HELPER(ep, ec, args) \
77 heim_error_get_code((*(ep) = heim_error_create args)) : (ec))
79 #define HEIM_ERROR(ep, ec, args) \
80 (ec == ENOMEM) ? HEIM_ENOMEM(ep) : HEIM_ERROR_HELPER(ep, ec, args);
82 static heim_string_t
to_base64(heim_data_t
, heim_error_t
*);
83 static heim_data_t
from_base64(heim_string_t
, heim_error_t
*);
85 static int open_file(const char *, int , int, int *, heim_error_t
*);
86 static int read_json(const char *, heim_object_t
*, heim_error_t
*);
87 static struct heim_db_type json_dbt
;
89 static void HEIM_CALLCONV
db_dealloc(void *ptr
);
91 struct heim_type_data db_object
= {
103 static heim_base_once_t db_plugin_init_once
= HEIM_BASE_ONCE_INIT
;
105 static heim_dict_t db_plugins
;
107 typedef struct db_plugin
{
109 heim_db_plug_open_f_t openf
;
110 heim_db_plug_clone_f_t clonef
;
111 heim_db_plug_close_f_t closef
;
112 heim_db_plug_lock_f_t lockf
;
113 heim_db_plug_unlock_f_t unlockf
;
114 heim_db_plug_sync_f_t syncf
;
115 heim_db_plug_begin_f_t beginf
;
116 heim_db_plug_commit_f_t commitf
;
117 heim_db_plug_rollback_f_t rollbackf
;
118 heim_db_plug_copy_value_f_t copyf
;
119 heim_db_plug_set_value_f_t setf
;
120 heim_db_plug_del_key_f_t delf
;
121 heim_db_plug_iter_f_t iterf
;
123 } db_plugin_desc
, *db_plugin
;
125 struct heim_db_data
{
127 heim_string_t dbtype
;
128 heim_string_t dbname
;
131 heim_data_t to_release
;
134 unsigned int in_transaction
:1;
136 unsigned int ro_tx
:1;
137 heim_dict_t set_keys
;
138 heim_dict_t del_keys
;
139 heim_string_t current_table
;
143 db_do_log_actions(heim_db_t db
, heim_error_t
*error
);
145 db_replay_log(heim_db_t db
, heim_error_t
*error
);
147 static HEIMDAL_MUTEX db_type_mutex
= HEIMDAL_MUTEX_INITIALIZER
;
150 db_init_plugins_once(void *arg
)
152 db_plugins
= heim_retain(arg
);
155 static void HEIM_CALLCONV
156 plugin_dealloc(void *arg
)
158 db_plugin plug
= arg
;
160 heim_release(plug
->name
);
164 * @brief Registers a DB type for use with heim_db_create().
166 * @param dbtype Name of DB type
167 * @param data Private data argument to the dbtype's openf method
168 * @param plugin Structure with DB type methods (function pointers)
170 * Backends that provide begin/commit/rollback methods must provide ACID
173 * The registered DB type will have ACID semantics for backends that do
174 * not provide begin/commit/rollback methods but do provide lock/unlock
175 * and rdjournal/wrjournal methods (using a replay log journalling
178 * If the registered DB type does not natively provide read vs. write
179 * transaction isolation but does provide a lock method then the DB will
180 * provide read/write transaction isolation.
182 * @return ENOMEM on failure, else 0.
184 * @addtogroup heimbase
187 heim_db_register(const char *dbtype
,
189 struct heim_db_type
*plugin
)
193 db_plugin plug
, plug2
;
196 if ((plugin
->beginf
!= NULL
&& plugin
->commitf
== NULL
) ||
197 (plugin
->beginf
!= NULL
&& plugin
->rollbackf
== NULL
) ||
198 (plugin
->lockf
!= NULL
&& plugin
->unlockf
== NULL
) ||
199 plugin
->copyf
== NULL
)
200 heim_abort("Invalid DB plugin; make sure methods are paired");
203 plugins
= heim_dict_create(11);
206 heim_base_once_f(&db_plugin_init_once
, plugins
, db_init_plugins_once
);
207 heim_release(plugins
);
208 heim_assert(db_plugins
!= NULL
, "heim_db plugin table initialized");
210 s
= heim_string_create(dbtype
);
214 plug
= heim_alloc(sizeof (*plug
), "db_plug", plugin_dealloc
);
220 plug
->name
= heim_retain(s
);
221 plug
->openf
= plugin
->openf
;
222 plug
->clonef
= plugin
->clonef
;
223 plug
->closef
= plugin
->closef
;
224 plug
->lockf
= plugin
->lockf
;
225 plug
->unlockf
= plugin
->unlockf
;
226 plug
->syncf
= plugin
->syncf
;
227 plug
->beginf
= plugin
->beginf
;
228 plug
->commitf
= plugin
->commitf
;
229 plug
->rollbackf
= plugin
->rollbackf
;
230 plug
->copyf
= plugin
->copyf
;
231 plug
->setf
= plugin
->setf
;
232 plug
->delf
= plugin
->delf
;
233 plug
->iterf
= plugin
->iterf
;
236 HEIMDAL_MUTEX_lock(&db_type_mutex
);
237 plug2
= heim_dict_get_value(db_plugins
, s
);
239 ret
= heim_dict_set_value(db_plugins
, s
, plug
);
240 HEIMDAL_MUTEX_unlock(&db_type_mutex
);
247 static void HEIM_CALLCONV
248 db_dealloc(void *arg
)
251 heim_assert(!db
->in_transaction
,
252 "rollback or commit heim_db_t before releasing it");
254 (void) db
->plug
->closef(db
->db_data
, NULL
);
255 heim_release(db
->to_release
);
256 heim_release(db
->dbtype
);
257 heim_release(db
->dbname
);
258 heim_release(db
->options
);
259 heim_release(db
->set_keys
);
260 heim_release(db
->del_keys
);
261 heim_release(db
->error
);
272 * Helper to create a DB handle with the first registered DB type that
273 * can open the given DB. This is useful when the app doesn't know the
274 * DB type a priori. This assumes that DB types can "taste" DBs, either
275 * from the filename extension or from the actual file contents.
278 dbtype_iter2create_f(heim_object_t dbtype
, heim_object_t junk
, void *arg
)
280 struct dbtype_iter
*iter_ctx
= arg
;
282 if (iter_ctx
->db
!= NULL
)
284 iter_ctx
->db
= heim_db_create(heim_string_get_utf8(dbtype
),
285 iter_ctx
->dbname
, iter_ctx
->options
,
290 * Open a database of the given dbtype.
292 * Database type names can be composed of one or more pseudo-DB types
293 * and one concrete DB type joined with a '+' between each. For
294 * example: "transaction+bdb" might be a Berkeley DB with a layer above
295 * that provides transactions.
297 * Options may be provided via a dict (an associative array). Existing
300 * - "create", with any value (create if DB doesn't exist)
301 * - "exclusive", with any value (exclusive create)
302 * - "truncate", with any value (truncate the DB)
303 * - "read-only", with any value (disallow writes)
304 * - "sync", with any value (make transactions durable)
305 * - "journal-name", with a string value naming a journal file name
307 * @param dbtype Name of DB type
308 * @param dbname Name of DB (likely a file path)
309 * @param options Options dict
310 * @param db Output open DB handle
311 * @param error Output error object
313 * @return a DB handle
315 * @addtogroup heimbase
318 heim_db_create(const char *dbtype
, const char *dbname
,
319 heim_dict_t options
, heim_error_t
*error
)
327 if (options
== NULL
) {
328 options
= heim_dict_create(11);
329 if (options
== NULL
) {
331 *error
= heim_error_create_enomem();
335 (void) heim_retain(options
);
338 if (db_plugins
== NULL
) {
339 heim_release(options
);
343 if (dbtype
== NULL
|| *dbtype
== '\0') {
344 struct dbtype_iter iter_ctx
= { NULL
, dbname
, options
, error
};
346 /* Try all dbtypes */
347 heim_dict_iterate_f(db_plugins
, &iter_ctx
, dbtype_iter2create_f
);
348 heim_release(options
);
350 } else if (strstr(dbtype
, "json")) {
351 (void) heim_db_register(dbtype
, NULL
, &json_dbt
);
355 * Allow for dbtypes that are composed from pseudo-dbtypes chained
356 * to a real DB type with '+'. For example a pseudo-dbtype might
357 * add locking, transactions, transcoding of values, ...
359 p
= strchr(dbtype
, '+');
361 s
= heim_string_create_with_bytes(dbtype
, p
- dbtype
);
363 s
= heim_string_create(dbtype
);
365 heim_release(options
);
369 HEIMDAL_MUTEX_lock(&db_type_mutex
);
370 plug
= heim_dict_get_value(db_plugins
, s
);
371 HEIMDAL_MUTEX_unlock(&db_type_mutex
);
375 *error
= heim_error_create(ENOENT
,
376 N_("Heimdal DB plugin not found: %s", ""),
378 heim_release(options
);
382 db
= _heim_alloc_object(&db_object
, sizeof(*db
));
384 heim_release(options
);
388 db
->in_transaction
= 0;
393 db
->options
= options
;
395 ret
= plug
->openf(plug
->data
, dbtype
, dbname
, options
, &db
->db_data
, error
);
398 if (error
&& *error
== NULL
)
399 *error
= heim_error_create(ENOENT
,
400 N_("Heimdal DB could not be opened: %s", ""),
405 ret
= db_replay_log(db
, error
);
411 if (plug
->clonef
== NULL
) {
412 db
->dbtype
= heim_string_create(dbtype
);
413 db
->dbname
= heim_string_create(dbname
);
415 if (!db
->dbtype
|| ! db
->dbname
) {
418 *error
= heim_error_create_enomem();
427 * Clone (duplicate) an open DB handle.
429 * This is useful for multi-threaded applications. Applications must
430 * synchronize access to any given DB handle.
432 * Returns EBUSY if there is an open transaction for the input db.
434 * @param db Open DB handle
435 * @param error Output error object
437 * @return a DB handle
439 * @addtogroup heimbase
442 heim_db_clone(heim_db_t db
, heim_error_t
*error
)
447 if (heim_get_tid(db
) != HEIM_TID_DB
)
448 heim_abort("Expected a database");
449 if (db
->in_transaction
)
450 heim_abort("DB handle is busy");
452 if (db
->plug
->clonef
== NULL
) {
453 return heim_db_create(heim_string_get_utf8(db
->dbtype
),
454 heim_string_get_utf8(db
->dbname
),
458 result
= _heim_alloc_object(&db_object
, sizeof(*result
));
459 if (result
== NULL
) {
461 *error
= heim_error_create_enomem();
465 result
->set_keys
= NULL
;
466 result
->del_keys
= NULL
;
467 ret
= db
->plug
->clonef(db
->db_data
, &result
->db_data
, error
);
469 heim_release(result
);
470 if (error
&& !*error
)
471 *error
= heim_error_create(ENOENT
,
472 N_("Could not re-open DB while cloning", ""));
480 * Open a transaction on the given db.
482 * @param db Open DB handle
483 * @param error Output error object
485 * @return 0 on success, system error otherwise
487 * @addtogroup heimbase
490 heim_db_begin(heim_db_t db
, int read_only
, heim_error_t
*error
)
494 if (heim_get_tid(db
) != HEIM_TID_DB
)
497 if (db
->in_transaction
&& (read_only
|| !db
->ro_tx
|| (!read_only
&& !db
->ro_tx
)))
498 heim_abort("DB already in transaction");
500 if (db
->plug
->setf
== NULL
|| db
->plug
->delf
== NULL
)
503 if (db
->plug
->beginf
) {
504 ret
= db
->plug
->beginf(db
->db_data
, read_only
, error
);
507 } else if (!db
->in_transaction
) {
508 /* Try to emulate transactions */
510 if (db
->plug
->lockf
== NULL
)
511 return EINVAL
; /* can't lock? -> no transactions */
513 /* Assume unlock provides sync/durability */
514 ret
= db
->plug
->lockf(db
->db_data
, read_only
, error
);
518 ret
= db_replay_log(db
, error
);
520 ret
= db
->plug
->unlockf(db
->db_data
, error
);
524 db
->set_keys
= heim_dict_create(11);
525 if (db
->set_keys
== NULL
)
527 db
->del_keys
= heim_dict_create(11);
528 if (db
->del_keys
== NULL
) {
529 heim_release(db
->set_keys
);
534 heim_assert(read_only
== 0, "Internal error");
535 ret
= db
->plug
->lockf(db
->db_data
, 0, error
);
539 db
->in_transaction
= 1;
540 db
->ro_tx
= !!read_only
;
545 * Commit an open transaction on the given db.
547 * @param db Open DB handle
548 * @param error Output error object
550 * @return 0 on success, system error otherwise
552 * @addtogroup heimbase
555 heim_db_commit(heim_db_t db
, heim_error_t
*error
)
558 heim_string_t journal_fname
= NULL
;
560 if (heim_get_tid(db
) != HEIM_TID_DB
)
562 if (!db
->in_transaction
)
564 if (db
->plug
->commitf
== NULL
&& db
->plug
->lockf
== NULL
)
567 if (db
->plug
->commitf
!= NULL
) {
568 ret
= db
->plug
->commitf(db
->db_data
, error
);
570 (void) db
->plug
->rollbackf(db
->db_data
, error
);
572 db
->in_transaction
= 0;
583 journal_fname
= heim_dict_get_value(db
->options
, HSTR("journal-filename"));
585 if (journal_fname
!= NULL
) {
587 heim_string_t journal_contents
;
591 /* Create contents for replay log */
593 a
= heim_array_create();
596 ret
= heim_array_append_value(a
, db
->set_keys
);
601 ret
= heim_array_append_value(a
, db
->del_keys
);
606 journal_contents
= heim_json_copy_serialize(a
, 0, error
);
609 /* Write replay log */
610 if (journal_fname
!= NULL
) {
613 ret
= open_file(heim_string_get_utf8(journal_fname
), 1, 0, &fd
, error
);
615 heim_release(journal_contents
);
618 len
= strlen(heim_string_get_utf8(journal_contents
));
619 bytes
= write(fd
, heim_string_get_utf8(journal_contents
), len
);
621 heim_release(journal_contents
);
624 /* Truncate replay log */
625 (void) open_file(heim_string_get_utf8(journal_fname
), 1, 0, NULL
, error
);
634 /* Apply logged actions */
635 ret
= db_do_log_actions(db
, error
);
639 if (db
->plug
->syncf
!= NULL
) {
640 /* fsync() or whatever */
641 ret
= db
->plug
->syncf(db
->db_data
, error
);
646 /* Truncate replay log and we're done */
647 if (journal_fname
!= NULL
) {
650 ret2
= open_file(heim_string_get_utf8(journal_fname
), 1, 0, &fd
, error
);
656 * Clean up; if we failed to remore the replay log that's OK, we'll
657 * handle that again in heim_db_commit()
660 heim_release(db
->set_keys
);
661 heim_release(db
->del_keys
);
664 db
->in_transaction
= 0;
667 ret2
= db
->plug
->unlockf(db
->db_data
, error
);
674 return HEIM_ERROR(error
, ret
,
675 (ret
, N_("Error while committing transaction: %s", ""),
680 * Rollback an open transaction on the given db.
682 * @param db Open DB handle
683 * @param error Output error object
685 * @return 0 on success, system error otherwise
687 * @addtogroup heimbase
690 heim_db_rollback(heim_db_t db
, heim_error_t
*error
)
694 if (heim_get_tid(db
) != HEIM_TID_DB
)
696 if (!db
->in_transaction
)
699 if (db
->plug
->rollbackf
!= NULL
)
700 ret
= db
->plug
->rollbackf(db
->db_data
, error
);
701 else if (db
->plug
->unlockf
!= NULL
)
702 ret
= db
->plug
->unlockf(db
->db_data
, error
);
704 heim_release(db
->set_keys
);
705 heim_release(db
->del_keys
);
708 db
->in_transaction
= 0;
715 * Get type ID of heim_db_t objects.
717 * @addtogroup heimbase
720 heim_db_get_type_id(void)
726 _heim_db_get_value(heim_db_t db
, heim_string_t table
, heim_data_t key
,
729 heim_release(db
->to_release
);
730 db
->to_release
= heim_db_copy_value(db
, table
, key
, error
);
731 return db
->to_release
;
735 * Lookup a key's value in the DB.
737 * Returns 0 on success, -1 if the key does not exist in the DB, or a
738 * system error number on failure.
740 * @param db Open DB handle
742 * @param error Output error object
744 * @return the value (retained), if there is one for the given key
746 * @addtogroup heimbase
749 heim_db_copy_value(heim_db_t db
, heim_string_t table
, heim_data_t key
,
755 if (heim_get_tid(db
) != HEIM_TID_DB
)
764 if (db
->in_transaction
) {
767 key64
= to_base64(key
, error
);
770 *error
= heim_error_create_enomem();
774 v
= heim_path_copy(db
->set_keys
, error
, table
, key64
, NULL
);
779 v
= heim_path_copy(db
->del_keys
, error
, table
, key64
, NULL
); /* can't be NULL */
785 result
= db
->plug
->copyf(db
->db_data
, table
, key
, error
);
791 * Set a key's value in the DB.
793 * @param db Open DB handle
795 * @param value Value (if NULL the key will be deleted, but empty is OK)
796 * @param error Output error object
798 * @return 0 on success, system error otherwise
800 * @addtogroup heimbase
803 heim_db_set_value(heim_db_t db
, heim_string_t table
,
804 heim_data_t key
, heim_data_t value
, heim_error_t
*error
)
806 heim_string_t key64
= NULL
;
816 /* Use heim_null_t instead of NULL */
817 return heim_db_delete_key(db
, table
, key
, error
);
819 if (heim_get_tid(db
) != HEIM_TID_DB
)
822 if (heim_get_tid(key
) != HEIM_TID_DATA
)
823 return HEIM_ERROR(error
, EINVAL
,
824 (EINVAL
, N_("DB keys must be data", "")));
826 if (db
->plug
->setf
== NULL
)
829 if (!db
->in_transaction
) {
830 ret
= heim_db_begin(db
, 0, error
);
833 heim_assert(db
->in_transaction
, "Internal error");
834 ret
= heim_db_set_value(db
, table
, key
, value
, error
);
836 (void) heim_db_rollback(db
, NULL
);
839 return heim_db_commit(db
, error
);
842 /* Transaction emulation */
843 heim_assert(db
->set_keys
!= NULL
, "Internal error");
844 key64
= to_base64(key
, error
);
846 return HEIM_ENOMEM(error
);
849 ret
= heim_db_begin(db
, 0, error
);
853 ret
= heim_path_create(db
->set_keys
, 29, value
, error
, table
, key64
, NULL
);
856 heim_path_delete(db
->del_keys
, error
, table
, key64
, NULL
);
863 return HEIM_ERROR(error
, ret
,
864 (ret
, N_("Could not set a dict value while while "
865 "setting a DB value", "")));
869 * Delete a key and its value from the DB
872 * @param db Open DB handle
874 * @param error Output error object
876 * @return 0 on success, system error otherwise
878 * @addtogroup heimbase
881 heim_db_delete_key(heim_db_t db
, heim_string_t table
, heim_data_t key
,
884 heim_string_t key64
= NULL
;
893 if (heim_get_tid(db
) != HEIM_TID_DB
)
896 if (db
->plug
->delf
== NULL
)
899 if (!db
->in_transaction
) {
900 ret
= heim_db_begin(db
, 0, error
);
903 heim_assert(db
->in_transaction
, "Internal error");
904 ret
= heim_db_delete_key(db
, table
, key
, error
);
906 (void) heim_db_rollback(db
, NULL
);
909 return heim_db_commit(db
, error
);
912 /* Transaction emulation */
913 heim_assert(db
->set_keys
!= NULL
, "Internal error");
914 key64
= to_base64(key
, error
);
916 return HEIM_ENOMEM(error
);
918 ret
= heim_db_begin(db
, 0, error
);
922 ret
= heim_path_create(db
->del_keys
, 29, heim_number_create(1), error
, table
, key64
, NULL
);
925 heim_path_delete(db
->set_keys
, error
, table
, key64
, NULL
);
932 return HEIM_ERROR(error
, ret
,
933 (ret
, N_("Could not set a dict value while while "
934 "deleting a DB value", "")));
938 * Iterate a callback function over keys and values from a DB.
940 * @param db Open DB handle
941 * @param iter_data Callback function's private data
942 * @param iter_f Callback function, called once per-key/value pair
943 * @param error Output error object
945 * @addtogroup heimbase
948 heim_db_iterate_f(heim_db_t db
, heim_string_t table
, void *iter_data
,
949 heim_db_iterator_f_t iter_f
, heim_error_t
*error
)
954 if (heim_get_tid(db
) != HEIM_TID_DB
)
957 if (!db
->in_transaction
)
958 db
->plug
->iterf(db
->db_data
, table
, iter_data
, iter_f
, error
);
962 db_replay_log_table_set_keys_iter(heim_object_t key
, heim_object_t value
,
971 k
= from_base64((heim_string_t
)key
, &db
->error
);
976 v
= (heim_data_t
)value
;
978 db
->ret
= db
->plug
->setf(db
->db_data
, db
->current_table
, k
, v
, &db
->error
);
983 db_replay_log_table_del_keys_iter(heim_object_t key
, heim_object_t value
,
994 k
= from_base64((heim_string_t
)key
, &db
->error
);
998 db
->ret
= db
->plug
->delf(db
->db_data
, db
->current_table
, k
, &db
->error
);
1003 db_replay_log_set_keys_iter(heim_object_t table
, heim_object_t table_dict
,
1011 db
->current_table
= table
;
1012 heim_dict_iterate_f(table_dict
, db
, db_replay_log_table_set_keys_iter
);
1016 db_replay_log_del_keys_iter(heim_object_t table
, heim_object_t table_dict
,
1024 db
->current_table
= table
;
1025 heim_dict_iterate_f(table_dict
, db
, db_replay_log_table_del_keys_iter
);
1029 db_do_log_actions(heim_db_t db
, heim_error_t
*error
)
1038 if (db
->set_keys
!= NULL
)
1039 heim_dict_iterate_f(db
->set_keys
, db
, db_replay_log_set_keys_iter
);
1040 if (db
->del_keys
!= NULL
)
1041 heim_dict_iterate_f(db
->del_keys
, db
, db_replay_log_del_keys_iter
);
1045 if (error
&& db
->error
) {
1049 heim_release(db
->error
);
1056 db_replay_log(heim_db_t db
, heim_error_t
*error
)
1059 heim_string_t journal_fname
= NULL
;
1060 heim_object_t journal
;
1063 heim_assert(!db
->in_transaction
, "DB transaction not open");
1064 heim_assert(db
->set_keys
== NULL
&& db
->set_keys
== NULL
, "DB transaction not open");
1069 if (db
->options
== NULL
)
1072 journal_fname
= heim_dict_get_value(db
->options
, HSTR("journal-filename"));
1073 if (journal_fname
== NULL
)
1076 ret
= read_json(heim_string_get_utf8(journal_fname
), &journal
, error
);
1077 if (ret
== ENOENT
) {
1078 heim_release(journal_fname
);
1081 if (ret
== 0 && journal
== NULL
) {
1082 heim_release(journal_fname
);
1086 heim_release(journal_fname
);
1090 if (heim_get_tid(journal
) != HEIM_TID_ARRAY
) {
1091 heim_release(journal_fname
);
1092 return HEIM_ERROR(error
, EINVAL
,
1093 (ret
, N_("Invalid journal contents; delete journal",
1097 len
= heim_array_get_length(journal
);
1100 db
->set_keys
= heim_array_get_value(journal
, 0);
1102 db
->del_keys
= heim_array_get_value(journal
, 1);
1103 ret
= db_do_log_actions(db
, error
);
1105 heim_release(journal_fname
);
1109 /* Truncate replay log and we're done */
1110 ret
= open_file(heim_string_get_utf8(journal_fname
), 1, 0, NULL
, error
);
1111 heim_release(journal_fname
);
1114 heim_release(db
->set_keys
);
1115 heim_release(db
->del_keys
);
1116 db
->set_keys
= NULL
;
1117 db
->del_keys
= NULL
;
1123 heim_string_t
to_base64(heim_data_t data
, heim_error_t
*error
)
1126 heim_string_t s
= NULL
;
1127 const heim_octet_string
*d
;
1130 d
= heim_data_get_data(data
);
1131 ret
= rk_base64_encode(d
->data
, d
->length
, &b64
);
1132 if (ret
< 0 || b64
== NULL
)
1134 s
= heim_string_ref_create(b64
, free
);
1142 *error
= heim_error_create_enomem();
1147 heim_data_t
from_base64(heim_string_t s
, heim_error_t
*error
)
1153 buf
= malloc(strlen(heim_string_get_utf8(s
)));
1155 len
= rk_base64_decode(heim_string_get_utf8(s
), buf
);
1156 if (len
> -1 && (d
= heim_data_ref_create(buf
, len
, free
)))
1160 *error
= heim_error_create_enomem();
1166 open_file(const char *dbname
, int for_write
, int excl
, int *fd_out
, heim_error_t
*error
)
1176 hFile
= CreateFile(dbname
, GENERIC_WRITE
| GENERIC_READ
, 0,
1177 NULL
, /* we'll close as soon as we read */
1178 CREATE_ALWAYS
, FILE_ATTRIBUTE_NORMAL
, NULL
);
1180 hFile
= CreateFile(dbname
, GENERIC_READ
, FILE_SHARE_READ
,
1181 NULL
, /* we'll close as soon as we read */
1182 OPEN_EXISTING
, FILE_ATTRIBUTE_NORMAL
, NULL
);
1183 if (hFile
== INVALID_HANDLE_VALUE
) {
1184 ret
= GetLastError();
1185 _set_errno(ret
); /* CreateFile() does not set errno */
1188 if (fd_out
== NULL
) {
1189 (void) CloseHandle(hFile
);
1193 *fd_out
= _open_osfhandle((intptr_t) hFile
, 0);
1196 (void) CloseHandle(hFile
);
1200 /* No need to lock given share deny mode */
1204 if (error
!= NULL
) {
1206 FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM
| FORMAT_MESSAGE_ALLOCATE_BUFFER
,
1207 0, ret
, 0, (LPTSTR
) &s
, 0, NULL
);
1208 *error
= heim_error_create(ret
, N_("Could not open JSON file %s: %s", ""),
1209 dbname
, s
? s
: "<error formatting error>");
1220 if (for_write
&& excl
)
1221 fd
= open(dbname
, O_CREAT
| O_EXCL
| O_WRONLY
, 0600);
1223 fd
= open(dbname
, O_CREAT
| O_TRUNC
| O_WRONLY
, 0600);
1225 fd
= open(dbname
, O_RDONLY
);
1228 *error
= heim_error_create(ret
, N_("Could not open JSON file %s: %s", ""),
1229 dbname
, strerror(errno
));
1233 if (fd_out
== NULL
) {
1238 ret
= flock(fd
, for_write
? LOCK_EX
: LOCK_SH
);
1240 /* Note that we if O_EXCL we're leaving the [lock] file around */
1242 return HEIM_ERROR(error
, errno
,
1243 (errno
, N_("Could not lock JSON file %s: %s", ""),
1244 dbname
, strerror(errno
)));
1254 read_json(const char *dbname
, heim_object_t
*out
, heim_error_t
*error
)
1263 ret
= open_file(dbname
, 0, 0, &fd
, error
);
1267 ret
= fstat(fd
, &st
);
1270 return HEIM_ERROR(error
, errno
,
1271 (ret
, N_("Could not stat JSON DB %s: %s", ""),
1272 dbname
, strerror(errno
)));
1275 if (st
.st_size
== 0) {
1280 str
= malloc(st
.st_size
+ 1);
1283 return HEIM_ENOMEM(error
);
1286 bytes
= read(fd
, str
, st
.st_size
);
1288 if (bytes
!= st
.st_size
) {
1291 errno
= EINVAL
; /* ?? */
1292 return HEIM_ERROR(error
, errno
,
1293 (ret
, N_("Could not read JSON DB %s: %s", ""),
1294 dbname
, strerror(errno
)));
1296 str
[st
.st_size
] = '\0';
1297 *out
= heim_json_create(str
, 10, 0, error
);
1300 return (error
&& *error
) ? heim_error_get_code(*error
) : EINVAL
;
1304 typedef struct json_db
{
1306 heim_string_t dbname
;
1307 heim_string_t bkpname
;
1309 time_t last_read_time
;
1310 unsigned int read_only
:1;
1311 unsigned int locked
:1;
1312 unsigned int locked_needs_unlink
:1;
1316 json_db_open(void *plug
, const char *dbtype
, const char *dbname
,
1317 heim_dict_t options
, void **db
, heim_error_t
*error
)
1320 heim_dict_t contents
= NULL
;
1321 heim_string_t dbname_s
= NULL
;
1322 heim_string_t bkpname_s
= NULL
;
1326 if (dbtype
&& *dbtype
&& strcmp(dbtype
, "json") != 0)
1327 return HEIM_ERROR(error
, EINVAL
, (EINVAL
, N_("Wrong DB type", "")));
1328 if (dbname
&& *dbname
&& strcmp(dbname
, "MEMORY") != 0) {
1329 char *ext
= strrchr(dbname
, '.');
1334 if (ext
== NULL
|| strcmp(ext
, ".json") != 0)
1335 return HEIM_ERROR(error
, EINVAL
,
1336 (EINVAL
, N_("JSON DB files must end in .json",
1340 heim_object_t vc
, ve
, vt
;
1342 vc
= heim_dict_get_value(options
, HSTR("create"));
1343 ve
= heim_dict_get_value(options
, HSTR("exclusive"));
1344 vt
= heim_dict_get_value(options
, HSTR("truncate"));
1346 ret
= open_file(dbname
, 1, ve
? 1 : 0, NULL
, error
);
1349 } else if (vc
|| ve
|| vt
) {
1350 return HEIM_ERROR(error
, EINVAL
,
1351 (EINVAL
, N_("Invalid JSON DB open options",
1355 * We don't want cloned handles to truncate the DB, eh?
1357 * We should really just create a copy of the options dict
1358 * rather than modify the caller's! But for that it'd be
1359 * nicer to have copy utilities in heimbase, something like
1362 * heim_object_t heim_copy(heim_object_t src, int depth,
1363 * heim_error_t *error);
1365 * so that options = heim_copy(options, 1); means copy the
1366 * dict but nothing else (whereas depth == 0 would mean
1367 * heim_retain(), and depth > 1 would be copy that many
1370 heim_dict_delete_key(options
, HSTR("create"));
1371 heim_dict_delete_key(options
, HSTR("exclusive"));
1372 heim_dict_delete_key(options
, HSTR("truncate"));
1374 dbname_s
= heim_string_create(dbname
);
1375 if (dbname_s
== NULL
)
1376 return HEIM_ENOMEM(error
);
1378 len
= snprintf(NULL
, 0, "%s~", dbname
);
1379 bkpname
= malloc(len
+ 2);
1380 if (bkpname
== NULL
) {
1381 heim_release(dbname_s
);
1382 return HEIM_ENOMEM(error
);
1384 (void) snprintf(bkpname
, len
+ 1, "%s~", dbname
);
1385 bkpname_s
= heim_string_create(bkpname
);
1387 if (bkpname_s
== NULL
) {
1388 heim_release(dbname_s
);
1389 return HEIM_ENOMEM(error
);
1392 ret
= read_json(dbname
, (heim_object_t
*)&contents
, error
);
1394 heim_release(bkpname_s
);
1395 heim_release(dbname_s
);
1399 if (contents
!= NULL
&& heim_get_tid(contents
) != HEIM_TID_DICT
) {
1400 heim_release(bkpname_s
);
1401 heim_release(dbname_s
);
1402 return HEIM_ERROR(error
, EINVAL
,
1403 (EINVAL
, N_("JSON DB contents not valid JSON",
1408 jsondb
= heim_alloc(sizeof (*jsondb
), "json_db", NULL
);
1409 if (jsondb
== NULL
) {
1410 heim_release(contents
);
1411 heim_release(dbname_s
);
1412 heim_release(bkpname_s
);
1416 jsondb
->last_read_time
= time(NULL
);
1418 jsondb
->dbname
= dbname_s
;
1419 jsondb
->bkpname
= bkpname_s
;
1420 jsondb
->read_only
= 0;
1422 if (contents
!= NULL
)
1423 jsondb
->dict
= contents
;
1425 jsondb
->dict
= heim_dict_create(29);
1426 if (jsondb
->dict
== NULL
) {
1427 heim_release(jsondb
);
1437 json_db_close(void *db
, heim_error_t
*error
)
1439 json_db_t jsondb
= db
;
1443 if (jsondb
->fd
> -1)
1444 (void) close(jsondb
->fd
);
1446 heim_release(jsondb
->dbname
);
1447 heim_release(jsondb
->bkpname
);
1448 heim_release(jsondb
->dict
);
1449 heim_release(jsondb
);
1454 json_db_lock(void *db
, int read_only
, heim_error_t
*error
)
1456 json_db_t jsondb
= db
;
1459 heim_assert(jsondb
->fd
== -1 || (jsondb
->read_only
&& !read_only
),
1460 "DB locks are not recursive");
1462 jsondb
->read_only
= read_only
? 1 : 0;
1463 if (jsondb
->fd
> -1)
1466 ret
= open_file(heim_string_get_utf8(jsondb
->bkpname
), 1, 1, &jsondb
->fd
, error
);
1468 jsondb
->locked_needs_unlink
= 1;
1475 json_db_unlock(void *db
, heim_error_t
*error
)
1477 json_db_t jsondb
= db
;
1480 heim_assert(jsondb
->locked
, "DB not locked when unlock attempted");
1481 if (jsondb
->fd
> -1)
1482 ret
= close(jsondb
->fd
);
1484 jsondb
->read_only
= 0;
1486 if (jsondb
->locked_needs_unlink
)
1487 unlink(heim_string_get_utf8(jsondb
->bkpname
));
1488 jsondb
->locked_needs_unlink
= 0;
1493 json_db_sync(void *db
, heim_error_t
*error
)
1495 json_db_t jsondb
= db
;
1499 const char *json_text
= NULL
;
1506 heim_assert(jsondb
->fd
> -1, "DB not locked when sync attempted");
1508 json
= heim_json_copy_serialize(jsondb
->dict
, 0, &e
);
1510 ret
= heim_error_get_code(e
);
1518 json_text
= heim_string_get_utf8(json
);
1519 len
= strlen(json_text
);
1524 ret
= open_file(heim_string_get_utf8(jsondb
->dbname
), 1, 0, &fd
, error
);
1537 bytes
= write(fd
, json_text
, len
);
1540 return errno
? errno
: EIO
;
1548 return GetLastError();
1550 ret
= rename(heim_string_get_utf8(jsondb
->bkpname
), heim_string_get_utf8(jsondb
->dbname
));
1552 jsondb
->locked_needs_unlink
= 0;
1561 json_db_copy_value(void *db
, heim_string_t table
, heim_data_t key
,
1562 heim_error_t
*error
)
1564 json_db_t jsondb
= db
;
1565 heim_string_t key_string
;
1566 const heim_octet_string
*key_data
= heim_data_get_data(key
);
1573 if (strnlen(key_data
->data
, key_data
->length
) != key_data
->length
) {
1574 HEIM_ERROR(error
, EINVAL
,
1575 (EINVAL
, N_("JSON DB requires keys that are actually "
1580 if (stat(heim_string_get_utf8(jsondb
->dbname
), &st
) == -1) {
1581 HEIM_ERROR(error
, errno
,
1582 (errno
, N_("Could not stat JSON DB file", "")));
1586 if (st
.st_mtime
> jsondb
->last_read_time
||
1587 st
.st_ctime
> jsondb
->last_read_time
) {
1588 heim_dict_t contents
= NULL
;
1591 /* Ignore file is gone (ENOENT) */
1592 ret
= read_json(heim_string_get_utf8(jsondb
->dbname
),
1593 (heim_object_t
*)&contents
, error
);
1596 if (contents
== NULL
)
1597 contents
= heim_dict_create(29);
1598 heim_release(jsondb
->dict
);
1599 jsondb
->dict
= contents
;
1600 jsondb
->last_read_time
= time(NULL
);
1603 key_string
= heim_string_create_with_bytes(key_data
->data
,
1605 if (key_string
== NULL
) {
1606 (void) HEIM_ENOMEM(error
);
1610 result
= heim_path_copy(jsondb
->dict
, error
, table
, key_string
, NULL
);
1611 heim_release(key_string
);
1616 json_db_set_value(void *db
, heim_string_t table
,
1617 heim_data_t key
, heim_data_t value
, heim_error_t
*error
)
1619 json_db_t jsondb
= db
;
1620 heim_string_t key_string
;
1621 const heim_octet_string
*key_data
= heim_data_get_data(key
);
1627 if (strnlen(key_data
->data
, key_data
->length
) != key_data
->length
)
1628 return HEIM_ERROR(error
, EINVAL
,
1630 N_("JSON DB requires keys that are actually strings",
1633 key_string
= heim_string_create_with_bytes(key_data
->data
,
1635 if (key_string
== NULL
)
1636 return HEIM_ENOMEM(error
);
1641 ret
= heim_path_create(jsondb
->dict
, 29, value
, error
, table
, key_string
, NULL
);
1642 heim_release(key_string
);
1647 json_db_del_key(void *db
, heim_string_t table
, heim_data_t key
,
1648 heim_error_t
*error
)
1650 json_db_t jsondb
= db
;
1651 heim_string_t key_string
;
1652 const heim_octet_string
*key_data
= heim_data_get_data(key
);
1657 if (strnlen(key_data
->data
, key_data
->length
) != key_data
->length
)
1658 return HEIM_ERROR(error
, EINVAL
,
1660 N_("JSON DB requires keys that are actually strings",
1663 key_string
= heim_string_create_with_bytes(key_data
->data
,
1665 if (key_string
== NULL
)
1666 return HEIM_ENOMEM(error
);
1671 heim_path_delete(jsondb
->dict
, error
, table
, key_string
, NULL
);
1672 heim_release(key_string
);
1676 struct json_db_iter_ctx
{
1677 heim_db_iterator_f_t iter_f
;
1681 static void json_db_iter_f(heim_object_t key
, heim_object_t value
, void *arg
)
1683 struct json_db_iter_ctx
*ctx
= arg
;
1684 const char *key_string
;
1685 heim_data_t key_data
;
1687 key_string
= heim_string_get_utf8((heim_string_t
)key
);
1688 key_data
= heim_data_ref_create(key_string
, strlen(key_string
), NULL
);
1689 ctx
->iter_f(key_data
, (heim_object_t
)value
, ctx
->iter_ctx
);
1690 heim_release(key_data
);
1694 json_db_iter(void *db
, heim_string_t table
, void *iter_data
,
1695 heim_db_iterator_f_t iter_f
, heim_error_t
*error
)
1697 json_db_t jsondb
= db
;
1698 struct json_db_iter_ctx ctx
;
1699 heim_dict_t table_dict
;
1707 table_dict
= heim_dict_get_value(jsondb
->dict
, table
);
1708 if (table_dict
== NULL
)
1711 ctx
.iter_ctx
= iter_data
;
1712 ctx
.iter_f
= iter_f
;
1714 heim_dict_iterate_f(table_dict
, &ctx
, json_db_iter_f
);
1717 static struct heim_db_type json_dbt
= {
1718 1, json_db_open
, NULL
, json_db_close
,
1719 json_db_lock
, json_db_unlock
, json_db_sync
,
1721 json_db_copy_value
, json_db_set_value
,
1722 json_db_del_key
, json_db_iter