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