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).
54 #include <sys/types.h>
69 #define HEIM_ENOMEM(ep) \
71 heim_error_get_code((*(ep) = heim_error_create_enomem())) : ENOMEM)
73 #define HEIM_ERROR_HELPER(ep, ec, args) \
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
= {
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
{
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
;
121 } db_plugin_desc
, *db_plugin
;
123 struct heim_db_data
{
125 heim_string_t dbtype
;
126 heim_string_t dbname
;
129 heim_data_t to_release
;
132 unsigned int in_transaction
: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
;
141 db_do_log_actions(heim_db_t db
, heim_error_t
*error
);
143 db_replay_log(heim_db_t db
, heim_error_t
*error
);
145 static HEIMDAL_MUTEX db_type_mutex
= HEIMDAL_MUTEX_INITIALIZER
;
148 db_init_plugins_once(void *arg
)
150 db_plugins
= heim_retain(arg
);
154 plugin_dealloc(void *arg
)
156 db_plugin plug
= arg
;
158 heim_release(plug
->name
);
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
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
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
,
187 struct heim_db_type
*plugin
)
191 db_plugin plug
, plug2
;
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");
201 plugins
= heim_dict_create(11);
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
);
212 plug
= heim_alloc(sizeof (*plug
), "db_plug", plugin_dealloc
);
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
;
234 HEIMDAL_MUTEX_lock(&db_type_mutex
);
235 plug2
= heim_dict_get_value(db_plugins
, s
);
237 ret
= heim_dict_set_value(db_plugins
, s
, plug
);
238 HEIMDAL_MUTEX_unlock(&db_type_mutex
);
246 db_dealloc(void *arg
)
249 heim_assert(!db
->in_transaction
,
250 "rollback or commit heim_db_t before releasing it");
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
);
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.
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
)
282 iter_ctx
->db
= heim_db_create(heim_string_get_utf8(dbtype
),
283 iter_ctx
->dbname
, iter_ctx
->options
,
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
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
316 heim_db_create(const char *dbtype
, const char *dbname
,
317 heim_dict_t options
, heim_error_t
*error
)
325 if (options
== NULL
) {
326 options
= heim_dict_create(11);
327 if (options
== NULL
) {
329 *error
= heim_error_create_enomem();
333 (void) heim_retain(options
);
336 if (db_plugins
== NULL
) {
337 heim_release(options
);
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
);
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
, '+');
359 s
= heim_string_create_with_bytes(dbtype
, p
- dbtype
);
361 s
= heim_string_create(dbtype
);
363 heim_release(options
);
367 HEIMDAL_MUTEX_lock(&db_type_mutex
);
368 plug
= heim_dict_get_value(db_plugins
, s
);
369 HEIMDAL_MUTEX_unlock(&db_type_mutex
);
373 *error
= heim_error_create(ENOENT
,
374 N_("Heimdal DB plugin not found: %s", ""),
376 heim_release(options
);
380 db
= _heim_alloc_object(&db_object
, sizeof(*db
));
382 heim_release(options
);
386 db
->in_transaction
= 0;
391 db
->options
= options
;
393 ret
= plug
->openf(plug
->data
, dbtype
, dbname
, options
, &db
->db_data
, error
);
396 if (error
&& *error
== NULL
)
397 *error
= heim_error_create(ENOENT
,
398 N_("Heimdal DB could not be opened: %s", ""),
403 ret
= db_replay_log(db
, error
);
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
) {
416 *error
= heim_error_create_enomem();
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
440 heim_db_clone(heim_db_t db
, heim_error_t
*error
)
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
),
456 result
= _heim_alloc_object(&db_object
, sizeof(*result
));
457 if (result
== NULL
) {
459 *error
= heim_error_create_enomem();
463 result
->set_keys
= NULL
;
464 result
->del_keys
= NULL
;
465 ret
= db
->plug
->clonef(db
->db_data
, &result
->db_data
, error
);
467 heim_release(result
);
468 if (error
&& !*error
)
469 *error
= heim_error_create(ENOENT
,
470 N_("Could not re-open DB while cloning", ""));
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
)
492 if (heim_get_tid(db
) != HEIM_TID_DB
)
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
)
501 if (db
->plug
->beginf
) {
502 ret
= db
->plug
->beginf(db
->db_data
, read_only
, error
);
503 } else if (!db
->in_transaction
) {
504 /* Try to emulate transactions */
506 if (db
->plug
->lockf
== NULL
)
507 return EINVAL
; /* can't lock? -> no transactions */
509 /* Assume unlock provides sync/durability */
510 ret
= db
->plug
->lockf(db
->db_data
, read_only
, error
);
514 ret
= db_replay_log(db
, error
);
516 ret
= db
->plug
->unlockf(db
->db_data
, error
);
520 db
->set_keys
= heim_dict_create(11);
521 if (db
->set_keys
== NULL
)
523 db
->del_keys
= heim_dict_create(11);
524 if (db
->del_keys
== NULL
) {
525 heim_release(db
->set_keys
);
530 heim_assert(read_only
== 0, "Internal error");
531 ret
= db
->plug
->lockf(db
->db_data
, 0, error
);
535 db
->in_transaction
= 1;
536 db
->ro_tx
= !!read_only
;
541 * Commit an open transaction on the given db.
543 * @param db Open DB handle
544 * @param error Output error object
546 * @return 0 on success, system error otherwise
548 * @addtogroup heimbase
551 heim_db_commit(heim_db_t db
, heim_error_t
*error
)
554 heim_string_t journal_fname
= NULL
;
556 if (heim_get_tid(db
) != HEIM_TID_DB
)
558 if (!db
->in_transaction
)
560 if (db
->plug
->commitf
== NULL
&& db
->plug
->lockf
== NULL
)
563 if (db
->plug
->commitf
!= NULL
) {
564 ret
= db
->plug
->commitf(db
->db_data
, error
);
566 (void) db
->plug
->rollbackf(db
->db_data
, error
);
568 db
->in_transaction
= 0;
578 if (db
->options
== NULL
)
579 journal_fname
= heim_dict_get_value(db
->options
, HSTR("journal-filename"));
581 if (journal_fname
!= NULL
) {
583 heim_string_t journal_contents
;
587 /* Create contents for replay log */
589 a
= heim_array_create();
592 ret
= heim_array_append_value(a
, db
->set_keys
);
597 ret
= heim_array_append_value(a
, db
->del_keys
);
602 journal_contents
= heim_copy_serialize(a
, 0, error
);
605 /* Write replay log */
606 if (journal_fname
!= NULL
) {
609 ret
= open_file(heim_string_get_utf8(journal_fname
), 1, 0, &fd
, error
);
611 heim_release(journal_contents
);
614 len
= strlen(heim_string_get_utf8(journal_contents
));
615 bytes
= write(fd
, heim_string_get_utf8(journal_contents
), len
);
617 heim_release(journal_contents
);
620 /* Truncate replay log */
621 (void) open_file(heim_string_get_utf8(journal_fname
), 1, 0, NULL
, error
);
630 /* Apply logged actions */
631 ret
= db_do_log_actions(db
, error
);
635 if (db
->plug
->syncf
!= NULL
) {
636 /* fsync() or whatever */
637 ret
= db
->plug
->syncf(db
->db_data
, error
);
642 /* Truncate replay log and we're done */
643 if (journal_fname
!= NULL
) {
646 ret2
= open_file(heim_string_get_utf8(journal_fname
), 1, 0, &fd
, error
);
652 * Clean up; if we failed to remore the replay log that's OK, we'll
653 * handle that again in heim_db_commit()
656 heim_release(db
->set_keys
);
657 heim_release(db
->del_keys
);
660 db
->in_transaction
= 0;
663 ret2
= db
->plug
->unlockf(db
->db_data
, error
);
670 return HEIM_ERROR(error
, ret
,
671 (ret
, N_("Error while committing transaction: %s", ""),
676 * Rollback an open transaction on the given db.
678 * @param db Open DB handle
679 * @param error Output error object
681 * @return 0 on success, system error otherwise
683 * @addtogroup heimbase
686 heim_db_rollback(heim_db_t db
, heim_error_t
*error
)
690 if (heim_get_tid(db
) != HEIM_TID_DB
)
692 if (!db
->in_transaction
)
695 if (db
->plug
->rollbackf
!= NULL
)
696 ret
= db
->plug
->rollbackf(db
->db_data
, error
);
697 else if (db
->plug
->unlockf
!= NULL
)
698 ret
= db
->plug
->unlockf(db
->db_data
, error
);
700 heim_release(db
->set_keys
);
701 heim_release(db
->del_keys
);
704 db
->in_transaction
= 0;
711 * Get type ID of heim_db_t objects.
713 * @addtogroup heimbase
716 heim_db_get_type_id(void)
722 _heim_db_get_value(heim_db_t db
, heim_string_t table
, heim_data_t key
,
725 heim_release(db
->to_release
);
726 db
->to_release
= heim_db_copy_value(db
, table
, key
, error
);
727 return db
->to_release
;
731 * Lookup a key's value in the DB.
733 * Returns 0 on success, -1 if the key does not exist in the DB, or a
734 * system error number on failure.
736 * @param db Open DB handle
738 * @param error Output error object
740 * @return the value (retained), if there is one for the given key
742 * @addtogroup heimbase
745 heim_db_copy_value(heim_db_t db
, heim_string_t table
, heim_data_t key
,
751 if (heim_get_tid(db
) != HEIM_TID_DB
)
760 if (db
->in_transaction
) {
763 key64
= to_base64(key
, error
);
766 *error
= heim_error_create_enomem();
770 v
= heim_path_copy(db
->set_keys
, error
, table
, key64
, NULL
);
775 v
= heim_path_copy(db
->del_keys
, error
, table
, key64
, NULL
); /* can't be NULL */
781 result
= db
->plug
->copyf(db
->db_data
, table
, key
, error
);
787 * Set a key's value in the DB.
789 * @param db Open DB handle
791 * @param value Value (if NULL the key will be deleted, but empty is OK)
792 * @param error Output error object
794 * @return 0 on success, system error otherwise
796 * @addtogroup heimbase
799 heim_db_set_value(heim_db_t db
, heim_string_t table
,
800 heim_data_t key
, heim_data_t value
, heim_error_t
*error
)
802 heim_string_t key64
= NULL
;
812 /* Use heim_null_t instead of NULL */
813 return heim_db_delete_key(db
, table
, key
, error
);
815 if (heim_get_tid(db
) != HEIM_TID_DB
)
818 if (heim_get_tid(key
) != HEIM_TID_DATA
)
819 return HEIM_ERROR(error
, EINVAL
,
820 (EINVAL
, N_("DB keys must be data", "")));
822 if (db
->plug
->setf
== NULL
)
825 if (!db
->in_transaction
) {
826 ret
= heim_db_begin(db
, 0, error
);
829 heim_assert(db
->in_transaction
, "Internal error");
830 ret
= heim_db_set_value(db
, table
, key
, value
, error
);
832 (void) heim_db_rollback(db
, NULL
);
835 return heim_db_commit(db
, error
);
838 /* Transaction emulation */
839 heim_assert(db
->set_keys
!= NULL
, "Internal error");
840 key64
= to_base64(key
, error
);
842 return HEIM_ENOMEM(error
);
845 ret
= heim_db_begin(db
, 0, error
);
849 ret
= heim_path_create(db
->set_keys
, 29, value
, error
, table
, key64
, NULL
);
852 heim_path_delete(db
->del_keys
, error
, table
, key64
, NULL
);
859 return HEIM_ERROR(error
, ret
,
860 (ret
, N_("Could not set a dict value while while "
861 "setting a DB value", "")));
865 * Delete a key and its value from the DB
868 * @param db Open DB handle
870 * @param error Output error object
872 * @return 0 on success, system error otherwise
874 * @addtogroup heimbase
877 heim_db_delete_key(heim_db_t db
, heim_string_t table
, heim_data_t key
,
880 heim_string_t key64
= NULL
;
889 if (heim_get_tid(db
) != HEIM_TID_DB
)
892 if (db
->plug
->delf
== NULL
)
895 if (!db
->in_transaction
) {
896 ret
= heim_db_begin(db
, 0, error
);
899 heim_assert(db
->in_transaction
, "Internal error");
900 ret
= heim_db_delete_key(db
, table
, key
, error
);
902 (void) heim_db_rollback(db
, NULL
);
905 return heim_db_commit(db
, error
);
908 /* Transaction emulation */
909 heim_assert(db
->set_keys
!= NULL
, "Internal error");
910 key64
= to_base64(key
, error
);
912 return HEIM_ENOMEM(error
);
914 ret
= heim_db_begin(db
, 0, error
);
918 ret
= heim_path_create(db
->del_keys
, 29, heim_number_create(1), error
, table
, key64
, NULL
);
921 heim_path_delete(db
->set_keys
, error
, table
, key64
, NULL
);
928 return HEIM_ERROR(error
, ret
,
929 (ret
, N_("Could not set a dict value while while "
930 "deleting a DB value", "")));
934 * Iterate a callback function over keys and values from a DB.
936 * @param db Open DB handle
937 * @param iter_data Callback function's private data
938 * @param iter_f Callback function, called once per-key/value pair
939 * @param error Output error object
941 * @addtogroup heimbase
944 heim_db_iterate_f(heim_db_t db
, heim_string_t table
, void *iter_data
,
945 heim_db_iterator_f_t iter_f
, heim_error_t
*error
)
950 if (heim_get_tid(db
) != HEIM_TID_DB
)
953 if (!db
->in_transaction
)
954 db
->plug
->iterf(db
->db_data
, table
, iter_data
, iter_f
, error
);
958 db_replay_log_table_set_keys_iter(heim_object_t key
, heim_object_t value
,
967 k
= from_base64((heim_string_t
)key
, &db
->error
);
972 v
= (heim_data_t
)value
;
974 db
->ret
= db
->plug
->setf(db
->db_data
, db
->current_table
, k
, v
, &db
->error
);
979 db_replay_log_table_del_keys_iter(heim_object_t key
, heim_object_t value
,
990 k
= from_base64((heim_string_t
)key
, &db
->error
);
994 k
= (heim_data_t
)key
;
996 db
->ret
= db
->plug
->delf(db
->db_data
, db
->current_table
, k
, &db
->error
);
1001 db_replay_log_set_keys_iter(heim_object_t table
, heim_object_t table_dict
,
1009 db
->current_table
= table
;
1010 heim_dict_iterate_f(table_dict
, db
, db_replay_log_table_set_keys_iter
);
1014 db_replay_log_del_keys_iter(heim_object_t table
, heim_object_t table_dict
,
1022 db
->current_table
= table
;
1023 heim_dict_iterate_f(table_dict
, db
, db_replay_log_table_del_keys_iter
);
1027 db_do_log_actions(heim_db_t db
, heim_error_t
*error
)
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
);
1043 if (error
&& db
->error
) {
1047 heim_release(db
->error
);
1054 db_replay_log(heim_db_t db
, heim_error_t
*error
)
1057 heim_string_t journal_fname
= NULL
;
1058 heim_object_t journal
;
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");
1067 if (db
->options
== NULL
)
1070 journal_fname
= heim_dict_get_value(db
->options
, HSTR("journal-filename"));
1071 if (journal_fname
== NULL
)
1074 ret
= read_json(heim_string_get_utf8(journal_fname
), &journal
, error
);
1077 if (ret
== 0 && journal
== NULL
)
1082 if (heim_get_tid(journal
) != HEIM_TID_ARRAY
)
1083 return HEIM_ERROR(error
, EINVAL
,
1084 (ret
, N_("Invalid journal contents; delete journal",
1087 len
= heim_array_get_length(journal
);
1090 db
->set_keys
= heim_array_get_value(journal
, 0);
1092 db
->del_keys
= heim_array_get_value(journal
, 1);
1093 ret
= db_do_log_actions(db
, error
);
1097 /* Truncate replay log and we're done */
1098 ret
= open_file(heim_string_get_utf8(journal_fname
), 1, 0, NULL
, error
);
1101 heim_release(db
->set_keys
);
1102 heim_release(db
->del_keys
);
1103 db
->set_keys
= NULL
;
1104 db
->del_keys
= NULL
;
1110 heim_string_t
to_base64(heim_data_t data
, heim_error_t
*error
)
1113 heim_string_t s
= NULL
;
1114 const heim_octet_string
*d
;
1117 d
= heim_data_get_data(data
);
1118 ret
= base64_encode(d
->data
, d
->length
, &b64
);
1119 if (ret
< 0 || b64
== NULL
)
1121 s
= heim_string_ref_create(b64
, free
);
1129 *error
= heim_error_create_enomem();
1134 heim_data_t
from_base64(heim_string_t s
, heim_error_t
*error
)
1140 buf
= malloc(strlen(heim_string_get_utf8(s
)));
1144 len
= base64_decode(heim_string_get_utf8(s
), buf
);
1145 d
= heim_data_ref_create(buf
, len
, free
);
1153 *error
= heim_error_create_enomem();
1159 open_file(const char *dbname
, int for_write
, int excl
, int *fd_out
, heim_error_t
*error
)
1169 hFile
= CreateFile(dbname
, GENERIC_WRITE
| GENERIC_READ
, 0,
1170 NULL
, /* we'll close as soon as we read */
1171 CREATE_ALWAYS
, FILE_ATTRIBUTE_NORMAL
, NULL
);
1173 hFile
= CreateFile(dbname
, GENERIC_READ
, FILE_SHARE_READ
,
1174 NULL
, /* we'll close as soon as we read */
1175 OPEN_EXISTING
, FILE_ATTRIBUTE_NORMAL
, NULL
);
1176 if (hFile
== INVALID_HANDLE_VALUE
) {
1177 ret
= GetLastError();
1178 _set_errno(ret
); /* CreateFile() does not set errno */
1181 if (fd_out
== NULL
) {
1182 (void) CloseHandle(hFile
);
1186 *fd_out
= _open_osfhandle((intptr_t) hFile
, 0);
1189 (void) CloseHandle(hFile
);
1193 /* No need to lock given share deny mode */
1197 if (error
!= NULL
) {
1199 FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM
| FORMAT_MESSAGE_ALLOCATE_BUFFER
,
1200 0, ret
, 0, (LPTSTR
) &s
, 0, NULL
);
1201 *error
= heim_error_create(ret
, N_("Could not open JSON file %s: %s", ""),
1202 dbname
, s
? s
: "<error formatting error>");
1213 if (for_write
&& excl
)
1214 fd
= open(dbname
, O_CREAT
| O_EXCL
| O_WRONLY
, 0600);
1216 fd
= open(dbname
, O_CREAT
| O_TRUNC
| O_WRONLY
, 0600);
1218 fd
= open(dbname
, O_RDONLY
);
1221 *error
= heim_error_create(ret
, N_("Could not open JSON file %s: %s", ""),
1222 dbname
, strerror(errno
));
1226 if (fd_out
== NULL
) {
1231 ret
= flock(fd
, for_write
? LOCK_EX
: LOCK_SH
);
1233 /* Note that we if O_EXCL we're leaving the [lock] file around */
1235 return HEIM_ERROR(error
, errno
,
1236 (errno
, N_("Could not lock JSON file %s: %s", ""),
1237 dbname
, strerror(errno
)));
1247 read_json(const char *dbname
, heim_object_t
*out
, heim_error_t
*error
)
1256 ret
= open_file(dbname
, 0, 0, &fd
, error
);
1260 ret
= fstat(fd
, &st
);
1263 return HEIM_ERROR(error
, errno
,
1264 (ret
, N_("Could not stat JSON DB %s: %s", ""),
1265 dbname
, strerror(errno
)));
1268 if (st
.st_size
== 0) {
1273 str
= malloc(st
.st_size
+ 1);
1276 return HEIM_ENOMEM(error
);
1279 bytes
= read(fd
, str
, st
.st_size
);
1281 if (bytes
!= st
.st_size
) {
1284 errno
= EINVAL
; /* ?? */
1285 return HEIM_ERROR(error
, errno
,
1286 (ret
, N_("Could not read JSON DB %s: %s", ""),
1287 dbname
, strerror(errno
)));
1289 str
[st
.st_size
] = '\0';
1290 *out
= heim_json_create(str
, 10, 0, error
);
1293 return (error
&& *error
) ? heim_error_get_code(*error
) : EINVAL
;
1297 typedef struct json_db
{
1299 heim_string_t dbname
;
1300 heim_string_t bkpname
;
1302 time_t last_read_time
;
1303 unsigned int read_only
:1;
1304 unsigned int locked
:1;
1305 unsigned int locked_needs_unlink
:1;
1309 json_db_open(void *plug
, const char *dbtype
, const char *dbname
,
1310 heim_dict_t options
, void **db
, heim_error_t
*error
)
1313 heim_dict_t contents
= NULL
;
1314 heim_string_t dbname_s
= NULL
;
1315 heim_string_t bkpname_s
= NULL
;
1319 if (dbtype
&& *dbtype
&& strcmp(dbtype
, "json"))
1320 return HEIM_ERROR(error
, EINVAL
, (EINVAL
, N_("Wrong DB type", "")));
1321 if (dbname
&& *dbname
&& strcmp(dbname
, "MEMORY") != 0) {
1322 char *ext
= strrchr(dbname
, '.');
1327 if (ext
== NULL
|| strcmp(ext
, ".json") != 0)
1328 return HEIM_ERROR(error
, EINVAL
,
1329 (EINVAL
, N_("JSON DB files must end in .json",
1333 heim_object_t vc
, ve
, vt
;
1335 vc
= heim_dict_get_value(options
, HSTR("create"));
1336 ve
= heim_dict_get_value(options
, HSTR("exclusive"));
1337 vt
= heim_dict_get_value(options
, HSTR("truncate"));
1339 ret
= open_file(dbname
, 1, ve
? 1 : 0, NULL
, error
);
1342 } else if (vc
|| ve
|| vt
) {
1343 return HEIM_ERROR(error
, EINVAL
,
1344 (EINVAL
, N_("Invalid JSON DB open options",
1348 * We don't want cloned handles to truncate the DB, eh?
1350 * We should really just create a copy of the options dict
1351 * rather than modify the caller's! But for that it'd be
1352 * nicer to have copy utilities in heimbase, something like
1355 * heim_object_t heim_copy(heim_object_t src, int depth,
1356 * heim_error_t *error);
1358 * so that options = heim_copy(options, 1); means copy the
1359 * dict but nothing else (whereas depth == 0 would mean
1360 * heim_retain(), and depth > 1 would be copy that many
1363 heim_dict_delete_key(options
, HSTR("create"));
1364 heim_dict_delete_key(options
, HSTR("exclusive"));
1365 heim_dict_delete_key(options
, HSTR("truncate"));
1367 dbname_s
= heim_string_create(dbname
);
1368 if (dbname_s
== NULL
)
1369 return HEIM_ENOMEM(error
);
1371 len
= snprintf(NULL
, 0, "%s~", dbname
);
1372 bkpname
= malloc(len
+ 2);
1373 if (bkpname
== NULL
) {
1374 heim_release(dbname_s
);
1375 return HEIM_ENOMEM(error
);
1377 (void) snprintf(bkpname
, len
+ 1, "%s~", dbname
);
1378 bkpname_s
= heim_string_create(bkpname
);
1380 if (bkpname_s
== NULL
) {
1381 heim_release(dbname_s
);
1382 return HEIM_ENOMEM(error
);
1385 ret
= read_json(dbname
, (heim_object_t
*)&contents
, error
);
1389 if (contents
!= NULL
&& heim_get_tid(contents
) != HEIM_TID_DICT
)
1390 return HEIM_ERROR(error
, EINVAL
,
1391 (EINVAL
, N_("JSON DB contents not valid JSON",
1395 jsondb
= heim_alloc(sizeof (*jsondb
), "json_db", NULL
);
1396 if (jsondb
== NULL
) {
1397 heim_release(contents
);
1398 heim_release(dbname_s
);
1402 jsondb
->last_read_time
= time(NULL
);
1404 jsondb
->dbname
= dbname_s
;
1405 jsondb
->bkpname
= bkpname_s
;
1406 jsondb
->read_only
= 0;
1408 if (contents
!= NULL
)
1409 jsondb
->dict
= contents
;
1411 jsondb
->dict
= heim_dict_create(29);
1412 if (jsondb
->dict
== NULL
) {
1413 heim_release(jsondb
);
1423 json_db_close(void *db
, heim_error_t
*error
)
1425 json_db_t jsondb
= db
;
1429 if (jsondb
->fd
> -1)
1430 (void) close(jsondb
->fd
);
1432 heim_release(jsondb
->dbname
);
1433 heim_release(jsondb
->bkpname
);
1434 heim_release(jsondb
->dict
);
1435 heim_release(jsondb
);
1440 json_db_lock(void *db
, int read_only
, heim_error_t
*error
)
1442 json_db_t jsondb
= db
;
1445 heim_assert(jsondb
->fd
== -1 || (jsondb
->read_only
&& !read_only
),
1446 "DB locks are not recursive");
1448 jsondb
->read_only
= read_only
? 1 : 0;
1449 if (jsondb
->fd
> -1)
1452 ret
= open_file(heim_string_get_utf8(jsondb
->bkpname
), 1, 1, &jsondb
->fd
, error
);
1454 jsondb
->locked_needs_unlink
= 1;
1461 json_db_unlock(void *db
, heim_error_t
*error
)
1463 json_db_t jsondb
= db
;
1466 heim_assert(jsondb
->locked
, "DB not locked when unlock attempted");
1467 if (jsondb
->fd
> -1)
1468 ret
= close(jsondb
->fd
);
1470 jsondb
->read_only
= 0;
1472 if (jsondb
->locked_needs_unlink
)
1473 unlink(heim_string_get_utf8(jsondb
->bkpname
));
1474 jsondb
->locked_needs_unlink
= 0;
1479 json_db_sync(void *db
, heim_error_t
*error
)
1481 json_db_t jsondb
= db
;
1485 const char *json_text
= NULL
;
1492 heim_assert(jsondb
->fd
> -1, "DB not locked when sync attempted");
1494 json
= heim_copy_serialize(jsondb
->dict
, 0, &e
);
1500 return heim_error_get_code(e
);
1503 json_text
= heim_string_get_utf8(json
);
1504 len
= strlen(json_text
);
1509 ret
= open_file(heim_string_get_utf8(jsondb
->dbname
), 1, 0, &fd
, error
);
1522 bytes
= write(fd
, json_text
, len
);
1525 return errno
? errno
: EIO
;
1533 return GetLastError();
1535 ret
= rename(heim_string_get_utf8(jsondb
->bkpname
), heim_string_get_utf8(jsondb
->dbname
));
1537 jsondb
->locked_needs_unlink
= 0;
1546 json_db_copy_value(void *db
, heim_string_t table
, heim_data_t key
,
1547 heim_error_t
*error
)
1549 json_db_t jsondb
= db
;
1550 heim_string_t key_string
;
1551 const heim_octet_string
*key_data
= heim_data_get_data(key
);
1558 if (strnlen(key_data
->data
, key_data
->length
) != key_data
->length
) {
1559 HEIM_ERROR(error
, EINVAL
,
1560 (EINVAL
, N_("JSON DB requires keys that are actually "
1565 if (stat(heim_string_get_utf8(jsondb
->dbname
), &st
) == -1) {
1566 HEIM_ERROR(error
, errno
,
1567 (errno
, N_("Could not stat JSON DB file", "")));
1571 if (st
.st_mtime
> jsondb
->last_read_time
||
1572 st
.st_ctime
> jsondb
->last_read_time
) {
1573 heim_dict_t contents
= NULL
;
1576 /* Ignore file is gone (ENOENT) */
1577 ret
= read_json(heim_string_get_utf8(jsondb
->dbname
),
1578 (heim_object_t
*)&contents
, error
);
1581 if (contents
== NULL
)
1582 contents
= heim_dict_create(29);
1583 heim_release(jsondb
->dict
);
1584 jsondb
->dict
= contents
;
1585 jsondb
->last_read_time
= time(NULL
);
1588 key_string
= heim_string_create_with_bytes(key_data
->data
,
1590 if (key_string
== NULL
) {
1591 (void) HEIM_ENOMEM(error
);
1595 result
= heim_path_copy(jsondb
->dict
, error
, table
, key_string
, NULL
);
1596 heim_release(key_string
);
1601 json_db_set_value(void *db
, heim_string_t table
,
1602 heim_data_t key
, heim_data_t value
, heim_error_t
*error
)
1604 json_db_t jsondb
= db
;
1605 heim_string_t key_string
;
1606 const heim_octet_string
*key_data
= heim_data_get_data(key
);
1612 if (strnlen(key_data
->data
, key_data
->length
) != key_data
->length
)
1613 return HEIM_ERROR(error
, EINVAL
,
1615 N_("JSON DB requires keys that are actually strings",
1618 key_string
= heim_string_create_with_bytes(key_data
->data
,
1620 if (key_string
== NULL
)
1621 return HEIM_ENOMEM(error
);
1626 ret
= heim_path_create(jsondb
->dict
, 29, value
, error
, table
, key_string
, NULL
);
1627 heim_release(key_string
);
1632 json_db_del_key(void *db
, heim_string_t table
, heim_data_t key
,
1633 heim_error_t
*error
)
1635 json_db_t jsondb
= db
;
1636 heim_string_t key_string
;
1637 const heim_octet_string
*key_data
= heim_data_get_data(key
);
1642 if (strnlen(key_data
->data
, key_data
->length
) != key_data
->length
)
1643 return HEIM_ERROR(error
, EINVAL
,
1645 N_("JSON DB requires keys that are actually strings",
1648 key_string
= heim_string_create_with_bytes(key_data
->data
,
1650 if (key_string
== NULL
)
1651 return HEIM_ENOMEM(error
);
1656 heim_path_delete(jsondb
->dict
, error
, table
, key_string
, NULL
);
1657 heim_release(key_string
);
1661 struct json_db_iter_ctx
{
1662 heim_db_iterator_f_t iter_f
;
1666 static void json_db_iter_f(heim_object_t key
, heim_object_t value
, void *arg
)
1668 struct json_db_iter_ctx
*ctx
= arg
;
1669 const char *key_string
;
1670 heim_data_t key_data
;
1672 key_string
= heim_string_get_utf8((heim_string_t
)key
);
1673 key_data
= heim_data_ref_create(key_string
, strlen(key_string
), NULL
);
1674 ctx
->iter_f(key_data
, (heim_object_t
)value
, ctx
->iter_ctx
);
1675 heim_release(key_data
);
1679 json_db_iter(void *db
, heim_string_t table
, void *iter_data
,
1680 heim_db_iterator_f_t iter_f
, heim_error_t
*error
)
1682 json_db_t jsondb
= db
;
1683 struct json_db_iter_ctx ctx
;
1684 heim_dict_t table_dict
;
1692 table_dict
= heim_dict_get_value(jsondb
->dict
, table
);
1693 if (table_dict
== NULL
)
1696 ctx
.iter_ctx
= iter_data
;
1697 ctx
.iter_f
= iter_f
;
1699 heim_dict_iterate_f(table_dict
, &ctx
, json_db_iter_f
);
1702 static struct heim_db_type json_dbt
= {
1703 1, json_db_open
, NULL
, json_db_close
,
1704 json_db_lock
, json_db_unlock
, json_db_sync
,
1706 json_db_copy_value
, json_db_set_value
,
1707 json_db_del_key
, json_db_iter