Coverage Report

Created: 2025-07-23 07:04

/src/samba/third_party/heimdal/lib/base/db.c
Line
Count
Source (jump to first uncovered line)
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
0
    (((ep) && !*(ep)) ? \
73
0
  heim_error_get_code((*(ep) = heim_error_create_enomem())) : ENOMEM)
74
75
#define HEIM_ERROR_HELPER(ep, ec, args) \
76
0
    (((ep) && !*(ep)) ? \
77
0
  heim_error_get_code((*(ep) = heim_error_create args)) : (ec))
78
79
#define HEIM_ERROR(ep, ec, args) \
80
0
    (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
db_init_plugins_once(void *arg)
151
0
{
152
0
    db_plugins = heim_retain(arg);
153
0
}
154
155
static void HEIM_CALLCONV
156
plugin_dealloc(void *arg)
157
0
{
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
heim_db_register(const char *dbtype,
188
     void *data,
189
     struct heim_db_type *plugin)
190
0
{
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
0
    }
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
0
}
246
247
static void HEIM_CALLCONV
248
db_dealloc(void *arg)
249
0
{
250
0
    heim_db_t db = arg;
251
0
    heim_assert(!db->in_transaction,
252
0
    "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
dbtype_iter2create_f(heim_object_t dbtype, heim_object_t junk, void *arg)
279
0
{
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
0
          iter_ctx->dbname, iter_ctx->options,
286
0
          iter_ctx->error);
287
0
}
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
0
{
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
0
  }
334
0
    } else {
335
0
  (void) heim_retain(options);
336
0
    }
337
338
0
    if (db_plugins == NULL) {
339
0
  heim_release(options);
340
0
  return NULL;
341
0
    }
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
0
    }
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
0
    else
363
0
  s = heim_string_create(dbtype);
364
0
    if (s == NULL) {
365
0
  heim_release(options);
366
0
  return NULL;
367
0
    }
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
0
               dbtype);
378
0
  heim_release(options);
379
0
  return NULL;
380
0
    }
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
0
    }
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
0
               dbname);
402
0
  return NULL;
403
0
    }
404
405
0
    ret = db_replay_log(db, error);
406
0
    if (ret) {
407
0
  heim_release(db);
408
0
  return NULL;
409
0
    }
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
0
  }
421
0
    }
422
423
0
    return db;
424
0
}
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
0
{
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
0
            heim_string_get_utf8(db->dbname),
455
0
            db->options, error);
456
0
    }
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
0
    }
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
0
    }
475
0
    db->db_data = NULL;
476
0
    return result;
477
0
}
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
0
{
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
0
  }
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
0
  }
533
0
    } 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
0
    }
539
0
    db->in_transaction = 1;
540
0
    db->ro_tx = !!read_only;
541
0
    return 0;
542
0
}
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
0
{
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
0
    }
576
577
0
    if (db->ro_tx) {
578
0
  ret = 0;
579
0
  goto done;
580
0
    }
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
0
  }
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
0
  }
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
0
      }
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
0
      }
629
0
      if (ret)
630
0
    goto err;
631
0
  }
632
0
    }
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
0
    }
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
0
    }
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
0
          (ret, N_("Error while committing transaction: %s", ""),
676
0
           strerror(ret)));
677
0
}
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
0
{
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
0
}
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
0
{
722
0
    return HEIM_TID_DB;
723
0
}
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
0
{
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
0
}
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
0
{
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
0
  }
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
0
  }
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
0
    }
784
785
0
    result = db->plug->copyf(db->db_data, table, key, error);
786
787
0
    return result;
788
0
}
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
0
{
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
0
  }
839
0
  return heim_db_commit(db, error);
840
0
    }
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
0
    }
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
0
          (ret, N_("Could not set a dict value while while "
865
0
           "setting a DB value", "")));
866
0
}
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
0
{
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
0
  }
909
0
  return heim_db_commit(db, error);
910
0
    }
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
0
    }
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
0
          (ret, N_("Could not set a dict value while while "
934
0
           "deleting a DB value", "")));
935
0
}
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
0
{
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
0
}
960
961
static void
962
db_replay_log_table_set_keys_iter(heim_object_t key, heim_object_t value,
963
          void *arg)
964
0
{
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
0
    }
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
0
}
981
982
static void
983
db_replay_log_table_del_keys_iter(heim_object_t key, heim_object_t value,
984
          void *arg)
985
0
{
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
0
    }
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
0
}
1001
1002
static void
1003
db_replay_log_set_keys_iter(heim_object_t table, heim_object_t table_dict,
1004
          void *arg)
1005
0
{
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
0
}
1014
1015
static void
1016
db_replay_log_del_keys_iter(heim_object_t table, heim_object_t table_dict,
1017
          void *arg)
