LCOV - code coverage report
Current view: top level - third_party/heimdal/lib/base - db.c (source / functions) Hit Total Coverage
Test: coverage report for v4-17-test 1498b464 Lines: 0 776 0.0 %
Date: 2024-06-13 04:01:37 Functions: 0 36 0.0 %

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

Generated by: LCOV version 1.13