/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 | | |