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