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