1018
0
{
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
0
}
1027
1028
static int
1029
db_do_log_actions(heim_db_t db, heim_error_t *error)
1030
0
{
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
0
    } else {
1049
0
  heim_release(db->error);
1050
0
  db->error = NULL;
1051
0
    }
1052
0
    return ret;
1053
0
}
1054
1055
static int
1056
db_replay_log(heim_db_t db, heim_error_t *error)
1057
0
{
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
0
    }
1081
0
    if (ret == 0 && journal == NULL) {
1082
0
        heim_release(journal_fname);
1083
0
  return 0;
1084
0
    }
1085
0
    if (ret != 0) {
1086
0
        heim_release(journal_fname);
1087
0
  return ret;
1088
0
    }
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
0
        (ret, N_("Invalid journal contents; delete journal",
1094
0
           "")));
1095
0
    }
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
0
    }
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
0
}
1121
1122
static
1123
heim_string_t to_base64(heim_data_t data, heim_error_t *error)
1124
0
{
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
0
}
1145
1146
static
1147
heim_data_t from_base64(heim_string_t s, heim_error_t *error)
1148
0
{
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
0
}
1163
1164
1165
static int
1166
open_file(const char *dbname, int for_write, int excl, int *fd_out, heim_error_t *error)
1167
0
{
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
0
    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
0
    }
1232
1233
0
    if (fd_out == NULL) {
1234
0
  (void) close(fd);
1235
0
  return 0;
1236
0
    }
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
0
        (errno, N_("Could not lock JSON file %s: %s", ""),
1244
0
         dbname, strerror(errno)));
1245
0
    }
1246
1247
0
    *fd_out = fd;
1248
    
1249
0
    return 0;
1250
0
#endif
1251
0
}
1252
1253
static int
1254
read_json(const char *dbname, heim_object_t *out, heim_error_t *error)
1255
0
{
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
0
        (ret, N_("Could not stat JSON DB %s: %s", ""),
1272
0
         dbname, strerror(errno)));
1273
0
    }
1274
1275
0
    if (st.st_size == 0) {
1276
0
  (void) close(fd);
1277
0
  return 0;
1278
0
    }
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
0
    }
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
0
        (ret, N_("Could not read JSON DB %s: %s", ""),
1294
0
         dbname, strerror(errno)));
1295
0
    }
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
0
}
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
json_db_open(void *plug, const char *dbtype, const char *dbname,
1317
       heim_dict_t options, void **db, heim_error_t *error)
1318
0
{
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
0
            (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
0
          (EINVAL, N_("Invalid JSON DB open options",
1352
0
                "")));
1353
0
      }
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
0
  }
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
0
  }
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
0
  }
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
0
        }
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
0
            (EINVAL, N_("JSON DB contents not valid JSON",
1404
0
            "")));
1405
0
        }
1406
0
    }
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
0
    }
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
0
    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
0
  }
1430
0
    }
1431
1432
0
    *db = jsondb;
1433
0
    return 0;
1434
0
}
1435
1436
static int
1437
json_db_close(void *db, heim_error_t *error)
1438
0
{
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
0
}
1452
1453
static int
1454
json_db_lock(void *db, int read_only, heim_error_t *error)
1455
0
{
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
0
    "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
0
    }
1471
0
    return ret;
1472
0
}
1473
1474
static int
1475
json_db_unlock(void *db, heim_error_t *error)
1476
0
{
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
0
}
1491
1492
static int
1493
json_db_sync(void *db, heim_error_t *error)
1494
0
{
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
0
  else
1514
0
      heim_release(e);
1515
0
  return ret;
1516
0
    }
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
0
#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
0
    }
1555
0
#endif /* WIN32 */
1556
1557
0
    return errno;
1558
0
}
1559
1560
static heim_data_t
1561
json_db_copy_value(void *db, heim_string_t table, heim_data_t key,
1562
      heim_error_t *error)
1563
0
{
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
0
       (EINVAL, N_("JSON DB requires keys that are actually "
1576
0
             "strings", "")));
1577
0
  return NULL;
1578
0
    }
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
0
    }
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
0
    (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
0
    }
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
0
    }
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
0
}
1614
1615
static int
1616
json_db_set_value(void *db, heim_string_t table,
1617
      heim_data_t key, heim_data_t value, heim_error_t *error)
1618
0
{
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
0
        (EINVAL,
1630
0
         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
0
}
1645
1646
static int
1647
json_db_del_key(void *db, heim_string_t table, heim_data_t key,
1648
    heim_error_t *error)
1649
0
{
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
0
        (EINVAL,
1660
0
         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
0
}
1675
1676
struct json_db_iter_ctx {
1677
    heim_db_iterator_f_t        iter_f;
1678
    void                        *iter_ctx;
1679
};
1680
1681
static void json_db_iter_f(heim_object_t key, heim_object_t value, void *arg)
1682
0
{
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
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
0
{
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
0
}
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