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