Home | History | Annotate | Line # | Download | only in base
      1 /*	$NetBSD: db.c,v 1.2 2017/01/28 21:31:45 christos Exp $	*/
      2 
      3 /*
      4  * Copyright (c) 2011, Secure Endpoints Inc.
      5  * All rights reserved.
      6  *
      7  * Redistribution and use in source and binary forms, with or without
      8  * modification, are permitted provided that the following conditions
      9  * are met:
     10  *
     11  * - Redistributions of source code must retain the above copyright
     12  *   notice, this list of conditions and the following disclaimer.
     13  *
     14  * - Redistributions in binary form must reproduce the above copyright
     15  *   notice, this list of conditions and the following disclaimer in
     16  *   the documentation and/or other materials provided with the
     17  *   distribution.
     18  *
     19  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
     20  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
     21  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
     22  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
     23  * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
     24  * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
     25  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
     26  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
     27  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
     28  * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
     29  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
     30  * OF THE POSSIBILITY OF SUCH DAMAGE.
     31  */
     32 
     33 /*
     34  * This is a pluggable simple DB abstraction, with a simple get/set/
     35  * delete key/value pair interface.
     36  *
     37  * Plugins may provide any of the following optional features:
     38  *
     39  *  - tables -- multiple attribute/value tables in one DB
     40  *  - locking
     41  *  - transactions (i.e., allow any heim_object_t as key or value)
     42  *  - transcoding of values
     43  *
     44  * Stackable plugins that provide missing optional features are
     45  * possible.
     46  *
     47  * Any plugin that provides locking will also provide transactions, but
     48  * those transactions will not be atomic in the face of failures (a
     49  * memory-based rollback log is used).
     50  */
     51 
     52 #include <errno.h>
     53 #include <stdio.h>
     54 #include <stdlib.h>
     55 #include <string.h>
     56 #include <sys/types.h>
     57 #include <sys/stat.h>
     58 #ifdef WIN32
     59 #include <io.h>
     60 #else
     61 #include <sys/file.h>
     62 #endif
     63 #ifdef HAVE_UNISTD_H
     64 #include <unistd.h>
     65 #endif
     66 #include <fcntl.h>
     67 
     68 #include "baselocl.h"
     69 #include <krb5/base64.h>
     70 
     71 #define HEIM_ENOMEM(ep) \
     72     (((ep) && !*(ep)) ? \
     73 	heim_error_get_code((*(ep) = heim_error_create_enomem())) : ENOMEM)
     74 
     75 #define HEIM_ERROR_HELPER(ep, ec, args) \
     76     (((ep) && !*(ep)) ? \
     77 	heim_error_get_code((*(ep) = heim_error_create args)) : (ec))
     78 
     79 #define HEIM_ERROR(ep, ec, args) \
     80     (ec == ENOMEM) ? HEIM_ENOMEM(ep) : HEIM_ERROR_HELPER(ep, ec, args);
     81 
     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 *);
     84 
     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;
     88 
     89 static void db_dealloc(void *ptr);
     90 
     91 struct heim_type_data db_object = {
     92     HEIM_TID_DB,
     93     "db-object",
     94     NULL,
     95     db_dealloc,
     96     NULL,
     97     NULL,
     98     NULL,
     99     NULL
    100 };
    101 
    102 
    103 static heim_base_once_t db_plugin_init_once = HEIM_BASE_ONCE_INIT;
    104 
    105 static heim_dict_t db_plugins;
    106 
    107 typedef struct db_plugin {
    108     heim_string_t               name;
    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;
    122     void                        *data;
    123 } db_plugin_desc, *db_plugin;
    124 
    125 struct heim_db_data {
    126     db_plugin           plug;
    127     heim_string_t       dbtype;
    128     heim_string_t       dbname;
    129     heim_dict_t         options;
    130     void                *db_data;
    131     heim_data_t		to_release;
    132     heim_error_t        error;
    133     int                 ret;
    134     unsigned int        in_transaction:1;
    135     unsigned int	ro: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;
    140 };
    141 
    142 static int
    143 db_do_log_actions(heim_db_t db, heim_error_t *error);
    144 static int
    145 db_replay_log(heim_db_t db, heim_error_t *error);
    146 
    147 static HEIMDAL_MUTEX db_type_mutex = HEIMDAL_MUTEX_INITIALIZER;
    148 
    149 static void
    150 db_init_plugins_once(void *arg)
    151 {
    152     db_plugins = heim_retain(arg);
    153 }
    154 
    155 static void
    156 plugin_dealloc(void *arg)
    157 {
    158     db_plugin plug = arg;
    159 
    160     heim_release(plug->name);
    161 }
    162 
    163 /** heim_db_register
    164  * @brief Registers a DB type for use with heim_db_create().
    165  *
    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)
    169  *
    170  * Backends that provide begin/commit/rollback methods must provide ACID
    171  * semantics.
    172  *
    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
    176  * scheme).
    177  *
    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.
    181  *
    182  * @return ENOMEM on failure, else 0.
    183  *
    184  * @addtogroup heimbase
    185  */
    186 int
    187 heim_db_register(const char *dbtype,
    188 		 void *data,
    189 		 struct heim_db_type *plugin)
    190 {
    191     heim_dict_t plugins;
    192     heim_string_t s;
    193     db_plugin plug, plug2;
    194     int ret = 0;
    195 
    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");
    201 
    202     /* Initialize */
    203     plugins = heim_dict_create(11);
    204     if (plugins == NULL)
    205 	return ENOMEM;
    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");
    209 
    210     s = heim_string_create(dbtype);
    211     if (s == NULL)
    212 	return ENOMEM;
    213 
    214     plug = heim_alloc(sizeof (*plug), "db_plug", plugin_dealloc);
    215     if (plug == NULL) {
    216 	heim_release(s);
    217 	return ENOMEM;
    218     }
    219 
    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;
    234     plug->data = data;
    235 
    236     HEIMDAL_MUTEX_lock(&db_type_mutex);
    237     plug2 = heim_dict_get_value(db_plugins, s);
    238     if (plug2 == NULL)
    239 	ret = heim_dict_set_value(db_plugins, s, plug);
    240     HEIMDAL_MUTEX_unlock(&db_type_mutex);
    241     heim_release(plug);
    242     heim_release(s);
    243 
    244     return ret;
    245 }
    246 
    247 static void
    248 db_dealloc(void *arg)
    249 {
    250     heim_db_t db = arg;
    251     heim_assert(!db->in_transaction,
    252 		"rollback or commit heim_db_t before releasing it");
    253     if (db->db_data)
    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);
    262 }
    263 
    264 struct dbtype_iter {
    265     heim_db_t           db;
    266     const char          *dbname;
    267     heim_dict_t         options;
    268     heim_error_t        *error;
    269 };
    270 
    271 /*
    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.
    276  */
    277 static void
    278 dbtype_iter2create_f(heim_object_t dbtype, heim_object_t junk, void *arg)
    279 {
    280     struct dbtype_iter *iter_ctx = arg;
    281 
    282     if (iter_ctx->db != NULL)
    283 	return;
    284     iter_ctx->db = heim_db_create(heim_string_get_utf8(dbtype),
    285 				  iter_ctx->dbname, iter_ctx->options,
    286 				  iter_ctx->error);
    287 }
    288 
    289 /**
    290  * Open a database of the given dbtype.
    291  *
    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.
    296  *
    297  * Options may be provided via a dict (an associative array).  Existing
    298  * options include:
    299  *
    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
    306  *
    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
    312  *
    313  * @return a DB handle
    314  *
    315  * @addtogroup heimbase
    316  */
    317 heim_db_t
    318 heim_db_create(const char *dbtype, const char *dbname,
    319 	       heim_dict_t options, heim_error_t *error)
    320 {
    321     heim_string_t s;
    322     char *p;
    323     db_plugin plug;
    324     heim_db_t db;
    325     int ret = 0;
    326 
    327     if (options == NULL) {
    328 	options = heim_dict_create(11);
    329 	if (options == NULL) {
    330 	    if (error)
    331 		*error = heim_error_create_enomem();
    332 	    return NULL;
    333 	}
    334     } else {
    335 	(void) heim_retain(options);
    336     }
    337 
    338     if (db_plugins == NULL) {
    339 	heim_release(options);
    340 	return NULL;
    341     }
    342 
    343     if (dbtype == NULL || *dbtype == '\0') {
    344 	struct dbtype_iter iter_ctx = { NULL, dbname, options, error};
    345 
    346 	/* Try all dbtypes */
    347 	heim_dict_iterate_f(db_plugins, &iter_ctx, dbtype_iter2create_f);
    348 	heim_release(options);
    349 	return iter_ctx.db;
    350     } else if (strstr(dbtype, "json")) {
    351 	(void) heim_db_register(dbtype, NULL, &json_dbt);
    352     }
    353 
    354     /*
    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, ...
    358      */
    359     p = strchr(dbtype, '+');
    360     if (p != NULL)
    361 	s = heim_string_create_with_bytes(dbtype, p - dbtype);
    362     else
    363 	s = heim_string_create(dbtype);
    364     if (s == NULL) {
    365 	heim_release(options);
    366 	return NULL;
    367     }
    368 
    369     HEIMDAL_MUTEX_lock(&db_type_mutex);
    370     plug = heim_dict_get_value(db_plugins, s);
    371     HEIMDAL_MUTEX_unlock(&db_type_mutex);
    372     heim_release(s);
    373     if (plug == NULL) {
    374 	if (error)
    375 	    *error = heim_error_create(ENOENT,
    376 				       N_("Heimdal DB plugin not found: %s", ""),
    377 				       dbtype);
    378 	heim_release(options);
    379 	return NULL;
    380     }
    381 
    382     db = _heim_alloc_object(&db_object, sizeof(*db));
    383     if (db == NULL) {
    384 	heim_release(options);
    385 	return NULL;
    386     }
    387 
    388     db->in_transaction = 0;
    389     db->ro_tx = 0;
    390     db->set_keys = NULL;
    391     db->del_keys = NULL;
    392     db->plug = plug;
    393     db->options = options;
    394 
    395     ret = plug->openf(plug->data, dbtype, dbname, options, &db->db_data, error);
    396     if (ret) {
    397 	heim_release(db);
    398 	if (error && *error == NULL)
    399 	    *error = heim_error_create(ENOENT,
    400 				       N_("Heimdal DB could not be opened: %s", ""),
    401 				       dbname);
    402 	return NULL;
    403     }
    404 
    405     ret = db_replay_log(db, error);
    406     if (ret) {
    407 	heim_release(db);
    408 	return NULL;
    409     }
    410 
    411     if (plug->clonef == NULL) {
    412 	db->dbtype = heim_string_create(dbtype);
    413 	db->dbname = heim_string_create(dbname);
    414 
    415 	if (!db->dbtype || ! db->dbname) {
    416 	    heim_release(db);
    417 	    if (error)
    418 		*error = heim_error_create_enomem();
    419 	    return NULL;
    420 	}
    421     }
    422 
    423     return db;
    424 }
    425 
    426 /**
    427  * Clone (duplicate) an open DB handle.
    428  *
    429  * This is useful for multi-threaded applications.  Applications must
    430  * synchronize access to any given DB handle.
    431  *
    432  * Returns EBUSY if there is an open transaction for the input db.
    433  *
    434  * @param db      Open DB handle
    435  * @param error   Output error object
    436  *
    437  * @return a DB handle
    438  *
    439  * @addtogroup heimbase
    440  */
    441 heim_db_t
    442 heim_db_clone(heim_db_t db, heim_error_t *error)
    443 {
    444     heim_db_t result;
    445     int ret;
    446 
    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");
    451 
    452     if (db->plug->clonef == NULL) {
    453 	return heim_db_create(heim_string_get_utf8(db->dbtype),
    454 			      heim_string_get_utf8(db->dbname),
    455 			      db->options, error);
    456     }
    457 
    458     result = _heim_alloc_object(&db_object, sizeof(*result));
    459     if (result == NULL) {
    460 	if (error)
    461 	    *error = heim_error_create_enomem();
    462 	return NULL;
    463     }
    464 
    465     result->set_keys = NULL;
    466     result->del_keys = NULL;
    467     ret = db->plug->clonef(db->db_data, &result->db_data, error);
    468     if (ret) {
    469 	heim_release(result);
    470 	if (error && !*error)
    471 	    *error = heim_error_create(ENOENT,
    472 				       N_("Could not re-open DB while cloning", ""));
    473 	return NULL;
    474     }
    475     db->db_data = NULL;
    476     return result;
    477 }
    478 
    479 /**
    480  * Open a transaction on the given db.
    481  *
    482  * @param db    Open DB handle
    483  * @param error Output error object
    484  *
    485  * @return 0 on success, system error otherwise
    486  *
    487  * @addtogroup heimbase
    488  */
    489 int
    490 heim_db_begin(heim_db_t db, int read_only, heim_error_t *error)
    491 {
    492     int ret;
    493 
    494     if (heim_get_tid(db) != HEIM_TID_DB)
    495 	return EINVAL;
    496 
    497     if (db->in_transaction && (read_only || !db->ro_tx || (!read_only && !db->ro_tx)))
    498 	heim_abort("DB already in transaction");
    499 
    500     if (db->plug->setf == NULL || db->plug->delf == NULL)
    501 	return EINVAL;
    502 
    503     if (db->plug->beginf) {
    504 	ret = db->plug->beginf(db->db_data, read_only, error);
    505         if (ret)
    506             return ret;
    507     } else if (!db->in_transaction) {
    508 	/* Try to emulate transactions */
    509 
    510 	if (db->plug->lockf == NULL)
    511 	    return EINVAL; /* can't lock? -> no transactions */
    512 
    513 	/* Assume unlock provides sync/durability */
    514 	ret = db->plug->lockf(db->db_data, read_only, error);
    515 	if (ret)
    516 	    return ret;
    517 
    518 	ret = db_replay_log(db, error);
    519 	if (ret) {
    520 	    ret = db->plug->unlockf(db->db_data, error);
    521 	    return ret;
    522 	}
    523 
    524 	db->set_keys = heim_dict_create(11);
    525 	if (db->set_keys == NULL)
    526 	    return ENOMEM;
    527 	db->del_keys = heim_dict_create(11);
    528 	if (db->del_keys == NULL) {
    529 	    heim_release(db->set_keys);
    530 	    db->set_keys = NULL;
    531 	    return ENOMEM;
    532 	}
    533     } else {
    534 	heim_assert(read_only == 0, "Internal error");
    535 	ret = db->plug->lockf(db->db_data, 0, error);
    536 	if (ret)
    537 	    return ret;
    538     }
    539     db->in_transaction = 1;
    540     db->ro_tx = !!read_only;
    541     return 0;
    542 }
    543 
    544 /**
    545  * Commit an open transaction on the given db.
    546  *
    547  * @param db    Open DB handle
    548  * @param error Output error object
    549  *
    550  * @return 0 on success, system error otherwise
    551  *
    552  * @addtogroup heimbase
    553  */
    554 int
    555 heim_db_commit(heim_db_t db, heim_error_t *error)
    556 {
    557     int ret, ret2;
    558     heim_string_t journal_fname = NULL;
    559 
    560     if (heim_get_tid(db) != HEIM_TID_DB)
    561 	return EINVAL;
    562     if (!db->in_transaction)
    563 	return 0;
    564     if (db->plug->commitf == NULL && db->plug->lockf == NULL)
    565 	return EINVAL;
    566 
    567     if (db->plug->commitf != NULL) {
    568 	ret = db->plug->commitf(db->db_data, error);
    569 	if (ret)
    570 	    (void) db->plug->rollbackf(db->db_data, error);
    571 
    572 	db->in_transaction = 0;
    573 	db->ro_tx = 0;
    574 	return ret;
    575     }
    576 
    577     if (db->ro_tx) {
    578 	ret = 0;
    579 	goto done;
    580     }
    581 
    582     if (db->options == NULL)
    583 	journal_fname = heim_dict_get_value(db->options, HSTR("journal-filename"));
    584 
    585     if (journal_fname != NULL) {
    586 	heim_array_t a;
    587 	heim_string_t journal_contents;
    588 	size_t len, bytes;
    589 	int save_errno;
    590 
    591 	/* Create contents for replay log */
    592 	ret = ENOMEM;
    593 	a = heim_array_create();
    594 	if (a == NULL)
    595 	    goto err;
    596 	ret = heim_array_append_value(a, db->set_keys);
    597 	if (ret) {
    598 	    heim_release(a);
    599 	    goto err;
    600 	}
    601 	ret = heim_array_append_value(a, db->del_keys);
    602 	if (ret) {
    603 	    heim_release(a);
    604 	    goto err;
    605 	}
    606 	journal_contents = heim_json_copy_serialize(a, 0, error);
    607 	heim_release(a);
    608 
    609 	/* Write replay log */
    610 	if (journal_fname != NULL) {
    611 	    int fd;
    612 
    613 	    ret = open_file(heim_string_get_utf8(journal_fname), 1, 0, &fd, error);
    614 	    if (ret) {
    615 		heim_release(journal_contents);
    616 		goto err;
    617 	    }
    618 	    len = strlen(heim_string_get_utf8(journal_contents));
    619 	    bytes = write(fd, heim_string_get_utf8(journal_contents), len);
    620 	    save_errno = errno;
    621 	    heim_release(journal_contents);
    622 	    ret = close(fd);
    623 	    if (bytes != len) {
    624 		/* Truncate replay log */
    625 		(void) open_file(heim_string_get_utf8(journal_fname), 1, 0, NULL, error);
    626 		ret = save_errno;
    627 		goto err;
    628 	    }
    629 	    if (ret)
    630 		goto err;
    631 	}
    632     }
    633 
    634     /* Apply logged actions */
    635     ret = db_do_log_actions(db, error);
    636     if (ret)
    637 	return ret;
    638 
    639     if (db->plug->syncf != NULL) {
    640 	/* fsync() or whatever */
    641 	ret = db->plug->syncf(db->db_data, error);
    642 	if (ret)
    643 	    return ret;
    644     }
    645 
    646     /* Truncate replay log and we're done */
    647     if (journal_fname != NULL) {
    648 	int fd;
    649 
    650 	ret2 = open_file(heim_string_get_utf8(journal_fname), 1, 0, &fd, error);
    651 	if (ret2 == 0)
    652 	    (void) close(fd);
    653     }
    654 
    655     /*
    656      * Clean up; if we failed to remore the replay log that's OK, we'll
    657      * handle that again in heim_db_commit()
    658      */
    659 done:
    660     heim_release(db->set_keys);
    661     heim_release(db->del_keys);
    662     db->set_keys = NULL;
    663     db->del_keys = NULL;
    664     db->in_transaction = 0;
    665     db->ro_tx = 0;
    666 
    667     ret2 = db->plug->unlockf(db->db_data, error);
    668     if (ret == 0)
    669 	ret = ret2;
    670 
    671     return ret;
    672 
    673 err:
    674     return HEIM_ERROR(error, ret,
    675 		      (ret, N_("Error while committing transaction: %s", ""),
    676 		       strerror(ret)));
    677 }
    678 
    679 /**
    680  * Rollback an open transaction on the given db.
    681  *
    682  * @param db    Open DB handle
    683  * @param error Output error object
    684  *
    685  * @return 0 on success, system error otherwise
    686  *
    687  * @addtogroup heimbase
    688  */
    689 int
    690 heim_db_rollback(heim_db_t db, heim_error_t *error)
    691 {
    692     int ret = 0;
    693 
    694     if (heim_get_tid(db) != HEIM_TID_DB)
    695 	return EINVAL;
    696     if (!db->in_transaction)
    697 	return 0;
    698 
    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);
    703 
    704     heim_release(db->set_keys);
    705     heim_release(db->del_keys);
    706     db->set_keys = NULL;
    707     db->del_keys = NULL;
    708     db->in_transaction = 0;
    709     db->ro_tx = 0;
    710 
    711     return ret;
    712 }
    713 
    714 /**
    715  * Get type ID of heim_db_t objects.
    716  *
    717  * @addtogroup heimbase
    718  */
    719 heim_tid_t
    720 heim_db_get_type_id(void)
    721 {
    722     return HEIM_TID_DB;
    723 }
    724 
    725 heim_data_t
    726 _heim_db_get_value(heim_db_t db, heim_string_t table, heim_data_t key,
    727 		   heim_error_t *error)
    728 {
    729     heim_release(db->to_release);
    730     db->to_release = heim_db_copy_value(db, table, key, error);
    731     return db->to_release;
    732 }
    733 
    734 /**
    735  * Lookup a key's value in the DB.
    736  *
    737  * Returns 0 on success, -1 if the key does not exist in the DB, or a
    738  * system error number on failure.
    739  *
    740  * @param db    Open DB handle
    741  * @param key   Key
    742  * @param error Output error object
    743  *
    744  * @return the value (retained), if there is one for the given key
    745  *
    746  * @addtogroup heimbase
    747  */
    748 heim_data_t
    749 heim_db_copy_value(heim_db_t db, heim_string_t table, heim_data_t key,
    750 		   heim_error_t *error)
    751 {
    752     heim_object_t v;
    753     heim_data_t result;
    754 
    755     if (heim_get_tid(db) != HEIM_TID_DB)
    756 	return NULL;
    757 
    758     if (error != NULL)
    759 	*error = NULL;
    760 
    761     if (table == NULL)
    762 	table = HSTR("");
    763 
    764     if (db->in_transaction) {
    765 	heim_string_t key64;
    766 
    767 	key64 = to_base64(key, error);
    768 	if (key64 == NULL) {
    769 	    if (error)
    770 		*error = heim_error_create_enomem();
    771 	    return NULL;
    772 	}
    773 
    774 	v = heim_path_copy(db->set_keys, error, table, key64, NULL);
    775 	if (v != NULL) {
    776 	    heim_release(key64);
    777 	    return v;
    778 	}
    779 	v = heim_path_copy(db->del_keys, error, table, key64, NULL); /* can't be NULL */
    780 	heim_release(key64);
    781 	if (v != NULL)
    782 	    return NULL;
    783     }
    784 
    785     result = db->plug->copyf(db->db_data, table, key, error);
    786 
    787     return result;
    788 }
    789 
    790 /**
    791  * Set a key's value in the DB.
    792  *
    793  * @param db    Open DB handle
    794  * @param key   Key
    795  * @param value Value (if NULL the key will be deleted, but empty is OK)
    796  * @param error Output error object
    797  *
    798  * @return 0 on success, system error otherwise
    799  *
    800  * @addtogroup heimbase
    801  */
    802 int
    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)
    805 {
    806     heim_string_t key64 = NULL;
    807     int ret;
    808 
    809     if (error != NULL)
    810 	*error = NULL;
    811 
    812     if (table == NULL)
    813 	table = HSTR("");
    814 
    815     if (value == NULL)
    816 	/* Use heim_null_t instead of NULL */
    817 	return heim_db_delete_key(db, table, key, error);
    818 
    819     if (heim_get_tid(db) != HEIM_TID_DB)
    820 	return EINVAL;
    821 
    822     if (heim_get_tid(key) != HEIM_TID_DATA)
    823 	return HEIM_ERROR(error, EINVAL,
    824 			  (EINVAL, N_("DB keys must be data", "")));
    825 
    826     if (db->plug->setf == NULL)
    827 	return EBADF;
    828 
    829     if (!db->in_transaction) {
    830 	ret = heim_db_begin(db, 0, error);
    831 	if (ret)
    832 	    goto err;
    833 	heim_assert(db->in_transaction, "Internal error");
    834 	ret = heim_db_set_value(db, table, key, value, error);
    835 	if (ret) {
    836 	    (void) heim_db_rollback(db, NULL);
    837 	    return ret;
    838 	}
    839 	return heim_db_commit(db, error);
    840     }
    841 
    842     /* Transaction emulation */
    843     heim_assert(db->set_keys != NULL, "Internal error");
    844     key64 = to_base64(key, error);
    845     if (key64 == NULL)
    846 	return HEIM_ENOMEM(error);
    847 
    848     if (db->ro_tx) {
    849 	ret = heim_db_begin(db, 0, error);
    850 	if (ret)
    851 	    goto err;
    852     }
    853     ret = heim_path_create(db->set_keys, 29, value, error, table, key64, NULL);
    854     if (ret)
    855 	goto err;
    856     heim_path_delete(db->del_keys, error, table, key64, NULL);
    857     heim_release(key64);
    858 
    859     return 0;
    860 
    861 err:
    862     heim_release(key64);
    863     return HEIM_ERROR(error, ret,
    864 		      (ret, N_("Could not set a dict value while while "
    865 		       "setting a DB value", "")));
    866 }
    867 
    868 /**
    869  * Delete a key and its value from the DB
    870  *
    871  *
    872  * @param db    Open DB handle
    873  * @param key   Key
    874  * @param error Output error object
    875  *
    876  * @return 0 on success, system error otherwise
    877  *
    878  * @addtogroup heimbase
    879  */
    880 int
    881 heim_db_delete_key(heim_db_t db, heim_string_t table, heim_data_t key,
    882 		   heim_error_t *error)
    883 {
    884     heim_string_t key64 = NULL;
    885     int ret;
    886 
    887     if (error != NULL)
    888 	*error = NULL;
    889 
    890     if (table == NULL)
    891 	table = HSTR("");
    892 
    893     if (heim_get_tid(db) != HEIM_TID_DB)
    894 	return EINVAL;
    895 
    896     if (db->plug->delf == NULL)
    897 	return EBADF;
    898 
    899     if (!db->in_transaction) {
    900 	ret = heim_db_begin(db, 0, error);
    901 	if (ret)
    902 	    goto err;
    903 	heim_assert(db->in_transaction, "Internal error");
    904 	ret = heim_db_delete_key(db, table, key, error);
    905 	if (ret) {
    906 	    (void) heim_db_rollback(db, NULL);
    907 	    return ret;
    908 	}
    909 	return heim_db_commit(db, error);
    910     }
    911 
    912     /* Transaction emulation */
    913     heim_assert(db->set_keys != NULL, "Internal error");
    914     key64 = to_base64(key, error);
    915     if (key64 == NULL)
    916 	return HEIM_ENOMEM(error);
    917     if (db->ro_tx) {
    918 	ret = heim_db_begin(db, 0, error);
    919 	if (ret)
    920 	    goto err;
    921     }
    922     ret = heim_path_create(db->del_keys, 29, heim_number_create(1), error, table, key64, NULL);
    923     if (ret)
    924 	goto err;
    925     heim_path_delete(db->set_keys, error, table, key64, NULL);
    926     heim_release(key64);
    927 
    928     return 0;
    929 
    930 err:
    931     heim_release(key64);
    932     return HEIM_ERROR(error, ret,
    933 		      (ret, N_("Could not set a dict value while while "
    934 		       "deleting a DB value", "")));
    935 }
    936 
    937 /**
    938  * Iterate a callback function over keys and values from a DB.
    939  *
    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
    944  *
    945  * @addtogroup heimbase
    946  */
    947 void
    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)
    950 {
    951     if (error != NULL)
    952 	*error = NULL;
    953 
    954     if (heim_get_tid(db) != HEIM_TID_DB)
    955 	return;
    956 
    957     if (!db->in_transaction)
    958 	db->plug->iterf(db->db_data, table, iter_data, iter_f, error);
    959 }
    960 
    961 static void
    962 db_replay_log_table_set_keys_iter(heim_object_t key, heim_object_t value,
    963 				  void *arg)
    964 {
    965     heim_db_t db = arg;
    966     heim_data_t k, v;
    967 
    968     if (db->ret)
    969 	return;
    970 
    971     k = from_base64((heim_string_t)key, &db->error);
    972     if (k == NULL) {
    973 	db->ret = ENOMEM;
    974 	return;
    975     }
    976     v = (heim_data_t)value;
    977 
    978     db->ret = db->plug->setf(db->db_data, db->current_table, k, v, &db->error);
    979     heim_release(k);
    980 }
    981 
    982 static void
    983 db_replay_log_table_del_keys_iter(heim_object_t key, heim_object_t value,
    984 				  void *arg)
    985 {
    986     heim_db_t db = arg;
    987     heim_data_t k;
    988 
    989     if (db->ret) {
    990 	db->ret = ENOMEM;
    991 	return;
    992     }
    993 
    994     k = from_base64((heim_string_t)key, &db->error);
    995     if (k == NULL)
    996 	return;
    997 
    998     db->ret = db->plug->delf(db->db_data, db->current_table, k, &db->error);
    999     heim_release(k);
   1000 }
   1001 
   1002 static void
   1003 db_replay_log_set_keys_iter(heim_object_t table, heim_object_t table_dict,
   1004 			    void *arg)
   1005 {
   1006     heim_db_t db = arg;
   1007 
   1008     if (db->ret)
   1009 	return;
   1010 
   1011     db->current_table = table;
   1012     heim_dict_iterate_f(table_dict, db, db_replay_log_table_set_keys_iter);
   1013 }
   1014 
   1015 static void
   1016 db_replay_log_del_keys_iter(heim_object_t table, heim_object_t table_dict,
   1017 			    void *arg)
   1018 {
   1019     heim_db_t db = arg;
   1020 
   1021     if (db->ret)
   1022 	return;
   1023 
   1024     db->current_table = table;
   1025     heim_dict_iterate_f(table_dict, db, db_replay_log_table_del_keys_iter);
   1026 }
   1027 
   1028 static int
   1029 db_do_log_actions(heim_db_t db, heim_error_t *error)
   1030 {
   1031     int ret;
   1032 
   1033     if (error)
   1034 	*error = NULL;
   1035 
   1036     db->ret = 0;
   1037     db->error = NULL;
   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);
   1042 
   1043     ret = db->ret;
   1044     db->ret = 0;
   1045     if (error && db->error) {
   1046 	*error = db->error;
   1047 	db->error = NULL;
   1048     } else {
   1049 	heim_release(db->error);
   1050 	db->error = NULL;
   1051     }
   1052     return ret;
   1053 }
   1054 
   1055 static int
   1056 db_replay_log(heim_db_t db, heim_error_t *error)
   1057 {
   1058     int ret;
   1059     heim_string_t journal_fname = NULL;
   1060     heim_object_t journal;
   1061     size_t len;
   1062 
   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");
   1065 
   1066     if (error)
   1067 	*error = NULL;
   1068 
   1069     if (db->options == NULL)
   1070 	return 0;
   1071 
   1072     journal_fname = heim_dict_get_value(db->options, HSTR("journal-filename"));
   1073     if (journal_fname == NULL)
   1074 	return 0;
   1075 
   1076     ret = read_json(heim_string_get_utf8(journal_fname), &journal, error);
   1077     if (ret == ENOENT) {
   1078         heim_release(journal_fname);
   1079 	return 0;
   1080     }
   1081     if (ret == 0 && journal == NULL) {
   1082         heim_release(journal_fname);
   1083 	return 0;
   1084     }
   1085     if (ret != 0) {
   1086         heim_release(journal_fname);
   1087 	return ret;
   1088     }
   1089 
   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",
   1094 				   "")));
   1095     }
   1096 
   1097     len = heim_array_get_length(journal);
   1098 
   1099     if (len > 0)
   1100 	db->set_keys = heim_array_get_value(journal, 0);
   1101     if (len > 1)
   1102 	db->del_keys = heim_array_get_value(journal, 1);
   1103     ret = db_do_log_actions(db, error);
   1104     if (ret) {
   1105         heim_release(journal_fname);
   1106 	return ret;
   1107     }
   1108 
   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);
   1112     if (ret)
   1113 	return ret;
   1114     heim_release(db->set_keys);
   1115     heim_release(db->del_keys);
   1116     db->set_keys = NULL;
   1117     db->del_keys = NULL;
   1118 
   1119     return 0;
   1120 }
   1121 
   1122 static
   1123 heim_string_t to_base64(heim_data_t data, heim_error_t *error)
   1124 {
   1125     char *b64 = NULL;
   1126     heim_string_t s = NULL;
   1127     const heim_octet_string *d;
   1128     int ret;
   1129 
   1130     d = heim_data_get_data(data);
   1131     ret = rk_base64_encode(d->data, d->length, &b64);
   1132     if (ret < 0 || b64 == NULL)
   1133 	goto enomem;
   1134     s = heim_string_ref_create(b64, free);
   1135     if (s == NULL)
   1136 	goto enomem;
   1137     return s;
   1138 
   1139 enomem:
   1140     free(b64);
   1141     if (error)
   1142 	*error = heim_error_create_enomem();
   1143     return NULL;
   1144 }
   1145 
   1146 static
   1147 heim_data_t from_base64(heim_string_t s, heim_error_t *error)
   1148 {
   1149     void *buf;
   1150     size_t len;
   1151     heim_data_t d;
   1152 
   1153     buf = malloc(strlen(heim_string_get_utf8(s)));
   1154     if (buf == NULL)
   1155 	goto enomem;
   1156 
   1157     len = rk_base64_decode(heim_string_get_utf8(s), buf);
   1158     d = heim_data_ref_create(buf, len, free);
   1159     if (d == NULL)
   1160 	goto enomem;
   1161     return d;
   1162 
   1163 enomem:
   1164     free(buf);
   1165     if (error)
   1166 	*error = heim_error_create_enomem();
   1167     return NULL;
   1168 }
   1169 
   1170 
   1171 static int
   1172 open_file(const char *dbname, int for_write, int excl, int *fd_out, heim_error_t *error)
   1173 {
   1174 #ifdef WIN32
   1175     HANDLE hFile;
   1176     int ret = 0;
   1177 
   1178     if (fd_out)
   1179 	*fd_out = -1;
   1180 
   1181     if (for_write)
   1182 	hFile = CreateFile(dbname, GENERIC_WRITE | GENERIC_READ, 0,
   1183 			   NULL, /* we'll close as soon as we read */
   1184 			   CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
   1185     else
   1186 	hFile = CreateFile(dbname, GENERIC_READ, FILE_SHARE_READ,
   1187 			   NULL, /* we'll close as soon as we read */
   1188 			   OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
   1189     if (hFile == INVALID_HANDLE_VALUE) {
   1190 	ret = GetLastError();
   1191 	_set_errno(ret); /* CreateFile() does not set errno */
   1192 	goto err;
   1193     }
   1194     if (fd_out == NULL) {
   1195 	(void) CloseHandle(hFile);
   1196 	return 0;
   1197     }
   1198 
   1199     *fd_out = _open_osfhandle((intptr_t) hFile, 0);
   1200     if (*fd_out < 0) {
   1201 	ret = errno;
   1202 	(void) CloseHandle(hFile);
   1203 	goto err;
   1204     }
   1205 
   1206     /* No need to lock given share deny mode */
   1207     return 0;
   1208 
   1209 err:
   1210     if (error != NULL) {
   1211 	char *s = NULL;
   1212 	FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ALLOCATE_BUFFER,
   1213 		      0, ret, 0, (LPTSTR) &s, 0, NULL);
   1214 	*error = heim_error_create(ret, N_("Could not open JSON file %s: %s", ""),
   1215 				   dbname, s ? s : "<error formatting error>");
   1216 	LocalFree(s);
   1217     }
   1218     return ret;
   1219 #else
   1220     int ret = 0;
   1221     int fd;
   1222 
   1223     if (fd_out)
   1224 	*fd_out = -1;
   1225 
   1226     if (for_write && excl)
   1227 	fd = open(dbname, O_CREAT | O_EXCL | O_WRONLY, 0600);
   1228     else if (for_write)
   1229 	fd = open(dbname, O_CREAT | O_TRUNC | O_WRONLY, 0600);
   1230     else
   1231 	fd = open(dbname, O_RDONLY);
   1232     if (fd < 0) {
   1233 	if (error != NULL)
   1234 	    *error = heim_error_create(ret, N_("Could not open JSON file %s: %s", ""),
   1235 				       dbname, strerror(errno));
   1236 	return errno;
   1237     }
   1238 
   1239     if (fd_out == NULL) {
   1240 	(void) close(fd);
   1241 	return 0;
   1242     }
   1243 
   1244     ret = flock(fd, for_write ? LOCK_EX : LOCK_SH);
   1245     if (ret == -1) {
   1246 	/* Note that we if O_EXCL we're leaving the [lock] file around */
   1247 	(void) close(fd);
   1248 	return HEIM_ERROR(error, errno,
   1249 			  (errno, N_("Could not lock JSON file %s: %s", ""),
   1250 			   dbname, strerror(errno)));
   1251     }
   1252 
   1253     *fd_out = fd;
   1254 
   1255     return 0;
   1256 #endif
   1257 }
   1258 
   1259 static int
   1260 read_json(const char *dbname, heim_object_t *out, heim_error_t *error)
   1261 {
   1262     struct stat st;
   1263     char *str = NULL;
   1264     int ret;
   1265     int fd = -1;
   1266     ssize_t bytes;
   1267 
   1268     *out = NULL;
   1269     ret = open_file(dbname, 0, 0, &fd, error);
   1270     if (ret)
   1271 	return ret;
   1272 
   1273     ret = fstat(fd, &st);
   1274     if (ret == -1) {
   1275 	(void) close(fd);
   1276 	return HEIM_ERROR(error, errno,
   1277 			  (ret, N_("Could not stat JSON DB %s: %s", ""),
   1278 			   dbname, strerror(errno)));
   1279     }
   1280 
   1281     if (st.st_size == 0) {
   1282 	(void) close(fd);
   1283 	return 0;
   1284     }
   1285 
   1286     str = malloc(st.st_size + 1);
   1287     if (str == NULL) {
   1288 	 (void) close(fd);
   1289 	return HEIM_ENOMEM(error);
   1290     }
   1291 
   1292     bytes = read(fd, str, st.st_size);
   1293      (void) close(fd);
   1294     if (bytes != st.st_size) {
   1295 	free(str);
   1296 	if (bytes >= 0)
   1297 	    errno = EINVAL; /* ?? */
   1298 	return HEIM_ERROR(error, errno,
   1299 			  (ret, N_("Could not read JSON DB %s: %s", ""),
   1300 			   dbname, strerror(errno)));
   1301     }
   1302     str[st.st_size] = '\0';
   1303     *out = heim_json_create(str, 10, 0, error);
   1304     free(str);
   1305     if (*out == NULL)
   1306 	return (error && *error) ? heim_error_get_code(*error) : EINVAL;
   1307     return 0;
   1308 }
   1309 
   1310 typedef struct json_db {
   1311     heim_dict_t dict;
   1312     heim_string_t dbname;
   1313     heim_string_t bkpname;
   1314     int fd;
   1315     time_t last_read_time;
   1316     unsigned int read_only:1;
   1317     unsigned int locked:1;
   1318     unsigned int locked_needs_unlink:1;
   1319 } *json_db_t;
   1320 
   1321 static int
   1322 json_db_open(void *plug, const char *dbtype, const char *dbname,
   1323 	     heim_dict_t options, void **db, heim_error_t *error)
   1324 {
   1325     json_db_t jsondb;
   1326     heim_dict_t contents = NULL;
   1327     heim_string_t dbname_s = NULL;
   1328     heim_string_t bkpname_s = NULL;
   1329 
   1330     if (error)
   1331 	*error = NULL;
   1332     if (dbtype && *dbtype && strcmp(dbtype, "json"))
   1333 	return HEIM_ERROR(error, EINVAL, (EINVAL, N_("Wrong DB type", "")));
   1334     if (dbname && *dbname && strcmp(dbname, "MEMORY") != 0) {
   1335 	char *ext = strrchr(dbname, '.');
   1336 	char *bkpname;
   1337 	size_t len;
   1338 	int ret;
   1339 
   1340 	if (ext == NULL || strcmp(ext, ".json") != 0)
   1341 	    return HEIM_ERROR(error, EINVAL,
   1342 			      (EINVAL, N_("JSON DB files must end in .json",
   1343 					  "")));
   1344 
   1345 	if (options) {
   1346 	    heim_object_t vc, ve, vt;
   1347 
   1348 	    vc = heim_dict_get_value(options, HSTR("create"));
   1349 	    ve = heim_dict_get_value(options, HSTR("exclusive"));
   1350 	    vt = heim_dict_get_value(options, HSTR("truncate"));
   1351 	    if (vc && vt) {
   1352 		ret = open_file(dbname, 1, ve ? 1 : 0, NULL, error);
   1353 		if (ret)
   1354 		    return ret;
   1355 	    } else if (vc || ve || vt) {
   1356 		return HEIM_ERROR(error, EINVAL,
   1357 				  (EINVAL, N_("Invalid JSON DB open options",
   1358 					      "")));
   1359 	    }
   1360 	    /*
   1361 	     * We don't want cloned handles to truncate the DB, eh?
   1362 	     *
   1363 	     * We should really just create a copy of the options dict
   1364 	     * rather than modify the caller's!  But for that it'd be
   1365 	     * nicer to have copy utilities in heimbase, something like
   1366 	     * this:
   1367 	     *
   1368 	     * heim_object_t heim_copy(heim_object_t src, int depth,
   1369 	     *                         heim_error_t *error);
   1370 	     *
   1371 	     * so that options = heim_copy(options, 1); means copy the
   1372 	     * dict but nothing else (whereas depth == 0 would mean
   1373 	     * heim_retain(), and depth > 1 would be copy that many
   1374 	     * levels).
   1375 	     */
   1376 	    heim_dict_delete_key(options, HSTR("create"));
   1377 	    heim_dict_delete_key(options, HSTR("exclusive"));
   1378 	    heim_dict_delete_key(options, HSTR("truncate"));
   1379 	}
   1380 	dbname_s = heim_string_create(dbname);
   1381 	if (dbname_s == NULL)
   1382 	    return HEIM_ENOMEM(error);
   1383 
   1384 	len = snprintf(NULL, 0, "%s~", dbname);
   1385 	bkpname = malloc(len + 2);
   1386 	if (bkpname == NULL) {
   1387 	    heim_release(dbname_s);
   1388 	    return HEIM_ENOMEM(error);
   1389 	}
   1390 	(void) snprintf(bkpname, len + 1, "%s~", dbname);
   1391 	bkpname_s = heim_string_create(bkpname);
   1392 	free(bkpname);
   1393 	if (bkpname_s == NULL) {
   1394 	    heim_release(dbname_s);
   1395 	    return HEIM_ENOMEM(error);
   1396 	}
   1397 
   1398 	ret = read_json(dbname, (heim_object_t *)&contents, error);
   1399 	if (ret) {
   1400 	    heim_release(bkpname_s);
   1401 	    heim_release(dbname_s);
   1402 	    return ret;
   1403         }
   1404 
   1405 	if (contents != NULL && heim_get_tid(contents) != HEIM_TID_DICT) {
   1406 	    heim_release(bkpname_s);
   1407 	    heim_release(dbname_s);
   1408 	    return HEIM_ERROR(error, EINVAL,
   1409 			      (EINVAL, N_("JSON DB contents not valid JSON",
   1410 					  "")));
   1411         }
   1412     }
   1413 
   1414     jsondb = heim_alloc(sizeof (*jsondb), "json_db", NULL);
   1415     if (jsondb == NULL) {
   1416 	heim_release(contents);
   1417 	heim_release(dbname_s);
   1418 	heim_release(bkpname_s);
   1419 	return ENOMEM;
   1420     }
   1421 
   1422     jsondb->last_read_time = time(NULL);
   1423     jsondb->fd = -1;
   1424     jsondb->dbname = dbname_s;
   1425     jsondb->bkpname = bkpname_s;
   1426     jsondb->read_only = 0;
   1427 
   1428     if (contents != NULL)
   1429 	jsondb->dict = contents;
   1430     else {
   1431 	jsondb->dict = heim_dict_create(29);
   1432 	if (jsondb->dict == NULL) {
   1433 	    heim_release(jsondb);
   1434 	    return ENOMEM;
   1435 	}
   1436     }
   1437 
   1438     *db = jsondb;
   1439     return 0;
   1440 }
   1441 
   1442 static int
   1443 json_db_close(void *db, heim_error_t *error)
   1444 {
   1445     json_db_t jsondb = db;
   1446 
   1447     if (error)
   1448 	*error = NULL;
   1449     if (jsondb->fd > -1)
   1450 	(void) close(jsondb->fd);
   1451     jsondb->fd = -1;
   1452     heim_release(jsondb->dbname);
   1453     heim_release(jsondb->bkpname);
   1454     heim_release(jsondb->dict);
   1455     heim_release(jsondb);
   1456     return 0;
   1457 }
   1458 
   1459 static int
   1460 json_db_lock(void *db, int read_only, heim_error_t *error)
   1461 {
   1462     json_db_t jsondb = db;
   1463     int ret;
   1464 
   1465     heim_assert(jsondb->fd == -1 || (jsondb->read_only && !read_only),
   1466 		"DB locks are not recursive");
   1467 
   1468     jsondb->read_only = read_only ? 1 : 0;
   1469     if (jsondb->fd > -1)
   1470 	return 0;
   1471 
   1472     ret = open_file(heim_string_get_utf8(jsondb->bkpname), 1, 1, &jsondb->fd, error);
   1473     if (ret == 0) {
   1474 	jsondb->locked_needs_unlink = 1;
   1475 	jsondb->locked = 1;
   1476     }
   1477     return ret;
   1478 }
   1479 
   1480 static int
   1481 json_db_unlock(void *db, heim_error_t *error)
   1482 {
   1483     json_db_t jsondb = db;
   1484     int ret = 0;
   1485 
   1486     heim_assert(jsondb->locked, "DB not locked when unlock attempted");
   1487     if (jsondb->fd > -1)
   1488 	ret = close(jsondb->fd);
   1489     jsondb->fd = -1;
   1490     jsondb->read_only = 0;
   1491     jsondb->locked = 0;
   1492     if (jsondb->locked_needs_unlink)
   1493 	unlink(heim_string_get_utf8(jsondb->bkpname));
   1494     jsondb->locked_needs_unlink = 0;
   1495     return ret;
   1496 }
   1497 
   1498 static int
   1499 json_db_sync(void *db, heim_error_t *error)
   1500 {
   1501     json_db_t jsondb = db;
   1502     size_t len, bytes;
   1503     heim_error_t e;
   1504     heim_string_t json;
   1505     const char *json_text = NULL;
   1506     int ret = 0;
   1507     int fd = -1;
   1508 #ifdef WIN32
   1509     int tries = 3;
   1510 #endif
   1511 
   1512     heim_assert(jsondb->fd > -1, "DB not locked when sync attempted");
   1513 
   1514     json = heim_json_copy_serialize(jsondb->dict, 0, &e);
   1515     if (json == NULL) {
   1516 	if (error)
   1517 	    *error = e;
   1518 	else
   1519 	    heim_release(e);
   1520 	return heim_error_get_code(e);
   1521     }
   1522 
   1523     json_text = heim_string_get_utf8(json);
   1524     len = strlen(json_text);
   1525     errno = 0;
   1526 
   1527 #ifdef WIN32
   1528     while (tries--) {
   1529 	ret = open_file(heim_string_get_utf8(jsondb->dbname), 1, 0, &fd, error);
   1530 	if (ret == 0)
   1531 	    break;
   1532 	sleep(1);
   1533     }
   1534     if (ret) {
   1535 	heim_release(json);
   1536 	return ret;
   1537     }
   1538 #else
   1539     fd = jsondb->fd;
   1540 #endif /* WIN32 */
   1541 
   1542     bytes = write(fd, json_text, len);
   1543     heim_release(json);
   1544     if (bytes != len)
   1545 	return errno ? errno : EIO;
   1546     ret = fsync(fd);
   1547     if (ret)
   1548 	return ret;
   1549 
   1550 #ifdef WIN32
   1551     ret = close(fd);
   1552     if (ret)
   1553 	return GetLastError();
   1554 #else
   1555     ret = rename(heim_string_get_utf8(jsondb->bkpname), heim_string_get_utf8(jsondb->dbname));
   1556     if (ret == 0) {
   1557 	jsondb->locked_needs_unlink = 0;
   1558 	return 0;
   1559     }
   1560 #endif /* WIN32 */
   1561 
   1562     return errno;
   1563 }
   1564 
   1565 static heim_data_t
   1566 json_db_copy_value(void *db, heim_string_t table, heim_data_t key,
   1567 		  heim_error_t *error)
   1568 {
   1569     json_db_t jsondb = db;
   1570     heim_string_t key_string;
   1571     const heim_octet_string *key_data = heim_data_get_data(key);
   1572     struct stat st;
   1573     heim_data_t result;
   1574 
   1575     if (error)
   1576 	*error = NULL;
   1577 
   1578     if (strnlen(key_data->data, key_data->length) != key_data->length) {
   1579 	HEIM_ERROR(error, EINVAL,
   1580 		   (EINVAL, N_("JSON DB requires keys that are actually "
   1581 			       "strings", "")));
   1582 	return NULL;
   1583     }
   1584 
   1585     if (stat(heim_string_get_utf8(jsondb->dbname), &st) == -1) {
   1586 	HEIM_ERROR(error, errno,
   1587 		   (errno, N_("Could not stat JSON DB file", "")));
   1588 	return NULL;
   1589     }
   1590 
   1591     if (st.st_mtime > jsondb->last_read_time ||
   1592 	st.st_ctime > jsondb->last_read_time) {
   1593 	heim_dict_t contents = NULL;
   1594 	int ret;
   1595 
   1596 	/* Ignore file is gone (ENOENT) */
   1597 	ret = read_json(heim_string_get_utf8(jsondb->dbname),
   1598 		(heim_object_t *)&contents, error);
   1599 	if (ret)
   1600 	    return NULL;
   1601 	if (contents == NULL)
   1602 	    contents = heim_dict_create(29);
   1603 	heim_release(jsondb->dict);
   1604 	jsondb->dict = contents;
   1605 	jsondb->last_read_time = time(NULL);
   1606     }
   1607 
   1608     key_string = heim_string_create_with_bytes(key_data->data,
   1609 					       key_data->length);
   1610     if (key_string == NULL) {
   1611 	(void) HEIM_ENOMEM(error);
   1612 	return NULL;
   1613     }
   1614 
   1615     result = heim_path_copy(jsondb->dict, error, table, key_string, NULL);
   1616     heim_release(key_string);
   1617     return result;
   1618 }
   1619 
   1620 static int
   1621 json_db_set_value(void *db, heim_string_t table,
   1622 		  heim_data_t key, heim_data_t value, heim_error_t *error)
   1623 {
   1624     json_db_t jsondb = db;
   1625     heim_string_t key_string;
   1626     const heim_octet_string *key_data = heim_data_get_data(key);
   1627     int ret;
   1628 
   1629     if (error)
   1630 	*error = NULL;
   1631 
   1632     if (strnlen(key_data->data, key_data->length) != key_data->length)
   1633 	return HEIM_ERROR(error, EINVAL,
   1634 			  (EINVAL,
   1635 			   N_("JSON DB requires keys that are actually strings",
   1636 			      "")));
   1637 
   1638     key_string = heim_string_create_with_bytes(key_data->data,
   1639 					       key_data->length);
   1640     if (key_string == NULL)
   1641 	return HEIM_ENOMEM(error);
   1642 
   1643     if (table == NULL)
   1644 	table = HSTR("");
   1645 
   1646     ret = heim_path_create(jsondb->dict, 29, value, error, table, key_string, NULL);
   1647     heim_release(key_string);
   1648     return ret;
   1649 }
   1650 
   1651 static int
   1652 json_db_del_key(void *db, heim_string_t table, heim_data_t key,
   1653 		heim_error_t *error)
   1654 {
   1655     json_db_t jsondb = db;
   1656     heim_string_t key_string;
   1657     const heim_octet_string *key_data = heim_data_get_data(key);
   1658 
   1659     if (error)
   1660 	*error = NULL;
   1661 
   1662     if (strnlen(key_data->data, key_data->length) != key_data->length)
   1663 	return HEIM_ERROR(error, EINVAL,
   1664 			  (EINVAL,
   1665 			   N_("JSON DB requires keys that are actually strings",
   1666 			      "")));
   1667 
   1668     key_string = heim_string_create_with_bytes(key_data->data,
   1669 					       key_data->length);
   1670     if (key_string == NULL)
   1671 	return HEIM_ENOMEM(error);
   1672 
   1673     if (table == NULL)
   1674 	table = HSTR("");
   1675 
   1676     heim_path_delete(jsondb->dict, error, table, key_string, NULL);
   1677     heim_release(key_string);
   1678     return 0;
   1679 }
   1680 
   1681 struct json_db_iter_ctx {
   1682     heim_db_iterator_f_t        iter_f;
   1683     void                        *iter_ctx;
   1684 };
   1685 
   1686 static void json_db_iter_f(heim_object_t key, heim_object_t value, void *arg)
   1687 {
   1688     struct json_db_iter_ctx *ctx = arg;
   1689     const char *key_string;
   1690     heim_data_t key_data;
   1691 
   1692     key_string = heim_string_get_utf8((heim_string_t)key);
   1693     key_data = heim_data_ref_create(key_string, strlen(key_string), NULL);
   1694     ctx->iter_f(key_data, (heim_object_t)value, ctx->iter_ctx);
   1695     heim_release(key_data);
   1696 }
   1697 
   1698 static void
   1699 json_db_iter(void *db, heim_string_t table, void *iter_data,
   1700 	     heim_db_iterator_f_t iter_f, heim_error_t *error)
   1701 {
   1702     json_db_t jsondb = db;
   1703     struct json_db_iter_ctx ctx;
   1704     heim_dict_t table_dict;
   1705 
   1706     if (error)
   1707 	*error = NULL;
   1708 
   1709     if (table == NULL)
   1710 	table = HSTR("");
   1711 
   1712     table_dict = heim_dict_get_value(jsondb->dict, table);
   1713     if (table_dict == NULL)
   1714 	return;
   1715 
   1716     ctx.iter_ctx = iter_data;
   1717     ctx.iter_f = iter_f;
   1718 
   1719     heim_dict_iterate_f(table_dict, &ctx, json_db_iter_f);
   1720 }
   1721 
   1722 static struct heim_db_type json_dbt = {
   1723     1, json_db_open, NULL, json_db_close,
   1724     json_db_lock, json_db_unlock, json_db_sync,
   1725     NULL, NULL, NULL,
   1726     json_db_copy_value, json_db_set_value,
   1727     json_db_del_key, json_db_iter
   1728 };
   1729 
   1730