LCOV - code coverage report
Current view: top level - third_party/heimdal/lib/base - db.c (source / functions) Hit Total Coverage
Test: coverage report for master 98b443d9 Lines: 0 856 0.0 %
Date: 2024-05-31 13:13:24 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 "config.h"
      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 <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 HEIM_CALLCONV 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           0 : db_init_plugins_once(void *arg)
     151             : {
     152           0 :     db_plugins = heim_retain(arg);
     153           0 : }
     154             : 
     155             : static void HEIM_CALLCONV
     156           0 : plugin_dealloc(void *arg)
     157             : {
     158           0 :     db_plugin plug = arg;
     159             : 
     160           0 :     heim_release(plug->name);
     161           0 : }
     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           0 : heim_db_register(const char *dbtype,
     188             :                  void *data,
     189             :                  struct heim_db_type *plugin)
     190             : {
     191           0 :     heim_dict_t plugins;
     192           0 :     heim_string_t s;
     193           0 :     db_plugin plug, plug2;
     194           0 :     int ret = 0;
     195             : 
     196           0 :     if ((plugin->beginf != NULL && plugin->commitf == NULL) ||
     197           0 :         (plugin->beginf != NULL && plugin->rollbackf == NULL) ||
     198           0 :         (plugin->lockf != NULL && plugin->unlockf == NULL) ||
     199           0 :         plugin->copyf == NULL)
     200           0 :         heim_abort("Invalid DB plugin; make sure methods are paired");
     201             : 
     202             :     /* Initialize */
     203           0 :     plugins = heim_dict_create(11);
     204           0 :     if (plugins == NULL)
     205           0 :         return ENOMEM;
     206           0 :     heim_base_once_f(&db_plugin_init_once, plugins, db_init_plugins_once);
     207           0 :     heim_release(plugins);
     208           0 :     heim_assert(db_plugins != NULL, "heim_db plugin table initialized");
     209             : 
     210           0 :     s = heim_string_create(dbtype);
     211           0 :     if (s == NULL)
     212           0 :         return ENOMEM;
     213             : 
     214           0 :     plug = heim_alloc(sizeof (*plug), "db_plug", plugin_dealloc);
     215           0 :     if (plug == NULL) {
     216           0 :         heim_release(s);
     217           0 :         return ENOMEM;
     218             :     }
     219             : 
     220           0 :     plug->name = heim_retain(s);
     221           0 :     plug->openf = plugin->openf;
     222           0 :     plug->clonef = plugin->clonef;
     223           0 :     plug->closef = plugin->closef;
     224           0 :     plug->lockf = plugin->lockf;
     225           0 :     plug->unlockf = plugin->unlockf;
     226           0 :     plug->syncf = plugin->syncf;
     227           0 :     plug->beginf = plugin->beginf;
     228           0 :     plug->commitf = plugin->commitf;
     229           0 :     plug->rollbackf = plugin->rollbackf;
     230           0 :     plug->copyf = plugin->copyf;
     231           0 :     plug->setf = plugin->setf;
     232           0 :     plug->delf = plugin->delf;
     233           0 :     plug->iterf = plugin->iterf;
     234           0 :     plug->data = data;
     235             : 
     236           0 :     HEIMDAL_MUTEX_lock(&db_type_mutex);
     237           0 :     plug2 = heim_dict_get_value(db_plugins, s);
     238           0 :     if (plug2 == NULL)
     239           0 :         ret = heim_dict_set_value(db_plugins, s, plug);
     240           0 :     HEIMDAL_MUTEX_unlock(&db_type_mutex);
     241           0 :     heim_release(plug);
     242           0 :     heim_release(s);
     243             : 
     244           0 :     return ret;
     245             : }
     246             : 
     247             : static void HEIM_CALLCONV
     248           0 : db_dealloc(void *arg)
     249             : {
     250           0 :     heim_db_t db = arg;
     251           0 :     heim_assert(!db->in_transaction,
     252             :                 "rollback or commit heim_db_t before releasing it");
     253           0 :     if (db->db_data)
     254           0 :         (void) db->plug->closef(db->db_data, NULL);
     255           0 :     heim_release(db->to_release);
     256           0 :     heim_release(db->dbtype);
     257           0 :     heim_release(db->dbname);
     258           0 :     heim_release(db->options);
     259           0 :     heim_release(db->set_keys);
     260           0 :     heim_release(db->del_keys);
     261           0 :     heim_release(db->error);
     262           0 : }
     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           0 : dbtype_iter2create_f(heim_object_t dbtype, heim_object_t junk, void *arg)
     279             : {
     280           0 :     struct dbtype_iter *iter_ctx = arg;
     281             : 
     282           0 :     if (iter_ctx->db != NULL)
     283           0 :         return;
     284           0 :     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           0 : heim_db_create(const char *dbtype, const char *dbname,
     319             :                heim_dict_t options, heim_error_t *error)
     320             : {
     321           0 :     heim_string_t s;
     322           0 :     char *p;
     323           0 :     db_plugin plug;
     324           0 :     heim_db_t db;
     325           0 :     int ret = 0;
     326             : 
     327           0 :     if (options == NULL) {
     328           0 :         options = heim_dict_create(11);
     329           0 :         if (options == NULL) {
     330           0 :             if (error)
     331           0 :                 *error = heim_error_create_enomem();
     332           0 :             return NULL;
     333             :         }
     334             :     } else {
     335           0 :         (void) heim_retain(options);
     336             :     }
     337             : 
     338           0 :     if (db_plugins == NULL) {
     339           0 :         heim_release(options);
     340           0 :         return NULL;
     341             :     }
     342             : 
     343           0 :     if (dbtype == NULL || *dbtype == '\0') {
     344           0 :         struct dbtype_iter iter_ctx = { NULL, dbname, options, error};
     345             : 
     346             :         /* Try all dbtypes */
     347           0 :         heim_dict_iterate_f(db_plugins, &iter_ctx, dbtype_iter2create_f);
     348           0 :         heim_release(options);
     349           0 :         return iter_ctx.db;
     350           0 :     } else if (strstr(dbtype, "json")) {
     351           0 :         (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           0 :     p = strchr(dbtype, '+');
     360           0 :     if (p != NULL)
     361           0 :         s = heim_string_create_with_bytes(dbtype, p - dbtype);
     362             :     else
     363           0 :         s = heim_string_create(dbtype);
     364           0 :     if (s == NULL) {
     365           0 :         heim_release(options);
     366           0 :         return NULL;
     367             :     }
     368             : 
     369           0 :     HEIMDAL_MUTEX_lock(&db_type_mutex);
     370           0 :     plug = heim_dict_get_value(db_plugins, s);
     371           0 :     HEIMDAL_MUTEX_unlock(&db_type_mutex);
     372           0 :     heim_release(s);
     373           0 :     if (plug == NULL) {
     374           0 :         if (error)
     375           0 :             *error = heim_error_create(ENOENT,
     376           0 :                                        N_("Heimdal DB plugin not found: %s", ""),
     377             :                                        dbtype);
     378           0 :         heim_release(options);
     379           0 :         return NULL;
     380             :     }
     381             : 
     382           0 :     db = _heim_alloc_object(&db_object, sizeof(*db));
     383           0 :     if (db == NULL) {
     384           0 :         heim_release(options);
     385           0 :         return NULL;
     386             :     }
     387             : 
     388           0 :     db->in_transaction = 0;
     389           0 :     db->ro_tx = 0;
     390           0 :     db->set_keys = NULL;
     391           0 :     db->del_keys = NULL;
     392           0 :     db->plug = plug;
     393           0 :     db->options = options;
     394             : 
     395           0 :     ret = plug->openf(plug->data, dbtype, dbname, options, &db->db_data, error);
     396           0 :     if (ret) {
     397           0 :         heim_release(db);
     398           0 :         if (error && *error == NULL)
     399           0 :             *error = heim_error_create(ENOENT,
     400           0 :                                        N_("Heimdal DB could not be opened: %s", ""),
     401             :                                        dbname);
     402           0 :         return NULL;
     403             :     }
     404             : 
     405           0 :     ret = db_replay_log(db, error);
     406           0 :     if (ret) {
     407           0 :         heim_release(db);
     408           0 :         return NULL;
     409             :     }
     410             : 
     411           0 :     if (plug->clonef == NULL) {
     412           0 :         db->dbtype = heim_string_create(dbtype);
     413           0 :         db->dbname = heim_string_create(dbname);
     414             : 
     415           0 :         if (!db->dbtype || ! db->dbname) {
     416           0 :             heim_release(db);
     417           0 :             if (error)
     418           0 :                 *error = heim_error_create_enomem();
     419           0 :             return NULL;
     420             :         }
     421             :     }
     422             : 
     423           0 :     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           0 : heim_db_clone(heim_db_t db, heim_error_t *error)
     443             : {
     444           0 :     heim_db_t result;
     445           0 :     int ret;
     446             : 
     447           0 :     if (heim_get_tid(db) != HEIM_TID_DB)
     448           0 :         heim_abort("Expected a database");
     449           0 :     if (db->in_transaction)
     450           0 :         heim_abort("DB handle is busy");
     451             : 
     452           0 :     if (db->plug->clonef == NULL) {
     453           0 :         return heim_db_create(heim_string_get_utf8(db->dbtype),
     454             :                               heim_string_get_utf8(db->dbname),
     455             :                               db->options, error);
     456             :     }
     457             : 
     458           0 :     result = _heim_alloc_object(&db_object, sizeof(*result));
     459           0 :     if (result == NULL) {
     460           0 :         if (error)
     461           0 :             *error = heim_error_create_enomem();
     462           0 :         return NULL;
     463             :     }
     464             : 
     465           0 :     result->set_keys = NULL;
     466           0 :     result->del_keys = NULL;
     467           0 :     ret = db->plug->clonef(db->db_data, &result->db_data, error);
     468           0 :     if (ret) {
     469           0 :         heim_release(result);
     470           0 :         if (error && !*error)
     471           0 :             *error = heim_error_create(ENOENT,
     472           0 :                                        N_("Could not re-open DB while cloning", ""));
     473           0 :         return NULL;
     474             :     }
     475           0 :     db->db_data = NULL;
     476           0 :     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           0 : heim_db_begin(heim_db_t db, int read_only, heim_error_t *error)
     491             : {
     492           0 :     int ret;
     493             : 
     494           0 :     if (heim_get_tid(db) != HEIM_TID_DB)
     495           0 :         return EINVAL;
     496             : 
     497           0 :     if (db->in_transaction && (read_only || !db->ro_tx || (!read_only && !db->ro_tx)))
     498           0 :         heim_abort("DB already in transaction");
     499             : 
     500           0 :     if (db->plug->setf == NULL || db->plug->delf == NULL)
     501           0 :         return EINVAL;
     502             : 
     503           0 :     if (db->plug->beginf) {
     504           0 :         ret = db->plug->beginf(db->db_data, read_only, error);
     505           0 :         if (ret)
     506           0 :             return ret;
     507           0 :     } else if (!db->in_transaction) {
     508             :         /* Try to emulate transactions */
     509             : 
     510           0 :         if (db->plug->lockf == NULL)
     511           0 :             return EINVAL; /* can't lock? -> no transactions */
     512             : 
     513             :         /* Assume unlock provides sync/durability */
     514           0 :         ret = db->plug->lockf(db->db_data, read_only, error);
     515           0 :         if (ret)
     516           0 :             return ret;
     517             : 
     518           0 :         ret = db_replay_log(db, error);
     519           0 :         if (ret) {
     520           0 :             ret = db->plug->unlockf(db->db_data, error);
     521           0 :             return ret;
     522             :         }
     523             : 
     524           0 :         db->set_keys = heim_dict_create(11);
     525           0 :         if (db->set_keys == NULL)
     526           0 :             return ENOMEM;
     527           0 :         db->del_keys = heim_dict_create(11);
     528           0 :         if (db->del_keys == NULL) {
     529           0 :             heim_release(db->set_keys);
     530           0 :             db->set_keys = NULL;
     531           0 :             return ENOMEM;
     532             :         }
     533             :     } else {
     534           0 :         heim_assert(read_only == 0, "Internal error");
     535           0 :         ret = db->plug->lockf(db->db_data, 0, error);
     536           0 :         if (ret)
     537           0 :             return ret;
     538             :     }
     539           0 :     db->in_transaction = 1;
     540           0 :     db->ro_tx = !!read_only;
     541           0 :     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           0 : heim_db_commit(heim_db_t db, heim_error_t *error)
     556             : {
     557           0 :     int ret, ret2;
     558           0 :     heim_string_t journal_fname = NULL;
     559             : 
     560           0 :     if (heim_get_tid(db) != HEIM_TID_DB)
     561           0 :         return EINVAL;
     562           0 :     if (!db->in_transaction)
     563           0 :         return 0;
     564           0 :     if (db->plug->commitf == NULL && db->plug->lockf == NULL)
     565           0 :         return EINVAL;
     566             : 
     567           0 :     if (db->plug->commitf != NULL) {
     568           0 :         ret = db->plug->commitf(db->db_data, error);
     569           0 :         if (ret)
     570           0 :             (void) db->plug->rollbackf(db->db_data, error);
     571             : 
     572           0 :         db->in_transaction = 0;
     573           0 :         db->ro_tx = 0;
     574           0 :         return ret;
     575             :     }
     576             : 
     577           0 :     if (db->ro_tx) {
     578           0 :         ret = 0;
     579           0 :         goto done;
     580             :     }
     581             : 
     582           0 :     if (db->options)
     583           0 :         journal_fname = heim_dict_get_value(db->options, HSTR("journal-filename"));
     584             : 
     585           0 :     if (journal_fname != NULL) {
     586           0 :         heim_array_t a;
     587           0 :         heim_string_t journal_contents;
     588           0 :         size_t len, bytes;
     589           0 :         int save_errno;
     590             : 
     591             :         /* Create contents for replay log */
     592           0 :         ret = ENOMEM;
     593           0 :         a = heim_array_create();
     594           0 :         if (a == NULL)
     595           0 :             goto err;
     596           0 :         ret = heim_array_append_value(a, db->set_keys);
     597           0 :         if (ret) {
     598           0 :             heim_release(a);
     599           0 :             goto err;
     600             :         }
     601           0 :         ret = heim_array_append_value(a, db->del_keys);
     602           0 :         if (ret) {
     603           0 :             heim_release(a);
     604           0 :             goto err;
     605             :         }
     606           0 :         journal_contents = heim_json_copy_serialize(a, 0, error);
     607           0 :         heim_release(a);
     608             : 
     609             :         /* Write replay log */
     610           0 :         if (journal_fname != NULL) {
     611           0 :             int fd;
     612             : 
     613           0 :             ret = open_file(heim_string_get_utf8(journal_fname), 1, 0, &fd, error);
     614           0 :             if (ret) {
     615           0 :                 heim_release(journal_contents);
     616           0 :                 goto err;
     617             :             }
     618           0 :             len = strlen(heim_string_get_utf8(journal_contents));
     619           0 :             bytes = write(fd, heim_string_get_utf8(journal_contents), len);
     620           0 :             save_errno = errno;
     621           0 :             heim_release(journal_contents);
     622           0 :             ret = close(fd);
     623           0 :             if (bytes != len) {
     624             :                 /* Truncate replay log */
     625           0 :                 (void) open_file(heim_string_get_utf8(journal_fname), 1, 0, NULL, error);
     626           0 :                 ret = save_errno;
     627           0 :                 goto err;
     628             :             }
     629           0 :             if (ret)
     630           0 :                 goto err;
     631             :         }
     632             :     }
     633             : 
     634             :     /* Apply logged actions */
     635           0 :     ret = db_do_log_actions(db, error);
     636           0 :     if (ret)
     637           0 :         return ret;
     638             : 
     639           0 :     if (db->plug->syncf != NULL) {
     640             :         /* fsync() or whatever */
     641           0 :         ret = db->plug->syncf(db->db_data, error);
     642           0 :         if (ret)
     643           0 :             return ret;
     644             :     }
     645             : 
     646             :     /* Truncate replay log and we're done */
     647           0 :     if (journal_fname != NULL) {
     648           0 :         int fd;
     649             : 
     650           0 :         ret2 = open_file(heim_string_get_utf8(journal_fname), 1, 0, &fd, error);
     651           0 :         if (ret2 == 0)
     652           0 :             (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           0 : done:
     660           0 :     heim_release(db->set_keys);
     661           0 :     heim_release(db->del_keys);
     662           0 :     db->set_keys = NULL;
     663           0 :     db->del_keys = NULL;
     664           0 :     db->in_transaction = 0;
     665           0 :     db->ro_tx = 0;
     666             : 
     667           0 :     ret2 = db->plug->unlockf(db->db_data, error);
     668           0 :     if (ret == 0)
     669           0 :         ret = ret2;
     670             : 
     671           0 :     return ret;
     672             : 
     673           0 : err:
     674           0 :     return HEIM_ERROR(error, ret,
     675             :                       (ret, N_("Error while committing transaction: %s", ""),
     676           0 :                        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           0 : heim_db_rollback(heim_db_t db, heim_error_t *error)
     691             : {
     692           0 :     int ret = 0;
     693             : 
     694           0 :     if (heim_get_tid(db) != HEIM_TID_DB)
     695           0 :         return EINVAL;
     696           0 :     if (!db->in_transaction)
     697           0 :         return 0;
     698             : 
     699           0 :     if (db->plug->rollbackf != NULL)
     700           0 :         ret = db->plug->rollbackf(db->db_data, error);
     701           0 :     else if (db->plug->unlockf != NULL)
     702           0 :         ret = db->plug->unlockf(db->db_data, error);
     703             : 
     704           0 :     heim_release(db->set_keys);
     705           0 :     heim_release(db->del_keys);
     706           0 :     db->set_keys = NULL;
     707           0 :     db->del_keys = NULL;
     708           0 :     db->in_transaction = 0;
     709           0 :     db->ro_tx = 0;
     710             : 
     711           0 :     return ret;
     712             : }
     713             : 
     714             : /**
     715             :  * Get type ID of heim_db_t objects.
     716             :  *
     717             :  * @addtogroup heimbase
     718             :  */
     719             : heim_tid_t
     720           0 : heim_db_get_type_id(void)
     721             : {
     722           0 :     return HEIM_TID_DB;
     723             : }
     724             : 
     725             : heim_data_t
     726           0 : _heim_db_get_value(heim_db_t db, heim_string_t table, heim_data_t key,
     727             :                    heim_error_t *error)
     728             : {
     729           0 :     heim_release(db->to_release);
     730           0 :     db->to_release = heim_db_copy_value(db, table, key, error);
     731           0 :     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           0 : heim_db_copy_value(heim_db_t db, heim_string_t table, heim_data_t key,
     750             :                    heim_error_t *error)
     751             : {
     752           0 :     heim_object_t v;
     753           0 :     heim_data_t result;
     754             : 
     755           0 :     if (heim_get_tid(db) != HEIM_TID_DB)
     756           0 :         return NULL;
     757             : 
     758           0 :     if (error != NULL)
     759           0 :         *error = NULL;
     760             : 
     761           0 :     if (table == NULL)
     762           0 :         table = HSTR("");
     763             : 
     764           0 :     if (db->in_transaction) {
     765           0 :         heim_string_t key64;
     766             : 
     767           0 :         key64 = to_base64(key, error);
     768           0 :         if (key64 == NULL) {
     769           0 :             if (error)
     770           0 :                 *error = heim_error_create_enomem();
     771           0 :             return NULL;
     772             :         }
     773             : 
     774           0 :         v = heim_path_copy(db->set_keys, error, table, key64, NULL);
     775           0 :         if (v != NULL) {
     776           0 :             heim_release(key64);
     777           0 :             return v;
     778             :         }
     779           0 :         v = heim_path_copy(db->del_keys, error, table, key64, NULL); /* can't be NULL */
     780           0 :         heim_release(key64);
     781           0 :         if (v != NULL)
     782           0 :             return NULL;
     783             :     }
     784             : 
     785           0 :     result = db->plug->copyf(db->db_data, table, key, error);
     786             : 
     787           0 :     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           0 : 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           0 :     heim_string_t key64 = NULL;
     807           0 :     int ret;
     808             : 
     809           0 :     if (error != NULL)
     810           0 :         *error = NULL;
     811             : 
     812           0 :     if (table == NULL)
     813           0 :         table = HSTR("");
     814             : 
     815           0 :     if (value == NULL)
     816             :         /* Use heim_null_t instead of NULL */
     817           0 :         return heim_db_delete_key(db, table, key, error);
     818             : 
     819           0 :     if (heim_get_tid(db) != HEIM_TID_DB)
     820           0 :         return EINVAL;
     821             : 
     822           0 :     if (heim_get_tid(key) != HEIM_TID_DATA)
     823           0 :         return HEIM_ERROR(error, EINVAL,
     824           0 :                           (EINVAL, N_("DB keys must be data", "")));
     825             : 
     826           0 :     if (db->plug->setf == NULL)
     827           0 :         return EBADF;
     828             : 
     829           0 :     if (!db->in_transaction) {
     830           0 :         ret = heim_db_begin(db, 0, error);
     831           0 :         if (ret)
     832           0 :             goto err;
     833           0 :         heim_assert(db->in_transaction, "Internal error");
     834           0 :         ret = heim_db_set_value(db, table, key, value, error);
     835           0 :         if (ret) {
     836           0 :             (void) heim_db_rollback(db, NULL);
     837           0 :             return ret;
     838             :         }
     839           0 :         return heim_db_commit(db, error);
     840             :     }
     841             : 
     842             :     /* Transaction emulation */
     843           0 :     heim_assert(db->set_keys != NULL, "Internal error");
     844           0 :     key64 = to_base64(key, error);
     845           0 :     if (key64 == NULL)
     846           0 :         return HEIM_ENOMEM(error);
     847             : 
     848           0 :     if (db->ro_tx) {
     849           0 :         ret = heim_db_begin(db, 0, error);
     850           0 :         if (ret)
     851           0 :             goto err;
     852             :     }
     853           0 :     ret = heim_path_create(db->set_keys, 29, value, error, table, key64, NULL);
     854           0 :     if (ret)
     855           0 :         goto err;
     856           0 :     heim_path_delete(db->del_keys, error, table, key64, NULL);
     857           0 :     heim_release(key64);
     858             : 
     859           0 :     return 0;
     860             : 
     861           0 : err:
     862           0 :     heim_release(key64);
     863           0 :     return HEIM_ERROR(error, ret,
     864             :                       (ret, N_("Could not set a dict value while while "
     865           0 :                        "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           0 : heim_db_delete_key(heim_db_t db, heim_string_t table, heim_data_t key,
     882             :                    heim_error_t *error)
     883             : {
     884           0 :     heim_string_t key64 = NULL;
     885           0 :     int ret;
     886             : 
     887           0 :     if (error != NULL)
     888           0 :         *error = NULL;
     889             : 
     890           0 :     if (table == NULL)
     891           0 :         table = HSTR("");
     892             : 
     893           0 :     if (heim_get_tid(db) != HEIM_TID_DB)
     894           0 :         return EINVAL;
     895             : 
     896           0 :     if (db->plug->delf == NULL)
     897           0 :         return EBADF;
     898             : 
     899           0 :     if (!db->in_transaction) {
     900           0 :         ret = heim_db_begin(db, 0, error);
     901           0 :         if (ret)
     902           0 :             goto err;
     903           0 :         heim_assert(db->in_transaction, "Internal error");
     904           0 :         ret = heim_db_delete_key(db, table, key, error);
     905           0 :         if (ret) {
     906           0 :             (void) heim_db_rollback(db, NULL);
     907           0 :             return ret;
     908             :         }
     909           0 :         return heim_db_commit(db, error);
     910             :     }
     911             : 
     912             :     /* Transaction emulation */
     913           0 :     heim_assert(db->set_keys != NULL, "Internal error");
     914           0 :     key64 = to_base64(key, error);
     915           0 :     if (key64 == NULL)
     916           0 :         return HEIM_ENOMEM(error);
     917           0 :     if (db->ro_tx) {
     918           0 :         ret = heim_db_begin(db, 0, error);
     919           0 :         if (ret)
     920           0 :             goto err;
     921             :     }
     922           0 :     ret = heim_path_create(db->del_keys, 29, heim_number_create(1), error, table, key64, NULL);
     923           0 :     if (ret)
     924           0 :         goto err;
     925           0 :     heim_path_delete(db->set_keys, error, table, key64, NULL);
     926           0 :     heim_release(key64);
     927             : 
     928           0 :     return 0;
     929             : 
     930           0 : err:
     931           0 :     heim_release(key64);
     932           0 :     return HEIM_ERROR(error, ret,
     933             :                       (ret, N_("Could not set a dict value while while "
     934           0 :                        "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           0 : 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           0 :     if (error != NULL)
     952           0 :         *error = NULL;
     953             : 
     954           0 :     if (heim_get_tid(db) != HEIM_TID_DB)
     955           0 :         return;
     956             : 
     957           0 :     if (!db->in_transaction)
     958           0 :         db->plug->iterf(db->db_data, table, iter_data, iter_f, error);
     959             : }
     960             : 
     961             : static void
     962           0 : db_replay_log_table_set_keys_iter(heim_object_t key, heim_object_t value,
     963             :                                   void *arg)
     964             : {
     965           0 :     heim_db_t db = arg;
     966           0 :     heim_data_t k, v;
     967             : 
     968           0 :     if (db->ret)
     969           0 :         return;
     970             : 
     971           0 :     k = from_base64((heim_string_t)key, &db->error);
     972           0 :     if (k == NULL) {
     973           0 :         db->ret = ENOMEM;
     974           0 :         return;
     975             :     }
     976           0 :     v = (heim_data_t)value;
     977             : 
     978           0 :     db->ret = db->plug->setf(db->db_data, db->current_table, k, v, &db->error);
     979           0 :     heim_release(k);
     980             : }
     981             : 
     982             : static void
     983           0 : db_replay_log_table_del_keys_iter(heim_object_t key, heim_object_t value,
     984             :                                   void *arg)
     985             : {
     986           0 :     heim_db_t db = arg;
     987           0 :     heim_data_t k;
     988             : 
     989           0 :     if (db->ret) {
     990           0 :         db->ret = ENOMEM;
     991           0 :         return;
     992             :     }
     993             : 
     994           0 :     k = from_base64((heim_string_t)key, &db->error);
     995           0 :     if (k == NULL)
     996           0 :         return;
     997             : 
     998           0 :     db->ret = db->plug->delf(db->db_data, db->current_table, k, &db->error);
     999           0 :     heim_release(k);
    1000             : }
    1001             : 
    1002             : static void
    1003           0 : db_replay_log_set_keys_iter(heim_object_t table, heim_object_t table_dict,
    1004             :                             void *arg)
    1005             : {
    1006           0 :     heim_db_t db = arg;
    1007             : 
    1008           0 :     if (db->ret)
    1009           0 :         return;
    1010             : 
    1011           0 :     db->current_table = table;
    1012           0 :     heim_dict_iterate_f(table_dict, db, db_replay_log_table_set_keys_iter);
    1013             : }
    1014             : 
    1015             : static void
    1016           0 : db_replay_log_del_keys_iter(heim_object_t table, heim_object_t table_dict,
    1017             :                             void *arg)
    1018             : {
    1019           0 :     heim_db_t db = arg;
    1020             : 
    1021           0 :     if (db->ret)
    1022           0 :         return;
    1023             : 
    1024           0 :     db->current_table = table;
    1025           0 :     heim_dict_iterate_f(table_dict, db, db_replay_log_table_del_keys_iter);
    1026             : }
    1027             : 
    1028             : static int
    1029           0 : db_do_log_actions(heim_db_t db, heim_error_t *error)
    1030             : {
    1031           0 :     int ret;
    1032             : 
    1033           0 :     if (error)
    1034           0 :         *error = NULL;
    1035             : 
    1036           0 :     db->ret = 0;
    1037           0 :     db->error = NULL;
    1038           0 :     if (db->set_keys != NULL)
    1039           0 :         heim_dict_iterate_f(db->set_keys, db, db_replay_log_set_keys_iter);
    1040           0 :     if (db->del_keys != NULL)
    1041           0 :         heim_dict_iterate_f(db->del_keys, db, db_replay_log_del_keys_iter);
    1042             : 
    1043           0 :     ret = db->ret;
    1044           0 :     db->ret = 0;
    1045           0 :     if (error && db->error) {
    1046           0 :         *error = db->error;
    1047           0 :         db->error = NULL;
    1048             :     } else {
    1049           0 :         heim_release(db->error);
    1050           0 :         db->error = NULL;
    1051             :     }
    1052           0 :     return ret;
    1053             : }
    1054             : 
    1055             : static int
    1056           0 : db_replay_log(heim_db_t db, heim_error_t *error)
    1057             : {
    1058           0 :     int ret;
    1059           0 :     heim_string_t journal_fname = NULL;
    1060           0 :     heim_object_t journal;
    1061           0 :     size_t len;
    1062             : 
    1063           0 :     heim_assert(!db->in_transaction, "DB transaction not open");
    1064           0 :     heim_assert(db->set_keys == NULL && db->set_keys == NULL, "DB transaction not open");
    1065             : 
    1066           0 :     if (error)
    1067           0 :         *error = NULL;
    1068             : 
    1069           0 :     if (db->options == NULL)
    1070           0 :         return 0;
    1071             : 
    1072           0 :     journal_fname = heim_dict_get_value(db->options, HSTR("journal-filename"));
    1073           0 :     if (journal_fname == NULL)
    1074           0 :         return 0;
    1075             : 
    1076           0 :     ret = read_json(heim_string_get_utf8(journal_fname), &journal, error);
    1077           0 :     if (ret == ENOENT) {
    1078           0 :         heim_release(journal_fname);
    1079           0 :         return 0;
    1080             :     }
    1081           0 :     if (ret == 0 && journal == NULL) {
    1082           0 :         heim_release(journal_fname);
    1083           0 :         return 0;
    1084             :     }
    1085           0 :     if (ret != 0) {
    1086           0 :         heim_release(journal_fname);
    1087           0 :         return ret;
    1088             :     }
    1089             : 
    1090           0 :     if (heim_get_tid(journal) != HEIM_TID_ARRAY) {
    1091           0 :         heim_release(journal_fname);
    1092           0 :         return HEIM_ERROR(error, EINVAL,
    1093             :                           (ret, N_("Invalid journal contents; delete journal",
    1094           0 :                                    "")));
    1095             :     }
    1096             : 
    1097           0 :     len = heim_array_get_length(journal);
    1098             : 
    1099           0 :     if (len > 0)
    1100           0 :         db->set_keys = heim_array_get_value(journal, 0);
    1101           0 :     if (len > 1)
    1102           0 :         db->del_keys = heim_array_get_value(journal, 1);
    1103           0 :     ret = db_do_log_actions(db, error);
    1104           0 :     if (ret) {
    1105           0 :         heim_release(journal_fname);
    1106           0 :         return ret;
    1107             :     }
    1108             : 
    1109             :     /* Truncate replay log and we're done */
    1110           0 :     ret = open_file(heim_string_get_utf8(journal_fname), 1, 0, NULL, error);
    1111           0 :     heim_release(journal_fname);
    1112           0 :     if (ret)
    1113           0 :         return ret;
    1114           0 :     heim_release(db->set_keys);
    1115           0 :     heim_release(db->del_keys);
    1116           0 :     db->set_keys = NULL;
    1117           0 :     db->del_keys = NULL;
    1118             : 
    1119           0 :     return 0;
    1120             : }
    1121             : 
    1122             : static
    1123           0 : heim_string_t to_base64(heim_data_t data, heim_error_t *error)
    1124             : {
    1125           0 :     char *b64 = NULL;
    1126           0 :     heim_string_t s = NULL;
    1127           0 :     const heim_octet_string *d;
    1128           0 :     int ret;
    1129             : 
    1130           0 :     d = heim_data_get_data(data);
    1131           0 :     ret = rk_base64_encode(d->data, d->length, &b64);
    1132           0 :     if (ret < 0 || b64 == NULL)
    1133           0 :         goto enomem;
    1134           0 :     s = heim_string_ref_create(b64, free);
    1135           0 :     if (s == NULL)
    1136           0 :         goto enomem;
    1137           0 :     return s;
    1138             : 
    1139           0 : enomem:
    1140           0 :     free(b64);
    1141           0 :     if (error)
    1142           0 :         *error = heim_error_create_enomem();
    1143           0 :     return NULL;
    1144             : }
    1145             : 
    1146             : static
    1147           0 : heim_data_t from_base64(heim_string_t s, heim_error_t *error)
    1148             : {
    1149           0 :     ssize_t len = -1;
    1150           0 :     void *buf;
    1151           0 :     heim_data_t d;
    1152             : 
    1153           0 :     buf = malloc(strlen(heim_string_get_utf8(s)));
    1154           0 :     if (buf)
    1155           0 :         len = rk_base64_decode(heim_string_get_utf8(s), buf);
    1156           0 :     if (len > -1 && (d = heim_data_ref_create(buf, len, free)))
    1157           0 :         return d;
    1158           0 :     free(buf);
    1159           0 :     if (error)
    1160           0 :         *error = heim_error_create_enomem();
    1161           0 :     return NULL;
    1162             : }
    1163             : 
    1164             : 
    1165             : static int
    1166           0 : open_file(const char *dbname, int for_write, int excl, int *fd_out, heim_error_t *error)
    1167             : {
    1168             : #ifdef WIN32
    1169             :     HANDLE hFile;
    1170             :     int ret = 0;
    1171             : 
    1172             :     if (fd_out)
    1173             :         *fd_out = -1;
    1174             : 
    1175             :     if (for_write)
    1176             :         hFile = CreateFile(dbname, GENERIC_WRITE | GENERIC_READ, 0,
    1177             :                            NULL, /* we'll close as soon as we read */
    1178             :                            CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
    1179             :     else
    1180             :         hFile = CreateFile(dbname, GENERIC_READ, FILE_SHARE_READ,
    1181             :                            NULL, /* we'll close as soon as we read */
    1182             :                            OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
    1183             :     if (hFile == INVALID_HANDLE_VALUE) {
    1184             :         ret = GetLastError();
    1185             :         _set_errno(ret); /* CreateFile() does not set errno */
    1186             :         goto err;
    1187             :     }
    1188             :     if (fd_out == NULL) {
    1189             :         (void) CloseHandle(hFile);
    1190             :         return 0;
    1191             :     }
    1192             : 
    1193             :     *fd_out = _open_osfhandle((intptr_t) hFile, 0);
    1194             :     if (*fd_out < 0) {
    1195             :         ret = errno;
    1196             :         (void) CloseHandle(hFile);
    1197             :         goto err;
    1198             :     }
    1199             : 
    1200             :     /* No need to lock given share deny mode */
    1201             :     return 0;
    1202             : 
    1203             : err:
    1204             :     if (error != NULL) {
    1205             :         char *s = NULL;
    1206             :         FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ALLOCATE_BUFFER,
    1207             :                       0, ret, 0, (LPTSTR) &s, 0, NULL);
    1208             :         *error = heim_error_create(ret, N_("Could not open JSON file %s: %s", ""),
    1209             :                                    dbname, s ? s : "<error formatting error>");
    1210             :         LocalFree(s);
    1211             :     }
    1212             :     return ret;
    1213             : #else
    1214           0 :     int ret = 0;
    1215           0 :     int fd;
    1216             : 
    1217           0 :     if (fd_out)
    1218           0 :         *fd_out = -1;
    1219             : 
    1220           0 :     if (for_write && excl)
    1221           0 :         fd = open(dbname, O_CREAT | O_EXCL | O_WRONLY, 0600);
    1222           0 :     else if (for_write)
    1223           0 :         fd = open(dbname, O_CREAT | O_TRUNC | O_WRONLY, 0600);
    1224             :     else
    1225           0 :         fd = open(dbname, O_RDONLY);
    1226           0 :     if (fd < 0) {
    1227           0 :         if (error != NULL)
    1228           0 :             *error = heim_error_create(ret, N_("Could not open JSON file %s: %s", ""),
    1229           0 :                                        dbname, strerror(errno));
    1230           0 :         return errno;
    1231             :     }
    1232             : 
    1233           0 :     if (fd_out == NULL) {
    1234           0 :         (void) close(fd);
    1235           0 :         return 0;
    1236             :     }
    1237             : 
    1238           0 :     ret = flock(fd, for_write ? LOCK_EX : LOCK_SH);
    1239           0 :     if (ret == -1) {
    1240             :         /* Note that we if O_EXCL we're leaving the [lock] file around */
    1241           0 :         (void) close(fd);
    1242           0 :         return HEIM_ERROR(error, errno,
    1243             :                           (errno, N_("Could not lock JSON file %s: %s", ""),
    1244           0 :                            dbname, strerror(errno)));
    1245             :     }
    1246             : 
    1247           0 :     *fd_out = fd;
    1248             :     
    1249           0 :     return 0;
    1250             : #endif
    1251             : }
    1252             : 
    1253             : static int
    1254           0 : read_json(const char *dbname, heim_object_t *out, heim_error_t *error)
    1255             : {
    1256           0 :     struct stat st;
    1257           0 :     char *str = NULL;
    1258           0 :     int ret;
    1259           0 :     int fd = -1;
    1260           0 :     ssize_t bytes;
    1261             : 
    1262           0 :     *out = NULL;
    1263           0 :     ret = open_file(dbname, 0, 0, &fd, error);
    1264           0 :     if (ret)
    1265           0 :         return ret;
    1266             : 
    1267           0 :     ret = fstat(fd, &st);
    1268           0 :     if (ret == -1) {
    1269           0 :         (void) close(fd);
    1270           0 :         return HEIM_ERROR(error, errno,
    1271             :                           (ret, N_("Could not stat JSON DB %s: %s", ""),
    1272           0 :                            dbname, strerror(errno)));
    1273             :     }
    1274             : 
    1275           0 :     if (st.st_size == 0) {
    1276           0 :         (void) close(fd);
    1277           0 :         return 0;
    1278             :     }
    1279             : 
    1280           0 :     str = malloc(st.st_size + 1);
    1281           0 :     if (str == NULL) {
    1282           0 :          (void) close(fd);
    1283           0 :         return HEIM_ENOMEM(error);
    1284             :     }
    1285             : 
    1286           0 :     bytes = read(fd, str, st.st_size);
    1287           0 :      (void) close(fd);
    1288           0 :     if (bytes != st.st_size) {
    1289           0 :         free(str);
    1290           0 :         if (bytes >= 0)
    1291           0 :             errno = EINVAL; /* ?? */
    1292           0 :         return HEIM_ERROR(error, errno,
    1293             :                           (ret, N_("Could not read JSON DB %s: %s", ""),
    1294           0 :                            dbname, strerror(errno)));
    1295             :     }
    1296           0 :     str[st.st_size] = '\0';
    1297           0 :     *out = heim_json_create(str, 10, 0, error);
    1298           0 :     free(str);
    1299           0 :     if (*out == NULL)
    1300           0 :         return (error && *error) ? heim_error_get_code(*error) : EINVAL;
    1301           0 :     return 0;
    1302             : }
    1303             : 
    1304             : typedef struct json_db {
    1305             :     heim_dict_t dict;
    1306             :     heim_string_t dbname;
    1307             :     heim_string_t bkpname;
    1308             :     int fd;
    1309             :     time_t last_read_time;
    1310             :     unsigned int read_only:1;
    1311             :     unsigned int locked:1;
    1312             :     unsigned int locked_needs_unlink:1;
    1313             : } *json_db_t;
    1314             : 
    1315             : static int
    1316           0 : json_db_open(void *plug, const char *dbtype, const char *dbname,
    1317             :              heim_dict_t options, void **db, heim_error_t *error)
    1318             : {
    1319           0 :     json_db_t jsondb;
    1320           0 :     heim_dict_t contents = NULL;
    1321           0 :     heim_string_t dbname_s = NULL;
    1322           0 :     heim_string_t bkpname_s = NULL;
    1323             : 
    1324           0 :     if (error)
    1325           0 :         *error = NULL;
    1326           0 :     if (dbtype && *dbtype && strcmp(dbtype, "json") != 0)
    1327           0 :         return HEIM_ERROR(error, EINVAL, (EINVAL, N_("Wrong DB type", "")));
    1328           0 :     if (dbname && *dbname && strcmp(dbname, "MEMORY") != 0) {
    1329           0 :         char *ext = strrchr(dbname, '.');
    1330           0 :         char *bkpname;
    1331           0 :         size_t len;
    1332           0 :         int ret;
    1333             : 
    1334           0 :         if (ext == NULL || strcmp(ext, ".json") != 0)
    1335           0 :             return HEIM_ERROR(error, EINVAL,
    1336             :                               (EINVAL, N_("JSON DB files must end in .json",
    1337           0 :                                           "")));
    1338             : 
    1339           0 :         if (options) {
    1340           0 :             heim_object_t vc, ve, vt;
    1341             : 
    1342           0 :             vc = heim_dict_get_value(options, HSTR("create"));
    1343           0 :             ve = heim_dict_get_value(options, HSTR("exclusive"));
    1344           0 :             vt = heim_dict_get_value(options, HSTR("truncate"));
    1345           0 :             if (vc && vt) {
    1346           0 :                 ret = open_file(dbname, 1, ve ? 1 : 0, NULL, error);
    1347           0 :                 if (ret)
    1348           0 :                     return ret;
    1349           0 :             } else if (vc || ve || vt) {
    1350           0 :                 return HEIM_ERROR(error, EINVAL,
    1351             :                                   (EINVAL, N_("Invalid JSON DB open options",
    1352           0 :                                               "")));
    1353             :             }
    1354             :             /*
    1355             :              * We don't want cloned handles to truncate the DB, eh?
    1356             :              *
    1357             :              * We should really just create a copy of the options dict
    1358             :              * rather than modify the caller's!  But for that it'd be
    1359             :              * nicer to have copy utilities in heimbase, something like
    1360             :              * this:
    1361             :              *
    1362             :              * heim_object_t heim_copy(heim_object_t src, int depth,
    1363             :              *                         heim_error_t *error);
    1364             :              * 
    1365             :              * so that options = heim_copy(options, 1); means copy the
    1366             :              * dict but nothing else (whereas depth == 0 would mean
    1367             :              * heim_retain(), and depth > 1 would be copy that many
    1368             :              * levels).
    1369             :              */
    1370           0 :             heim_dict_delete_key(options, HSTR("create"));
    1371           0 :             heim_dict_delete_key(options, HSTR("exclusive"));
    1372           0 :             heim_dict_delete_key(options, HSTR("truncate"));
    1373             :         }
    1374           0 :         dbname_s = heim_string_create(dbname);
    1375           0 :         if (dbname_s == NULL)
    1376           0 :             return HEIM_ENOMEM(error);
    1377             :         
    1378           0 :         len = snprintf(NULL, 0, "%s~", dbname);
    1379           0 :         bkpname = malloc(len + 2);
    1380           0 :         if (bkpname == NULL) {
    1381           0 :             heim_release(dbname_s);
    1382           0 :             return HEIM_ENOMEM(error);
    1383             :         }
    1384           0 :         (void) snprintf(bkpname, len + 1, "%s~", dbname);
    1385           0 :         bkpname_s = heim_string_create(bkpname);
    1386           0 :         free(bkpname);
    1387           0 :         if (bkpname_s == NULL) {
    1388           0 :             heim_release(dbname_s);
    1389           0 :             return HEIM_ENOMEM(error);
    1390             :         }
    1391             : 
    1392           0 :         ret = read_json(dbname, (heim_object_t *)&contents, error);
    1393           0 :         if (ret) {
    1394           0 :             heim_release(bkpname_s);
    1395           0 :             heim_release(dbname_s);
    1396           0 :             return ret;
    1397             :         }
    1398             : 
    1399           0 :         if (contents != NULL && heim_get_tid(contents) != HEIM_TID_DICT) {
    1400           0 :             heim_release(bkpname_s);
    1401           0 :             heim_release(dbname_s);
    1402           0 :             return HEIM_ERROR(error, EINVAL,
    1403             :                               (EINVAL, N_("JSON DB contents not valid JSON",
    1404           0 :                                           "")));
    1405             :         }
    1406             :     }
    1407             : 
    1408           0 :     jsondb = heim_alloc(sizeof (*jsondb), "json_db", NULL);
    1409           0 :     if (jsondb == NULL) {
    1410           0 :         heim_release(contents);
    1411           0 :         heim_release(dbname_s);
    1412           0 :         heim_release(bkpname_s);
    1413           0 :         return ENOMEM;
    1414             :     }
    1415             : 
    1416           0 :     jsondb->last_read_time = time(NULL);
    1417           0 :     jsondb->fd = -1;
    1418           0 :     jsondb->dbname = dbname_s;
    1419           0 :     jsondb->bkpname = bkpname_s;
    1420           0 :     jsondb->read_only = 0;
    1421             : 
    1422           0 :     if (contents != NULL)
    1423           0 :         jsondb->dict = contents;
    1424             :     else {
    1425           0 :         jsondb->dict = heim_dict_create(29);
    1426           0 :         if (jsondb->dict == NULL) {
    1427           0 :             heim_release(jsondb);
    1428           0 :             return ENOMEM;
    1429             :         }
    1430             :     }
    1431             : 
    1432           0 :     *db = jsondb;
    1433           0 :     return 0;
    1434             : }
    1435             : 
    1436             : static int
    1437           0 : json_db_close(void *db, heim_error_t *error)
    1438             : {
    1439           0 :     json_db_t jsondb = db;
    1440             : 
    1441           0 :     if (error)
    1442           0 :         *error = NULL;
    1443           0 :     if (jsondb->fd > -1)
    1444           0 :         (void) close(jsondb->fd);
    1445           0 :     jsondb->fd = -1;
    1446           0 :     heim_release(jsondb->dbname);
    1447           0 :     heim_release(jsondb->bkpname);
    1448           0 :     heim_release(jsondb->dict);
    1449           0 :     heim_release(jsondb);
    1450           0 :     return 0;
    1451             : }
    1452             : 
    1453             : static int
    1454           0 : json_db_lock(void *db, int read_only, heim_error_t *error)
    1455             : {
    1456           0 :     json_db_t jsondb = db;
    1457           0 :     int ret;
    1458             : 
    1459           0 :     heim_assert(jsondb->fd == -1 || (jsondb->read_only && !read_only),
    1460             :                 "DB locks are not recursive");
    1461             : 
    1462           0 :     jsondb->read_only = read_only ? 1 : 0;
    1463           0 :     if (jsondb->fd > -1)
    1464           0 :         return 0;
    1465             : 
    1466           0 :     ret = open_file(heim_string_get_utf8(jsondb->bkpname), 1, 1, &jsondb->fd, error);
    1467           0 :     if (ret == 0) {
    1468           0 :         jsondb->locked_needs_unlink = 1;
    1469           0 :         jsondb->locked = 1;
    1470             :     }
    1471           0 :     return ret;
    1472             : }
    1473             : 
    1474             : static int
    1475           0 : json_db_unlock(void *db, heim_error_t *error)
    1476             : {
    1477           0 :     json_db_t jsondb = db;
    1478           0 :     int ret = 0;
    1479             : 
    1480           0 :     heim_assert(jsondb->locked, "DB not locked when unlock attempted");
    1481           0 :     if (jsondb->fd > -1)
    1482           0 :         ret = close(jsondb->fd);
    1483           0 :     jsondb->fd = -1;
    1484           0 :     jsondb->read_only = 0;
    1485           0 :     jsondb->locked = 0;
    1486           0 :     if (jsondb->locked_needs_unlink)
    1487           0 :         unlink(heim_string_get_utf8(jsondb->bkpname));
    1488           0 :     jsondb->locked_needs_unlink = 0;
    1489           0 :     return ret;
    1490             : }
    1491             : 
    1492             : static int
    1493           0 : json_db_sync(void *db, heim_error_t *error)
    1494             : {
    1495           0 :     json_db_t jsondb = db;
    1496           0 :     size_t len, bytes;
    1497           0 :     heim_error_t e;
    1498           0 :     heim_string_t json;
    1499           0 :     const char *json_text = NULL;
    1500           0 :     int ret = 0;
    1501           0 :     int fd = -1;
    1502             : #ifdef WIN32
    1503             :     int tries = 3;
    1504             : #endif
    1505             : 
    1506           0 :     heim_assert(jsondb->fd > -1, "DB not locked when sync attempted");
    1507             : 
    1508           0 :     json = heim_json_copy_serialize(jsondb->dict, 0, &e);
    1509           0 :     if (json == NULL) {
    1510           0 :         ret = heim_error_get_code(e);
    1511           0 :         if (error)
    1512           0 :             *error = e;
    1513             :         else
    1514           0 :             heim_release(e);
    1515           0 :         return ret;
    1516             :     }
    1517             : 
    1518           0 :     json_text = heim_string_get_utf8(json);
    1519           0 :     len = strlen(json_text);
    1520           0 :     errno = 0;
    1521             : 
    1522             : #ifdef WIN32
    1523             :     while (tries--) {
    1524             :         ret = open_file(heim_string_get_utf8(jsondb->dbname), 1, 0, &fd, error);
    1525             :         if (ret == 0)
    1526             :             break;
    1527             :         sleep(1);
    1528             :     }
    1529             :     if (ret) {
    1530             :         heim_release(json);
    1531             :         return ret;
    1532             :     }
    1533             : #else
    1534           0 :     fd = jsondb->fd;
    1535             : #endif /* WIN32 */
    1536             : 
    1537           0 :     bytes = write(fd, json_text, len);
    1538           0 :     heim_release(json);
    1539           0 :     if (bytes != len)
    1540           0 :         return errno ? errno : EIO;
    1541           0 :     ret = fsync(fd);
    1542           0 :     if (ret)
    1543           0 :         return ret;
    1544             : 
    1545             : #ifdef WIN32
    1546             :     ret = close(fd);
    1547             :     if (ret)
    1548             :         return GetLastError();
    1549             : #else
    1550           0 :     ret = rename(heim_string_get_utf8(jsondb->bkpname), heim_string_get_utf8(jsondb->dbname));
    1551           0 :     if (ret == 0) {
    1552           0 :         jsondb->locked_needs_unlink = 0;
    1553           0 :         return 0;
    1554             :     }
    1555             : #endif /* WIN32 */
    1556             : 
    1557           0 :     return errno;
    1558             : }
    1559             : 
    1560             : static heim_data_t
    1561           0 : json_db_copy_value(void *db, heim_string_t table, heim_data_t key,
    1562             :                   heim_error_t *error)
    1563             : {
    1564           0 :     json_db_t jsondb = db;
    1565           0 :     heim_string_t key_string;
    1566           0 :     const heim_octet_string *key_data = heim_data_get_data(key);
    1567           0 :     struct stat st;
    1568           0 :     heim_data_t result;
    1569             : 
    1570           0 :     if (error)
    1571           0 :         *error = NULL;
    1572             : 
    1573           0 :     if (strnlen(key_data->data, key_data->length) != key_data->length) {
    1574           0 :         HEIM_ERROR(error, EINVAL,
    1575             :                    (EINVAL, N_("JSON DB requires keys that are actually "
    1576           0 :                                "strings", "")));
    1577           0 :         return NULL;
    1578             :     }
    1579             : 
    1580           0 :     if (stat(heim_string_get_utf8(jsondb->dbname), &st) == -1) {
    1581           0 :         HEIM_ERROR(error, errno,
    1582           0 :                    (errno, N_("Could not stat JSON DB file", "")));
    1583           0 :         return NULL;
    1584             :     }
    1585             : 
    1586           0 :     if (st.st_mtime > jsondb->last_read_time ||
    1587           0 :         st.st_ctime > jsondb->last_read_time) {
    1588           0 :         heim_dict_t contents = NULL;
    1589           0 :         int ret;
    1590             : 
    1591             :         /* Ignore file is gone (ENOENT) */
    1592           0 :         ret = read_json(heim_string_get_utf8(jsondb->dbname),
    1593             :                 (heim_object_t *)&contents, error);
    1594           0 :         if (ret)
    1595           0 :             return NULL;
    1596           0 :         if (contents == NULL)
    1597           0 :             contents = heim_dict_create(29);
    1598           0 :         heim_release(jsondb->dict);
    1599           0 :         jsondb->dict = contents;
    1600           0 :         jsondb->last_read_time = time(NULL);
    1601             :     }
    1602             : 
    1603           0 :     key_string = heim_string_create_with_bytes(key_data->data,
    1604           0 :                                                key_data->length);
    1605           0 :     if (key_string == NULL) {
    1606           0 :         (void) HEIM_ENOMEM(error);
    1607           0 :         return NULL;
    1608             :     }
    1609             : 
    1610           0 :     result = heim_path_copy(jsondb->dict, error, table, key_string, NULL);
    1611           0 :     heim_release(key_string);
    1612           0 :     return result;
    1613             : }
    1614             : 
    1615             : static int
    1616           0 : json_db_set_value(void *db, heim_string_t table,
    1617             :                   heim_data_t key, heim_data_t value, heim_error_t *error)
    1618             : {
    1619           0 :     json_db_t jsondb = db;
    1620           0 :     heim_string_t key_string;
    1621           0 :     const heim_octet_string *key_data = heim_data_get_data(key);
    1622           0 :     int ret;
    1623             : 
    1624           0 :     if (error)
    1625           0 :         *error = NULL;
    1626             : 
    1627           0 :     if (strnlen(key_data->data, key_data->length) != key_data->length)
    1628           0 :         return HEIM_ERROR(error, EINVAL,
    1629             :                           (EINVAL,
    1630             :                            N_("JSON DB requires keys that are actually strings",
    1631           0 :                               "")));
    1632             : 
    1633           0 :     key_string = heim_string_create_with_bytes(key_data->data,
    1634           0 :                                                key_data->length);
    1635           0 :     if (key_string == NULL)
    1636           0 :         return HEIM_ENOMEM(error);
    1637             : 
    1638           0 :     if (table == NULL)
    1639           0 :         table = HSTR("");
    1640             : 
    1641           0 :     ret = heim_path_create(jsondb->dict, 29, value, error, table, key_string, NULL);
    1642           0 :     heim_release(key_string);
    1643           0 :     return ret;
    1644             : }
    1645             : 
    1646             : static int
    1647           0 : json_db_del_key(void *db, heim_string_t table, heim_data_t key,
    1648             :                 heim_error_t *error)
    1649             : {
    1650           0 :     json_db_t jsondb = db;
    1651           0 :     heim_string_t key_string;
    1652           0 :     const heim_octet_string *key_data = heim_data_get_data(key);
    1653             : 
    1654           0 :     if (error)
    1655           0 :         *error = NULL;
    1656             : 
    1657           0 :     if (strnlen(key_data->data, key_data->length) != key_data->length)
    1658           0 :         return HEIM_ERROR(error, EINVAL,
    1659             :                           (EINVAL,
    1660             :                            N_("JSON DB requires keys that are actually strings",
    1661           0 :                               "")));
    1662             : 
    1663           0 :     key_string = heim_string_create_with_bytes(key_data->data,
    1664           0 :                                                key_data->length);
    1665           0 :     if (key_string == NULL)
    1666           0 :         return HEIM_ENOMEM(error);
    1667             : 
    1668           0 :     if (table == NULL)
    1669           0 :         table = HSTR("");
    1670             : 
    1671           0 :     heim_path_delete(jsondb->dict, error, table, key_string, NULL);
    1672           0 :     heim_release(key_string);
    1673           0 :     return 0;
    1674             : }
    1675             : 
    1676             : struct json_db_iter_ctx {
    1677             :     heim_db_iterator_f_t        iter_f;
    1678             :     void                        *iter_ctx;
    1679             : };
    1680             : 
    1681           0 : static void json_db_iter_f(heim_object_t key, heim_object_t value, void *arg)
    1682             : {
    1683           0 :     struct json_db_iter_ctx *ctx = arg;
    1684           0 :     const char *key_string;
    1685           0 :     heim_data_t key_data;
    1686             : 
    1687           0 :     key_string = heim_string_get_utf8((heim_string_t)key);
    1688           0 :     key_data = heim_data_ref_create(key_string, strlen(key_string), NULL);
    1689           0 :     ctx->iter_f(key_data, (heim_object_t)value, ctx->iter_ctx);
    1690           0 :     heim_release(key_data);
    1691           0 : }
    1692             : 
    1693             : static void
    1694           0 : json_db_iter(void *db, heim_string_t table, void *iter_data,
    1695             :              heim_db_iterator_f_t iter_f, heim_error_t *error)
    1696             : {
    1697           0 :     json_db_t jsondb = db;
    1698           0 :     struct json_db_iter_ctx ctx;
    1699           0 :     heim_dict_t table_dict;
    1700             : 
    1701           0 :     if (error)
    1702           0 :         *error = NULL;
    1703             : 
    1704           0 :     if (table == NULL)
    1705           0 :         table = HSTR("");
    1706             : 
    1707           0 :     table_dict = heim_dict_get_value(jsondb->dict, table);
    1708           0 :     if (table_dict == NULL)
    1709           0 :         return;
    1710             : 
    1711           0 :     ctx.iter_ctx = iter_data;
    1712           0 :     ctx.iter_f = iter_f;
    1713             : 
    1714           0 :     heim_dict_iterate_f(table_dict, &ctx, json_db_iter_f);
    1715             : }
    1716             : 
    1717             : static struct heim_db_type json_dbt = {
    1718             :     1, json_db_open, NULL, json_db_close,
    1719             :     json_db_lock, json_db_unlock, json_db_sync,
    1720             :     NULL, NULL, NULL,
    1721             :     json_db_copy_value, json_db_set_value,
    1722             :     json_db_del_key, json_db_iter
    1723             : };
    1724             : 

Generated by: LCOV version 1.14