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