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