summaryrefslogtreecommitdiffstats
path: root/lib/tdb
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--lib/tdb/ABI/tdb-1.2.1.sigs95
-rw-r--r--lib/tdb/ABI/tdb-1.2.10.sigs66
-rw-r--r--lib/tdb/ABI/tdb-1.2.11.sigs67
-rw-r--r--lib/tdb/ABI/tdb-1.2.12.sigs67
-rw-r--r--lib/tdb/ABI/tdb-1.2.13.sigs67
-rw-r--r--lib/tdb/ABI/tdb-1.2.2.sigs60
-rw-r--r--lib/tdb/ABI/tdb-1.2.3.sigs60
-rw-r--r--lib/tdb/ABI/tdb-1.2.4.sigs60
-rw-r--r--lib/tdb/ABI/tdb-1.2.5.sigs61
-rw-r--r--lib/tdb/ABI/tdb-1.2.6.sigs61
-rw-r--r--lib/tdb/ABI/tdb-1.2.7.sigs61
-rw-r--r--lib/tdb/ABI/tdb-1.2.8.sigs61
-rw-r--r--lib/tdb/ABI/tdb-1.2.9.sigs62
-rw-r--r--lib/tdb/ABI/tdb-1.3.0.sigs68
-rw-r--r--lib/tdb/ABI/tdb-1.3.1.sigs68
-rw-r--r--lib/tdb/ABI/tdb-1.3.10.sigs69
-rw-r--r--lib/tdb/ABI/tdb-1.3.11.sigs70
-rw-r--r--lib/tdb/ABI/tdb-1.3.12.sigs70
-rw-r--r--lib/tdb/ABI/tdb-1.3.13.sigs70
-rw-r--r--lib/tdb/ABI/tdb-1.3.14.sigs71
-rw-r--r--lib/tdb/ABI/tdb-1.3.15.sigs71
-rw-r--r--lib/tdb/ABI/tdb-1.3.16.sigs71
-rw-r--r--lib/tdb/ABI/tdb-1.3.17.sigs73
-rw-r--r--lib/tdb/ABI/tdb-1.3.18.sigs73
-rw-r--r--lib/tdb/ABI/tdb-1.3.2.sigs68
-rw-r--r--lib/tdb/ABI/tdb-1.3.3.sigs68
-rw-r--r--lib/tdb/ABI/tdb-1.3.4.sigs68
-rw-r--r--lib/tdb/ABI/tdb-1.3.5.sigs69
-rw-r--r--lib/tdb/ABI/tdb-1.3.6.sigs69
-rw-r--r--lib/tdb/ABI/tdb-1.3.7.sigs69
-rw-r--r--lib/tdb/ABI/tdb-1.3.8.sigs69
-rw-r--r--lib/tdb/ABI/tdb-1.3.9.sigs69
-rw-r--r--lib/tdb/ABI/tdb-1.4.0.sigs73
-rw-r--r--lib/tdb/ABI/tdb-1.4.1.sigs73
-rw-r--r--lib/tdb/ABI/tdb-1.4.2.sigs73
-rw-r--r--lib/tdb/ABI/tdb-1.4.3.sigs73
-rw-r--r--lib/tdb/ABI/tdb-1.4.4.sigs73
-rw-r--r--lib/tdb/ABI/tdb-1.4.5.sigs73
-rw-r--r--lib/tdb/ABI/tdb-1.4.6.sigs73
-rw-r--r--lib/tdb/ABI/tdb-1.4.7.sigs73
-rw-r--r--lib/tdb/Makefile68
-rw-r--r--lib/tdb/_tdb_text.py137
-rw-r--r--lib/tdb/common/check.c489
-rw-r--r--lib/tdb/common/dump.c149
-rw-r--r--lib/tdb/common/error.c57
-rw-r--r--lib/tdb/common/freelist.c747
-rw-r--r--lib/tdb/common/freelistcheck.c107
-rw-r--r--lib/tdb/common/hash.c345
-rw-r--r--lib/tdb/common/io.c806
-rw-r--r--lib/tdb/common/lock.c1033
-rw-r--r--lib/tdb/common/mutex.c1078
-rw-r--r--lib/tdb/common/open.c962
-rw-r--r--lib/tdb/common/rescue.c351
-rw-r--r--lib/tdb/common/summary.c219
-rw-r--r--lib/tdb/common/tdb.c1348
-rw-r--r--lib/tdb/common/tdb_private.h370
-rw-r--r--lib/tdb/common/transaction.c1388
-rw-r--r--lib/tdb/common/traverse.c510
-rwxr-xr-xlib/tdb/configure28
-rw-r--r--lib/tdb/docs/README273
-rw-r--r--lib/tdb/docs/mainpage.dox61
-rw-r--r--lib/tdb/docs/mutex.txt136
-rw-r--r--lib/tdb/docs/tdb.magic10
-rw-r--r--lib/tdb/docs/tracing.txt46
-rw-r--r--lib/tdb/doxy.config1697
-rw-r--r--lib/tdb/include/tdb.h1038
-rw-r--r--lib/tdb/man/tdbbackup.8.xml157
-rw-r--r--lib/tdb/man/tdbdump.8.xml93
-rw-r--r--lib/tdb/man/tdbrestore.8.xml67
-rw-r--r--lib/tdb/man/tdbtool.8.xml275
-rw-r--r--lib/tdb/pytdb.c876
-rw-r--r--lib/tdb/python/tdbdump.py12
-rw-r--r--lib/tdb/python/tests/simple.py315
-rw-r--r--lib/tdb/tdb.pc.in11
-rw-r--r--lib/tdb/test/circular_chain.tdbbin0 -> 272 bytes
-rw-r--r--lib/tdb/test/circular_freelist.tdbbin0 -> 400 bytes
-rw-r--r--lib/tdb/test/external-agent.c224
-rw-r--r--lib/tdb/test/external-agent.h44
-rw-r--r--lib/tdb/test/jenkins-be-hash.tdbbin0 -> 696 bytes
-rw-r--r--lib/tdb/test/jenkins-le-hash.tdbbin0 -> 696 bytes
-rw-r--r--lib/tdb/test/lock-tracking.c157
-rw-r--r--lib/tdb/test/lock-tracking.h25
-rw-r--r--lib/tdb/test/logging.c33
-rw-r--r--lib/tdb/test/logging.h11
-rw-r--r--lib/tdb/test/old-nohash-be.tdbbin0 -> 696 bytes
-rw-r--r--lib/tdb/test/old-nohash-le.tdbbin0 -> 696 bytes
-rw-r--r--lib/tdb/test/run-3G-file.c145
-rw-r--r--lib/tdb/test/run-allrecord-traverse-deadlock.c203
-rw-r--r--lib/tdb/test/run-bad-tdb-header.c59
-rw-r--r--lib/tdb/test/run-check.c65
-rw-r--r--lib/tdb/test/run-circular-chain.c42
-rw-r--r--lib/tdb/test/run-circular-freelist.c50
-rw-r--r--lib/tdb/test/run-corrupt.c132
-rw-r--r--lib/tdb/test/run-die-during-transaction.c232
-rw-r--r--lib/tdb/test/run-endian.c64
-rw-r--r--lib/tdb/test/run-fcntl-deadlock.c202
-rw-r--r--lib/tdb/test/run-incompatible.c188
-rw-r--r--lib/tdb/test/run-marklock-deadlock.c278
-rw-r--r--lib/tdb/test/run-mutex-allrecord-bench.c82
-rw-r--r--lib/tdb/test/run-mutex-allrecord-block.c120
-rw-r--r--lib/tdb/test/run-mutex-allrecord-trylock.c113
-rw-r--r--lib/tdb/test/run-mutex-die.c269
-rw-r--r--lib/tdb/test/run-mutex-openflags2.c146
-rw-r--r--lib/tdb/test/run-mutex-transaction1.c236
-rw-r--r--lib/tdb/test/run-mutex-trylock.c122
-rw-r--r--lib/tdb/test/run-mutex1.c138
-rw-r--r--lib/tdb/test/run-nested-transactions.c79
-rw-r--r--lib/tdb/test/run-nested-traverse.c111
-rw-r--r--lib/tdb/test/run-no-lock-during-traverse.c114
-rw-r--r--lib/tdb/test/run-oldhash.c50
-rw-r--r--lib/tdb/test/run-open-during-transaction.c183
-rw-r--r--lib/tdb/test/run-rdlock-upgrade.c166
-rw-r--r--lib/tdb/test/run-readonly-check.c53
-rw-r--r--lib/tdb/test/run-rescue-find_entry.c51
-rw-r--r--lib/tdb/test/run-rescue.c127
-rw-r--r--lib/tdb/test/run-rwlock-check.c46
-rw-r--r--lib/tdb/test/run-summary.c65
-rw-r--r--lib/tdb/test/run-transaction-expand.c125
-rw-r--r--lib/tdb/test/run-traverse-chain.c94
-rw-r--r--lib/tdb/test/run-traverse-in-transaction.c90
-rw-r--r--lib/tdb/test/run-wronghash-fail.c121
-rw-r--r--lib/tdb/test/run-zero-append.c41
-rw-r--r--lib/tdb/test/run.c50
-rw-r--r--lib/tdb/test/rwlock-be.tdbbin0 -> 696 bytes
-rw-r--r--lib/tdb/test/rwlock-le.tdbbin0 -> 696 bytes
-rw-r--r--lib/tdb/test/sample_tdb.tdbbin0 -> 8192 bytes
-rw-r--r--lib/tdb/test/tap-interface.h58
-rw-r--r--lib/tdb/test/tap-to-subunit.h155
-rw-r--r--lib/tdb/test/tdb.corruptbin0 -> 192512 bytes
-rwxr-xr-xlib/tdb/test/test_tdbbackup.sh57
-rw-r--r--lib/tdb/tools/tdbbackup.c370
-rw-r--r--lib/tdb/tools/tdbdump.c181
-rw-r--r--lib/tdb/tools/tdbrestore.c202
-rw-r--r--lib/tdb/tools/tdbtest.c290
-rw-r--r--lib/tdb/tools/tdbtool.c924
-rw-r--r--lib/tdb/tools/tdbtortseq.c123
-rw-r--r--lib/tdb/tools/tdbtorture.c500
-rw-r--r--lib/tdb/web/index.html48
-rw-r--r--lib/tdb/wscript264
-rw-r--r--lib/tdb_wrap/tdb_wrap.c183
-rw-r--r--lib/tdb_wrap/tdb_wrap.h43
-rw-r--r--lib/tdb_wrap/wscript_build8
142 files changed, 26366 insertions, 0 deletions
diff --git a/lib/tdb/ABI/tdb-1.2.1.sigs b/lib/tdb/ABI/tdb-1.2.1.sigs
new file mode 100644
index 0000000..84f2007
--- /dev/null
+++ b/lib/tdb/ABI/tdb-1.2.1.sigs
@@ -0,0 +1,95 @@
+tdb_add_flags: void (struct tdb_context *, unsigned int)
+tdb_alloc_read: unsigned char *(struct tdb_context *, tdb_off_t, tdb_len_t)
+tdb_allocate: tdb_off_t (struct tdb_context *, tdb_len_t, struct tdb_record *)
+tdb_allrecord_lock: int (struct tdb_context *, int, enum tdb_lock_flags, bool)
+tdb_allrecord_unlock: int (struct tdb_context *, int, bool)
+tdb_allrecord_upgrade: int (struct tdb_context *)
+tdb_append: int (struct tdb_context *, TDB_DATA, TDB_DATA)
+tdb_brlock: int (struct tdb_context *, int, tdb_off_t, size_t, enum tdb_lock_flags)
+tdb_brunlock: int (struct tdb_context *, int, tdb_off_t, size_t)
+tdb_chainlock: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_mark: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_nonblock: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_read: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_unmark: int (struct tdb_context *, TDB_DATA)
+tdb_chainunlock: int (struct tdb_context *, TDB_DATA)
+tdb_chainunlock_read: int (struct tdb_context *, TDB_DATA)
+tdb_check: int (struct tdb_context *, int (*)(TDB_DATA, TDB_DATA, void *), void *)
+tdb_close: int (struct tdb_context *)
+tdb_convert: void *(void *, uint32_t)
+tdb_delete: int (struct tdb_context *, TDB_DATA)
+tdb_do_delete: int (struct tdb_context *, tdb_off_t, struct tdb_record *)
+tdb_dump_all: void (struct tdb_context *)
+tdb_enable_seqnum: void (struct tdb_context *)
+tdb_error: enum TDB_ERROR (struct tdb_context *)
+tdb_errorstr: const char *(struct tdb_context *)
+tdb_exists: int (struct tdb_context *, TDB_DATA)
+tdb_expand: int (struct tdb_context *, tdb_off_t)
+tdb_fd: int (struct tdb_context *)
+tdb_fetch: TDB_DATA (struct tdb_context *, TDB_DATA)
+tdb_find_lock_hash: tdb_off_t (struct tdb_context *, TDB_DATA, uint32_t, int, struct tdb_record *)
+tdb_firstkey: TDB_DATA (struct tdb_context *)
+tdb_free: int (struct tdb_context *, tdb_off_t, struct tdb_record *)
+tdb_freelist_size: int (struct tdb_context *)
+tdb_get_flags: int (struct tdb_context *)
+tdb_get_logging_private: void *(struct tdb_context *)
+tdb_get_seqnum: int (struct tdb_context *)
+tdb_hash_size: int (struct tdb_context *)
+tdb_have_extra_locks: bool (struct tdb_context *)
+tdb_increment_seqnum_nonblock: void (struct tdb_context *)
+tdb_io_init: void (struct tdb_context *)
+tdb_lock: int (struct tdb_context *, int, int)
+tdb_lock_nonblock: int (struct tdb_context *, int, int)
+tdb_lock_record: int (struct tdb_context *, tdb_off_t)
+tdb_lockall: int (struct tdb_context *)
+tdb_lockall_mark: int (struct tdb_context *)
+tdb_lockall_nonblock: int (struct tdb_context *)
+tdb_lockall_read: int (struct tdb_context *)
+tdb_lockall_read_nonblock: int (struct tdb_context *)
+tdb_lockall_unmark: int (struct tdb_context *)
+tdb_log_fn: tdb_log_func (struct tdb_context *)
+tdb_map_size: size_t (struct tdb_context *)
+tdb_mmap: void (struct tdb_context *)
+tdb_munmap: int (struct tdb_context *)
+tdb_name: const char *(struct tdb_context *)
+tdb_needs_recovery: bool (struct tdb_context *)
+tdb_nest_lock: int (struct tdb_context *, uint32_t, int, enum tdb_lock_flags)
+tdb_nest_unlock: int (struct tdb_context *, uint32_t, int, bool)
+tdb_nextkey: TDB_DATA (struct tdb_context *, TDB_DATA)
+tdb_ofs_read: int (struct tdb_context *, tdb_off_t, tdb_off_t *)
+tdb_ofs_write: int (struct tdb_context *, tdb_off_t, tdb_off_t *)
+tdb_open: struct tdb_context *(const char *, int, int, int, mode_t)
+tdb_open_ex: struct tdb_context *(const char *, int, int, int, mode_t, const struct tdb_logging_context *, tdb_hash_func)
+tdb_parse_data: int (struct tdb_context *, TDB_DATA, tdb_off_t, tdb_len_t, int (*)(TDB_DATA, TDB_DATA, void *), void *)
+tdb_parse_record: int (struct tdb_context *, TDB_DATA, int (*)(TDB_DATA, TDB_DATA, void *), void *)
+tdb_printfreelist: int (struct tdb_context *)
+tdb_rec_free_read: int (struct tdb_context *, tdb_off_t, struct tdb_record *)
+tdb_rec_read: int (struct tdb_context *, tdb_off_t, struct tdb_record *)
+tdb_rec_write: int (struct tdb_context *, tdb_off_t, struct tdb_record *)
+tdb_release_transaction_locks: void (struct tdb_context *)
+tdb_remove_flags: void (struct tdb_context *, unsigned int)
+tdb_reopen: int (struct tdb_context *)
+tdb_reopen_all: int (int)
+tdb_repack: int (struct tdb_context *)
+tdb_set_logging_function: void (struct tdb_context *, const struct tdb_logging_context *)
+tdb_set_max_dead: void (struct tdb_context *, int)
+tdb_setalarm_sigptr: void (struct tdb_context *, volatile sig_atomic_t *)
+tdb_store: int (struct tdb_context *, TDB_DATA, TDB_DATA, int)
+tdb_transaction_cancel: int (struct tdb_context *)
+tdb_transaction_commit: int (struct tdb_context *)
+tdb_transaction_lock: int (struct tdb_context *, int, enum tdb_lock_flags)
+tdb_transaction_prepare_commit: int (struct tdb_context *)
+tdb_transaction_recover: int (struct tdb_context *)
+tdb_transaction_start: int (struct tdb_context *)
+tdb_transaction_start_nonblock: int (struct tdb_context *)
+tdb_transaction_unlock: int (struct tdb_context *, int)
+tdb_traverse: int (struct tdb_context *, tdb_traverse_func, void *)
+tdb_traverse_read: int (struct tdb_context *, tdb_traverse_func, void *)
+tdb_unlock: int (struct tdb_context *, int, int)
+tdb_unlock_record: int (struct tdb_context *, tdb_off_t)
+tdb_unlockall: int (struct tdb_context *)
+tdb_unlockall_read: int (struct tdb_context *)
+tdb_validate_freelist: int (struct tdb_context *, int *)
+tdb_wipe_all: int (struct tdb_context *)
+tdb_write_lock_record: int (struct tdb_context *, tdb_off_t)
+tdb_write_unlock_record: int (struct tdb_context *, tdb_off_t)
diff --git a/lib/tdb/ABI/tdb-1.2.10.sigs b/lib/tdb/ABI/tdb-1.2.10.sigs
new file mode 100644
index 0000000..61f6c19
--- /dev/null
+++ b/lib/tdb/ABI/tdb-1.2.10.sigs
@@ -0,0 +1,66 @@
+tdb_add_flags: void (struct tdb_context *, unsigned int)
+tdb_append: int (struct tdb_context *, TDB_DATA, TDB_DATA)
+tdb_chainlock: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_mark: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_nonblock: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_read: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_unmark: int (struct tdb_context *, TDB_DATA)
+tdb_chainunlock: int (struct tdb_context *, TDB_DATA)
+tdb_chainunlock_read: int (struct tdb_context *, TDB_DATA)
+tdb_check: int (struct tdb_context *, int (*)(TDB_DATA, TDB_DATA, void *), void *)
+tdb_close: int (struct tdb_context *)
+tdb_delete: int (struct tdb_context *, TDB_DATA)
+tdb_dump_all: void (struct tdb_context *)
+tdb_enable_seqnum: void (struct tdb_context *)
+tdb_error: enum TDB_ERROR (struct tdb_context *)
+tdb_errorstr: const char *(struct tdb_context *)
+tdb_exists: int (struct tdb_context *, TDB_DATA)
+tdb_fd: int (struct tdb_context *)
+tdb_fetch: TDB_DATA (struct tdb_context *, TDB_DATA)
+tdb_firstkey: TDB_DATA (struct tdb_context *)
+tdb_freelist_size: int (struct tdb_context *)
+tdb_get_flags: int (struct tdb_context *)
+tdb_get_logging_private: void *(struct tdb_context *)
+tdb_get_seqnum: int (struct tdb_context *)
+tdb_hash_size: int (struct tdb_context *)
+tdb_increment_seqnum_nonblock: void (struct tdb_context *)
+tdb_jenkins_hash: unsigned int (TDB_DATA *)
+tdb_lock_nonblock: int (struct tdb_context *, int, int)
+tdb_lockall: int (struct tdb_context *)
+tdb_lockall_mark: int (struct tdb_context *)
+tdb_lockall_nonblock: int (struct tdb_context *)
+tdb_lockall_read: int (struct tdb_context *)
+tdb_lockall_read_nonblock: int (struct tdb_context *)
+tdb_lockall_unmark: int (struct tdb_context *)
+tdb_log_fn: tdb_log_func (struct tdb_context *)
+tdb_map_size: size_t (struct tdb_context *)
+tdb_name: const char *(struct tdb_context *)
+tdb_nextkey: TDB_DATA (struct tdb_context *, TDB_DATA)
+tdb_null: dptr = 0xXXXX, dsize = 0
+tdb_open: struct tdb_context *(const char *, int, int, int, mode_t)
+tdb_open_ex: struct tdb_context *(const char *, int, int, int, mode_t, const struct tdb_logging_context *, tdb_hash_func)
+tdb_parse_record: int (struct tdb_context *, TDB_DATA, int (*)(TDB_DATA, TDB_DATA, void *), void *)
+tdb_printfreelist: int (struct tdb_context *)
+tdb_remove_flags: void (struct tdb_context *, unsigned int)
+tdb_reopen: int (struct tdb_context *)
+tdb_reopen_all: int (int)
+tdb_repack: int (struct tdb_context *)
+tdb_set_logging_function: void (struct tdb_context *, const struct tdb_logging_context *)
+tdb_set_max_dead: void (struct tdb_context *, int)
+tdb_setalarm_sigptr: void (struct tdb_context *, volatile sig_atomic_t *)
+tdb_store: int (struct tdb_context *, TDB_DATA, TDB_DATA, int)
+tdb_summary: char *(struct tdb_context *)
+tdb_transaction_cancel: int (struct tdb_context *)
+tdb_transaction_commit: int (struct tdb_context *)
+tdb_transaction_prepare_commit: int (struct tdb_context *)
+tdb_transaction_start: int (struct tdb_context *)
+tdb_transaction_start_nonblock: int (struct tdb_context *)
+tdb_transaction_write_lock_mark: int (struct tdb_context *)
+tdb_transaction_write_lock_unmark: int (struct tdb_context *)
+tdb_traverse: int (struct tdb_context *, tdb_traverse_func, void *)
+tdb_traverse_read: int (struct tdb_context *, tdb_traverse_func, void *)
+tdb_unlock: int (struct tdb_context *, int, int)
+tdb_unlockall: int (struct tdb_context *)
+tdb_unlockall_read: int (struct tdb_context *)
+tdb_validate_freelist: int (struct tdb_context *, int *)
+tdb_wipe_all: int (struct tdb_context *)
diff --git a/lib/tdb/ABI/tdb-1.2.11.sigs b/lib/tdb/ABI/tdb-1.2.11.sigs
new file mode 100644
index 0000000..d727f21
--- /dev/null
+++ b/lib/tdb/ABI/tdb-1.2.11.sigs
@@ -0,0 +1,67 @@
+tdb_add_flags: void (struct tdb_context *, unsigned int)
+tdb_append: int (struct tdb_context *, TDB_DATA, TDB_DATA)
+tdb_chainlock: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_mark: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_nonblock: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_read: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_unmark: int (struct tdb_context *, TDB_DATA)
+tdb_chainunlock: int (struct tdb_context *, TDB_DATA)
+tdb_chainunlock_read: int (struct tdb_context *, TDB_DATA)
+tdb_check: int (struct tdb_context *, int (*)(TDB_DATA, TDB_DATA, void *), void *)
+tdb_close: int (struct tdb_context *)
+tdb_delete: int (struct tdb_context *, TDB_DATA)
+tdb_dump_all: void (struct tdb_context *)
+tdb_enable_seqnum: void (struct tdb_context *)
+tdb_error: enum TDB_ERROR (struct tdb_context *)
+tdb_errorstr: const char *(struct tdb_context *)
+tdb_exists: int (struct tdb_context *, TDB_DATA)
+tdb_fd: int (struct tdb_context *)
+tdb_fetch: TDB_DATA (struct tdb_context *, TDB_DATA)
+tdb_firstkey: TDB_DATA (struct tdb_context *)
+tdb_freelist_size: int (struct tdb_context *)
+tdb_get_flags: int (struct tdb_context *)
+tdb_get_logging_private: void *(struct tdb_context *)
+tdb_get_seqnum: int (struct tdb_context *)
+tdb_hash_size: int (struct tdb_context *)
+tdb_increment_seqnum_nonblock: void (struct tdb_context *)
+tdb_jenkins_hash: unsigned int (TDB_DATA *)
+tdb_lock_nonblock: int (struct tdb_context *, int, int)
+tdb_lockall: int (struct tdb_context *)
+tdb_lockall_mark: int (struct tdb_context *)
+tdb_lockall_nonblock: int (struct tdb_context *)
+tdb_lockall_read: int (struct tdb_context *)
+tdb_lockall_read_nonblock: int (struct tdb_context *)
+tdb_lockall_unmark: int (struct tdb_context *)
+tdb_log_fn: tdb_log_func (struct tdb_context *)
+tdb_map_size: size_t (struct tdb_context *)
+tdb_name: const char *(struct tdb_context *)
+tdb_nextkey: TDB_DATA (struct tdb_context *, TDB_DATA)
+tdb_null: dptr = 0xXXXX, dsize = 0
+tdb_open: struct tdb_context *(const char *, int, int, int, mode_t)
+tdb_open_ex: struct tdb_context *(const char *, int, int, int, mode_t, const struct tdb_logging_context *, tdb_hash_func)
+tdb_parse_record: int (struct tdb_context *, TDB_DATA, int (*)(TDB_DATA, TDB_DATA, void *), void *)
+tdb_printfreelist: int (struct tdb_context *)
+tdb_remove_flags: void (struct tdb_context *, unsigned int)
+tdb_reopen: int (struct tdb_context *)
+tdb_reopen_all: int (int)
+tdb_repack: int (struct tdb_context *)
+tdb_rescue: int (struct tdb_context *, void (*)(TDB_DATA, TDB_DATA, void *), void *)
+tdb_set_logging_function: void (struct tdb_context *, const struct tdb_logging_context *)
+tdb_set_max_dead: void (struct tdb_context *, int)
+tdb_setalarm_sigptr: void (struct tdb_context *, volatile sig_atomic_t *)
+tdb_store: int (struct tdb_context *, TDB_DATA, TDB_DATA, int)
+tdb_summary: char *(struct tdb_context *)
+tdb_transaction_cancel: int (struct tdb_context *)
+tdb_transaction_commit: int (struct tdb_context *)
+tdb_transaction_prepare_commit: int (struct tdb_context *)
+tdb_transaction_start: int (struct tdb_context *)
+tdb_transaction_start_nonblock: int (struct tdb_context *)
+tdb_transaction_write_lock_mark: int (struct tdb_context *)
+tdb_transaction_write_lock_unmark: int (struct tdb_context *)
+tdb_traverse: int (struct tdb_context *, tdb_traverse_func, void *)
+tdb_traverse_read: int (struct tdb_context *, tdb_traverse_func, void *)
+tdb_unlock: int (struct tdb_context *, int, int)
+tdb_unlockall: int (struct tdb_context *)
+tdb_unlockall_read: int (struct tdb_context *)
+tdb_validate_freelist: int (struct tdb_context *, int *)
+tdb_wipe_all: int (struct tdb_context *)
diff --git a/lib/tdb/ABI/tdb-1.2.12.sigs b/lib/tdb/ABI/tdb-1.2.12.sigs
new file mode 100644
index 0000000..d727f21
--- /dev/null
+++ b/lib/tdb/ABI/tdb-1.2.12.sigs
@@ -0,0 +1,67 @@
+tdb_add_flags: void (struct tdb_context *, unsigned int)
+tdb_append: int (struct tdb_context *, TDB_DATA, TDB_DATA)
+tdb_chainlock: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_mark: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_nonblock: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_read: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_unmark: int (struct tdb_context *, TDB_DATA)
+tdb_chainunlock: int (struct tdb_context *, TDB_DATA)
+tdb_chainunlock_read: int (struct tdb_context *, TDB_DATA)
+tdb_check: int (struct tdb_context *, int (*)(TDB_DATA, TDB_DATA, void *), void *)
+tdb_close: int (struct tdb_context *)
+tdb_delete: int (struct tdb_context *, TDB_DATA)
+tdb_dump_all: void (struct tdb_context *)
+tdb_enable_seqnum: void (struct tdb_context *)
+tdb_error: enum TDB_ERROR (struct tdb_context *)
+tdb_errorstr: const char *(struct tdb_context *)
+tdb_exists: int (struct tdb_context *, TDB_DATA)
+tdb_fd: int (struct tdb_context *)
+tdb_fetch: TDB_DATA (struct tdb_context *, TDB_DATA)
+tdb_firstkey: TDB_DATA (struct tdb_context *)
+tdb_freelist_size: int (struct tdb_context *)
+tdb_get_flags: int (struct tdb_context *)
+tdb_get_logging_private: void *(struct tdb_context *)
+tdb_get_seqnum: int (struct tdb_context *)
+tdb_hash_size: int (struct tdb_context *)
+tdb_increment_seqnum_nonblock: void (struct tdb_context *)
+tdb_jenkins_hash: unsigned int (TDB_DATA *)
+tdb_lock_nonblock: int (struct tdb_context *, int, int)
+tdb_lockall: int (struct tdb_context *)
+tdb_lockall_mark: int (struct tdb_context *)
+tdb_lockall_nonblock: int (struct tdb_context *)
+tdb_lockall_read: int (struct tdb_context *)
+tdb_lockall_read_nonblock: int (struct tdb_context *)
+tdb_lockall_unmark: int (struct tdb_context *)
+tdb_log_fn: tdb_log_func (struct tdb_context *)
+tdb_map_size: size_t (struct tdb_context *)
+tdb_name: const char *(struct tdb_context *)
+tdb_nextkey: TDB_DATA (struct tdb_context *, TDB_DATA)
+tdb_null: dptr = 0xXXXX, dsize = 0
+tdb_open: struct tdb_context *(const char *, int, int, int, mode_t)
+tdb_open_ex: struct tdb_context *(const char *, int, int, int, mode_t, const struct tdb_logging_context *, tdb_hash_func)
+tdb_parse_record: int (struct tdb_context *, TDB_DATA, int (*)(TDB_DATA, TDB_DATA, void *), void *)
+tdb_printfreelist: int (struct tdb_context *)
+tdb_remove_flags: void (struct tdb_context *, unsigned int)
+tdb_reopen: int (struct tdb_context *)
+tdb_reopen_all: int (int)
+tdb_repack: int (struct tdb_context *)
+tdb_rescue: int (struct tdb_context *, void (*)(TDB_DATA, TDB_DATA, void *), void *)
+tdb_set_logging_function: void (struct tdb_context *, const struct tdb_logging_context *)
+tdb_set_max_dead: void (struct tdb_context *, int)
+tdb_setalarm_sigptr: void (struct tdb_context *, volatile sig_atomic_t *)
+tdb_store: int (struct tdb_context *, TDB_DATA, TDB_DATA, int)
+tdb_summary: char *(struct tdb_context *)
+tdb_transaction_cancel: int (struct tdb_context *)
+tdb_transaction_commit: int (struct tdb_context *)
+tdb_transaction_prepare_commit: int (struct tdb_context *)
+tdb_transaction_start: int (struct tdb_context *)
+tdb_transaction_start_nonblock: int (struct tdb_context *)
+tdb_transaction_write_lock_mark: int (struct tdb_context *)
+tdb_transaction_write_lock_unmark: int (struct tdb_context *)
+tdb_traverse: int (struct tdb_context *, tdb_traverse_func, void *)
+tdb_traverse_read: int (struct tdb_context *, tdb_traverse_func, void *)
+tdb_unlock: int (struct tdb_context *, int, int)
+tdb_unlockall: int (struct tdb_context *)
+tdb_unlockall_read: int (struct tdb_context *)
+tdb_validate_freelist: int (struct tdb_context *, int *)
+tdb_wipe_all: int (struct tdb_context *)
diff --git a/lib/tdb/ABI/tdb-1.2.13.sigs b/lib/tdb/ABI/tdb-1.2.13.sigs
new file mode 100644
index 0000000..d727f21
--- /dev/null
+++ b/lib/tdb/ABI/tdb-1.2.13.sigs
@@ -0,0 +1,67 @@
+tdb_add_flags: void (struct tdb_context *, unsigned int)
+tdb_append: int (struct tdb_context *, TDB_DATA, TDB_DATA)
+tdb_chainlock: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_mark: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_nonblock: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_read: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_unmark: int (struct tdb_context *, TDB_DATA)
+tdb_chainunlock: int (struct tdb_context *, TDB_DATA)
+tdb_chainunlock_read: int (struct tdb_context *, TDB_DATA)
+tdb_check: int (struct tdb_context *, int (*)(TDB_DATA, TDB_DATA, void *), void *)
+tdb_close: int (struct tdb_context *)
+tdb_delete: int (struct tdb_context *, TDB_DATA)
+tdb_dump_all: void (struct tdb_context *)
+tdb_enable_seqnum: void (struct tdb_context *)
+tdb_error: enum TDB_ERROR (struct tdb_context *)
+tdb_errorstr: const char *(struct tdb_context *)
+tdb_exists: int (struct tdb_context *, TDB_DATA)
+tdb_fd: int (struct tdb_context *)
+tdb_fetch: TDB_DATA (struct tdb_context *, TDB_DATA)
+tdb_firstkey: TDB_DATA (struct tdb_context *)
+tdb_freelist_size: int (struct tdb_context *)
+tdb_get_flags: int (struct tdb_context *)
+tdb_get_logging_private: void *(struct tdb_context *)
+tdb_get_seqnum: int (struct tdb_context *)
+tdb_hash_size: int (struct tdb_context *)
+tdb_increment_seqnum_nonblock: void (struct tdb_context *)
+tdb_jenkins_hash: unsigned int (TDB_DATA *)
+tdb_lock_nonblock: int (struct tdb_context *, int, int)
+tdb_lockall: int (struct tdb_context *)
+tdb_lockall_mark: int (struct tdb_context *)
+tdb_lockall_nonblock: int (struct tdb_context *)
+tdb_lockall_read: int (struct tdb_context *)
+tdb_lockall_read_nonblock: int (struct tdb_context *)
+tdb_lockall_unmark: int (struct tdb_context *)
+tdb_log_fn: tdb_log_func (struct tdb_context *)
+tdb_map_size: size_t (struct tdb_context *)
+tdb_name: const char *(struct tdb_context *)
+tdb_nextkey: TDB_DATA (struct tdb_context *, TDB_DATA)
+tdb_null: dptr = 0xXXXX, dsize = 0
+tdb_open: struct tdb_context *(const char *, int, int, int, mode_t)
+tdb_open_ex: struct tdb_context *(const char *, int, int, int, mode_t, const struct tdb_logging_context *, tdb_hash_func)
+tdb_parse_record: int (struct tdb_context *, TDB_DATA, int (*)(TDB_DATA, TDB_DATA, void *), void *)
+tdb_printfreelist: int (struct tdb_context *)
+tdb_remove_flags: void (struct tdb_context *, unsigned int)
+tdb_reopen: int (struct tdb_context *)
+tdb_reopen_all: int (int)
+tdb_repack: int (struct tdb_context *)
+tdb_rescue: int (struct tdb_context *, void (*)(TDB_DATA, TDB_DATA, void *), void *)
+tdb_set_logging_function: void (struct tdb_context *, const struct tdb_logging_context *)
+tdb_set_max_dead: void (struct tdb_context *, int)
+tdb_setalarm_sigptr: void (struct tdb_context *, volatile sig_atomic_t *)
+tdb_store: int (struct tdb_context *, TDB_DATA, TDB_DATA, int)
+tdb_summary: char *(struct tdb_context *)
+tdb_transaction_cancel: int (struct tdb_context *)
+tdb_transaction_commit: int (struct tdb_context *)
+tdb_transaction_prepare_commit: int (struct tdb_context *)
+tdb_transaction_start: int (struct tdb_context *)
+tdb_transaction_start_nonblock: int (struct tdb_context *)
+tdb_transaction_write_lock_mark: int (struct tdb_context *)
+tdb_transaction_write_lock_unmark: int (struct tdb_context *)
+tdb_traverse: int (struct tdb_context *, tdb_traverse_func, void *)
+tdb_traverse_read: int (struct tdb_context *, tdb_traverse_func, void *)
+tdb_unlock: int (struct tdb_context *, int, int)
+tdb_unlockall: int (struct tdb_context *)
+tdb_unlockall_read: int (struct tdb_context *)
+tdb_validate_freelist: int (struct tdb_context *, int *)
+tdb_wipe_all: int (struct tdb_context *)
diff --git a/lib/tdb/ABI/tdb-1.2.2.sigs b/lib/tdb/ABI/tdb-1.2.2.sigs
new file mode 100644
index 0000000..043790d
--- /dev/null
+++ b/lib/tdb/ABI/tdb-1.2.2.sigs
@@ -0,0 +1,60 @@
+tdb_add_flags: void (struct tdb_context *, unsigned int)
+tdb_append: int (struct tdb_context *, TDB_DATA, TDB_DATA)
+tdb_chainlock: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_mark: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_nonblock: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_read: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_unmark: int (struct tdb_context *, TDB_DATA)
+tdb_chainunlock: int (struct tdb_context *, TDB_DATA)
+tdb_chainunlock_read: int (struct tdb_context *, TDB_DATA)
+tdb_check: int (struct tdb_context *, int (*)(TDB_DATA, TDB_DATA, void *), void *)
+tdb_close: int (struct tdb_context *)
+tdb_delete: int (struct tdb_context *, TDB_DATA)
+tdb_dump_all: void (struct tdb_context *)
+tdb_enable_seqnum: void (struct tdb_context *)
+tdb_error: enum TDB_ERROR (struct tdb_context *)
+tdb_errorstr: const char *(struct tdb_context *)
+tdb_exists: int (struct tdb_context *, TDB_DATA)
+tdb_fd: int (struct tdb_context *)
+tdb_fetch: TDB_DATA (struct tdb_context *, TDB_DATA)
+tdb_firstkey: TDB_DATA (struct tdb_context *)
+tdb_freelist_size: int (struct tdb_context *)
+tdb_get_flags: int (struct tdb_context *)
+tdb_get_logging_private: void *(struct tdb_context *)
+tdb_get_seqnum: int (struct tdb_context *)
+tdb_hash_size: int (struct tdb_context *)
+tdb_increment_seqnum_nonblock: void (struct tdb_context *)
+tdb_lockall: int (struct tdb_context *)
+tdb_lockall_mark: int (struct tdb_context *)
+tdb_lockall_nonblock: int (struct tdb_context *)
+tdb_lockall_read: int (struct tdb_context *)
+tdb_lockall_read_nonblock: int (struct tdb_context *)
+tdb_lockall_unmark: int (struct tdb_context *)
+tdb_log_fn: tdb_log_func (struct tdb_context *)
+tdb_map_size: size_t (struct tdb_context *)
+tdb_name: const char *(struct tdb_context *)
+tdb_nextkey: TDB_DATA (struct tdb_context *, TDB_DATA)
+tdb_null: dptr = 0xXXXX, dsize = 0
+tdb_open: struct tdb_context *(const char *, int, int, int, mode_t)
+tdb_open_ex: struct tdb_context *(const char *, int, int, int, mode_t, const struct tdb_logging_context *, tdb_hash_func)
+tdb_parse_record: int (struct tdb_context *, TDB_DATA, int (*)(TDB_DATA, TDB_DATA, void *), void *)
+tdb_printfreelist: int (struct tdb_context *)
+tdb_remove_flags: void (struct tdb_context *, unsigned int)
+tdb_reopen: int (struct tdb_context *)
+tdb_reopen_all: int (int)
+tdb_repack: int (struct tdb_context *)
+tdb_set_logging_function: void (struct tdb_context *, const struct tdb_logging_context *)
+tdb_set_max_dead: void (struct tdb_context *, int)
+tdb_setalarm_sigptr: void (struct tdb_context *, volatile sig_atomic_t *)
+tdb_store: int (struct tdb_context *, TDB_DATA, TDB_DATA, int)
+tdb_transaction_cancel: int (struct tdb_context *)
+tdb_transaction_commit: int (struct tdb_context *)
+tdb_transaction_prepare_commit: int (struct tdb_context *)
+tdb_transaction_start: int (struct tdb_context *)
+tdb_transaction_start_nonblock: int (struct tdb_context *)
+tdb_traverse: int (struct tdb_context *, tdb_traverse_func, void *)
+tdb_traverse_read: int (struct tdb_context *, tdb_traverse_func, void *)
+tdb_unlockall: int (struct tdb_context *)
+tdb_unlockall_read: int (struct tdb_context *)
+tdb_validate_freelist: int (struct tdb_context *, int *)
+tdb_wipe_all: int (struct tdb_context *)
diff --git a/lib/tdb/ABI/tdb-1.2.3.sigs b/lib/tdb/ABI/tdb-1.2.3.sigs
new file mode 100644
index 0000000..043790d
--- /dev/null
+++ b/lib/tdb/ABI/tdb-1.2.3.sigs
@@ -0,0 +1,60 @@
+tdb_add_flags: void (struct tdb_context *, unsigned int)
+tdb_append: int (struct tdb_context *, TDB_DATA, TDB_DATA)
+tdb_chainlock: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_mark: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_nonblock: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_read: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_unmark: int (struct tdb_context *, TDB_DATA)
+tdb_chainunlock: int (struct tdb_context *, TDB_DATA)
+tdb_chainunlock_read: int (struct tdb_context *, TDB_DATA)
+tdb_check: int (struct tdb_context *, int (*)(TDB_DATA, TDB_DATA, void *), void *)
+tdb_close: int (struct tdb_context *)
+tdb_delete: int (struct tdb_context *, TDB_DATA)
+tdb_dump_all: void (struct tdb_context *)
+tdb_enable_seqnum: void (struct tdb_context *)
+tdb_error: enum TDB_ERROR (struct tdb_context *)
+tdb_errorstr: const char *(struct tdb_context *)
+tdb_exists: int (struct tdb_context *, TDB_DATA)
+tdb_fd: int (struct tdb_context *)
+tdb_fetch: TDB_DATA (struct tdb_context *, TDB_DATA)
+tdb_firstkey: TDB_DATA (struct tdb_context *)
+tdb_freelist_size: int (struct tdb_context *)
+tdb_get_flags: int (struct tdb_context *)
+tdb_get_logging_private: void *(struct tdb_context *)
+tdb_get_seqnum: int (struct tdb_context *)
+tdb_hash_size: int (struct tdb_context *)
+tdb_increment_seqnum_nonblock: void (struct tdb_context *)
+tdb_lockall: int (struct tdb_context *)
+tdb_lockall_mark: int (struct tdb_context *)
+tdb_lockall_nonblock: int (struct tdb_context *)
+tdb_lockall_read: int (struct tdb_context *)
+tdb_lockall_read_nonblock: int (struct tdb_context *)
+tdb_lockall_unmark: int (struct tdb_context *)
+tdb_log_fn: tdb_log_func (struct tdb_context *)
+tdb_map_size: size_t (struct tdb_context *)
+tdb_name: const char *(struct tdb_context *)
+tdb_nextkey: TDB_DATA (struct tdb_context *, TDB_DATA)
+tdb_null: dptr = 0xXXXX, dsize = 0
+tdb_open: struct tdb_context *(const char *, int, int, int, mode_t)
+tdb_open_ex: struct tdb_context *(const char *, int, int, int, mode_t, const struct tdb_logging_context *, tdb_hash_func)
+tdb_parse_record: int (struct tdb_context *, TDB_DATA, int (*)(TDB_DATA, TDB_DATA, void *), void *)
+tdb_printfreelist: int (struct tdb_context *)
+tdb_remove_flags: void (struct tdb_context *, unsigned int)
+tdb_reopen: int (struct tdb_context *)
+tdb_reopen_all: int (int)
+tdb_repack: int (struct tdb_context *)
+tdb_set_logging_function: void (struct tdb_context *, const struct tdb_logging_context *)
+tdb_set_max_dead: void (struct tdb_context *, int)
+tdb_setalarm_sigptr: void (struct tdb_context *, volatile sig_atomic_t *)
+tdb_store: int (struct tdb_context *, TDB_DATA, TDB_DATA, int)
+tdb_transaction_cancel: int (struct tdb_context *)
+tdb_transaction_commit: int (struct tdb_context *)
+tdb_transaction_prepare_commit: int (struct tdb_context *)
+tdb_transaction_start: int (struct tdb_context *)
+tdb_transaction_start_nonblock: int (struct tdb_context *)
+tdb_traverse: int (struct tdb_context *, tdb_traverse_func, void *)
+tdb_traverse_read: int (struct tdb_context *, tdb_traverse_func, void *)
+tdb_unlockall: int (struct tdb_context *)
+tdb_unlockall_read: int (struct tdb_context *)
+tdb_validate_freelist: int (struct tdb_context *, int *)
+tdb_wipe_all: int (struct tdb_context *)
diff --git a/lib/tdb/ABI/tdb-1.2.4.sigs b/lib/tdb/ABI/tdb-1.2.4.sigs
new file mode 100644
index 0000000..043790d
--- /dev/null
+++ b/lib/tdb/ABI/tdb-1.2.4.sigs
@@ -0,0 +1,60 @@
+tdb_add_flags: void (struct tdb_context *, unsigned int)
+tdb_append: int (struct tdb_context *, TDB_DATA, TDB_DATA)
+tdb_chainlock: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_mark: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_nonblock: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_read: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_unmark: int (struct tdb_context *, TDB_DATA)
+tdb_chainunlock: int (struct tdb_context *, TDB_DATA)
+tdb_chainunlock_read: int (struct tdb_context *, TDB_DATA)
+tdb_check: int (struct tdb_context *, int (*)(TDB_DATA, TDB_DATA, void *), void *)
+tdb_close: int (struct tdb_context *)
+tdb_delete: int (struct tdb_context *, TDB_DATA)
+tdb_dump_all: void (struct tdb_context *)
+tdb_enable_seqnum: void (struct tdb_context *)
+tdb_error: enum TDB_ERROR (struct tdb_context *)
+tdb_errorstr: const char *(struct tdb_context *)
+tdb_exists: int (struct tdb_context *, TDB_DATA)
+tdb_fd: int (struct tdb_context *)
+tdb_fetch: TDB_DATA (struct tdb_context *, TDB_DATA)
+tdb_firstkey: TDB_DATA (struct tdb_context *)
+tdb_freelist_size: int (struct tdb_context *)
+tdb_get_flags: int (struct tdb_context *)
+tdb_get_logging_private: void *(struct tdb_context *)
+tdb_get_seqnum: int (struct tdb_context *)
+tdb_hash_size: int (struct tdb_context *)
+tdb_increment_seqnum_nonblock: void (struct tdb_context *)
+tdb_lockall: int (struct tdb_context *)
+tdb_lockall_mark: int (struct tdb_context *)
+tdb_lockall_nonblock: int (struct tdb_context *)
+tdb_lockall_read: int (struct tdb_context *)
+tdb_lockall_read_nonblock: int (struct tdb_context *)
+tdb_lockall_unmark: int (struct tdb_context *)
+tdb_log_fn: tdb_log_func (struct tdb_context *)
+tdb_map_size: size_t (struct tdb_context *)
+tdb_name: const char *(struct tdb_context *)
+tdb_nextkey: TDB_DATA (struct tdb_context *, TDB_DATA)
+tdb_null: dptr = 0xXXXX, dsize = 0
+tdb_open: struct tdb_context *(const char *, int, int, int, mode_t)
+tdb_open_ex: struct tdb_context *(const char *, int, int, int, mode_t, const struct tdb_logging_context *, tdb_hash_func)
+tdb_parse_record: int (struct tdb_context *, TDB_DATA, int (*)(TDB_DATA, TDB_DATA, void *), void *)
+tdb_printfreelist: int (struct tdb_context *)
+tdb_remove_flags: void (struct tdb_context *, unsigned int)
+tdb_reopen: int (struct tdb_context *)
+tdb_reopen_all: int (int)
+tdb_repack: int (struct tdb_context *)
+tdb_set_logging_function: void (struct tdb_context *, const struct tdb_logging_context *)
+tdb_set_max_dead: void (struct tdb_context *, int)
+tdb_setalarm_sigptr: void (struct tdb_context *, volatile sig_atomic_t *)
+tdb_store: int (struct tdb_context *, TDB_DATA, TDB_DATA, int)
+tdb_transaction_cancel: int (struct tdb_context *)
+tdb_transaction_commit: int (struct tdb_context *)
+tdb_transaction_prepare_commit: int (struct tdb_context *)
+tdb_transaction_start: int (struct tdb_context *)
+tdb_transaction_start_nonblock: int (struct tdb_context *)
+tdb_traverse: int (struct tdb_context *, tdb_traverse_func, void *)
+tdb_traverse_read: int (struct tdb_context *, tdb_traverse_func, void *)
+tdb_unlockall: int (struct tdb_context *)
+tdb_unlockall_read: int (struct tdb_context *)
+tdb_validate_freelist: int (struct tdb_context *, int *)
+tdb_wipe_all: int (struct tdb_context *)
diff --git a/lib/tdb/ABI/tdb-1.2.5.sigs b/lib/tdb/ABI/tdb-1.2.5.sigs
new file mode 100644
index 0000000..1e01f3b
--- /dev/null
+++ b/lib/tdb/ABI/tdb-1.2.5.sigs
@@ -0,0 +1,61 @@
+tdb_add_flags: void (struct tdb_context *, unsigned int)
+tdb_append: int (struct tdb_context *, TDB_DATA, TDB_DATA)
+tdb_chainlock: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_mark: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_nonblock: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_read: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_unmark: int (struct tdb_context *, TDB_DATA)
+tdb_chainunlock: int (struct tdb_context *, TDB_DATA)
+tdb_chainunlock_read: int (struct tdb_context *, TDB_DATA)
+tdb_check: int (struct tdb_context *, int (*)(TDB_DATA, TDB_DATA, void *), void *)
+tdb_close: int (struct tdb_context *)
+tdb_delete: int (struct tdb_context *, TDB_DATA)
+tdb_dump_all: void (struct tdb_context *)
+tdb_enable_seqnum: void (struct tdb_context *)
+tdb_error: enum TDB_ERROR (struct tdb_context *)
+tdb_errorstr: const char *(struct tdb_context *)
+tdb_exists: int (struct tdb_context *, TDB_DATA)
+tdb_fd: int (struct tdb_context *)
+tdb_fetch: TDB_DATA (struct tdb_context *, TDB_DATA)
+tdb_firstkey: TDB_DATA (struct tdb_context *)
+tdb_freelist_size: int (struct tdb_context *)
+tdb_get_flags: int (struct tdb_context *)
+tdb_get_logging_private: void *(struct tdb_context *)
+tdb_get_seqnum: int (struct tdb_context *)
+tdb_hash_size: int (struct tdb_context *)
+tdb_increment_seqnum_nonblock: void (struct tdb_context *)
+tdb_jenkins_hash: unsigned int (TDB_DATA *)
+tdb_lockall: int (struct tdb_context *)
+tdb_lockall_mark: int (struct tdb_context *)
+tdb_lockall_nonblock: int (struct tdb_context *)
+tdb_lockall_read: int (struct tdb_context *)
+tdb_lockall_read_nonblock: int (struct tdb_context *)
+tdb_lockall_unmark: int (struct tdb_context *)
+tdb_log_fn: tdb_log_func (struct tdb_context *)
+tdb_map_size: size_t (struct tdb_context *)
+tdb_name: const char *(struct tdb_context *)
+tdb_nextkey: TDB_DATA (struct tdb_context *, TDB_DATA)
+tdb_null: dptr = 0xXXXX, dsize = 0
+tdb_open: struct tdb_context *(const char *, int, int, int, mode_t)
+tdb_open_ex: struct tdb_context *(const char *, int, int, int, mode_t, const struct tdb_logging_context *, tdb_hash_func)
+tdb_parse_record: int (struct tdb_context *, TDB_DATA, int (*)(TDB_DATA, TDB_DATA, void *), void *)
+tdb_printfreelist: int (struct tdb_context *)
+tdb_remove_flags: void (struct tdb_context *, unsigned int)
+tdb_reopen: int (struct tdb_context *)
+tdb_reopen_all: int (int)
+tdb_repack: int (struct tdb_context *)
+tdb_set_logging_function: void (struct tdb_context *, const struct tdb_logging_context *)
+tdb_set_max_dead: void (struct tdb_context *, int)
+tdb_setalarm_sigptr: void (struct tdb_context *, volatile sig_atomic_t *)
+tdb_store: int (struct tdb_context *, TDB_DATA, TDB_DATA, int)
+tdb_transaction_cancel: int (struct tdb_context *)
+tdb_transaction_commit: int (struct tdb_context *)
+tdb_transaction_prepare_commit: int (struct tdb_context *)
+tdb_transaction_start: int (struct tdb_context *)
+tdb_transaction_start_nonblock: int (struct tdb_context *)
+tdb_traverse: int (struct tdb_context *, tdb_traverse_func, void *)
+tdb_traverse_read: int (struct tdb_context *, tdb_traverse_func, void *)
+tdb_unlockall: int (struct tdb_context *)
+tdb_unlockall_read: int (struct tdb_context *)
+tdb_validate_freelist: int (struct tdb_context *, int *)
+tdb_wipe_all: int (struct tdb_context *)
diff --git a/lib/tdb/ABI/tdb-1.2.6.sigs b/lib/tdb/ABI/tdb-1.2.6.sigs
new file mode 100644
index 0000000..1e01f3b
--- /dev/null
+++ b/lib/tdb/ABI/tdb-1.2.6.sigs
@@ -0,0 +1,61 @@
+tdb_add_flags: void (struct tdb_context *, unsigned int)
+tdb_append: int (struct tdb_context *, TDB_DATA, TDB_DATA)
+tdb_chainlock: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_mark: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_nonblock: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_read: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_unmark: int (struct tdb_context *, TDB_DATA)
+tdb_chainunlock: int (struct tdb_context *, TDB_DATA)
+tdb_chainunlock_read: int (struct tdb_context *, TDB_DATA)
+tdb_check: int (struct tdb_context *, int (*)(TDB_DATA, TDB_DATA, void *), void *)
+tdb_close: int (struct tdb_context *)
+tdb_delete: int (struct tdb_context *, TDB_DATA)
+tdb_dump_all: void (struct tdb_context *)
+tdb_enable_seqnum: void (struct tdb_context *)
+tdb_error: enum TDB_ERROR (struct tdb_context *)
+tdb_errorstr: const char *(struct tdb_context *)
+tdb_exists: int (struct tdb_context *, TDB_DATA)
+tdb_fd: int (struct tdb_context *)
+tdb_fetch: TDB_DATA (struct tdb_context *, TDB_DATA)
+tdb_firstkey: TDB_DATA (struct tdb_context *)
+tdb_freelist_size: int (struct tdb_context *)
+tdb_get_flags: int (struct tdb_context *)
+tdb_get_logging_private: void *(struct tdb_context *)
+tdb_get_seqnum: int (struct tdb_context *)
+tdb_hash_size: int (struct tdb_context *)
+tdb_increment_seqnum_nonblock: void (struct tdb_context *)
+tdb_jenkins_hash: unsigned int (TDB_DATA *)
+tdb_lockall: int (struct tdb_context *)
+tdb_lockall_mark: int (struct tdb_context *)
+tdb_lockall_nonblock: int (struct tdb_context *)
+tdb_lockall_read: int (struct tdb_context *)
+tdb_lockall_read_nonblock: int (struct tdb_context *)
+tdb_lockall_unmark: int (struct tdb_context *)
+tdb_log_fn: tdb_log_func (struct tdb_context *)
+tdb_map_size: size_t (struct tdb_context *)
+tdb_name: const char *(struct tdb_context *)
+tdb_nextkey: TDB_DATA (struct tdb_context *, TDB_DATA)
+tdb_null: dptr = 0xXXXX, dsize = 0
+tdb_open: struct tdb_context *(const char *, int, int, int, mode_t)
+tdb_open_ex: struct tdb_context *(const char *, int, int, int, mode_t, const struct tdb_logging_context *, tdb_hash_func)
+tdb_parse_record: int (struct tdb_context *, TDB_DATA, int (*)(TDB_DATA, TDB_DATA, void *), void *)
+tdb_printfreelist: int (struct tdb_context *)
+tdb_remove_flags: void (struct tdb_context *, unsigned int)
+tdb_reopen: int (struct tdb_context *)
+tdb_reopen_all: int (int)
+tdb_repack: int (struct tdb_context *)
+tdb_set_logging_function: void (struct tdb_context *, const struct tdb_logging_context *)
+tdb_set_max_dead: void (struct tdb_context *, int)
+tdb_setalarm_sigptr: void (struct tdb_context *, volatile sig_atomic_t *)
+tdb_store: int (struct tdb_context *, TDB_DATA, TDB_DATA, int)
+tdb_transaction_cancel: int (struct tdb_context *)
+tdb_transaction_commit: int (struct tdb_context *)
+tdb_transaction_prepare_commit: int (struct tdb_context *)
+tdb_transaction_start: int (struct tdb_context *)
+tdb_transaction_start_nonblock: int (struct tdb_context *)
+tdb_traverse: int (struct tdb_context *, tdb_traverse_func, void *)
+tdb_traverse_read: int (struct tdb_context *, tdb_traverse_func, void *)
+tdb_unlockall: int (struct tdb_context *)
+tdb_unlockall_read: int (struct tdb_context *)
+tdb_validate_freelist: int (struct tdb_context *, int *)
+tdb_wipe_all: int (struct tdb_context *)
diff --git a/lib/tdb/ABI/tdb-1.2.7.sigs b/lib/tdb/ABI/tdb-1.2.7.sigs
new file mode 100644
index 0000000..1e01f3b
--- /dev/null
+++ b/lib/tdb/ABI/tdb-1.2.7.sigs
@@ -0,0 +1,61 @@
+tdb_add_flags: void (struct tdb_context *, unsigned int)
+tdb_append: int (struct tdb_context *, TDB_DATA, TDB_DATA)
+tdb_chainlock: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_mark: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_nonblock: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_read: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_unmark: int (struct tdb_context *, TDB_DATA)
+tdb_chainunlock: int (struct tdb_context *, TDB_DATA)
+tdb_chainunlock_read: int (struct tdb_context *, TDB_DATA)
+tdb_check: int (struct tdb_context *, int (*)(TDB_DATA, TDB_DATA, void *), void *)
+tdb_close: int (struct tdb_context *)
+tdb_delete: int (struct tdb_context *, TDB_DATA)
+tdb_dump_all: void (struct tdb_context *)
+tdb_enable_seqnum: void (struct tdb_context *)
+tdb_error: enum TDB_ERROR (struct tdb_context *)
+tdb_errorstr: const char *(struct tdb_context *)
+tdb_exists: int (struct tdb_context *, TDB_DATA)
+tdb_fd: int (struct tdb_context *)
+tdb_fetch: TDB_DATA (struct tdb_context *, TDB_DATA)
+tdb_firstkey: TDB_DATA (struct tdb_context *)
+tdb_freelist_size: int (struct tdb_context *)
+tdb_get_flags: int (struct tdb_context *)
+tdb_get_logging_private: void *(struct tdb_context *)
+tdb_get_seqnum: int (struct tdb_context *)
+tdb_hash_size: int (struct tdb_context *)
+tdb_increment_seqnum_nonblock: void (struct tdb_context *)
+tdb_jenkins_hash: unsigned int (TDB_DATA *)
+tdb_lockall: int (struct tdb_context *)
+tdb_lockall_mark: int (struct tdb_context *)
+tdb_lockall_nonblock: int (struct tdb_context *)
+tdb_lockall_read: int (struct tdb_context *)
+tdb_lockall_read_nonblock: int (struct tdb_context *)
+tdb_lockall_unmark: int (struct tdb_context *)
+tdb_log_fn: tdb_log_func (struct tdb_context *)
+tdb_map_size: size_t (struct tdb_context *)
+tdb_name: const char *(struct tdb_context *)
+tdb_nextkey: TDB_DATA (struct tdb_context *, TDB_DATA)
+tdb_null: dptr = 0xXXXX, dsize = 0
+tdb_open: struct tdb_context *(const char *, int, int, int, mode_t)
+tdb_open_ex: struct tdb_context *(const char *, int, int, int, mode_t, const struct tdb_logging_context *, tdb_hash_func)
+tdb_parse_record: int (struct tdb_context *, TDB_DATA, int (*)(TDB_DATA, TDB_DATA, void *), void *)
+tdb_printfreelist: int (struct tdb_context *)
+tdb_remove_flags: void (struct tdb_context *, unsigned int)
+tdb_reopen: int (struct tdb_context *)
+tdb_reopen_all: int (int)
+tdb_repack: int (struct tdb_context *)
+tdb_set_logging_function: void (struct tdb_context *, const struct tdb_logging_context *)
+tdb_set_max_dead: void (struct tdb_context *, int)
+tdb_setalarm_sigptr: void (struct tdb_context *, volatile sig_atomic_t *)
+tdb_store: int (struct tdb_context *, TDB_DATA, TDB_DATA, int)
+tdb_transaction_cancel: int (struct tdb_context *)
+tdb_transaction_commit: int (struct tdb_context *)
+tdb_transaction_prepare_commit: int (struct tdb_context *)
+tdb_transaction_start: int (struct tdb_context *)
+tdb_transaction_start_nonblock: int (struct tdb_context *)
+tdb_traverse: int (struct tdb_context *, tdb_traverse_func, void *)
+tdb_traverse_read: int (struct tdb_context *, tdb_traverse_func, void *)
+tdb_unlockall: int (struct tdb_context *)
+tdb_unlockall_read: int (struct tdb_context *)
+tdb_validate_freelist: int (struct tdb_context *, int *)
+tdb_wipe_all: int (struct tdb_context *)
diff --git a/lib/tdb/ABI/tdb-1.2.8.sigs b/lib/tdb/ABI/tdb-1.2.8.sigs
new file mode 100644
index 0000000..1e01f3b
--- /dev/null
+++ b/lib/tdb/ABI/tdb-1.2.8.sigs
@@ -0,0 +1,61 @@
+tdb_add_flags: void (struct tdb_context *, unsigned int)
+tdb_append: int (struct tdb_context *, TDB_DATA, TDB_DATA)
+tdb_chainlock: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_mark: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_nonblock: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_read: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_unmark: int (struct tdb_context *, TDB_DATA)
+tdb_chainunlock: int (struct tdb_context *, TDB_DATA)
+tdb_chainunlock_read: int (struct tdb_context *, TDB_DATA)
+tdb_check: int (struct tdb_context *, int (*)(TDB_DATA, TDB_DATA, void *), void *)
+tdb_close: int (struct tdb_context *)
+tdb_delete: int (struct tdb_context *, TDB_DATA)
+tdb_dump_all: void (struct tdb_context *)
+tdb_enable_seqnum: void (struct tdb_context *)
+tdb_error: enum TDB_ERROR (struct tdb_context *)
+tdb_errorstr: const char *(struct tdb_context *)
+tdb_exists: int (struct tdb_context *, TDB_DATA)
+tdb_fd: int (struct tdb_context *)
+tdb_fetch: TDB_DATA (struct tdb_context *, TDB_DATA)
+tdb_firstkey: TDB_DATA (struct tdb_context *)
+tdb_freelist_size: int (struct tdb_context *)
+tdb_get_flags: int (struct tdb_context *)
+tdb_get_logging_private: void *(struct tdb_context *)
+tdb_get_seqnum: int (struct tdb_context *)
+tdb_hash_size: int (struct tdb_context *)
+tdb_increment_seqnum_nonblock: void (struct tdb_context *)
+tdb_jenkins_hash: unsigned int (TDB_DATA *)
+tdb_lockall: int (struct tdb_context *)
+tdb_lockall_mark: int (struct tdb_context *)
+tdb_lockall_nonblock: int (struct tdb_context *)
+tdb_lockall_read: int (struct tdb_context *)
+tdb_lockall_read_nonblock: int (struct tdb_context *)
+tdb_lockall_unmark: int (struct tdb_context *)
+tdb_log_fn: tdb_log_func (struct tdb_context *)
+tdb_map_size: size_t (struct tdb_context *)
+tdb_name: const char *(struct tdb_context *)
+tdb_nextkey: TDB_DATA (struct tdb_context *, TDB_DATA)
+tdb_null: dptr = 0xXXXX, dsize = 0
+tdb_open: struct tdb_context *(const char *, int, int, int, mode_t)
+tdb_open_ex: struct tdb_context *(const char *, int, int, int, mode_t, const struct tdb_logging_context *, tdb_hash_func)
+tdb_parse_record: int (struct tdb_context *, TDB_DATA, int (*)(TDB_DATA, TDB_DATA, void *), void *)
+tdb_printfreelist: int (struct tdb_context *)
+tdb_remove_flags: void (struct tdb_context *, unsigned int)
+tdb_reopen: int (struct tdb_context *)
+tdb_reopen_all: int (int)
+tdb_repack: int (struct tdb_context *)
+tdb_set_logging_function: void (struct tdb_context *, const struct tdb_logging_context *)
+tdb_set_max_dead: void (struct tdb_context *, int)
+tdb_setalarm_sigptr: void (struct tdb_context *, volatile sig_atomic_t *)
+tdb_store: int (struct tdb_context *, TDB_DATA, TDB_DATA, int)
+tdb_transaction_cancel: int (struct tdb_context *)
+tdb_transaction_commit: int (struct tdb_context *)
+tdb_transaction_prepare_commit: int (struct tdb_context *)
+tdb_transaction_start: int (struct tdb_context *)
+tdb_transaction_start_nonblock: int (struct tdb_context *)
+tdb_traverse: int (struct tdb_context *, tdb_traverse_func, void *)
+tdb_traverse_read: int (struct tdb_context *, tdb_traverse_func, void *)
+tdb_unlockall: int (struct tdb_context *)
+tdb_unlockall_read: int (struct tdb_context *)
+tdb_validate_freelist: int (struct tdb_context *, int *)
+tdb_wipe_all: int (struct tdb_context *)
diff --git a/lib/tdb/ABI/tdb-1.2.9.sigs b/lib/tdb/ABI/tdb-1.2.9.sigs
new file mode 100644
index 0000000..9e4149b
--- /dev/null
+++ b/lib/tdb/ABI/tdb-1.2.9.sigs
@@ -0,0 +1,62 @@
+tdb_add_flags: void (struct tdb_context *, unsigned int)
+tdb_append: int (struct tdb_context *, TDB_DATA, TDB_DATA)
+tdb_chainlock: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_mark: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_nonblock: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_read: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_unmark: int (struct tdb_context *, TDB_DATA)
+tdb_chainunlock: int (struct tdb_context *, TDB_DATA)
+tdb_chainunlock_read: int (struct tdb_context *, TDB_DATA)
+tdb_check: int (struct tdb_context *, int (*)(TDB_DATA, TDB_DATA, void *), void *)
+tdb_close: int (struct tdb_context *)
+tdb_delete: int (struct tdb_context *, TDB_DATA)
+tdb_dump_all: void (struct tdb_context *)
+tdb_enable_seqnum: void (struct tdb_context *)
+tdb_error: enum TDB_ERROR (struct tdb_context *)
+tdb_errorstr: const char *(struct tdb_context *)
+tdb_exists: int (struct tdb_context *, TDB_DATA)
+tdb_fd: int (struct tdb_context *)
+tdb_fetch: TDB_DATA (struct tdb_context *, TDB_DATA)
+tdb_firstkey: TDB_DATA (struct tdb_context *)
+tdb_freelist_size: int (struct tdb_context *)
+tdb_get_flags: int (struct tdb_context *)
+tdb_get_logging_private: void *(struct tdb_context *)
+tdb_get_seqnum: int (struct tdb_context *)
+tdb_hash_size: int (struct tdb_context *)
+tdb_increment_seqnum_nonblock: void (struct tdb_context *)
+tdb_jenkins_hash: unsigned int (TDB_DATA *)
+tdb_lockall: int (struct tdb_context *)
+tdb_lockall_mark: int (struct tdb_context *)
+tdb_lockall_nonblock: int (struct tdb_context *)
+tdb_lockall_read: int (struct tdb_context *)
+tdb_lockall_read_nonblock: int (struct tdb_context *)
+tdb_lockall_unmark: int (struct tdb_context *)
+tdb_log_fn: tdb_log_func (struct tdb_context *)
+tdb_map_size: size_t (struct tdb_context *)
+tdb_name: const char *(struct tdb_context *)
+tdb_nextkey: TDB_DATA (struct tdb_context *, TDB_DATA)
+tdb_null: dptr = 0xXXXX, dsize = 0
+tdb_open: struct tdb_context *(const char *, int, int, int, mode_t)
+tdb_open_ex: struct tdb_context *(const char *, int, int, int, mode_t, const struct tdb_logging_context *, tdb_hash_func)
+tdb_parse_record: int (struct tdb_context *, TDB_DATA, int (*)(TDB_DATA, TDB_DATA, void *), void *)
+tdb_printfreelist: int (struct tdb_context *)
+tdb_remove_flags: void (struct tdb_context *, unsigned int)
+tdb_reopen: int (struct tdb_context *)
+tdb_reopen_all: int (int)
+tdb_repack: int (struct tdb_context *)
+tdb_set_logging_function: void (struct tdb_context *, const struct tdb_logging_context *)
+tdb_set_max_dead: void (struct tdb_context *, int)
+tdb_setalarm_sigptr: void (struct tdb_context *, volatile sig_atomic_t *)
+tdb_store: int (struct tdb_context *, TDB_DATA, TDB_DATA, int)
+tdb_summary: char *(struct tdb_context *)
+tdb_transaction_cancel: int (struct tdb_context *)
+tdb_transaction_commit: int (struct tdb_context *)
+tdb_transaction_prepare_commit: int (struct tdb_context *)
+tdb_transaction_start: int (struct tdb_context *)
+tdb_transaction_start_nonblock: int (struct tdb_context *)
+tdb_traverse: int (struct tdb_context *, tdb_traverse_func, void *)
+tdb_traverse_read: int (struct tdb_context *, tdb_traverse_func, void *)
+tdb_unlockall: int (struct tdb_context *)
+tdb_unlockall_read: int (struct tdb_context *)
+tdb_validate_freelist: int (struct tdb_context *, int *)
+tdb_wipe_all: int (struct tdb_context *)
diff --git a/lib/tdb/ABI/tdb-1.3.0.sigs b/lib/tdb/ABI/tdb-1.3.0.sigs
new file mode 100644
index 0000000..7d3e469
--- /dev/null
+++ b/lib/tdb/ABI/tdb-1.3.0.sigs
@@ -0,0 +1,68 @@
+tdb_add_flags: void (struct tdb_context *, unsigned int)
+tdb_append: int (struct tdb_context *, TDB_DATA, TDB_DATA)
+tdb_chainlock: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_mark: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_nonblock: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_read: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_unmark: int (struct tdb_context *, TDB_DATA)
+tdb_chainunlock: int (struct tdb_context *, TDB_DATA)
+tdb_chainunlock_read: int (struct tdb_context *, TDB_DATA)
+tdb_check: int (struct tdb_context *, int (*)(TDB_DATA, TDB_DATA, void *), void *)
+tdb_close: int (struct tdb_context *)
+tdb_delete: int (struct tdb_context *, TDB_DATA)
+tdb_dump_all: void (struct tdb_context *)
+tdb_enable_seqnum: void (struct tdb_context *)
+tdb_error: enum TDB_ERROR (struct tdb_context *)
+tdb_errorstr: const char *(struct tdb_context *)
+tdb_exists: int (struct tdb_context *, TDB_DATA)
+tdb_fd: int (struct tdb_context *)
+tdb_fetch: TDB_DATA (struct tdb_context *, TDB_DATA)
+tdb_firstkey: TDB_DATA (struct tdb_context *)
+tdb_freelist_size: int (struct tdb_context *)
+tdb_get_flags: int (struct tdb_context *)
+tdb_get_logging_private: void *(struct tdb_context *)
+tdb_get_seqnum: int (struct tdb_context *)
+tdb_hash_size: int (struct tdb_context *)
+tdb_increment_seqnum_nonblock: void (struct tdb_context *)
+tdb_jenkins_hash: unsigned int (TDB_DATA *)
+tdb_lock_nonblock: int (struct tdb_context *, int, int)
+tdb_lockall: int (struct tdb_context *)
+tdb_lockall_mark: int (struct tdb_context *)
+tdb_lockall_nonblock: int (struct tdb_context *)
+tdb_lockall_read: int (struct tdb_context *)
+tdb_lockall_read_nonblock: int (struct tdb_context *)
+tdb_lockall_unmark: int (struct tdb_context *)
+tdb_log_fn: tdb_log_func (struct tdb_context *)
+tdb_map_size: size_t (struct tdb_context *)
+tdb_name: const char *(struct tdb_context *)
+tdb_nextkey: TDB_DATA (struct tdb_context *, TDB_DATA)
+tdb_null: dptr = 0xXXXX, dsize = 0
+tdb_open: struct tdb_context *(const char *, int, int, int, mode_t)
+tdb_open_ex: struct tdb_context *(const char *, int, int, int, mode_t, const struct tdb_logging_context *, tdb_hash_func)
+tdb_parse_record: int (struct tdb_context *, TDB_DATA, int (*)(TDB_DATA, TDB_DATA, void *), void *)
+tdb_printfreelist: int (struct tdb_context *)
+tdb_remove_flags: void (struct tdb_context *, unsigned int)
+tdb_reopen: int (struct tdb_context *)
+tdb_reopen_all: int (int)
+tdb_repack: int (struct tdb_context *)
+tdb_rescue: int (struct tdb_context *, void (*)(TDB_DATA, TDB_DATA, void *), void *)
+tdb_runtime_check_for_robust_mutexes: bool (void)
+tdb_set_logging_function: void (struct tdb_context *, const struct tdb_logging_context *)
+tdb_set_max_dead: void (struct tdb_context *, int)
+tdb_setalarm_sigptr: void (struct tdb_context *, volatile sig_atomic_t *)
+tdb_store: int (struct tdb_context *, TDB_DATA, TDB_DATA, int)
+tdb_summary: char *(struct tdb_context *)
+tdb_transaction_cancel: int (struct tdb_context *)
+tdb_transaction_commit: int (struct tdb_context *)
+tdb_transaction_prepare_commit: int (struct tdb_context *)
+tdb_transaction_start: int (struct tdb_context *)
+tdb_transaction_start_nonblock: int (struct tdb_context *)
+tdb_transaction_write_lock_mark: int (struct tdb_context *)
+tdb_transaction_write_lock_unmark: int (struct tdb_context *)
+tdb_traverse: int (struct tdb_context *, tdb_traverse_func, void *)
+tdb_traverse_read: int (struct tdb_context *, tdb_traverse_func, void *)
+tdb_unlock: int (struct tdb_context *, int, int)
+tdb_unlockall: int (struct tdb_context *)
+tdb_unlockall_read: int (struct tdb_context *)
+tdb_validate_freelist: int (struct tdb_context *, int *)
+tdb_wipe_all: int (struct tdb_context *)
diff --git a/lib/tdb/ABI/tdb-1.3.1.sigs b/lib/tdb/ABI/tdb-1.3.1.sigs
new file mode 100644
index 0000000..7d3e469
--- /dev/null
+++ b/lib/tdb/ABI/tdb-1.3.1.sigs
@@ -0,0 +1,68 @@
+tdb_add_flags: void (struct tdb_context *, unsigned int)
+tdb_append: int (struct tdb_context *, TDB_DATA, TDB_DATA)
+tdb_chainlock: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_mark: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_nonblock: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_read: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_unmark: int (struct tdb_context *, TDB_DATA)
+tdb_chainunlock: int (struct tdb_context *, TDB_DATA)
+tdb_chainunlock_read: int (struct tdb_context *, TDB_DATA)
+tdb_check: int (struct tdb_context *, int (*)(TDB_DATA, TDB_DATA, void *), void *)
+tdb_close: int (struct tdb_context *)
+tdb_delete: int (struct tdb_context *, TDB_DATA)
+tdb_dump_all: void (struct tdb_context *)
+tdb_enable_seqnum: void (struct tdb_context *)
+tdb_error: enum TDB_ERROR (struct tdb_context *)
+tdb_errorstr: const char *(struct tdb_context *)
+tdb_exists: int (struct tdb_context *, TDB_DATA)
+tdb_fd: int (struct tdb_context *)
+tdb_fetch: TDB_DATA (struct tdb_context *, TDB_DATA)
+tdb_firstkey: TDB_DATA (struct tdb_context *)
+tdb_freelist_size: int (struct tdb_context *)
+tdb_get_flags: int (struct tdb_context *)
+tdb_get_logging_private: void *(struct tdb_context *)
+tdb_get_seqnum: int (struct tdb_context *)
+tdb_hash_size: int (struct tdb_context *)
+tdb_increment_seqnum_nonblock: void (struct tdb_context *)
+tdb_jenkins_hash: unsigned int (TDB_DATA *)
+tdb_lock_nonblock: int (struct tdb_context *, int, int)
+tdb_lockall: int (struct tdb_context *)
+tdb_lockall_mark: int (struct tdb_context *)
+tdb_lockall_nonblock: int (struct tdb_context *)
+tdb_lockall_read: int (struct tdb_context *)
+tdb_lockall_read_nonblock: int (struct tdb_context *)
+tdb_lockall_unmark: int (struct tdb_context *)
+tdb_log_fn: tdb_log_func (struct tdb_context *)
+tdb_map_size: size_t (struct tdb_context *)
+tdb_name: const char *(struct tdb_context *)
+tdb_nextkey: TDB_DATA (struct tdb_context *, TDB_DATA)
+tdb_null: dptr = 0xXXXX, dsize = 0
+tdb_open: struct tdb_context *(const char *, int, int, int, mode_t)
+tdb_open_ex: struct tdb_context *(const char *, int, int, int, mode_t, const struct tdb_logging_context *, tdb_hash_func)
+tdb_parse_record: int (struct tdb_context *, TDB_DATA, int (*)(TDB_DATA, TDB_DATA, void *), void *)
+tdb_printfreelist: int (struct tdb_context *)
+tdb_remove_flags: void (struct tdb_context *, unsigned int)
+tdb_reopen: int (struct tdb_context *)
+tdb_reopen_all: int (int)
+tdb_repack: int (struct tdb_context *)
+tdb_rescue: int (struct tdb_context *, void (*)(TDB_DATA, TDB_DATA, void *), void *)
+tdb_runtime_check_for_robust_mutexes: bool (void)
+tdb_set_logging_function: void (struct tdb_context *, const struct tdb_logging_context *)
+tdb_set_max_dead: void (struct tdb_context *, int)
+tdb_setalarm_sigptr: void (struct tdb_context *, volatile sig_atomic_t *)
+tdb_store: int (struct tdb_context *, TDB_DATA, TDB_DATA, int)
+tdb_summary: char *(struct tdb_context *)
+tdb_transaction_cancel: int (struct tdb_context *)
+tdb_transaction_commit: int (struct tdb_context *)
+tdb_transaction_prepare_commit: int (struct tdb_context *)
+tdb_transaction_start: int (struct tdb_context *)
+tdb_transaction_start_nonblock: int (struct tdb_context *)
+tdb_transaction_write_lock_mark: int (struct tdb_context *)
+tdb_transaction_write_lock_unmark: int (struct tdb_context *)
+tdb_traverse: int (struct tdb_context *, tdb_traverse_func, void *)
+tdb_traverse_read: int (struct tdb_context *, tdb_traverse_func, void *)
+tdb_unlock: int (struct tdb_context *, int, int)
+tdb_unlockall: int (struct tdb_context *)
+tdb_unlockall_read: int (struct tdb_context *)
+tdb_validate_freelist: int (struct tdb_context *, int *)
+tdb_wipe_all: int (struct tdb_context *)
diff --git a/lib/tdb/ABI/tdb-1.3.10.sigs b/lib/tdb/ABI/tdb-1.3.10.sigs
new file mode 100644
index 0000000..2545c99
--- /dev/null
+++ b/lib/tdb/ABI/tdb-1.3.10.sigs
@@ -0,0 +1,69 @@
+tdb_add_flags: void (struct tdb_context *, unsigned int)
+tdb_append: int (struct tdb_context *, TDB_DATA, TDB_DATA)
+tdb_chainlock: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_mark: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_nonblock: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_read: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_read_nonblock: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_unmark: int (struct tdb_context *, TDB_DATA)
+tdb_chainunlock: int (struct tdb_context *, TDB_DATA)
+tdb_chainunlock_read: int (struct tdb_context *, TDB_DATA)
+tdb_check: int (struct tdb_context *, int (*)(TDB_DATA, TDB_DATA, void *), void *)
+tdb_close: int (struct tdb_context *)
+tdb_delete: int (struct tdb_context *, TDB_DATA)
+tdb_dump_all: void (struct tdb_context *)
+tdb_enable_seqnum: void (struct tdb_context *)
+tdb_error: enum TDB_ERROR (struct tdb_context *)
+tdb_errorstr: const char *(struct tdb_context *)
+tdb_exists: int (struct tdb_context *, TDB_DATA)
+tdb_fd: int (struct tdb_context *)
+tdb_fetch: TDB_DATA (struct tdb_context *, TDB_DATA)
+tdb_firstkey: TDB_DATA (struct tdb_context *)
+tdb_freelist_size: int (struct tdb_context *)
+tdb_get_flags: int (struct tdb_context *)
+tdb_get_logging_private: void *(struct tdb_context *)
+tdb_get_seqnum: int (struct tdb_context *)
+tdb_hash_size: int (struct tdb_context *)
+tdb_increment_seqnum_nonblock: void (struct tdb_context *)
+tdb_jenkins_hash: unsigned int (TDB_DATA *)
+tdb_lock_nonblock: int (struct tdb_context *, int, int)
+tdb_lockall: int (struct tdb_context *)
+tdb_lockall_mark: int (struct tdb_context *)
+tdb_lockall_nonblock: int (struct tdb_context *)
+tdb_lockall_read: int (struct tdb_context *)
+tdb_lockall_read_nonblock: int (struct tdb_context *)
+tdb_lockall_unmark: int (struct tdb_context *)
+tdb_log_fn: tdb_log_func (struct tdb_context *)
+tdb_map_size: size_t (struct tdb_context *)
+tdb_name: const char *(struct tdb_context *)
+tdb_nextkey: TDB_DATA (struct tdb_context *, TDB_DATA)
+tdb_null: dptr = 0xXXXX, dsize = 0
+tdb_open: struct tdb_context *(const char *, int, int, int, mode_t)
+tdb_open_ex: struct tdb_context *(const char *, int, int, int, mode_t, const struct tdb_logging_context *, tdb_hash_func)
+tdb_parse_record: int (struct tdb_context *, TDB_DATA, int (*)(TDB_DATA, TDB_DATA, void *), void *)
+tdb_printfreelist: int (struct tdb_context *)
+tdb_remove_flags: void (struct tdb_context *, unsigned int)
+tdb_reopen: int (struct tdb_context *)
+tdb_reopen_all: int (int)
+tdb_repack: int (struct tdb_context *)
+tdb_rescue: int (struct tdb_context *, void (*)(TDB_DATA, TDB_DATA, void *), void *)
+tdb_runtime_check_for_robust_mutexes: bool (void)
+tdb_set_logging_function: void (struct tdb_context *, const struct tdb_logging_context *)
+tdb_set_max_dead: void (struct tdb_context *, int)
+tdb_setalarm_sigptr: void (struct tdb_context *, volatile sig_atomic_t *)
+tdb_store: int (struct tdb_context *, TDB_DATA, TDB_DATA, int)
+tdb_summary: char *(struct tdb_context *)
+tdb_transaction_cancel: int (struct tdb_context *)
+tdb_transaction_commit: int (struct tdb_context *)
+tdb_transaction_prepare_commit: int (struct tdb_context *)
+tdb_transaction_start: int (struct tdb_context *)
+tdb_transaction_start_nonblock: int (struct tdb_context *)
+tdb_transaction_write_lock_mark: int (struct tdb_context *)
+tdb_transaction_write_lock_unmark: int (struct tdb_context *)
+tdb_traverse: int (struct tdb_context *, tdb_traverse_func, void *)
+tdb_traverse_read: int (struct tdb_context *, tdb_traverse_func, void *)
+tdb_unlock: int (struct tdb_context *, int, int)
+tdb_unlockall: int (struct tdb_context *)
+tdb_unlockall_read: int (struct tdb_context *)
+tdb_validate_freelist: int (struct tdb_context *, int *)
+tdb_wipe_all: int (struct tdb_context *)
diff --git a/lib/tdb/ABI/tdb-1.3.11.sigs b/lib/tdb/ABI/tdb-1.3.11.sigs
new file mode 100644
index 0000000..48f4278
--- /dev/null
+++ b/lib/tdb/ABI/tdb-1.3.11.sigs
@@ -0,0 +1,70 @@
+tdb_add_flags: void (struct tdb_context *, unsigned int)
+tdb_append: int (struct tdb_context *, TDB_DATA, TDB_DATA)
+tdb_chainlock: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_mark: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_nonblock: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_read: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_read_nonblock: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_unmark: int (struct tdb_context *, TDB_DATA)
+tdb_chainunlock: int (struct tdb_context *, TDB_DATA)
+tdb_chainunlock_read: int (struct tdb_context *, TDB_DATA)
+tdb_check: int (struct tdb_context *, int (*)(TDB_DATA, TDB_DATA, void *), void *)
+tdb_close: int (struct tdb_context *)
+tdb_delete: int (struct tdb_context *, TDB_DATA)
+tdb_dump_all: void (struct tdb_context *)
+tdb_enable_seqnum: void (struct tdb_context *)
+tdb_error: enum TDB_ERROR (struct tdb_context *)
+tdb_errorstr: const char *(struct tdb_context *)
+tdb_exists: int (struct tdb_context *, TDB_DATA)
+tdb_fd: int (struct tdb_context *)
+tdb_fetch: TDB_DATA (struct tdb_context *, TDB_DATA)
+tdb_firstkey: TDB_DATA (struct tdb_context *)
+tdb_freelist_size: int (struct tdb_context *)
+tdb_get_flags: int (struct tdb_context *)
+tdb_get_logging_private: void *(struct tdb_context *)
+tdb_get_seqnum: int (struct tdb_context *)
+tdb_hash_size: int (struct tdb_context *)
+tdb_increment_seqnum_nonblock: void (struct tdb_context *)
+tdb_jenkins_hash: unsigned int (TDB_DATA *)
+tdb_lock_nonblock: int (struct tdb_context *, int, int)
+tdb_lockall: int (struct tdb_context *)
+tdb_lockall_mark: int (struct tdb_context *)
+tdb_lockall_nonblock: int (struct tdb_context *)
+tdb_lockall_read: int (struct tdb_context *)
+tdb_lockall_read_nonblock: int (struct tdb_context *)
+tdb_lockall_unmark: int (struct tdb_context *)
+tdb_log_fn: tdb_log_func (struct tdb_context *)
+tdb_map_size: size_t (struct tdb_context *)
+tdb_name: const char *(struct tdb_context *)
+tdb_nextkey: TDB_DATA (struct tdb_context *, TDB_DATA)
+tdb_null: dptr = 0xXXXX, dsize = 0
+tdb_open: struct tdb_context *(const char *, int, int, int, mode_t)
+tdb_open_ex: struct tdb_context *(const char *, int, int, int, mode_t, const struct tdb_logging_context *, tdb_hash_func)
+tdb_parse_record: int (struct tdb_context *, TDB_DATA, int (*)(TDB_DATA, TDB_DATA, void *), void *)
+tdb_printfreelist: int (struct tdb_context *)
+tdb_remove_flags: void (struct tdb_context *, unsigned int)
+tdb_reopen: int (struct tdb_context *)
+tdb_reopen_all: int (int)
+tdb_repack: int (struct tdb_context *)
+tdb_rescue: int (struct tdb_context *, void (*)(TDB_DATA, TDB_DATA, void *), void *)
+tdb_runtime_check_for_robust_mutexes: bool (void)
+tdb_set_logging_function: void (struct tdb_context *, const struct tdb_logging_context *)
+tdb_set_max_dead: void (struct tdb_context *, int)
+tdb_setalarm_sigptr: void (struct tdb_context *, volatile sig_atomic_t *)
+tdb_store: int (struct tdb_context *, TDB_DATA, TDB_DATA, int)
+tdb_storev: int (struct tdb_context *, TDB_DATA, const TDB_DATA *, int, int)
+tdb_summary: char *(struct tdb_context *)
+tdb_transaction_cancel: int (struct tdb_context *)
+tdb_transaction_commit: int (struct tdb_context *)
+tdb_transaction_prepare_commit: int (struct tdb_context *)
+tdb_transaction_start: int (struct tdb_context *)
+tdb_transaction_start_nonblock: int (struct tdb_context *)
+tdb_transaction_write_lock_mark: int (struct tdb_context *)
+tdb_transaction_write_lock_unmark: int (struct tdb_context *)
+tdb_traverse: int (struct tdb_context *, tdb_traverse_func, void *)
+tdb_traverse_read: int (struct tdb_context *, tdb_traverse_func, void *)
+tdb_unlock: int (struct tdb_context *, int, int)
+tdb_unlockall: int (struct tdb_context *)
+tdb_unlockall_read: int (struct tdb_context *)
+tdb_validate_freelist: int (struct tdb_context *, int *)
+tdb_wipe_all: int (struct tdb_context *)
diff --git a/lib/tdb/ABI/tdb-1.3.12.sigs b/lib/tdb/ABI/tdb-1.3.12.sigs
new file mode 100644
index 0000000..48f4278
--- /dev/null
+++ b/lib/tdb/ABI/tdb-1.3.12.sigs
@@ -0,0 +1,70 @@
+tdb_add_flags: void (struct tdb_context *, unsigned int)
+tdb_append: int (struct tdb_context *, TDB_DATA, TDB_DATA)
+tdb_chainlock: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_mark: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_nonblock: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_read: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_read_nonblock: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_unmark: int (struct tdb_context *, TDB_DATA)
+tdb_chainunlock: int (struct tdb_context *, TDB_DATA)
+tdb_chainunlock_read: int (struct tdb_context *, TDB_DATA)
+tdb_check: int (struct tdb_context *, int (*)(TDB_DATA, TDB_DATA, void *), void *)
+tdb_close: int (struct tdb_context *)
+tdb_delete: int (struct tdb_context *, TDB_DATA)
+tdb_dump_all: void (struct tdb_context *)
+tdb_enable_seqnum: void (struct tdb_context *)
+tdb_error: enum TDB_ERROR (struct tdb_context *)
+tdb_errorstr: const char *(struct tdb_context *)
+tdb_exists: int (struct tdb_context *, TDB_DATA)
+tdb_fd: int (struct tdb_context *)
+tdb_fetch: TDB_DATA (struct tdb_context *, TDB_DATA)
+tdb_firstkey: TDB_DATA (struct tdb_context *)
+tdb_freelist_size: int (struct tdb_context *)
+tdb_get_flags: int (struct tdb_context *)
+tdb_get_logging_private: void *(struct tdb_context *)
+tdb_get_seqnum: int (struct tdb_context *)
+tdb_hash_size: int (struct tdb_context *)
+tdb_increment_seqnum_nonblock: void (struct tdb_context *)
+tdb_jenkins_hash: unsigned int (TDB_DATA *)
+tdb_lock_nonblock: int (struct tdb_context *, int, int)
+tdb_lockall: int (struct tdb_context *)
+tdb_lockall_mark: int (struct tdb_context *)
+tdb_lockall_nonblock: int (struct tdb_context *)
+tdb_lockall_read: int (struct tdb_context *)
+tdb_lockall_read_nonblock: int (struct tdb_context *)
+tdb_lockall_unmark: int (struct tdb_context *)
+tdb_log_fn: tdb_log_func (struct tdb_context *)
+tdb_map_size: size_t (struct tdb_context *)
+tdb_name: const char *(struct tdb_context *)
+tdb_nextkey: TDB_DATA (struct tdb_context *, TDB_DATA)
+tdb_null: dptr = 0xXXXX, dsize = 0
+tdb_open: struct tdb_context *(const char *, int, int, int, mode_t)
+tdb_open_ex: struct tdb_context *(const char *, int, int, int, mode_t, const struct tdb_logging_context *, tdb_hash_func)
+tdb_parse_record: int (struct tdb_context *, TDB_DATA, int (*)(TDB_DATA, TDB_DATA, void *), void *)
+tdb_printfreelist: int (struct tdb_context *)
+tdb_remove_flags: void (struct tdb_context *, unsigned int)
+tdb_reopen: int (struct tdb_context *)
+tdb_reopen_all: int (int)
+tdb_repack: int (struct tdb_context *)
+tdb_rescue: int (struct tdb_context *, void (*)(TDB_DATA, TDB_DATA, void *), void *)
+tdb_runtime_check_for_robust_mutexes: bool (void)
+tdb_set_logging_function: void (struct tdb_context *, const struct tdb_logging_context *)
+tdb_set_max_dead: void (struct tdb_context *, int)
+tdb_setalarm_sigptr: void (struct tdb_context *, volatile sig_atomic_t *)
+tdb_store: int (struct tdb_context *, TDB_DATA, TDB_DATA, int)
+tdb_storev: int (struct tdb_context *, TDB_DATA, const TDB_DATA *, int, int)
+tdb_summary: char *(struct tdb_context *)
+tdb_transaction_cancel: int (struct tdb_context *)
+tdb_transaction_commit: int (struct tdb_context *)
+tdb_transaction_prepare_commit: int (struct tdb_context *)
+tdb_transaction_start: int (struct tdb_context *)
+tdb_transaction_start_nonblock: int (struct tdb_context *)
+tdb_transaction_write_lock_mark: int (struct tdb_context *)
+tdb_transaction_write_lock_unmark: int (struct tdb_context *)
+tdb_traverse: int (struct tdb_context *, tdb_traverse_func, void *)
+tdb_traverse_read: int (struct tdb_context *, tdb_traverse_func, void *)
+tdb_unlock: int (struct tdb_context *, int, int)
+tdb_unlockall: int (struct tdb_context *)
+tdb_unlockall_read: int (struct tdb_context *)
+tdb_validate_freelist: int (struct tdb_context *, int *)
+tdb_wipe_all: int (struct tdb_context *)
diff --git a/lib/tdb/ABI/tdb-1.3.13.sigs b/lib/tdb/ABI/tdb-1.3.13.sigs
new file mode 100644
index 0000000..48f4278
--- /dev/null
+++ b/lib/tdb/ABI/tdb-1.3.13.sigs
@@ -0,0 +1,70 @@
+tdb_add_flags: void (struct tdb_context *, unsigned int)
+tdb_append: int (struct tdb_context *, TDB_DATA, TDB_DATA)
+tdb_chainlock: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_mark: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_nonblock: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_read: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_read_nonblock: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_unmark: int (struct tdb_context *, TDB_DATA)
+tdb_chainunlock: int (struct tdb_context *, TDB_DATA)
+tdb_chainunlock_read: int (struct tdb_context *, TDB_DATA)
+tdb_check: int (struct tdb_context *, int (*)(TDB_DATA, TDB_DATA, void *), void *)
+tdb_close: int (struct tdb_context *)
+tdb_delete: int (struct tdb_context *, TDB_DATA)
+tdb_dump_all: void (struct tdb_context *)
+tdb_enable_seqnum: void (struct tdb_context *)
+tdb_error: enum TDB_ERROR (struct tdb_context *)
+tdb_errorstr: const char *(struct tdb_context *)
+tdb_exists: int (struct tdb_context *, TDB_DATA)
+tdb_fd: int (struct tdb_context *)
+tdb_fetch: TDB_DATA (struct tdb_context *, TDB_DATA)
+tdb_firstkey: TDB_DATA (struct tdb_context *)
+tdb_freelist_size: int (struct tdb_context *)
+tdb_get_flags: int (struct tdb_context *)
+tdb_get_logging_private: void *(struct tdb_context *)
+tdb_get_seqnum: int (struct tdb_context *)
+tdb_hash_size: int (struct tdb_context *)
+tdb_increment_seqnum_nonblock: void (struct tdb_context *)
+tdb_jenkins_hash: unsigned int (TDB_DATA *)
+tdb_lock_nonblock: int (struct tdb_context *, int, int)
+tdb_lockall: int (struct tdb_context *)
+tdb_lockall_mark: int (struct tdb_context *)
+tdb_lockall_nonblock: int (struct tdb_context *)
+tdb_lockall_read: int (struct tdb_context *)
+tdb_lockall_read_nonblock: int (struct tdb_context *)
+tdb_lockall_unmark: int (struct tdb_context *)
+tdb_log_fn: tdb_log_func (struct tdb_context *)
+tdb_map_size: size_t (struct tdb_context *)
+tdb_name: const char *(struct tdb_context *)
+tdb_nextkey: TDB_DATA (struct tdb_context *, TDB_DATA)
+tdb_null: dptr = 0xXXXX, dsize = 0
+tdb_open: struct tdb_context *(const char *, int, int, int, mode_t)
+tdb_open_ex: struct tdb_context *(const char *, int, int, int, mode_t, const struct tdb_logging_context *, tdb_hash_func)
+tdb_parse_record: int (struct tdb_context *, TDB_DATA, int (*)(TDB_DATA, TDB_DATA, void *), void *)
+tdb_printfreelist: int (struct tdb_context *)
+tdb_remove_flags: void (struct tdb_context *, unsigned int)
+tdb_reopen: int (struct tdb_context *)
+tdb_reopen_all: int (int)
+tdb_repack: int (struct tdb_context *)
+tdb_rescue: int (struct tdb_context *, void (*)(TDB_DATA, TDB_DATA, void *), void *)
+tdb_runtime_check_for_robust_mutexes: bool (void)
+tdb_set_logging_function: void (struct tdb_context *, const struct tdb_logging_context *)
+tdb_set_max_dead: void (struct tdb_context *, int)
+tdb_setalarm_sigptr: void (struct tdb_context *, volatile sig_atomic_t *)
+tdb_store: int (struct tdb_context *, TDB_DATA, TDB_DATA, int)
+tdb_storev: int (struct tdb_context *, TDB_DATA, const TDB_DATA *, int, int)
+tdb_summary: char *(struct tdb_context *)
+tdb_transaction_cancel: int (struct tdb_context *)
+tdb_transaction_commit: int (struct tdb_context *)
+tdb_transaction_prepare_commit: int (struct tdb_context *)
+tdb_transaction_start: int (struct tdb_context *)
+tdb_transaction_start_nonblock: int (struct tdb_context *)
+tdb_transaction_write_lock_mark: int (struct tdb_context *)
+tdb_transaction_write_lock_unmark: int (struct tdb_context *)
+tdb_traverse: int (struct tdb_context *, tdb_traverse_func, void *)
+tdb_traverse_read: int (struct tdb_context *, tdb_traverse_func, void *)
+tdb_unlock: int (struct tdb_context *, int, int)
+tdb_unlockall: int (struct tdb_context *)
+tdb_unlockall_read: int (struct tdb_context *)
+tdb_validate_freelist: int (struct tdb_context *, int *)
+tdb_wipe_all: int (struct tdb_context *)
diff --git a/lib/tdb/ABI/tdb-1.3.14.sigs b/lib/tdb/ABI/tdb-1.3.14.sigs
new file mode 100644
index 0000000..61ce5e6
--- /dev/null
+++ b/lib/tdb/ABI/tdb-1.3.14.sigs
@@ -0,0 +1,71 @@
+tdb_add_flags: void (struct tdb_context *, unsigned int)
+tdb_append: int (struct tdb_context *, TDB_DATA, TDB_DATA)
+tdb_chainlock: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_mark: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_nonblock: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_read: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_read_nonblock: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_unmark: int (struct tdb_context *, TDB_DATA)
+tdb_chainunlock: int (struct tdb_context *, TDB_DATA)
+tdb_chainunlock_read: int (struct tdb_context *, TDB_DATA)
+tdb_check: int (struct tdb_context *, int (*)(TDB_DATA, TDB_DATA, void *), void *)
+tdb_close: int (struct tdb_context *)
+tdb_delete: int (struct tdb_context *, TDB_DATA)
+tdb_dump_all: void (struct tdb_context *)
+tdb_enable_seqnum: void (struct tdb_context *)
+tdb_error: enum TDB_ERROR (struct tdb_context *)
+tdb_errorstr: const char *(struct tdb_context *)
+tdb_exists: int (struct tdb_context *, TDB_DATA)
+tdb_fd: int (struct tdb_context *)
+tdb_fetch: TDB_DATA (struct tdb_context *, TDB_DATA)
+tdb_firstkey: TDB_DATA (struct tdb_context *)
+tdb_freelist_size: int (struct tdb_context *)
+tdb_get_flags: int (struct tdb_context *)
+tdb_get_logging_private: void *(struct tdb_context *)
+tdb_get_seqnum: int (struct tdb_context *)
+tdb_hash_size: int (struct tdb_context *)
+tdb_increment_seqnum_nonblock: void (struct tdb_context *)
+tdb_jenkins_hash: unsigned int (TDB_DATA *)
+tdb_lock_nonblock: int (struct tdb_context *, int, int)
+tdb_lockall: int (struct tdb_context *)
+tdb_lockall_mark: int (struct tdb_context *)
+tdb_lockall_nonblock: int (struct tdb_context *)
+tdb_lockall_read: int (struct tdb_context *)
+tdb_lockall_read_nonblock: int (struct tdb_context *)
+tdb_lockall_unmark: int (struct tdb_context *)
+tdb_log_fn: tdb_log_func (struct tdb_context *)
+tdb_map_size: size_t (struct tdb_context *)
+tdb_name: const char *(struct tdb_context *)
+tdb_nextkey: TDB_DATA (struct tdb_context *, TDB_DATA)
+tdb_null: dptr = 0xXXXX, dsize = 0
+tdb_open: struct tdb_context *(const char *, int, int, int, mode_t)
+tdb_open_ex: struct tdb_context *(const char *, int, int, int, mode_t, const struct tdb_logging_context *, tdb_hash_func)
+tdb_parse_record: int (struct tdb_context *, TDB_DATA, int (*)(TDB_DATA, TDB_DATA, void *), void *)
+tdb_printfreelist: int (struct tdb_context *)
+tdb_remove_flags: void (struct tdb_context *, unsigned int)
+tdb_reopen: int (struct tdb_context *)
+tdb_reopen_all: int (int)
+tdb_repack: int (struct tdb_context *)
+tdb_rescue: int (struct tdb_context *, void (*)(TDB_DATA, TDB_DATA, void *), void *)
+tdb_runtime_check_for_robust_mutexes: bool (void)
+tdb_set_logging_function: void (struct tdb_context *, const struct tdb_logging_context *)
+tdb_set_max_dead: void (struct tdb_context *, int)
+tdb_setalarm_sigptr: void (struct tdb_context *, volatile sig_atomic_t *)
+tdb_store: int (struct tdb_context *, TDB_DATA, TDB_DATA, int)
+tdb_storev: int (struct tdb_context *, TDB_DATA, const TDB_DATA *, int, int)
+tdb_summary: char *(struct tdb_context *)
+tdb_transaction_active: bool (struct tdb_context *)
+tdb_transaction_cancel: int (struct tdb_context *)
+tdb_transaction_commit: int (struct tdb_context *)
+tdb_transaction_prepare_commit: int (struct tdb_context *)
+tdb_transaction_start: int (struct tdb_context *)
+tdb_transaction_start_nonblock: int (struct tdb_context *)
+tdb_transaction_write_lock_mark: int (struct tdb_context *)
+tdb_transaction_write_lock_unmark: int (struct tdb_context *)
+tdb_traverse: int (struct tdb_context *, tdb_traverse_func, void *)
+tdb_traverse_read: int (struct tdb_context *, tdb_traverse_func, void *)
+tdb_unlock: int (struct tdb_context *, int, int)
+tdb_unlockall: int (struct tdb_context *)
+tdb_unlockall_read: int (struct tdb_context *)
+tdb_validate_freelist: int (struct tdb_context *, int *)
+tdb_wipe_all: int (struct tdb_context *)
diff --git a/lib/tdb/ABI/tdb-1.3.15.sigs b/lib/tdb/ABI/tdb-1.3.15.sigs
new file mode 100644
index 0000000..61ce5e6
--- /dev/null
+++ b/lib/tdb/ABI/tdb-1.3.15.sigs
@@ -0,0 +1,71 @@
+tdb_add_flags: void (struct tdb_context *, unsigned int)
+tdb_append: int (struct tdb_context *, TDB_DATA, TDB_DATA)
+tdb_chainlock: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_mark: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_nonblock: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_read: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_read_nonblock: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_unmark: int (struct tdb_context *, TDB_DATA)
+tdb_chainunlock: int (struct tdb_context *, TDB_DATA)
+tdb_chainunlock_read: int (struct tdb_context *, TDB_DATA)
+tdb_check: int (struct tdb_context *, int (*)(TDB_DATA, TDB_DATA, void *), void *)
+tdb_close: int (struct tdb_context *)
+tdb_delete: int (struct tdb_context *, TDB_DATA)
+tdb_dump_all: void (struct tdb_context *)
+tdb_enable_seqnum: void (struct tdb_context *)
+tdb_error: enum TDB_ERROR (struct tdb_context *)
+tdb_errorstr: const char *(struct tdb_context *)
+tdb_exists: int (struct tdb_context *, TDB_DATA)
+tdb_fd: int (struct tdb_context *)
+tdb_fetch: TDB_DATA (struct tdb_context *, TDB_DATA)
+tdb_firstkey: TDB_DATA (struct tdb_context *)
+tdb_freelist_size: int (struct tdb_context *)
+tdb_get_flags: int (struct tdb_context *)
+tdb_get_logging_private: void *(struct tdb_context *)
+tdb_get_seqnum: int (struct tdb_context *)
+tdb_hash_size: int (struct tdb_context *)
+tdb_increment_seqnum_nonblock: void (struct tdb_context *)
+tdb_jenkins_hash: unsigned int (TDB_DATA *)
+tdb_lock_nonblock: int (struct tdb_context *, int, int)
+tdb_lockall: int (struct tdb_context *)
+tdb_lockall_mark: int (struct tdb_context *)
+tdb_lockall_nonblock: int (struct tdb_context *)
+tdb_lockall_read: int (struct tdb_context *)
+tdb_lockall_read_nonblock: int (struct tdb_context *)
+tdb_lockall_unmark: int (struct tdb_context *)
+tdb_log_fn: tdb_log_func (struct tdb_context *)
+tdb_map_size: size_t (struct tdb_context *)
+tdb_name: const char *(struct tdb_context *)
+tdb_nextkey: TDB_DATA (struct tdb_context *, TDB_DATA)
+tdb_null: dptr = 0xXXXX, dsize = 0
+tdb_open: struct tdb_context *(const char *, int, int, int, mode_t)
+tdb_open_ex: struct tdb_context *(const char *, int, int, int, mode_t, const struct tdb_logging_context *, tdb_hash_func)
+tdb_parse_record: int (struct tdb_context *, TDB_DATA, int (*)(TDB_DATA, TDB_DATA, void *), void *)
+tdb_printfreelist: int (struct tdb_context *)
+tdb_remove_flags: void (struct tdb_context *, unsigned int)
+tdb_reopen: int (struct tdb_context *)
+tdb_reopen_all: int (int)
+tdb_repack: int (struct tdb_context *)
+tdb_rescue: int (struct tdb_context *, void (*)(TDB_DATA, TDB_DATA, void *), void *)
+tdb_runtime_check_for_robust_mutexes: bool (void)
+tdb_set_logging_function: void (struct tdb_context *, const struct tdb_logging_context *)
+tdb_set_max_dead: void (struct tdb_context *, int)
+tdb_setalarm_sigptr: void (struct tdb_context *, volatile sig_atomic_t *)
+tdb_store: int (struct tdb_context *, TDB_DATA, TDB_DATA, int)
+tdb_storev: int (struct tdb_context *, TDB_DATA, const TDB_DATA *, int, int)
+tdb_summary: char *(struct tdb_context *)
+tdb_transaction_active: bool (struct tdb_context *)
+tdb_transaction_cancel: int (struct tdb_context *)
+tdb_transaction_commit: int (struct tdb_context *)
+tdb_transaction_prepare_commit: int (struct tdb_context *)
+tdb_transaction_start: int (struct tdb_context *)
+tdb_transaction_start_nonblock: int (struct tdb_context *)
+tdb_transaction_write_lock_mark: int (struct tdb_context *)
+tdb_transaction_write_lock_unmark: int (struct tdb_context *)
+tdb_traverse: int (struct tdb_context *, tdb_traverse_func, void *)
+tdb_traverse_read: int (struct tdb_context *, tdb_traverse_func, void *)
+tdb_unlock: int (struct tdb_context *, int, int)
+tdb_unlockall: int (struct tdb_context *)
+tdb_unlockall_read: int (struct tdb_context *)
+tdb_validate_freelist: int (struct tdb_context *, int *)
+tdb_wipe_all: int (struct tdb_context *)
diff --git a/lib/tdb/ABI/tdb-1.3.16.sigs b/lib/tdb/ABI/tdb-1.3.16.sigs
new file mode 100644
index 0000000..61ce5e6
--- /dev/null
+++ b/lib/tdb/ABI/tdb-1.3.16.sigs
@@ -0,0 +1,71 @@
+tdb_add_flags: void (struct tdb_context *, unsigned int)
+tdb_append: int (struct tdb_context *, TDB_DATA, TDB_DATA)
+tdb_chainlock: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_mark: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_nonblock: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_read: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_read_nonblock: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_unmark: int (struct tdb_context *, TDB_DATA)
+tdb_chainunlock: int (struct tdb_context *, TDB_DATA)
+tdb_chainunlock_read: int (struct tdb_context *, TDB_DATA)
+tdb_check: int (struct tdb_context *, int (*)(TDB_DATA, TDB_DATA, void *), void *)
+tdb_close: int (struct tdb_context *)
+tdb_delete: int (struct tdb_context *, TDB_DATA)
+tdb_dump_all: void (struct tdb_context *)
+tdb_enable_seqnum: void (struct tdb_context *)
+tdb_error: enum TDB_ERROR (struct tdb_context *)
+tdb_errorstr: const char *(struct tdb_context *)
+tdb_exists: int (struct tdb_context *, TDB_DATA)
+tdb_fd: int (struct tdb_context *)
+tdb_fetch: TDB_DATA (struct tdb_context *, TDB_DATA)
+tdb_firstkey: TDB_DATA (struct tdb_context *)
+tdb_freelist_size: int (struct tdb_context *)
+tdb_get_flags: int (struct tdb_context *)
+tdb_get_logging_private: void *(struct tdb_context *)
+tdb_get_seqnum: int (struct tdb_context *)
+tdb_hash_size: int (struct tdb_context *)
+tdb_increment_seqnum_nonblock: void (struct tdb_context *)
+tdb_jenkins_hash: unsigned int (TDB_DATA *)
+tdb_lock_nonblock: int (struct tdb_context *, int, int)
+tdb_lockall: int (struct tdb_context *)
+tdb_lockall_mark: int (struct tdb_context *)
+tdb_lockall_nonblock: int (struct tdb_context *)
+tdb_lockall_read: int (struct tdb_context *)
+tdb_lockall_read_nonblock: int (struct tdb_context *)
+tdb_lockall_unmark: int (struct tdb_context *)
+tdb_log_fn: tdb_log_func (struct tdb_context *)
+tdb_map_size: size_t (struct tdb_context *)
+tdb_name: const char *(struct tdb_context *)
+tdb_nextkey: TDB_DATA (struct tdb_context *, TDB_DATA)
+tdb_null: dptr = 0xXXXX, dsize = 0
+tdb_open: struct tdb_context *(const char *, int, int, int, mode_t)
+tdb_open_ex: struct tdb_context *(const char *, int, int, int, mode_t, const struct tdb_logging_context *, tdb_hash_func)
+tdb_parse_record: int (struct tdb_context *, TDB_DATA, int (*)(TDB_DATA, TDB_DATA, void *), void *)
+tdb_printfreelist: int (struct tdb_context *)
+tdb_remove_flags: void (struct tdb_context *, unsigned int)
+tdb_reopen: int (struct tdb_context *)
+tdb_reopen_all: int (int)
+tdb_repack: int (struct tdb_context *)
+tdb_rescue: int (struct tdb_context *, void (*)(TDB_DATA, TDB_DATA, void *), void *)
+tdb_runtime_check_for_robust_mutexes: bool (void)
+tdb_set_logging_function: void (struct tdb_context *, const struct tdb_logging_context *)
+tdb_set_max_dead: void (struct tdb_context *, int)
+tdb_setalarm_sigptr: void (struct tdb_context *, volatile sig_atomic_t *)
+tdb_store: int (struct tdb_context *, TDB_DATA, TDB_DATA, int)
+tdb_storev: int (struct tdb_context *, TDB_DATA, const TDB_DATA *, int, int)
+tdb_summary: char *(struct tdb_context *)
+tdb_transaction_active: bool (struct tdb_context *)
+tdb_transaction_cancel: int (struct tdb_context *)
+tdb_transaction_commit: int (struct tdb_context *)
+tdb_transaction_prepare_commit: int (struct tdb_context *)
+tdb_transaction_start: int (struct tdb_context *)
+tdb_transaction_start_nonblock: int (struct tdb_context *)
+tdb_transaction_write_lock_mark: int (struct tdb_context *)
+tdb_transaction_write_lock_unmark: int (struct tdb_context *)
+tdb_traverse: int (struct tdb_context *, tdb_traverse_func, void *)
+tdb_traverse_read: int (struct tdb_context *, tdb_traverse_func, void *)
+tdb_unlock: int (struct tdb_context *, int, int)
+tdb_unlockall: int (struct tdb_context *)
+tdb_unlockall_read: int (struct tdb_context *)
+tdb_validate_freelist: int (struct tdb_context *, int *)
+tdb_wipe_all: int (struct tdb_context *)
diff --git a/lib/tdb/ABI/tdb-1.3.17.sigs b/lib/tdb/ABI/tdb-1.3.17.sigs
new file mode 100644
index 0000000..e2b0427
--- /dev/null
+++ b/lib/tdb/ABI/tdb-1.3.17.sigs
@@ -0,0 +1,73 @@
+tdb_add_flags: void (struct tdb_context *, unsigned int)
+tdb_append: int (struct tdb_context *, TDB_DATA, TDB_DATA)
+tdb_chainlock: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_mark: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_nonblock: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_read: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_read_nonblock: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_unmark: int (struct tdb_context *, TDB_DATA)
+tdb_chainunlock: int (struct tdb_context *, TDB_DATA)
+tdb_chainunlock_read: int (struct tdb_context *, TDB_DATA)
+tdb_check: int (struct tdb_context *, int (*)(TDB_DATA, TDB_DATA, void *), void *)
+tdb_close: int (struct tdb_context *)
+tdb_delete: int (struct tdb_context *, TDB_DATA)
+tdb_dump_all: void (struct tdb_context *)
+tdb_enable_seqnum: void (struct tdb_context *)
+tdb_error: enum TDB_ERROR (struct tdb_context *)
+tdb_errorstr: const char *(struct tdb_context *)
+tdb_exists: int (struct tdb_context *, TDB_DATA)
+tdb_fd: int (struct tdb_context *)
+tdb_fetch: TDB_DATA (struct tdb_context *, TDB_DATA)
+tdb_firstkey: TDB_DATA (struct tdb_context *)
+tdb_freelist_size: int (struct tdb_context *)
+tdb_get_flags: int (struct tdb_context *)
+tdb_get_logging_private: void *(struct tdb_context *)
+tdb_get_seqnum: int (struct tdb_context *)
+tdb_hash_size: int (struct tdb_context *)
+tdb_increment_seqnum_nonblock: void (struct tdb_context *)
+tdb_jenkins_hash: unsigned int (TDB_DATA *)
+tdb_lock_nonblock: int (struct tdb_context *, int, int)
+tdb_lockall: int (struct tdb_context *)
+tdb_lockall_mark: int (struct tdb_context *)
+tdb_lockall_nonblock: int (struct tdb_context *)
+tdb_lockall_read: int (struct tdb_context *)
+tdb_lockall_read_nonblock: int (struct tdb_context *)
+tdb_lockall_unmark: int (struct tdb_context *)
+tdb_log_fn: tdb_log_func (struct tdb_context *)
+tdb_map_size: size_t (struct tdb_context *)
+tdb_name: const char *(struct tdb_context *)
+tdb_nextkey: TDB_DATA (struct tdb_context *, TDB_DATA)
+tdb_null: dptr = 0xXXXX, dsize = 0
+tdb_open: struct tdb_context *(const char *, int, int, int, mode_t)
+tdb_open_ex: struct tdb_context *(const char *, int, int, int, mode_t, const struct tdb_logging_context *, tdb_hash_func)
+tdb_parse_record: int (struct tdb_context *, TDB_DATA, int (*)(TDB_DATA, TDB_DATA, void *), void *)
+tdb_printfreelist: int (struct tdb_context *)
+tdb_remove_flags: void (struct tdb_context *, unsigned int)
+tdb_reopen: int (struct tdb_context *)
+tdb_reopen_all: int (int)
+tdb_repack: int (struct tdb_context *)
+tdb_rescue: int (struct tdb_context *, void (*)(TDB_DATA, TDB_DATA, void *), void *)
+tdb_runtime_check_for_robust_mutexes: bool (void)
+tdb_set_logging_function: void (struct tdb_context *, const struct tdb_logging_context *)
+tdb_set_max_dead: void (struct tdb_context *, int)
+tdb_setalarm_sigptr: void (struct tdb_context *, volatile sig_atomic_t *)
+tdb_store: int (struct tdb_context *, TDB_DATA, TDB_DATA, int)
+tdb_storev: int (struct tdb_context *, TDB_DATA, const TDB_DATA *, int, int)
+tdb_summary: char *(struct tdb_context *)
+tdb_transaction_active: bool (struct tdb_context *)
+tdb_transaction_cancel: int (struct tdb_context *)
+tdb_transaction_commit: int (struct tdb_context *)
+tdb_transaction_prepare_commit: int (struct tdb_context *)
+tdb_transaction_start: int (struct tdb_context *)
+tdb_transaction_start_nonblock: int (struct tdb_context *)
+tdb_transaction_write_lock_mark: int (struct tdb_context *)
+tdb_transaction_write_lock_unmark: int (struct tdb_context *)
+tdb_traverse: int (struct tdb_context *, tdb_traverse_func, void *)
+tdb_traverse_chain: int (struct tdb_context *, unsigned int, tdb_traverse_func, void *)
+tdb_traverse_key_chain: int (struct tdb_context *, TDB_DATA, tdb_traverse_func, void *)
+tdb_traverse_read: int (struct tdb_context *, tdb_traverse_func, void *)
+tdb_unlock: int (struct tdb_context *, int, int)
+tdb_unlockall: int (struct tdb_context *)
+tdb_unlockall_read: int (struct tdb_context *)
+tdb_validate_freelist: int (struct tdb_context *, int *)
+tdb_wipe_all: int (struct tdb_context *)
diff --git a/lib/tdb/ABI/tdb-1.3.18.sigs b/lib/tdb/ABI/tdb-1.3.18.sigs
new file mode 100644
index 0000000..e2b0427
--- /dev/null
+++ b/lib/tdb/ABI/tdb-1.3.18.sigs
@@ -0,0 +1,73 @@
+tdb_add_flags: void (struct tdb_context *, unsigned int)
+tdb_append: int (struct tdb_context *, TDB_DATA, TDB_DATA)
+tdb_chainlock: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_mark: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_nonblock: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_read: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_read_nonblock: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_unmark: int (struct tdb_context *, TDB_DATA)
+tdb_chainunlock: int (struct tdb_context *, TDB_DATA)
+tdb_chainunlock_read: int (struct tdb_context *, TDB_DATA)
+tdb_check: int (struct tdb_context *, int (*)(TDB_DATA, TDB_DATA, void *), void *)
+tdb_close: int (struct tdb_context *)
+tdb_delete: int (struct tdb_context *, TDB_DATA)
+tdb_dump_all: void (struct tdb_context *)
+tdb_enable_seqnum: void (struct tdb_context *)
+tdb_error: enum TDB_ERROR (struct tdb_context *)
+tdb_errorstr: const char *(struct tdb_context *)
+tdb_exists: int (struct tdb_context *, TDB_DATA)
+tdb_fd: int (struct tdb_context *)
+tdb_fetch: TDB_DATA (struct tdb_context *, TDB_DATA)
+tdb_firstkey: TDB_DATA (struct tdb_context *)
+tdb_freelist_size: int (struct tdb_context *)
+tdb_get_flags: int (struct tdb_context *)
+tdb_get_logging_private: void *(struct tdb_context *)
+tdb_get_seqnum: int (struct tdb_context *)
+tdb_hash_size: int (struct tdb_context *)
+tdb_increment_seqnum_nonblock: void (struct tdb_context *)
+tdb_jenkins_hash: unsigned int (TDB_DATA *)
+tdb_lock_nonblock: int (struct tdb_context *, int, int)
+tdb_lockall: int (struct tdb_context *)
+tdb_lockall_mark: int (struct tdb_context *)
+tdb_lockall_nonblock: int (struct tdb_context *)
+tdb_lockall_read: int (struct tdb_context *)
+tdb_lockall_read_nonblock: int (struct tdb_context *)
+tdb_lockall_unmark: int (struct tdb_context *)
+tdb_log_fn: tdb_log_func (struct tdb_context *)
+tdb_map_size: size_t (struct tdb_context *)
+tdb_name: const char *(struct tdb_context *)
+tdb_nextkey: TDB_DATA (struct tdb_context *, TDB_DATA)
+tdb_null: dptr = 0xXXXX, dsize = 0
+tdb_open: struct tdb_context *(const char *, int, int, int, mode_t)
+tdb_open_ex: struct tdb_context *(const char *, int, int, int, mode_t, const struct tdb_logging_context *, tdb_hash_func)
+tdb_parse_record: int (struct tdb_context *, TDB_DATA, int (*)(TDB_DATA, TDB_DATA, void *), void *)
+tdb_printfreelist: int (struct tdb_context *)
+tdb_remove_flags: void (struct tdb_context *, unsigned int)
+tdb_reopen: int (struct tdb_context *)
+tdb_reopen_all: int (int)
+tdb_repack: int (struct tdb_context *)
+tdb_rescue: int (struct tdb_context *, void (*)(TDB_DATA, TDB_DATA, void *), void *)
+tdb_runtime_check_for_robust_mutexes: bool (void)
+tdb_set_logging_function: void (struct tdb_context *, const struct tdb_logging_context *)
+tdb_set_max_dead: void (struct tdb_context *, int)
+tdb_setalarm_sigptr: void (struct tdb_context *, volatile sig_atomic_t *)
+tdb_store: int (struct tdb_context *, TDB_DATA, TDB_DATA, int)
+tdb_storev: int (struct tdb_context *, TDB_DATA, const TDB_DATA *, int, int)
+tdb_summary: char *(struct tdb_context *)
+tdb_transaction_active: bool (struct tdb_context *)
+tdb_transaction_cancel: int (struct tdb_context *)
+tdb_transaction_commit: int (struct tdb_context *)
+tdb_transaction_prepare_commit: int (struct tdb_context *)
+tdb_transaction_start: int (struct tdb_context *)
+tdb_transaction_start_nonblock: int (struct tdb_context *)
+tdb_transaction_write_lock_mark: int (struct tdb_context *)
+tdb_transaction_write_lock_unmark: int (struct tdb_context *)
+tdb_traverse: int (struct tdb_context *, tdb_traverse_func, void *)
+tdb_traverse_chain: int (struct tdb_context *, unsigned int, tdb_traverse_func, void *)
+tdb_traverse_key_chain: int (struct tdb_context *, TDB_DATA, tdb_traverse_func, void *)
+tdb_traverse_read: int (struct tdb_context *, tdb_traverse_func, void *)
+tdb_unlock: int (struct tdb_context *, int, int)
+tdb_unlockall: int (struct tdb_context *)
+tdb_unlockall_read: int (struct tdb_context *)
+tdb_validate_freelist: int (struct tdb_context *, int *)
+tdb_wipe_all: int (struct tdb_context *)
diff --git a/lib/tdb/ABI/tdb-1.3.2.sigs b/lib/tdb/ABI/tdb-1.3.2.sigs
new file mode 100644
index 0000000..7d3e469
--- /dev/null
+++ b/lib/tdb/ABI/tdb-1.3.2.sigs
@@ -0,0 +1,68 @@
+tdb_add_flags: void (struct tdb_context *, unsigned int)
+tdb_append: int (struct tdb_context *, TDB_DATA, TDB_DATA)
+tdb_chainlock: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_mark: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_nonblock: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_read: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_unmark: int (struct tdb_context *, TDB_DATA)
+tdb_chainunlock: int (struct tdb_context *, TDB_DATA)
+tdb_chainunlock_read: int (struct tdb_context *, TDB_DATA)
+tdb_check: int (struct tdb_context *, int (*)(TDB_DATA, TDB_DATA, void *), void *)
+tdb_close: int (struct tdb_context *)
+tdb_delete: int (struct tdb_context *, TDB_DATA)
+tdb_dump_all: void (struct tdb_context *)
+tdb_enable_seqnum: void (struct tdb_context *)
+tdb_error: enum TDB_ERROR (struct tdb_context *)
+tdb_errorstr: const char *(struct tdb_context *)
+tdb_exists: int (struct tdb_context *, TDB_DATA)
+tdb_fd: int (struct tdb_context *)
+tdb_fetch: TDB_DATA (struct tdb_context *, TDB_DATA)
+tdb_firstkey: TDB_DATA (struct tdb_context *)
+tdb_freelist_size: int (struct tdb_context *)
+tdb_get_flags: int (struct tdb_context *)
+tdb_get_logging_private: void *(struct tdb_context *)
+tdb_get_seqnum: int (struct tdb_context *)
+tdb_hash_size: int (struct tdb_context *)
+tdb_increment_seqnum_nonblock: void (struct tdb_context *)
+tdb_jenkins_hash: unsigned int (TDB_DATA *)
+tdb_lock_nonblock: int (struct tdb_context *, int, int)
+tdb_lockall: int (struct tdb_context *)
+tdb_lockall_mark: int (struct tdb_context *)
+tdb_lockall_nonblock: int (struct tdb_context *)
+tdb_lockall_read: int (struct tdb_context *)
+tdb_lockall_read_nonblock: int (struct tdb_context *)
+tdb_lockall_unmark: int (struct tdb_context *)
+tdb_log_fn: tdb_log_func (struct tdb_context *)
+tdb_map_size: size_t (struct tdb_context *)
+tdb_name: const char *(struct tdb_context *)
+tdb_nextkey: TDB_DATA (struct tdb_context *, TDB_DATA)
+tdb_null: dptr = 0xXXXX, dsize = 0
+tdb_open: struct tdb_context *(const char *, int, int, int, mode_t)
+tdb_open_ex: struct tdb_context *(const char *, int, int, int, mode_t, const struct tdb_logging_context *, tdb_hash_func)
+tdb_parse_record: int (struct tdb_context *, TDB_DATA, int (*)(TDB_DATA, TDB_DATA, void *), void *)
+tdb_printfreelist: int (struct tdb_context *)
+tdb_remove_flags: void (struct tdb_context *, unsigned int)
+tdb_reopen: int (struct tdb_context *)
+tdb_reopen_all: int (int)
+tdb_repack: int (struct tdb_context *)
+tdb_rescue: int (struct tdb_context *, void (*)(TDB_DATA, TDB_DATA, void *), void *)
+tdb_runtime_check_for_robust_mutexes: bool (void)
+tdb_set_logging_function: void (struct tdb_context *, const struct tdb_logging_context *)
+tdb_set_max_dead: void (struct tdb_context *, int)
+tdb_setalarm_sigptr: void (struct tdb_context *, volatile sig_atomic_t *)
+tdb_store: int (struct tdb_context *, TDB_DATA, TDB_DATA, int)
+tdb_summary: char *(struct tdb_context *)
+tdb_transaction_cancel: int (struct tdb_context *)
+tdb_transaction_commit: int (struct tdb_context *)
+tdb_transaction_prepare_commit: int (struct tdb_context *)
+tdb_transaction_start: int (struct tdb_context *)
+tdb_transaction_start_nonblock: int (struct tdb_context *)
+tdb_transaction_write_lock_mark: int (struct tdb_context *)
+tdb_transaction_write_lock_unmark: int (struct tdb_context *)
+tdb_traverse: int (struct tdb_context *, tdb_traverse_func, void *)
+tdb_traverse_read: int (struct tdb_context *, tdb_traverse_func, void *)
+tdb_unlock: int (struct tdb_context *, int, int)
+tdb_unlockall: int (struct tdb_context *)
+tdb_unlockall_read: int (struct tdb_context *)
+tdb_validate_freelist: int (struct tdb_context *, int *)
+tdb_wipe_all: int (struct tdb_context *)
diff --git a/lib/tdb/ABI/tdb-1.3.3.sigs b/lib/tdb/ABI/tdb-1.3.3.sigs
new file mode 100644
index 0000000..7d3e469
--- /dev/null
+++ b/lib/tdb/ABI/tdb-1.3.3.sigs
@@ -0,0 +1,68 @@
+tdb_add_flags: void (struct tdb_context *, unsigned int)
+tdb_append: int (struct tdb_context *, TDB_DATA, TDB_DATA)
+tdb_chainlock: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_mark: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_nonblock: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_read: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_unmark: int (struct tdb_context *, TDB_DATA)
+tdb_chainunlock: int (struct tdb_context *, TDB_DATA)
+tdb_chainunlock_read: int (struct tdb_context *, TDB_DATA)
+tdb_check: int (struct tdb_context *, int (*)(TDB_DATA, TDB_DATA, void *), void *)
+tdb_close: int (struct tdb_context *)
+tdb_delete: int (struct tdb_context *, TDB_DATA)
+tdb_dump_all: void (struct tdb_context *)
+tdb_enable_seqnum: void (struct tdb_context *)
+tdb_error: enum TDB_ERROR (struct tdb_context *)
+tdb_errorstr: const char *(struct tdb_context *)
+tdb_exists: int (struct tdb_context *, TDB_DATA)
+tdb_fd: int (struct tdb_context *)
+tdb_fetch: TDB_DATA (struct tdb_context *, TDB_DATA)
+tdb_firstkey: TDB_DATA (struct tdb_context *)
+tdb_freelist_size: int (struct tdb_context *)
+tdb_get_flags: int (struct tdb_context *)
+tdb_get_logging_private: void *(struct tdb_context *)
+tdb_get_seqnum: int (struct tdb_context *)
+tdb_hash_size: int (struct tdb_context *)
+tdb_increment_seqnum_nonblock: void (struct tdb_context *)
+tdb_jenkins_hash: unsigned int (TDB_DATA *)
+tdb_lock_nonblock: int (struct tdb_context *, int, int)
+tdb_lockall: int (struct tdb_context *)
+tdb_lockall_mark: int (struct tdb_context *)
+tdb_lockall_nonblock: int (struct tdb_context *)
+tdb_lockall_read: int (struct tdb_context *)
+tdb_lockall_read_nonblock: int (struct tdb_context *)
+tdb_lockall_unmark: int (struct tdb_context *)
+tdb_log_fn: tdb_log_func (struct tdb_context *)
+tdb_map_size: size_t (struct tdb_context *)
+tdb_name: const char *(struct tdb_context *)
+tdb_nextkey: TDB_DATA (struct tdb_context *, TDB_DATA)
+tdb_null: dptr = 0xXXXX, dsize = 0
+tdb_open: struct tdb_context *(const char *, int, int, int, mode_t)
+tdb_open_ex: struct tdb_context *(const char *, int, int, int, mode_t, const struct tdb_logging_context *, tdb_hash_func)
+tdb_parse_record: int (struct tdb_context *, TDB_DATA, int (*)(TDB_DATA, TDB_DATA, void *), void *)
+tdb_printfreelist: int (struct tdb_context *)
+tdb_remove_flags: void (struct tdb_context *, unsigned int)
+tdb_reopen: int (struct tdb_context *)
+tdb_reopen_all: int (int)
+tdb_repack: int (struct tdb_context *)
+tdb_rescue: int (struct tdb_context *, void (*)(TDB_DATA, TDB_DATA, void *), void *)
+tdb_runtime_check_for_robust_mutexes: bool (void)
+tdb_set_logging_function: void (struct tdb_context *, const struct tdb_logging_context *)
+tdb_set_max_dead: void (struct tdb_context *, int)
+tdb_setalarm_sigptr: void (struct tdb_context *, volatile sig_atomic_t *)
+tdb_store: int (struct tdb_context *, TDB_DATA, TDB_DATA, int)
+tdb_summary: char *(struct tdb_context *)
+tdb_transaction_cancel: int (struct tdb_context *)
+tdb_transaction_commit: int (struct tdb_context *)
+tdb_transaction_prepare_commit: int (struct tdb_context *)
+tdb_transaction_start: int (struct tdb_context *)
+tdb_transaction_start_nonblock: int (struct tdb_context *)
+tdb_transaction_write_lock_mark: int (struct tdb_context *)
+tdb_transaction_write_lock_unmark: int (struct tdb_context *)
+tdb_traverse: int (struct tdb_context *, tdb_traverse_func, void *)
+tdb_traverse_read: int (struct tdb_context *, tdb_traverse_func, void *)
+tdb_unlock: int (struct tdb_context *, int, int)
+tdb_unlockall: int (struct tdb_context *)
+tdb_unlockall_read: int (struct tdb_context *)
+tdb_validate_freelist: int (struct tdb_context *, int *)
+tdb_wipe_all: int (struct tdb_context *)
diff --git a/lib/tdb/ABI/tdb-1.3.4.sigs b/lib/tdb/ABI/tdb-1.3.4.sigs
new file mode 100644
index 0000000..7d3e469
--- /dev/null
+++ b/lib/tdb/ABI/tdb-1.3.4.sigs
@@ -0,0 +1,68 @@
+tdb_add_flags: void (struct tdb_context *, unsigned int)
+tdb_append: int (struct tdb_context *, TDB_DATA, TDB_DATA)
+tdb_chainlock: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_mark: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_nonblock: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_read: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_unmark: int (struct tdb_context *, TDB_DATA)
+tdb_chainunlock: int (struct tdb_context *, TDB_DATA)
+tdb_chainunlock_read: int (struct tdb_context *, TDB_DATA)
+tdb_check: int (struct tdb_context *, int (*)(TDB_DATA, TDB_DATA, void *), void *)
+tdb_close: int (struct tdb_context *)
+tdb_delete: int (struct tdb_context *, TDB_DATA)
+tdb_dump_all: void (struct tdb_context *)
+tdb_enable_seqnum: void (struct tdb_context *)
+tdb_error: enum TDB_ERROR (struct tdb_context *)
+tdb_errorstr: const char *(struct tdb_context *)
+tdb_exists: int (struct tdb_context *, TDB_DATA)
+tdb_fd: int (struct tdb_context *)
+tdb_fetch: TDB_DATA (struct tdb_context *, TDB_DATA)
+tdb_firstkey: TDB_DATA (struct tdb_context *)
+tdb_freelist_size: int (struct tdb_context *)
+tdb_get_flags: int (struct tdb_context *)
+tdb_get_logging_private: void *(struct tdb_context *)
+tdb_get_seqnum: int (struct tdb_context *)
+tdb_hash_size: int (struct tdb_context *)
+tdb_increment_seqnum_nonblock: void (struct tdb_context *)
+tdb_jenkins_hash: unsigned int (TDB_DATA *)
+tdb_lock_nonblock: int (struct tdb_context *, int, int)
+tdb_lockall: int (struct tdb_context *)
+tdb_lockall_mark: int (struct tdb_context *)
+tdb_lockall_nonblock: int (struct tdb_context *)
+tdb_lockall_read: int (struct tdb_context *)
+tdb_lockall_read_nonblock: int (struct tdb_context *)
+tdb_lockall_unmark: int (struct tdb_context *)
+tdb_log_fn: tdb_log_func (struct tdb_context *)
+tdb_map_size: size_t (struct tdb_context *)
+tdb_name: const char *(struct tdb_context *)
+tdb_nextkey: TDB_DATA (struct tdb_context *, TDB_DATA)
+tdb_null: dptr = 0xXXXX, dsize = 0
+tdb_open: struct tdb_context *(const char *, int, int, int, mode_t)
+tdb_open_ex: struct tdb_context *(const char *, int, int, int, mode_t, const struct tdb_logging_context *, tdb_hash_func)
+tdb_parse_record: int (struct tdb_context *, TDB_DATA, int (*)(TDB_DATA, TDB_DATA, void *), void *)
+tdb_printfreelist: int (struct tdb_context *)
+tdb_remove_flags: void (struct tdb_context *, unsigned int)
+tdb_reopen: int (struct tdb_context *)
+tdb_reopen_all: int (int)
+tdb_repack: int (struct tdb_context *)
+tdb_rescue: int (struct tdb_context *, void (*)(TDB_DATA, TDB_DATA, void *), void *)
+tdb_runtime_check_for_robust_mutexes: bool (void)
+tdb_set_logging_function: void (struct tdb_context *, const struct tdb_logging_context *)
+tdb_set_max_dead: void (struct tdb_context *, int)
+tdb_setalarm_sigptr: void (struct tdb_context *, volatile sig_atomic_t *)
+tdb_store: int (struct tdb_context *, TDB_DATA, TDB_DATA, int)
+tdb_summary: char *(struct tdb_context *)
+tdb_transaction_cancel: int (struct tdb_context *)
+tdb_transaction_commit: int (struct tdb_context *)
+tdb_transaction_prepare_commit: int (struct tdb_context *)
+tdb_transaction_start: int (struct tdb_context *)
+tdb_transaction_start_nonblock: int (struct tdb_context *)
+tdb_transaction_write_lock_mark: int (struct tdb_context *)
+tdb_transaction_write_lock_unmark: int (struct tdb_context *)
+tdb_traverse: int (struct tdb_context *, tdb_traverse_func, void *)
+tdb_traverse_read: int (struct tdb_context *, tdb_traverse_func, void *)
+tdb_unlock: int (struct tdb_context *, int, int)
+tdb_unlockall: int (struct tdb_context *)
+tdb_unlockall_read: int (struct tdb_context *)
+tdb_validate_freelist: int (struct tdb_context *, int *)
+tdb_wipe_all: int (struct tdb_context *)
diff --git a/lib/tdb/ABI/tdb-1.3.5.sigs b/lib/tdb/ABI/tdb-1.3.5.sigs
new file mode 100644
index 0000000..2545c99
--- /dev/null
+++ b/lib/tdb/ABI/tdb-1.3.5.sigs
@@ -0,0 +1,69 @@
+tdb_add_flags: void (struct tdb_context *, unsigned int)
+tdb_append: int (struct tdb_context *, TDB_DATA, TDB_DATA)
+tdb_chainlock: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_mark: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_nonblock: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_read: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_read_nonblock: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_unmark: int (struct tdb_context *, TDB_DATA)
+tdb_chainunlock: int (struct tdb_context *, TDB_DATA)
+tdb_chainunlock_read: int (struct tdb_context *, TDB_DATA)
+tdb_check: int (struct tdb_context *, int (*)(TDB_DATA, TDB_DATA, void *), void *)
+tdb_close: int (struct tdb_context *)
+tdb_delete: int (struct tdb_context *, TDB_DATA)
+tdb_dump_all: void (struct tdb_context *)
+tdb_enable_seqnum: void (struct tdb_context *)
+tdb_error: enum TDB_ERROR (struct tdb_context *)
+tdb_errorstr: const char *(struct tdb_context *)
+tdb_exists: int (struct tdb_context *, TDB_DATA)
+tdb_fd: int (struct tdb_context *)
+tdb_fetch: TDB_DATA (struct tdb_context *, TDB_DATA)
+tdb_firstkey: TDB_DATA (struct tdb_context *)
+tdb_freelist_size: int (struct tdb_context *)
+tdb_get_flags: int (struct tdb_context *)
+tdb_get_logging_private: void *(struct tdb_context *)
+tdb_get_seqnum: int (struct tdb_context *)
+tdb_hash_size: int (struct tdb_context *)
+tdb_increment_seqnum_nonblock: void (struct tdb_context *)
+tdb_jenkins_hash: unsigned int (TDB_DATA *)
+tdb_lock_nonblock: int (struct tdb_context *, int, int)
+tdb_lockall: int (struct tdb_context *)
+tdb_lockall_mark: int (struct tdb_context *)
+tdb_lockall_nonblock: int (struct tdb_context *)
+tdb_lockall_read: int (struct tdb_context *)
+tdb_lockall_read_nonblock: int (struct tdb_context *)
+tdb_lockall_unmark: int (struct tdb_context *)
+tdb_log_fn: tdb_log_func (struct tdb_context *)
+tdb_map_size: size_t (struct tdb_context *)
+tdb_name: const char *(struct tdb_context *)
+tdb_nextkey: TDB_DATA (struct tdb_context *, TDB_DATA)
+tdb_null: dptr = 0xXXXX, dsize = 0
+tdb_open: struct tdb_context *(const char *, int, int, int, mode_t)
+tdb_open_ex: struct tdb_context *(const char *, int, int, int, mode_t, const struct tdb_logging_context *, tdb_hash_func)
+tdb_parse_record: int (struct tdb_context *, TDB_DATA, int (*)(TDB_DATA, TDB_DATA, void *), void *)
+tdb_printfreelist: int (struct tdb_context *)
+tdb_remove_flags: void (struct tdb_context *, unsigned int)
+tdb_reopen: int (struct tdb_context *)
+tdb_reopen_all: int (int)
+tdb_repack: int (struct tdb_context *)
+tdb_rescue: int (struct tdb_context *, void (*)(TDB_DATA, TDB_DATA, void *), void *)
+tdb_runtime_check_for_robust_mutexes: bool (void)
+tdb_set_logging_function: void (struct tdb_context *, const struct tdb_logging_context *)
+tdb_set_max_dead: void (struct tdb_context *, int)
+tdb_setalarm_sigptr: void (struct tdb_context *, volatile sig_atomic_t *)
+tdb_store: int (struct tdb_context *, TDB_DATA, TDB_DATA, int)
+tdb_summary: char *(struct tdb_context *)
+tdb_transaction_cancel: int (struct tdb_context *)
+tdb_transaction_commit: int (struct tdb_context *)
+tdb_transaction_prepare_commit: int (struct tdb_context *)
+tdb_transaction_start: int (struct tdb_context *)
+tdb_transaction_start_nonblock: int (struct tdb_context *)
+tdb_transaction_write_lock_mark: int (struct tdb_context *)
+tdb_transaction_write_lock_unmark: int (struct tdb_context *)
+tdb_traverse: int (struct tdb_context *, tdb_traverse_func, void *)
+tdb_traverse_read: int (struct tdb_context *, tdb_traverse_func, void *)
+tdb_unlock: int (struct tdb_context *, int, int)
+tdb_unlockall: int (struct tdb_context *)
+tdb_unlockall_read: int (struct tdb_context *)
+tdb_validate_freelist: int (struct tdb_context *, int *)
+tdb_wipe_all: int (struct tdb_context *)
diff --git a/lib/tdb/ABI/tdb-1.3.6.sigs b/lib/tdb/ABI/tdb-1.3.6.sigs
new file mode 100644
index 0000000..2545c99
--- /dev/null
+++ b/lib/tdb/ABI/tdb-1.3.6.sigs
@@ -0,0 +1,69 @@
+tdb_add_flags: void (struct tdb_context *, unsigned int)
+tdb_append: int (struct tdb_context *, TDB_DATA, TDB_DATA)
+tdb_chainlock: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_mark: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_nonblock: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_read: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_read_nonblock: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_unmark: int (struct tdb_context *, TDB_DATA)
+tdb_chainunlock: int (struct tdb_context *, TDB_DATA)
+tdb_chainunlock_read: int (struct tdb_context *, TDB_DATA)
+tdb_check: int (struct tdb_context *, int (*)(TDB_DATA, TDB_DATA, void *), void *)
+tdb_close: int (struct tdb_context *)
+tdb_delete: int (struct tdb_context *, TDB_DATA)
+tdb_dump_all: void (struct tdb_context *)
+tdb_enable_seqnum: void (struct tdb_context *)
+tdb_error: enum TDB_ERROR (struct tdb_context *)
+tdb_errorstr: const char *(struct tdb_context *)
+tdb_exists: int (struct tdb_context *, TDB_DATA)
+tdb_fd: int (struct tdb_context *)
+tdb_fetch: TDB_DATA (struct tdb_context *, TDB_DATA)
+tdb_firstkey: TDB_DATA (struct tdb_context *)
+tdb_freelist_size: int (struct tdb_context *)
+tdb_get_flags: int (struct tdb_context *)
+tdb_get_logging_private: void *(struct tdb_context *)
+tdb_get_seqnum: int (struct tdb_context *)
+tdb_hash_size: int (struct tdb_context *)
+tdb_increment_seqnum_nonblock: void (struct tdb_context *)
+tdb_jenkins_hash: unsigned int (TDB_DATA *)
+tdb_lock_nonblock: int (struct tdb_context *, int, int)
+tdb_lockall: int (struct tdb_context *)
+tdb_lockall_mark: int (struct tdb_context *)
+tdb_lockall_nonblock: int (struct tdb_context *)
+tdb_lockall_read: int (struct tdb_context *)
+tdb_lockall_read_nonblock: int (struct tdb_context *)
+tdb_lockall_unmark: int (struct tdb_context *)
+tdb_log_fn: tdb_log_func (struct tdb_context *)
+tdb_map_size: size_t (struct tdb_context *)
+tdb_name: const char *(struct tdb_context *)
+tdb_nextkey: TDB_DATA (struct tdb_context *, TDB_DATA)
+tdb_null: dptr = 0xXXXX, dsize = 0
+tdb_open: struct tdb_context *(const char *, int, int, int, mode_t)
+tdb_open_ex: struct tdb_context *(const char *, int, int, int, mode_t, const struct tdb_logging_context *, tdb_hash_func)
+tdb_parse_record: int (struct tdb_context *, TDB_DATA, int (*)(TDB_DATA, TDB_DATA, void *), void *)
+tdb_printfreelist: int (struct tdb_context *)
+tdb_remove_flags: void (struct tdb_context *, unsigned int)
+tdb_reopen: int (struct tdb_context *)
+tdb_reopen_all: int (int)
+tdb_repack: int (struct tdb_context *)
+tdb_rescue: int (struct tdb_context *, void (*)(TDB_DATA, TDB_DATA, void *), void *)
+tdb_runtime_check_for_robust_mutexes: bool (void)
+tdb_set_logging_function: void (struct tdb_context *, const struct tdb_logging_context *)
+tdb_set_max_dead: void (struct tdb_context *, int)
+tdb_setalarm_sigptr: void (struct tdb_context *, volatile sig_atomic_t *)
+tdb_store: int (struct tdb_context *, TDB_DATA, TDB_DATA, int)
+tdb_summary: char *(struct tdb_context *)
+tdb_transaction_cancel: int (struct tdb_context *)
+tdb_transaction_commit: int (struct tdb_context *)
+tdb_transaction_prepare_commit: int (struct tdb_context *)
+tdb_transaction_start: int (struct tdb_context *)
+tdb_transaction_start_nonblock: int (struct tdb_context *)
+tdb_transaction_write_lock_mark: int (struct tdb_context *)
+tdb_transaction_write_lock_unmark: int (struct tdb_context *)
+tdb_traverse: int (struct tdb_context *, tdb_traverse_func, void *)
+tdb_traverse_read: int (struct tdb_context *, tdb_traverse_func, void *)
+tdb_unlock: int (struct tdb_context *, int, int)
+tdb_unlockall: int (struct tdb_context *)
+tdb_unlockall_read: int (struct tdb_context *)
+tdb_validate_freelist: int (struct tdb_context *, int *)
+tdb_wipe_all: int (struct tdb_context *)
diff --git a/lib/tdb/ABI/tdb-1.3.7.sigs b/lib/tdb/ABI/tdb-1.3.7.sigs
new file mode 100644
index 0000000..2545c99
--- /dev/null
+++ b/lib/tdb/ABI/tdb-1.3.7.sigs
@@ -0,0 +1,69 @@
+tdb_add_flags: void (struct tdb_context *, unsigned int)
+tdb_append: int (struct tdb_context *, TDB_DATA, TDB_DATA)
+tdb_chainlock: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_mark: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_nonblock: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_read: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_read_nonblock: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_unmark: int (struct tdb_context *, TDB_DATA)
+tdb_chainunlock: int (struct tdb_context *, TDB_DATA)
+tdb_chainunlock_read: int (struct tdb_context *, TDB_DATA)
+tdb_check: int (struct tdb_context *, int (*)(TDB_DATA, TDB_DATA, void *), void *)
+tdb_close: int (struct tdb_context *)
+tdb_delete: int (struct tdb_context *, TDB_DATA)
+tdb_dump_all: void (struct tdb_context *)
+tdb_enable_seqnum: void (struct tdb_context *)
+tdb_error: enum TDB_ERROR (struct tdb_context *)
+tdb_errorstr: const char *(struct tdb_context *)
+tdb_exists: int (struct tdb_context *, TDB_DATA)
+tdb_fd: int (struct tdb_context *)
+tdb_fetch: TDB_DATA (struct tdb_context *, TDB_DATA)
+tdb_firstkey: TDB_DATA (struct tdb_context *)
+tdb_freelist_size: int (struct tdb_context *)
+tdb_get_flags: int (struct tdb_context *)
+tdb_get_logging_private: void *(struct tdb_context *)
+tdb_get_seqnum: int (struct tdb_context *)
+tdb_hash_size: int (struct tdb_context *)
+tdb_increment_seqnum_nonblock: void (struct tdb_context *)
+tdb_jenkins_hash: unsigned int (TDB_DATA *)
+tdb_lock_nonblock: int (struct tdb_context *, int, int)
+tdb_lockall: int (struct tdb_context *)
+tdb_lockall_mark: int (struct tdb_context *)
+tdb_lockall_nonblock: int (struct tdb_context *)
+tdb_lockall_read: int (struct tdb_context *)
+tdb_lockall_read_nonblock: int (struct tdb_context *)
+tdb_lockall_unmark: int (struct tdb_context *)
+tdb_log_fn: tdb_log_func (struct tdb_context *)
+tdb_map_size: size_t (struct tdb_context *)
+tdb_name: const char *(struct tdb_context *)
+tdb_nextkey: TDB_DATA (struct tdb_context *, TDB_DATA)
+tdb_null: dptr = 0xXXXX, dsize = 0
+tdb_open: struct tdb_context *(const char *, int, int, int, mode_t)
+tdb_open_ex: struct tdb_context *(const char *, int, int, int, mode_t, const struct tdb_logging_context *, tdb_hash_func)
+tdb_parse_record: int (struct tdb_context *, TDB_DATA, int (*)(TDB_DATA, TDB_DATA, void *), void *)
+tdb_printfreelist: int (struct tdb_context *)
+tdb_remove_flags: void (struct tdb_context *, unsigned int)
+tdb_reopen: int (struct tdb_context *)
+tdb_reopen_all: int (int)
+tdb_repack: int (struct tdb_context *)
+tdb_rescue: int (struct tdb_context *, void (*)(TDB_DATA, TDB_DATA, void *), void *)
+tdb_runtime_check_for_robust_mutexes: bool (void)
+tdb_set_logging_function: void (struct tdb_context *, const struct tdb_logging_context *)
+tdb_set_max_dead: void (struct tdb_context *, int)
+tdb_setalarm_sigptr: void (struct tdb_context *, volatile sig_atomic_t *)
+tdb_store: int (struct tdb_context *, TDB_DATA, TDB_DATA, int)
+tdb_summary: char *(struct tdb_context *)
+tdb_transaction_cancel: int (struct tdb_context *)
+tdb_transaction_commit: int (struct tdb_context *)
+tdb_transaction_prepare_commit: int (struct tdb_context *)
+tdb_transaction_start: int (struct tdb_context *)
+tdb_transaction_start_nonblock: int (struct tdb_context *)
+tdb_transaction_write_lock_mark: int (struct tdb_context *)
+tdb_transaction_write_lock_unmark: int (struct tdb_context *)
+tdb_traverse: int (struct tdb_context *, tdb_traverse_func, void *)
+tdb_traverse_read: int (struct tdb_context *, tdb_traverse_func, void *)
+tdb_unlock: int (struct tdb_context *, int, int)
+tdb_unlockall: int (struct tdb_context *)
+tdb_unlockall_read: int (struct tdb_context *)
+tdb_validate_freelist: int (struct tdb_context *, int *)
+tdb_wipe_all: int (struct tdb_context *)
diff --git a/lib/tdb/ABI/tdb-1.3.8.sigs b/lib/tdb/ABI/tdb-1.3.8.sigs
new file mode 100644
index 0000000..2545c99
--- /dev/null
+++ b/lib/tdb/ABI/tdb-1.3.8.sigs
@@ -0,0 +1,69 @@
+tdb_add_flags: void (struct tdb_context *, unsigned int)
+tdb_append: int (struct tdb_context *, TDB_DATA, TDB_DATA)
+tdb_chainlock: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_mark: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_nonblock: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_read: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_read_nonblock: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_unmark: int (struct tdb_context *, TDB_DATA)
+tdb_chainunlock: int (struct tdb_context *, TDB_DATA)
+tdb_chainunlock_read: int (struct tdb_context *, TDB_DATA)
+tdb_check: int (struct tdb_context *, int (*)(TDB_DATA, TDB_DATA, void *), void *)
+tdb_close: int (struct tdb_context *)
+tdb_delete: int (struct tdb_context *, TDB_DATA)
+tdb_dump_all: void (struct tdb_context *)
+tdb_enable_seqnum: void (struct tdb_context *)
+tdb_error: enum TDB_ERROR (struct tdb_context *)
+tdb_errorstr: const char *(struct tdb_context *)
+tdb_exists: int (struct tdb_context *, TDB_DATA)
+tdb_fd: int (struct tdb_context *)
+tdb_fetch: TDB_DATA (struct tdb_context *, TDB_DATA)
+tdb_firstkey: TDB_DATA (struct tdb_context *)
+tdb_freelist_size: int (struct tdb_context *)
+tdb_get_flags: int (struct tdb_context *)
+tdb_get_logging_private: void *(struct tdb_context *)
+tdb_get_seqnum: int (struct tdb_context *)
+tdb_hash_size: int (struct tdb_context *)
+tdb_increment_seqnum_nonblock: void (struct tdb_context *)
+tdb_jenkins_hash: unsigned int (TDB_DATA *)
+tdb_lock_nonblock: int (struct tdb_context *, int, int)
+tdb_lockall: int (struct tdb_context *)
+tdb_lockall_mark: int (struct tdb_context *)
+tdb_lockall_nonblock: int (struct tdb_context *)
+tdb_lockall_read: int (struct tdb_context *)
+tdb_lockall_read_nonblock: int (struct tdb_context *)
+tdb_lockall_unmark: int (struct tdb_context *)
+tdb_log_fn: tdb_log_func (struct tdb_context *)
+tdb_map_size: size_t (struct tdb_context *)
+tdb_name: const char *(struct tdb_context *)
+tdb_nextkey: TDB_DATA (struct tdb_context *, TDB_DATA)
+tdb_null: dptr = 0xXXXX, dsize = 0
+tdb_open: struct tdb_context *(const char *, int, int, int, mode_t)
+tdb_open_ex: struct tdb_context *(const char *, int, int, int, mode_t, const struct tdb_logging_context *, tdb_hash_func)
+tdb_parse_record: int (struct tdb_context *, TDB_DATA, int (*)(TDB_DATA, TDB_DATA, void *), void *)
+tdb_printfreelist: int (struct tdb_context *)
+tdb_remove_flags: void (struct tdb_context *, unsigned int)
+tdb_reopen: int (struct tdb_context *)
+tdb_reopen_all: int (int)
+tdb_repack: int (struct tdb_context *)
+tdb_rescue: int (struct tdb_context *, void (*)(TDB_DATA, TDB_DATA, void *), void *)
+tdb_runtime_check_for_robust_mutexes: bool (void)
+tdb_set_logging_function: void (struct tdb_context *, const struct tdb_logging_context *)
+tdb_set_max_dead: void (struct tdb_context *, int)
+tdb_setalarm_sigptr: void (struct tdb_context *, volatile sig_atomic_t *)
+tdb_store: int (struct tdb_context *, TDB_DATA, TDB_DATA, int)
+tdb_summary: char *(struct tdb_context *)
+tdb_transaction_cancel: int (struct tdb_context *)
+tdb_transaction_commit: int (struct tdb_context *)
+tdb_transaction_prepare_commit: int (struct tdb_context *)
+tdb_transaction_start: int (struct tdb_context *)
+tdb_transaction_start_nonblock: int (struct tdb_context *)
+tdb_transaction_write_lock_mark: int (struct tdb_context *)
+tdb_transaction_write_lock_unmark: int (struct tdb_context *)
+tdb_traverse: int (struct tdb_context *, tdb_traverse_func, void *)
+tdb_traverse_read: int (struct tdb_context *, tdb_traverse_func, void *)
+tdb_unlock: int (struct tdb_context *, int, int)
+tdb_unlockall: int (struct tdb_context *)
+tdb_unlockall_read: int (struct tdb_context *)
+tdb_validate_freelist: int (struct tdb_context *, int *)
+tdb_wipe_all: int (struct tdb_context *)
diff --git a/lib/tdb/ABI/tdb-1.3.9.sigs b/lib/tdb/ABI/tdb-1.3.9.sigs
new file mode 100644
index 0000000..2545c99
--- /dev/null
+++ b/lib/tdb/ABI/tdb-1.3.9.sigs
@@ -0,0 +1,69 @@
+tdb_add_flags: void (struct tdb_context *, unsigned int)
+tdb_append: int (struct tdb_context *, TDB_DATA, TDB_DATA)
+tdb_chainlock: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_mark: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_nonblock: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_read: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_read_nonblock: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_unmark: int (struct tdb_context *, TDB_DATA)
+tdb_chainunlock: int (struct tdb_context *, TDB_DATA)
+tdb_chainunlock_read: int (struct tdb_context *, TDB_DATA)
+tdb_check: int (struct tdb_context *, int (*)(TDB_DATA, TDB_DATA, void *), void *)
+tdb_close: int (struct tdb_context *)
+tdb_delete: int (struct tdb_context *, TDB_DATA)
+tdb_dump_all: void (struct tdb_context *)
+tdb_enable_seqnum: void (struct tdb_context *)
+tdb_error: enum TDB_ERROR (struct tdb_context *)
+tdb_errorstr: const char *(struct tdb_context *)
+tdb_exists: int (struct tdb_context *, TDB_DATA)
+tdb_fd: int (struct tdb_context *)
+tdb_fetch: TDB_DATA (struct tdb_context *, TDB_DATA)
+tdb_firstkey: TDB_DATA (struct tdb_context *)
+tdb_freelist_size: int (struct tdb_context *)
+tdb_get_flags: int (struct tdb_context *)
+tdb_get_logging_private: void *(struct tdb_context *)
+tdb_get_seqnum: int (struct tdb_context *)
+tdb_hash_size: int (struct tdb_context *)
+tdb_increment_seqnum_nonblock: void (struct tdb_context *)
+tdb_jenkins_hash: unsigned int (TDB_DATA *)
+tdb_lock_nonblock: int (struct tdb_context *, int, int)
+tdb_lockall: int (struct tdb_context *)
+tdb_lockall_mark: int (struct tdb_context *)
+tdb_lockall_nonblock: int (struct tdb_context *)
+tdb_lockall_read: int (struct tdb_context *)
+tdb_lockall_read_nonblock: int (struct tdb_context *)
+tdb_lockall_unmark: int (struct tdb_context *)
+tdb_log_fn: tdb_log_func (struct tdb_context *)
+tdb_map_size: size_t (struct tdb_context *)
+tdb_name: const char *(struct tdb_context *)
+tdb_nextkey: TDB_DATA (struct tdb_context *, TDB_DATA)
+tdb_null: dptr = 0xXXXX, dsize = 0
+tdb_open: struct tdb_context *(const char *, int, int, int, mode_t)
+tdb_open_ex: struct tdb_context *(const char *, int, int, int, mode_t, const struct tdb_logging_context *, tdb_hash_func)
+tdb_parse_record: int (struct tdb_context *, TDB_DATA, int (*)(TDB_DATA, TDB_DATA, void *), void *)
+tdb_printfreelist: int (struct tdb_context *)
+tdb_remove_flags: void (struct tdb_context *, unsigned int)
+tdb_reopen: int (struct tdb_context *)
+tdb_reopen_all: int (int)
+tdb_repack: int (struct tdb_context *)
+tdb_rescue: int (struct tdb_context *, void (*)(TDB_DATA, TDB_DATA, void *), void *)
+tdb_runtime_check_for_robust_mutexes: bool (void)
+tdb_set_logging_function: void (struct tdb_context *, const struct tdb_logging_context *)
+tdb_set_max_dead: void (struct tdb_context *, int)
+tdb_setalarm_sigptr: void (struct tdb_context *, volatile sig_atomic_t *)
+tdb_store: int (struct tdb_context *, TDB_DATA, TDB_DATA, int)
+tdb_summary: char *(struct tdb_context *)
+tdb_transaction_cancel: int (struct tdb_context *)
+tdb_transaction_commit: int (struct tdb_context *)
+tdb_transaction_prepare_commit: int (struct tdb_context *)
+tdb_transaction_start: int (struct tdb_context *)
+tdb_transaction_start_nonblock: int (struct tdb_context *)
+tdb_transaction_write_lock_mark: int (struct tdb_context *)
+tdb_transaction_write_lock_unmark: int (struct tdb_context *)
+tdb_traverse: int (struct tdb_context *, tdb_traverse_func, void *)
+tdb_traverse_read: int (struct tdb_context *, tdb_traverse_func, void *)
+tdb_unlock: int (struct tdb_context *, int, int)
+tdb_unlockall: int (struct tdb_context *)
+tdb_unlockall_read: int (struct tdb_context *)
+tdb_validate_freelist: int (struct tdb_context *, int *)
+tdb_wipe_all: int (struct tdb_context *)
diff --git a/lib/tdb/ABI/tdb-1.4.0.sigs b/lib/tdb/ABI/tdb-1.4.0.sigs
new file mode 100644
index 0000000..e2b0427
--- /dev/null
+++ b/lib/tdb/ABI/tdb-1.4.0.sigs
@@ -0,0 +1,73 @@
+tdb_add_flags: void (struct tdb_context *, unsigned int)
+tdb_append: int (struct tdb_context *, TDB_DATA, TDB_DATA)
+tdb_chainlock: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_mark: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_nonblock: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_read: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_read_nonblock: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_unmark: int (struct tdb_context *, TDB_DATA)
+tdb_chainunlock: int (struct tdb_context *, TDB_DATA)
+tdb_chainunlock_read: int (struct tdb_context *, TDB_DATA)
+tdb_check: int (struct tdb_context *, int (*)(TDB_DATA, TDB_DATA, void *), void *)
+tdb_close: int (struct tdb_context *)
+tdb_delete: int (struct tdb_context *, TDB_DATA)
+tdb_dump_all: void (struct tdb_context *)
+tdb_enable_seqnum: void (struct tdb_context *)
+tdb_error: enum TDB_ERROR (struct tdb_context *)
+tdb_errorstr: const char *(struct tdb_context *)
+tdb_exists: int (struct tdb_context *, TDB_DATA)
+tdb_fd: int (struct tdb_context *)
+tdb_fetch: TDB_DATA (struct tdb_context *, TDB_DATA)
+tdb_firstkey: TDB_DATA (struct tdb_context *)
+tdb_freelist_size: int (struct tdb_context *)
+tdb_get_flags: int (struct tdb_context *)
+tdb_get_logging_private: void *(struct tdb_context *)
+tdb_get_seqnum: int (struct tdb_context *)
+tdb_hash_size: int (struct tdb_context *)
+tdb_increment_seqnum_nonblock: void (struct tdb_context *)
+tdb_jenkins_hash: unsigned int (TDB_DATA *)
+tdb_lock_nonblock: int (struct tdb_context *, int, int)
+tdb_lockall: int (struct tdb_context *)
+tdb_lockall_mark: int (struct tdb_context *)
+tdb_lockall_nonblock: int (struct tdb_context *)
+tdb_lockall_read: int (struct tdb_context *)
+tdb_lockall_read_nonblock: int (struct tdb_context *)
+tdb_lockall_unmark: int (struct tdb_context *)
+tdb_log_fn: tdb_log_func (struct tdb_context *)
+tdb_map_size: size_t (struct tdb_context *)
+tdb_name: const char *(struct tdb_context *)
+tdb_nextkey: TDB_DATA (struct tdb_context *, TDB_DATA)
+tdb_null: dptr = 0xXXXX, dsize = 0
+tdb_open: struct tdb_context *(const char *, int, int, int, mode_t)
+tdb_open_ex: struct tdb_context *(const char *, int, int, int, mode_t, const struct tdb_logging_context *, tdb_hash_func)
+tdb_parse_record: int (struct tdb_context *, TDB_DATA, int (*)(TDB_DATA, TDB_DATA, void *), void *)
+tdb_printfreelist: int (struct tdb_context *)
+tdb_remove_flags: void (struct tdb_context *, unsigned int)
+tdb_reopen: int (struct tdb_context *)
+tdb_reopen_all: int (int)
+tdb_repack: int (struct tdb_context *)
+tdb_rescue: int (struct tdb_context *, void (*)(TDB_DATA, TDB_DATA, void *), void *)
+tdb_runtime_check_for_robust_mutexes: bool (void)
+tdb_set_logging_function: void (struct tdb_context *, const struct tdb_logging_context *)
+tdb_set_max_dead: void (struct tdb_context *, int)
+tdb_setalarm_sigptr: void (struct tdb_context *, volatile sig_atomic_t *)
+tdb_store: int (struct tdb_context *, TDB_DATA, TDB_DATA, int)
+tdb_storev: int (struct tdb_context *, TDB_DATA, const TDB_DATA *, int, int)
+tdb_summary: char *(struct tdb_context *)
+tdb_transaction_active: bool (struct tdb_context *)
+tdb_transaction_cancel: int (struct tdb_context *)
+tdb_transaction_commit: int (struct tdb_context *)
+tdb_transaction_prepare_commit: int (struct tdb_context *)
+tdb_transaction_start: int (struct tdb_context *)
+tdb_transaction_start_nonblock: int (struct tdb_context *)
+tdb_transaction_write_lock_mark: int (struct tdb_context *)
+tdb_transaction_write_lock_unmark: int (struct tdb_context *)
+tdb_traverse: int (struct tdb_context *, tdb_traverse_func, void *)
+tdb_traverse_chain: int (struct tdb_context *, unsigned int, tdb_traverse_func, void *)
+tdb_traverse_key_chain: int (struct tdb_context *, TDB_DATA, tdb_traverse_func, void *)
+tdb_traverse_read: int (struct tdb_context *, tdb_traverse_func, void *)
+tdb_unlock: int (struct tdb_context *, int, int)
+tdb_unlockall: int (struct tdb_context *)
+tdb_unlockall_read: int (struct tdb_context *)
+tdb_validate_freelist: int (struct tdb_context *, int *)
+tdb_wipe_all: int (struct tdb_context *)
diff --git a/lib/tdb/ABI/tdb-1.4.1.sigs b/lib/tdb/ABI/tdb-1.4.1.sigs
new file mode 100644
index 0000000..e2b0427
--- /dev/null
+++ b/lib/tdb/ABI/tdb-1.4.1.sigs
@@ -0,0 +1,73 @@
+tdb_add_flags: void (struct tdb_context *, unsigned int)
+tdb_append: int (struct tdb_context *, TDB_DATA, TDB_DATA)
+tdb_chainlock: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_mark: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_nonblock: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_read: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_read_nonblock: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_unmark: int (struct tdb_context *, TDB_DATA)
+tdb_chainunlock: int (struct tdb_context *, TDB_DATA)
+tdb_chainunlock_read: int (struct tdb_context *, TDB_DATA)
+tdb_check: int (struct tdb_context *, int (*)(TDB_DATA, TDB_DATA, void *), void *)
+tdb_close: int (struct tdb_context *)
+tdb_delete: int (struct tdb_context *, TDB_DATA)
+tdb_dump_all: void (struct tdb_context *)
+tdb_enable_seqnum: void (struct tdb_context *)
+tdb_error: enum TDB_ERROR (struct tdb_context *)
+tdb_errorstr: const char *(struct tdb_context *)
+tdb_exists: int (struct tdb_context *, TDB_DATA)
+tdb_fd: int (struct tdb_context *)
+tdb_fetch: TDB_DATA (struct tdb_context *, TDB_DATA)
+tdb_firstkey: TDB_DATA (struct tdb_context *)
+tdb_freelist_size: int (struct tdb_context *)
+tdb_get_flags: int (struct tdb_context *)
+tdb_get_logging_private: void *(struct tdb_context *)
+tdb_get_seqnum: int (struct tdb_context *)
+tdb_hash_size: int (struct tdb_context *)
+tdb_increment_seqnum_nonblock: void (struct tdb_context *)
+tdb_jenkins_hash: unsigned int (TDB_DATA *)
+tdb_lock_nonblock: int (struct tdb_context *, int, int)
+tdb_lockall: int (struct tdb_context *)
+tdb_lockall_mark: int (struct tdb_context *)
+tdb_lockall_nonblock: int (struct tdb_context *)
+tdb_lockall_read: int (struct tdb_context *)
+tdb_lockall_read_nonblock: int (struct tdb_context *)
+tdb_lockall_unmark: int (struct tdb_context *)
+tdb_log_fn: tdb_log_func (struct tdb_context *)
+tdb_map_size: size_t (struct tdb_context *)
+tdb_name: const char *(struct tdb_context *)
+tdb_nextkey: TDB_DATA (struct tdb_context *, TDB_DATA)
+tdb_null: dptr = 0xXXXX, dsize = 0
+tdb_open: struct tdb_context *(const char *, int, int, int, mode_t)
+tdb_open_ex: struct tdb_context *(const char *, int, int, int, mode_t, const struct tdb_logging_context *, tdb_hash_func)
+tdb_parse_record: int (struct tdb_context *, TDB_DATA, int (*)(TDB_DATA, TDB_DATA, void *), void *)
+tdb_printfreelist: int (struct tdb_context *)
+tdb_remove_flags: void (struct tdb_context *, unsigned int)
+tdb_reopen: int (struct tdb_context *)
+tdb_reopen_all: int (int)
+tdb_repack: int (struct tdb_context *)
+tdb_rescue: int (struct tdb_context *, void (*)(TDB_DATA, TDB_DATA, void *), void *)
+tdb_runtime_check_for_robust_mutexes: bool (void)
+tdb_set_logging_function: void (struct tdb_context *, const struct tdb_logging_context *)
+tdb_set_max_dead: void (struct tdb_context *, int)
+tdb_setalarm_sigptr: void (struct tdb_context *, volatile sig_atomic_t *)
+tdb_store: int (struct tdb_context *, TDB_DATA, TDB_DATA, int)
+tdb_storev: int (struct tdb_context *, TDB_DATA, const TDB_DATA *, int, int)
+tdb_summary: char *(struct tdb_context *)
+tdb_transaction_active: bool (struct tdb_context *)
+tdb_transaction_cancel: int (struct tdb_context *)
+tdb_transaction_commit: int (struct tdb_context *)
+tdb_transaction_prepare_commit: int (struct tdb_context *)
+tdb_transaction_start: int (struct tdb_context *)
+tdb_transaction_start_nonblock: int (struct tdb_context *)
+tdb_transaction_write_lock_mark: int (struct tdb_context *)
+tdb_transaction_write_lock_unmark: int (struct tdb_context *)
+tdb_traverse: int (struct tdb_context *, tdb_traverse_func, void *)
+tdb_traverse_chain: int (struct tdb_context *, unsigned int, tdb_traverse_func, void *)
+tdb_traverse_key_chain: int (struct tdb_context *, TDB_DATA, tdb_traverse_func, void *)
+tdb_traverse_read: int (struct tdb_context *, tdb_traverse_func, void *)
+tdb_unlock: int (struct tdb_context *, int, int)
+tdb_unlockall: int (struct tdb_context *)
+tdb_unlockall_read: int (struct tdb_context *)
+tdb_validate_freelist: int (struct tdb_context *, int *)
+tdb_wipe_all: int (struct tdb_context *)
diff --git a/lib/tdb/ABI/tdb-1.4.2.sigs b/lib/tdb/ABI/tdb-1.4.2.sigs
new file mode 100644
index 0000000..e2b0427
--- /dev/null
+++ b/lib/tdb/ABI/tdb-1.4.2.sigs
@@ -0,0 +1,73 @@
+tdb_add_flags: void (struct tdb_context *, unsigned int)
+tdb_append: int (struct tdb_context *, TDB_DATA, TDB_DATA)
+tdb_chainlock: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_mark: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_nonblock: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_read: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_read_nonblock: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_unmark: int (struct tdb_context *, TDB_DATA)
+tdb_chainunlock: int (struct tdb_context *, TDB_DATA)
+tdb_chainunlock_read: int (struct tdb_context *, TDB_DATA)
+tdb_check: int (struct tdb_context *, int (*)(TDB_DATA, TDB_DATA, void *), void *)
+tdb_close: int (struct tdb_context *)
+tdb_delete: int (struct tdb_context *, TDB_DATA)
+tdb_dump_all: void (struct tdb_context *)
+tdb_enable_seqnum: void (struct tdb_context *)
+tdb_error: enum TDB_ERROR (struct tdb_context *)
+tdb_errorstr: const char *(struct tdb_context *)
+tdb_exists: int (struct tdb_context *, TDB_DATA)
+tdb_fd: int (struct tdb_context *)
+tdb_fetch: TDB_DATA (struct tdb_context *, TDB_DATA)
+tdb_firstkey: TDB_DATA (struct tdb_context *)
+tdb_freelist_size: int (struct tdb_context *)
+tdb_get_flags: int (struct tdb_context *)
+tdb_get_logging_private: void *(struct tdb_context *)
+tdb_get_seqnum: int (struct tdb_context *)
+tdb_hash_size: int (struct tdb_context *)
+tdb_increment_seqnum_nonblock: void (struct tdb_context *)
+tdb_jenkins_hash: unsigned int (TDB_DATA *)
+tdb_lock_nonblock: int (struct tdb_context *, int, int)
+tdb_lockall: int (struct tdb_context *)
+tdb_lockall_mark: int (struct tdb_context *)
+tdb_lockall_nonblock: int (struct tdb_context *)
+tdb_lockall_read: int (struct tdb_context *)
+tdb_lockall_read_nonblock: int (struct tdb_context *)
+tdb_lockall_unmark: int (struct tdb_context *)
+tdb_log_fn: tdb_log_func (struct tdb_context *)
+tdb_map_size: size_t (struct tdb_context *)
+tdb_name: const char *(struct tdb_context *)
+tdb_nextkey: TDB_DATA (struct tdb_context *, TDB_DATA)
+tdb_null: dptr = 0xXXXX, dsize = 0
+tdb_open: struct tdb_context *(const char *, int, int, int, mode_t)
+tdb_open_ex: struct tdb_context *(const char *, int, int, int, mode_t, const struct tdb_logging_context *, tdb_hash_func)
+tdb_parse_record: int (struct tdb_context *, TDB_DATA, int (*)(TDB_DATA, TDB_DATA, void *), void *)
+tdb_printfreelist: int (struct tdb_context *)
+tdb_remove_flags: void (struct tdb_context *, unsigned int)
+tdb_reopen: int (struct tdb_context *)
+tdb_reopen_all: int (int)
+tdb_repack: int (struct tdb_context *)
+tdb_rescue: int (struct tdb_context *, void (*)(TDB_DATA, TDB_DATA, void *), void *)
+tdb_runtime_check_for_robust_mutexes: bool (void)
+tdb_set_logging_function: void (struct tdb_context *, const struct tdb_logging_context *)
+tdb_set_max_dead: void (struct tdb_context *, int)
+tdb_setalarm_sigptr: void (struct tdb_context *, volatile sig_atomic_t *)
+tdb_store: int (struct tdb_context *, TDB_DATA, TDB_DATA, int)
+tdb_storev: int (struct tdb_context *, TDB_DATA, const TDB_DATA *, int, int)
+tdb_summary: char *(struct tdb_context *)
+tdb_transaction_active: bool (struct tdb_context *)
+tdb_transaction_cancel: int (struct tdb_context *)
+tdb_transaction_commit: int (struct tdb_context *)
+tdb_transaction_prepare_commit: int (struct tdb_context *)
+tdb_transaction_start: int (struct tdb_context *)
+tdb_transaction_start_nonblock: int (struct tdb_context *)
+tdb_transaction_write_lock_mark: int (struct tdb_context *)
+tdb_transaction_write_lock_unmark: int (struct tdb_context *)
+tdb_traverse: int (struct tdb_context *, tdb_traverse_func, void *)
+tdb_traverse_chain: int (struct tdb_context *, unsigned int, tdb_traverse_func, void *)
+tdb_traverse_key_chain: int (struct tdb_context *, TDB_DATA, tdb_traverse_func, void *)
+tdb_traverse_read: int (struct tdb_context *, tdb_traverse_func, void *)
+tdb_unlock: int (struct tdb_context *, int, int)
+tdb_unlockall: int (struct tdb_context *)
+tdb_unlockall_read: int (struct tdb_context *)
+tdb_validate_freelist: int (struct tdb_context *, int *)
+tdb_wipe_all: int (struct tdb_context *)
diff --git a/lib/tdb/ABI/tdb-1.4.3.sigs b/lib/tdb/ABI/tdb-1.4.3.sigs
new file mode 100644
index 0000000..e2b0427
--- /dev/null
+++ b/lib/tdb/ABI/tdb-1.4.3.sigs
@@ -0,0 +1,73 @@
+tdb_add_flags: void (struct tdb_context *, unsigned int)
+tdb_append: int (struct tdb_context *, TDB_DATA, TDB_DATA)
+tdb_chainlock: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_mark: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_nonblock: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_read: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_read_nonblock: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_unmark: int (struct tdb_context *, TDB_DATA)
+tdb_chainunlock: int (struct tdb_context *, TDB_DATA)
+tdb_chainunlock_read: int (struct tdb_context *, TDB_DATA)
+tdb_check: int (struct tdb_context *, int (*)(TDB_DATA, TDB_DATA, void *), void *)
+tdb_close: int (struct tdb_context *)
+tdb_delete: int (struct tdb_context *, TDB_DATA)
+tdb_dump_all: void (struct tdb_context *)
+tdb_enable_seqnum: void (struct tdb_context *)
+tdb_error: enum TDB_ERROR (struct tdb_context *)
+tdb_errorstr: const char *(struct tdb_context *)
+tdb_exists: int (struct tdb_context *, TDB_DATA)
+tdb_fd: int (struct tdb_context *)
+tdb_fetch: TDB_DATA (struct tdb_context *, TDB_DATA)
+tdb_firstkey: TDB_DATA (struct tdb_context *)
+tdb_freelist_size: int (struct tdb_context *)
+tdb_get_flags: int (struct tdb_context *)
+tdb_get_logging_private: void *(struct tdb_context *)
+tdb_get_seqnum: int (struct tdb_context *)
+tdb_hash_size: int (struct tdb_context *)
+tdb_increment_seqnum_nonblock: void (struct tdb_context *)
+tdb_jenkins_hash: unsigned int (TDB_DATA *)
+tdb_lock_nonblock: int (struct tdb_context *, int, int)
+tdb_lockall: int (struct tdb_context *)
+tdb_lockall_mark: int (struct tdb_context *)
+tdb_lockall_nonblock: int (struct tdb_context *)
+tdb_lockall_read: int (struct tdb_context *)
+tdb_lockall_read_nonblock: int (struct tdb_context *)
+tdb_lockall_unmark: int (struct tdb_context *)
+tdb_log_fn: tdb_log_func (struct tdb_context *)
+tdb_map_size: size_t (struct tdb_context *)
+tdb_name: const char *(struct tdb_context *)
+tdb_nextkey: TDB_DATA (struct tdb_context *, TDB_DATA)
+tdb_null: dptr = 0xXXXX, dsize = 0
+tdb_open: struct tdb_context *(const char *, int, int, int, mode_t)
+tdb_open_ex: struct tdb_context *(const char *, int, int, int, mode_t, const struct tdb_logging_context *, tdb_hash_func)
+tdb_parse_record: int (struct tdb_context *, TDB_DATA, int (*)(TDB_DATA, TDB_DATA, void *), void *)
+tdb_printfreelist: int (struct tdb_context *)
+tdb_remove_flags: void (struct tdb_context *, unsigned int)
+tdb_reopen: int (struct tdb_context *)
+tdb_reopen_all: int (int)
+tdb_repack: int (struct tdb_context *)
+tdb_rescue: int (struct tdb_context *, void (*)(TDB_DATA, TDB_DATA, void *), void *)
+tdb_runtime_check_for_robust_mutexes: bool (void)
+tdb_set_logging_function: void (struct tdb_context *, const struct tdb_logging_context *)
+tdb_set_max_dead: void (struct tdb_context *, int)
+tdb_setalarm_sigptr: void (struct tdb_context *, volatile sig_atomic_t *)
+tdb_store: int (struct tdb_context *, TDB_DATA, TDB_DATA, int)
+tdb_storev: int (struct tdb_context *, TDB_DATA, const TDB_DATA *, int, int)
+tdb_summary: char *(struct tdb_context *)
+tdb_transaction_active: bool (struct tdb_context *)
+tdb_transaction_cancel: int (struct tdb_context *)
+tdb_transaction_commit: int (struct tdb_context *)
+tdb_transaction_prepare_commit: int (struct tdb_context *)
+tdb_transaction_start: int (struct tdb_context *)
+tdb_transaction_start_nonblock: int (struct tdb_context *)
+tdb_transaction_write_lock_mark: int (struct tdb_context *)
+tdb_transaction_write_lock_unmark: int (struct tdb_context *)
+tdb_traverse: int (struct tdb_context *, tdb_traverse_func, void *)
+tdb_traverse_chain: int (struct tdb_context *, unsigned int, tdb_traverse_func, void *)
+tdb_traverse_key_chain: int (struct tdb_context *, TDB_DATA, tdb_traverse_func, void *)
+tdb_traverse_read: int (struct tdb_context *, tdb_traverse_func, void *)
+tdb_unlock: int (struct tdb_context *, int, int)
+tdb_unlockall: int (struct tdb_context *)
+tdb_unlockall_read: int (struct tdb_context *)
+tdb_validate_freelist: int (struct tdb_context *, int *)
+tdb_wipe_all: int (struct tdb_context *)
diff --git a/lib/tdb/ABI/tdb-1.4.4.sigs b/lib/tdb/ABI/tdb-1.4.4.sigs
new file mode 100644
index 0000000..e2b0427
--- /dev/null
+++ b/lib/tdb/ABI/tdb-1.4.4.sigs
@@ -0,0 +1,73 @@
+tdb_add_flags: void (struct tdb_context *, unsigned int)
+tdb_append: int (struct tdb_context *, TDB_DATA, TDB_DATA)
+tdb_chainlock: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_mark: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_nonblock: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_read: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_read_nonblock: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_unmark: int (struct tdb_context *, TDB_DATA)
+tdb_chainunlock: int (struct tdb_context *, TDB_DATA)
+tdb_chainunlock_read: int (struct tdb_context *, TDB_DATA)
+tdb_check: int (struct tdb_context *, int (*)(TDB_DATA, TDB_DATA, void *), void *)
+tdb_close: int (struct tdb_context *)
+tdb_delete: int (struct tdb_context *, TDB_DATA)
+tdb_dump_all: void (struct tdb_context *)
+tdb_enable_seqnum: void (struct tdb_context *)
+tdb_error: enum TDB_ERROR (struct tdb_context *)
+tdb_errorstr: const char *(struct tdb_context *)
+tdb_exists: int (struct tdb_context *, TDB_DATA)
+tdb_fd: int (struct tdb_context *)
+tdb_fetch: TDB_DATA (struct tdb_context *, TDB_DATA)
+tdb_firstkey: TDB_DATA (struct tdb_context *)
+tdb_freelist_size: int (struct tdb_context *)
+tdb_get_flags: int (struct tdb_context *)
+tdb_get_logging_private: void *(struct tdb_context *)
+tdb_get_seqnum: int (struct tdb_context *)
+tdb_hash_size: int (struct tdb_context *)
+tdb_increment_seqnum_nonblock: void (struct tdb_context *)
+tdb_jenkins_hash: unsigned int (TDB_DATA *)
+tdb_lock_nonblock: int (struct tdb_context *, int, int)
+tdb_lockall: int (struct tdb_context *)
+tdb_lockall_mark: int (struct tdb_context *)
+tdb_lockall_nonblock: int (struct tdb_context *)
+tdb_lockall_read: int (struct tdb_context *)
+tdb_lockall_read_nonblock: int (struct tdb_context *)
+tdb_lockall_unmark: int (struct tdb_context *)
+tdb_log_fn: tdb_log_func (struct tdb_context *)
+tdb_map_size: size_t (struct tdb_context *)
+tdb_name: const char *(struct tdb_context *)
+tdb_nextkey: TDB_DATA (struct tdb_context *, TDB_DATA)
+tdb_null: dptr = 0xXXXX, dsize = 0
+tdb_open: struct tdb_context *(const char *, int, int, int, mode_t)
+tdb_open_ex: struct tdb_context *(const char *, int, int, int, mode_t, const struct tdb_logging_context *, tdb_hash_func)
+tdb_parse_record: int (struct tdb_context *, TDB_DATA, int (*)(TDB_DATA, TDB_DATA, void *), void *)
+tdb_printfreelist: int (struct tdb_context *)
+tdb_remove_flags: void (struct tdb_context *, unsigned int)
+tdb_reopen: int (struct tdb_context *)
+tdb_reopen_all: int (int)
+tdb_repack: int (struct tdb_context *)
+tdb_rescue: int (struct tdb_context *, void (*)(TDB_DATA, TDB_DATA, void *), void *)
+tdb_runtime_check_for_robust_mutexes: bool (void)
+tdb_set_logging_function: void (struct tdb_context *, const struct tdb_logging_context *)
+tdb_set_max_dead: void (struct tdb_context *, int)
+tdb_setalarm_sigptr: void (struct tdb_context *, volatile sig_atomic_t *)
+tdb_store: int (struct tdb_context *, TDB_DATA, TDB_DATA, int)
+tdb_storev: int (struct tdb_context *, TDB_DATA, const TDB_DATA *, int, int)
+tdb_summary: char *(struct tdb_context *)
+tdb_transaction_active: bool (struct tdb_context *)
+tdb_transaction_cancel: int (struct tdb_context *)
+tdb_transaction_commit: int (struct tdb_context *)
+tdb_transaction_prepare_commit: int (struct tdb_context *)
+tdb_transaction_start: int (struct tdb_context *)
+tdb_transaction_start_nonblock: int (struct tdb_context *)
+tdb_transaction_write_lock_mark: int (struct tdb_context *)
+tdb_transaction_write_lock_unmark: int (struct tdb_context *)
+tdb_traverse: int (struct tdb_context *, tdb_traverse_func, void *)
+tdb_traverse_chain: int (struct tdb_context *, unsigned int, tdb_traverse_func, void *)
+tdb_traverse_key_chain: int (struct tdb_context *, TDB_DATA, tdb_traverse_func, void *)
+tdb_traverse_read: int (struct tdb_context *, tdb_traverse_func, void *)
+tdb_unlock: int (struct tdb_context *, int, int)
+tdb_unlockall: int (struct tdb_context *)
+tdb_unlockall_read: int (struct tdb_context *)
+tdb_validate_freelist: int (struct tdb_context *, int *)
+tdb_wipe_all: int (struct tdb_context *)
diff --git a/lib/tdb/ABI/tdb-1.4.5.sigs b/lib/tdb/ABI/tdb-1.4.5.sigs
new file mode 100644
index 0000000..e2b0427
--- /dev/null
+++ b/lib/tdb/ABI/tdb-1.4.5.sigs
@@ -0,0 +1,73 @@
+tdb_add_flags: void (struct tdb_context *, unsigned int)
+tdb_append: int (struct tdb_context *, TDB_DATA, TDB_DATA)
+tdb_chainlock: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_mark: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_nonblock: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_read: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_read_nonblock: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_unmark: int (struct tdb_context *, TDB_DATA)
+tdb_chainunlock: int (struct tdb_context *, TDB_DATA)
+tdb_chainunlock_read: int (struct tdb_context *, TDB_DATA)
+tdb_check: int (struct tdb_context *, int (*)(TDB_DATA, TDB_DATA, void *), void *)
+tdb_close: int (struct tdb_context *)
+tdb_delete: int (struct tdb_context *, TDB_DATA)
+tdb_dump_all: void (struct tdb_context *)
+tdb_enable_seqnum: void (struct tdb_context *)
+tdb_error: enum TDB_ERROR (struct tdb_context *)
+tdb_errorstr: const char *(struct tdb_context *)
+tdb_exists: int (struct tdb_context *, TDB_DATA)
+tdb_fd: int (struct tdb_context *)
+tdb_fetch: TDB_DATA (struct tdb_context *, TDB_DATA)
+tdb_firstkey: TDB_DATA (struct tdb_context *)
+tdb_freelist_size: int (struct tdb_context *)
+tdb_get_flags: int (struct tdb_context *)
+tdb_get_logging_private: void *(struct tdb_context *)
+tdb_get_seqnum: int (struct tdb_context *)
+tdb_hash_size: int (struct tdb_context *)
+tdb_increment_seqnum_nonblock: void (struct tdb_context *)
+tdb_jenkins_hash: unsigned int (TDB_DATA *)
+tdb_lock_nonblock: int (struct tdb_context *, int, int)
+tdb_lockall: int (struct tdb_context *)
+tdb_lockall_mark: int (struct tdb_context *)
+tdb_lockall_nonblock: int (struct tdb_context *)
+tdb_lockall_read: int (struct tdb_context *)
+tdb_lockall_read_nonblock: int (struct tdb_context *)
+tdb_lockall_unmark: int (struct tdb_context *)
+tdb_log_fn: tdb_log_func (struct tdb_context *)
+tdb_map_size: size_t (struct tdb_context *)
+tdb_name: const char *(struct tdb_context *)
+tdb_nextkey: TDB_DATA (struct tdb_context *, TDB_DATA)
+tdb_null: dptr = 0xXXXX, dsize = 0
+tdb_open: struct tdb_context *(const char *, int, int, int, mode_t)
+tdb_open_ex: struct tdb_context *(const char *, int, int, int, mode_t, const struct tdb_logging_context *, tdb_hash_func)
+tdb_parse_record: int (struct tdb_context *, TDB_DATA, int (*)(TDB_DATA, TDB_DATA, void *), void *)
+tdb_printfreelist: int (struct tdb_context *)
+tdb_remove_flags: void (struct tdb_context *, unsigned int)
+tdb_reopen: int (struct tdb_context *)
+tdb_reopen_all: int (int)
+tdb_repack: int (struct tdb_context *)
+tdb_rescue: int (struct tdb_context *, void (*)(TDB_DATA, TDB_DATA, void *), void *)
+tdb_runtime_check_for_robust_mutexes: bool (void)
+tdb_set_logging_function: void (struct tdb_context *, const struct tdb_logging_context *)
+tdb_set_max_dead: void (struct tdb_context *, int)
+tdb_setalarm_sigptr: void (struct tdb_context *, volatile sig_atomic_t *)
+tdb_store: int (struct tdb_context *, TDB_DATA, TDB_DATA, int)
+tdb_storev: int (struct tdb_context *, TDB_DATA, const TDB_DATA *, int, int)
+tdb_summary: char *(struct tdb_context *)
+tdb_transaction_active: bool (struct tdb_context *)
+tdb_transaction_cancel: int (struct tdb_context *)
+tdb_transaction_commit: int (struct tdb_context *)
+tdb_transaction_prepare_commit: int (struct tdb_context *)
+tdb_transaction_start: int (struct tdb_context *)
+tdb_transaction_start_nonblock: int (struct tdb_context *)
+tdb_transaction_write_lock_mark: int (struct tdb_context *)
+tdb_transaction_write_lock_unmark: int (struct tdb_context *)
+tdb_traverse: int (struct tdb_context *, tdb_traverse_func, void *)
+tdb_traverse_chain: int (struct tdb_context *, unsigned int, tdb_traverse_func, void *)
+tdb_traverse_key_chain: int (struct tdb_context *, TDB_DATA, tdb_traverse_func, void *)
+tdb_traverse_read: int (struct tdb_context *, tdb_traverse_func, void *)
+tdb_unlock: int (struct tdb_context *, int, int)
+tdb_unlockall: int (struct tdb_context *)
+tdb_unlockall_read: int (struct tdb_context *)
+tdb_validate_freelist: int (struct tdb_context *, int *)
+tdb_wipe_all: int (struct tdb_context *)
diff --git a/lib/tdb/ABI/tdb-1.4.6.sigs b/lib/tdb/ABI/tdb-1.4.6.sigs
new file mode 100644
index 0000000..e2b0427
--- /dev/null
+++ b/lib/tdb/ABI/tdb-1.4.6.sigs
@@ -0,0 +1,73 @@
+tdb_add_flags: void (struct tdb_context *, unsigned int)
+tdb_append: int (struct tdb_context *, TDB_DATA, TDB_DATA)
+tdb_chainlock: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_mark: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_nonblock: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_read: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_read_nonblock: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_unmark: int (struct tdb_context *, TDB_DATA)
+tdb_chainunlock: int (struct tdb_context *, TDB_DATA)
+tdb_chainunlock_read: int (struct tdb_context *, TDB_DATA)
+tdb_check: int (struct tdb_context *, int (*)(TDB_DATA, TDB_DATA, void *), void *)
+tdb_close: int (struct tdb_context *)
+tdb_delete: int (struct tdb_context *, TDB_DATA)
+tdb_dump_all: void (struct tdb_context *)
+tdb_enable_seqnum: void (struct tdb_context *)
+tdb_error: enum TDB_ERROR (struct tdb_context *)
+tdb_errorstr: const char *(struct tdb_context *)
+tdb_exists: int (struct tdb_context *, TDB_DATA)
+tdb_fd: int (struct tdb_context *)
+tdb_fetch: TDB_DATA (struct tdb_context *, TDB_DATA)
+tdb_firstkey: TDB_DATA (struct tdb_context *)
+tdb_freelist_size: int (struct tdb_context *)
+tdb_get_flags: int (struct tdb_context *)
+tdb_get_logging_private: void *(struct tdb_context *)
+tdb_get_seqnum: int (struct tdb_context *)
+tdb_hash_size: int (struct tdb_context *)
+tdb_increment_seqnum_nonblock: void (struct tdb_context *)
+tdb_jenkins_hash: unsigned int (TDB_DATA *)
+tdb_lock_nonblock: int (struct tdb_context *, int, int)
+tdb_lockall: int (struct tdb_context *)
+tdb_lockall_mark: int (struct tdb_context *)
+tdb_lockall_nonblock: int (struct tdb_context *)
+tdb_lockall_read: int (struct tdb_context *)
+tdb_lockall_read_nonblock: int (struct tdb_context *)
+tdb_lockall_unmark: int (struct tdb_context *)
+tdb_log_fn: tdb_log_func (struct tdb_context *)
+tdb_map_size: size_t (struct tdb_context *)
+tdb_name: const char *(struct tdb_context *)
+tdb_nextkey: TDB_DATA (struct tdb_context *, TDB_DATA)
+tdb_null: dptr = 0xXXXX, dsize = 0
+tdb_open: struct tdb_context *(const char *, int, int, int, mode_t)
+tdb_open_ex: struct tdb_context *(const char *, int, int, int, mode_t, const struct tdb_logging_context *, tdb_hash_func)
+tdb_parse_record: int (struct tdb_context *, TDB_DATA, int (*)(TDB_DATA, TDB_DATA, void *), void *)
+tdb_printfreelist: int (struct tdb_context *)
+tdb_remove_flags: void (struct tdb_context *, unsigned int)
+tdb_reopen: int (struct tdb_context *)
+tdb_reopen_all: int (int)
+tdb_repack: int (struct tdb_context *)
+tdb_rescue: int (struct tdb_context *, void (*)(TDB_DATA, TDB_DATA, void *), void *)
+tdb_runtime_check_for_robust_mutexes: bool (void)
+tdb_set_logging_function: void (struct tdb_context *, const struct tdb_logging_context *)
+tdb_set_max_dead: void (struct tdb_context *, int)
+tdb_setalarm_sigptr: void (struct tdb_context *, volatile sig_atomic_t *)
+tdb_store: int (struct tdb_context *, TDB_DATA, TDB_DATA, int)
+tdb_storev: int (struct tdb_context *, TDB_DATA, const TDB_DATA *, int, int)
+tdb_summary: char *(struct tdb_context *)
+tdb_transaction_active: bool (struct tdb_context *)
+tdb_transaction_cancel: int (struct tdb_context *)
+tdb_transaction_commit: int (struct tdb_context *)
+tdb_transaction_prepare_commit: int (struct tdb_context *)
+tdb_transaction_start: int (struct tdb_context *)
+tdb_transaction_start_nonblock: int (struct tdb_context *)
+tdb_transaction_write_lock_mark: int (struct tdb_context *)
+tdb_transaction_write_lock_unmark: int (struct tdb_context *)
+tdb_traverse: int (struct tdb_context *, tdb_traverse_func, void *)
+tdb_traverse_chain: int (struct tdb_context *, unsigned int, tdb_traverse_func, void *)
+tdb_traverse_key_chain: int (struct tdb_context *, TDB_DATA, tdb_traverse_func, void *)
+tdb_traverse_read: int (struct tdb_context *, tdb_traverse_func, void *)
+tdb_unlock: int (struct tdb_context *, int, int)
+tdb_unlockall: int (struct tdb_context *)
+tdb_unlockall_read: int (struct tdb_context *)
+tdb_validate_freelist: int (struct tdb_context *, int *)
+tdb_wipe_all: int (struct tdb_context *)
diff --git a/lib/tdb/ABI/tdb-1.4.7.sigs b/lib/tdb/ABI/tdb-1.4.7.sigs
new file mode 100644
index 0000000..e2b0427
--- /dev/null
+++ b/lib/tdb/ABI/tdb-1.4.7.sigs
@@ -0,0 +1,73 @@
+tdb_add_flags: void (struct tdb_context *, unsigned int)
+tdb_append: int (struct tdb_context *, TDB_DATA, TDB_DATA)
+tdb_chainlock: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_mark: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_nonblock: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_read: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_read_nonblock: int (struct tdb_context *, TDB_DATA)
+tdb_chainlock_unmark: int (struct tdb_context *, TDB_DATA)
+tdb_chainunlock: int (struct tdb_context *, TDB_DATA)
+tdb_chainunlock_read: int (struct tdb_context *, TDB_DATA)
+tdb_check: int (struct tdb_context *, int (*)(TDB_DATA, TDB_DATA, void *), void *)
+tdb_close: int (struct tdb_context *)
+tdb_delete: int (struct tdb_context *, TDB_DATA)
+tdb_dump_all: void (struct tdb_context *)
+tdb_enable_seqnum: void (struct tdb_context *)
+tdb_error: enum TDB_ERROR (struct tdb_context *)
+tdb_errorstr: const char *(struct tdb_context *)
+tdb_exists: int (struct tdb_context *, TDB_DATA)
+tdb_fd: int (struct tdb_context *)
+tdb_fetch: TDB_DATA (struct tdb_context *, TDB_DATA)
+tdb_firstkey: TDB_DATA (struct tdb_context *)
+tdb_freelist_size: int (struct tdb_context *)
+tdb_get_flags: int (struct tdb_context *)
+tdb_get_logging_private: void *(struct tdb_context *)
+tdb_get_seqnum: int (struct tdb_context *)
+tdb_hash_size: int (struct tdb_context *)
+tdb_increment_seqnum_nonblock: void (struct tdb_context *)
+tdb_jenkins_hash: unsigned int (TDB_DATA *)
+tdb_lock_nonblock: int (struct tdb_context *, int, int)
+tdb_lockall: int (struct tdb_context *)
+tdb_lockall_mark: int (struct tdb_context *)
+tdb_lockall_nonblock: int (struct tdb_context *)
+tdb_lockall_read: int (struct tdb_context *)
+tdb_lockall_read_nonblock: int (struct tdb_context *)
+tdb_lockall_unmark: int (struct tdb_context *)
+tdb_log_fn: tdb_log_func (struct tdb_context *)
+tdb_map_size: size_t (struct tdb_context *)
+tdb_name: const char *(struct tdb_context *)
+tdb_nextkey: TDB_DATA (struct tdb_context *, TDB_DATA)
+tdb_null: dptr = 0xXXXX, dsize = 0
+tdb_open: struct tdb_context *(const char *, int, int, int, mode_t)
+tdb_open_ex: struct tdb_context *(const char *, int, int, int, mode_t, const struct tdb_logging_context *, tdb_hash_func)
+tdb_parse_record: int (struct tdb_context *, TDB_DATA, int (*)(TDB_DATA, TDB_DATA, void *), void *)
+tdb_printfreelist: int (struct tdb_context *)
+tdb_remove_flags: void (struct tdb_context *, unsigned int)
+tdb_reopen: int (struct tdb_context *)
+tdb_reopen_all: int (int)
+tdb_repack: int (struct tdb_context *)
+tdb_rescue: int (struct tdb_context *, void (*)(TDB_DATA, TDB_DATA, void *), void *)
+tdb_runtime_check_for_robust_mutexes: bool (void)
+tdb_set_logging_function: void (struct tdb_context *, const struct tdb_logging_context *)
+tdb_set_max_dead: void (struct tdb_context *, int)
+tdb_setalarm_sigptr: void (struct tdb_context *, volatile sig_atomic_t *)
+tdb_store: int (struct tdb_context *, TDB_DATA, TDB_DATA, int)
+tdb_storev: int (struct tdb_context *, TDB_DATA, const TDB_DATA *, int, int)
+tdb_summary: char *(struct tdb_context *)
+tdb_transaction_active: bool (struct tdb_context *)
+tdb_transaction_cancel: int (struct tdb_context *)
+tdb_transaction_commit: int (struct tdb_context *)
+tdb_transaction_prepare_commit: int (struct tdb_context *)
+tdb_transaction_start: int (struct tdb_context *)
+tdb_transaction_start_nonblock: int (struct tdb_context *)
+tdb_transaction_write_lock_mark: int (struct tdb_context *)
+tdb_transaction_write_lock_unmark: int (struct tdb_context *)
+tdb_traverse: int (struct tdb_context *, tdb_traverse_func, void *)
+tdb_traverse_chain: int (struct tdb_context *, unsigned int, tdb_traverse_func, void *)
+tdb_traverse_key_chain: int (struct tdb_context *, TDB_DATA, tdb_traverse_func, void *)
+tdb_traverse_read: int (struct tdb_context *, tdb_traverse_func, void *)
+tdb_unlock: int (struct tdb_context *, int, int)
+tdb_unlockall: int (struct tdb_context *)
+tdb_unlockall_read: int (struct tdb_context *)
+tdb_validate_freelist: int (struct tdb_context *, int *)
+tdb_wipe_all: int (struct tdb_context *)
diff --git a/lib/tdb/Makefile b/lib/tdb/Makefile
new file mode 100644
index 0000000..8fd56c8
--- /dev/null
+++ b/lib/tdb/Makefile
@@ -0,0 +1,68 @@
+# simple makefile wrapper to run waf
+
+WAF_BIN=`PATH=buildtools/bin:../../buildtools/bin:$$PATH which waf`
+WAF_BINARY=$(PYTHON) $(WAF_BIN)
+WAF=PYTHONHASHSEED=1 WAF_MAKE=1 $(WAF_BINARY)
+
+all:
+ $(WAF) build
+
+install:
+ $(WAF) install
+
+uninstall:
+ $(WAF) uninstall
+
+test: FORCE
+ $(WAF) test $(TEST_OPTIONS)
+
+testenv:
+ $(WAF) test --testenv $(TEST_OPTIONS)
+
+quicktest:
+ $(WAF) test --quick $(TEST_OPTIONS)
+
+dist:
+ touch .tmplock
+ WAFLOCK=.tmplock $(WAF) dist
+
+distcheck:
+ touch .tmplock
+ WAFLOCK=.tmplock $(WAF) distcheck
+
+clean:
+ $(WAF) clean
+
+distclean:
+ $(WAF) distclean
+
+reconfigure: configure
+ $(WAF) reconfigure
+
+show_waf_options:
+ $(WAF) --help
+
+# some compatibility make targets
+everything: all
+
+testsuite: all
+
+check: test
+
+torture: all
+
+# this should do an install as well, once install is finished
+installcheck: test
+
+etags:
+ $(WAF) etags
+
+ctags:
+ $(WAF) ctags
+
+pydoctor:
+ $(WAF) pydoctor
+
+bin/%:: FORCE
+ $(WAF) --targets=`basename $@`
+FORCE:
diff --git a/lib/tdb/_tdb_text.py b/lib/tdb/_tdb_text.py
new file mode 100644
index 0000000..f3caa53
--- /dev/null
+++ b/lib/tdb/_tdb_text.py
@@ -0,0 +1,137 @@
+# Text wrapper for tdb bindings
+#
+# Copyright (C) 2015 Petr Viktorin <pviktori@redhat.com>
+# Published under the GNU LGPLv3 or later
+
+import sys
+
+import tdb
+
+
+class TdbTextWrapper(object):
+ """Text interface for a TDB file"""
+
+ def __init__(self, tdb):
+ self._tdb = tdb
+
+ @property
+ def raw(self):
+ return self._tdb
+
+ def get(self, key):
+ key = key.encode('utf-8')
+ result = self._tdb.get(key)
+ if result is not None:
+ return result.decode('utf-8')
+
+ def append(self, key, value):
+ key = key.encode('utf-8')
+ value = value.encode('utf-8')
+ self._tdb.append(key, value)
+
+ def firstkey(self):
+ result = self._tdb.firstkey()
+ if result:
+ return result.decode('utf-8')
+
+ def nextkey(self, key):
+ key = key.encode('utf-8')
+ result = self._tdb.nextkey(key)
+ if result is not None:
+ return result.decode('utf-8')
+
+ def delete(self, key):
+ key = key.encode('utf-8')
+ self._tdb.delete(key)
+
+ def store(self, key, value):
+ key = key.encode('utf-8')
+ value = value.encode('utf-8')
+ self._tdb.store(key, value)
+
+ def __iter__(self):
+ for key in iter(self._tdb):
+ yield key.decode('utf-8')
+
+ def __getitem__(self, key):
+ key = key.encode('utf-8')
+ result = self._tdb[key]
+ return result.decode('utf-8')
+
+ def __contains__(self, key):
+ key = key.encode('utf-8')
+ return key in self._tdb
+
+ def __repr__(self):
+ return '<TdbTextWrapper for %r>' % self._tdb
+
+ def __setitem__(self, key, value):
+ key = key.encode('utf-8')
+ value = value.encode('utf-8')
+ self._tdb[key] = value
+
+ def __delitem__(self, key):
+ key = key.encode('utf-8')
+ del self._tdb[key]
+
+ if sys.version_info > (3, 0):
+ keys = __iter__
+ else:
+ iterkeys = __iter__
+ has_key = __contains__
+
+
+## Add wrappers for functions and getters that don't deal with text
+
+def _add_wrapper(name):
+ orig = getattr(tdb.Tdb, name)
+
+ def wrapper(self, *args, **kwargs):
+ return orig(self._tdb, *args, **kwargs)
+ wrapper.__name__ = orig.__name__
+ wrapper.__doc__ = orig.__doc__
+
+ setattr(TdbTextWrapper, name, wrapper)
+
+for name in ("transaction_cancel",
+ "transaction_commit",
+ "transaction_prepare_commit",
+ "transaction_start",
+ "reopen",
+ "lock_all",
+ "unlock_all",
+ "read_lock_all",
+ "read_unlock_all",
+ "close",
+ "add_flags",
+ "remove_flags",
+ "clear",
+ "repack",
+ "enable_seqnum",
+ "increment_seqnum_nonblock",
+ ):
+ _add_wrapper(name)
+
+
+def _add_getter(name):
+ orig = getattr(tdb.Tdb, name)
+ doc = orig.__doc__
+
+ def getter(self):
+ return getattr(self._tdb, name)
+
+ def setter(self, value):
+ return setattr(self._tdb, name, value)
+
+ setattr(TdbTextWrapper, name, property(getter, setter, doc=doc))
+
+for name in ("hash_size",
+ "map_size",
+ "freelist_size",
+ "flags",
+ "max_dead",
+ "filename",
+ "seqnum",
+ "text",
+ ):
+ _add_getter(name)
diff --git a/lib/tdb/common/check.c b/lib/tdb/common/check.c
new file mode 100644
index 0000000..d7741f6
--- /dev/null
+++ b/lib/tdb/common/check.c
@@ -0,0 +1,489 @@
+ /*
+ Unix SMB/CIFS implementation.
+
+ trivial database library
+
+ Copyright (C) Rusty Russell 2009
+
+ ** NOTE! The following LGPL license applies to the tdb
+ ** library. This does NOT imply that all of Samba is released
+ ** under the LGPL
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 3 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, see <http://www.gnu.org/licenses/>.
+*/
+#include "tdb_private.h"
+
+/* Since we opened it, these shouldn't fail unless it's recent corruption. */
+static bool tdb_check_header(struct tdb_context *tdb, tdb_off_t *recovery)
+{
+ struct tdb_header hdr;
+ uint32_t h1, h2;
+
+ if (tdb->methods->tdb_read(tdb, 0, &hdr, sizeof(hdr), 0) == -1)
+ return false;
+ if (strcmp(hdr.magic_food, TDB_MAGIC_FOOD) != 0)
+ goto corrupt;
+
+ CONVERT(hdr);
+ if (hdr.version != TDB_VERSION)
+ goto corrupt;
+
+ if (hdr.rwlocks != 0 &&
+ hdr.rwlocks != TDB_FEATURE_FLAG_MAGIC &&
+ hdr.rwlocks != TDB_HASH_RWLOCK_MAGIC)
+ goto corrupt;
+
+ tdb_header_hash(tdb, &h1, &h2);
+ if (hdr.magic1_hash && hdr.magic2_hash &&
+ (hdr.magic1_hash != h1 || hdr.magic2_hash != h2))
+ goto corrupt;
+
+ if (hdr.hash_size == 0)
+ goto corrupt;
+
+ if (hdr.hash_size != tdb->hash_size)
+ goto corrupt;
+
+ if (hdr.recovery_start != 0 &&
+ hdr.recovery_start < TDB_DATA_START(tdb->hash_size))
+ goto corrupt;
+
+ *recovery = hdr.recovery_start;
+ return true;
+
+corrupt:
+ tdb->ecode = TDB_ERR_CORRUPT;
+ TDB_LOG((tdb, TDB_DEBUG_ERROR, "Header is corrupt\n"));
+ return false;
+}
+
+/* Generic record header check. */
+static bool tdb_check_record(struct tdb_context *tdb,
+ tdb_off_t off,
+ const struct tdb_record *rec)
+{
+ tdb_off_t tailer;
+
+ /* Check rec->next: 0 or points to record offset, aligned. */
+ if (rec->next > 0 && rec->next < TDB_DATA_START(tdb->hash_size)){
+ TDB_LOG((tdb, TDB_DEBUG_ERROR,
+ "Record offset %u too small next %u\n",
+ off, rec->next));
+ goto corrupt;
+ }
+ if (rec->next + sizeof(*rec) < rec->next) {
+ TDB_LOG((tdb, TDB_DEBUG_ERROR,
+ "Record offset %u too large next %u\n",
+ off, rec->next));
+ goto corrupt;
+ }
+ if ((rec->next % TDB_ALIGNMENT) != 0) {
+ TDB_LOG((tdb, TDB_DEBUG_ERROR,
+ "Record offset %u misaligned next %u\n",
+ off, rec->next));
+ goto corrupt;
+ }
+ if (tdb_oob(tdb, rec->next, sizeof(*rec), 0))
+ goto corrupt;
+
+ /* Check rec_len: similar to rec->next, implies next record. */
+ if ((rec->rec_len % TDB_ALIGNMENT) != 0) {
+ TDB_LOG((tdb, TDB_DEBUG_ERROR,
+ "Record offset %u misaligned length %u\n",
+ off, rec->rec_len));
+ goto corrupt;
+ }
+ /* Must fit tailer. */
+ if (rec->rec_len < sizeof(tailer)) {
+ TDB_LOG((tdb, TDB_DEBUG_ERROR,
+ "Record offset %u too short length %u\n",
+ off, rec->rec_len));
+ goto corrupt;
+ }
+ /* OOB allows "right at the end" access, so this works for last rec. */
+ if (tdb_oob(tdb, off, sizeof(*rec)+rec->rec_len, 0))
+ goto corrupt;
+
+ /* Check tailer. */
+ if (tdb_ofs_read(tdb, off+sizeof(*rec)+rec->rec_len-sizeof(tailer),
+ &tailer) == -1)
+ goto corrupt;
+ if (tailer != sizeof(*rec) + rec->rec_len) {
+ TDB_LOG((tdb, TDB_DEBUG_ERROR,
+ "Record offset %u invalid tailer\n", off));
+ goto corrupt;
+ }
+
+ return true;
+
+corrupt:
+ tdb->ecode = TDB_ERR_CORRUPT;
+ return false;
+}
+
+/* Grab some bytes: may copy if can't use mmap.
+ Caller has already done bounds check. */
+static TDB_DATA get_bytes(struct tdb_context *tdb,
+ tdb_off_t off, tdb_len_t len)
+{
+ TDB_DATA d;
+
+ d.dsize = len;
+
+ if (tdb->transaction == NULL && tdb->map_ptr != NULL)
+ d.dptr = (unsigned char *)tdb->map_ptr + off;
+ else
+ d.dptr = tdb_alloc_read(tdb, off, d.dsize);
+ return d;
+}
+
+/* Frees data if we're not able to simply use mmap. */
+static void put_bytes(struct tdb_context *tdb, TDB_DATA d)
+{
+ if (tdb->transaction == NULL && tdb->map_ptr != NULL)
+ return;
+ free(d.dptr);
+}
+
+/* We use the excellent Jenkins lookup3 hash; this is based on hash_word2.
+ * See: http://burtleburtle.net/bob/c/lookup3.c
+ */
+#define rot(x,k) (((x)<<(k)) | ((x)>>(32-(k))))
+static void hash(uint32_t key, uint32_t *pc, uint32_t *pb)
+{
+ uint32_t a,b,c;
+
+ /* Set up the internal state */
+ a = b = c = 0xdeadbeef + *pc;
+ c += *pb;
+ a += key;
+ c ^= b; c -= rot(b,14);
+ a ^= c; a -= rot(c,11);
+ b ^= a; b -= rot(a,25);
+ c ^= b; c -= rot(b,16);
+ a ^= c; a -= rot(c,4);
+ b ^= a; b -= rot(a,14);
+ c ^= b; c -= rot(b,24);
+ *pc=c; *pb=b;
+}
+
+/*
+ We want to check that all free records are in the free list
+ (only once), and all free list entries are free records. Similarly
+ for each hash chain of used records.
+
+ Doing that naively (without walking hash chains, since we want to be
+ linear) means keeping a list of records which have been seen in each
+ hash chain, and another of records pointed to (ie. next pointers
+ from records and the initial hash chain heads). These two lists
+ should be equal. This will take 8 bytes per record, and require
+ sorting at the end.
+
+ So instead, we record each offset in a bitmap such a way that
+ recording it twice will cancel out. Since each offset should appear
+ exactly twice, the bitmap should be zero at the end.
+
+ The approach was inspired by Bloom Filters (see Wikipedia). For
+ each value, we flip K bits in a bitmap of size N. The number of
+ distinct arrangements is:
+
+ N! / (K! * (N-K)!)
+
+ Of course, not all arrangements are actually distinct, but testing
+ shows this formula to be close enough.
+
+ So, if K == 8 and N == 256, the probability of two things flipping the same
+ bits is 1 in 409,663,695,276,000.
+
+ Given that ldb uses a hash size of 10000, using 32 bytes per hash chain
+ (320k) seems reasonable.
+*/
+#define NUM_HASHES 8
+#define BITMAP_BITS 256
+
+static void bit_flip(unsigned char bits[], unsigned int idx)
+{
+ bits[idx / CHAR_BIT] ^= (1 << (idx % CHAR_BIT));
+}
+
+/* We record offsets in a bitmap for the particular chain it should be in. */
+static void record_offset(unsigned char bits[], tdb_off_t off)
+{
+ uint32_t h1 = off, h2 = 0;
+ unsigned int i;
+
+ /* We get two good hash values out of jhash2, so we use both. Then
+ * we keep going to produce further hash values. */
+ for (i = 0; i < NUM_HASHES / 2; i++) {
+ hash(off, &h1, &h2);
+ bit_flip(bits, h1 % BITMAP_BITS);
+ bit_flip(bits, h2 % BITMAP_BITS);
+ h2++;
+ }
+}
+
+/* Check that an in-use record is valid. */
+static bool tdb_check_used_record(struct tdb_context *tdb,
+ tdb_off_t off,
+ const struct tdb_record *rec,
+ unsigned char **hashes,
+ int (*check)(TDB_DATA, TDB_DATA, void *),
+ void *private_data)
+{
+ TDB_DATA key, data;
+ tdb_len_t len;
+
+ if (!tdb_check_record(tdb, off, rec))
+ return false;
+
+ /* key + data + tailer must fit in record */
+ len = rec->key_len;
+ len += rec->data_len;
+ if (len < rec->data_len) {
+ /* overflow */
+ TDB_LOG((tdb, TDB_DEBUG_ERROR, "Record lengths overflow\n"));
+ return false;
+ }
+ len += sizeof(tdb_off_t);
+ if (len < sizeof(tdb_off_t)) {
+ /* overflow */
+ TDB_LOG((tdb, TDB_DEBUG_ERROR, "Record lengths overflow\n"));
+ return false;
+ }
+
+ if (len > rec->rec_len) {
+ TDB_LOG((tdb, TDB_DEBUG_ERROR,
+ "Record offset %u too short for contents\n", off));
+ return false;
+ }
+
+ key = get_bytes(tdb, off + sizeof(*rec), rec->key_len);
+ if (!key.dptr)
+ return false;
+
+ if (tdb->hash_fn(&key) != rec->full_hash) {
+ TDB_LOG((tdb, TDB_DEBUG_ERROR,
+ "Record offset %u has incorrect hash\n", off));
+ goto fail_put_key;
+ }
+
+ /* Mark this offset as a known value for this hash bucket. */
+ record_offset(hashes[BUCKET(rec->full_hash)+1], off);
+ /* And similarly if the next pointer is valid. */
+ if (rec->next)
+ record_offset(hashes[BUCKET(rec->full_hash)+1], rec->next);
+
+ /* If they supply a check function and this record isn't dead,
+ get data and feed it. */
+ if (check && rec->magic != TDB_DEAD_MAGIC) {
+ data = get_bytes(tdb, off + sizeof(*rec) + rec->key_len,
+ rec->data_len);
+ if (!data.dptr)
+ goto fail_put_key;
+
+ if (check(key, data, private_data) == -1)
+ goto fail_put_data;
+ put_bytes(tdb, data);
+ }
+
+ put_bytes(tdb, key);
+ return true;
+
+fail_put_data:
+ put_bytes(tdb, data);
+fail_put_key:
+ put_bytes(tdb, key);
+ return false;
+}
+
+/* Check that an unused record is valid. */
+static bool tdb_check_free_record(struct tdb_context *tdb,
+ tdb_off_t off,
+ const struct tdb_record *rec,
+ unsigned char **hashes)
+{
+ if (!tdb_check_record(tdb, off, rec))
+ return false;
+
+ /* Mark this offset as a known value for the free list. */
+ record_offset(hashes[0], off);
+ /* And similarly if the next pointer is valid. */
+ if (rec->next)
+ record_offset(hashes[0], rec->next);
+ return true;
+}
+
+/* Slow, but should be very rare. */
+size_t tdb_dead_space(struct tdb_context *tdb, tdb_off_t off)
+{
+ size_t len;
+
+ for (len = 0; off + len < tdb->map_size; len++) {
+ char c;
+ if (tdb->methods->tdb_read(tdb, off, &c, 1, 0))
+ return 0;
+ if (c != 0 && c != 0x42)
+ break;
+ }
+ return len;
+}
+
+_PUBLIC_ int tdb_check(struct tdb_context *tdb,
+ int (*check)(TDB_DATA key, TDB_DATA data, void *private_data),
+ void *private_data)
+{
+ unsigned int h;
+ unsigned char **hashes;
+ tdb_off_t off, recovery_start;
+ struct tdb_record rec;
+ bool found_recovery = false;
+ tdb_len_t dead;
+ bool locked;
+
+ /* Read-only databases use no locking at all: it's best-effort.
+ * We may have a write lock already, so skip that case too. */
+ if (tdb->read_only || tdb->allrecord_lock.count != 0) {
+ locked = false;
+ } else {
+ if (tdb_lockall_read(tdb) == -1)
+ return -1;
+ locked = true;
+ }
+
+ /* Make sure we know true size of the underlying file. */
+ tdb_oob(tdb, tdb->map_size, 1, 1);
+
+ /* Header must be OK: also gets us the recovery ptr, if any. */
+ if (!tdb_check_header(tdb, &recovery_start))
+ goto unlock;
+
+ /* We should have the whole header, too. */
+ if (tdb->map_size < TDB_DATA_START(tdb->hash_size)) {
+ tdb->ecode = TDB_ERR_CORRUPT;
+ TDB_LOG((tdb, TDB_DEBUG_ERROR, "File too short for hashes\n"));
+ goto unlock;
+ }
+
+ /* One big malloc: pointers then bit arrays. */
+ hashes = (unsigned char **)calloc(
+ 1, sizeof(hashes[0]) * (1+tdb->hash_size)
+ + BITMAP_BITS / CHAR_BIT * (1+tdb->hash_size));
+ if (!hashes) {
+ tdb->ecode = TDB_ERR_OOM;
+ goto unlock;
+ }
+
+ /* Initialize pointers */
+ hashes[0] = (unsigned char *)(&hashes[1+tdb->hash_size]);
+ for (h = 1; h < 1+tdb->hash_size; h++)
+ hashes[h] = hashes[h-1] + BITMAP_BITS / CHAR_BIT;
+
+ /* Freelist and hash headers are all in a row: read them. */
+ for (h = 0; h < 1+tdb->hash_size; h++) {
+ if (tdb_ofs_read(tdb, FREELIST_TOP + h*sizeof(tdb_off_t),
+ &off) == -1)
+ goto free;
+ if (off)
+ record_offset(hashes[h], off);
+ }
+
+ /* For each record, read it in and check it's ok. */
+ for (off = TDB_DATA_START(tdb->hash_size);
+ off < tdb->map_size;
+ off += sizeof(rec) + rec.rec_len) {
+ if (tdb->methods->tdb_read(tdb, off, &rec, sizeof(rec),
+ DOCONV()) == -1)
+ goto free;
+ switch (rec.magic) {
+ case TDB_MAGIC:
+ case TDB_DEAD_MAGIC:
+ if (!tdb_check_used_record(tdb, off, &rec, hashes,
+ check, private_data))
+ goto free;
+ break;
+ case TDB_FREE_MAGIC:
+ if (!tdb_check_free_record(tdb, off, &rec, hashes))
+ goto free;
+ break;
+ /* If we crash after ftruncate, we can get zeroes or fill. */
+ case TDB_RECOVERY_INVALID_MAGIC:
+ case 0x42424242:
+ if (recovery_start == off) {
+ found_recovery = true;
+ break;
+ }
+ dead = tdb_dead_space(tdb, off);
+ if (dead < sizeof(rec))
+ goto corrupt;
+
+ TDB_LOG((tdb, TDB_DEBUG_ERROR,
+ "Dead space at %u-%u (of %u)\n",
+ off, off + dead, tdb->map_size));
+ rec.rec_len = dead - sizeof(rec);
+ break;
+ case TDB_RECOVERY_MAGIC:
+ if (recovery_start != off) {
+ TDB_LOG((tdb, TDB_DEBUG_ERROR,
+ "Unexpected recovery record at offset %u\n",
+ off));
+ goto free;
+ }
+ found_recovery = true;
+ break;
+ default: ;
+ corrupt:
+ tdb->ecode = TDB_ERR_CORRUPT;
+ TDB_LOG((tdb, TDB_DEBUG_ERROR,
+ "Bad magic 0x%x at offset %u\n",
+ rec.magic, off));
+ goto free;
+ }
+ }
+
+ /* Now, hashes should all be empty: each record exists and is referred
+ * to by one other. */
+ for (h = 0; h < 1+tdb->hash_size; h++) {
+ unsigned int i;
+ for (i = 0; i < BITMAP_BITS / CHAR_BIT; i++) {
+ if (hashes[h][i] != 0) {
+ tdb->ecode = TDB_ERR_CORRUPT;
+ TDB_LOG((tdb, TDB_DEBUG_ERROR,
+ "Hashes do not match records\n"));
+ goto free;
+ }
+ }
+ }
+
+ /* We must have found recovery area if there was one. */
+ if (recovery_start != 0 && !found_recovery) {
+ TDB_LOG((tdb, TDB_DEBUG_ERROR,
+ "Expected a recovery area at %u\n",
+ recovery_start));
+ goto free;
+ }
+
+ free(hashes);
+ if (locked) {
+ tdb_unlockall_read(tdb);
+ }
+ return 0;
+
+free:
+ free(hashes);
+unlock:
+ if (locked) {
+ tdb_unlockall_read(tdb);
+ }
+ return -1;
+}
diff --git a/lib/tdb/common/dump.c b/lib/tdb/common/dump.c
new file mode 100644
index 0000000..adcf591
--- /dev/null
+++ b/lib/tdb/common/dump.c
@@ -0,0 +1,149 @@
+ /*
+ Unix SMB/CIFS implementation.
+
+ trivial database library
+
+ Copyright (C) Andrew Tridgell 1999-2005
+ Copyright (C) Paul `Rusty' Russell 2000
+ Copyright (C) Jeremy Allison 2000-2003
+
+ ** NOTE! The following LGPL license applies to the tdb
+ ** library. This does NOT imply that all of Samba is released
+ ** under the LGPL
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 3 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "tdb_private.h"
+
+static tdb_off_t tdb_dump_record(struct tdb_context *tdb, int hash,
+ tdb_off_t offset)
+{
+ struct tdb_record rec;
+ tdb_off_t tailer_ofs, tailer;
+
+ if (tdb->methods->tdb_read(tdb, offset, (char *)&rec,
+ sizeof(rec), DOCONV()) == -1) {
+ printf("ERROR: failed to read record at %u\n", offset);
+ return 0;
+ }
+
+ printf(" rec: hash=%d offset=0x%08x next=0x%08x rec_len=%u "
+ "key_len=%u data_len=%u full_hash=0x%08x magic=0x%08x\n",
+ hash, offset, rec.next, rec.rec_len, rec.key_len, rec.data_len,
+ rec.full_hash, rec.magic);
+
+ tailer_ofs = offset + sizeof(rec) + rec.rec_len - sizeof(tdb_off_t);
+
+ if (tdb_ofs_read(tdb, tailer_ofs, &tailer) == -1) {
+ printf("ERROR: failed to read tailer at %u\n", tailer_ofs);
+ return rec.next;
+ }
+
+ if (tailer != rec.rec_len + sizeof(rec)) {
+ printf("ERROR: tailer does not match record! tailer=%u totalsize=%u\n",
+ (unsigned int)tailer, (unsigned int)(rec.rec_len + sizeof(rec)));
+ }
+ return rec.next;
+}
+
+static int tdb_dump_chain(struct tdb_context *tdb, int i)
+{
+ struct tdb_chainwalk_ctx chainwalk;
+ tdb_off_t rec_ptr, top;
+
+ if (i == -1) {
+ top = FREELIST_TOP;
+ } else {
+ top = TDB_HASH_TOP(i);
+ }
+
+ if (tdb_lock(tdb, i, F_WRLCK) != 0)
+ return -1;
+
+ if (tdb_ofs_read(tdb, top, &rec_ptr) == -1)
+ return tdb_unlock(tdb, i, F_WRLCK);
+
+ tdb_chainwalk_init(&chainwalk, rec_ptr);
+
+ if (rec_ptr)
+ printf("hash=%d\n", i);
+
+ while (rec_ptr) {
+ bool ok;
+ rec_ptr = tdb_dump_record(tdb, i, rec_ptr);
+ ok = tdb_chainwalk_check(tdb, &chainwalk, rec_ptr);
+ if (!ok) {
+ printf("circular hash chain %d\n", i);
+ break;
+ }
+ }
+
+ return tdb_unlock(tdb, i, F_WRLCK);
+}
+
+_PUBLIC_ void tdb_dump_all(struct tdb_context *tdb)
+{
+ uint32_t i;
+ for (i=0;i<tdb->hash_size;i++) {
+ tdb_dump_chain(tdb, i);
+ }
+ printf("freelist:\n");
+ tdb_dump_chain(tdb, -1);
+}
+
+_PUBLIC_ int tdb_printfreelist(struct tdb_context *tdb)
+{
+ int ret;
+ long total_free = 0;
+ tdb_off_t offset, rec_ptr;
+ struct tdb_record rec;
+
+ if ((ret = tdb_lock(tdb, -1, F_WRLCK)) != 0)
+ return ret;
+
+ offset = FREELIST_TOP;
+
+ /* read in the freelist top */
+ if (tdb_ofs_read(tdb, offset, &rec_ptr) == -1) {
+ tdb_unlock(tdb, -1, F_WRLCK);
+ return 0;
+ }
+
+ printf("freelist top=[0x%08x]\n", rec_ptr );
+ while (rec_ptr) {
+ if (tdb->methods->tdb_read(tdb, rec_ptr, (char *)&rec,
+ sizeof(rec), DOCONV()) == -1) {
+ tdb_unlock(tdb, -1, F_WRLCK);
+ return -1;
+ }
+
+ if (rec.magic != TDB_FREE_MAGIC) {
+ printf("bad magic 0x%08x in free list\n", rec.magic);
+ tdb_unlock(tdb, -1, F_WRLCK);
+ return -1;
+ }
+
+ printf("entry offset=[0x%08x], rec.rec_len = [0x%08x (%u)] (end = 0x%08x)\n",
+ rec_ptr, rec.rec_len, rec.rec_len, rec_ptr + rec.rec_len);
+ total_free += rec.rec_len;
+
+ /* move to the next record */
+ rec_ptr = rec.next;
+ }
+ printf("total rec_len = [0x%08lx (%lu)]\n", total_free, total_free);
+
+ return tdb_unlock(tdb, -1, F_WRLCK);
+}
+
diff --git a/lib/tdb/common/error.c b/lib/tdb/common/error.c
new file mode 100644
index 0000000..478eb88
--- /dev/null
+++ b/lib/tdb/common/error.c
@@ -0,0 +1,57 @@
+ /*
+ Unix SMB/CIFS implementation.
+
+ trivial database library
+
+ Copyright (C) Andrew Tridgell 1999-2005
+ Copyright (C) Paul `Rusty' Russell 2000
+ Copyright (C) Jeremy Allison 2000-2003
+
+ ** NOTE! The following LGPL license applies to the tdb
+ ** library. This does NOT imply that all of Samba is released
+ ** under the LGPL
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 3 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "tdb_private.h"
+
+_PUBLIC_ enum TDB_ERROR tdb_error(struct tdb_context *tdb)
+{
+ return tdb->ecode;
+}
+
+static struct tdb_errname {
+ enum TDB_ERROR ecode; const char *estring;
+} emap[] = { {TDB_SUCCESS, "Success"},
+ {TDB_ERR_CORRUPT, "Corrupt database"},
+ {TDB_ERR_IO, "IO Error"},
+ {TDB_ERR_LOCK, "Locking error"},
+ {TDB_ERR_OOM, "Out of memory"},
+ {TDB_ERR_EXISTS, "Record exists"},
+ {TDB_ERR_NOLOCK, "Lock exists on other keys"},
+ {TDB_ERR_EINVAL, "Invalid parameter"},
+ {TDB_ERR_NOEXIST, "Record does not exist"},
+ {TDB_ERR_RDONLY, "write not permitted"} };
+
+/* Error string for the last tdb error */
+_PUBLIC_ const char *tdb_errorstr(struct tdb_context *tdb)
+{
+ uint32_t i;
+ for (i = 0; i < sizeof(emap) / sizeof(struct tdb_errname); i++)
+ if (tdb->ecode == emap[i].ecode)
+ return emap[i].estring;
+ return "Invalid error code";
+}
+
diff --git a/lib/tdb/common/freelist.c b/lib/tdb/common/freelist.c
new file mode 100644
index 0000000..046c747
--- /dev/null
+++ b/lib/tdb/common/freelist.c
@@ -0,0 +1,747 @@
+ /*
+ Unix SMB/CIFS implementation.
+
+ trivial database library
+
+ Copyright (C) Andrew Tridgell 1999-2005
+ Copyright (C) Paul `Rusty' Russell 2000
+ Copyright (C) Jeremy Allison 2000-2003
+
+ ** NOTE! The following LGPL license applies to the tdb
+ ** library. This does NOT imply that all of Samba is released
+ ** under the LGPL
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 3 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "tdb_private.h"
+
+/* read a freelist record and check for simple errors */
+int tdb_rec_free_read(struct tdb_context *tdb, tdb_off_t off, struct tdb_record *rec)
+{
+ if (tdb->methods->tdb_read(tdb, off, rec, sizeof(*rec),DOCONV()) == -1)
+ return -1;
+
+ if (rec->magic == TDB_MAGIC) {
+ /* this happens when a app is showdown while deleting a record - we should
+ not completely fail when this happens */
+ TDB_LOG((tdb, TDB_DEBUG_WARNING, "tdb_rec_free_read non-free magic 0x%x at offset=%u - fixing\n",
+ rec->magic, off));
+ rec->magic = TDB_FREE_MAGIC;
+ if (tdb_rec_write(tdb, off, rec) == -1)
+ return -1;
+ }
+
+ if (rec->magic != TDB_FREE_MAGIC) {
+ /* Ensure ecode is set for log fn. */
+ tdb->ecode = TDB_ERR_CORRUPT;
+ TDB_LOG((tdb, TDB_DEBUG_WARNING, "tdb_rec_free_read bad magic 0x%x at offset=%u\n",
+ rec->magic, off));
+ return -1;
+ }
+ if (tdb_oob(tdb, rec->next, sizeof(*rec), 0) != 0)
+ return -1;
+ return 0;
+}
+
+/* update a record tailer (must hold allocation lock) */
+static int update_tailer(struct tdb_context *tdb, tdb_off_t offset,
+ const struct tdb_record *rec)
+{
+ tdb_off_t totalsize;
+
+ /* Offset of tailer from record header */
+ totalsize = sizeof(*rec) + rec->rec_len;
+ return tdb_ofs_write(tdb, offset + totalsize - sizeof(tdb_off_t),
+ &totalsize);
+}
+
+/**
+ * Read the record directly on the left.
+ * Fail if there is no record on the left.
+ */
+static int read_record_on_left(struct tdb_context *tdb, tdb_off_t rec_ptr,
+ tdb_off_t *left_p,
+ struct tdb_record *left_r)
+{
+ tdb_off_t left_ptr;
+ tdb_off_t left_size;
+ struct tdb_record left_rec;
+ int ret;
+
+ left_ptr = rec_ptr - sizeof(tdb_off_t);
+
+ if (left_ptr <= TDB_DATA_START(tdb->hash_size)) {
+ /* no record on the left */
+ return -1;
+ }
+
+ /* Read in tailer and jump back to header */
+ ret = tdb_ofs_read(tdb, left_ptr, &left_size);
+ if (ret == -1) {
+ TDB_LOG((tdb, TDB_DEBUG_FATAL,
+ "tdb_free: left offset read failed at %u\n", left_ptr));
+ return -1;
+ }
+
+ /* it could be uninitialised data */
+ if (left_size == 0 || left_size == TDB_PAD_U32) {
+ return -1;
+ }
+
+ if (left_size > rec_ptr) {
+ return -1;
+ }
+
+ left_ptr = rec_ptr - left_size;
+
+ if (left_ptr < TDB_DATA_START(tdb->hash_size)) {
+ return -1;
+ }
+
+ /* Now read in the left record */
+ ret = tdb->methods->tdb_read(tdb, left_ptr, &left_rec,
+ sizeof(left_rec), DOCONV());
+ if (ret == -1) {
+ TDB_LOG((tdb, TDB_DEBUG_FATAL,
+ "tdb_free: left read failed at %u (%u)\n",
+ left_ptr, left_size));
+ return -1;
+ }
+
+ *left_p = left_ptr;
+ *left_r = left_rec;
+
+ return 0;
+}
+
+/**
+ * Merge new freelist record with the direct left neighbour.
+ * This assumes that left_rec represents the record
+ * directly to the left of right_rec and that this is
+ * a freelist record.
+ */
+static int merge_with_left_record(struct tdb_context *tdb,
+ tdb_off_t left_ptr,
+ struct tdb_record *left_rec,
+ struct tdb_record *right_rec)
+{
+ int ret;
+
+ left_rec->rec_len += sizeof(*right_rec) + right_rec->rec_len;
+
+ ret = tdb_rec_write(tdb, left_ptr, left_rec);
+ if (ret == -1) {
+ TDB_LOG((tdb, TDB_DEBUG_FATAL,
+ "merge_with_left_record: update_left failed at %u\n",
+ left_ptr));
+ return -1;
+ }
+
+ ret = update_tailer(tdb, left_ptr, left_rec);
+ if (ret == -1) {
+ TDB_LOG((tdb, TDB_DEBUG_FATAL,
+ "merge_with_left_record: update_tailer failed at %u\n",
+ left_ptr));
+ return -1;
+ }
+
+ return 0;
+}
+
+/**
+ * Check whether the record left of a given freelist record is
+ * also a freelist record, and if so, merge the two records.
+ *
+ * Return code:
+ * -1 upon error
+ * 0 if left was not a free record
+ * 1 if left was free and successfully merged.
+ *
+ * The current record is handed in with pointer and fully read record.
+ *
+ * The left record pointer and struct can be retrieved as result
+ * in lp and lr;
+ */
+static int check_merge_with_left_record(struct tdb_context *tdb,
+ tdb_off_t rec_ptr,
+ struct tdb_record *rec,
+ tdb_off_t *lp,
+ struct tdb_record *lr)
+{
+ tdb_off_t left_ptr;
+ struct tdb_record left_rec;
+ int ret;
+
+ ret = read_record_on_left(tdb, rec_ptr, &left_ptr, &left_rec);
+ if (ret != 0) {
+ return 0;
+ }
+
+ if (left_rec.magic != TDB_FREE_MAGIC) {
+ return 0;
+ }
+
+ /* It's free - expand to include it. */
+ ret = merge_with_left_record(tdb, left_ptr, &left_rec, rec);
+ if (ret != 0) {
+ return -1;
+ }
+
+ if (lp != NULL) {
+ *lp = left_ptr;
+ }
+
+ if (lr != NULL) {
+ *lr = left_rec;
+ }
+
+ return 1;
+}
+
+/**
+ * Check whether the record left of a given freelist record is
+ * also a freelist record, and if so, merge the two records.
+ *
+ * Return code:
+ * -1 upon error
+ * 0 if left was not a free record
+ * 1 if left was free and successfully merged.
+ *
+ * In this variant, the input record is specified just as the pointer
+ * and is read from the database if needed.
+ *
+ * next_ptr will contain the original record's next pointer after
+ * successful merging (which will be lost after merging), so that
+ * the caller can update the last pointer.
+ */
+static int check_merge_ptr_with_left_record(struct tdb_context *tdb,
+ tdb_off_t rec_ptr,
+ tdb_off_t *next_ptr)
+{
+ tdb_off_t left_ptr;
+ struct tdb_record rec, left_rec;
+ int ret;
+
+ ret = read_record_on_left(tdb, rec_ptr, &left_ptr, &left_rec);
+ if (ret != 0) {
+ return 0;
+ }
+
+ if (left_rec.magic != TDB_FREE_MAGIC) {
+ return 0;
+ }
+
+ /* It's free - expand to include it. */
+
+ ret = tdb->methods->tdb_read(tdb, rec_ptr, &rec,
+ sizeof(rec), DOCONV());
+ if (ret != 0) {
+ return -1;
+ }
+
+ ret = merge_with_left_record(tdb, left_ptr, &left_rec, &rec);
+ if (ret != 0) {
+ return -1;
+ }
+
+ if (next_ptr != NULL) {
+ *next_ptr = rec.next;
+ }
+
+ return 1;
+}
+
+/**
+ * Add an element into the freelist.
+ *
+ * We merge the new record into the left record if it is also a
+ * free record, but not with the right one. This makes the
+ * operation O(1) instead of O(n): merging with the right record
+ * requires a traverse of the freelist to find the previous
+ * record in the free list.
+ *
+ * This prevents db traverses from being O(n^2) after a lot of deletes.
+ */
+int tdb_free(struct tdb_context *tdb, tdb_off_t offset, struct tdb_record *rec)
+{
+ int ret;
+
+ /* Allocation and tailer lock */
+ if (tdb_lock(tdb, -1, F_WRLCK) != 0)
+ return -1;
+
+ /* set an initial tailer, so if we fail we don't leave a bogus record */
+ if (update_tailer(tdb, offset, rec) != 0) {
+ TDB_LOG((tdb, TDB_DEBUG_FATAL, "tdb_free: update_tailer failed!\n"));
+ goto fail;
+ }
+
+ ret = check_merge_with_left_record(tdb, offset, rec, NULL, NULL);
+ if (ret == -1) {
+ goto fail;
+ }
+ if (ret == 1) {
+ /* merged */
+ goto done;
+ }
+
+ /* Nothing to merge, prepend to free list */
+
+ rec->magic = TDB_FREE_MAGIC;
+
+ if (tdb_ofs_read(tdb, FREELIST_TOP, &rec->next) == -1 ||
+ tdb_rec_write(tdb, offset, rec) == -1 ||
+ tdb_ofs_write(tdb, FREELIST_TOP, &offset) == -1) {
+ TDB_LOG((tdb, TDB_DEBUG_FATAL, "tdb_free record write failed at offset=%u\n", offset));
+ goto fail;
+ }
+
+done:
+ /* And we're done. */
+ tdb_unlock(tdb, -1, F_WRLCK);
+ return 0;
+
+ fail:
+ tdb_unlock(tdb, -1, F_WRLCK);
+ return -1;
+}
+
+
+
+/*
+ the core of tdb_allocate - called when we have decided which
+ free list entry to use
+
+ Note that we try to allocate by grabbing data from the end of an existing record,
+ not the beginning. This is so the left merge in a free is more likely to be
+ able to free up the record without fragmentation
+ */
+static tdb_off_t tdb_allocate_ofs(struct tdb_context *tdb,
+ tdb_len_t length, tdb_off_t rec_ptr,
+ struct tdb_record *rec, tdb_off_t last_ptr)
+{
+#define MIN_REC_SIZE (sizeof(struct tdb_record) + sizeof(tdb_off_t) + 8)
+
+ if (rec->rec_len < length + MIN_REC_SIZE) {
+ /* we have to grab the whole record */
+
+ /* unlink it from the previous record */
+ if (tdb_ofs_write(tdb, last_ptr, &rec->next) == -1) {
+ return 0;
+ }
+
+ /* mark it not free */
+ rec->magic = TDB_MAGIC;
+ if (tdb_rec_write(tdb, rec_ptr, rec) == -1) {
+ return 0;
+ }
+ return rec_ptr;
+ }
+
+ /* we're going to just shorten the existing record */
+ rec->rec_len -= (length + sizeof(*rec));
+ if (tdb_rec_write(tdb, rec_ptr, rec) == -1) {
+ return 0;
+ }
+ if (update_tailer(tdb, rec_ptr, rec) == -1) {
+ return 0;
+ }
+
+ /* and setup the new record */
+ rec_ptr += sizeof(*rec) + rec->rec_len;
+
+ memset(rec, '\0', sizeof(*rec));
+ rec->rec_len = length;
+ rec->magic = TDB_MAGIC;
+
+ if (tdb_rec_write(tdb, rec_ptr, rec) == -1) {
+ return 0;
+ }
+
+ if (update_tailer(tdb, rec_ptr, rec) == -1) {
+ return 0;
+ }
+
+ return rec_ptr;
+}
+
+/* allocate some space from the free list. The offset returned points
+ to a unconnected tdb_record within the database with room for at
+ least length bytes of total data
+
+ 0 is returned if the space could not be allocated
+ */
+static tdb_off_t tdb_allocate_from_freelist(
+ struct tdb_context *tdb, tdb_len_t length, struct tdb_record *rec)
+{
+ tdb_off_t rec_ptr, last_ptr, newrec_ptr;
+ struct tdb_chainwalk_ctx chainwalk;
+ bool modified;
+ struct {
+ tdb_off_t rec_ptr, last_ptr;
+ tdb_len_t rec_len;
+ } bestfit;
+ float multiplier = 1.0;
+ bool merge_created_candidate;
+
+ /* over-allocate to reduce fragmentation */
+ length *= 1.25;
+
+ /* Extra bytes required for tailer */
+ length += sizeof(tdb_off_t);
+ length = TDB_ALIGN(length, TDB_ALIGNMENT);
+
+ again:
+ merge_created_candidate = false;
+ last_ptr = FREELIST_TOP;
+
+ /* read in the freelist top */
+ if (tdb_ofs_read(tdb, FREELIST_TOP, &rec_ptr) == -1)
+ return 0;
+
+ modified = false;
+ tdb_chainwalk_init(&chainwalk, rec_ptr);
+
+ bestfit.rec_ptr = 0;
+ bestfit.last_ptr = 0;
+ bestfit.rec_len = 0;
+
+ /*
+ this is a best fit allocation strategy. Originally we used
+ a first fit strategy, but it suffered from massive fragmentation
+ issues when faced with a slowly increasing record size.
+ */
+ while (rec_ptr) {
+ int ret;
+ tdb_off_t left_ptr;
+ struct tdb_record left_rec;
+
+ if (tdb_rec_free_read(tdb, rec_ptr, rec) == -1) {
+ return 0;
+ }
+
+ ret = check_merge_with_left_record(tdb, rec_ptr, rec,
+ &left_ptr, &left_rec);
+ if (ret == -1) {
+ return 0;
+ }
+ if (ret == 1) {
+ /* merged */
+ rec_ptr = rec->next;
+ ret = tdb_ofs_write(tdb, last_ptr, &rec->next);
+ if (ret == -1) {
+ return 0;
+ }
+
+ /*
+ * We have merged the current record into the left
+ * neighbour. So our traverse of the freelist will
+ * skip it and consider the next record in the chain.
+ *
+ * But the enlarged left neighbour may be a candidate.
+ * If it is, we can not directly use it, though.
+ * The only thing we can do and have to do here is to
+ * update the current best fit size in the chain if the
+ * current best fit is the left record. (By that we may
+ * worsen the best fit we already had, bit this is not a
+ * problem.)
+ *
+ * If the current best fit is not the left record,
+ * all we can do is remember the fact that a merge
+ * created a new candidate so that we can trigger
+ * a second walk of the freelist if at the end of
+ * the first walk we have not found any fit.
+ * This way we can avoid expanding the database.
+ */
+
+ if (bestfit.rec_ptr == left_ptr) {
+ bestfit.rec_len = left_rec.rec_len;
+ }
+
+ if (left_rec.rec_len > length) {
+ merge_created_candidate = true;
+ }
+
+ modified = true;
+
+ continue;
+ }
+
+ if (rec->rec_len >= length) {
+ if (bestfit.rec_ptr == 0 ||
+ rec->rec_len < bestfit.rec_len) {
+ bestfit.rec_len = rec->rec_len;
+ bestfit.rec_ptr = rec_ptr;
+ bestfit.last_ptr = last_ptr;
+ }
+ }
+
+ /* move to the next record */
+ last_ptr = rec_ptr;
+ rec_ptr = rec->next;
+
+ if (!modified) {
+ bool ok;
+ ok = tdb_chainwalk_check(tdb, &chainwalk, rec_ptr);
+ if (!ok) {
+ return 0;
+ }
+ }
+
+ /* if we've found a record that is big enough, then
+ stop searching if its also not too big. The
+ definition of 'too big' changes as we scan
+ through */
+ if (bestfit.rec_len > 0 &&
+ bestfit.rec_len < length * multiplier) {
+ break;
+ }
+
+ /* this multiplier means we only extremely rarely
+ search more than 50 or so records. At 50 records we
+ accept records up to 11 times larger than what we
+ want */
+ multiplier *= 1.05;
+ }
+
+ if (bestfit.rec_ptr != 0) {
+ if (tdb_rec_free_read(tdb, bestfit.rec_ptr, rec) == -1) {
+ return 0;
+ }
+
+ newrec_ptr = tdb_allocate_ofs(tdb, length, bestfit.rec_ptr,
+ rec, bestfit.last_ptr);
+ return newrec_ptr;
+ }
+
+ if (merge_created_candidate) {
+ goto again;
+ }
+
+ /* we didn't find enough space. See if we can expand the
+ database and if we can then try again */
+ if (tdb_expand(tdb, length + sizeof(*rec)) == 0)
+ goto again;
+
+ return 0;
+}
+
+static bool tdb_alloc_dead(
+ struct tdb_context *tdb, int hash, tdb_len_t length,
+ tdb_off_t *rec_ptr, struct tdb_record *rec)
+{
+ tdb_off_t last_ptr;
+
+ *rec_ptr = tdb_find_dead(tdb, hash, rec, length, &last_ptr);
+ if (*rec_ptr == 0) {
+ return false;
+ }
+ /*
+ * Unlink the record from the hash chain, it's about to be moved into
+ * another one.
+ */
+ return (tdb_ofs_write(tdb, last_ptr, &rec->next) == 0);
+}
+
+static void tdb_purge_dead(struct tdb_context *tdb, uint32_t hash)
+{
+ int max_dead_records = tdb->max_dead_records;
+
+ tdb->max_dead_records = 0;
+
+ tdb_trim_dead(tdb, hash);
+
+ tdb->max_dead_records = max_dead_records;
+}
+
+/*
+ * Chain "hash" is assumed to be locked
+ */
+
+tdb_off_t tdb_allocate(struct tdb_context *tdb, int hash, tdb_len_t length,
+ struct tdb_record *rec)
+{
+ tdb_off_t ret;
+ uint32_t i;
+
+ if (tdb->max_dead_records == 0) {
+ /*
+ * No dead records to expect anywhere. Do the blocking
+ * freelist lock without trying to steal from others
+ */
+ goto blocking_freelist_allocate;
+ }
+
+ /*
+ * The following loop tries to get the freelist lock nonblocking. If
+ * it gets the lock, allocate from there. If the freelist is busy,
+ * instead of waiting we try to steal dead records from other hash
+ * chains.
+ *
+ * Be aware that we do nonblocking locks on the other hash chains as
+ * well and fail gracefully. This way we avoid deadlocks (we block two
+ * hash chains, something which is pretty bad normally)
+ */
+
+ for (i=0; i<tdb->hash_size; i++) {
+
+ int list;
+
+ list = BUCKET(hash+i);
+
+ if (tdb_lock_nonblock(tdb, list, F_WRLCK) == 0) {
+ bool got_dead;
+
+ got_dead = tdb_alloc_dead(tdb, list, length, &ret, rec);
+ tdb_unlock(tdb, list, F_WRLCK);
+
+ if (got_dead) {
+ return ret;
+ }
+ }
+
+ if (tdb_lock_nonblock(tdb, -1, F_WRLCK) == 0) {
+ /*
+ * Under the freelist lock take the chance to give
+ * back our dead records.
+ */
+ tdb_purge_dead(tdb, hash);
+
+ ret = tdb_allocate_from_freelist(tdb, length, rec);
+ tdb_unlock(tdb, -1, F_WRLCK);
+ return ret;
+ }
+ }
+
+blocking_freelist_allocate:
+
+ if (tdb_lock(tdb, -1, F_WRLCK) == -1) {
+ return 0;
+ }
+ /*
+ * Dead records can happen even if max_dead_records==0, they
+ * are older than the max_dead_records concept: They happen if
+ * tdb_delete happens concurrently with a traverse.
+ */
+ tdb_purge_dead(tdb, hash);
+ ret = tdb_allocate_from_freelist(tdb, length, rec);
+ tdb_unlock(tdb, -1, F_WRLCK);
+ return ret;
+}
+
+/**
+ * Merge adjacent records in the freelist.
+ */
+static int tdb_freelist_merge_adjacent(struct tdb_context *tdb,
+ int *count_records, int *count_merged)
+{
+ tdb_off_t cur, next;
+ int count = 0;
+ int merged = 0;
+ int ret;
+
+ ret = tdb_lock(tdb, -1, F_RDLCK);
+ if (ret == -1) {
+ return -1;
+ }
+
+ cur = FREELIST_TOP;
+ while (tdb_ofs_read(tdb, cur, &next) == 0 && next != 0) {
+ tdb_off_t next2;
+
+ count++;
+
+ ret = check_merge_ptr_with_left_record(tdb, next, &next2);
+ if (ret == -1) {
+ goto done;
+ }
+ if (ret == 1) {
+ /*
+ * merged:
+ * now let cur->next point to next2 instead of next
+ */
+
+ ret = tdb_ofs_write(tdb, cur, &next2);
+ if (ret != 0) {
+ goto done;
+ }
+
+ next = next2;
+ merged++;
+ }
+
+ cur = next;
+ }
+
+ if (count_records != NULL) {
+ *count_records = count;
+ }
+
+ if (count_merged != NULL) {
+ *count_merged = merged;
+ }
+
+ ret = 0;
+
+done:
+ tdb_unlock(tdb, -1, F_RDLCK);
+ return ret;
+}
+
+/**
+ * return the size of the freelist - no merging done
+ */
+static int tdb_freelist_size_no_merge(struct tdb_context *tdb)
+{
+ tdb_off_t ptr;
+ int count=0;
+
+ if (tdb_lock(tdb, -1, F_RDLCK) == -1) {
+ return -1;
+ }
+
+ ptr = FREELIST_TOP;
+ while (tdb_ofs_read(tdb, ptr, &ptr) == 0 && ptr != 0) {
+ count++;
+ }
+
+ tdb_unlock(tdb, -1, F_RDLCK);
+ return count;
+}
+
+/**
+ * return the size of the freelist - used to decide if we should repack
+ *
+ * As a side effect, adjacent records are merged unless the
+ * database is read-only, in order to reduce the fragmentation
+ * without repacking.
+ */
+_PUBLIC_ int tdb_freelist_size(struct tdb_context *tdb)
+{
+
+ int count = 0;
+
+ if (tdb->read_only) {
+ count = tdb_freelist_size_no_merge(tdb);
+ } else {
+ int ret;
+ ret = tdb_freelist_merge_adjacent(tdb, &count, NULL);
+ if (ret != 0) {
+ return -1;
+ }
+ }
+
+ return count;
+}
diff --git a/lib/tdb/common/freelistcheck.c b/lib/tdb/common/freelistcheck.c
new file mode 100644
index 0000000..2f1e6eb
--- /dev/null
+++ b/lib/tdb/common/freelistcheck.c
@@ -0,0 +1,107 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ trivial database library
+
+ Copyright (C) Jeremy Allison 2006
+
+ ** NOTE! The following LGPL license applies to the tdb
+ ** library. This does NOT imply that all of Samba is released
+ ** under the LGPL
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 3 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "tdb_private.h"
+
+/* Check the freelist is good and contains no loops.
+ Very memory intensive - only do this as a consistency
+ checker. Heh heh - uses an in memory tdb as the storage
+ for the "seen" record list. For some reason this strikes
+ me as extremely clever as I don't have to write another tree
+ data structure implementation :-).
+ */
+
+static int seen_insert(struct tdb_context *mem_tdb, tdb_off_t rec_ptr)
+{
+ TDB_DATA key;
+
+ key.dptr = (unsigned char *)&rec_ptr;
+ key.dsize = sizeof(rec_ptr);
+ return tdb_store(mem_tdb, key, tdb_null, TDB_INSERT);
+}
+
+_PUBLIC_ int tdb_validate_freelist(struct tdb_context *tdb, int *pnum_entries)
+{
+ struct tdb_context *mem_tdb = NULL;
+ struct tdb_record rec;
+ tdb_off_t rec_ptr, last_ptr;
+ int ret = -1;
+
+ *pnum_entries = 0;
+
+ mem_tdb = tdb_open("flval", tdb->hash_size,
+ TDB_INTERNAL, O_RDWR, 0600);
+ if (!mem_tdb) {
+ return -1;
+ }
+
+ if (tdb_lock(tdb, -1, F_WRLCK) == -1) {
+ tdb_close(mem_tdb);
+ return 0;
+ }
+
+ last_ptr = FREELIST_TOP;
+
+ /* Store the FREELIST_TOP record. */
+ if (seen_insert(mem_tdb, last_ptr) == -1) {
+ tdb->ecode = TDB_ERR_CORRUPT;
+ ret = -1;
+ goto fail;
+ }
+
+ /* read in the freelist top */
+ if (tdb_ofs_read(tdb, FREELIST_TOP, &rec_ptr) == -1) {
+ goto fail;
+ }
+
+ while (rec_ptr) {
+
+ /* If we can't store this record (we've seen it
+ before) then the free list has a loop and must
+ be corrupt. */
+
+ if (seen_insert(mem_tdb, rec_ptr)) {
+ tdb->ecode = TDB_ERR_CORRUPT;
+ ret = -1;
+ goto fail;
+ }
+
+ if (tdb_rec_free_read(tdb, rec_ptr, &rec) == -1) {
+ goto fail;
+ }
+
+ /* move to the next record */
+ rec_ptr = rec.next;
+ *pnum_entries += 1;
+ }
+
+ ret = 0;
+
+ fail:
+
+ tdb_close(mem_tdb);
+ tdb_unlock(tdb, -1, F_WRLCK);
+ return ret;
+}
diff --git a/lib/tdb/common/hash.c b/lib/tdb/common/hash.c
new file mode 100644
index 0000000..4de7ba9
--- /dev/null
+++ b/lib/tdb/common/hash.c
@@ -0,0 +1,345 @@
+ /*
+ Unix SMB/CIFS implementation.
+
+ trivial database library
+
+ Copyright (C) Rusty Russell 2010
+
+ ** NOTE! The following LGPL license applies to the tdb
+ ** library. This does NOT imply that all of Samba is released
+ ** under the LGPL
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 3 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, see <http://www.gnu.org/licenses/>.
+*/
+#include "tdb_private.h"
+
+/* This is based on the hash algorithm from gdbm */
+unsigned int tdb_old_hash(TDB_DATA *key)
+{
+ uint32_t value; /* Used to compute the hash value. */
+ uint32_t i; /* Used to cycle through random values. */
+
+ /* Set the initial value from the key size. */
+ for (value = 0x238F13AF * key->dsize, i=0; i < key->dsize; i++)
+ value = (value + (key->dptr[i] << (i*5 % 24)));
+
+ return (1103515243 * value + 12345);
+}
+
+#ifndef WORDS_BIGENDIAN
+# define HASH_LITTLE_ENDIAN 1
+# define HASH_BIG_ENDIAN 0
+#else
+# define HASH_LITTLE_ENDIAN 0
+# define HASH_BIG_ENDIAN 1
+#endif
+
+/*
+-------------------------------------------------------------------------------
+lookup3.c, by Bob Jenkins, May 2006, Public Domain.
+
+These are functions for producing 32-bit hashes for hash table lookup.
+hash_word(), hashlittle(), hashlittle2(), hashbig(), mix(), and final()
+are externally useful functions. Routines to test the hash are included
+if SELF_TEST is defined. You can use this free for any purpose. It's in
+the public domain. It has no warranty.
+
+You probably want to use hashlittle(). hashlittle() and hashbig()
+hash byte arrays. hashlittle() is is faster than hashbig() on
+little-endian machines. Intel and AMD are little-endian machines.
+On second thought, you probably want hashlittle2(), which is identical to
+hashlittle() except it returns two 32-bit hashes for the price of one.
+You could implement hashbig2() if you wanted but I haven't bothered here.
+
+If you want to find a hash of, say, exactly 7 integers, do
+ a = i1; b = i2; c = i3;
+ mix(a,b,c);
+ a += i4; b += i5; c += i6;
+ mix(a,b,c);
+ a += i7;
+ final(a,b,c);
+then use c as the hash value. If you have a variable length array of
+4-byte integers to hash, use hash_word(). If you have a byte array (like
+a character string), use hashlittle(). If you have several byte arrays, or
+a mix of things, see the comments above hashlittle().
+
+Why is this so big? I read 12 bytes at a time into 3 4-byte integers,
+then mix those integers. This is fast (you can do a lot more thorough
+mixing with 12*3 instructions on 3 integers than you can with 3 instructions
+on 1 byte), but shoehorning those bytes into integers efficiently is messy.
+*/
+
+#define hashsize(n) ((uint32_t)1<<(n))
+#define hashmask(n) (hashsize(n)-1)
+#define rot(x,k) (((x)<<(k)) | ((x)>>(32-(k))))
+
+/*
+-------------------------------------------------------------------------------
+mix -- mix 3 32-bit values reversibly.
+
+This is reversible, so any information in (a,b,c) before mix() is
+still in (a,b,c) after mix().
+
+If four pairs of (a,b,c) inputs are run through mix(), or through
+mix() in reverse, there are at least 32 bits of the output that
+are sometimes the same for one pair and different for another pair.
+This was tested for:
+* pairs that differed by one bit, by two bits, in any combination
+ of top bits of (a,b,c), or in any combination of bottom bits of
+ (a,b,c).
+* "differ" is defined as +, -, ^, or ~^. For + and -, I transformed
+ the output delta to a Gray code (a^(a>>1)) so a string of 1's (as
+ is commonly produced by subtraction) look like a single 1-bit
+ difference.
+* the base values were pseudorandom, all zero but one bit set, or
+ all zero plus a counter that starts at zero.
+
+Some k values for my "a-=c; a^=rot(c,k); c+=b;" arrangement that
+satisfy this are
+ 4 6 8 16 19 4
+ 9 15 3 18 27 15
+ 14 9 3 7 17 3
+Well, "9 15 3 18 27 15" didn't quite get 32 bits diffing
+for "differ" defined as + with a one-bit base and a two-bit delta. I
+used http://burtleburtle.net/bob/hash/avalanche.html to choose
+the operations, constants, and arrangements of the variables.
+
+This does not achieve avalanche. There are input bits of (a,b,c)
+that fail to affect some output bits of (a,b,c), especially of a. The
+most thoroughly mixed value is c, but it doesn't really even achieve
+avalanche in c.
+
+This allows some parallelism. Read-after-writes are good at doubling
+the number of bits affected, so the goal of mixing pulls in the opposite
+direction as the goal of parallelism. I did what I could. Rotates
+seem to cost as much as shifts on every machine I could lay my hands
+on, and rotates are much kinder to the top and bottom bits, so I used
+rotates.
+-------------------------------------------------------------------------------
+*/
+#define mix(a,b,c) \
+{ \
+ a -= c; a ^= rot(c, 4); c += b; \
+ b -= a; b ^= rot(a, 6); a += c; \
+ c -= b; c ^= rot(b, 8); b += a; \
+ a -= c; a ^= rot(c,16); c += b; \
+ b -= a; b ^= rot(a,19); a += c; \
+ c -= b; c ^= rot(b, 4); b += a; \
+}
+
+/*
+-------------------------------------------------------------------------------
+final -- final mixing of 3 32-bit values (a,b,c) into c
+
+Pairs of (a,b,c) values differing in only a few bits will usually
+produce values of c that look totally different. This was tested for
+* pairs that differed by one bit, by two bits, in any combination
+ of top bits of (a,b,c), or in any combination of bottom bits of
+ (a,b,c).
+* "differ" is defined as +, -, ^, or ~^. For + and -, I transformed
+ the output delta to a Gray code (a^(a>>1)) so a string of 1's (as
+ is commonly produced by subtraction) look like a single 1-bit
+ difference.
+* the base values were pseudorandom, all zero but one bit set, or
+ all zero plus a counter that starts at zero.
+
+These constants passed:
+ 14 11 25 16 4 14 24
+ 12 14 25 16 4 14 24
+and these came close:
+ 4 8 15 26 3 22 24
+ 10 8 15 26 3 22 24
+ 11 8 15 26 3 22 24
+-------------------------------------------------------------------------------
+*/
+#define final(a,b,c) \
+{ \
+ c ^= b; c -= rot(b,14); \
+ a ^= c; a -= rot(c,11); \
+ b ^= a; b -= rot(a,25); \
+ c ^= b; c -= rot(b,16); \
+ a ^= c; a -= rot(c,4); \
+ b ^= a; b -= rot(a,14); \
+ c ^= b; c -= rot(b,24); \
+}
+
+
+/*
+-------------------------------------------------------------------------------
+hashlittle() -- hash a variable-length key into a 32-bit value
+ k : the key (the unaligned variable-length array of bytes)
+ length : the length of the key, counting by bytes
+ val2 : IN: can be any 4-byte value OUT: second 32 bit hash.
+Returns a 32-bit value. Every bit of the key affects every bit of
+the return value. Two keys differing by one or two bits will have
+totally different hash values. Note that the return value is better
+mixed than val2, so use that first.
+
+The best hash table sizes are powers of 2. There is no need to do
+mod a prime (mod is sooo slow!). If you need less than 32 bits,
+use a bitmask. For example, if you need only 10 bits, do
+ h = (h & hashmask(10));
+In which case, the hash table should have hashsize(10) elements.
+
+If you are hashing n strings (uint8_t **)k, do it like this:
+ for (i=0, h=0; i<n; ++i) h = hashlittle( k[i], len[i], h);
+
+By Bob Jenkins, 2006. bob_jenkins@burtleburtle.net. You may use this
+code any way you wish, private, educational, or commercial. It's free.
+
+Use for hash table lookup, or anything where one collision in 2^^32 is
+acceptable. Do NOT use for cryptographic purposes.
+-------------------------------------------------------------------------------
+*/
+
+static uint32_t hashlittle( const void *key, size_t length )
+{
+ uint32_t a,b,c; /* internal state */
+ union { const void *ptr; size_t i; } u; /* needed for Mac Powerbook G4 */
+
+ /* Set up the internal state */
+ a = b = c = 0xdeadbeef + ((uint32_t)length);
+
+ u.ptr = key;
+ if (HASH_LITTLE_ENDIAN && ((u.i & 0x3) == 0)) {
+ const uint32_t *k = (const uint32_t *)key; /* read 32-bit chunks */
+ const uint8_t *k8;
+
+ /*------ all but last block: aligned reads and affect 32 bits of (a,b,c) */
+ while (length > 12)
+ {
+ a += k[0];
+ b += k[1];
+ c += k[2];
+ mix(a,b,c);
+ length -= 12;
+ k += 3;
+ }
+
+ /*----------------------------- handle the last (probably partial) block */
+ k8 = (const uint8_t *)k;
+ switch(length)
+ {
+ case 12: c+=k[2]; b+=k[1]; a+=k[0]; break;
+ case 11: c+=((uint32_t)k8[10])<<16; FALL_THROUGH;
+ case 10: c+=((uint32_t)k8[9])<<8; FALL_THROUGH;
+ case 9 : c+=k8[8]; FALL_THROUGH;
+ case 8 : b+=k[1]; a+=k[0]; break;
+ case 7 : b+=((uint32_t)k8[6])<<16; FALL_THROUGH;
+ case 6 : b+=((uint32_t)k8[5])<<8; FALL_THROUGH;
+ case 5 : b+=k8[4]; FALL_THROUGH;
+ case 4 : a+=k[0]; break;
+ case 3 : a+=((uint32_t)k8[2])<<16; FALL_THROUGH;
+ case 2 : a+=((uint32_t)k8[1])<<8; FALL_THROUGH;
+ case 1 : a+=k8[0]; break;
+ case 0 : return c;
+ }
+ } else if (HASH_LITTLE_ENDIAN && ((u.i & 0x1) == 0)) {
+ const uint16_t *k = (const uint16_t *)key; /* read 16-bit chunks */
+ const uint8_t *k8;
+
+ /*--------------- all but last block: aligned reads and different mixing */
+ while (length > 12)
+ {
+ a += k[0] + (((uint32_t)k[1])<<16);
+ b += k[2] + (((uint32_t)k[3])<<16);
+ c += k[4] + (((uint32_t)k[5])<<16);
+ mix(a,b,c);
+ length -= 12;
+ k += 6;
+ }
+
+ /*----------------------------- handle the last (probably partial) block */
+ k8 = (const uint8_t *)k;
+ switch(length)
+ {
+ case 12: c+=k[4]+(((uint32_t)k[5])<<16);
+ b+=k[2]+(((uint32_t)k[3])<<16);
+ a+=k[0]+(((uint32_t)k[1])<<16);
+ break;
+ case 11: c+=((uint32_t)k8[10])<<16; FALL_THROUGH;
+ case 10: c+=k[4];
+ b+=k[2]+(((uint32_t)k[3])<<16);
+ a+=k[0]+(((uint32_t)k[1])<<16);
+ break;
+ case 9 : c+=k8[8]; FALL_THROUGH;
+ case 8 : b+=k[2]+(((uint32_t)k[3])<<16);
+ a+=k[0]+(((uint32_t)k[1])<<16);
+ break;
+ case 7 : b+=((uint32_t)k8[6])<<16; FALL_THROUGH;
+ case 6 : b+=k[2];
+ a+=k[0]+(((uint32_t)k[1])<<16);
+ break;
+ case 5 : b+=k8[4]; FALL_THROUGH;
+ case 4 : a+=k[0]+(((uint32_t)k[1])<<16);
+ break;
+ case 3 : a+=((uint32_t)k8[2])<<16; FALL_THROUGH;
+ case 2 : a+=k[0];
+ break;
+ case 1 : a+=k8[0];
+ break;
+ case 0 : return c; /* zero length requires no mixing */
+ }
+
+ } else { /* need to read the key one byte at a time */
+ const uint8_t *k = (const uint8_t *)key;
+
+ /*--------------- all but the last block: affect some 32 bits of (a,b,c) */
+ while (length > 12)
+ {
+ a += k[0];
+ a += ((uint32_t)k[1])<<8;
+ a += ((uint32_t)k[2])<<16;
+ a += ((uint32_t)k[3])<<24;
+ b += k[4];
+ b += ((uint32_t)k[5])<<8;
+ b += ((uint32_t)k[6])<<16;
+ b += ((uint32_t)k[7])<<24;
+ c += k[8];
+ c += ((uint32_t)k[9])<<8;
+ c += ((uint32_t)k[10])<<16;
+ c += ((uint32_t)k[11])<<24;
+ mix(a,b,c);
+ length -= 12;
+ k += 12;
+ }
+
+ /*-------------------------------- last block: affect all 32 bits of (c) */
+ switch(length)
+ {
+ case 12: c+=((uint32_t)k[11])<<24; FALL_THROUGH;
+ case 11: c+=((uint32_t)k[10])<<16; FALL_THROUGH;
+ case 10: c+=((uint32_t)k[9])<<8; FALL_THROUGH;
+ case 9 : c+=k[8]; FALL_THROUGH;
+ case 8 : b+=((uint32_t)k[7])<<24; FALL_THROUGH;
+ case 7 : b+=((uint32_t)k[6])<<16; FALL_THROUGH;
+ case 6 : b+=((uint32_t)k[5])<<8; FALL_THROUGH;
+ case 5 : b+=k[4]; FALL_THROUGH;
+ case 4 : a+=((uint32_t)k[3])<<24; FALL_THROUGH;
+ case 3 : a+=((uint32_t)k[2])<<16; FALL_THROUGH;
+ case 2 : a+=((uint32_t)k[1])<<8; FALL_THROUGH;
+ case 1 : a+=k[0];
+ break;
+ case 0 : return c;
+ }
+ }
+
+ final(a,b,c);
+ return c;
+}
+
+_PUBLIC_ unsigned int tdb_jenkins_hash(TDB_DATA *key)
+{
+ return hashlittle(key->dptr, key->dsize);
+}
diff --git a/lib/tdb/common/io.c b/lib/tdb/common/io.c
new file mode 100644
index 0000000..0de0dab
--- /dev/null
+++ b/lib/tdb/common/io.c
@@ -0,0 +1,806 @@
+ /*
+ Unix SMB/CIFS implementation.
+
+ trivial database library
+
+ Copyright (C) Andrew Tridgell 1999-2005
+ Copyright (C) Paul `Rusty' Russell 2000
+ Copyright (C) Jeremy Allison 2000-2003
+
+ ** NOTE! The following LGPL license applies to the tdb
+ ** library. This does NOT imply that all of Samba is released
+ ** under the LGPL
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 3 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, see <http://www.gnu.org/licenses/>.
+*/
+
+
+#include "tdb_private.h"
+
+/*
+ * We prepend the mutex area, so fixup offsets. See mutex.c for details.
+ * tdb->hdr_ofs is 0 or header.mutex_size.
+ *
+ * Note: that we only have the 4GB limit of tdb_off_t for
+ * tdb->map_size. The file size on disk can be 4GB + tdb->hdr_ofs!
+ */
+
+static bool tdb_adjust_offset(struct tdb_context *tdb, off_t *off)
+{
+ off_t tmp = tdb->hdr_ofs + *off;
+
+ if ((tmp < tdb->hdr_ofs) || (tmp < *off)) {
+ errno = EIO;
+ return false;
+ }
+
+ *off = tmp;
+ return true;
+}
+
+static ssize_t tdb_pwrite(struct tdb_context *tdb, const void *buf,
+ size_t count, off_t offset)
+{
+ ssize_t ret;
+
+ if (!tdb_adjust_offset(tdb, &offset)) {
+ return -1;
+ }
+
+ do {
+ ret = pwrite(tdb->fd, buf, count, offset);
+ } while ((ret == -1) && (errno == EINTR));
+
+ return ret;
+}
+
+static ssize_t tdb_pread(struct tdb_context *tdb, void *buf,
+ size_t count, off_t offset)
+{
+ ssize_t ret;
+
+ if (!tdb_adjust_offset(tdb, &offset)) {
+ return -1;
+ }
+
+ do {
+ ret = pread(tdb->fd, buf, count, offset);
+ } while ((ret == -1) && (errno == EINTR));
+
+ return ret;
+}
+
+static int tdb_ftruncate(struct tdb_context *tdb, off_t length)
+{
+ ssize_t ret;
+
+ if (!tdb_adjust_offset(tdb, &length)) {
+ return -1;
+ }
+
+ do {
+ ret = ftruncate(tdb->fd, length);
+ } while ((ret == -1) && (errno == EINTR));
+
+ return ret;
+}
+
+#ifdef HAVE_POSIX_FALLOCATE
+static int tdb_posix_fallocate(struct tdb_context *tdb, off_t offset,
+ off_t len)
+{
+ ssize_t ret;
+
+ if (!tdb_adjust_offset(tdb, &offset)) {
+ return -1;
+ }
+
+ do {
+ ret = posix_fallocate(tdb->fd, offset, len);
+ } while ((ret == -1) && (errno == EINTR));
+
+ return ret;
+}
+#endif
+
+static int tdb_fstat(struct tdb_context *tdb, struct stat *buf)
+{
+ int ret;
+
+ ret = fstat(tdb->fd, buf);
+ if (ret == -1) {
+ return -1;
+ }
+
+ if (buf->st_size < tdb->hdr_ofs) {
+ errno = EIO;
+ return -1;
+ }
+ buf->st_size -= tdb->hdr_ofs;
+
+ return ret;
+}
+
+/* check for an out of bounds access - if it is out of bounds then
+ see if the database has been expanded by someone else and expand
+ if necessary
+*/
+static int tdb_notrans_oob(
+ struct tdb_context *tdb, tdb_off_t off, tdb_len_t len, int probe)
+{
+ struct stat st;
+ if (len + off < len) {
+ if (!probe) {
+ /* Ensure ecode is set for log fn. */
+ tdb->ecode = TDB_ERR_IO;
+ TDB_LOG((tdb, TDB_DEBUG_FATAL,"tdb_oob off %u len %u wrap\n",
+ off, len));
+ }
+ return -1;
+ }
+
+ /*
+ * This duplicates functionality from tdb_oob(). Don't remove:
+ * we still have direct callers of tdb->methods->tdb_oob()
+ * inside transaction.c.
+ */
+ if (off + len <= tdb->map_size)
+ return 0;
+ if (tdb->flags & TDB_INTERNAL) {
+ if (!probe) {
+ /* Ensure ecode is set for log fn. */
+ tdb->ecode = TDB_ERR_IO;
+ TDB_LOG((tdb, TDB_DEBUG_FATAL,"tdb_oob len %u beyond internal malloc size %u\n",
+ (int)(off + len), (int)tdb->map_size));
+ }
+ return -1;
+ }
+
+ if (tdb_fstat(tdb, &st) == -1) {
+ tdb->ecode = TDB_ERR_IO;
+ return -1;
+ }
+
+ /* Beware >4G files! */
+ if ((tdb_off_t)st.st_size != st.st_size) {
+ /* Ensure ecode is set for log fn. */
+ tdb->ecode = TDB_ERR_IO;
+ TDB_LOG((tdb, TDB_DEBUG_FATAL, "tdb_oob len %llu too large!\n",
+ (long long)st.st_size));
+ return -1;
+ }
+
+ /* Unmap, update size, remap. We do this unconditionally, to handle
+ * the unusual case where the db is truncated.
+ *
+ * This can happen to a child using tdb_reopen_all(true) on a
+ * TDB_CLEAR_IF_FIRST tdb whose parent crashes: the next
+ * opener will truncate the database. */
+ if (tdb_munmap(tdb) == -1) {
+ tdb->ecode = TDB_ERR_IO;
+ return -1;
+ }
+ tdb->map_size = st.st_size;
+ if (tdb_mmap(tdb) != 0) {
+ return -1;
+ }
+
+ if (st.st_size < (size_t)off + len) {
+ if (!probe) {
+ /* Ensure ecode is set for log fn. */
+ tdb->ecode = TDB_ERR_IO;
+ TDB_LOG((tdb, TDB_DEBUG_FATAL,"tdb_oob len %u beyond eof at %u\n",
+ (int)(off + len), (int)st.st_size));
+ }
+ return -1;
+ }
+ return 0;
+}
+
+/* write a lump of data at a specified offset */
+static int tdb_write(struct tdb_context *tdb, tdb_off_t off,
+ const void *buf, tdb_len_t len)
+{
+ if (len == 0) {
+ return 0;
+ }
+
+ if (tdb->read_only || tdb->traverse_read) {
+ tdb->ecode = TDB_ERR_RDONLY;
+ return -1;
+ }
+
+ if (tdb_oob(tdb, off, len, 0) != 0)
+ return -1;
+
+ if (tdb->map_ptr) {
+ memcpy(off + (char *)tdb->map_ptr, buf, len);
+ } else {
+#ifdef HAVE_INCOHERENT_MMAP
+ tdb->ecode = TDB_ERR_IO;
+ return -1;
+#else
+ ssize_t written;
+
+ written = tdb_pwrite(tdb, buf, len, off);
+
+ if ((written != (ssize_t)len) && (written != -1)) {
+ /* try once more */
+ tdb->ecode = TDB_ERR_IO;
+ TDB_LOG((tdb, TDB_DEBUG_FATAL, "tdb_write: wrote only "
+ "%zi of %u bytes at %u, trying once more\n",
+ written, len, off));
+ written = tdb_pwrite(tdb, (const char *)buf+written,
+ len-written, off+written);
+ }
+ if (written == -1) {
+ /* Ensure ecode is set for log fn. */
+ tdb->ecode = TDB_ERR_IO;
+ TDB_LOG((tdb, TDB_DEBUG_FATAL,"tdb_write failed at %u "
+ "len=%u (%s)\n", off, len, strerror(errno)));
+ return -1;
+ } else if (written != (ssize_t)len) {
+ tdb->ecode = TDB_ERR_IO;
+ TDB_LOG((tdb, TDB_DEBUG_FATAL, "tdb_write: failed to "
+ "write %u bytes at %u in two attempts\n",
+ len, off));
+ return -1;
+ }
+#endif
+ }
+ return 0;
+}
+
+/* Endian conversion: we only ever deal with 4 byte quantities */
+void *tdb_convert(void *buf, uint32_t size)
+{
+ uint32_t i, *p = (uint32_t *)buf;
+ for (i = 0; i < size / 4; i++)
+ p[i] = TDB_BYTEREV(p[i]);
+ return buf;
+}
+
+
+/* read a lump of data at a specified offset, maybe convert */
+static int tdb_read(struct tdb_context *tdb, tdb_off_t off, void *buf,
+ tdb_len_t len, int cv)
+{
+ if (tdb_oob(tdb, off, len, 0) != 0) {
+ return -1;
+ }
+
+ if (tdb->map_ptr) {
+ memcpy(buf, off + (char *)tdb->map_ptr, len);
+ } else {
+#ifdef HAVE_INCOHERENT_MMAP
+ tdb->ecode = TDB_ERR_IO;
+ return -1;
+#else
+ ssize_t ret;
+
+ ret = tdb_pread(tdb, buf, len, off);
+ if (ret != (ssize_t)len) {
+ /* Ensure ecode is set for log fn. */
+ tdb->ecode = TDB_ERR_IO;
+ TDB_LOG((tdb, TDB_DEBUG_FATAL,"tdb_read failed at %u "
+ "len=%u ret=%zi (%s) map_size=%u\n",
+ off, len, ret, strerror(errno),
+ tdb->map_size));
+ return -1;
+ }
+#endif
+ }
+ if (cv) {
+ tdb_convert(buf, len);
+ }
+ return 0;
+}
+
+
+
+/*
+ do an unlocked scan of the hash table heads to find the next non-zero head. The value
+ will then be confirmed with the lock held
+*/
+static void tdb_next_hash_chain(struct tdb_context *tdb, uint32_t *chain)
+{
+ uint32_t h = *chain;
+ if (tdb->map_ptr) {
+ for (;h < tdb->hash_size;h++) {
+ if (0 != *(uint32_t *)(TDB_HASH_TOP(h) + (unsigned char *)tdb->map_ptr)) {
+ break;
+ }
+ }
+ } else {
+ uint32_t off=0;
+ for (;h < tdb->hash_size;h++) {
+ if (tdb_ofs_read(tdb, TDB_HASH_TOP(h), &off) != 0 || off != 0) {
+ break;
+ }
+ }
+ }
+ (*chain) = h;
+}
+
+
+int tdb_munmap(struct tdb_context *tdb)
+{
+ if (tdb->flags & TDB_INTERNAL)
+ return 0;
+
+#ifdef HAVE_MMAP
+ if (tdb->map_ptr) {
+ int ret;
+
+ ret = munmap(tdb->map_ptr, tdb->map_size);
+ if (ret != 0)
+ return ret;
+ }
+#endif
+ tdb->map_ptr = NULL;
+ return 0;
+}
+
+/* If mmap isn't coherent, *everyone* must always mmap. */
+static bool should_mmap(const struct tdb_context *tdb)
+{
+#ifdef HAVE_INCOHERENT_MMAP
+ return true;
+#else
+ return !(tdb->flags & TDB_NOMMAP);
+#endif
+}
+
+int tdb_mmap(struct tdb_context *tdb)
+{
+ if (tdb->flags & TDB_INTERNAL)
+ return 0;
+
+#ifdef HAVE_MMAP
+ if (should_mmap(tdb)) {
+ tdb->map_ptr = mmap(NULL, tdb->map_size,
+ PROT_READ|(tdb->read_only? 0:PROT_WRITE),
+ MAP_SHARED|MAP_FILE, tdb->fd,
+ tdb->hdr_ofs);
+
+ /*
+ * NB. When mmap fails it returns MAP_FAILED *NOT* NULL !!!!
+ */
+
+ if (tdb->map_ptr == MAP_FAILED) {
+ tdb->map_ptr = NULL;
+ TDB_LOG((tdb, TDB_DEBUG_WARNING, "tdb_mmap failed for size %u (%s)\n",
+ tdb->map_size, strerror(errno)));
+#ifdef HAVE_INCOHERENT_MMAP
+ tdb->ecode = TDB_ERR_IO;
+ return -1;
+#endif
+ }
+ } else {
+ tdb->map_ptr = NULL;
+ }
+#else
+ tdb->map_ptr = NULL;
+#endif
+ return 0;
+}
+
+/* expand a file. we prefer to use ftruncate, as that is what posix
+ says to use for mmap expansion */
+static int tdb_expand_file(struct tdb_context *tdb, tdb_off_t size, tdb_off_t addition)
+{
+ char buf[8192];
+ tdb_off_t new_size;
+ int ret;
+
+ if (tdb->read_only || tdb->traverse_read) {
+ tdb->ecode = TDB_ERR_RDONLY;
+ return -1;
+ }
+
+ if (!tdb_add_off_t(size, addition, &new_size)) {
+ tdb->ecode = TDB_ERR_OOM;
+ TDB_LOG((tdb, TDB_DEBUG_FATAL, "expand_file write "
+ "overflow detected current size[%u] addition[%u]!\n",
+ (unsigned)size, (unsigned)addition));
+ errno = ENOSPC;
+ return -1;
+ }
+
+#ifdef HAVE_POSIX_FALLOCATE
+ ret = tdb_posix_fallocate(tdb, size, addition);
+ if (ret == 0) {
+ return 0;
+ }
+ if (ret == ENOSPC) {
+ /*
+ * The Linux glibc (at least as of 2.24) fallback if
+ * the file system does not support fallocate does not
+ * reset the file size back to where it was. Also, to
+ * me it is unclear from the posix spec of
+ * posix_fallocate whether this is allowed or
+ * not. Better be safe than sorry and "goto fail" but
+ * "return -1" here, leaving the EOF pointer too
+ * large.
+ */
+ goto fail;
+ }
+
+ /*
+ * Retry the "old" way. Possibly unnecessary, but looking at
+ * our configure script there seem to be weird failure modes
+ * for posix_fallocate. See commit 3264a98ff16de, which
+ * probably refers to
+ * https://sourceware.org/bugzilla/show_bug.cgi?id=1083.
+ */
+#endif
+
+ ret = tdb_ftruncate(tdb, new_size);
+ if (ret == -1) {
+ char b = 0;
+ ssize_t written = tdb_pwrite(tdb, &b, 1, new_size - 1);
+ if (written == 0) {
+ /* try once more, potentially revealing errno */
+ written = tdb_pwrite(tdb, &b, 1, new_size - 1);
+ }
+ if (written == 0) {
+ /* again - give up, guessing errno */
+ errno = ENOSPC;
+ }
+ if (written != 1) {
+ tdb->ecode = TDB_ERR_OOM;
+ TDB_LOG((tdb, TDB_DEBUG_FATAL, "expand_file to %u failed (%s)\n",
+ (unsigned)new_size, strerror(errno)));
+ return -1;
+ }
+ }
+
+ /* now fill the file with something. This ensures that the
+ file isn't sparse, which would be very bad if we ran out of
+ disk. This must be done with write, not via mmap */
+ memset(buf, TDB_PAD_BYTE, sizeof(buf));
+ while (addition) {
+ size_t n = addition>sizeof(buf)?sizeof(buf):addition;
+ ssize_t written = tdb_pwrite(tdb, buf, n, size);
+ if (written == 0) {
+ /* prevent infinite loops: try _once_ more */
+ written = tdb_pwrite(tdb, buf, n, size);
+ }
+ if (written == 0) {
+ /* give up, trying to provide a useful errno */
+ tdb->ecode = TDB_ERR_OOM;
+ TDB_LOG((tdb, TDB_DEBUG_FATAL, "expand_file write "
+ "returned 0 twice: giving up!\n"));
+ errno = ENOSPC;
+ goto fail;
+ }
+ if (written == -1) {
+ tdb->ecode = TDB_ERR_OOM;
+ TDB_LOG((tdb, TDB_DEBUG_FATAL, "expand_file write of "
+ "%u bytes failed (%s)\n", (int)n,
+ strerror(errno)));
+ goto fail;
+ }
+ if (written != n) {
+ TDB_LOG((tdb, TDB_DEBUG_WARNING, "expand_file: wrote "
+ "only %zu of %zi bytes - retrying\n", written,
+ n));
+ }
+ addition -= written;
+ size += written;
+ }
+ return 0;
+
+fail:
+ {
+ int err = errno;
+
+ /*
+ * We're holding the freelist lock or are inside a
+ * transaction. Cutting the file is safe, the space we
+ * tried to allocate can't have been used anywhere in
+ * the meantime.
+ */
+
+ ret = tdb_ftruncate(tdb, size);
+ if (ret == -1) {
+ TDB_LOG((tdb, TDB_DEBUG_WARNING, "expand_file: "
+ "retruncate to %ju failed\n",
+ (uintmax_t)size));
+ }
+ errno = err;
+ }
+
+ return -1;
+}
+
+
+/* You need 'size', this tells you how much you should expand by. */
+tdb_off_t tdb_expand_adjust(tdb_off_t map_size, tdb_off_t size, int page_size)
+{
+ tdb_off_t new_size, top_size, increment;
+ tdb_off_t max_size = UINT32_MAX - map_size;
+
+ if (size > max_size) {
+ /*
+ * We can't round up anymore, just give back
+ * what we're asked for.
+ *
+ * The caller has to take care of the ENOSPC handling.
+ */
+ return size;
+ }
+
+ /* limit size in order to avoid using up huge amounts of memory for
+ * in memory tdbs if an oddball huge record creeps in */
+ if (size > 100 * 1024) {
+ increment = size * 2;
+ } else {
+ increment = size * 100;
+ }
+ if (increment < size) {
+ goto overflow;
+ }
+
+ if (!tdb_add_off_t(map_size, increment, &top_size)) {
+ goto overflow;
+ }
+
+ /* always make room for at least top_size more records, and at
+ least 25% more space. if the DB is smaller than 100MiB,
+ otherwise grow it by 10% only. */
+ if (map_size > 100 * 1024 * 1024) {
+ new_size = map_size * 1.10;
+ } else {
+ new_size = map_size * 1.25;
+ }
+ if (new_size < map_size) {
+ goto overflow;
+ }
+
+ /* Round the database up to a multiple of the page size */
+ new_size = MAX(top_size, new_size);
+
+ if (new_size + page_size < new_size) {
+ /* There's a "+" in TDB_ALIGN that might overflow... */
+ goto overflow;
+ }
+
+ return TDB_ALIGN(new_size, page_size) - map_size;
+
+overflow:
+ /*
+ * Somewhere in between we went over 4GB. Make one big jump to
+ * exactly 4GB database size.
+ */
+ return max_size;
+}
+
+/* expand the database at least size bytes by expanding the underlying
+ file and doing the mmap again if necessary */
+int tdb_expand(struct tdb_context *tdb, tdb_off_t size)
+{
+ struct tdb_record rec;
+ tdb_off_t offset;
+ tdb_off_t new_size;
+
+ if (tdb_lock(tdb, -1, F_WRLCK) == -1) {
+ TDB_LOG((tdb, TDB_DEBUG_ERROR, "lock failed in tdb_expand\n"));
+ return -1;
+ }
+
+ /* must know about any previous expansions by another process */
+ tdb_oob(tdb, tdb->map_size, 1, 1);
+
+ /*
+ * Note: that we don't care about tdb->hdr_ofs != 0 here
+ *
+ * The 4GB limitation is just related to tdb->map_size
+ * and the offset calculation in the records.
+ *
+ * The file on disk can be up to 4GB + tdb->hdr_ofs
+ */
+ size = tdb_expand_adjust(tdb->map_size, size, tdb->page_size);
+
+ if (!tdb_add_off_t(tdb->map_size, size, &new_size)) {
+ tdb->ecode = TDB_ERR_OOM;
+ TDB_LOG((tdb, TDB_DEBUG_FATAL, "tdb_expand "
+ "overflow detected current map_size[%u] size[%u]!\n",
+ (unsigned)tdb->map_size, (unsigned)size));
+ goto fail;
+ }
+
+ /* form a new freelist record */
+ offset = tdb->map_size;
+ memset(&rec,'\0',sizeof(rec));
+ rec.rec_len = size - sizeof(rec);
+
+ if (tdb->flags & TDB_INTERNAL) {
+ char *new_map_ptr;
+
+ new_map_ptr = (char *)realloc(tdb->map_ptr, new_size);
+ if (!new_map_ptr) {
+ tdb->ecode = TDB_ERR_OOM;
+ goto fail;
+ }
+ tdb->map_ptr = new_map_ptr;
+ tdb->map_size = new_size;
+ } else {
+ int ret;
+
+ /*
+ * expand the file itself
+ */
+ ret = tdb->methods->tdb_expand_file(tdb, tdb->map_size, size);
+ if (ret != 0) {
+ goto fail;
+ }
+
+ /* Explicitly remap: if we're in a transaction, this won't
+ * happen automatically! */
+ tdb_munmap(tdb);
+ tdb->map_size = new_size;
+ if (tdb_mmap(tdb) != 0) {
+ goto fail;
+ }
+ }
+
+ /* link it into the free list */
+ if (tdb_free(tdb, offset, &rec) == -1)
+ goto fail;
+
+ tdb_unlock(tdb, -1, F_WRLCK);
+ return 0;
+ fail:
+ tdb_unlock(tdb, -1, F_WRLCK);
+ return -1;
+}
+
+int _tdb_oob(struct tdb_context *tdb, tdb_off_t off, tdb_len_t len, int probe)
+{
+ int ret = tdb->methods->tdb_oob(tdb, off, len, probe);
+ return ret;
+}
+
+/* read/write a tdb_off_t */
+int tdb_ofs_read(struct tdb_context *tdb, tdb_off_t offset, tdb_off_t *d)
+{
+ return tdb->methods->tdb_read(tdb, offset, (char*)d, sizeof(*d), DOCONV());
+}
+
+int tdb_ofs_write(struct tdb_context *tdb, tdb_off_t offset, tdb_off_t *d)
+{
+ tdb_off_t off = *d;
+ return tdb->methods->tdb_write(tdb, offset, CONVERT(off), sizeof(*d));
+}
+
+
+/* read a lump of data, allocating the space for it */
+unsigned char *tdb_alloc_read(struct tdb_context *tdb, tdb_off_t offset, tdb_len_t len)
+{
+ unsigned char *buf;
+
+ /* some systems don't like zero length malloc */
+
+ if (!(buf = (unsigned char *)malloc(len ? len : 1))) {
+ /* Ensure ecode is set for log fn. */
+ tdb->ecode = TDB_ERR_OOM;
+ TDB_LOG((tdb, TDB_DEBUG_ERROR,"tdb_alloc_read malloc failed len=%u (%s)\n",
+ len, strerror(errno)));
+ return NULL;
+ }
+ if (tdb->methods->tdb_read(tdb, offset, buf, len, 0) == -1) {
+ SAFE_FREE(buf);
+ return NULL;
+ }
+ return buf;
+}
+
+/* Give a piece of tdb data to a parser */
+
+int tdb_parse_data(struct tdb_context *tdb, TDB_DATA key,
+ tdb_off_t offset, tdb_len_t len,
+ int (*parser)(TDB_DATA key, TDB_DATA data,
+ void *private_data),
+ void *private_data)
+{
+ TDB_DATA data;
+ int result;
+
+ data.dsize = len;
+
+ if ((tdb->transaction == NULL) && (tdb->map_ptr != NULL)) {
+ /*
+ * Optimize by avoiding the malloc/memcpy/free, point the
+ * parser directly at the mmap area.
+ */
+ if (tdb_oob(tdb, offset, len, 0) != 0) {
+ return -1;
+ }
+ data.dptr = offset + (unsigned char *)tdb->map_ptr;
+ return parser(key, data, private_data);
+ }
+
+ if (!(data.dptr = tdb_alloc_read(tdb, offset, len))) {
+ return -1;
+ }
+
+ result = parser(key, data, private_data);
+ free(data.dptr);
+ return result;
+}
+
+/* read/write a record */
+int tdb_rec_read(struct tdb_context *tdb, tdb_off_t offset, struct tdb_record *rec)
+{
+ int ret;
+ tdb_len_t overall_len;
+
+ if (tdb->methods->tdb_read(tdb, offset, rec, sizeof(*rec),DOCONV()) == -1)
+ return -1;
+ if (TDB_BAD_MAGIC(rec)) {
+ /* Ensure ecode is set for log fn. */
+ tdb->ecode = TDB_ERR_CORRUPT;
+ TDB_LOG((tdb, TDB_DEBUG_FATAL,"tdb_rec_read bad magic 0x%x at offset=%u\n", rec->magic, offset));
+ return -1;
+ }
+
+ overall_len = rec->key_len + rec->data_len;
+ if (overall_len < rec->data_len) {
+ /* overflow */
+ return -1;
+ }
+
+ if (overall_len > rec->rec_len) {
+ /* invalid record */
+ return -1;
+ }
+
+ ret = tdb_oob(tdb, offset, rec->key_len, 1);
+ if (ret == -1) {
+ return -1;
+ }
+ ret = tdb_oob(tdb, offset, rec->data_len, 1);
+ if (ret == -1) {
+ return -1;
+ }
+ ret = tdb_oob(tdb, offset, rec->rec_len, 1);
+ if (ret == -1) {
+ return -1;
+ }
+
+ return tdb_oob(tdb, rec->next, sizeof(*rec), 0);
+}
+
+int tdb_rec_write(struct tdb_context *tdb, tdb_off_t offset, struct tdb_record *rec)
+{
+ struct tdb_record r = *rec;
+ return tdb->methods->tdb_write(tdb, offset, CONVERT(r), sizeof(r));
+}
+
+static const struct tdb_methods io_methods = {
+ tdb_read,
+ tdb_write,
+ tdb_next_hash_chain,
+ tdb_notrans_oob,
+ tdb_expand_file,
+};
+
+/*
+ initialise the default methods table
+*/
+void tdb_io_init(struct tdb_context *tdb)
+{
+ tdb->methods = &io_methods;
+}
diff --git a/lib/tdb/common/lock.c b/lib/tdb/common/lock.c
new file mode 100644
index 0000000..c4e276f
--- /dev/null
+++ b/lib/tdb/common/lock.c
@@ -0,0 +1,1033 @@
+ /*
+ Unix SMB/CIFS implementation.
+
+ trivial database library
+
+ Copyright (C) Andrew Tridgell 1999-2005
+ Copyright (C) Paul `Rusty' Russell 2000
+ Copyright (C) Jeremy Allison 2000-2003
+
+ ** NOTE! The following LGPL license applies to the tdb
+ ** library. This does NOT imply that all of Samba is released
+ ** under the LGPL
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 3 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "tdb_private.h"
+
+_PUBLIC_ void tdb_setalarm_sigptr(struct tdb_context *tdb, volatile sig_atomic_t *ptr)
+{
+ tdb->interrupt_sig_ptr = ptr;
+}
+
+static int fcntl_lock(struct tdb_context *tdb,
+ int rw, off_t off, off_t len, bool waitflag)
+{
+ struct flock fl;
+ int cmd;
+
+#ifdef USE_TDB_MUTEX_LOCKING
+ {
+ int ret;
+ if (tdb_mutex_lock(tdb, rw, off, len, waitflag, &ret)) {
+ return ret;
+ }
+ }
+#endif
+
+ fl.l_type = rw;
+ fl.l_whence = SEEK_SET;
+ fl.l_start = off;
+ fl.l_len = len;
+ fl.l_pid = 0;
+
+ cmd = waitflag ? F_SETLKW : F_SETLK;
+
+ return fcntl(tdb->fd, cmd, &fl);
+}
+
+static int fcntl_unlock(struct tdb_context *tdb, int rw, off_t off, off_t len)
+{
+ struct flock fl;
+#if 0 /* Check they matched up locks and unlocks correctly. */
+ char line[80];
+ FILE *locks;
+ bool found = false;
+
+ locks = fopen("/proc/locks", "r");
+
+ while (fgets(line, 80, locks)) {
+ char *p;
+ int type, start, l;
+
+ /* eg. 1: FLOCK ADVISORY WRITE 2440 08:01:2180826 0 EOF */
+ p = strchr(line, ':') + 1;
+ if (strncmp(p, " POSIX ADVISORY ", strlen(" POSIX ADVISORY ")))
+ continue;
+ p += strlen(" FLOCK ADVISORY ");
+ if (strncmp(p, "READ ", strlen("READ ")) == 0)
+ type = F_RDLCK;
+ else if (strncmp(p, "WRITE ", strlen("WRITE ")) == 0)
+ type = F_WRLCK;
+ else
+ abort();
+ p += 6;
+ if (atoi(p) != getpid())
+ continue;
+ p = strchr(strchr(p, ' ') + 1, ' ') + 1;
+ start = atoi(p);
+ p = strchr(p, ' ') + 1;
+ if (strncmp(p, "EOF", 3) == 0)
+ l = 0;
+ else
+ l = atoi(p) - start + 1;
+
+ if (off == start) {
+ if (len != l) {
+ fprintf(stderr, "Len %u should be %u: %s",
+ (int)len, l, line);
+ abort();
+ }
+ if (type != rw) {
+ fprintf(stderr, "Type %s wrong: %s",
+ rw == F_RDLCK ? "READ" : "WRITE", line);
+ abort();
+ }
+ found = true;
+ break;
+ }
+ }
+
+ if (!found) {
+ fprintf(stderr, "Unlock on %u@%u not found!\n",
+ (int)off, (int)len);
+ abort();
+ }
+
+ fclose(locks);
+#endif
+
+#ifdef USE_TDB_MUTEX_LOCKING
+ {
+ int ret;
+ if (tdb_mutex_unlock(tdb, rw, off, len, &ret)) {
+ return ret;
+ }
+ }
+#endif
+
+ fl.l_type = F_UNLCK;
+ fl.l_whence = SEEK_SET;
+ fl.l_start = off;
+ fl.l_len = len;
+ fl.l_pid = 0;
+
+ return fcntl(tdb->fd, F_SETLKW, &fl);
+}
+
+/*
+ * Calculate the lock offset for a list
+ *
+ * list -1 is the freelist, otherwise a hash chain.
+ *
+ * Note that we consistently (but without real reason) lock hash chains at an
+ * offset that is 4 bytes below the real offset of the corresponding list head
+ * in the db.
+ *
+ * This is the memory layout of the hashchain array:
+ *
+ * FREELIST_TOP + 0 = freelist
+ * FREELIST_TOP + 4 = hashtable list 0
+ * FREELIST_TOP + 8 = hashtable list 1
+ * ...
+ *
+ * Otoh lock_offset computes:
+ *
+ * freelist = FREELIST_TOP - 4
+ * list 0 = FREELIST_TOP + 0
+ * list 1 = FREELIST_TOP + 4
+ * ...
+ *
+ * Unfortunately we can't change this calculation in order to align the locking
+ * offset with the memory layout, as that would make the locking incompatible
+ * between different tdb versions.
+ */
+static tdb_off_t lock_offset(int list)
+{
+ return FREELIST_TOP + 4*list;
+}
+
+/* a byte range locking function - return 0 on success
+ this functions locks/unlocks "len" byte at the specified offset.
+
+ On error, errno is also set so that errors are passed back properly
+ through tdb_open().
+
+ note that a len of zero means lock to end of file
+*/
+int tdb_brlock(struct tdb_context *tdb,
+ int rw_type, tdb_off_t offset, size_t len,
+ enum tdb_lock_flags flags)
+{
+ int ret;
+
+ if (tdb->flags & TDB_NOLOCK) {
+ return 0;
+ }
+
+ if (flags & TDB_LOCK_MARK_ONLY) {
+ return 0;
+ }
+
+ if ((rw_type == F_WRLCK) && (tdb->read_only || tdb->traverse_read)) {
+ tdb->ecode = TDB_ERR_RDONLY;
+ return -1;
+ }
+
+ do {
+ ret = fcntl_lock(tdb, rw_type, offset, len,
+ flags & TDB_LOCK_WAIT);
+ /* Check for a sigalarm break. */
+ if (ret == -1 && errno == EINTR &&
+ tdb->interrupt_sig_ptr &&
+ *tdb->interrupt_sig_ptr) {
+ break;
+ }
+ } while (ret == -1 && errno == EINTR);
+
+ if (ret == -1) {
+ tdb->ecode = TDB_ERR_LOCK;
+ /* Generic lock error. errno set by fcntl.
+ * EAGAIN is an expected return from non-blocking
+ * locks. */
+ if (!(flags & TDB_LOCK_PROBE) && errno != EAGAIN) {
+ TDB_LOG((tdb, TDB_DEBUG_TRACE,"tdb_brlock failed (fd=%d) at offset %u rw_type=%d flags=%d len=%zu\n",
+ tdb->fd, offset, rw_type, flags, len));
+ }
+ return -1;
+ }
+ return 0;
+}
+
+int tdb_brunlock(struct tdb_context *tdb,
+ int rw_type, tdb_off_t offset, size_t len)
+{
+ int ret;
+
+ if (tdb->flags & TDB_NOLOCK) {
+ return 0;
+ }
+
+ do {
+ ret = fcntl_unlock(tdb, rw_type, offset, len);
+ } while (ret == -1 && errno == EINTR);
+
+ if (ret == -1) {
+ TDB_LOG((tdb, TDB_DEBUG_TRACE,"tdb_brunlock failed (fd=%d) at offset %u rw_type=%u len=%zu\n",
+ tdb->fd, offset, rw_type, len));
+ }
+ return ret;
+}
+
+/*
+ * Do a tdb_brlock in a loop. Some OSes (such as solaris) have too
+ * conservative deadlock detection and claim a deadlock when progress can be
+ * made. For those OSes we may loop for a while.
+ */
+
+static int tdb_brlock_retry(struct tdb_context *tdb,
+ int rw_type, tdb_off_t offset, size_t len,
+ enum tdb_lock_flags flags)
+{
+ int count = 1000;
+
+ while (count--) {
+ struct timeval tv;
+ int ret;
+
+ ret = tdb_brlock(tdb, rw_type, offset, len, flags);
+ if (ret == 0) {
+ return 0;
+ }
+ if (errno != EDEADLK) {
+ break;
+ }
+ /* sleep for as short a time as we can - more portable than usleep() */
+ tv.tv_sec = 0;
+ tv.tv_usec = 1;
+ select(0, NULL, NULL, NULL, &tv);
+ }
+ return -1;
+}
+
+/*
+ upgrade a read lock to a write lock.
+*/
+int tdb_allrecord_upgrade(struct tdb_context *tdb)
+{
+ int ret;
+
+ if (tdb->allrecord_lock.count != 1) {
+ TDB_LOG((tdb, TDB_DEBUG_ERROR,
+ "tdb_allrecord_upgrade failed: count %u too high\n",
+ tdb->allrecord_lock.count));
+ tdb->ecode = TDB_ERR_LOCK;
+ return -1;
+ }
+
+ if (tdb->allrecord_lock.off != 1) {
+ TDB_LOG((tdb, TDB_DEBUG_ERROR,
+ "tdb_allrecord_upgrade failed: already upgraded?\n"));
+ tdb->ecode = TDB_ERR_LOCK;
+ return -1;
+ }
+
+ if (tdb_have_mutexes(tdb)) {
+ ret = tdb_mutex_allrecord_upgrade(tdb);
+ if (ret == -1) {
+ goto fail;
+ }
+ ret = tdb_brlock_retry(tdb, F_WRLCK, lock_offset(tdb->hash_size),
+ 0, TDB_LOCK_WAIT|TDB_LOCK_PROBE);
+ if (ret == -1) {
+ tdb_mutex_allrecord_downgrade(tdb);
+ }
+ } else {
+ ret = tdb_brlock_retry(tdb, F_WRLCK, FREELIST_TOP, 0,
+ TDB_LOCK_WAIT|TDB_LOCK_PROBE);
+ }
+
+ if (ret == 0) {
+ tdb->allrecord_lock.ltype = F_WRLCK;
+ tdb->allrecord_lock.off = 0;
+ return 0;
+ }
+fail:
+ TDB_LOG((tdb, TDB_DEBUG_TRACE,"tdb_allrecord_upgrade failed\n"));
+ return -1;
+}
+
+static struct tdb_lock_type *find_nestlock(struct tdb_context *tdb,
+ tdb_off_t offset)
+{
+ int i;
+
+ for (i=0; i<tdb->num_lockrecs; i++) {
+ if (tdb->lockrecs[i].off == offset) {
+ return &tdb->lockrecs[i];
+ }
+ }
+ return NULL;
+}
+
+/* lock an offset in the database. */
+int tdb_nest_lock(struct tdb_context *tdb, uint32_t offset, int ltype,
+ enum tdb_lock_flags flags)
+{
+ struct tdb_lock_type *new_lck;
+
+ if (offset >= lock_offset(tdb->hash_size)) {
+ tdb->ecode = TDB_ERR_LOCK;
+ TDB_LOG((tdb, TDB_DEBUG_ERROR,"tdb_lock: invalid offset %u for ltype=%d\n",
+ offset, ltype));
+ return -1;
+ }
+ if (tdb->flags & TDB_NOLOCK)
+ return 0;
+
+ new_lck = find_nestlock(tdb, offset);
+ if (new_lck) {
+ if ((new_lck->ltype == F_RDLCK) && (ltype == F_WRLCK)) {
+ if (!tdb_have_mutexes(tdb)) {
+ int ret;
+ /*
+ * Upgrade the underlying fcntl
+ * lock. Mutexes don't do readlocks,
+ * so this only applies to fcntl
+ * locking.
+ */
+ ret = tdb_brlock(tdb, ltype, offset, 1, flags);
+ if (ret != 0) {
+ return ret;
+ }
+ }
+ new_lck->ltype = F_WRLCK;
+ }
+ /*
+ * Just increment the in-memory struct, posix locks
+ * don't stack.
+ */
+ new_lck->count++;
+ return 0;
+ }
+
+ if (tdb->num_lockrecs == tdb->lockrecs_array_length) {
+ new_lck = (struct tdb_lock_type *)realloc(
+ tdb->lockrecs,
+ sizeof(*tdb->lockrecs) * (tdb->num_lockrecs+1));
+ if (new_lck == NULL) {
+ errno = ENOMEM;
+ return -1;
+ }
+ tdb->lockrecs_array_length = tdb->num_lockrecs+1;
+ tdb->lockrecs = new_lck;
+ }
+
+ /* Since fcntl locks don't nest, we do a lock for the first one,
+ and simply bump the count for future ones */
+ if (tdb_brlock(tdb, ltype, offset, 1, flags)) {
+ return -1;
+ }
+
+ new_lck = &tdb->lockrecs[tdb->num_lockrecs];
+
+ new_lck->off = offset;
+ new_lck->count = 1;
+ new_lck->ltype = ltype;
+ tdb->num_lockrecs++;
+
+ return 0;
+}
+
+static int tdb_lock_and_recover(struct tdb_context *tdb)
+{
+ int ret;
+
+ /* We need to match locking order in transaction commit. */
+ if (tdb_brlock(tdb, F_WRLCK, FREELIST_TOP, 0, TDB_LOCK_WAIT)) {
+ return -1;
+ }
+
+ if (tdb_brlock(tdb, F_WRLCK, OPEN_LOCK, 1, TDB_LOCK_WAIT)) {
+ tdb_brunlock(tdb, F_WRLCK, FREELIST_TOP, 0);
+ return -1;
+ }
+
+ ret = tdb_transaction_recover(tdb);
+
+ tdb_brunlock(tdb, F_WRLCK, OPEN_LOCK, 1);
+ tdb_brunlock(tdb, F_WRLCK, FREELIST_TOP, 0);
+
+ return ret;
+}
+
+static bool have_data_locks(const struct tdb_context *tdb)
+{
+ int i;
+
+ for (i = 0; i < tdb->num_lockrecs; i++) {
+ if (tdb->lockrecs[i].off >= lock_offset(-1))
+ return true;
+ }
+ return false;
+}
+
+/*
+ * A allrecord lock allows us to avoid per chain locks. Check if the allrecord
+ * lock is strong enough.
+ */
+static int tdb_lock_covered_by_allrecord_lock(struct tdb_context *tdb,
+ int ltype)
+{
+ if (ltype == F_RDLCK) {
+ /*
+ * The allrecord_lock is equal (F_RDLCK) or stronger
+ * (F_WRLCK). Pass.
+ */
+ return 0;
+ }
+
+ if (tdb->allrecord_lock.ltype == F_RDLCK) {
+ /*
+ * We ask for ltype==F_WRLCK, but the allrecord_lock
+ * is too weak. We can't upgrade here, so fail.
+ */
+ tdb->ecode = TDB_ERR_LOCK;
+ return -1;
+ }
+
+ /*
+ * Asking for F_WRLCK, allrecord is F_WRLCK as well. Pass.
+ */
+ return 0;
+}
+
+static int tdb_lock_list(struct tdb_context *tdb, int list, int ltype,
+ enum tdb_lock_flags waitflag)
+{
+ int ret;
+ bool check = false;
+
+ if (tdb->allrecord_lock.count) {
+ return tdb_lock_covered_by_allrecord_lock(tdb, ltype);
+ }
+
+ /*
+ * Check for recoveries: Someone might have kill -9'ed a process
+ * during a commit.
+ */
+ check = !have_data_locks(tdb);
+ ret = tdb_nest_lock(tdb, lock_offset(list), ltype, waitflag);
+
+ if (ret == 0 && check && tdb_needs_recovery(tdb)) {
+ tdb_nest_unlock(tdb, lock_offset(list), ltype, false);
+
+ if (tdb_lock_and_recover(tdb) == -1) {
+ return -1;
+ }
+ return tdb_lock_list(tdb, list, ltype, waitflag);
+ }
+ return ret;
+}
+
+/* lock a list in the database. list -1 is the alloc list */
+int tdb_lock(struct tdb_context *tdb, int list, int ltype)
+{
+ int ret;
+
+ ret = tdb_lock_list(tdb, list, ltype, TDB_LOCK_WAIT);
+ if (ret) {
+ TDB_LOG((tdb, TDB_DEBUG_ERROR, "tdb_lock failed on list %d "
+ "ltype=%d (%s)\n", list, ltype, strerror(errno)));
+ }
+ return ret;
+}
+
+/* lock a list in the database. list -1 is the alloc list. non-blocking lock */
+_PUBLIC_ int tdb_lock_nonblock(struct tdb_context *tdb, int list, int ltype);
+_PUBLIC_ int tdb_lock_nonblock(struct tdb_context *tdb, int list, int ltype)
+{
+ return tdb_lock_list(tdb, list, ltype, TDB_LOCK_NOWAIT);
+}
+
+
+int tdb_nest_unlock(struct tdb_context *tdb, uint32_t offset, int ltype,
+ bool mark_lock)
+{
+ int ret = -1;
+ struct tdb_lock_type *lck;
+
+ if (tdb->flags & TDB_NOLOCK)
+ return 0;
+
+ /* Sanity checks */
+ if (offset >= lock_offset(tdb->hash_size)) {
+ TDB_LOG((tdb, TDB_DEBUG_ERROR, "tdb_unlock: offset %u invalid (%d)\n", offset, tdb->hash_size));
+ return ret;
+ }
+
+ lck = find_nestlock(tdb, offset);
+ if ((lck == NULL) || (lck->count == 0)) {
+ TDB_LOG((tdb, TDB_DEBUG_ERROR, "tdb_unlock: count is 0\n"));
+ return -1;
+ }
+
+ if (lck->count > 1) {
+ lck->count--;
+ return 0;
+ }
+
+ /*
+ * This lock has count==1 left, so we need to unlock it in the
+ * kernel. We don't bother with decrementing the in-memory array
+ * element, we're about to overwrite it with the last array element
+ * anyway.
+ */
+
+ if (mark_lock) {
+ ret = 0;
+ } else {
+ ret = tdb_brunlock(tdb, ltype, offset, 1);
+ }
+
+ /*
+ * Shrink the array by overwriting the element just unlocked with the
+ * last array element.
+ */
+ *lck = tdb->lockrecs[--tdb->num_lockrecs];
+
+ /*
+ * We don't bother with realloc when the array shrinks, but if we have
+ * a completely idle tdb we should get rid of the locked array.
+ */
+
+ if (ret)
+ TDB_LOG((tdb, TDB_DEBUG_ERROR, "tdb_unlock: An error occurred unlocking!\n"));
+ return ret;
+}
+
+_PUBLIC_ int tdb_unlock(struct tdb_context *tdb, int list, int ltype);
+_PUBLIC_ int tdb_unlock(struct tdb_context *tdb, int list, int ltype)
+{
+ /* a global lock allows us to avoid per chain locks */
+ if (tdb->allrecord_lock.count) {
+ return tdb_lock_covered_by_allrecord_lock(tdb, ltype);
+ }
+
+ return tdb_nest_unlock(tdb, lock_offset(list), ltype, false);
+}
+
+/*
+ get the transaction lock
+ */
+int tdb_transaction_lock(struct tdb_context *tdb, int ltype,
+ enum tdb_lock_flags lockflags)
+{
+ return tdb_nest_lock(tdb, TRANSACTION_LOCK, ltype, lockflags);
+}
+
+/*
+ release the transaction lock
+ */
+int tdb_transaction_unlock(struct tdb_context *tdb, int ltype)
+{
+ return tdb_nest_unlock(tdb, TRANSACTION_LOCK, ltype, false);
+}
+
+/* Returns 0 if all done, -1 if error, 1 if ok. */
+static int tdb_allrecord_check(struct tdb_context *tdb, int ltype,
+ enum tdb_lock_flags flags, bool upgradable)
+{
+ /* There are no locks on read-only dbs */
+ if (tdb->read_only || tdb->traverse_read) {
+ tdb->ecode = TDB_ERR_LOCK;
+ return -1;
+ }
+
+ if (tdb->allrecord_lock.count &&
+ tdb->allrecord_lock.ltype == (uint32_t)ltype) {
+ tdb->allrecord_lock.count++;
+ return 0;
+ }
+
+ if (tdb->allrecord_lock.count) {
+ /* a global lock of a different type exists */
+ tdb->ecode = TDB_ERR_LOCK;
+ return -1;
+ }
+
+ if (tdb_have_extra_locks(tdb)) {
+ /* can't combine global and chain locks */
+ tdb->ecode = TDB_ERR_LOCK;
+ return -1;
+ }
+
+ if (upgradable && ltype != F_RDLCK) {
+ /* tdb error: you can't upgrade a write lock! */
+ tdb->ecode = TDB_ERR_LOCK;
+ return -1;
+ }
+ return 1;
+}
+
+/* We only need to lock individual bytes, but Linux merges consecutive locks
+ * so we lock in contiguous ranges. */
+static int tdb_chainlock_gradual(struct tdb_context *tdb,
+ int ltype, enum tdb_lock_flags flags,
+ size_t off, size_t len)
+{
+ int ret;
+ enum tdb_lock_flags nb_flags = (flags & ~TDB_LOCK_WAIT);
+
+ if (len <= 4) {
+ /* Single record. Just do blocking lock. */
+ return tdb_brlock(tdb, ltype, off, len, flags);
+ }
+
+ /* First we try non-blocking. */
+ ret = tdb_brlock(tdb, ltype, off, len, nb_flags);
+ if (ret == 0) {
+ return 0;
+ }
+
+ /* Try locking first half, then second. */
+ ret = tdb_chainlock_gradual(tdb, ltype, flags, off, len / 2);
+ if (ret == -1)
+ return -1;
+
+ ret = tdb_chainlock_gradual(tdb, ltype, flags,
+ off + len / 2, len - len / 2);
+ if (ret == -1) {
+ tdb_brunlock(tdb, ltype, off, len / 2);
+ return -1;
+ }
+ return 0;
+}
+
+/* lock/unlock entire database. It can only be upgradable if you have some
+ * other way of guaranteeing exclusivity (ie. transaction write lock).
+ * We do the locking gradually to avoid being starved by smaller locks. */
+int tdb_allrecord_lock(struct tdb_context *tdb, int ltype,
+ enum tdb_lock_flags flags, bool upgradable)
+{
+ int ret;
+
+ switch (tdb_allrecord_check(tdb, ltype, flags, upgradable)) {
+ case -1:
+ return -1;
+ case 0:
+ return 0;
+ }
+
+ /* We cover two kinds of locks:
+ * 1) Normal chain locks. Taken for almost all operations.
+ * 2) Individual records locks. Taken after normal or free
+ * chain locks.
+ *
+ * It is (1) which cause the starvation problem, so we're only
+ * gradual for that. */
+
+ if (tdb_have_mutexes(tdb)) {
+ ret = tdb_mutex_allrecord_lock(tdb, ltype, flags);
+ } else {
+ ret = tdb_chainlock_gradual(tdb, ltype, flags, FREELIST_TOP,
+ tdb->hash_size * 4);
+ }
+
+ if (ret == -1) {
+ return -1;
+ }
+
+ /* Grab individual record locks. */
+ if (tdb_brlock(tdb, ltype, lock_offset(tdb->hash_size), 0,
+ flags) == -1) {
+ if (tdb_have_mutexes(tdb)) {
+ tdb_mutex_allrecord_unlock(tdb);
+ } else {
+ tdb_brunlock(tdb, ltype, FREELIST_TOP,
+ tdb->hash_size * 4);
+ }
+ return -1;
+ }
+
+ tdb->allrecord_lock.count = 1;
+ /* If it's upgradable, it's actually exclusive so we can treat
+ * it as a write lock. */
+ tdb->allrecord_lock.ltype = upgradable ? F_WRLCK : ltype;
+ tdb->allrecord_lock.off = upgradable;
+
+ if (tdb_needs_recovery(tdb)) {
+ bool mark = flags & TDB_LOCK_MARK_ONLY;
+ tdb_allrecord_unlock(tdb, ltype, mark);
+ if (mark) {
+ tdb->ecode = TDB_ERR_LOCK;
+ TDB_LOG((tdb, TDB_DEBUG_ERROR,
+ "tdb_lockall_mark cannot do recovery\n"));
+ return -1;
+ }
+ if (tdb_lock_and_recover(tdb) == -1) {
+ return -1;
+ }
+ return tdb_allrecord_lock(tdb, ltype, flags, upgradable);
+ }
+
+ return 0;
+}
+
+
+
+/* unlock entire db */
+int tdb_allrecord_unlock(struct tdb_context *tdb, int ltype, bool mark_lock)
+{
+ /* There are no locks on read-only dbs */
+ if (tdb->read_only || tdb->traverse_read) {
+ tdb->ecode = TDB_ERR_LOCK;
+ return -1;
+ }
+
+ if (tdb->allrecord_lock.count == 0) {
+ tdb->ecode = TDB_ERR_LOCK;
+ return -1;
+ }
+
+ /* Upgradable locks are marked as write locks. */
+ if (tdb->allrecord_lock.ltype != (uint32_t)ltype
+ && (!tdb->allrecord_lock.off || ltype != F_RDLCK)) {
+ tdb->ecode = TDB_ERR_LOCK;
+ return -1;
+ }
+
+ if (tdb->allrecord_lock.count > 1) {
+ tdb->allrecord_lock.count--;
+ return 0;
+ }
+
+ if (!mark_lock) {
+ int ret;
+
+ if (tdb_have_mutexes(tdb)) {
+ ret = tdb_mutex_allrecord_unlock(tdb);
+ if (ret == 0) {
+ ret = tdb_brunlock(tdb, ltype,
+ lock_offset(tdb->hash_size),
+ 0);
+ }
+ } else {
+ ret = tdb_brunlock(tdb, ltype, FREELIST_TOP, 0);
+ }
+
+ if (ret != 0) {
+ TDB_LOG((tdb, TDB_DEBUG_ERROR, "tdb_unlockall failed "
+ "(%s)\n", strerror(errno)));
+ return -1;
+ }
+ }
+
+ tdb->allrecord_lock.count = 0;
+ tdb->allrecord_lock.ltype = 0;
+
+ return 0;
+}
+
+/* lock entire database with write lock */
+_PUBLIC_ int tdb_lockall(struct tdb_context *tdb)
+{
+ tdb_trace(tdb, "tdb_lockall");
+ return tdb_allrecord_lock(tdb, F_WRLCK, TDB_LOCK_WAIT, false);
+}
+
+/* lock entire database with write lock - mark only */
+_PUBLIC_ int tdb_lockall_mark(struct tdb_context *tdb)
+{
+ tdb_trace(tdb, "tdb_lockall_mark");
+ return tdb_allrecord_lock(tdb, F_WRLCK, TDB_LOCK_MARK_ONLY, false);
+}
+
+/* unlock entire database with write lock - unmark only */
+_PUBLIC_ int tdb_lockall_unmark(struct tdb_context *tdb)
+{
+ tdb_trace(tdb, "tdb_lockall_unmark");
+ return tdb_allrecord_unlock(tdb, F_WRLCK, true);
+}
+
+/* lock entire database with write lock - nonblocking varient */
+_PUBLIC_ int tdb_lockall_nonblock(struct tdb_context *tdb)
+{
+ int ret = tdb_allrecord_lock(tdb, F_WRLCK, TDB_LOCK_NOWAIT, false);
+ tdb_trace_ret(tdb, "tdb_lockall_nonblock", ret);
+ return ret;
+}
+
+/* unlock entire database with write lock */
+_PUBLIC_ int tdb_unlockall(struct tdb_context *tdb)
+{
+ tdb_trace(tdb, "tdb_unlockall");
+ return tdb_allrecord_unlock(tdb, F_WRLCK, false);
+}
+
+/* lock entire database with read lock */
+_PUBLIC_ int tdb_lockall_read(struct tdb_context *tdb)
+{
+ tdb_trace(tdb, "tdb_lockall_read");
+ return tdb_allrecord_lock(tdb, F_RDLCK, TDB_LOCK_WAIT, false);
+}
+
+/* lock entire database with read lock - nonblock varient */
+_PUBLIC_ int tdb_lockall_read_nonblock(struct tdb_context *tdb)
+{
+ int ret = tdb_allrecord_lock(tdb, F_RDLCK, TDB_LOCK_NOWAIT, false);
+ tdb_trace_ret(tdb, "tdb_lockall_read_nonblock", ret);
+ return ret;
+}
+
+/* unlock entire database with read lock */
+_PUBLIC_ int tdb_unlockall_read(struct tdb_context *tdb)
+{
+ tdb_trace(tdb, "tdb_unlockall_read");
+ return tdb_allrecord_unlock(tdb, F_RDLCK, false);
+}
+
+/* lock/unlock one hash chain. This is meant to be used to reduce
+ contention - it cannot guarantee how many records will be locked */
+_PUBLIC_ int tdb_chainlock(struct tdb_context *tdb, TDB_DATA key)
+{
+ int ret = tdb_lock(tdb, BUCKET(tdb->hash_fn(&key)), F_WRLCK);
+ tdb_trace_1rec(tdb, "tdb_chainlock", key);
+ return ret;
+}
+
+/* lock/unlock one hash chain, non-blocking. This is meant to be used
+ to reduce contention - it cannot guarantee how many records will be
+ locked */
+_PUBLIC_ int tdb_chainlock_nonblock(struct tdb_context *tdb, TDB_DATA key)
+{
+ int ret = tdb_lock_nonblock(tdb, BUCKET(tdb->hash_fn(&key)), F_WRLCK);
+ tdb_trace_1rec_ret(tdb, "tdb_chainlock_nonblock", key, ret);
+ return ret;
+}
+
+/* mark a chain as locked without actually locking it. Warning! use with great caution! */
+_PUBLIC_ int tdb_chainlock_mark(struct tdb_context *tdb, TDB_DATA key)
+{
+ int ret = tdb_nest_lock(tdb, lock_offset(BUCKET(tdb->hash_fn(&key))),
+ F_WRLCK, TDB_LOCK_MARK_ONLY);
+ tdb_trace_1rec(tdb, "tdb_chainlock_mark", key);
+ return ret;
+}
+
+/* unmark a chain as locked without actually locking it. Warning! use with great caution! */
+_PUBLIC_ int tdb_chainlock_unmark(struct tdb_context *tdb, TDB_DATA key)
+{
+ tdb_trace_1rec(tdb, "tdb_chainlock_unmark", key);
+ return tdb_nest_unlock(tdb, lock_offset(BUCKET(tdb->hash_fn(&key))),
+ F_WRLCK, true);
+}
+
+_PUBLIC_ int tdb_chainunlock(struct tdb_context *tdb, TDB_DATA key)
+{
+ tdb_trace_1rec(tdb, "tdb_chainunlock", key);
+ return tdb_unlock(tdb, BUCKET(tdb->hash_fn(&key)), F_WRLCK);
+}
+
+_PUBLIC_ int tdb_chainlock_read(struct tdb_context *tdb, TDB_DATA key)
+{
+ int ret;
+ ret = tdb_lock(tdb, BUCKET(tdb->hash_fn(&key)), F_RDLCK);
+ tdb_trace_1rec(tdb, "tdb_chainlock_read", key);
+ return ret;
+}
+
+_PUBLIC_ int tdb_chainunlock_read(struct tdb_context *tdb, TDB_DATA key)
+{
+ tdb_trace_1rec(tdb, "tdb_chainunlock_read", key);
+ return tdb_unlock(tdb, BUCKET(tdb->hash_fn(&key)), F_RDLCK);
+}
+
+_PUBLIC_ int tdb_chainlock_read_nonblock(struct tdb_context *tdb, TDB_DATA key)
+{
+ int ret = tdb_lock_nonblock(tdb, BUCKET(tdb->hash_fn(&key)), F_RDLCK);
+ tdb_trace_1rec_ret(tdb, "tdb_chainlock_read_nonblock", key, ret);
+ return ret;
+}
+
+/* record lock stops delete underneath */
+int tdb_lock_record(struct tdb_context *tdb, tdb_off_t off)
+{
+ if (tdb->allrecord_lock.count) {
+ return 0;
+ }
+ return off ? tdb_brlock(tdb, F_RDLCK, off, 1, TDB_LOCK_WAIT) : 0;
+}
+
+/*
+ Write locks override our own fcntl readlocks, so check it here.
+ Note this is meant to be F_SETLK, *not* F_SETLKW, as it's not
+ an error to fail to get the lock here.
+*/
+int tdb_write_lock_record(struct tdb_context *tdb, tdb_off_t off)
+{
+ struct tdb_traverse_lock *i;
+ if (tdb == NULL) {
+ return -1;
+ }
+ for (i = &tdb->travlocks; i; i = i->next)
+ if (i->off == off)
+ return -1;
+ if (tdb->allrecord_lock.count) {
+ if (tdb->allrecord_lock.ltype == F_WRLCK) {
+ return 0;
+ }
+ return -1;
+ }
+ return tdb_brlock(tdb, F_WRLCK, off, 1, TDB_LOCK_NOWAIT|TDB_LOCK_PROBE);
+}
+
+int tdb_write_unlock_record(struct tdb_context *tdb, tdb_off_t off)
+{
+ if (tdb->allrecord_lock.count) {
+ return 0;
+ }
+ return tdb_brunlock(tdb, F_WRLCK, off, 1);
+}
+
+/* fcntl locks don't stack: avoid unlocking someone else's */
+int tdb_unlock_record(struct tdb_context *tdb, tdb_off_t off)
+{
+ struct tdb_traverse_lock *i;
+ uint32_t count = 0;
+
+ if (tdb->allrecord_lock.count) {
+ return 0;
+ }
+
+ if (off == 0)
+ return 0;
+ for (i = &tdb->travlocks; i; i = i->next)
+ if (i->off == off)
+ count++;
+ return (count == 1 ? tdb_brunlock(tdb, F_RDLCK, off, 1) : 0);
+}
+
+bool tdb_have_extra_locks(struct tdb_context *tdb)
+{
+ unsigned int extra = tdb->num_lockrecs;
+
+ /* A transaction holds the lock for all records. */
+ if (!tdb->transaction && tdb->allrecord_lock.count) {
+ return true;
+ }
+
+ /* We always hold the active lock if CLEAR_IF_FIRST. */
+ if (find_nestlock(tdb, ACTIVE_LOCK)) {
+ extra--;
+ }
+
+ /* In a transaction, we expect to hold the transaction lock */
+ if (tdb->transaction && find_nestlock(tdb, TRANSACTION_LOCK)) {
+ extra--;
+ }
+
+ return extra;
+}
+
+/* The transaction code uses this to remove all locks. */
+void tdb_release_transaction_locks(struct tdb_context *tdb)
+{
+ int i;
+ unsigned int active = 0;
+
+ if (tdb->allrecord_lock.count != 0) {
+ tdb_allrecord_unlock(tdb, tdb->allrecord_lock.ltype, false);
+ tdb->allrecord_lock.count = 0;
+ }
+
+ for (i=0;i<tdb->num_lockrecs;i++) {
+ struct tdb_lock_type *lck = &tdb->lockrecs[i];
+
+ /* Don't release the active lock! Copy it to first entry. */
+ if (lck->off == ACTIVE_LOCK) {
+ tdb->lockrecs[active++] = *lck;
+ } else {
+ tdb_brunlock(tdb, lck->ltype, lck->off, 1);
+ }
+ }
+ tdb->num_lockrecs = active;
+}
+
+/* Following functions are added specifically to support CTDB. */
+
+/* Don't do actual fcntl locking, just mark tdb locked */
+_PUBLIC_ int tdb_transaction_write_lock_mark(struct tdb_context *tdb);
+_PUBLIC_ int tdb_transaction_write_lock_mark(struct tdb_context *tdb)
+{
+ return tdb_transaction_lock(tdb, F_WRLCK, TDB_LOCK_MARK_ONLY);
+}
+
+/* Don't do actual fcntl unlocking, just mark tdb unlocked */
+_PUBLIC_ int tdb_transaction_write_lock_unmark(struct tdb_context *tdb);
+_PUBLIC_ int tdb_transaction_write_lock_unmark(struct tdb_context *tdb)
+{
+ return tdb_nest_unlock(tdb, TRANSACTION_LOCK, F_WRLCK, true);
+}
diff --git a/lib/tdb/common/mutex.c b/lib/tdb/common/mutex.c
new file mode 100644
index 0000000..78926db
--- /dev/null
+++ b/lib/tdb/common/mutex.c
@@ -0,0 +1,1078 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ trivial database library
+
+ Copyright (C) Volker Lendecke 2012,2013
+ Copyright (C) Stefan Metzmacher 2013,2014
+ Copyright (C) Michael Adam 2014
+
+ ** NOTE! The following LGPL license applies to the tdb
+ ** library. This does NOT imply that all of Samba is released
+ ** under the LGPL
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 3 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, see <http://www.gnu.org/licenses/>.
+*/
+#include "tdb_private.h"
+#include "system/threads.h"
+
+#ifdef USE_TDB_MUTEX_LOCKING
+
+/*
+ * If we run with mutexes, we store the "struct tdb_mutexes" at the
+ * beginning of the file. We store an additional tdb_header right
+ * beyond the mutex area, page aligned. All the offsets within the tdb
+ * are relative to the area behind the mutex area. tdb->map_ptr points
+ * behind the mmap area as well, so the read and write path in the
+ * mutex case can remain unchanged.
+ *
+ * Early in the mutex development the mutexes were placed between the hash
+ * chain pointers and the real tdb data. This had two drawbacks: First, it
+ * made pointer calculations more complex. Second, we had to mmap the mutex
+ * area twice. One was the normal map_ptr in the tdb. This frequently changed
+ * from within tdb_oob. At least the Linux glibc robust mutex code assumes
+ * constant pointers in memory, so a constantly changing mmap area destroys
+ * the mutex list. So we had to mmap the first bytes of the file with a second
+ * mmap call. With that scheme, very weird errors happened that could be
+ * easily fixed by doing the mutex mmap in a second file. It seemed that
+ * mapping the same memory area twice does not end up in accessing the same
+ * physical page, looking at the mutexes in gdb it seemed that old data showed
+ * up after some re-mapping. To avoid a separate mutex file, the code now puts
+ * the real content of the tdb file after the mutex area. This way we do not
+ * have overlapping mmap areas, the mutex area is mmapped once and not
+ * changed, the tdb data area's mmap is constantly changed but does not
+ * overlap.
+ */
+
+struct tdb_mutexes {
+ struct tdb_header hdr;
+
+ /* protect allrecord_lock */
+ pthread_mutex_t allrecord_mutex;
+
+ /*
+ * F_UNLCK: free,
+ * F_RDLCK: shared,
+ * F_WRLCK: exclusive
+ */
+ short int allrecord_lock;
+
+ /*
+ * Index 0 is the freelist mutex, followed by
+ * one mutex per hashchain.
+ */
+ pthread_mutex_t hashchains[1];
+};
+
+bool tdb_have_mutexes(struct tdb_context *tdb)
+{
+ return ((tdb->feature_flags & TDB_FEATURE_FLAG_MUTEX) != 0);
+}
+
+size_t tdb_mutex_size(struct tdb_context *tdb)
+{
+ size_t mutex_size;
+
+ if (!tdb_have_mutexes(tdb)) {
+ return 0;
+ }
+
+ mutex_size = sizeof(struct tdb_mutexes);
+ mutex_size += tdb->hash_size * sizeof(pthread_mutex_t);
+
+ return TDB_ALIGN(mutex_size, tdb->page_size);
+}
+
+/*
+ * Get the index for a chain mutex
+ */
+static bool tdb_mutex_index(struct tdb_context *tdb, off_t off, off_t len,
+ unsigned *idx)
+{
+ /*
+ * Weird but true: We fcntl lock 1 byte at an offset 4 bytes before
+ * the 4 bytes of the freelist start and the hash chain that is about
+ * to be locked. See lock_offset() where the freelist is -1 vs the
+ * "+1" in TDB_HASH_TOP(). Because the mutex array is represented in
+ * the tdb file itself as data, we need to adjust the offset here.
+ */
+ const off_t freelist_lock_ofs = FREELIST_TOP - sizeof(tdb_off_t);
+
+ if (!tdb_have_mutexes(tdb)) {
+ return false;
+ }
+ if (len != 1) {
+ /* Possibly the allrecord lock */
+ return false;
+ }
+ if (off < freelist_lock_ofs) {
+ /* One of the special locks */
+ return false;
+ }
+ if (tdb->hash_size == 0) {
+ /* tdb not initialized yet, called from tdb_open_ex() */
+ return false;
+ }
+ if (off >= TDB_DATA_START(tdb->hash_size)) {
+ /* Single record lock from traverses */
+ return false;
+ }
+
+ /*
+ * Now we know it's a freelist or hash chain lock. Those are always 4
+ * byte aligned. Paranoia check.
+ */
+ if ((off % sizeof(tdb_off_t)) != 0) {
+ abort();
+ }
+
+ /*
+ * Re-index the fcntl offset into an offset into the mutex array
+ */
+ off -= freelist_lock_ofs; /* rebase to index 0 */
+ off /= sizeof(tdb_off_t); /* 0 for freelist 1-n for hashchain */
+
+ *idx = off;
+ return true;
+}
+
+static bool tdb_have_mutex_chainlocks(struct tdb_context *tdb)
+{
+ int i;
+
+ for (i=0; i < tdb->num_lockrecs; i++) {
+ bool ret;
+ unsigned idx;
+
+ ret = tdb_mutex_index(tdb,
+ tdb->lockrecs[i].off,
+ tdb->lockrecs[i].count,
+ &idx);
+ if (!ret) {
+ continue;
+ }
+
+ if (idx == 0) {
+ /* this is the freelist mutex */
+ continue;
+ }
+
+ return true;
+ }
+
+ return false;
+}
+
+static int chain_mutex_lock(pthread_mutex_t *m, bool waitflag)
+{
+ int ret;
+
+ if (waitflag) {
+ ret = pthread_mutex_lock(m);
+ } else {
+ ret = pthread_mutex_trylock(m);
+ }
+ if (ret != EOWNERDEAD) {
+ return ret;
+ }
+
+ /*
+ * For chainlocks, we don't do any cleanup (yet?)
+ */
+ return pthread_mutex_consistent(m);
+}
+
+static int allrecord_mutex_lock(struct tdb_mutexes *m, bool waitflag)
+{
+ int ret;
+
+ if (waitflag) {
+ ret = pthread_mutex_lock(&m->allrecord_mutex);
+ } else {
+ ret = pthread_mutex_trylock(&m->allrecord_mutex);
+ }
+ if (ret != EOWNERDEAD) {
+ return ret;
+ }
+
+ /*
+ * The allrecord lock holder died. We need to reset the allrecord_lock
+ * to F_UNLCK. This should also be the indication for
+ * tdb_needs_recovery.
+ */
+ m->allrecord_lock = F_UNLCK;
+
+ return pthread_mutex_consistent(&m->allrecord_mutex);
+}
+
+bool tdb_mutex_lock(struct tdb_context *tdb, int rw, off_t off, off_t len,
+ bool waitflag, int *pret)
+{
+ struct tdb_mutexes *m = tdb->mutexes;
+ pthread_mutex_t *chain;
+ int ret;
+ unsigned idx;
+ bool allrecord_ok;
+
+ if (!tdb_mutex_index(tdb, off, len, &idx)) {
+ return false;
+ }
+ chain = &m->hashchains[idx];
+
+again:
+ ret = chain_mutex_lock(chain, waitflag);
+ if (ret == EBUSY) {
+ ret = EAGAIN;
+ }
+ if (ret != 0) {
+ errno = ret;
+ goto fail;
+ }
+
+ if (idx == 0) {
+ /*
+ * This is a freelist lock, which is independent to
+ * the allrecord lock. So we're done once we got the
+ * freelist mutex.
+ */
+ *pret = 0;
+ return true;
+ }
+
+ if (tdb_have_mutex_chainlocks(tdb)) {
+ /*
+ * We can only check the allrecord lock once. If we do it with
+ * one chain mutex locked, we will deadlock with the allrecord
+ * locker process in the following way: We lock the first hash
+ * chain, we check for the allrecord lock. We keep the hash
+ * chain locked. Then the allrecord locker locks the
+ * allrecord_mutex. It walks the list of chain mutexes,
+ * locking them all in sequence. Meanwhile, we have the chain
+ * mutex locked, so the allrecord locker blocks trying to lock
+ * our chain mutex. Then we come in and try to lock the second
+ * chain lock, which in most cases will be the freelist. We
+ * see that the allrecord lock is locked and put ourselves on
+ * the allrecord_mutex. This will never be signalled though
+ * because the allrecord locker waits for us to give up the
+ * chain lock.
+ */
+
+ *pret = 0;
+ return true;
+ }
+
+ /*
+ * Check if someone is has the allrecord lock: queue if so.
+ */
+
+ allrecord_ok = false;
+
+ if (m->allrecord_lock == F_UNLCK) {
+ /*
+ * allrecord lock not taken
+ */
+ allrecord_ok = true;
+ }
+
+ if ((m->allrecord_lock == F_RDLCK) && (rw == F_RDLCK)) {
+ /*
+ * allrecord shared lock taken, but we only want to read
+ */
+ allrecord_ok = true;
+ }
+
+ if (allrecord_ok) {
+ *pret = 0;
+ return true;
+ }
+
+ ret = pthread_mutex_unlock(chain);
+ if (ret != 0) {
+ TDB_LOG((tdb, TDB_DEBUG_FATAL, "pthread_mutex_unlock"
+ "(chain_mutex) failed: %s\n", strerror(ret)));
+ errno = ret;
+ goto fail;
+ }
+ ret = allrecord_mutex_lock(m, waitflag);
+ if (ret == EBUSY) {
+ ret = EAGAIN;
+ }
+ if (ret != 0) {
+ if (waitflag || (ret != EAGAIN)) {
+ TDB_LOG((tdb, TDB_DEBUG_FATAL, "pthread_mutex_%slock"
+ "(allrecord_mutex) failed: %s\n",
+ waitflag ? "" : "try_", strerror(ret)));
+ }
+ errno = ret;
+ goto fail;
+ }
+ ret = pthread_mutex_unlock(&m->allrecord_mutex);
+ if (ret != 0) {
+ TDB_LOG((tdb, TDB_DEBUG_FATAL, "pthread_mutex_unlock"
+ "(allrecord_mutex) failed: %s\n", strerror(ret)));
+ errno = ret;
+ goto fail;
+ }
+ goto again;
+
+fail:
+ *pret = -1;
+ return true;
+}
+
+bool tdb_mutex_unlock(struct tdb_context *tdb, int rw, off_t off, off_t len,
+ int *pret)
+{
+ struct tdb_mutexes *m = tdb->mutexes;
+ pthread_mutex_t *chain;
+ int ret;
+ unsigned idx;
+
+ if (!tdb_mutex_index(tdb, off, len, &idx)) {
+ return false;
+ }
+ chain = &m->hashchains[idx];
+
+ ret = pthread_mutex_unlock(chain);
+ if (ret == 0) {
+ *pret = 0;
+ return true;
+ }
+ errno = ret;
+ *pret = -1;
+ return true;
+}
+
+int tdb_mutex_allrecord_lock(struct tdb_context *tdb, int ltype,
+ enum tdb_lock_flags flags)
+{
+ struct tdb_mutexes *m = tdb->mutexes;
+ int ret;
+ uint32_t i;
+ bool waitflag = (flags & TDB_LOCK_WAIT);
+ int saved_errno;
+
+ if (tdb->flags & TDB_NOLOCK) {
+ return 0;
+ }
+
+ if (flags & TDB_LOCK_MARK_ONLY) {
+ return 0;
+ }
+
+ ret = allrecord_mutex_lock(m, waitflag);
+ if (!waitflag && (ret == EBUSY)) {
+ errno = EAGAIN;
+ tdb->ecode = TDB_ERR_LOCK;
+ return -1;
+ }
+ if (ret != 0) {
+ if (!(flags & TDB_LOCK_PROBE)) {
+ TDB_LOG((tdb, TDB_DEBUG_TRACE,
+ "allrecord_mutex_lock() failed: %s\n",
+ strerror(ret)));
+ }
+ tdb->ecode = TDB_ERR_LOCK;
+ return -1;
+ }
+
+ if (m->allrecord_lock != F_UNLCK) {
+ TDB_LOG((tdb, TDB_DEBUG_FATAL, "allrecord_lock == %d\n",
+ (int)m->allrecord_lock));
+ goto fail_unlock_allrecord_mutex;
+ }
+ m->allrecord_lock = (ltype == F_RDLCK) ? F_RDLCK : F_WRLCK;
+
+ for (i=0; i<tdb->hash_size; i++) {
+
+ /* ignore hashchains[0], the freelist */
+ pthread_mutex_t *chain = &m->hashchains[i+1];
+
+ ret = chain_mutex_lock(chain, waitflag);
+ if (!waitflag && (ret == EBUSY)) {
+ errno = EAGAIN;
+ goto fail_unroll_allrecord_lock;
+ }
+ if (ret != 0) {
+ if (!(flags & TDB_LOCK_PROBE)) {
+ TDB_LOG((tdb, TDB_DEBUG_TRACE,
+ "chain_mutex_lock() failed: %s\n",
+ strerror(ret)));
+ }
+ errno = ret;
+ goto fail_unroll_allrecord_lock;
+ }
+
+ ret = pthread_mutex_unlock(chain);
+ if (ret != 0) {
+ TDB_LOG((tdb, TDB_DEBUG_FATAL, "pthread_mutex_unlock"
+ "(chainlock) failed: %s\n", strerror(ret)));
+ errno = ret;
+ goto fail_unroll_allrecord_lock;
+ }
+ }
+ /*
+ * We leave this routine with m->allrecord_mutex locked
+ */
+ return 0;
+
+fail_unroll_allrecord_lock:
+ m->allrecord_lock = F_UNLCK;
+
+fail_unlock_allrecord_mutex:
+ saved_errno = errno;
+ ret = pthread_mutex_unlock(&m->allrecord_mutex);
+ if (ret != 0) {
+ TDB_LOG((tdb, TDB_DEBUG_FATAL, "pthread_mutex_unlock"
+ "(allrecord_mutex) failed: %s\n", strerror(ret)));
+ }
+ errno = saved_errno;
+ tdb->ecode = TDB_ERR_LOCK;
+ return -1;
+}
+
+int tdb_mutex_allrecord_upgrade(struct tdb_context *tdb)
+{
+ struct tdb_mutexes *m = tdb->mutexes;
+ int ret;
+ uint32_t i;
+
+ if (tdb->flags & TDB_NOLOCK) {
+ return 0;
+ }
+
+ /*
+ * Our only caller tdb_allrecord_upgrade()
+ * garantees that we already own the allrecord lock.
+ *
+ * Which means m->allrecord_mutex is still locked by us.
+ */
+
+ if (m->allrecord_lock != F_RDLCK) {
+ tdb->ecode = TDB_ERR_LOCK;
+ TDB_LOG((tdb, TDB_DEBUG_FATAL, "allrecord_lock == %d\n",
+ (int)m->allrecord_lock));
+ return -1;
+ }
+
+ m->allrecord_lock = F_WRLCK;
+
+ for (i=0; i<tdb->hash_size; i++) {
+
+ /* ignore hashchains[0], the freelist */
+ pthread_mutex_t *chain = &m->hashchains[i+1];
+
+ ret = chain_mutex_lock(chain, true);
+ if (ret != 0) {
+ TDB_LOG((tdb, TDB_DEBUG_FATAL, "pthread_mutex_lock"
+ "(chainlock) failed: %s\n", strerror(ret)));
+ goto fail_unroll_allrecord_lock;
+ }
+
+ ret = pthread_mutex_unlock(chain);
+ if (ret != 0) {
+ TDB_LOG((tdb, TDB_DEBUG_FATAL, "pthread_mutex_unlock"
+ "(chainlock) failed: %s\n", strerror(ret)));
+ goto fail_unroll_allrecord_lock;
+ }
+ }
+
+ return 0;
+
+fail_unroll_allrecord_lock:
+ m->allrecord_lock = F_RDLCK;
+ tdb->ecode = TDB_ERR_LOCK;
+ return -1;
+}
+
+void tdb_mutex_allrecord_downgrade(struct tdb_context *tdb)
+{
+ struct tdb_mutexes *m = tdb->mutexes;
+
+ /*
+ * Our only caller tdb_allrecord_upgrade() (in the error case)
+ * garantees that we already own the allrecord lock.
+ *
+ * Which means m->allrecord_mutex is still locked by us.
+ */
+
+ if (m->allrecord_lock != F_WRLCK) {
+ TDB_LOG((tdb, TDB_DEBUG_FATAL, "allrecord_lock == %d\n",
+ (int)m->allrecord_lock));
+ return;
+ }
+
+ m->allrecord_lock = F_RDLCK;
+ return;
+}
+
+
+int tdb_mutex_allrecord_unlock(struct tdb_context *tdb)
+{
+ struct tdb_mutexes *m = tdb->mutexes;
+ short old;
+ int ret;
+
+ if (tdb->flags & TDB_NOLOCK) {
+ return 0;
+ }
+
+ /*
+ * Our only callers tdb_allrecord_unlock() and
+ * tdb_allrecord_lock() (in the error path)
+ * garantee that we already own the allrecord lock.
+ *
+ * Which means m->allrecord_mutex is still locked by us.
+ */
+
+ if ((m->allrecord_lock != F_RDLCK) && (m->allrecord_lock != F_WRLCK)) {
+ TDB_LOG((tdb, TDB_DEBUG_FATAL, "allrecord_lock == %d\n",
+ (int)m->allrecord_lock));
+ return -1;
+ }
+
+ old = m->allrecord_lock;
+ m->allrecord_lock = F_UNLCK;
+
+ ret = pthread_mutex_unlock(&m->allrecord_mutex);
+ if (ret != 0) {
+ m->allrecord_lock = old;
+ TDB_LOG((tdb, TDB_DEBUG_FATAL, "pthread_mutex_unlock"
+ "(allrecord_mutex) failed: %s\n", strerror(ret)));
+ return -1;
+ }
+ return 0;
+}
+
+int tdb_mutex_init(struct tdb_context *tdb)
+{
+ struct tdb_mutexes *m;
+ pthread_mutexattr_t ma;
+ uint32_t i;
+ int ret;
+
+ ret = tdb_mutex_mmap(tdb);
+ if (ret == -1) {
+ return -1;
+ }
+ m = tdb->mutexes;
+
+ ret = pthread_mutexattr_init(&ma);
+ if (ret != 0) {
+ goto fail_munmap;
+ }
+ ret = pthread_mutexattr_settype(&ma, PTHREAD_MUTEX_ERRORCHECK);
+ if (ret != 0) {
+ goto fail;
+ }
+ ret = pthread_mutexattr_setpshared(&ma, PTHREAD_PROCESS_SHARED);
+ if (ret != 0) {
+ goto fail;
+ }
+ ret = pthread_mutexattr_setrobust(&ma, PTHREAD_MUTEX_ROBUST);
+ if (ret != 0) {
+ goto fail;
+ }
+
+ for (i=0; i<tdb->hash_size+1; i++) {
+ pthread_mutex_t *chain = &m->hashchains[i];
+
+ ret = pthread_mutex_init(chain, &ma);
+ if (ret != 0) {
+ goto fail;
+ }
+ }
+
+ m->allrecord_lock = F_UNLCK;
+
+ ret = pthread_mutex_init(&m->allrecord_mutex, &ma);
+ if (ret != 0) {
+ goto fail;
+ }
+ ret = 0;
+fail:
+ pthread_mutexattr_destroy(&ma);
+fail_munmap:
+
+ if (ret == 0) {
+ return 0;
+ }
+
+ tdb_mutex_munmap(tdb);
+
+ errno = ret;
+ return -1;
+}
+
+int tdb_mutex_mmap(struct tdb_context *tdb)
+{
+ size_t len;
+ void *ptr;
+
+ len = tdb_mutex_size(tdb);
+ if (len == 0) {
+ return 0;
+ }
+
+ if (tdb->mutexes != NULL) {
+ return 0;
+ }
+
+ ptr = mmap(NULL, len, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_FILE,
+ tdb->fd, 0);
+ if (ptr == MAP_FAILED) {
+ return -1;
+ }
+ tdb->mutexes = (struct tdb_mutexes *)ptr;
+
+ return 0;
+}
+
+int tdb_mutex_munmap(struct tdb_context *tdb)
+{
+ size_t len;
+ int ret;
+
+ len = tdb_mutex_size(tdb);
+ if (len == 0) {
+ return 0;
+ }
+
+ ret = munmap(tdb->mutexes, len);
+ if (ret == -1) {
+ return -1;
+ }
+ tdb->mutexes = NULL;
+
+ return 0;
+}
+
+static bool tdb_mutex_locking_cached;
+
+static bool tdb_mutex_locking_supported(void)
+{
+ pthread_mutexattr_t ma;
+ pthread_mutex_t m;
+ int ret;
+ static bool initialized;
+
+ if (initialized) {
+ return tdb_mutex_locking_cached;
+ }
+
+ initialized = true;
+
+ ret = pthread_mutexattr_init(&ma);
+ if (ret != 0) {
+ return false;
+ }
+ ret = pthread_mutexattr_settype(&ma, PTHREAD_MUTEX_ERRORCHECK);
+ if (ret != 0) {
+ goto cleanup_ma;
+ }
+ ret = pthread_mutexattr_setpshared(&ma, PTHREAD_PROCESS_SHARED);
+ if (ret != 0) {
+ goto cleanup_ma;
+ }
+ ret = pthread_mutexattr_setrobust(&ma, PTHREAD_MUTEX_ROBUST);
+ if (ret != 0) {
+ goto cleanup_ma;
+ }
+ ret = pthread_mutex_init(&m, &ma);
+ if (ret != 0) {
+ goto cleanup_ma;
+ }
+ ret = pthread_mutex_lock(&m);
+ if (ret != 0) {
+ goto cleanup_m;
+ }
+ /*
+ * This makes sure we have real mutexes
+ * from a threading library instead of just
+ * stubs from libc.
+ */
+ ret = pthread_mutex_lock(&m);
+ if (ret != EDEADLK) {
+ goto cleanup_lock;
+ }
+ ret = pthread_mutex_unlock(&m);
+ if (ret != 0) {
+ goto cleanup_m;
+ }
+
+ tdb_mutex_locking_cached = true;
+ goto cleanup_m;
+
+cleanup_lock:
+ pthread_mutex_unlock(&m);
+cleanup_m:
+ pthread_mutex_destroy(&m);
+cleanup_ma:
+ pthread_mutexattr_destroy(&ma);
+ return tdb_mutex_locking_cached;
+}
+
+static void (*tdb_robust_mutext_old_handler)(int) = SIG_ERR;
+static pid_t tdb_robust_mutex_pid = -1;
+
+static bool tdb_robust_mutex_setup_sigchild(void (*handler)(int),
+ void (**p_old_handler)(int))
+{
+#ifdef HAVE_SIGACTION
+ struct sigaction act;
+ struct sigaction oldact;
+
+ memset(&act, '\0', sizeof(act));
+
+ act.sa_handler = handler;
+#ifdef SA_RESTART
+ act.sa_flags = SA_RESTART;
+#endif
+ sigemptyset(&act.sa_mask);
+ sigaddset(&act.sa_mask, SIGCHLD);
+ sigaction(SIGCHLD, &act, &oldact);
+ if (p_old_handler) {
+ *p_old_handler = oldact.sa_handler;
+ }
+ return true;
+#else /* !HAVE_SIGACTION */
+ return false;
+#endif
+}
+
+static void tdb_robust_mutex_handler(int sig)
+{
+ pid_t child_pid = tdb_robust_mutex_pid;
+
+ if (child_pid != -1) {
+ pid_t pid;
+
+ pid = waitpid(child_pid, NULL, WNOHANG);
+ if (pid == -1) {
+ switch (errno) {
+ case ECHILD:
+ tdb_robust_mutex_pid = -1;
+ return;
+
+ default:
+ return;
+ }
+ }
+ if (pid == child_pid) {
+ tdb_robust_mutex_pid = -1;
+ return;
+ }
+ }
+
+ if (tdb_robust_mutext_old_handler == SIG_DFL) {
+ return;
+ }
+ if (tdb_robust_mutext_old_handler == SIG_IGN) {
+ return;
+ }
+ if (tdb_robust_mutext_old_handler == SIG_ERR) {
+ return;
+ }
+
+ tdb_robust_mutext_old_handler(sig);
+}
+
+static void tdb_robust_mutex_wait_for_child(pid_t *child_pid)
+{
+ int options = WNOHANG;
+
+ if (*child_pid == -1) {
+ return;
+ }
+
+ while (tdb_robust_mutex_pid > 0) {
+ pid_t pid;
+
+ /*
+ * First we try with WNOHANG, as the process might not exist
+ * anymore. Once we've sent SIGKILL we block waiting for the
+ * exit.
+ */
+ pid = waitpid(*child_pid, NULL, options);
+ if (pid == -1) {
+ if (errno == EINTR) {
+ continue;
+ } else if (errno == ECHILD) {
+ break;
+ } else {
+ abort();
+ }
+ }
+ if (pid == *child_pid) {
+ break;
+ }
+
+ kill(*child_pid, SIGKILL);
+ options = 0;
+ }
+
+ tdb_robust_mutex_pid = -1;
+ *child_pid = -1;
+}
+
+_PUBLIC_ bool tdb_runtime_check_for_robust_mutexes(void)
+{
+ void *ptr = NULL;
+ pthread_mutex_t *m = NULL;
+ pthread_mutexattr_t ma;
+ int ret = 1;
+ int pipe_down[2] = { -1, -1 };
+ int pipe_up[2] = { -1, -1 };
+ ssize_t nread;
+ char c = 0;
+ bool ok;
+ static bool initialized;
+ pid_t saved_child_pid = -1;
+ bool cleanup_ma = false;
+
+ if (initialized) {
+ return tdb_mutex_locking_cached;
+ }
+
+ initialized = true;
+
+ ok = tdb_mutex_locking_supported();
+ if (!ok) {
+ return false;
+ }
+
+ tdb_mutex_locking_cached = false;
+
+ ptr = mmap(NULL, sizeof(pthread_mutex_t), PROT_READ|PROT_WRITE,
+ MAP_SHARED|MAP_ANON, -1 /* fd */, 0);
+ if (ptr == MAP_FAILED) {
+ return false;
+ }
+
+ ret = pipe(pipe_down);
+ if (ret != 0) {
+ goto cleanup;
+ }
+ ret = pipe(pipe_up);
+ if (ret != 0) {
+ goto cleanup;
+ }
+
+ ret = pthread_mutexattr_init(&ma);
+ if (ret != 0) {
+ goto cleanup;
+ }
+ cleanup_ma = true;
+ ret = pthread_mutexattr_settype(&ma, PTHREAD_MUTEX_ERRORCHECK);
+ if (ret != 0) {
+ goto cleanup;
+ }
+ ret = pthread_mutexattr_setpshared(&ma, PTHREAD_PROCESS_SHARED);
+ if (ret != 0) {
+ goto cleanup;
+ }
+ ret = pthread_mutexattr_setrobust(&ma, PTHREAD_MUTEX_ROBUST);
+ if (ret != 0) {
+ goto cleanup;
+ }
+ ret = pthread_mutex_init(ptr, &ma);
+ if (ret != 0) {
+ goto cleanup;
+ }
+ m = (pthread_mutex_t *)ptr;
+
+ if (tdb_robust_mutex_setup_sigchild(tdb_robust_mutex_handler,
+ &tdb_robust_mutext_old_handler) == false) {
+ goto cleanup;
+ }
+
+ tdb_robust_mutex_pid = fork();
+ saved_child_pid = tdb_robust_mutex_pid;
+ if (tdb_robust_mutex_pid == 0) {
+ size_t nwritten;
+ close(pipe_down[1]);
+ close(pipe_up[0]);
+ ret = pthread_mutex_lock(m);
+ nwritten = write(pipe_up[1], &ret, sizeof(ret));
+ if (nwritten != sizeof(ret)) {
+ _exit(1);
+ }
+ if (ret != 0) {
+ _exit(1);
+ }
+ nread = read(pipe_down[0], &c, 1);
+ if (nread != 1) {
+ _exit(1);
+ }
+ /* leave locked */
+ _exit(0);
+ }
+ if (tdb_robust_mutex_pid == -1) {
+ goto cleanup;
+ }
+ close(pipe_down[0]);
+ pipe_down[0] = -1;
+ close(pipe_up[1]);
+ pipe_up[1] = -1;
+
+ nread = read(pipe_up[0], &ret, sizeof(ret));
+ if (nread != sizeof(ret)) {
+ goto cleanup;
+ }
+
+ ret = pthread_mutex_trylock(m);
+ if (ret != EBUSY) {
+ if (ret == 0) {
+ pthread_mutex_unlock(m);
+ }
+ goto cleanup;
+ }
+
+ if (write(pipe_down[1], &c, 1) != 1) {
+ goto cleanup;
+ }
+
+ nread = read(pipe_up[0], &c, 1);
+ if (nread != 0) {
+ goto cleanup;
+ }
+
+ tdb_robust_mutex_wait_for_child(&saved_child_pid);
+
+ ret = pthread_mutex_trylock(m);
+ if (ret != EOWNERDEAD) {
+ if (ret == 0) {
+ pthread_mutex_unlock(m);
+ }
+ goto cleanup;
+ }
+
+ ret = pthread_mutex_consistent(m);
+ if (ret != 0) {
+ goto cleanup;
+ }
+
+ ret = pthread_mutex_trylock(m);
+ if (ret != EDEADLK && ret != EBUSY) {
+ pthread_mutex_unlock(m);
+ goto cleanup;
+ }
+
+ ret = pthread_mutex_unlock(m);
+ if (ret != 0) {
+ goto cleanup;
+ }
+
+ tdb_mutex_locking_cached = true;
+
+cleanup:
+ /*
+ * Note that we don't reset the signal handler we just reset
+ * tdb_robust_mutex_pid to -1. This is ok as this code path is only
+ * called once per process.
+ *
+ * Leaving our signal handler avoids races with other threads potentialy
+ * setting up their SIGCHLD handlers.
+ *
+ * The worst thing that can happen is that the other newer signal
+ * handler will get the SIGCHLD signal for our child and/or reap the
+ * child with a wait() function. tdb_robust_mutex_wait_for_child()
+ * handles the case where waitpid returns ECHILD.
+ */
+ tdb_robust_mutex_wait_for_child(&saved_child_pid);
+
+ if (m != NULL) {
+ pthread_mutex_destroy(m);
+ }
+ if (cleanup_ma) {
+ pthread_mutexattr_destroy(&ma);
+ }
+ if (pipe_down[0] != -1) {
+ close(pipe_down[0]);
+ }
+ if (pipe_down[1] != -1) {
+ close(pipe_down[1]);
+ }
+ if (pipe_up[0] != -1) {
+ close(pipe_up[0]);
+ }
+ if (pipe_up[1] != -1) {
+ close(pipe_up[1]);
+ }
+ if (ptr != NULL) {
+ munmap(ptr, sizeof(pthread_mutex_t));
+ }
+
+ return tdb_mutex_locking_cached;
+}
+
+#else
+
+size_t tdb_mutex_size(struct tdb_context *tdb)
+{
+ return 0;
+}
+
+bool tdb_have_mutexes(struct tdb_context *tdb)
+{
+ return false;
+}
+
+int tdb_mutex_allrecord_lock(struct tdb_context *tdb, int ltype,
+ enum tdb_lock_flags flags)
+{
+ tdb->ecode = TDB_ERR_LOCK;
+ return -1;
+}
+
+int tdb_mutex_allrecord_unlock(struct tdb_context *tdb)
+{
+ return -1;
+}
+
+int tdb_mutex_allrecord_upgrade(struct tdb_context *tdb)
+{
+ tdb->ecode = TDB_ERR_LOCK;
+ return -1;
+}
+
+void tdb_mutex_allrecord_downgrade(struct tdb_context *tdb)
+{
+ return;
+}
+
+int tdb_mutex_mmap(struct tdb_context *tdb)
+{
+ errno = ENOSYS;
+ return -1;
+}
+
+int tdb_mutex_munmap(struct tdb_context *tdb)
+{
+ errno = ENOSYS;
+ return -1;
+}
+
+int tdb_mutex_init(struct tdb_context *tdb)
+{
+ errno = ENOSYS;
+ return -1;
+}
+
+_PUBLIC_ bool tdb_runtime_check_for_robust_mutexes(void)
+{
+ return false;
+}
+
+#endif
diff --git a/lib/tdb/common/open.c b/lib/tdb/common/open.c
new file mode 100644
index 0000000..f7f65b0
--- /dev/null
+++ b/lib/tdb/common/open.c
@@ -0,0 +1,962 @@
+ /*
+ Unix SMB/CIFS implementation.
+
+ trivial database library
+
+ Copyright (C) Andrew Tridgell 1999-2005
+ Copyright (C) Paul `Rusty' Russell 2000
+ Copyright (C) Jeremy Allison 2000-2003
+
+ ** NOTE! The following LGPL license applies to the tdb
+ ** library. This does NOT imply that all of Samba is released
+ ** under the LGPL
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 3 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "tdb_private.h"
+
+/* all contexts, to ensure no double-opens (fcntl locks don't nest!) */
+static struct tdb_context *tdbs = NULL;
+
+/* We use two hashes to double-check they're using the right hash function. */
+void tdb_header_hash(struct tdb_context *tdb,
+ uint32_t *magic1_hash, uint32_t *magic2_hash)
+{
+ TDB_DATA hash_key;
+ uint32_t tdb_magic = TDB_MAGIC;
+
+ hash_key.dptr = discard_const_p(unsigned char, TDB_MAGIC_FOOD);
+ hash_key.dsize = sizeof(TDB_MAGIC_FOOD);
+ *magic1_hash = tdb->hash_fn(&hash_key);
+
+ hash_key.dptr = (unsigned char *)CONVERT(tdb_magic);
+ hash_key.dsize = sizeof(tdb_magic);
+ *magic2_hash = tdb->hash_fn(&hash_key);
+
+ /* Make sure at least one hash is non-zero! */
+ if (*magic1_hash == 0 && *magic2_hash == 0)
+ *magic1_hash = 1;
+}
+
+/* initialise a new database with a specified hash size */
+static int tdb_new_database(struct tdb_context *tdb, struct tdb_header *header,
+ int hash_size)
+{
+ struct tdb_header *newdb;
+ size_t size;
+ int ret = -1;
+
+ /* We make it up in memory, then write it out if not internal */
+ size = sizeof(struct tdb_header) + (hash_size+1)*sizeof(tdb_off_t);
+ if (!(newdb = (struct tdb_header *)calloc(size, 1))) {
+ tdb->ecode = TDB_ERR_OOM;
+ return -1;
+ }
+
+ /* Fill in the header */
+ newdb->version = TDB_VERSION;
+ newdb->hash_size = hash_size;
+
+ tdb_header_hash(tdb, &newdb->magic1_hash, &newdb->magic2_hash);
+
+ /* Make sure older tdbs (which don't check the magic hash fields)
+ * will refuse to open this TDB. */
+ if (tdb->flags & TDB_INCOMPATIBLE_HASH)
+ newdb->rwlocks = TDB_HASH_RWLOCK_MAGIC;
+
+ /*
+ * We create a tdb with TDB_FEATURE_FLAG_MUTEX support,
+ * the flag combination and runtime feature checks
+ * are done by the caller already.
+ */
+ if (tdb->flags & TDB_MUTEX_LOCKING) {
+ newdb->feature_flags |= TDB_FEATURE_FLAG_MUTEX;
+ }
+
+ /*
+ * If we have any features we add the FEATURE_FLAG_MAGIC, overwriting the
+ * TDB_HASH_RWLOCK_MAGIC above.
+ */
+ if (newdb->feature_flags != 0) {
+ newdb->rwlocks = TDB_FEATURE_FLAG_MAGIC;
+ }
+
+ /*
+ * It's required for some following code pathes
+ * to have the fields on 'tdb' up-to-date.
+ *
+ * E.g. tdb_mutex_size() requires it
+ */
+ tdb->feature_flags = newdb->feature_flags;
+ tdb->hash_size = newdb->hash_size;
+
+ if (tdb->flags & TDB_INTERNAL) {
+ tdb->map_size = size;
+ tdb->map_ptr = (char *)newdb;
+ memcpy(header, newdb, sizeof(*header));
+ /* Convert the `ondisk' version if asked. */
+ CONVERT(*newdb);
+ return 0;
+ }
+ if (lseek(tdb->fd, 0, SEEK_SET) == -1)
+ goto fail;
+
+ if (ftruncate(tdb->fd, 0) == -1)
+ goto fail;
+
+ if (newdb->feature_flags & TDB_FEATURE_FLAG_MUTEX) {
+ newdb->mutex_size = tdb_mutex_size(tdb);
+ tdb->hdr_ofs = newdb->mutex_size;
+ }
+
+ /* This creates an endian-converted header, as if read from disk */
+ CONVERT(*newdb);
+ memcpy(header, newdb, sizeof(*header));
+ /* Don't endian-convert the magic food! */
+ memcpy(newdb->magic_food, TDB_MAGIC_FOOD, strlen(TDB_MAGIC_FOOD)+1);
+
+ if (!tdb_write_all(tdb->fd, newdb, size))
+ goto fail;
+
+ if (newdb->feature_flags & TDB_FEATURE_FLAG_MUTEX) {
+
+ /*
+ * Now we init the mutex area
+ * followed by a second header.
+ */
+
+ ret = ftruncate(
+ tdb->fd,
+ newdb->mutex_size + sizeof(struct tdb_header));
+ if (ret == -1) {
+ goto fail;
+ }
+ ret = tdb_mutex_init(tdb);
+ if (ret == -1) {
+ goto fail;
+ }
+
+ /*
+ * Write a second header behind the mutexes. That's the area
+ * that will be mmapp'ed.
+ */
+ ret = lseek(tdb->fd, newdb->mutex_size, SEEK_SET);
+ if (ret == -1) {
+ goto fail;
+ }
+ if (!tdb_write_all(tdb->fd, newdb, size)) {
+ goto fail;
+ }
+ }
+
+ ret = 0;
+ fail:
+ SAFE_FREE(newdb);
+ return ret;
+}
+
+
+
+static int tdb_already_open(dev_t device,
+ ino_t ino)
+{
+ struct tdb_context *i;
+
+ for (i = tdbs; i; i = i->next) {
+ if (i->device == device && i->inode == ino) {
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+/* open the database, creating it if necessary
+
+ The open_flags and mode are passed straight to the open call on the
+ database file. A flags value of O_WRONLY is invalid. The hash size
+ is advisory, use zero for a default value.
+
+ Return is NULL on error, in which case errno is also set. Don't
+ try to call tdb_error or tdb_errname, just do strerror(errno).
+
+ @param name may be NULL for internal databases. */
+_PUBLIC_ struct tdb_context *tdb_open(const char *name, int hash_size, int tdb_flags,
+ int open_flags, mode_t mode)
+{
+ return tdb_open_ex(name, hash_size, tdb_flags, open_flags, mode, NULL, NULL);
+}
+
+/* a default logging function */
+static void null_log_fn(struct tdb_context *tdb, enum tdb_debug_level level, const char *fmt, ...) PRINTF_ATTRIBUTE(3, 4);
+static void null_log_fn(struct tdb_context *tdb, enum tdb_debug_level level, const char *fmt, ...)
+{
+}
+
+static bool check_header_hash(struct tdb_context *tdb,
+ struct tdb_header *header,
+ bool default_hash, uint32_t *m1, uint32_t *m2)
+{
+ tdb_header_hash(tdb, m1, m2);
+ if (header->magic1_hash == *m1 &&
+ header->magic2_hash == *m2) {
+ return true;
+ }
+
+ /* If they explicitly set a hash, always respect it. */
+ if (!default_hash)
+ return false;
+
+ /* Otherwise, try the other inbuilt hash. */
+ if (tdb->hash_fn == tdb_old_hash)
+ tdb->hash_fn = tdb_jenkins_hash;
+ else
+ tdb->hash_fn = tdb_old_hash;
+ return check_header_hash(tdb, header, false, m1, m2);
+}
+
+static bool tdb_mutex_open_ok(struct tdb_context *tdb,
+ const struct tdb_header *header)
+{
+ if (tdb->flags & TDB_NOLOCK) {
+ /*
+ * We don't look at locks, so it does not matter to have a
+ * compatible mutex implementation. Allow the open.
+ */
+ return true;
+ }
+
+ if (!(tdb->flags & TDB_MUTEX_LOCKING)) {
+ TDB_LOG((tdb, TDB_DEBUG_ERROR, "tdb_mutex_open_ok[%s]: "
+ "Can use mutexes only with "
+ "MUTEX_LOCKING or NOLOCK\n",
+ tdb->name));
+ return false;
+ }
+
+ if (tdb_mutex_size(tdb) != header->mutex_size) {
+ TDB_LOG((tdb, TDB_DEBUG_ERROR, "tdb_mutex_open_ok[%s]: "
+ "Mutex size changed from %"PRIu32" to %zu\n.",
+ tdb->name,
+ header->mutex_size,
+ tdb_mutex_size(tdb)));
+ return false;
+ }
+
+ return true;
+}
+
+_PUBLIC_ struct tdb_context *tdb_open_ex(const char *name, int hash_size, int tdb_flags,
+ int open_flags, mode_t mode,
+ const struct tdb_logging_context *log_ctx,
+ tdb_hash_func hash_fn)
+{
+ int orig_errno = errno;
+ struct tdb_header header = {
+ .version = 0,
+ };
+ struct tdb_context *tdb;
+ struct stat st;
+ int rev = 0;
+ bool locked = false;
+ unsigned char *vp;
+ uint32_t vertest;
+ unsigned v;
+ const char *hash_alg;
+ uint32_t magic1, magic2;
+ int ret;
+
+ if (!(tdb = (struct tdb_context *)calloc(1, sizeof *tdb))) {
+ /* Can't log this */
+ errno = ENOMEM;
+ goto fail;
+ }
+ tdb_io_init(tdb);
+
+ if (tdb_flags & TDB_INTERNAL) {
+ tdb_flags |= TDB_INCOMPATIBLE_HASH;
+ }
+ if (tdb_flags & TDB_MUTEX_LOCKING) {
+ tdb_flags |= TDB_INCOMPATIBLE_HASH;
+ }
+
+ tdb->fd = -1;
+#ifdef TDB_TRACE
+ tdb->tracefd = -1;
+#endif
+ tdb->name = NULL;
+ tdb->map_ptr = NULL;
+ tdb->flags = tdb_flags;
+ tdb->open_flags = open_flags;
+ if (log_ctx) {
+ tdb->log = *log_ctx;
+ } else {
+ tdb->log.log_fn = null_log_fn;
+ tdb->log.log_private = NULL;
+ }
+
+ if (name == NULL && (tdb_flags & TDB_INTERNAL)) {
+ name = "__TDB_INTERNAL__";
+ }
+
+ if (name == NULL) {
+ tdb->name = discard_const_p(char, "__NULL__");
+ TDB_LOG((tdb, TDB_DEBUG_FATAL, "tdb_open_ex: called with name == NULL\n"));
+ tdb->name = NULL;
+ errno = EINVAL;
+ goto fail;
+ }
+
+ /* now make a copy of the name, as the caller memory might go away */
+ if (!(tdb->name = (char *)strdup(name))) {
+ /*
+ * set the name as the given string, so that tdb_name() will
+ * work in case of an error.
+ */
+ tdb->name = discard_const_p(char, name);
+ TDB_LOG((tdb, TDB_DEBUG_ERROR, "tdb_open_ex: can't strdup(%s)\n",
+ name));
+ tdb->name = NULL;
+ errno = ENOMEM;
+ goto fail;
+ }
+
+ if (hash_fn) {
+ tdb->hash_fn = hash_fn;
+ hash_alg = "the user defined";
+ } else {
+ /* This controls what we use when creating a tdb. */
+ if (tdb->flags & TDB_INCOMPATIBLE_HASH) {
+ tdb->hash_fn = tdb_jenkins_hash;
+ } else {
+ tdb->hash_fn = tdb_old_hash;
+ }
+ hash_alg = "either default";
+ }
+
+ /* cache the page size */
+ tdb->page_size = getpagesize();
+ if (tdb->page_size <= 0) {
+ tdb->page_size = 0x2000;
+ }
+
+ tdb->max_dead_records = (tdb_flags & TDB_VOLATILE) ? 5 : 0;
+
+ if ((open_flags & O_ACCMODE) == O_WRONLY) {
+ TDB_LOG((tdb, TDB_DEBUG_ERROR, "tdb_open_ex: can't open tdb %s write-only\n",
+ name));
+ errno = EINVAL;
+ goto fail;
+ }
+
+ if (hash_size == 0)
+ hash_size = DEFAULT_HASH_SIZE;
+ if ((open_flags & O_ACCMODE) == O_RDONLY) {
+ tdb->read_only = 1;
+ /* read only databases don't do locking or clear if first */
+ tdb->flags |= TDB_NOLOCK;
+ tdb->flags &= ~(TDB_CLEAR_IF_FIRST|TDB_MUTEX_LOCKING);
+ }
+
+ if ((tdb->flags & TDB_ALLOW_NESTING) &&
+ (tdb->flags & TDB_DISALLOW_NESTING)) {
+ tdb->ecode = TDB_ERR_NESTING;
+ TDB_LOG((tdb, TDB_DEBUG_FATAL, "tdb_open_ex: "
+ "allow_nesting and disallow_nesting are not allowed together!"));
+ errno = EINVAL;
+ goto fail;
+ }
+
+ if (tdb->flags & TDB_MUTEX_LOCKING) {
+ /*
+ * Here we catch bugs in the callers,
+ * the runtime check for existing tdb's comes later.
+ */
+
+ if (tdb->flags & TDB_INTERNAL) {
+ TDB_LOG((tdb, TDB_DEBUG_ERROR, "tdb_open_ex: "
+ "invalid flags for %s - TDB_MUTEX_LOCKING and "
+ "TDB_INTERNAL are not allowed together\n", name));
+ errno = EINVAL;
+ goto fail;
+ }
+
+ if (tdb->flags & TDB_NOMMAP) {
+ TDB_LOG((tdb, TDB_DEBUG_ERROR, "tdb_open_ex: "
+ "invalid flags for %s - TDB_MUTEX_LOCKING and "
+ "TDB_NOMMAP are not allowed together\n", name));
+ errno = EINVAL;
+ goto fail;
+ }
+
+ if (tdb->read_only) {
+ TDB_LOG((tdb, TDB_DEBUG_ERROR, "tdb_open_ex: "
+ "invalid flags for %s - TDB_MUTEX_LOCKING "
+ "not allowed read only\n", name));
+ errno = EINVAL;
+ goto fail;
+ }
+
+ /*
+ * The callers should have called
+ * tdb_runtime_check_for_robust_mutexes()
+ * before using TDB_MUTEX_LOCKING!
+ *
+ * This makes sure the caller understands
+ * that the locking may behave a bit differently
+ * than with pure fcntl locking. E.g. multiple
+ * read locks are not supported.
+ */
+ if (!tdb_runtime_check_for_robust_mutexes()) {
+ TDB_LOG((tdb, TDB_DEBUG_ERROR, "tdb_open_ex: "
+ "invalid flags for %s - TDB_MUTEX_LOCKING "
+ "requires support for robust_mutexes\n",
+ name));
+ errno = ENOSYS;
+ goto fail;
+ }
+ }
+
+ if (getenv("TDB_NO_FSYNC")) {
+ tdb->flags |= TDB_NOSYNC;
+ }
+
+ /*
+ * TDB_ALLOW_NESTING is the default behavior.
+ * Note: this may change in future versions!
+ */
+ if (!(tdb->flags & TDB_DISALLOW_NESTING)) {
+ tdb->flags |= TDB_ALLOW_NESTING;
+ }
+
+ /* internal databases don't mmap or lock, and start off cleared */
+ if (tdb->flags & TDB_INTERNAL) {
+ tdb->flags |= (TDB_NOLOCK | TDB_NOMMAP);
+ tdb->flags &= ~TDB_CLEAR_IF_FIRST;
+ if (tdb_new_database(tdb, &header, hash_size) != 0) {
+ TDB_LOG((tdb, TDB_DEBUG_ERROR, "tdb_open_ex: tdb_new_database failed!"));
+ goto fail;
+ }
+ tdb->hash_size = hash_size;
+ goto internal;
+ }
+
+ if ((tdb->fd = open(name, open_flags, mode)) == -1) {
+ TDB_LOG((tdb, TDB_DEBUG_WARNING, "tdb_open_ex: could not open file %s: %s\n",
+ name, strerror(errno)));
+ goto fail; /* errno set by open(2) */
+ }
+
+ /* on exec, don't inherit the fd */
+ v = fcntl(tdb->fd, F_GETFD, 0);
+ fcntl(tdb->fd, F_SETFD, v | FD_CLOEXEC);
+
+ /* ensure there is only one process initialising at once */
+ if (tdb_nest_lock(tdb, OPEN_LOCK, F_WRLCK, TDB_LOCK_WAIT) == -1) {
+ TDB_LOG((tdb, TDB_DEBUG_ERROR, "tdb_open_ex: failed to get open lock on %s: %s\n",
+ name, strerror(errno)));
+ goto fail; /* errno set by tdb_brlock */
+ }
+
+ /* we need to zero database if we are the only one with it open */
+ if ((tdb_flags & TDB_CLEAR_IF_FIRST) &&
+ (!tdb->read_only)) {
+ ret = tdb_nest_lock(tdb, ACTIVE_LOCK, F_WRLCK,
+ TDB_LOCK_NOWAIT|TDB_LOCK_PROBE);
+ locked = (ret == 0);
+
+ if (locked) {
+ ret = tdb_brlock(tdb, F_WRLCK, FREELIST_TOP, 0,
+ TDB_LOCK_WAIT);
+ if (ret == -1) {
+ TDB_LOG((tdb, TDB_DEBUG_FATAL, "tdb_open_ex: "
+ "tdb_brlock failed for %s: %s\n",
+ name, strerror(errno)));
+ goto fail;
+ }
+ ret = tdb_new_database(tdb, &header, hash_size);
+ if (ret == -1) {
+ TDB_LOG((tdb, TDB_DEBUG_FATAL, "tdb_open_ex: "
+ "tdb_new_database failed for "
+ "%s: %s\n", name, strerror(errno)));
+ tdb_unlockall(tdb);
+ goto fail;
+ }
+ ret = tdb_brunlock(tdb, F_WRLCK, FREELIST_TOP, 0);
+ if (ret == -1) {
+ TDB_LOG((tdb, TDB_DEBUG_FATAL, "tdb_open_ex: "
+ "tdb_unlockall failed for %s: %s\n",
+ name, strerror(errno)));
+ goto fail;
+ }
+ ret = lseek(tdb->fd, 0, SEEK_SET);
+ if (ret == -1) {
+ TDB_LOG((tdb, TDB_DEBUG_FATAL, "tdb_open_ex: "
+ "lseek failed for %s: %s\n",
+ name, strerror(errno)));
+ goto fail;
+ }
+ }
+ }
+
+ errno = 0;
+ if (read(tdb->fd, &header, sizeof(header)) != sizeof(header)
+ || strcmp(header.magic_food, TDB_MAGIC_FOOD) != 0) {
+ if (!(open_flags & O_CREAT) ||
+ tdb_new_database(tdb, &header, hash_size) == -1) {
+ if (errno == 0) {
+ errno = EIO; /* ie bad format or something */
+ }
+ goto fail;
+ }
+ rev = (tdb->flags & TDB_CONVERT);
+ } else if (header.version != TDB_VERSION
+ && !(rev = (header.version==TDB_BYTEREV(TDB_VERSION)))) {
+ /* wrong version */
+ errno = EIO;
+ goto fail;
+ }
+ vp = (unsigned char *)&header.version;
+ vertest = (((uint32_t)vp[0]) << 24) | (((uint32_t)vp[1]) << 16) |
+ (((uint32_t)vp[2]) << 8) | (uint32_t)vp[3];
+ tdb->flags |= (vertest==TDB_VERSION) ? TDB_BIGENDIAN : 0;
+ if (!rev)
+ tdb->flags &= ~TDB_CONVERT;
+ else {
+ tdb->flags |= TDB_CONVERT;
+ tdb_convert(&header, sizeof(header));
+ }
+
+ /*
+ * We only use st.st_dev and st.st_ino from the raw fstat()
+ * call, everything else needs to use tdb_fstat() in order
+ * to skip tdb->hdr_ofs!
+ */
+ if (fstat(tdb->fd, &st) == -1) {
+ goto fail;
+ }
+ tdb->device = st.st_dev;
+ tdb->inode = st.st_ino;
+ ZERO_STRUCT(st);
+
+ if (header.rwlocks != 0 &&
+ header.rwlocks != TDB_FEATURE_FLAG_MAGIC &&
+ header.rwlocks != TDB_HASH_RWLOCK_MAGIC) {
+ TDB_LOG((tdb, TDB_DEBUG_ERROR, "tdb_open_ex: spinlocks no longer supported\n"));
+ errno = ENOSYS;
+ goto fail;
+ }
+
+ if (header.hash_size == 0) {
+ TDB_LOG((tdb, TDB_DEBUG_ERROR, "tdb_open_ex: invalid database: 0 hash_size\n"));
+ errno = ENOSYS;
+ goto fail;
+ }
+
+ tdb->hash_size = header.hash_size;
+
+ if (header.rwlocks == TDB_FEATURE_FLAG_MAGIC) {
+ tdb->feature_flags = header.feature_flags;
+ }
+
+ if (tdb->feature_flags & ~TDB_SUPPORTED_FEATURE_FLAGS) {
+ TDB_LOG((tdb, TDB_DEBUG_ERROR, "tdb_open_ex: unsupported "
+ "features in tdb %s: 0x%08x (supported: 0x%08x)\n",
+ name, (unsigned)tdb->feature_flags,
+ (unsigned)TDB_SUPPORTED_FEATURE_FLAGS));
+ errno = ENOSYS;
+ goto fail;
+ }
+
+ if (tdb->feature_flags & TDB_FEATURE_FLAG_MUTEX) {
+ if (!tdb_mutex_open_ok(tdb, &header)) {
+ errno = EINVAL;
+ goto fail;
+ }
+
+ /*
+ * We need to remember the hdr_ofs
+ * also for the TDB_NOLOCK case
+ * if the current library doesn't support
+ * mutex locking.
+ */
+ tdb->hdr_ofs = header.mutex_size;
+
+ if ((!(tdb_flags & TDB_CLEAR_IF_FIRST)) && (!tdb->read_only)) {
+ /*
+ * Open an existing mutexed tdb, but without
+ * CLEAR_IF_FIRST. We need to initialize the
+ * mutex array and keep the CLEAR_IF_FIRST
+ * lock locked.
+ */
+ ret = tdb_nest_lock(tdb, ACTIVE_LOCK, F_WRLCK,
+ TDB_LOCK_NOWAIT|TDB_LOCK_PROBE);
+ locked = (ret == 0);
+
+ if (locked) {
+ ret = tdb_mutex_init(tdb);
+ if (ret == -1) {
+ TDB_LOG((tdb,
+ TDB_DEBUG_FATAL,
+ "tdb_open_ex: tdb_mutex_init "
+ "failed for ""%s: %s\n",
+ name, strerror(errno)));
+ goto fail;
+ }
+ }
+ }
+ }
+
+ if ((header.magic1_hash == 0) && (header.magic2_hash == 0)) {
+ /* older TDB without magic hash references */
+ tdb->hash_fn = tdb_old_hash;
+ } else if (!check_header_hash(tdb, &header, !hash_fn,
+ &magic1, &magic2)) {
+ TDB_LOG((tdb, TDB_DEBUG_FATAL, "tdb_open_ex: "
+ "%s was not created with %s hash function we are using\n"
+ "magic1_hash[0x%08X %s 0x%08X] "
+ "magic2_hash[0x%08X %s 0x%08X]\n",
+ name, hash_alg,
+ header.magic1_hash,
+ (header.magic1_hash == magic1) ? "==" : "!=",
+ magic1,
+ header.magic2_hash,
+ (header.magic2_hash == magic2) ? "==" : "!=",
+ magic2));
+ errno = EINVAL;
+ goto fail;
+ }
+
+ /* Is it already in the open list? If so, fail. */
+ if (tdb_already_open(tdb->device, tdb->inode)) {
+ TDB_LOG((tdb, TDB_DEBUG_ERROR, "tdb_open_ex: "
+ "%s (%d,%d) is already open in this process\n",
+ name, (int)tdb->device, (int)tdb->inode));
+ errno = EBUSY;
+ goto fail;
+ }
+
+ /*
+ * We had tdb_mmap(tdb) here before,
+ * but we need to use tdb_fstat(),
+ * which is triggered from tdb_oob() before calling tdb_mmap().
+ * As this skips tdb->hdr_ofs.
+ */
+ tdb->map_size = 0;
+ ret = tdb_oob(tdb, 0, 1, 0);
+ if (ret == -1) {
+ errno = EIO;
+ goto fail;
+ }
+
+ if (tdb->feature_flags & TDB_FEATURE_FLAG_MUTEX) {
+ if (!(tdb->flags & TDB_NOLOCK)) {
+ ret = tdb_mutex_mmap(tdb);
+ if (ret != 0) {
+ goto fail;
+ }
+ }
+ }
+
+ if (tdb->hash_size > UINT32_MAX/4) {
+ TDB_LOG((tdb, TDB_DEBUG_FATAL, "tdb_open_ex: "
+ "hash size %"PRIu32" too large\n", tdb->hash_size));
+ errno = EINVAL;
+ goto fail;
+ }
+
+ ret = tdb_oob(tdb, FREELIST_TOP, 4*tdb->hash_size, 1);
+ if (ret == -1) {
+ TDB_LOG((tdb, TDB_DEBUG_FATAL, "tdb_open_ex: "
+ "hash size %"PRIu32" does not fit\n", tdb->hash_size));
+ errno = EINVAL;
+ goto fail;
+ }
+
+ if (locked) {
+ if (tdb_nest_unlock(tdb, ACTIVE_LOCK, F_WRLCK, false) == -1) {
+ TDB_LOG((tdb, TDB_DEBUG_ERROR, "tdb_open_ex: "
+ "failed to release ACTIVE_LOCK on %s: %s\n",
+ name, strerror(errno)));
+ goto fail;
+ }
+
+
+ }
+
+ if (locked || (tdb_flags & TDB_CLEAR_IF_FIRST)) {
+ /*
+ * We always need to do this if the CLEAR_IF_FIRST
+ * flag is set, even if we didn't get the initial
+ * exclusive lock as we need to let all other users
+ * know we're using it.
+ */
+
+ ret = tdb_nest_lock(tdb, ACTIVE_LOCK, F_RDLCK, TDB_LOCK_WAIT);
+ if (ret == -1) {
+ goto fail;
+ }
+ }
+
+ /* if needed, run recovery */
+ if (tdb_transaction_recover(tdb) == -1) {
+ goto fail;
+ }
+
+#ifdef TDB_TRACE
+ {
+ char tracefile[strlen(name) + 32];
+
+ snprintf(tracefile, sizeof(tracefile),
+ "%s.trace.%li", name, (long)getpid());
+ tdb->tracefd = open(tracefile, O_WRONLY|O_CREAT|O_EXCL, 0600);
+ if (tdb->tracefd >= 0) {
+ tdb_enable_seqnum(tdb);
+ tdb_trace_open(tdb, "tdb_open", hash_size, tdb_flags,
+ open_flags);
+ } else
+ TDB_LOG((tdb, TDB_DEBUG_ERROR, "tdb_open_ex: failed to open trace file %s!\n", tracefile));
+ }
+#endif
+
+ internal:
+ /* Internal (memory-only) databases skip all the code above to
+ * do with disk files, and resume here by releasing their
+ * open lock and hooking into the active list. */
+ if (tdb_nest_unlock(tdb, OPEN_LOCK, F_WRLCK, false) == -1) {
+ goto fail;
+ }
+ tdb->next = tdbs;
+ tdbs = tdb;
+ errno = orig_errno;
+ return tdb;
+
+ fail:
+ { int save_errno = errno;
+
+ if (!tdb)
+ return NULL;
+
+#ifdef TDB_TRACE
+ close(tdb->tracefd);
+#endif
+ if (tdb->map_ptr) {
+ if (tdb->flags & TDB_INTERNAL)
+ SAFE_FREE(tdb->map_ptr);
+ else
+ tdb_munmap(tdb);
+ }
+ if (tdb->fd != -1)
+ if (close(tdb->fd) != 0)
+ TDB_LOG((tdb, TDB_DEBUG_ERROR, "tdb_open_ex: failed to close tdb->fd on error!\n"));
+ SAFE_FREE(tdb->lockrecs);
+ SAFE_FREE(tdb->name);
+ SAFE_FREE(tdb);
+ errno = save_errno;
+ return NULL;
+ }
+}
+
+/*
+ * Set the maximum number of dead records per hash chain
+ */
+
+_PUBLIC_ void tdb_set_max_dead(struct tdb_context *tdb, int max_dead)
+{
+ tdb->max_dead_records = max_dead;
+}
+
+/**
+ * Close a database.
+ *
+ * @returns -1 for error; 0 for success.
+ **/
+_PUBLIC_ int tdb_close(struct tdb_context *tdb)
+{
+ struct tdb_context **i;
+ int ret = 0;
+
+ if (tdb->transaction) {
+ tdb_transaction_cancel(tdb);
+ }
+ tdb_trace(tdb, "tdb_close");
+
+ if (tdb->map_ptr) {
+ if (tdb->flags & TDB_INTERNAL)
+ SAFE_FREE(tdb->map_ptr);
+ else
+ tdb_munmap(tdb);
+ }
+
+ tdb_mutex_munmap(tdb);
+
+ SAFE_FREE(tdb->name);
+ if (tdb->fd != -1) {
+ ret = close(tdb->fd);
+ tdb->fd = -1;
+ }
+ SAFE_FREE(tdb->lockrecs);
+
+ /* Remove from contexts list */
+ for (i = &tdbs; *i; i = &(*i)->next) {
+ if (*i == tdb) {
+ *i = tdb->next;
+ break;
+ }
+ }
+
+#ifdef TDB_TRACE
+ close(tdb->tracefd);
+#endif
+ memset(tdb, 0, sizeof(*tdb));
+ SAFE_FREE(tdb);
+
+ return ret;
+}
+
+/* register a loging function */
+_PUBLIC_ void tdb_set_logging_function(struct tdb_context *tdb,
+ const struct tdb_logging_context *log_ctx)
+{
+ tdb->log = *log_ctx;
+}
+
+_PUBLIC_ void *tdb_get_logging_private(struct tdb_context *tdb)
+{
+ return tdb->log.log_private;
+}
+
+static int tdb_reopen_internal(struct tdb_context *tdb, bool active_lock)
+{
+#if !defined(LIBREPLACE_PREAD_NOT_REPLACED) || \
+ !defined(LIBREPLACE_PWRITE_NOT_REPLACED)
+ struct stat st;
+#endif
+
+ if (tdb->flags & TDB_INTERNAL) {
+ return 0; /* Nothing to do. */
+ }
+
+ if (tdb_have_extra_locks(tdb)) {
+ TDB_LOG((tdb, TDB_DEBUG_ERROR, "tdb_reopen: reopen not allowed with locks held\n"));
+ goto fail;
+ }
+
+ if (tdb->transaction != 0) {
+ TDB_LOG((tdb, TDB_DEBUG_ERROR, "tdb_reopen: reopen not allowed inside a transaction\n"));
+ goto fail;
+ }
+
+/* If we have real pread & pwrite, we can skip reopen. */
+#if !defined(LIBREPLACE_PREAD_NOT_REPLACED) || \
+ !defined(LIBREPLACE_PWRITE_NOT_REPLACED)
+ if (tdb_munmap(tdb) != 0) {
+ TDB_LOG((tdb, TDB_DEBUG_FATAL, "tdb_reopen: munmap failed (%s)\n", strerror(errno)));
+ goto fail;
+ }
+ if (close(tdb->fd) != 0)
+ TDB_LOG((tdb, TDB_DEBUG_FATAL, "tdb_reopen: WARNING closing tdb->fd failed!\n"));
+ tdb->fd = open(tdb->name, tdb->open_flags & ~(O_CREAT|O_TRUNC), 0);
+ if (tdb->fd == -1) {
+ TDB_LOG((tdb, TDB_DEBUG_FATAL, "tdb_reopen: open failed (%s)\n", strerror(errno)));
+ goto fail;
+ }
+ /*
+ * We only use st.st_dev and st.st_ino from the raw fstat()
+ * call, everything else needs to use tdb_fstat() in order
+ * to skip tdb->hdr_ofs!
+ */
+ if (fstat(tdb->fd, &st) != 0) {
+ TDB_LOG((tdb, TDB_DEBUG_FATAL, "tdb_reopen: fstat failed (%s)\n", strerror(errno)));
+ goto fail;
+ }
+ if (st.st_ino != tdb->inode || st.st_dev != tdb->device) {
+ TDB_LOG((tdb, TDB_DEBUG_FATAL, "tdb_reopen: file dev/inode has changed!\n"));
+ goto fail;
+ }
+ ZERO_STRUCT(st);
+
+ /*
+ * We had tdb_mmap(tdb) here before,
+ * but we need to use tdb_fstat(),
+ * which is triggered from tdb_oob() before calling tdb_mmap().
+ * As this skips tdb->hdr_ofs.
+ */
+ tdb->map_size = 0;
+ if (tdb_oob(tdb, 0, 1, 0) != 0) {
+ goto fail;
+ }
+#endif /* fake pread or pwrite */
+
+ /* We may still think we hold the active lock. */
+ tdb->num_lockrecs = 0;
+ SAFE_FREE(tdb->lockrecs);
+ tdb->lockrecs_array_length = 0;
+
+ if (active_lock && tdb_nest_lock(tdb, ACTIVE_LOCK, F_RDLCK, TDB_LOCK_WAIT) == -1) {
+ TDB_LOG((tdb, TDB_DEBUG_FATAL, "tdb_reopen: failed to obtain active lock\n"));
+ goto fail;
+ }
+
+ return 0;
+
+fail:
+ tdb_close(tdb);
+ return -1;
+}
+
+/* reopen a tdb - this can be used after a fork to ensure that we have an independent
+ seek pointer from our parent and to re-establish locks */
+_PUBLIC_ int tdb_reopen(struct tdb_context *tdb)
+{
+ bool active_lock;
+ active_lock = (tdb->flags & (TDB_CLEAR_IF_FIRST|TDB_MUTEX_LOCKING));
+
+ return tdb_reopen_internal(tdb, active_lock);
+}
+
+/* reopen all tdb's */
+_PUBLIC_ int tdb_reopen_all(int parent_longlived)
+{
+ struct tdb_context *tdb;
+
+ for (tdb=tdbs; tdb; tdb = tdb->next) {
+ bool active_lock;
+
+ active_lock =
+ (tdb->flags & (TDB_CLEAR_IF_FIRST|TDB_MUTEX_LOCKING));
+
+ /*
+ * If the parent is longlived (ie. a
+ * parent daemon architecture), we know
+ * it will keep it's active lock on a
+ * tdb opened with CLEAR_IF_FIRST. Thus
+ * for child processes we don't have to
+ * add an active lock. This is essential
+ * to improve performance on systems that
+ * keep POSIX locks as a non-scalable data
+ * structure in the kernel.
+ */
+ if (parent_longlived) {
+ /* Ensure no clear-if-first. */
+ active_lock = false;
+ }
+
+ if (tdb_reopen_internal(tdb, active_lock) != 0)
+ return -1;
+ }
+
+ return 0;
+}
diff --git a/lib/tdb/common/rescue.c b/lib/tdb/common/rescue.c
new file mode 100644
index 0000000..7a85ebc
--- /dev/null
+++ b/lib/tdb/common/rescue.c
@@ -0,0 +1,351 @@
+ /*
+ Unix SMB/CIFS implementation.
+
+ trivial database library, rescue attempt code.
+
+ Copyright (C) Rusty Russell 2012
+
+ ** NOTE! The following LGPL license applies to the tdb
+ ** library. This does NOT imply that all of Samba is released
+ ** under the LGPL
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 3 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, see <http://www.gnu.org/licenses/>.
+*/
+#include "tdb_private.h"
+#include <assert.h>
+
+
+struct found {
+ tdb_off_t head; /* 0 -> invalid. */
+ struct tdb_record rec;
+ TDB_DATA key;
+ bool in_hash;
+ bool in_free;
+};
+
+struct found_table {
+ /* As an ordered array (by head offset). */
+ struct found *arr;
+ unsigned int num, max;
+};
+
+static bool looks_like_valid_record(struct tdb_context *tdb,
+ tdb_off_t off,
+ const struct tdb_record *rec,
+ TDB_DATA *key)
+{
+ unsigned int hval;
+
+ if (rec->magic != TDB_MAGIC)
+ return false;
+
+ if (rec->key_len + rec->data_len > rec->rec_len)
+ return false;
+
+ if (rec->rec_len % TDB_ALIGNMENT)
+ return false;
+
+ /* Next pointer must make some sense. */
+ if (rec->next > 0 && rec->next < TDB_DATA_START(tdb->hash_size))
+ return false;
+
+ if (tdb_oob(tdb, rec->next, sizeof(*rec), 1))
+ return false;
+
+ key->dsize = rec->key_len;
+ key->dptr = tdb_alloc_read(tdb, off + sizeof(*rec), key->dsize);
+ if (!key->dptr)
+ return false;
+
+ hval = tdb->hash_fn(key);
+ if (hval != rec->full_hash) {
+ free(key->dptr);
+ return false;
+ }
+
+ /* Caller frees up key->dptr */
+ return true;
+}
+
+static bool add_to_table(struct found_table *found,
+ tdb_off_t off,
+ struct tdb_record *rec,
+ TDB_DATA key)
+{
+ if (found->num + 1 > found->max) {
+ struct found *new;
+ found->max = (found->max ? found->max * 2 : 128);
+ new = realloc(found->arr, found->max * sizeof(found->arr[0]));
+ if (!new)
+ return false;
+ found->arr = new;
+ }
+
+ found->arr[found->num].head = off;
+ found->arr[found->num].rec = *rec;
+ found->arr[found->num].key = key;
+ found->arr[found->num].in_hash = false;
+ found->arr[found->num].in_free = false;
+
+ found->num++;
+ return true;
+}
+
+static bool walk_record(struct tdb_context *tdb,
+ const struct found *f,
+ void (*walk)(TDB_DATA, TDB_DATA, void *private_data),
+ void *private_data)
+{
+ TDB_DATA data;
+
+ data.dsize = f->rec.data_len;
+ data.dptr = tdb_alloc_read(tdb,
+ f->head + sizeof(f->rec) + f->rec.key_len,
+ data.dsize);
+ if (!data.dptr) {
+ if (tdb->ecode == TDB_ERR_OOM)
+ return false;
+ /* I/O errors are expected. */
+ return true;
+ }
+
+ walk(f->key, data, private_data);
+ free(data.dptr);
+ return true;
+}
+
+/* First entry which has offset >= this one. */
+static unsigned int find_entry(struct found_table *found, tdb_off_t off)
+{
+ unsigned int start = 0, end = found->num;
+
+ while (start < end) {
+ /* We can't overflow here. */
+ unsigned int mid = (start + end) / 2;
+
+ if (off < found->arr[mid].head) {
+ end = mid;
+ } else if (off > found->arr[mid].head) {
+ start = mid + 1;
+ } else {
+ return mid;
+ }
+ }
+
+ assert(start == end);
+ return end;
+}
+
+static void found_in_hashchain(struct found_table *found, tdb_off_t head)
+{
+ unsigned int match;
+
+ match = find_entry(found, head);
+ if (match < found->num && found->arr[match].head == head) {
+ found->arr[match].in_hash = true;
+ }
+}
+
+static void mark_free_area(struct found_table *found, tdb_off_t head,
+ tdb_len_t len)
+{
+ unsigned int match;
+
+ match = find_entry(found, head);
+ /* Mark everything within this free entry. */
+ while (match < found->num) {
+ if (found->arr[match].head >= head + len) {
+ break;
+ }
+ found->arr[match].in_free = true;
+ match++;
+ }
+}
+
+static int cmp_key(const void *a, const void *b)
+{
+ const struct found *fa = a, *fb = b;
+
+ if (fa->key.dsize < fb->key.dsize) {
+ return -1;
+ } else if (fa->key.dsize > fb->key.dsize) {
+ return 1;
+ }
+ return memcmp(fa->key.dptr, fb->key.dptr, fa->key.dsize);
+}
+
+static bool key_eq(TDB_DATA a, TDB_DATA b)
+{
+ return a.dsize == b.dsize
+ && memcmp(a.dptr, b.dptr, a.dsize) == 0;
+}
+
+static void free_table(struct found_table *found)
+{
+ unsigned int i;
+
+ for (i = 0; i < found->num; i++) {
+ free(found->arr[i].key.dptr);
+ }
+ free(found->arr);
+}
+
+static void logging_suppressed(struct tdb_context *tdb,
+ enum tdb_debug_level level, const char *fmt, ...)
+{
+}
+
+_PUBLIC_ int tdb_rescue(struct tdb_context *tdb,
+ void (*walk)(TDB_DATA, TDB_DATA, void *private_data),
+ void *private_data)
+{
+ struct found_table found = { NULL, 0, 0 };
+ tdb_off_t h, off, i;
+ tdb_log_func oldlog = tdb->log.log_fn;
+ struct tdb_record rec;
+ TDB_DATA key;
+ bool locked;
+
+ /* Read-only databases use no locking at all: it's best-effort.
+ * We may have a write lock already, so skip that case too. */
+ if (tdb->read_only || tdb->allrecord_lock.count != 0) {
+ locked = false;
+ } else {
+ if (tdb_lockall_read(tdb) == -1)
+ return -1;
+ locked = true;
+ }
+
+ /* Make sure we know true size of the underlying file. */
+ tdb_oob(tdb, tdb->map_size, 1, 1);
+
+ /* Suppress logging, since we anticipate errors. */
+ tdb->log.log_fn = logging_suppressed;
+
+ /* Now walk entire db looking for records. */
+ for (off = TDB_DATA_START(tdb->hash_size);
+ off < tdb->map_size;
+ off += TDB_ALIGNMENT) {
+ if (tdb->methods->tdb_read(tdb, off, &rec, sizeof(rec),
+ DOCONV()) == -1)
+ continue;
+
+ if (looks_like_valid_record(tdb, off, &rec, &key)) {
+ if (!add_to_table(&found, off, &rec, key)) {
+ goto oom;
+ }
+ }
+ }
+
+ /* Walk hash chains to positive vet. */
+ for (h = 0; h < 1+tdb->hash_size; h++) {
+ bool slow_chase = false;
+ tdb_off_t slow_off = FREELIST_TOP + h*sizeof(tdb_off_t);
+
+ if (tdb_ofs_read(tdb, FREELIST_TOP + h*sizeof(tdb_off_t),
+ &off) == -1)
+ continue;
+
+ while (off && off != slow_off) {
+ if (tdb->methods->tdb_read(tdb, off, &rec, sizeof(rec),
+ DOCONV()) != 0) {
+ break;
+ }
+
+ /* 0 is the free list, rest are hash chains. */
+ if (h == 0) {
+ /* Don't mark garbage as free. */
+ if (rec.magic != TDB_FREE_MAGIC) {
+ break;
+ }
+ mark_free_area(&found, off,
+ sizeof(rec) + rec.rec_len);
+ } else {
+ found_in_hashchain(&found, off);
+ }
+
+ off = rec.next;
+
+ /* Loop detection using second pointer at half-speed */
+ if (slow_chase) {
+ /* First entry happens to be next ptr */
+ tdb_ofs_read(tdb, slow_off, &slow_off);
+ }
+ slow_chase = !slow_chase;
+ }
+ }
+
+ /* Recovery area: must be marked as free, since it often has old
+ * records in there! */
+ if (tdb_ofs_read(tdb, TDB_RECOVERY_HEAD, &off) == 0 && off != 0) {
+ if (tdb->methods->tdb_read(tdb, off, &rec, sizeof(rec),
+ DOCONV()) == 0) {
+ mark_free_area(&found, off, sizeof(rec) + rec.rec_len);
+ }
+ }
+
+ /* Now sort by key! */
+ if (found.arr != NULL) {
+ qsort(found.arr, found.num, sizeof(found.arr[0]), cmp_key);
+ }
+
+ for (i = 0; (found.arr != NULL) && i < found.num; ) {
+ unsigned int num, num_in_hash = 0;
+
+ /* How many are identical? */
+ for (num = 0; num < found.num - i; num++) {
+ if (!key_eq(found.arr[i].key, found.arr[i+num].key)) {
+ break;
+ }
+ if (found.arr[i+num].in_hash) {
+ if (!walk_record(tdb, &found.arr[i+num],
+ walk, private_data))
+ goto oom;
+ num_in_hash++;
+ }
+ }
+ assert(num);
+
+ /* If none were in the hash, we print any not in free list. */
+ if (num_in_hash == 0) {
+ unsigned int j;
+
+ for (j = i; j < i + num; j++) {
+ if (!found.arr[j].in_free) {
+ if (!walk_record(tdb, &found.arr[j],
+ walk, private_data))
+ goto oom;
+ }
+ }
+ }
+
+ i += num;
+ }
+
+ tdb->log.log_fn = oldlog;
+ if (locked) {
+ tdb_unlockall_read(tdb);
+ }
+ return 0;
+
+oom:
+ tdb->log.log_fn = oldlog;
+ tdb->ecode = TDB_ERR_OOM;
+ TDB_LOG((tdb, TDB_DEBUG_ERROR, "tdb_rescue: failed allocating\n"));
+ free_table(&found);
+ if (locked) {
+ tdb_unlockall_read(tdb);
+ }
+ return -1;
+}
diff --git a/lib/tdb/common/summary.c b/lib/tdb/common/summary.c
new file mode 100644
index 0000000..a93eb93
--- /dev/null
+++ b/lib/tdb/common/summary.c
@@ -0,0 +1,219 @@
+ /*
+ Trivial Database: human-readable summary code
+ Copyright (C) Rusty Russell 2010
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 3 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, see <http://www.gnu.org/licenses/>.
+*/
+#include "tdb_private.h"
+
+#define SUMMARY_FORMAT \
+ "Size of file/data: %llu/%zu\n" \
+ "Header offset/logical size: %zu/%zu\n" \
+ "Number of records: %zu\n" \
+ "Incompatible hash: %s\n" \
+ "Active/supported feature flags: 0x%08x/0x%08x\n" \
+ "Robust mutexes locking: %s\n" \
+ "Smallest/average/largest keys: %zu/%zu/%zu\n" \
+ "Smallest/average/largest data: %zu/%zu/%zu\n" \
+ "Smallest/average/largest padding: %zu/%zu/%zu\n" \
+ "Number of dead records: %zu\n" \
+ "Smallest/average/largest dead records: %zu/%zu/%zu\n" \
+ "Number of free records: %zu\n" \
+ "Smallest/average/largest free records: %zu/%zu/%zu\n" \
+ "Number of hash chains: %zu\n" \
+ "Smallest/average/largest hash chains: %zu/%zu/%zu\n" \
+ "Number of uncoalesced records: %zu\n" \
+ "Smallest/average/largest uncoalesced runs: %zu/%zu/%zu\n" \
+ "Percentage keys/data/padding/free/dead/rechdrs&tailers/hashes: %.0f/%.0f/%.0f/%.0f/%.0f/%.0f/%.0f\n"
+
+/* We don't use tally module, to keep upstream happy. */
+struct tally {
+ size_t min, max, total;
+ size_t num;
+};
+
+static void tally_init(struct tally *tally)
+{
+ tally->total = 0;
+ tally->num = 0;
+ tally->min = tally->max = 0;
+}
+
+static void tally_add(struct tally *tally, size_t len)
+{
+ if (tally->num == 0)
+ tally->max = tally->min = len;
+ else if (len > tally->max)
+ tally->max = len;
+ else if (len < tally->min)
+ tally->min = len;
+ tally->num++;
+ tally->total += len;
+}
+
+static size_t tally_mean(const struct tally *tally)
+{
+ if (!tally->num)
+ return 0;
+ return tally->total / tally->num;
+}
+
+static size_t get_hash_length(struct tdb_context *tdb, unsigned int i)
+{
+ tdb_off_t rec_ptr;
+ struct tdb_chainwalk_ctx chainwalk;
+ size_t count = 0;
+
+ if (tdb_ofs_read(tdb, TDB_HASH_TOP(i), &rec_ptr) == -1)
+ return 0;
+
+ tdb_chainwalk_init(&chainwalk, rec_ptr);
+
+ /* keep looking until we find the right record */
+ while (rec_ptr) {
+ struct tdb_record r;
+ bool ok;
+ ++count;
+ if (tdb_rec_read(tdb, rec_ptr, &r) == -1)
+ return 0;
+ rec_ptr = r.next;
+ ok = tdb_chainwalk_check(tdb, &chainwalk, rec_ptr);
+ if (!ok) {
+ return SIZE_MAX;
+ }
+ }
+ return count;
+}
+
+_PUBLIC_ char *tdb_summary(struct tdb_context *tdb)
+{
+ off_t file_size;
+ tdb_off_t off, rec_off;
+ struct tally freet, keys, data, dead, extra, hashval, uncoal;
+ struct tdb_record rec;
+ char *ret = NULL;
+ bool locked;
+ size_t unc = 0;
+ int len;
+ struct tdb_record recovery;
+
+ /* Read-only databases use no locking at all: it's best-effort.
+ * We may have a write lock already, so skip that case too. */
+ if (tdb->read_only || tdb->allrecord_lock.count != 0) {
+ locked = false;
+ } else {
+ if (tdb_lockall_read(tdb) == -1)
+ return NULL;
+ locked = true;
+ }
+
+ if (tdb_recovery_area(tdb, tdb->methods, &rec_off, &recovery) != 0) {
+ goto unlock;
+ }
+
+ tally_init(&freet);
+ tally_init(&keys);
+ tally_init(&data);
+ tally_init(&dead);
+ tally_init(&extra);
+ tally_init(&hashval);
+ tally_init(&uncoal);
+
+ for (off = TDB_DATA_START(tdb->hash_size);
+ off < tdb->map_size - 1;
+ off += sizeof(rec) + rec.rec_len) {
+ if (tdb->methods->tdb_read(tdb, off, &rec, sizeof(rec),
+ DOCONV()) == -1)
+ goto unlock;
+ switch (rec.magic) {
+ case TDB_MAGIC:
+ tally_add(&keys, rec.key_len);
+ tally_add(&data, rec.data_len);
+ tally_add(&extra, rec.rec_len - (rec.key_len
+ + rec.data_len));
+ if (unc > 1)
+ tally_add(&uncoal, unc - 1);
+ unc = 0;
+ break;
+ case TDB_FREE_MAGIC:
+ tally_add(&freet, rec.rec_len);
+ unc++;
+ break;
+ /* If we crash after ftruncate, we can get zeroes or fill. */
+ case TDB_RECOVERY_INVALID_MAGIC:
+ case 0x42424242:
+ unc++;
+ /* If it's a valid recovery, we can trust rec_len. */
+ if (off != rec_off) {
+ rec.rec_len = tdb_dead_space(tdb, off)
+ - sizeof(rec);
+ }
+
+ FALL_THROUGH;
+ case TDB_DEAD_MAGIC:
+ tally_add(&dead, rec.rec_len);
+ break;
+ default:
+ TDB_LOG((tdb, TDB_DEBUG_ERROR,
+ "Unexpected record magic 0x%x at offset %u\n",
+ rec.magic, off));
+ goto unlock;
+ }
+ }
+ if (unc > 1)
+ tally_add(&uncoal, unc - 1);
+
+ for (off = 0; off < tdb->hash_size; off++)
+ tally_add(&hashval, get_hash_length(tdb, off));
+
+ file_size = tdb->hdr_ofs + tdb->map_size;
+
+ len = asprintf(&ret, SUMMARY_FORMAT,
+ (unsigned long long)file_size, keys.total+data.total,
+ (size_t)tdb->hdr_ofs, (size_t)tdb->map_size,
+ keys.num,
+ (tdb->hash_fn == tdb_jenkins_hash)?"yes":"no",
+ (unsigned)tdb->feature_flags, TDB_SUPPORTED_FEATURE_FLAGS,
+ (tdb->feature_flags & TDB_FEATURE_FLAG_MUTEX)?"yes":"no",
+ keys.min, tally_mean(&keys), keys.max,
+ data.min, tally_mean(&data), data.max,
+ extra.min, tally_mean(&extra), extra.max,
+ dead.num,
+ dead.min, tally_mean(&dead), dead.max,
+ freet.num,
+ freet.min, tally_mean(&freet), freet.max,
+ hashval.num,
+ hashval.min, tally_mean(&hashval), hashval.max,
+ uncoal.total,
+ uncoal.min, tally_mean(&uncoal), uncoal.max,
+ keys.total * 100.0 / file_size,
+ data.total * 100.0 / file_size,
+ extra.total * 100.0 / file_size,
+ freet.total * 100.0 / file_size,
+ dead.total * 100.0 / file_size,
+ (keys.num + freet.num + dead.num)
+ * (sizeof(struct tdb_record) + sizeof(uint32_t))
+ * 100.0 / file_size,
+ tdb->hash_size * sizeof(tdb_off_t)
+ * 100.0 / file_size);
+ if (len == -1) {
+ goto unlock;
+ }
+
+unlock:
+ if (locked) {
+ tdb_unlockall_read(tdb);
+ }
+ return ret;
+}
diff --git a/lib/tdb/common/tdb.c b/lib/tdb/common/tdb.c
new file mode 100644
index 0000000..de829bb
--- /dev/null
+++ b/lib/tdb/common/tdb.c
@@ -0,0 +1,1348 @@
+ /*
+ Unix SMB/CIFS implementation.
+
+ trivial database library
+
+ Copyright (C) Andrew Tridgell 1999-2005
+ Copyright (C) Paul `Rusty' Russell 2000
+ Copyright (C) Jeremy Allison 2000-2003
+
+ ** NOTE! The following LGPL license applies to the tdb
+ ** library. This does NOT imply that all of Samba is released
+ ** under the LGPL
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 3 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "tdb_private.h"
+
+_PUBLIC_ TDB_DATA tdb_null;
+
+/*
+ non-blocking increment of the tdb sequence number if the tdb has been opened using
+ the TDB_SEQNUM flag
+*/
+_PUBLIC_ void tdb_increment_seqnum_nonblock(struct tdb_context *tdb)
+{
+ tdb_off_t seqnum=0;
+
+ if (!(tdb->flags & TDB_SEQNUM)) {
+ return;
+ }
+
+ /* we ignore errors from this, as we have no sane way of
+ dealing with them.
+ */
+ tdb_ofs_read(tdb, TDB_SEQNUM_OFS, &seqnum);
+ seqnum++;
+ tdb_ofs_write(tdb, TDB_SEQNUM_OFS, &seqnum);
+}
+
+/*
+ increment the tdb sequence number if the tdb has been opened using
+ the TDB_SEQNUM flag
+*/
+static void tdb_increment_seqnum(struct tdb_context *tdb)
+{
+ if (!(tdb->flags & TDB_SEQNUM)) {
+ return;
+ }
+
+ if (tdb->transaction != NULL) {
+ tdb_increment_seqnum_nonblock(tdb);
+ return;
+ }
+
+#if defined(HAVE___ATOMIC_ADD_FETCH) && defined(HAVE___ATOMIC_ADD_LOAD)
+ if (tdb->map_ptr != NULL) {
+ uint32_t *pseqnum = (uint32_t *)(
+ TDB_SEQNUM_OFS + (char *)tdb->map_ptr);
+ __atomic_add_fetch(pseqnum, 1, __ATOMIC_SEQ_CST);
+ return;
+ }
+#endif
+
+ if (tdb_nest_lock(tdb, TDB_SEQNUM_OFS, F_WRLCK,
+ TDB_LOCK_WAIT|TDB_LOCK_PROBE) != 0) {
+ return;
+ }
+
+ tdb_increment_seqnum_nonblock(tdb);
+
+ tdb_nest_unlock(tdb, TDB_SEQNUM_OFS, F_WRLCK, false);
+}
+
+static int tdb_key_compare(TDB_DATA key, TDB_DATA data, void *private_data)
+{
+ return memcmp(data.dptr, key.dptr, data.dsize);
+}
+
+void tdb_chainwalk_init(struct tdb_chainwalk_ctx *ctx, tdb_off_t ptr)
+{
+ *ctx = (struct tdb_chainwalk_ctx) { .slow_ptr = ptr };
+}
+
+bool tdb_chainwalk_check(struct tdb_context *tdb,
+ struct tdb_chainwalk_ctx *ctx,
+ tdb_off_t next_ptr)
+{
+ int ret;
+
+ if (ctx->slow_chase) {
+ ret = tdb_ofs_read(tdb, ctx->slow_ptr, &ctx->slow_ptr);
+ if (ret == -1) {
+ return false;
+ }
+ }
+ ctx->slow_chase = !ctx->slow_chase;
+
+ if (next_ptr == ctx->slow_ptr) {
+ tdb->ecode = TDB_ERR_CORRUPT;
+ TDB_LOG((tdb, TDB_DEBUG_ERROR,
+ "tdb_chainwalk_check: circular chain\n"));
+ return false;
+ }
+
+ return true;
+}
+
+/* Returns 0 on fail. On success, return offset of record, and fills
+ in rec */
+static tdb_off_t tdb_find(struct tdb_context *tdb, TDB_DATA key, uint32_t hash,
+ struct tdb_record *r)
+{
+ tdb_off_t rec_ptr;
+ struct tdb_chainwalk_ctx chainwalk;
+
+ /* read in the hash top */
+ if (tdb_ofs_read(tdb, TDB_HASH_TOP(hash), &rec_ptr) == -1)
+ return 0;
+
+ tdb_chainwalk_init(&chainwalk, rec_ptr);
+
+ /* keep looking until we find the right record */
+ while (rec_ptr) {
+ bool ok;
+
+ if (tdb_rec_read(tdb, rec_ptr, r) == -1)
+ return 0;
+
+ if (!TDB_DEAD(r) && hash==r->full_hash
+ && key.dsize==r->key_len
+ && tdb_parse_data(tdb, key, rec_ptr + sizeof(*r),
+ r->key_len, tdb_key_compare,
+ NULL) == 0) {
+ return rec_ptr;
+ }
+ rec_ptr = r->next;
+
+ ok = tdb_chainwalk_check(tdb, &chainwalk, rec_ptr);
+ if (!ok) {
+ return 0;
+ }
+ }
+ tdb->ecode = TDB_ERR_NOEXIST;
+ return 0;
+}
+
+/* As tdb_find, but if you succeed, keep the lock */
+tdb_off_t tdb_find_lock_hash(struct tdb_context *tdb, TDB_DATA key, uint32_t hash, int locktype,
+ struct tdb_record *rec)
+{
+ uint32_t rec_ptr;
+
+ if (tdb_lock(tdb, BUCKET(hash), locktype) == -1)
+ return 0;
+ if (!(rec_ptr = tdb_find(tdb, key, hash, rec)))
+ tdb_unlock(tdb, BUCKET(hash), locktype);
+ return rec_ptr;
+}
+
+static TDB_DATA _tdb_fetch(struct tdb_context *tdb, TDB_DATA key);
+
+struct tdb_update_hash_state {
+ const TDB_DATA *dbufs;
+ int num_dbufs;
+ tdb_len_t dbufs_len;
+};
+
+static int tdb_update_hash_cmp(TDB_DATA key, TDB_DATA data, void *private_data)
+{
+ struct tdb_update_hash_state *state = private_data;
+ unsigned char *dptr = data.dptr;
+ int i;
+
+ if (state->dbufs_len != data.dsize) {
+ return -1;
+ }
+
+ for (i=0; i<state->num_dbufs; i++) {
+ TDB_DATA dbuf = state->dbufs[i];
+ if( dbuf.dsize > 0) {
+ int ret;
+ ret = memcmp(dptr, dbuf.dptr, dbuf.dsize);
+ if (ret != 0) {
+ return -1;
+ }
+ dptr += dbuf.dsize;
+ }
+ }
+
+ return 0;
+}
+
+/* update an entry in place - this only works if the new data size
+ is <= the old data size and the key exists.
+ on failure return -1.
+*/
+static int tdb_update_hash(struct tdb_context *tdb, TDB_DATA key,
+ uint32_t hash,
+ const TDB_DATA *dbufs, int num_dbufs,
+ tdb_len_t dbufs_len)
+{
+ struct tdb_record rec;
+ tdb_off_t rec_ptr, ofs;
+ int i;
+
+ /* find entry */
+ if (!(rec_ptr = tdb_find(tdb, key, hash, &rec)))
+ return -1;
+
+ /* it could be an exact duplicate of what is there - this is
+ * surprisingly common (eg. with a ldb re-index). */
+ if (rec.data_len == dbufs_len) {
+ struct tdb_update_hash_state state = {
+ .dbufs = dbufs, .num_dbufs = num_dbufs,
+ .dbufs_len = dbufs_len
+ };
+ int ret;
+
+ ret = tdb_parse_record(tdb, key, tdb_update_hash_cmp, &state);
+ if (ret == 0) {
+ return 0;
+ }
+ }
+
+ /* must be long enough key, data and tailer */
+ if (rec.rec_len < key.dsize + dbufs_len + sizeof(tdb_off_t)) {
+ tdb->ecode = TDB_SUCCESS; /* Not really an error */
+ return -1;
+ }
+
+ ofs = rec_ptr + sizeof(rec) + rec.key_len;
+
+ for (i=0; i<num_dbufs; i++) {
+ TDB_DATA dbuf = dbufs[i];
+ int ret;
+
+ ret = tdb->methods->tdb_write(tdb, ofs, dbuf.dptr, dbuf.dsize);
+ if (ret == -1) {
+ return -1;
+ }
+ ofs += dbuf.dsize;
+ }
+
+ if (dbufs_len != rec.data_len) {
+ /* update size */
+ rec.data_len = dbufs_len;
+ return tdb_rec_write(tdb, rec_ptr, &rec);
+ }
+
+ return 0;
+}
+
+/* find an entry in the database given a key */
+/* If an entry doesn't exist tdb_err will be set to
+ * TDB_ERR_NOEXIST. If a key has no data attached
+ * then the TDB_DATA will have zero length but
+ * a non-zero pointer
+ */
+static TDB_DATA _tdb_fetch(struct tdb_context *tdb, TDB_DATA key)
+{
+ tdb_off_t rec_ptr;
+ struct tdb_record rec;
+ TDB_DATA ret;
+ uint32_t hash;
+
+ /* find which hash bucket it is in */
+ hash = tdb->hash_fn(&key);
+ if (!(rec_ptr = tdb_find_lock_hash(tdb,key,hash,F_RDLCK,&rec)))
+ return tdb_null;
+
+ ret.dptr = tdb_alloc_read(tdb, rec_ptr + sizeof(rec) + rec.key_len,
+ rec.data_len);
+ ret.dsize = rec.data_len;
+ tdb_unlock(tdb, BUCKET(rec.full_hash), F_RDLCK);
+ return ret;
+}
+
+_PUBLIC_ TDB_DATA tdb_fetch(struct tdb_context *tdb, TDB_DATA key)
+{
+ TDB_DATA ret = _tdb_fetch(tdb, key);
+
+ tdb_trace_1rec_retrec(tdb, "tdb_fetch", key, ret);
+ return ret;
+}
+
+/*
+ * Find an entry in the database and hand the record's data to a parsing
+ * function. The parsing function is executed under the chain read lock, so it
+ * should be fast and should not block on other syscalls.
+ *
+ * DON'T CALL OTHER TDB CALLS FROM THE PARSER, THIS MIGHT LEAD TO SEGFAULTS.
+ *
+ * For mmapped tdb's that do not have a transaction open it points the parsing
+ * function directly at the mmap area, it avoids the malloc/memcpy in this
+ * case. If a transaction is open or no mmap is available, it has to do
+ * malloc/read/parse/free.
+ *
+ * This is interesting for all readers of potentially large data structures in
+ * the tdb records, ldb indexes being one example.
+ *
+ * Return -1 if the record was not found.
+ */
+
+_PUBLIC_ int tdb_parse_record(struct tdb_context *tdb, TDB_DATA key,
+ int (*parser)(TDB_DATA key, TDB_DATA data,
+ void *private_data),
+ void *private_data)
+{
+ tdb_off_t rec_ptr;
+ struct tdb_record rec;
+ int ret;
+ uint32_t hash;
+
+ /* find which hash bucket it is in */
+ hash = tdb->hash_fn(&key);
+
+ if (!(rec_ptr = tdb_find_lock_hash(tdb,key,hash,F_RDLCK,&rec))) {
+ /* record not found */
+ tdb_trace_1rec_ret(tdb, "tdb_parse_record", key, -1);
+ tdb->ecode = TDB_ERR_NOEXIST;
+ return -1;
+ }
+ tdb_trace_1rec_ret(tdb, "tdb_parse_record", key, 0);
+
+ ret = tdb_parse_data(tdb, key, rec_ptr + sizeof(rec) + rec.key_len,
+ rec.data_len, parser, private_data);
+
+ tdb_unlock(tdb, BUCKET(rec.full_hash), F_RDLCK);
+
+ return ret;
+}
+
+/* check if an entry in the database exists
+
+ note that 1 is returned if the key is found and 0 is returned if not found
+ this doesn't match the conventions in the rest of this module, but is
+ compatible with gdbm
+*/
+static int tdb_exists_hash(struct tdb_context *tdb, TDB_DATA key, uint32_t hash)
+{
+ struct tdb_record rec;
+
+ if (tdb_find_lock_hash(tdb, key, hash, F_RDLCK, &rec) == 0)
+ return 0;
+ tdb_unlock(tdb, BUCKET(rec.full_hash), F_RDLCK);
+ return 1;
+}
+
+_PUBLIC_ int tdb_exists(struct tdb_context *tdb, TDB_DATA key)
+{
+ uint32_t hash = tdb->hash_fn(&key);
+ int ret;
+
+ ret = tdb_exists_hash(tdb, key, hash);
+ tdb_trace_1rec_ret(tdb, "tdb_exists", key, ret);
+ return ret;
+}
+
+/*
+ * Move a dead record to the freelist. The hash chain and freelist
+ * must be locked.
+ */
+static int tdb_del_dead(struct tdb_context *tdb,
+ uint32_t last_ptr,
+ uint32_t rec_ptr,
+ struct tdb_record *rec,
+ bool *deleted)
+{
+ int ret;
+
+ ret = tdb_write_lock_record(tdb, rec_ptr);
+ if (ret == -1) {
+ /* Someone traversing here: Just leave it dead */
+ return 0;
+ }
+ ret = tdb_write_unlock_record(tdb, rec_ptr);
+ if (ret == -1) {
+ return -1;
+ }
+ ret = tdb_ofs_write(tdb, last_ptr, &rec->next);
+ if (ret == -1) {
+ return -1;
+ }
+
+ *deleted = true;
+
+ ret = tdb_free(tdb, rec_ptr, rec);
+ return ret;
+}
+
+/*
+ * Walk the hash chain and leave tdb->max_dead_records around. Move
+ * the rest of dead records to the freelist.
+ */
+int tdb_trim_dead(struct tdb_context *tdb, uint32_t hash)
+{
+ struct tdb_chainwalk_ctx chainwalk;
+ struct tdb_record rec;
+ tdb_off_t last_ptr, rec_ptr;
+ bool locked_freelist = false;
+ int num_dead = 0;
+ int ret;
+
+ last_ptr = TDB_HASH_TOP(hash);
+
+ /*
+ * Init chainwalk with the pointer to the hash top. It might
+ * be that the very first record in the chain is a dead one
+ * that we have to delete.
+ */
+ tdb_chainwalk_init(&chainwalk, last_ptr);
+
+ ret = tdb_ofs_read(tdb, last_ptr, &rec_ptr);
+ if (ret == -1) {
+ return -1;
+ }
+
+ while (rec_ptr != 0) {
+ bool deleted = false;
+ uint32_t next;
+
+ ret = tdb_rec_read(tdb, rec_ptr, &rec);
+ if (ret == -1) {
+ goto fail;
+ }
+
+ /*
+ * Make a copy of rec.next: Further down we might
+ * delete and put the record on the freelist. Make
+ * sure that modifications in that code path can't
+ * break the chainwalk here.
+ */
+ next = rec.next;
+
+ if (rec.magic == TDB_DEAD_MAGIC) {
+ num_dead += 1;
+
+ if (num_dead > tdb->max_dead_records) {
+
+ if (!locked_freelist) {
+ /*
+ * Lock the freelist only if
+ * it's really required.
+ */
+ ret = tdb_lock(tdb, -1, F_WRLCK);
+ if (ret == -1) {
+ goto fail;
+ };
+ locked_freelist = true;
+ }
+
+ ret = tdb_del_dead(
+ tdb,
+ last_ptr,
+ rec_ptr,
+ &rec,
+ &deleted);
+
+ if (ret == -1) {
+ goto fail;
+ }
+ }
+ }
+
+ /*
+ * Don't do the chainwalk check if "rec_ptr" was
+ * deleted. We reduced the chain, and the chainwalk
+ * check might catch up early. Imagine a valid chain
+ * with just dead records: We never can bump the
+ * "slow" pointer in chainwalk_check, as there isn't
+ * anything left to jump to and compare.
+ */
+ if (!deleted) {
+ bool ok;
+
+ last_ptr = rec_ptr;
+
+ ok = tdb_chainwalk_check(tdb, &chainwalk, next);
+ if (!ok) {
+ ret = -1;
+ goto fail;
+ }
+ }
+ rec_ptr = next;
+ }
+ ret = 0;
+fail:
+ if (locked_freelist) {
+ tdb_unlock(tdb, -1, F_WRLCK);
+ }
+ return ret;
+}
+
+/* delete an entry in the database given a key */
+static int tdb_delete_hash(struct tdb_context *tdb, TDB_DATA key, uint32_t hash)
+{
+ tdb_off_t rec_ptr;
+ struct tdb_record rec;
+ int ret;
+
+ if (tdb->read_only || tdb->traverse_read) {
+ tdb->ecode = TDB_ERR_RDONLY;
+ return -1;
+ }
+
+ rec_ptr = tdb_find_lock_hash(tdb, key, hash, F_WRLCK, &rec);
+ if (rec_ptr == 0) {
+ return -1;
+ }
+
+ /*
+ * Mark the record dead
+ */
+ rec.magic = TDB_DEAD_MAGIC;
+ ret = tdb_rec_write(tdb, rec_ptr, &rec);
+ if (ret == -1) {
+ goto done;
+ }
+
+ tdb_increment_seqnum(tdb);
+
+ ret = tdb_trim_dead(tdb, hash);
+done:
+ if (tdb_unlock(tdb, BUCKET(hash), F_WRLCK) != 0)
+ TDB_LOG((tdb, TDB_DEBUG_WARNING, "tdb_delete: WARNING tdb_unlock failed!\n"));
+ return ret;
+}
+
+_PUBLIC_ int tdb_delete(struct tdb_context *tdb, TDB_DATA key)
+{
+ uint32_t hash = tdb->hash_fn(&key);
+ int ret;
+
+ ret = tdb_delete_hash(tdb, key, hash);
+ tdb_trace_1rec_ret(tdb, "tdb_delete", key, ret);
+ return ret;
+}
+
+/*
+ * See if we have a dead record around with enough space
+ */
+tdb_off_t tdb_find_dead(struct tdb_context *tdb, uint32_t hash,
+ struct tdb_record *r, tdb_len_t length,
+ tdb_off_t *p_last_ptr)
+{
+ tdb_off_t rec_ptr, last_ptr;
+ struct tdb_chainwalk_ctx chainwalk;
+ tdb_off_t best_rec_ptr = 0;
+ tdb_off_t best_last_ptr = 0;
+ struct tdb_record best = { .rec_len = UINT32_MAX };
+
+ length += sizeof(tdb_off_t); /* tailer */
+
+ last_ptr = TDB_HASH_TOP(hash);
+
+ /* read in the hash top */
+ if (tdb_ofs_read(tdb, last_ptr, &rec_ptr) == -1)
+ return 0;
+
+ tdb_chainwalk_init(&chainwalk, rec_ptr);
+
+ /* keep looking until we find the right record */
+ while (rec_ptr) {
+ bool ok;
+
+ if (tdb_rec_read(tdb, rec_ptr, r) == -1)
+ return 0;
+
+ if (TDB_DEAD(r) && (r->rec_len >= length) &&
+ (r->rec_len < best.rec_len)) {
+ best_rec_ptr = rec_ptr;
+ best_last_ptr = last_ptr;
+ best = *r;
+ }
+ last_ptr = rec_ptr;
+ rec_ptr = r->next;
+
+ ok = tdb_chainwalk_check(tdb, &chainwalk, rec_ptr);
+ if (!ok) {
+ return 0;
+ }
+ }
+
+ if (best.rec_len == UINT32_MAX) {
+ return 0;
+ }
+
+ *r = best;
+ *p_last_ptr = best_last_ptr;
+ return best_rec_ptr;
+}
+
+static int _tdb_storev(struct tdb_context *tdb, TDB_DATA key,
+ const TDB_DATA *dbufs, int num_dbufs,
+ int flag, uint32_t hash)
+{
+ struct tdb_record rec;
+ tdb_off_t rec_ptr, ofs;
+ tdb_len_t rec_len, dbufs_len;
+ int i;
+ int ret = -1;
+
+ dbufs_len = 0;
+
+ for (i=0; i<num_dbufs; i++) {
+ size_t dsize = dbufs[i].dsize;
+
+ if ((dsize != 0) && (dbufs[i].dptr == NULL)) {
+ tdb->ecode = TDB_ERR_EINVAL;
+ goto fail;
+ }
+
+ dbufs_len += dsize;
+ if (dbufs_len < dsize) {
+ tdb->ecode = TDB_ERR_OOM;
+ goto fail;
+ }
+ }
+
+ rec_len = key.dsize + dbufs_len;
+ if ((rec_len < key.dsize) || (rec_len < dbufs_len)) {
+ tdb->ecode = TDB_ERR_OOM;
+ goto fail;
+ }
+
+ /* check for it existing, on insert. */
+ if (flag == TDB_INSERT) {
+ if (tdb_exists_hash(tdb, key, hash)) {
+ tdb->ecode = TDB_ERR_EXISTS;
+ goto fail;
+ }
+ } else {
+ /* first try in-place update, on modify or replace. */
+ if (tdb_update_hash(tdb, key, hash, dbufs, num_dbufs,
+ dbufs_len) == 0) {
+ goto done;
+ }
+ if (tdb->ecode == TDB_ERR_NOEXIST &&
+ flag == TDB_MODIFY) {
+ /* if the record doesn't exist and we are in TDB_MODIFY mode then
+ we should fail the store */
+ goto fail;
+ }
+ }
+ /* reset the error code potentially set by the tdb_update_hash() */
+ tdb->ecode = TDB_SUCCESS;
+
+ /* delete any existing record - if it doesn't exist we don't
+ care. Doing this first reduces fragmentation, and avoids
+ coalescing with `allocated' block before it's updated. */
+ if (flag != TDB_INSERT)
+ tdb_delete_hash(tdb, key, hash);
+
+ /* we have to allocate some space */
+ rec_ptr = tdb_allocate(tdb, hash, rec_len, &rec);
+
+ if (rec_ptr == 0) {
+ goto fail;
+ }
+
+ /* Read hash top into next ptr */
+ if (tdb_ofs_read(tdb, TDB_HASH_TOP(hash), &rec.next) == -1)
+ goto fail;
+
+ rec.key_len = key.dsize;
+ rec.data_len = dbufs_len;
+ rec.full_hash = hash;
+ rec.magic = TDB_MAGIC;
+
+ ofs = rec_ptr;
+
+ /* write out and point the top of the hash chain at it */
+ ret = tdb_rec_write(tdb, ofs, &rec);
+ if (ret == -1) {
+ goto fail;
+ }
+ ofs += sizeof(rec);
+
+ ret = tdb->methods->tdb_write(tdb, ofs, key.dptr, key.dsize);
+ if (ret == -1) {
+ goto fail;
+ }
+ ofs += key.dsize;
+
+ for (i=0; i<num_dbufs; i++) {
+ if (dbufs[i].dsize == 0) {
+ continue;
+ }
+
+ ret = tdb->methods->tdb_write(tdb, ofs, dbufs[i].dptr,
+ dbufs[i].dsize);
+ if (ret == -1) {
+ goto fail;
+ }
+ ofs += dbufs[i].dsize;
+ }
+
+ ret = tdb_ofs_write(tdb, TDB_HASH_TOP(hash), &rec_ptr);
+ if (ret == -1) {
+ /* Need to tdb_unallocate() here */
+ goto fail;
+ }
+
+ done:
+ ret = 0;
+ fail:
+ if (ret == 0) {
+ tdb_increment_seqnum(tdb);
+ }
+ return ret;
+}
+
+static int _tdb_store(struct tdb_context *tdb, TDB_DATA key,
+ TDB_DATA dbuf, int flag, uint32_t hash)
+{
+ return _tdb_storev(tdb, key, &dbuf, 1, flag, hash);
+}
+
+/* store an element in the database, replacing any existing element
+ with the same key
+
+ return 0 on success, -1 on failure
+*/
+_PUBLIC_ int tdb_store(struct tdb_context *tdb, TDB_DATA key, TDB_DATA dbuf, int flag)
+{
+ uint32_t hash;
+ int ret;
+
+ if (tdb->read_only || tdb->traverse_read) {
+ tdb->ecode = TDB_ERR_RDONLY;
+ tdb_trace_2rec_flag_ret(tdb, "tdb_store", key, dbuf, flag, -1);
+ return -1;
+ }
+
+ /* find which hash bucket it is in */
+ hash = tdb->hash_fn(&key);
+ if (tdb_lock(tdb, BUCKET(hash), F_WRLCK) == -1)
+ return -1;
+
+ ret = _tdb_store(tdb, key, dbuf, flag, hash);
+ tdb_trace_2rec_flag_ret(tdb, "tdb_store", key, dbuf, flag, ret);
+ tdb_unlock(tdb, BUCKET(hash), F_WRLCK);
+ return ret;
+}
+
+_PUBLIC_ int tdb_storev(struct tdb_context *tdb, TDB_DATA key,
+ const TDB_DATA *dbufs, int num_dbufs, int flag)
+{
+ uint32_t hash;
+ int ret;
+
+ if (tdb->read_only || tdb->traverse_read) {
+ tdb->ecode = TDB_ERR_RDONLY;
+ tdb_trace_1plusn_rec_flag_ret(tdb, "tdb_storev", key,
+ dbufs, num_dbufs, flag, -1);
+ return -1;
+ }
+
+ /* find which hash bucket it is in */
+ hash = tdb->hash_fn(&key);
+ if (tdb_lock(tdb, BUCKET(hash), F_WRLCK) == -1)
+ return -1;
+
+ ret = _tdb_storev(tdb, key, dbufs, num_dbufs, flag, hash);
+ tdb_trace_1plusn_rec_flag_ret(tdb, "tdb_storev", key,
+ dbufs, num_dbufs, flag, -1);
+ tdb_unlock(tdb, BUCKET(hash), F_WRLCK);
+ return ret;
+}
+
+/* Append to an entry. Create if not exist. */
+_PUBLIC_ int tdb_append(struct tdb_context *tdb, TDB_DATA key, TDB_DATA new_dbuf)
+{
+ uint32_t hash;
+ TDB_DATA dbufs[2];
+ int ret = -1;
+
+ /* find which hash bucket it is in */
+ hash = tdb->hash_fn(&key);
+ if (tdb_lock(tdb, BUCKET(hash), F_WRLCK) == -1)
+ return -1;
+
+ dbufs[0] = _tdb_fetch(tdb, key);
+ dbufs[1] = new_dbuf;
+
+ ret = _tdb_storev(tdb, key, dbufs, 2, 0, hash);
+ tdb_trace_2rec_retrec(tdb, "tdb_append", key, dbufs[0], dbufs[1]);
+
+ tdb_unlock(tdb, BUCKET(hash), F_WRLCK);
+ SAFE_FREE(dbufs[0].dptr);
+ return ret;
+}
+
+
+/*
+ return the name of the current tdb file
+ useful for external logging functions
+*/
+_PUBLIC_ const char *tdb_name(struct tdb_context *tdb)
+{
+ return tdb->name;
+}
+
+/*
+ return the underlying file descriptor being used by tdb, or -1
+ useful for external routines that want to check the device/inode
+ of the fd
+*/
+_PUBLIC_ int tdb_fd(struct tdb_context *tdb)
+{
+ return tdb->fd;
+}
+
+/*
+ return the current logging function
+ useful for external tdb routines that wish to log tdb errors
+*/
+_PUBLIC_ tdb_log_func tdb_log_fn(struct tdb_context *tdb)
+{
+ return tdb->log.log_fn;
+}
+
+
+/*
+ get the tdb sequence number. Only makes sense if the writers opened
+ with TDB_SEQNUM set. Note that this sequence number will wrap quite
+ quickly, so it should only be used for a 'has something changed'
+ test, not for code that relies on the count of the number of changes
+ made. If you want a counter then use a tdb record.
+
+ The aim of this sequence number is to allow for a very lightweight
+ test of a possible tdb change.
+*/
+_PUBLIC_ int tdb_get_seqnum(struct tdb_context *tdb)
+{
+ tdb_off_t seqnum=0;
+
+ if (tdb->transaction != NULL) {
+ tdb_ofs_read(tdb, TDB_SEQNUM_OFS, &seqnum);
+ return seqnum;
+ }
+
+#if defined(HAVE___ATOMIC_ADD_FETCH) && defined(HAVE___ATOMIC_ADD_LOAD)
+ if (tdb->map_ptr != NULL) {
+ uint32_t *pseqnum = (uint32_t *)(
+ TDB_SEQNUM_OFS + (char *)tdb->map_ptr);
+ uint32_t ret;
+ __atomic_load(pseqnum, &ret,__ATOMIC_SEQ_CST);
+ return ret;
+ }
+#endif
+
+ tdb_ofs_read(tdb, TDB_SEQNUM_OFS, &seqnum);
+ return seqnum;
+}
+
+_PUBLIC_ int tdb_hash_size(struct tdb_context *tdb)
+{
+ return tdb->hash_size;
+}
+
+_PUBLIC_ size_t tdb_map_size(struct tdb_context *tdb)
+{
+ return tdb->map_size;
+}
+
+_PUBLIC_ int tdb_get_flags(struct tdb_context *tdb)
+{
+ return tdb->flags;
+}
+
+_PUBLIC_ void tdb_add_flags(struct tdb_context *tdb, unsigned flags)
+{
+ if ((flags & TDB_ALLOW_NESTING) &&
+ (flags & TDB_DISALLOW_NESTING)) {
+ tdb->ecode = TDB_ERR_NESTING;
+ TDB_LOG((tdb, TDB_DEBUG_FATAL, "tdb_add_flags: "
+ "allow_nesting and disallow_nesting are not allowed together!"));
+ return;
+ }
+
+ if (flags & TDB_ALLOW_NESTING) {
+ tdb->flags &= ~TDB_DISALLOW_NESTING;
+ }
+ if (flags & TDB_DISALLOW_NESTING) {
+ tdb->flags &= ~TDB_ALLOW_NESTING;
+ }
+
+ tdb->flags |= flags;
+}
+
+_PUBLIC_ void tdb_remove_flags(struct tdb_context *tdb, unsigned flags)
+{
+ if ((flags & TDB_ALLOW_NESTING) &&
+ (flags & TDB_DISALLOW_NESTING)) {
+ tdb->ecode = TDB_ERR_NESTING;
+ TDB_LOG((tdb, TDB_DEBUG_FATAL, "tdb_remove_flags: "
+ "allow_nesting and disallow_nesting are not allowed together!"));
+ return;
+ }
+
+ if ((flags & TDB_NOLOCK) &&
+ (tdb->feature_flags & TDB_FEATURE_FLAG_MUTEX) &&
+ (tdb->mutexes == NULL)) {
+ tdb->ecode = TDB_ERR_LOCK;
+ TDB_LOG((tdb, TDB_DEBUG_FATAL, "tdb_remove_flags: "
+ "Can not remove NOLOCK flag on mutexed databases"));
+ return;
+ }
+
+ if (flags & TDB_ALLOW_NESTING) {
+ tdb->flags |= TDB_DISALLOW_NESTING;
+ }
+ if (flags & TDB_DISALLOW_NESTING) {
+ tdb->flags |= TDB_ALLOW_NESTING;
+ }
+
+ tdb->flags &= ~flags;
+}
+
+
+/*
+ enable sequence number handling on an open tdb
+*/
+_PUBLIC_ void tdb_enable_seqnum(struct tdb_context *tdb)
+{
+ tdb->flags |= TDB_SEQNUM;
+}
+
+
+/*
+ add a region of the file to the freelist. Length is the size of the region in bytes,
+ which includes the free list header that needs to be added
+ */
+static int tdb_free_region(struct tdb_context *tdb, tdb_off_t offset, ssize_t length)
+{
+ struct tdb_record rec;
+ if (length <= sizeof(rec)) {
+ /* the region is not worth adding */
+ return 0;
+ }
+ if (length + offset > tdb->map_size) {
+ TDB_LOG((tdb, TDB_DEBUG_FATAL,"tdb_free_region: adding region beyond end of file\n"));
+ return -1;
+ }
+ memset(&rec,'\0',sizeof(rec));
+ rec.rec_len = length - sizeof(rec);
+ if (tdb_free(tdb, offset, &rec) == -1) {
+ TDB_LOG((tdb, TDB_DEBUG_FATAL,"tdb_free_region: failed to add free record\n"));
+ return -1;
+ }
+ return 0;
+}
+
+/*
+ wipe the entire database, deleting all records. This can be done
+ very fast by using a allrecord lock. The entire data portion of the
+ file becomes a single entry in the freelist.
+
+ This code carefully steps around the recovery area, leaving it alone
+ */
+_PUBLIC_ int tdb_wipe_all(struct tdb_context *tdb)
+{
+ uint32_t i;
+ tdb_off_t offset = 0;
+ ssize_t data_len;
+ tdb_off_t recovery_head;
+ tdb_len_t recovery_size = 0;
+
+ if (tdb_lockall(tdb) != 0) {
+ return -1;
+ }
+
+ tdb_trace(tdb, "tdb_wipe_all");
+
+ /* see if the tdb has a recovery area, and remember its size
+ if so. We don't want to lose this as otherwise each
+ tdb_wipe_all() in a transaction will increase the size of
+ the tdb by the size of the recovery area */
+ if (tdb_ofs_read(tdb, TDB_RECOVERY_HEAD, &recovery_head) == -1) {
+ TDB_LOG((tdb, TDB_DEBUG_FATAL, "tdb_wipe_all: failed to read recovery head\n"));
+ goto failed;
+ }
+
+ if (recovery_head != 0) {
+ struct tdb_record rec;
+ if (tdb->methods->tdb_read(tdb, recovery_head, &rec, sizeof(rec), DOCONV()) == -1) {
+ TDB_LOG((tdb, TDB_DEBUG_FATAL, "tdb_wipe_all: failed to read recovery record\n"));
+ return -1;
+ }
+ recovery_size = rec.rec_len + sizeof(rec);
+ }
+
+ /* wipe the hashes */
+ for (i=0;i<tdb->hash_size;i++) {
+ if (tdb_ofs_write(tdb, TDB_HASH_TOP(i), &offset) == -1) {
+ TDB_LOG((tdb, TDB_DEBUG_FATAL,"tdb_wipe_all: failed to write hash %d\n", i));
+ goto failed;
+ }
+ }
+
+ /* wipe the freelist */
+ if (tdb_ofs_write(tdb, FREELIST_TOP, &offset) == -1) {
+ TDB_LOG((tdb, TDB_DEBUG_FATAL,"tdb_wipe_all: failed to write freelist\n"));
+ goto failed;
+ }
+
+ /* add all the rest of the file to the freelist, possibly leaving a gap
+ for the recovery area */
+ if (recovery_size == 0) {
+ /* the simple case - the whole file can be used as a freelist */
+ data_len = (tdb->map_size - TDB_DATA_START(tdb->hash_size));
+ if (tdb_free_region(tdb, TDB_DATA_START(tdb->hash_size), data_len) != 0) {
+ goto failed;
+ }
+ } else {
+ /* we need to add two freelist entries - one on either
+ side of the recovery area
+
+ Note that we cannot shift the recovery area during
+ this operation. Only the transaction.c code may
+ move the recovery area or we risk subtle data
+ corruption
+ */
+ data_len = (recovery_head - TDB_DATA_START(tdb->hash_size));
+ if (tdb_free_region(tdb, TDB_DATA_START(tdb->hash_size), data_len) != 0) {
+ goto failed;
+ }
+ /* and the 2nd free list entry after the recovery area - if any */
+ data_len = tdb->map_size - (recovery_head+recovery_size);
+ if (tdb_free_region(tdb, recovery_head+recovery_size, data_len) != 0) {
+ goto failed;
+ }
+ }
+
+ tdb_increment_seqnum_nonblock(tdb);
+
+ if (tdb_unlockall(tdb) != 0) {
+ TDB_LOG((tdb, TDB_DEBUG_FATAL,"tdb_wipe_all: failed to unlock\n"));
+ goto failed;
+ }
+
+ return 0;
+
+failed:
+ tdb_unlockall(tdb);
+ return -1;
+}
+
+struct traverse_state {
+ bool error;
+ struct tdb_context *dest_db;
+};
+
+/*
+ traverse function for repacking
+ */
+static int repack_traverse(struct tdb_context *tdb, TDB_DATA key, TDB_DATA data, void *private_data)
+{
+ struct traverse_state *state = (struct traverse_state *)private_data;
+ if (tdb_store(state->dest_db, key, data, TDB_INSERT) != 0) {
+ state->error = true;
+ return -1;
+ }
+ return 0;
+}
+
+/*
+ repack a tdb
+ */
+_PUBLIC_ int tdb_repack(struct tdb_context *tdb)
+{
+ struct tdb_context *tmp_db;
+ struct traverse_state state;
+
+ tdb_trace(tdb, "tdb_repack");
+
+ if (tdb_transaction_start(tdb) != 0) {
+ TDB_LOG((tdb, TDB_DEBUG_FATAL, __location__ " Failed to start transaction\n"));
+ return -1;
+ }
+
+ tmp_db = tdb_open("tmpdb", tdb_hash_size(tdb), TDB_INTERNAL, O_RDWR|O_CREAT, 0);
+ if (tmp_db == NULL) {
+ TDB_LOG((tdb, TDB_DEBUG_FATAL, __location__ " Failed to create tmp_db\n"));
+ tdb_transaction_cancel(tdb);
+ return -1;
+ }
+
+ state.error = false;
+ state.dest_db = tmp_db;
+
+ if (tdb_traverse_read(tdb, repack_traverse, &state) == -1) {
+ TDB_LOG((tdb, TDB_DEBUG_FATAL, __location__ " Failed to traverse copying out\n"));
+ tdb_transaction_cancel(tdb);
+ tdb_close(tmp_db);
+ return -1;
+ }
+
+ if (state.error) {
+ TDB_LOG((tdb, TDB_DEBUG_FATAL, __location__ " Error during traversal\n"));
+ tdb_transaction_cancel(tdb);
+ tdb_close(tmp_db);
+ return -1;
+ }
+
+ if (tdb_wipe_all(tdb) != 0) {
+ TDB_LOG((tdb, TDB_DEBUG_FATAL, __location__ " Failed to wipe database\n"));
+ tdb_transaction_cancel(tdb);
+ tdb_close(tmp_db);
+ return -1;
+ }
+
+ state.error = false;
+ state.dest_db = tdb;
+
+ if (tdb_traverse_read(tmp_db, repack_traverse, &state) == -1) {
+ TDB_LOG((tdb, TDB_DEBUG_FATAL, __location__ " Failed to traverse copying back\n"));
+ tdb_transaction_cancel(tdb);
+ tdb_close(tmp_db);
+ return -1;
+ }
+
+ if (state.error) {
+ TDB_LOG((tdb, TDB_DEBUG_FATAL, __location__ " Error during second traversal\n"));
+ tdb_transaction_cancel(tdb);
+ tdb_close(tmp_db);
+ return -1;
+ }
+
+ tdb_close(tmp_db);
+
+ if (tdb_transaction_commit(tdb) != 0) {
+ TDB_LOG((tdb, TDB_DEBUG_FATAL, __location__ " Failed to commit\n"));
+ return -1;
+ }
+
+ return 0;
+}
+
+/* Even on files, we can get partial writes due to signals. */
+bool tdb_write_all(int fd, const void *buf, size_t count)
+{
+ while (count) {
+ ssize_t ret;
+ ret = write(fd, buf, count);
+ if (ret < 0)
+ return false;
+ buf = (const char *)buf + ret;
+ count -= ret;
+ }
+ return true;
+}
+
+bool tdb_add_off_t(tdb_off_t a, tdb_off_t b, tdb_off_t *pret)
+{
+ tdb_off_t ret = a + b;
+
+ if ((ret < a) || (ret < b)) {
+ return false;
+ }
+ *pret = ret;
+ return true;
+}
+
+#ifdef TDB_TRACE
+static void tdb_trace_write(struct tdb_context *tdb, const char *str)
+{
+ if (!tdb_write_all(tdb->tracefd, str, strlen(str))) {
+ close(tdb->tracefd);
+ tdb->tracefd = -1;
+ }
+}
+
+static void tdb_trace_start(struct tdb_context *tdb)
+{
+ tdb_off_t seqnum=0;
+ char msg[sizeof(tdb_off_t) * 4 + 1];
+
+ tdb_ofs_read(tdb, TDB_SEQNUM_OFS, &seqnum);
+ snprintf(msg, sizeof(msg), "%u ", seqnum);
+ tdb_trace_write(tdb, msg);
+}
+
+static void tdb_trace_end(struct tdb_context *tdb)
+{
+ tdb_trace_write(tdb, "\n");
+}
+
+static void tdb_trace_end_ret(struct tdb_context *tdb, int ret)
+{
+ char msg[sizeof(ret) * 4 + 4];
+ snprintf(msg, sizeof(msg), " = %i\n", ret);
+ tdb_trace_write(tdb, msg);
+}
+
+static void tdb_trace_record(struct tdb_context *tdb, TDB_DATA rec)
+{
+ char msg[20 + rec.dsize*2], *p;
+ unsigned int i;
+
+ /* We differentiate zero-length records from non-existent ones. */
+ if (rec.dptr == NULL) {
+ tdb_trace_write(tdb, " NULL");
+ return;
+ }
+
+ /* snprintf here is purely cargo-cult programming. */
+ p = msg;
+ p += snprintf(p, sizeof(msg), " %zu:", rec.dsize);
+ for (i = 0; i < rec.dsize; i++)
+ p += snprintf(p, 2, "%02x", rec.dptr[i]);
+
+ tdb_trace_write(tdb, msg);
+}
+
+void tdb_trace(struct tdb_context *tdb, const char *op)
+{
+ tdb_trace_start(tdb);
+ tdb_trace_write(tdb, op);
+ tdb_trace_end(tdb);
+}
+
+void tdb_trace_seqnum(struct tdb_context *tdb, uint32_t seqnum, const char *op)
+{
+ char msg[sizeof(tdb_off_t) * 4 + 1];
+
+ snprintf(msg, sizeof(msg), "%u ", seqnum);
+ tdb_trace_write(tdb, msg);
+ tdb_trace_write(tdb, op);
+ tdb_trace_end(tdb);
+}
+
+void tdb_trace_open(struct tdb_context *tdb, const char *op,
+ unsigned hash_size, unsigned tdb_flags, unsigned open_flags)
+{
+ char msg[128];
+
+ snprintf(msg, sizeof(msg),
+ "%s %u 0x%x 0x%x", op, hash_size, tdb_flags, open_flags);
+ tdb_trace_start(tdb);
+ tdb_trace_write(tdb, msg);
+ tdb_trace_end(tdb);
+}
+
+void tdb_trace_ret(struct tdb_context *tdb, const char *op, int ret)
+{
+ tdb_trace_start(tdb);
+ tdb_trace_write(tdb, op);
+ tdb_trace_end_ret(tdb, ret);
+}
+
+void tdb_trace_retrec(struct tdb_context *tdb, const char *op, TDB_DATA ret)
+{
+ tdb_trace_start(tdb);
+ tdb_trace_write(tdb, op);
+ tdb_trace_write(tdb, " =");
+ tdb_trace_record(tdb, ret);
+ tdb_trace_end(tdb);
+}
+
+void tdb_trace_1rec(struct tdb_context *tdb, const char *op,
+ TDB_DATA rec)
+{
+ tdb_trace_start(tdb);
+ tdb_trace_write(tdb, op);
+ tdb_trace_record(tdb, rec);
+ tdb_trace_end(tdb);
+}
+
+void tdb_trace_1rec_ret(struct tdb_context *tdb, const char *op,
+ TDB_DATA rec, int ret)
+{
+ tdb_trace_start(tdb);
+ tdb_trace_write(tdb, op);
+ tdb_trace_record(tdb, rec);
+ tdb_trace_end_ret(tdb, ret);
+}
+
+void tdb_trace_1rec_retrec(struct tdb_context *tdb, const char *op,
+ TDB_DATA rec, TDB_DATA ret)
+{
+ tdb_trace_start(tdb);
+ tdb_trace_write(tdb, op);
+ tdb_trace_record(tdb, rec);
+ tdb_trace_write(tdb, " =");
+ tdb_trace_record(tdb, ret);
+ tdb_trace_end(tdb);
+}
+
+void tdb_trace_2rec_flag_ret(struct tdb_context *tdb, const char *op,
+ TDB_DATA rec1, TDB_DATA rec2, unsigned flag,
+ int ret)
+{
+ char msg[1 + sizeof(ret) * 4];
+
+ snprintf(msg, sizeof(msg), " %#x", flag);
+ tdb_trace_start(tdb);
+ tdb_trace_write(tdb, op);
+ tdb_trace_record(tdb, rec1);
+ tdb_trace_record(tdb, rec2);
+ tdb_trace_write(tdb, msg);
+ tdb_trace_end_ret(tdb, ret);
+}
+
+void tdb_trace_1plusn_rec_flag_ret(struct tdb_context *tdb, const char *op,
+ TDB_DATA rec,
+ const TDB_DATA *recs, int num_recs,
+ unsigned flag, int ret)
+{
+ char msg[1 + sizeof(ret) * 4];
+ int i;
+
+ snprintf(msg, sizeof(msg), " %#x", flag);
+ tdb_trace_start(tdb);
+ tdb_trace_write(tdb, op);
+ tdb_trace_record(tdb, rec);
+ for (i=0; i<num_recs; i++) {
+ tdb_trace_record(tdb, recs[i]);
+ }
+ tdb_trace_write(tdb, msg);
+ tdb_trace_end_ret(tdb, ret);
+}
+
+void tdb_trace_2rec_retrec(struct tdb_context *tdb, const char *op,
+ TDB_DATA rec1, TDB_DATA rec2, TDB_DATA ret)
+{
+ tdb_trace_start(tdb);
+ tdb_trace_write(tdb, op);
+ tdb_trace_record(tdb, rec1);
+ tdb_trace_record(tdb, rec2);
+ tdb_trace_write(tdb, " =");
+ tdb_trace_record(tdb, ret);
+ tdb_trace_end(tdb);
+}
+#endif
diff --git a/lib/tdb/common/tdb_private.h b/lib/tdb/common/tdb_private.h
new file mode 100644
index 0000000..2979043
--- /dev/null
+++ b/lib/tdb/common/tdb_private.h
@@ -0,0 +1,370 @@
+#ifndef TDB_PRIVATE_H
+#define TDB_PRIVATE_H
+ /*
+ Unix SMB/CIFS implementation.
+
+ trivial database library - private includes
+
+ Copyright (C) Andrew Tridgell 2005
+
+ ** NOTE! The following LGPL license applies to the tdb
+ ** library. This does NOT imply that all of Samba is released
+ ** under the LGPL
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 3 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "replace.h"
+#include "system/filesys.h"
+#include "system/time.h"
+#include "system/shmem.h"
+#include "system/select.h"
+#include "system/wait.h"
+#include "tdb.h"
+
+/* #define TDB_TRACE 1 */
+#ifndef HAVE_GETPAGESIZE
+#define getpagesize() 0x2000
+#endif
+
+typedef uint32_t tdb_len_t;
+typedef uint32_t tdb_off_t;
+
+#ifndef offsetof
+#define offsetof(t,f) ((unsigned int)&((t *)0)->f)
+#endif
+
+#define TDB_MAGIC_FOOD "TDB file\n"
+#define TDB_VERSION (0x26011967 + 6)
+#define TDB_MAGIC (0x26011999U)
+#define TDB_FREE_MAGIC (~TDB_MAGIC)
+#define TDB_DEAD_MAGIC (0xFEE1DEAD)
+#define TDB_RECOVERY_MAGIC (0xf53bc0e7U)
+#define TDB_RECOVERY_INVALID_MAGIC (0x0)
+#define TDB_HASH_RWLOCK_MAGIC (0xbad1a51U)
+#define TDB_FEATURE_FLAG_MAGIC (0xbad1a52U)
+#define TDB_ALIGNMENT 4
+#define DEFAULT_HASH_SIZE 131
+#define FREELIST_TOP (sizeof(struct tdb_header))
+#define TDB_ALIGN(x,a) (((x) + (a)-1) & ~((a)-1))
+#define TDB_BYTEREV(x) (((((x)&0xff)<<24)|((x)&0xFF00)<<8)|(((x)>>8)&0xFF00)|((x)>>24))
+#define TDB_DEAD(r) ((r)->magic == TDB_DEAD_MAGIC)
+#define TDB_BAD_MAGIC(r) ((r)->magic != TDB_MAGIC && !TDB_DEAD(r))
+#define TDB_HASH_TOP(hash) (FREELIST_TOP + (BUCKET(hash)+1)*sizeof(tdb_off_t))
+#define TDB_HASHTABLE_SIZE(tdb) ((tdb->hash_size+1)*sizeof(tdb_off_t))
+#define TDB_DATA_START(hash_size) (TDB_HASH_TOP(hash_size-1) + sizeof(tdb_off_t))
+#define TDB_RECOVERY_HEAD offsetof(struct tdb_header, recovery_start)
+#define TDB_SEQNUM_OFS offsetof(struct tdb_header, sequence_number)
+#define TDB_PAD_BYTE 0x42
+#define TDB_PAD_U32 0x42424242
+
+#define TDB_FEATURE_FLAG_MUTEX 0x00000001
+
+#define TDB_SUPPORTED_FEATURE_FLAGS ( \
+ TDB_FEATURE_FLAG_MUTEX | \
+ 0)
+
+/* NB assumes there is a local variable called "tdb" that is the
+ * current context, also takes doubly-parenthesized print-style
+ * argument. */
+#define TDB_LOG(x) tdb->log.log_fn x
+
+#ifdef TDB_TRACE
+void tdb_trace(struct tdb_context *tdb, const char *op);
+void tdb_trace_seqnum(struct tdb_context *tdb, uint32_t seqnum, const char *op);
+void tdb_trace_open(struct tdb_context *tdb, const char *op,
+ unsigned hash_size, unsigned tdb_flags, unsigned open_flags);
+void tdb_trace_ret(struct tdb_context *tdb, const char *op, int ret);
+void tdb_trace_retrec(struct tdb_context *tdb, const char *op, TDB_DATA ret);
+void tdb_trace_1rec(struct tdb_context *tdb, const char *op,
+ TDB_DATA rec);
+void tdb_trace_1rec_ret(struct tdb_context *tdb, const char *op,
+ TDB_DATA rec, int ret);
+void tdb_trace_1rec_retrec(struct tdb_context *tdb, const char *op,
+ TDB_DATA rec, TDB_DATA ret);
+void tdb_trace_2rec_flag_ret(struct tdb_context *tdb, const char *op,
+ TDB_DATA rec1, TDB_DATA rec2, unsigned flag,
+ int ret);
+void tdb_trace_1plusn_rec_flag_ret(struct tdb_context *tdb, const char *op,
+ TDB_DATA rec,
+ const TDB_DATA *recs, int num_recs,
+ unsigned flag, int ret);
+void tdb_trace_2rec_retrec(struct tdb_context *tdb, const char *op,
+ TDB_DATA rec1, TDB_DATA rec2, TDB_DATA ret);
+#else
+#define tdb_trace(tdb, op)
+#define tdb_trace_seqnum(tdb, seqnum, op)
+#define tdb_trace_open(tdb, op, hash_size, tdb_flags, open_flags)
+#define tdb_trace_ret(tdb, op, ret)
+#define tdb_trace_retrec(tdb, op, ret)
+#define tdb_trace_1rec(tdb, op, rec)
+#define tdb_trace_1rec_ret(tdb, op, rec, ret)
+#define tdb_trace_1rec_retrec(tdb, op, rec, ret)
+#define tdb_trace_2rec_flag_ret(tdb, op, rec1, rec2, flag, ret)
+#define tdb_trace_1plusn_rec_flag_ret(tdb, op, rec, recs, num_recs, flag, ret);
+#define tdb_trace_2rec_retrec(tdb, op, rec1, rec2, ret)
+#endif /* !TDB_TRACE */
+
+/* lock offsets */
+#define OPEN_LOCK 0
+#define ACTIVE_LOCK 4
+#define TRANSACTION_LOCK 8
+
+/* free memory if the pointer is valid and zero the pointer */
+#ifndef SAFE_FREE
+#define SAFE_FREE(x) do { if ((x) != NULL) {free(x); (x)=NULL;} } while(0)
+#endif
+
+/*
+ * Note: the BUCKET macro is broken as it returns an unexpected result when
+ * called as BUCKET(-1) for the freelist:
+ *
+ * -1 is sign converted to an unsigned int 4294967295 and then the modulo
+ * tdb->hashtable_size is computed. So with a hashtable_size of 10 the result
+ * is
+ *
+ * 4294967295 % hashtable_size = 5.
+ *
+ * where it should be -1 (C uses symmetric modulo).
+ *
+ * As all callers will lock the same wrong list consistently locking is still
+ * consistent. We can not change this without an incompatible on-disk format
+ * change, otherwise different tdb versions would use incompatible locking.
+ */
+#define BUCKET(hash) ((hash) % tdb->hash_size)
+
+#define DOCONV() (tdb->flags & TDB_CONVERT)
+#define CONVERT(x) (DOCONV() ? tdb_convert(&x, sizeof(x)) : &x)
+
+
+/* the body of the database is made of one tdb_record for the free space
+ plus a separate data list for each hash value */
+struct tdb_record {
+ tdb_off_t next; /* offset of the next record in the list */
+ tdb_len_t rec_len; /* total byte length of record */
+ tdb_len_t key_len; /* byte length of key */
+ tdb_len_t data_len; /* byte length of data */
+ uint32_t full_hash; /* the full 32 bit hash of the key */
+ uint32_t magic; /* try to catch errors */
+ /* the following union is implied:
+ union {
+ char record[rec_len];
+ struct {
+ char key[key_len];
+ char data[data_len];
+ }
+ uint32_t totalsize; (tailer)
+ }
+ */
+};
+
+
+/* this is stored at the front of every database */
+struct tdb_header {
+ char magic_food[32]; /* for /etc/magic */
+ uint32_t version; /* version of the code */
+ uint32_t hash_size; /* number of hash entries */
+ tdb_off_t rwlocks; /* obsolete - kept to detect old formats */
+ tdb_off_t recovery_start; /* offset of transaction recovery region */
+ tdb_off_t sequence_number; /* used when TDB_SEQNUM is set */
+ uint32_t magic1_hash; /* hash of TDB_MAGIC_FOOD. */
+ uint32_t magic2_hash; /* hash of TDB_MAGIC. */
+ uint32_t feature_flags;
+ tdb_len_t mutex_size; /* set if TDB_FEATURE_FLAG_MUTEX is set */
+ tdb_off_t reserved[25];
+};
+
+struct tdb_lock_type {
+ uint32_t off;
+ uint32_t count;
+ uint32_t ltype;
+};
+
+struct tdb_chainwalk_ctx {
+ tdb_off_t slow_ptr;
+ bool slow_chase;
+};
+
+struct tdb_traverse_lock {
+ struct tdb_traverse_lock *next;
+ uint32_t off;
+ uint32_t list;
+ int lock_rw;
+};
+
+void tdb_chainwalk_init(struct tdb_chainwalk_ctx *ctx, tdb_off_t ptr);
+bool tdb_chainwalk_check(struct tdb_context *tdb,
+ struct tdb_chainwalk_ctx *ctx,
+ tdb_off_t next_ptr);
+
+enum tdb_lock_flags {
+ /* WAIT == F_SETLKW, NOWAIT == F_SETLK */
+ TDB_LOCK_NOWAIT = 0,
+ TDB_LOCK_WAIT = 1,
+ /* If set, don't log an error on failure. */
+ TDB_LOCK_PROBE = 2,
+ /* If set, don't actually lock at all. */
+ TDB_LOCK_MARK_ONLY = 4,
+};
+
+struct tdb_methods {
+ int (*tdb_read)(struct tdb_context *, tdb_off_t , void *, tdb_len_t , int );
+ int (*tdb_write)(struct tdb_context *, tdb_off_t, const void *, tdb_len_t);
+ void (*next_hash_chain)(struct tdb_context *, uint32_t *);
+ int (*tdb_oob)(struct tdb_context *, tdb_off_t , tdb_len_t, int );
+ int (*tdb_expand_file)(struct tdb_context *, tdb_off_t , tdb_off_t );
+};
+
+struct tdb_mutexes;
+
+struct tdb_context {
+ char *name; /* the name of the database */
+ void *map_ptr; /* where it is currently mapped */
+ int fd; /* open file descriptor for the database */
+ tdb_len_t map_size; /* how much space has been mapped */
+ int read_only; /* opened read-only */
+ int traverse_read; /* read-only traversal */
+ int traverse_write; /* read-write traversal */
+ struct tdb_lock_type allrecord_lock; /* .offset == upgradable */
+ int num_lockrecs;
+ struct tdb_lock_type *lockrecs; /* only real locks, all with count>0 */
+ int lockrecs_array_length;
+
+ tdb_off_t hdr_ofs; /* this is 0 or header.mutex_size */
+ struct tdb_mutexes *mutexes; /* mmap of the mutex area */
+
+ enum TDB_ERROR ecode; /* error code for last tdb error */
+ uint32_t hash_size;
+ uint32_t feature_flags;
+ uint32_t flags; /* the flags passed to tdb_open */
+ struct tdb_traverse_lock travlocks; /* current traversal locks */
+ struct tdb_context *next; /* all tdbs to avoid multiple opens */
+ dev_t device; /* uniquely identifies this tdb */
+ ino_t inode; /* uniquely identifies this tdb */
+ struct tdb_logging_context log;
+ unsigned int (*hash_fn)(TDB_DATA *key);
+ int open_flags; /* flags used in the open - needed by reopen */
+ const struct tdb_methods *methods;
+ struct tdb_transaction *transaction;
+ int page_size;
+ int max_dead_records;
+#ifdef TDB_TRACE
+ int tracefd;
+#endif
+ volatile sig_atomic_t *interrupt_sig_ptr;
+};
+
+
+/*
+ internal prototypes
+*/
+int tdb_munmap(struct tdb_context *tdb);
+int tdb_mmap(struct tdb_context *tdb);
+int tdb_lock(struct tdb_context *tdb, int list, int ltype);
+int tdb_lock_nonblock(struct tdb_context *tdb, int list, int ltype);
+int tdb_nest_lock(struct tdb_context *tdb, uint32_t offset, int ltype,
+ enum tdb_lock_flags flags);
+int tdb_nest_unlock(struct tdb_context *tdb, uint32_t offset, int ltype,
+ bool mark_lock);
+int tdb_unlock(struct tdb_context *tdb, int list, int ltype);
+int tdb_brlock(struct tdb_context *tdb,
+ int rw_type, tdb_off_t offset, size_t len,
+ enum tdb_lock_flags flags);
+int tdb_brunlock(struct tdb_context *tdb,
+ int rw_type, tdb_off_t offset, size_t len);
+bool tdb_have_extra_locks(struct tdb_context *tdb);
+void tdb_release_transaction_locks(struct tdb_context *tdb);
+int tdb_transaction_lock(struct tdb_context *tdb, int ltype,
+ enum tdb_lock_flags lockflags);
+int tdb_transaction_unlock(struct tdb_context *tdb, int ltype);
+int tdb_recovery_area(struct tdb_context *tdb,
+ const struct tdb_methods *methods,
+ tdb_off_t *recovery_offset,
+ struct tdb_record *rec);
+int tdb_allrecord_lock(struct tdb_context *tdb, int ltype,
+ enum tdb_lock_flags flags, bool upgradable);
+int tdb_allrecord_unlock(struct tdb_context *tdb, int ltype, bool mark_lock);
+int tdb_allrecord_upgrade(struct tdb_context *tdb);
+int tdb_write_lock_record(struct tdb_context *tdb, tdb_off_t off);
+int tdb_write_unlock_record(struct tdb_context *tdb, tdb_off_t off);
+int tdb_ofs_read(struct tdb_context *tdb, tdb_off_t offset, tdb_off_t *d);
+int tdb_ofs_write(struct tdb_context *tdb, tdb_off_t offset, tdb_off_t *d);
+void *tdb_convert(void *buf, uint32_t size);
+int tdb_free(struct tdb_context *tdb, tdb_off_t offset, struct tdb_record *rec);
+tdb_off_t tdb_allocate(struct tdb_context *tdb, int hash, tdb_len_t length,
+ struct tdb_record *rec);
+
+int _tdb_oob(struct tdb_context *tdb, tdb_off_t off, tdb_len_t len, int probe);
+
+static inline int tdb_oob(
+ struct tdb_context *tdb, tdb_off_t off, tdb_len_t len, int probe)
+{
+ if (likely((off + len >= off) && (off + len <= tdb->map_size))) {
+ return 0;
+ }
+ return _tdb_oob(tdb, off, len, probe);
+}
+
+
+int tdb_ofs_read(struct tdb_context *tdb, tdb_off_t offset, tdb_off_t *d);
+int tdb_ofs_write(struct tdb_context *tdb, tdb_off_t offset, tdb_off_t *d);
+int tdb_lock_record(struct tdb_context *tdb, tdb_off_t off);
+int tdb_unlock_record(struct tdb_context *tdb, tdb_off_t off);
+bool tdb_needs_recovery(struct tdb_context *tdb);
+int tdb_rec_read(struct tdb_context *tdb, tdb_off_t offset, struct tdb_record *rec);
+int tdb_rec_write(struct tdb_context *tdb, tdb_off_t offset, struct tdb_record *rec);
+unsigned char *tdb_alloc_read(struct tdb_context *tdb, tdb_off_t offset, tdb_len_t len);
+int tdb_parse_data(struct tdb_context *tdb, TDB_DATA key,
+ tdb_off_t offset, tdb_len_t len,
+ int (*parser)(TDB_DATA key, TDB_DATA data,
+ void *private_data),
+ void *private_data);
+tdb_off_t tdb_find_lock_hash(struct tdb_context *tdb, TDB_DATA key, uint32_t hash, int locktype,
+ struct tdb_record *rec);
+tdb_off_t tdb_find_dead(struct tdb_context *tdb, uint32_t hash,
+ struct tdb_record *r, tdb_len_t length,
+ tdb_off_t *p_last_ptr);
+int tdb_trim_dead(struct tdb_context *tdb, uint32_t hash);
+void tdb_io_init(struct tdb_context *tdb);
+int tdb_expand(struct tdb_context *tdb, tdb_off_t size);
+tdb_off_t tdb_expand_adjust(tdb_off_t map_size, tdb_off_t size, int page_size);
+int tdb_rec_free_read(struct tdb_context *tdb, tdb_off_t off,
+ struct tdb_record *rec);
+bool tdb_write_all(int fd, const void *buf, size_t count);
+int tdb_transaction_recover(struct tdb_context *tdb);
+void tdb_header_hash(struct tdb_context *tdb,
+ uint32_t *magic1_hash, uint32_t *magic2_hash);
+unsigned int tdb_old_hash(TDB_DATA *key);
+size_t tdb_dead_space(struct tdb_context *tdb, tdb_off_t off);
+bool tdb_add_off_t(tdb_off_t a, tdb_off_t b, tdb_off_t *pret);
+
+/* tdb_off_t and tdb_len_t right now are both uint32_t */
+#define tdb_add_len_t tdb_add_off_t
+
+size_t tdb_mutex_size(struct tdb_context *tdb);
+bool tdb_have_mutexes(struct tdb_context *tdb);
+int tdb_mutex_init(struct tdb_context *tdb);
+int tdb_mutex_mmap(struct tdb_context *tdb);
+int tdb_mutex_munmap(struct tdb_context *tdb);
+bool tdb_mutex_lock(struct tdb_context *tdb, int rw, off_t off, off_t len,
+ bool waitflag, int *pret);
+bool tdb_mutex_unlock(struct tdb_context *tdb, int rw, off_t off, off_t len,
+ int *pret);
+int tdb_mutex_allrecord_lock(struct tdb_context *tdb, int ltype,
+ enum tdb_lock_flags flags);
+int tdb_mutex_allrecord_unlock(struct tdb_context *tdb);
+int tdb_mutex_allrecord_upgrade(struct tdb_context *tdb);
+void tdb_mutex_allrecord_downgrade(struct tdb_context *tdb);
+
+#endif /* TDB_PRIVATE_H */
diff --git a/lib/tdb/common/transaction.c b/lib/tdb/common/transaction.c
new file mode 100644
index 0000000..5ab38d0
--- /dev/null
+++ b/lib/tdb/common/transaction.c
@@ -0,0 +1,1388 @@
+ /*
+ Unix SMB/CIFS implementation.
+
+ trivial database library
+
+ Copyright (C) Andrew Tridgell 2005
+
+ ** NOTE! The following LGPL license applies to the tdb
+ ** library. This does NOT imply that all of Samba is released
+ ** under the LGPL
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 3 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "tdb_private.h"
+
+/*
+ transaction design:
+
+ - only allow a single transaction at a time per database. This makes
+ using the transaction API simpler, as otherwise the caller would
+ have to cope with temporary failures in transactions that conflict
+ with other current transactions
+
+ - keep the transaction recovery information in the same file as the
+ database, using a special 'transaction recovery' record pointed at
+ by the header. This removes the need for extra journal files as
+ used by some other databases
+
+ - dynamically allocated the transaction recover record, re-using it
+ for subsequent transactions. If a larger record is needed then
+ tdb_free() the old record to place it on the normal tdb freelist
+ before allocating the new record
+
+ - during transactions, keep a linked list of all writes that have
+ been performed by intercepting all tdb_write() calls. The hooked
+ transaction versions of tdb_read() and tdb_write() check this
+ linked list and try to use the elements of the list in preference
+ to the real database.
+
+ - don't allow any locks to be held when a transaction starts,
+ otherwise we can end up with deadlock (plus lack of lock nesting
+ in posix locks would mean the lock is lost)
+
+ - if the caller gains a lock during the transaction but doesn't
+ release it then fail the commit
+
+ - allow for nested calls to tdb_transaction_start(), re-using the
+ existing transaction record. If the inner transaction is cancelled
+ then a subsequent commit will fail
+
+ - keep a mirrored copy of the tdb hash chain heads to allow for the
+ fast hash heads scan on traverse, updating the mirrored copy in
+ the transaction version of tdb_write
+
+ - allow callers to mix transaction and non-transaction use of tdb,
+ although once a transaction is started then an exclusive lock is
+ gained until the transaction is committed or cancelled
+
+ - the commit stategy involves first saving away all modified data
+ into a linearised buffer in the transaction recovery area, then
+ marking the transaction recovery area with a magic value to
+ indicate a valid recovery record. In total 4 fsync/msync calls are
+ needed per commit to prevent race conditions. It might be possible
+ to reduce this to 3 or even 2 with some more work.
+
+ - check for a valid recovery record on open of the tdb, while the
+ open lock is held. Automatically recover from the transaction
+ recovery area if needed, then continue with the open as
+ usual. This allows for smooth crash recovery with no administrator
+ intervention.
+
+ - if TDB_NOSYNC is passed to flags in tdb_open then transactions are
+ still available, but no fsync/msync calls are made. This means we
+ are still proof against a process dying during transaction commit,
+ but not against machine reboot.
+
+ - if TDB_ALLOW_NESTING is passed to flags in tdb open, or added using
+ tdb_add_flags() transaction nesting is enabled.
+ It resets the TDB_DISALLOW_NESTING flag, as both cannot be used together.
+ The default is that transaction nesting is allowed.
+ Note: this default may change in future versions of tdb.
+
+ Beware. when transactions are nested a transaction successfully
+ completed with tdb_transaction_commit() can be silently unrolled later.
+
+ - if TDB_DISALLOW_NESTING is passed to flags in tdb open, or added using
+ tdb_add_flags() transaction nesting is disabled.
+ It resets the TDB_ALLOW_NESTING flag, as both cannot be used together.
+ An attempt create a nested transaction will fail with TDB_ERR_NESTING.
+ The default is that transaction nesting is allowed.
+ Note: this default may change in future versions of tdb.
+*/
+
+
+/*
+ hold the context of any current transaction
+*/
+struct tdb_transaction {
+ /* we keep a mirrored copy of the tdb hash heads here so
+ tdb_next_hash_chain() can operate efficiently */
+ uint32_t *hash_heads;
+
+ /* the original io methods - used to do IOs to the real db */
+ const struct tdb_methods *io_methods;
+
+ /* the list of transaction blocks. When a block is first
+ written to, it gets created in this list */
+ uint8_t **blocks;
+ uint32_t num_blocks;
+ uint32_t block_size; /* bytes in each block */
+ uint32_t last_block_size; /* number of valid bytes in the last block */
+
+ /* non-zero when an internal transaction error has
+ occurred. All write operations will then fail until the
+ transaction is ended */
+ int transaction_error;
+
+ /* when inside a transaction we need to keep track of any
+ nested tdb_transaction_start() calls, as these are allowed,
+ but don't create a new transaction */
+ int nesting;
+
+ /* set when a prepare has already occurred */
+ bool prepared;
+ tdb_off_t magic_offset;
+
+ /* old file size before transaction */
+ tdb_len_t old_map_size;
+
+ /* did we expand in this transaction */
+ bool expanded;
+};
+
+
+/*
+ read while in a transaction. We need to check first if the data is in our list
+ of transaction elements, then if not do a real read
+*/
+static int transaction_read(struct tdb_context *tdb, tdb_off_t off, void *buf,
+ tdb_len_t len, int cv)
+{
+ uint32_t blk;
+
+ /* break it down into block sized ops */
+ while (len + (off % tdb->transaction->block_size) > tdb->transaction->block_size) {
+ tdb_len_t len2 = tdb->transaction->block_size - (off % tdb->transaction->block_size);
+ if (transaction_read(tdb, off, buf, len2, cv) != 0) {
+ return -1;
+ }
+ len -= len2;
+ off += len2;
+ buf = (void *)(len2 + (char *)buf);
+ }
+
+ if (len == 0) {
+ return 0;
+ }
+
+ blk = off / tdb->transaction->block_size;
+
+ /* see if we have it in the block list */
+ if (tdb->transaction->num_blocks <= blk ||
+ tdb->transaction->blocks[blk] == NULL) {
+ /* nope, do a real read */
+ if (tdb->transaction->io_methods->tdb_read(tdb, off, buf, len, cv) != 0) {
+ goto fail;
+ }
+ return 0;
+ }
+
+ /* it is in the block list. Now check for the last block */
+ if (blk == tdb->transaction->num_blocks-1) {
+ if (len > tdb->transaction->last_block_size) {
+ goto fail;
+ }
+ }
+
+ /* now copy it out of this block */
+ memcpy(buf, tdb->transaction->blocks[blk] + (off % tdb->transaction->block_size), len);
+ if (cv) {
+ tdb_convert(buf, len);
+ }
+ return 0;
+
+fail:
+ TDB_LOG((tdb, TDB_DEBUG_FATAL, "transaction_read: failed at off=%u len=%u\n", off, len));
+ tdb->ecode = TDB_ERR_IO;
+ tdb->transaction->transaction_error = 1;
+ return -1;
+}
+
+
+/*
+ write while in a transaction
+*/
+static int transaction_write(struct tdb_context *tdb, tdb_off_t off,
+ const void *buf, tdb_len_t len)
+{
+ uint32_t blk;
+
+ if (buf == NULL) {
+ return -1;
+ }
+
+ /* Only a commit is allowed on a prepared transaction */
+ if (tdb->transaction->prepared) {
+ tdb->ecode = TDB_ERR_EINVAL;
+ TDB_LOG((tdb, TDB_DEBUG_FATAL, "transaction_write: transaction already prepared, write not allowed\n"));
+ tdb->transaction->transaction_error = 1;
+ return -1;
+ }
+
+ /* if the write is to a hash head, then update the transaction
+ hash heads */
+ if (len == sizeof(tdb_off_t) && off >= FREELIST_TOP &&
+ off < FREELIST_TOP+TDB_HASHTABLE_SIZE(tdb)) {
+ uint32_t chain = (off-FREELIST_TOP) / sizeof(tdb_off_t);
+ memcpy(&tdb->transaction->hash_heads[chain], buf, len);
+ }
+
+ /* break it up into block sized chunks */
+ while (len + (off % tdb->transaction->block_size) > tdb->transaction->block_size) {
+ tdb_len_t len2 = tdb->transaction->block_size - (off % tdb->transaction->block_size);
+ if (transaction_write(tdb, off, buf, len2) != 0) {
+ return -1;
+ }
+ len -= len2;
+ off += len2;
+ buf = (const void *)(len2 + (const char *)buf);
+ }
+
+ if (len == 0) {
+ return 0;
+ }
+
+ blk = off / tdb->transaction->block_size;
+ off = off % tdb->transaction->block_size;
+
+ if (tdb->transaction->num_blocks <= blk) {
+ uint8_t **new_blocks;
+ /* expand the blocks array */
+ new_blocks = (uint8_t **)realloc(tdb->transaction->blocks,
+ (blk+1)*sizeof(uint8_t *));
+ if (new_blocks == NULL) {
+ tdb->ecode = TDB_ERR_OOM;
+ goto fail;
+ }
+ memset(&new_blocks[tdb->transaction->num_blocks], 0,
+ (1+(blk - tdb->transaction->num_blocks))*sizeof(uint8_t *));
+ tdb->transaction->blocks = new_blocks;
+ tdb->transaction->num_blocks = blk+1;
+ tdb->transaction->last_block_size = 0;
+ }
+
+ /* allocate and fill a block? */
+ if (tdb->transaction->blocks[blk] == NULL) {
+ tdb->transaction->blocks[blk] = (uint8_t *)calloc(tdb->transaction->block_size, 1);
+ if (tdb->transaction->blocks[blk] == NULL) {
+ tdb->ecode = TDB_ERR_OOM;
+ tdb->transaction->transaction_error = 1;
+ return -1;
+ }
+ if (tdb->transaction->old_map_size > blk * tdb->transaction->block_size) {
+ tdb_len_t len2 = tdb->transaction->block_size;
+ if (len2 + (blk * tdb->transaction->block_size) > tdb->transaction->old_map_size) {
+ len2 = tdb->transaction->old_map_size - (blk * tdb->transaction->block_size);
+ }
+ if (tdb->transaction->io_methods->tdb_read(tdb, blk * tdb->transaction->block_size,
+ tdb->transaction->blocks[blk],
+ len2, 0) != 0) {
+ SAFE_FREE(tdb->transaction->blocks[blk]);
+ tdb->ecode = TDB_ERR_IO;
+ goto fail;
+ }
+ if (blk == tdb->transaction->num_blocks-1) {
+ tdb->transaction->last_block_size = len2;
+ }
+ }
+ }
+
+ /* overwrite part of an existing block */
+ memcpy(tdb->transaction->blocks[blk] + off, buf, len);
+ if (blk == tdb->transaction->num_blocks-1) {
+ if (len + off > tdb->transaction->last_block_size) {
+ tdb->transaction->last_block_size = len + off;
+ }
+ }
+
+ return 0;
+
+fail:
+ TDB_LOG((tdb, TDB_DEBUG_FATAL, "transaction_write: failed at off=%u len=%u\n",
+ (blk*tdb->transaction->block_size) + off, len));
+ tdb->transaction->transaction_error = 1;
+ return -1;
+}
+
+
+/*
+ write while in a transaction - this variant never expands the transaction blocks, it only
+ updates existing blocks. This means it cannot change the recovery size
+*/
+static int transaction_write_existing(struct tdb_context *tdb, tdb_off_t off,
+ const void *buf, tdb_len_t len)
+{
+ uint32_t blk;
+
+ /* break it up into block sized chunks */
+ while (len + (off % tdb->transaction->block_size) > tdb->transaction->block_size) {
+ tdb_len_t len2 = tdb->transaction->block_size - (off % tdb->transaction->block_size);
+ if (transaction_write_existing(tdb, off, buf, len2) != 0) {
+ return -1;
+ }
+ len -= len2;
+ off += len2;
+ if (buf != NULL) {
+ buf = (const void *)(len2 + (const char *)buf);
+ }
+ }
+
+ if (len == 0 || buf == NULL) {
+ return 0;
+ }
+
+ blk = off / tdb->transaction->block_size;
+ off = off % tdb->transaction->block_size;
+
+ if (tdb->transaction->num_blocks <= blk ||
+ tdb->transaction->blocks[blk] == NULL) {
+ return 0;
+ }
+
+ if (blk == tdb->transaction->num_blocks-1 &&
+ off + len > tdb->transaction->last_block_size) {
+ if (off >= tdb->transaction->last_block_size) {
+ return 0;
+ }
+ len = tdb->transaction->last_block_size - off;
+ }
+
+ /* overwrite part of an existing block */
+ memcpy(tdb->transaction->blocks[blk] + off, buf, len);
+
+ return 0;
+}
+
+
+/*
+ accelerated hash chain head search, using the cached hash heads
+*/
+static void transaction_next_hash_chain(struct tdb_context *tdb, uint32_t *chain)
+{
+ uint32_t h = *chain;
+ for (;h < tdb->hash_size;h++) {
+ /* the +1 takes account of the freelist */
+ if (0 != tdb->transaction->hash_heads[h+1]) {
+ break;
+ }
+ }
+ (*chain) = h;
+}
+
+/*
+ out of bounds check during a transaction
+*/
+static int transaction_oob(struct tdb_context *tdb, tdb_off_t off,
+ tdb_len_t len, int probe)
+{
+ /*
+ * This duplicates functionality from tdb_oob(). Don't remove:
+ * we still have direct callers of tdb->methods->tdb_oob()
+ * inside transaction.c.
+ */
+ if (off + len >= off && off + len <= tdb->map_size) {
+ return 0;
+ }
+ tdb->ecode = TDB_ERR_IO;
+ return -1;
+}
+
+/*
+ transaction version of tdb_expand().
+*/
+static int transaction_expand_file(struct tdb_context *tdb, tdb_off_t size,
+ tdb_off_t addition)
+{
+ const char buf_zero[8192] = {0};
+ size_t buf_len = sizeof(buf_zero);
+
+ while (addition > 0) {
+ size_t n = MIN(addition, buf_len);
+ int ret;
+
+ ret = transaction_write(tdb, size, buf_zero, n);
+ if (ret != 0) {
+ return ret;
+ }
+
+ addition -= n;
+ size += n;
+ }
+
+ tdb->transaction->expanded = true;
+
+ return 0;
+}
+
+static const struct tdb_methods transaction_methods = {
+ transaction_read,
+ transaction_write,
+ transaction_next_hash_chain,
+ transaction_oob,
+ transaction_expand_file,
+};
+
+/*
+ * Is a transaction currently active on this context?
+ *
+ */
+_PUBLIC_ bool tdb_transaction_active(struct tdb_context *tdb)
+{
+ return (tdb->transaction != NULL);
+}
+
+/*
+ start a tdb transaction. No token is returned, as only a single
+ transaction is allowed to be pending per tdb_context
+*/
+static int _tdb_transaction_start(struct tdb_context *tdb,
+ enum tdb_lock_flags lockflags)
+{
+ /* some sanity checks */
+ if (tdb->read_only || (tdb->flags & TDB_INTERNAL)
+ || tdb->traverse_read) {
+ TDB_LOG((tdb, TDB_DEBUG_ERROR, "tdb_transaction_start: cannot start a transaction on a read-only or internal db\n"));
+ tdb->ecode = TDB_ERR_EINVAL;
+ return -1;
+ }
+
+ /* cope with nested tdb_transaction_start() calls */
+ if (tdb->transaction != NULL) {
+ if (!(tdb->flags & TDB_ALLOW_NESTING)) {
+ tdb->ecode = TDB_ERR_NESTING;
+ return -1;
+ }
+ tdb->transaction->nesting++;
+ TDB_LOG((tdb, TDB_DEBUG_TRACE, "tdb_transaction_start: nesting %d\n",
+ tdb->transaction->nesting));
+ return 0;
+ }
+
+ if (tdb_have_extra_locks(tdb)) {
+ /* the caller must not have any locks when starting a
+ transaction as otherwise we'll be screwed by lack
+ of nested locks in posix */
+ TDB_LOG((tdb, TDB_DEBUG_ERROR, "tdb_transaction_start: cannot start a transaction with locks held\n"));
+ tdb->ecode = TDB_ERR_LOCK;
+ return -1;
+ }
+
+ if (tdb->travlocks.next != NULL) {
+ /* you cannot use transactions inside a traverse (although you can use
+ traverse inside a transaction) as otherwise you can end up with
+ deadlock */
+ TDB_LOG((tdb, TDB_DEBUG_ERROR, "tdb_transaction_start: cannot start a transaction within a traverse\n"));
+ tdb->ecode = TDB_ERR_LOCK;
+ return -1;
+ }
+
+ tdb->transaction = (struct tdb_transaction *)
+ calloc(sizeof(struct tdb_transaction), 1);
+ if (tdb->transaction == NULL) {
+ tdb->ecode = TDB_ERR_OOM;
+ return -1;
+ }
+
+ /* a page at a time seems like a reasonable compromise between compactness and efficiency */
+ tdb->transaction->block_size = tdb->page_size;
+
+ /* get the transaction write lock. This is a blocking lock. As
+ discussed with Volker, there are a number of ways we could
+ make this async, which we will probably do in the future */
+ if (tdb_transaction_lock(tdb, F_WRLCK, lockflags) == -1) {
+ SAFE_FREE(tdb->transaction->blocks);
+ SAFE_FREE(tdb->transaction);
+ if ((lockflags & TDB_LOCK_WAIT) == 0) {
+ tdb->ecode = TDB_ERR_NOLOCK;
+ } else {
+ TDB_LOG((tdb, TDB_DEBUG_ERROR,
+ "tdb_transaction_start: "
+ "failed to get transaction lock\n"));
+ }
+ return -1;
+ }
+
+ /* get a read lock from the freelist to the end of file. This
+ is upgraded to a write lock during the commit */
+ if (tdb_allrecord_lock(tdb, F_RDLCK, TDB_LOCK_WAIT, true) == -1) {
+ TDB_LOG((tdb, TDB_DEBUG_ERROR, "tdb_transaction_start: failed to get hash locks\n"));
+ goto fail_allrecord_lock;
+ }
+
+ /* setup a copy of the hash table heads so the hash scan in
+ traverse can be fast */
+ tdb->transaction->hash_heads = (uint32_t *)
+ calloc(tdb->hash_size+1, sizeof(uint32_t));
+ if (tdb->transaction->hash_heads == NULL) {
+ tdb->ecode = TDB_ERR_OOM;
+ goto fail;
+ }
+ if (tdb->methods->tdb_read(tdb, FREELIST_TOP, tdb->transaction->hash_heads,
+ TDB_HASHTABLE_SIZE(tdb), 0) != 0) {
+ TDB_LOG((tdb, TDB_DEBUG_FATAL, "tdb_transaction_start: failed to read hash heads\n"));
+ tdb->ecode = TDB_ERR_IO;
+ goto fail;
+ }
+
+ /* make sure we know about any file expansions already done by
+ anyone else */
+ tdb_oob(tdb, tdb->map_size, 1, 1);
+ tdb->transaction->old_map_size = tdb->map_size;
+
+ /* finally hook the io methods, replacing them with
+ transaction specific methods */
+ tdb->transaction->io_methods = tdb->methods;
+ tdb->methods = &transaction_methods;
+
+ /* Trace at the end, so we get sequence number correct. */
+ tdb_trace(tdb, "tdb_transaction_start");
+ return 0;
+
+fail:
+ tdb_allrecord_unlock(tdb, F_RDLCK, false);
+fail_allrecord_lock:
+ tdb_transaction_unlock(tdb, F_WRLCK);
+ SAFE_FREE(tdb->transaction->blocks);
+ SAFE_FREE(tdb->transaction->hash_heads);
+ SAFE_FREE(tdb->transaction);
+ return -1;
+}
+
+_PUBLIC_ int tdb_transaction_start(struct tdb_context *tdb)
+{
+ return _tdb_transaction_start(tdb, TDB_LOCK_WAIT);
+}
+
+_PUBLIC_ int tdb_transaction_start_nonblock(struct tdb_context *tdb)
+{
+ return _tdb_transaction_start(tdb, TDB_LOCK_NOWAIT|TDB_LOCK_PROBE);
+}
+
+/*
+ sync to disk
+*/
+static int transaction_sync(struct tdb_context *tdb, tdb_off_t offset, tdb_len_t length)
+{
+ if (tdb->flags & TDB_NOSYNC) {
+ return 0;
+ }
+
+#ifdef HAVE_FDATASYNC
+ if (fdatasync(tdb->fd) != 0) {
+#else
+ if (fsync(tdb->fd) != 0) {
+#endif
+ tdb->ecode = TDB_ERR_IO;
+ TDB_LOG((tdb, TDB_DEBUG_FATAL, "tdb_transaction: fsync failed\n"));
+ return -1;
+ }
+#ifdef HAVE_MMAP
+ if (tdb->map_ptr) {
+ tdb_off_t moffset = offset & ~(tdb->page_size-1);
+ if (msync(moffset + (char *)tdb->map_ptr,
+ length + (offset - moffset), MS_SYNC) != 0) {
+ tdb->ecode = TDB_ERR_IO;
+ TDB_LOG((tdb, TDB_DEBUG_FATAL, "tdb_transaction: msync failed - %s\n",
+ strerror(errno)));
+ return -1;
+ }
+ }
+#endif
+ return 0;
+}
+
+
+static int _tdb_transaction_cancel(struct tdb_context *tdb)
+{
+ uint32_t i;
+ int ret = 0;
+
+ if (tdb->transaction == NULL) {
+ TDB_LOG((tdb, TDB_DEBUG_ERROR, "tdb_transaction_cancel: no transaction\n"));
+ return -1;
+ }
+
+ if (tdb->transaction->nesting != 0) {
+ tdb->transaction->transaction_error = 1;
+ tdb->transaction->nesting--;
+ return 0;
+ }
+
+ tdb->map_size = tdb->transaction->old_map_size;
+
+ /* free all the transaction blocks */
+ for (i=0;i<tdb->transaction->num_blocks;i++) {
+ if ((tdb->transaction->blocks != NULL) &&
+ tdb->transaction->blocks[i] != NULL) {
+ free(tdb->transaction->blocks[i]);
+ }
+ }
+ SAFE_FREE(tdb->transaction->blocks);
+
+ if (tdb->transaction->magic_offset) {
+ const struct tdb_methods *methods = tdb->transaction->io_methods;
+ const uint32_t invalid = TDB_RECOVERY_INVALID_MAGIC;
+
+ /* remove the recovery marker */
+ if (methods->tdb_write(tdb, tdb->transaction->magic_offset, &invalid, 4) == -1 ||
+ transaction_sync(tdb, tdb->transaction->magic_offset, 4) == -1) {
+ TDB_LOG((tdb, TDB_DEBUG_FATAL, "tdb_transaction_cancel: failed to remove recovery magic\n"));
+ ret = -1;
+ }
+ }
+
+ /* This also removes the OPEN_LOCK, if we have it. */
+ tdb_release_transaction_locks(tdb);
+
+ /* restore the normal io methods */
+ tdb->methods = tdb->transaction->io_methods;
+
+ SAFE_FREE(tdb->transaction->hash_heads);
+ SAFE_FREE(tdb->transaction);
+
+ return ret;
+}
+
+/*
+ cancel the current transaction
+*/
+_PUBLIC_ int tdb_transaction_cancel(struct tdb_context *tdb)
+{
+ tdb_trace(tdb, "tdb_transaction_cancel");
+ return _tdb_transaction_cancel(tdb);
+}
+
+/*
+ work out how much space the linearised recovery data will consume
+*/
+static bool tdb_recovery_size(struct tdb_context *tdb, tdb_len_t *result)
+{
+ tdb_len_t recovery_size = 0;
+ uint32_t i;
+
+ recovery_size = sizeof(uint32_t);
+ for (i=0;i<tdb->transaction->num_blocks;i++) {
+ tdb_len_t block_size;
+ if (i * tdb->transaction->block_size >= tdb->transaction->old_map_size) {
+ break;
+ }
+ if (tdb->transaction->blocks[i] == NULL) {
+ continue;
+ }
+ if (!tdb_add_len_t(recovery_size, 2*sizeof(tdb_off_t),
+ &recovery_size)) {
+ return false;
+ }
+ if (i == tdb->transaction->num_blocks-1) {
+ block_size = tdb->transaction->last_block_size;
+ } else {
+ block_size = tdb->transaction->block_size;
+ }
+ if (!tdb_add_len_t(recovery_size, block_size,
+ &recovery_size)) {
+ return false;
+ }
+ }
+
+ *result = recovery_size;
+ return true;
+}
+
+int tdb_recovery_area(struct tdb_context *tdb,
+ const struct tdb_methods *methods,
+ tdb_off_t *recovery_offset,
+ struct tdb_record *rec)
+{
+ int ret;
+
+ if (tdb_ofs_read(tdb, TDB_RECOVERY_HEAD, recovery_offset) == -1) {
+ return -1;
+ }
+
+ if (*recovery_offset == 0) {
+ rec->rec_len = 0;
+ return 0;
+ }
+
+ if (methods->tdb_read(tdb, *recovery_offset, rec, sizeof(*rec),
+ DOCONV()) == -1) {
+ return -1;
+ }
+
+ /* ignore invalid recovery regions: can happen in crash */
+ if (rec->magic != TDB_RECOVERY_MAGIC &&
+ rec->magic != TDB_RECOVERY_INVALID_MAGIC) {
+ *recovery_offset = 0;
+ rec->rec_len = 0;
+ }
+
+ ret = methods->tdb_oob(tdb, *recovery_offset, rec->rec_len, 1);
+ if (ret == -1) {
+ *recovery_offset = 0;
+ rec->rec_len = 0;
+ }
+
+ return 0;
+}
+
+/*
+ allocate the recovery area, or use an existing recovery area if it is
+ large enough
+*/
+static int tdb_recovery_allocate(struct tdb_context *tdb,
+ tdb_len_t *recovery_size,
+ tdb_off_t *recovery_offset,
+ tdb_len_t *recovery_max_size)
+{
+ struct tdb_record rec;
+ const struct tdb_methods *methods = tdb->transaction->io_methods;
+ tdb_off_t recovery_head, new_end;
+
+ if (tdb_recovery_area(tdb, methods, &recovery_head, &rec) == -1) {
+ TDB_LOG((tdb, TDB_DEBUG_FATAL, "tdb_recovery_allocate: failed to read recovery head\n"));
+ return -1;
+ }
+
+ if (!tdb_recovery_size(tdb, recovery_size)) {
+ TDB_LOG((tdb, TDB_DEBUG_FATAL, "tdb_recovery_allocate: "
+ "overflow recovery size\n"));
+ return -1;
+ }
+
+ /* Existing recovery area? */
+ if (recovery_head != 0 && *recovery_size <= rec.rec_len) {
+ /* it fits in the existing area */
+ *recovery_max_size = rec.rec_len;
+ *recovery_offset = recovery_head;
+ return 0;
+ }
+
+ /* If recovery area in middle of file, we need a new one. */
+ if (recovery_head == 0
+ || recovery_head + sizeof(rec) + rec.rec_len != tdb->map_size) {
+ /* we need to free up the old recovery area, then allocate a
+ new one at the end of the file. Note that we cannot use
+ tdb_allocate() to allocate the new one as that might return
+ us an area that is being currently used (as of the start of
+ the transaction) */
+ if (recovery_head) {
+ if (tdb_free(tdb, recovery_head, &rec) == -1) {
+ TDB_LOG((tdb, TDB_DEBUG_FATAL,
+ "tdb_recovery_allocate: failed to"
+ " free previous recovery area\n"));
+ return -1;
+ }
+
+ /* the tdb_free() call might have increased
+ * the recovery size */
+ if (!tdb_recovery_size(tdb, recovery_size)) {
+ TDB_LOG((tdb, TDB_DEBUG_FATAL,
+ "tdb_recovery_allocate: "
+ "overflow recovery size\n"));
+ return -1;
+ }
+ }
+
+ /* New head will be at end of file. */
+ recovery_head = tdb->map_size;
+ }
+
+ /* Now we know where it will be. */
+ *recovery_offset = recovery_head;
+
+ /* Expand by more than we need, so we don't do it often. */
+ *recovery_max_size = tdb_expand_adjust(tdb->map_size,
+ *recovery_size,
+ tdb->page_size)
+ - sizeof(rec);
+
+ if (!tdb_add_off_t(recovery_head, sizeof(rec), &new_end) ||
+ !tdb_add_off_t(new_end, *recovery_max_size, &new_end)) {
+ TDB_LOG((tdb, TDB_DEBUG_FATAL, "tdb_recovery_allocate: "
+ "overflow recovery area\n"));
+ return -1;
+ }
+
+ if (methods->tdb_expand_file(tdb, tdb->transaction->old_map_size,
+ new_end - tdb->transaction->old_map_size)
+ == -1) {
+ TDB_LOG((tdb, TDB_DEBUG_FATAL, "tdb_recovery_allocate: failed to create recovery area\n"));
+ return -1;
+ }
+
+ /* remap the file (if using mmap) */
+ methods->tdb_oob(tdb, tdb->map_size, 1, 1);
+
+ /* we have to reset the old map size so that we don't try to expand the file
+ again in the transaction commit, which would destroy the recovery area */
+ tdb->transaction->old_map_size = tdb->map_size;
+
+ /* write the recovery header offset and sync - we can sync without a race here
+ as the magic ptr in the recovery record has not been set */
+ CONVERT(recovery_head);
+ if (methods->tdb_write(tdb, TDB_RECOVERY_HEAD,
+ &recovery_head, sizeof(tdb_off_t)) == -1) {
+ TDB_LOG((tdb, TDB_DEBUG_FATAL, "tdb_recovery_allocate: failed to write recovery head\n"));
+ return -1;
+ }
+ if (transaction_write_existing(tdb, TDB_RECOVERY_HEAD, &recovery_head, sizeof(tdb_off_t)) == -1) {
+ TDB_LOG((tdb, TDB_DEBUG_FATAL, "tdb_recovery_allocate: failed to write recovery head\n"));
+ return -1;
+ }
+
+ return 0;
+}
+
+
+/*
+ setup the recovery data that will be used on a crash during commit
+*/
+static int transaction_setup_recovery(struct tdb_context *tdb,
+ tdb_off_t *magic_offset)
+{
+ tdb_len_t recovery_size;
+ unsigned char *data, *p;
+ const struct tdb_methods *methods = tdb->transaction->io_methods;
+ struct tdb_record *rec;
+ tdb_off_t recovery_offset, recovery_max_size;
+ tdb_off_t old_map_size = tdb->transaction->old_map_size;
+ uint32_t magic, tailer;
+ uint32_t i;
+
+ /*
+ check that the recovery area has enough space
+ */
+ if (tdb_recovery_allocate(tdb, &recovery_size,
+ &recovery_offset, &recovery_max_size) == -1) {
+ return -1;
+ }
+
+ rec = malloc(recovery_size + sizeof(*rec));
+ if (rec == NULL) {
+ tdb->ecode = TDB_ERR_OOM;
+ return -1;
+ }
+
+ memset(rec, 0, sizeof(*rec));
+
+ rec->magic = TDB_RECOVERY_INVALID_MAGIC;
+ rec->data_len = recovery_size;
+ rec->rec_len = recovery_max_size;
+ rec->key_len = old_map_size;
+ CONVERT(*rec);
+
+ data = (unsigned char *)rec;
+
+ /* build the recovery data into a single blob to allow us to do a single
+ large write, which should be more efficient */
+ p = data + sizeof(*rec);
+ for (i=0;i<tdb->transaction->num_blocks;i++) {
+ tdb_off_t offset;
+ tdb_len_t length;
+
+ if (tdb->transaction->blocks[i] == NULL) {
+ continue;
+ }
+
+ offset = i * tdb->transaction->block_size;
+ length = tdb->transaction->block_size;
+ if (i == tdb->transaction->num_blocks-1) {
+ length = tdb->transaction->last_block_size;
+ }
+
+ if (offset >= old_map_size) {
+ continue;
+ }
+ if (offset + length > tdb->transaction->old_map_size) {
+ TDB_LOG((tdb, TDB_DEBUG_FATAL, "tdb_transaction_setup_recovery: transaction data over new region boundary\n"));
+ free(data);
+ tdb->ecode = TDB_ERR_CORRUPT;
+ return -1;
+ }
+ memcpy(p, &offset, 4);
+ memcpy(p+4, &length, 4);
+ if (DOCONV()) {
+ tdb_convert(p, 8);
+ }
+ /* the recovery area contains the old data, not the
+ new data, so we have to call the original tdb_read
+ method to get it */
+ if (methods->tdb_read(tdb, offset, p + 8, length, 0) != 0) {
+ free(data);
+ tdb->ecode = TDB_ERR_IO;
+ return -1;
+ }
+ p += 8 + length;
+ }
+
+ /* and the tailer */
+ tailer = sizeof(*rec) + recovery_max_size;
+ memcpy(p, &tailer, 4);
+ if (DOCONV()) {
+ tdb_convert(p, 4);
+ }
+
+ /* write the recovery data to the recovery area */
+ if (methods->tdb_write(tdb, recovery_offset, data, sizeof(*rec) + recovery_size) == -1) {
+ TDB_LOG((tdb, TDB_DEBUG_FATAL, "tdb_transaction_setup_recovery: failed to write recovery data\n"));
+ free(data);
+ tdb->ecode = TDB_ERR_IO;
+ return -1;
+ }
+ if (transaction_write_existing(tdb, recovery_offset, data, sizeof(*rec) + recovery_size) == -1) {
+ TDB_LOG((tdb, TDB_DEBUG_FATAL, "tdb_transaction_setup_recovery: failed to write secondary recovery data\n"));
+ free(data);
+ tdb->ecode = TDB_ERR_IO;
+ return -1;
+ }
+
+ /* as we don't have ordered writes, we have to sync the recovery
+ data before we update the magic to indicate that the recovery
+ data is present */
+ if (transaction_sync(tdb, recovery_offset, sizeof(*rec) + recovery_size) == -1) {
+ free(data);
+ return -1;
+ }
+
+ free(data);
+
+ magic = TDB_RECOVERY_MAGIC;
+ CONVERT(magic);
+
+ *magic_offset = recovery_offset + offsetof(struct tdb_record, magic);
+
+ if (methods->tdb_write(tdb, *magic_offset, &magic, sizeof(magic)) == -1) {
+ TDB_LOG((tdb, TDB_DEBUG_FATAL, "tdb_transaction_setup_recovery: failed to write recovery magic\n"));
+ tdb->ecode = TDB_ERR_IO;
+ return -1;
+ }
+ if (transaction_write_existing(tdb, *magic_offset, &magic, sizeof(magic)) == -1) {
+ TDB_LOG((tdb, TDB_DEBUG_FATAL, "tdb_transaction_setup_recovery: failed to write secondary recovery magic\n"));
+ tdb->ecode = TDB_ERR_IO;
+ return -1;
+ }
+
+ /* ensure the recovery magic marker is on disk */
+ if (transaction_sync(tdb, *magic_offset, sizeof(magic)) == -1) {
+ return -1;
+ }
+
+ return 0;
+}
+
+static int _tdb_transaction_prepare_commit(struct tdb_context *tdb)
+{
+ const struct tdb_methods *methods;
+
+ if (tdb->transaction == NULL) {
+ TDB_LOG((tdb, TDB_DEBUG_ERROR, "tdb_transaction_prepare_commit: no transaction\n"));
+ return -1;
+ }
+
+ if (tdb->transaction->prepared) {
+ tdb->ecode = TDB_ERR_EINVAL;
+ _tdb_transaction_cancel(tdb);
+ TDB_LOG((tdb, TDB_DEBUG_ERROR, "tdb_transaction_prepare_commit: transaction already prepared\n"));
+ return -1;
+ }
+
+ if (tdb->transaction->transaction_error) {
+ tdb->ecode = TDB_ERR_IO;
+ _tdb_transaction_cancel(tdb);
+ TDB_LOG((tdb, TDB_DEBUG_ERROR, "tdb_transaction_prepare_commit: transaction error pending\n"));
+ return -1;
+ }
+
+
+ if (tdb->transaction->nesting != 0) {
+ return 0;
+ }
+
+ /* check for a null transaction */
+ if (tdb->transaction->blocks == NULL) {
+ return 0;
+ }
+
+ methods = tdb->transaction->io_methods;
+
+ /* if there are any locks pending then the caller has not
+ nested their locks properly, so fail the transaction */
+ if (tdb_have_extra_locks(tdb)) {
+ tdb->ecode = TDB_ERR_LOCK;
+ TDB_LOG((tdb, TDB_DEBUG_ERROR, "tdb_transaction_prepare_commit: locks pending on commit\n"));
+ _tdb_transaction_cancel(tdb);
+ return -1;
+ }
+
+ /* upgrade the main transaction lock region to a write lock */
+ if (tdb_allrecord_upgrade(tdb) == -1) {
+ if (tdb->ecode == TDB_ERR_RDONLY && tdb->read_only) {
+ TDB_LOG((tdb, TDB_DEBUG_ERROR,
+ "tdb_transaction_prepare_commit: "
+ "failed to upgrade hash locks: "
+ "database is read only\n"));
+ } else if (tdb->ecode == TDB_ERR_RDONLY
+ && tdb->traverse_read) {
+ TDB_LOG((tdb, TDB_DEBUG_ERROR,
+ "tdb_transaction_prepare_commit: "
+ "failed to upgrade hash locks: "
+ "a database traverse is in progress\n"));
+ } else {
+ TDB_LOG((tdb, TDB_DEBUG_ERROR,
+ "tdb_transaction_prepare_commit: "
+ "failed to upgrade hash locks: %s\n",
+ tdb_errorstr(tdb)));
+ }
+ _tdb_transaction_cancel(tdb);
+ return -1;
+ }
+
+ /* get the open lock - this prevents new users attaching to the database
+ during the commit */
+ if (tdb_nest_lock(tdb, OPEN_LOCK, F_WRLCK, TDB_LOCK_WAIT) == -1) {
+ TDB_LOG((tdb, TDB_DEBUG_ERROR, "tdb_transaction_prepare_commit: failed to get open lock\n"));
+ _tdb_transaction_cancel(tdb);
+ return -1;
+ }
+
+ /* write the recovery data to the end of the file */
+ if (transaction_setup_recovery(tdb, &tdb->transaction->magic_offset) == -1) {
+ TDB_LOG((tdb, TDB_DEBUG_FATAL, "tdb_transaction_prepare_commit: failed to setup recovery data\n"));
+ _tdb_transaction_cancel(tdb);
+ return -1;
+ }
+
+ tdb->transaction->prepared = true;
+
+ /* expand the file to the new size if needed */
+ if (tdb->map_size != tdb->transaction->old_map_size) {
+ if (methods->tdb_expand_file(tdb, tdb->transaction->old_map_size,
+ tdb->map_size -
+ tdb->transaction->old_map_size) == -1) {
+ tdb->ecode = TDB_ERR_IO;
+ TDB_LOG((tdb, TDB_DEBUG_FATAL, "tdb_transaction_prepare_commit: expansion failed\n"));
+ _tdb_transaction_cancel(tdb);
+ return -1;
+ }
+ tdb->map_size = tdb->transaction->old_map_size;
+ methods->tdb_oob(tdb, tdb->map_size, 1, 1);
+ }
+
+ /* Keep the open lock until the actual commit */
+
+ return 0;
+}
+
+/*
+ prepare to commit the current transaction
+*/
+_PUBLIC_ int tdb_transaction_prepare_commit(struct tdb_context *tdb)
+{
+ tdb_trace(tdb, "tdb_transaction_prepare_commit");
+ return _tdb_transaction_prepare_commit(tdb);
+}
+
+/* A repack is worthwhile if the largest is less than half total free. */
+static bool repack_worthwhile(struct tdb_context *tdb)
+{
+ tdb_off_t ptr;
+ struct tdb_record rec;
+ tdb_len_t total = 0, largest = 0;
+
+ if (tdb_ofs_read(tdb, FREELIST_TOP, &ptr) == -1) {
+ return false;
+ }
+
+ while (ptr != 0 && tdb_rec_free_read(tdb, ptr, &rec) == 0) {
+ total += rec.rec_len;
+ if (rec.rec_len > largest) {
+ largest = rec.rec_len;
+ }
+ ptr = rec.next;
+ }
+
+ return total > largest * 2;
+}
+
+/*
+ commit the current transaction
+*/
+_PUBLIC_ int tdb_transaction_commit(struct tdb_context *tdb)
+{
+ const struct tdb_methods *methods;
+ uint32_t i;
+ bool need_repack = false;
+
+ if (tdb->transaction == NULL) {
+ TDB_LOG((tdb, TDB_DEBUG_ERROR, "tdb_transaction_commit: no transaction\n"));
+ return -1;
+ }
+
+ tdb_trace(tdb, "tdb_transaction_commit");
+
+ if (tdb->transaction->transaction_error) {
+ tdb->ecode = TDB_ERR_IO;
+ _tdb_transaction_cancel(tdb);
+ TDB_LOG((tdb, TDB_DEBUG_ERROR, "tdb_transaction_commit: transaction error pending\n"));
+ return -1;
+ }
+
+
+ if (tdb->transaction->nesting != 0) {
+ tdb->transaction->nesting--;
+ return 0;
+ }
+
+ /* check for a null transaction */
+ if (tdb->transaction->blocks == NULL) {
+ _tdb_transaction_cancel(tdb);
+ return 0;
+ }
+
+ if (!tdb->transaction->prepared) {
+ int ret = _tdb_transaction_prepare_commit(tdb);
+ if (ret)
+ return ret;
+ }
+
+ methods = tdb->transaction->io_methods;
+
+ /* perform all the writes */
+ for (i=0;i<tdb->transaction->num_blocks;i++) {
+ tdb_off_t offset;
+ tdb_len_t length;
+
+ if (tdb->transaction->blocks[i] == NULL) {
+ continue;
+ }
+
+ offset = i * tdb->transaction->block_size;
+ length = tdb->transaction->block_size;
+ if (i == tdb->transaction->num_blocks-1) {
+ length = tdb->transaction->last_block_size;
+ }
+
+ if (methods->tdb_write(tdb, offset, tdb->transaction->blocks[i], length) == -1) {
+ TDB_LOG((tdb, TDB_DEBUG_FATAL, "tdb_transaction_commit: write failed during commit\n"));
+
+ /* we've overwritten part of the data and
+ possibly expanded the file, so we need to
+ run the crash recovery code */
+ tdb->methods = methods;
+ tdb_transaction_recover(tdb);
+
+ _tdb_transaction_cancel(tdb);
+
+ TDB_LOG((tdb, TDB_DEBUG_FATAL, "tdb_transaction_commit: write failed\n"));
+ return -1;
+ }
+ SAFE_FREE(tdb->transaction->blocks[i]);
+ }
+
+ /* Do this before we drop lock or blocks. */
+ if (tdb->transaction->expanded) {
+ need_repack = repack_worthwhile(tdb);
+ }
+
+ SAFE_FREE(tdb->transaction->blocks);
+ tdb->transaction->num_blocks = 0;
+
+ /* ensure the new data is on disk */
+ if (transaction_sync(tdb, 0, tdb->map_size) == -1) {
+ return -1;
+ }
+
+ /*
+ TODO: maybe write to some dummy hdr field, or write to magic
+ offset without mmap, before the last sync, instead of the
+ utime() call
+ */
+
+ /* on some systems (like Linux 2.6.x) changes via mmap/msync
+ don't change the mtime of the file, this means the file may
+ not be backed up (as tdb rounding to block sizes means that
+ file size changes are quite rare too). The following forces
+ mtime changes when a transaction completes */
+#ifdef HAVE_UTIME
+ utime(tdb->name, NULL);
+#endif
+
+ /* use a transaction cancel to free memory and remove the
+ transaction locks */
+ _tdb_transaction_cancel(tdb);
+
+ if (need_repack) {
+ int ret = tdb_repack(tdb);
+ if (ret != 0) {
+ TDB_LOG((tdb, TDB_DEBUG_FATAL,
+ __location__ " Failed to repack database (not fatal)\n"));
+ }
+ /*
+ * Ignore the error.
+ *
+ * Why?
+ *
+ * We just committed to the DB above, so anything
+ * written during the transaction is committed, the
+ * caller needs to know that the long-term state was
+ * successfully modified.
+ *
+ * tdb_repack is an optimization that can fail for
+ * reasons like lock ordering and we cannot recover
+ * the transaction lock at this point, having released
+ * it above.
+ *
+ * If we return a failure the caller thinks the
+ * transaction was rolled back.
+ */
+ }
+
+ return 0;
+}
+
+
+/*
+ recover from an aborted transaction. Must be called with exclusive
+ database write access already established (including the open
+ lock to prevent new processes attaching)
+*/
+int tdb_transaction_recover(struct tdb_context *tdb)
+{
+ tdb_off_t recovery_head, recovery_eof;
+ unsigned char *data, *p;
+ uint32_t zero = 0;
+ struct tdb_record rec;
+
+ /* find the recovery area */
+ if (tdb_ofs_read(tdb, TDB_RECOVERY_HEAD, &recovery_head) == -1) {
+ TDB_LOG((tdb, TDB_DEBUG_FATAL, "tdb_transaction_recover: failed to read recovery head\n"));
+ tdb->ecode = TDB_ERR_IO;
+ return -1;
+ }
+
+ if (recovery_head == 0) {
+ /* we have never allocated a recovery record */
+ return 0;
+ }
+
+ /* read the recovery record */
+ if (tdb->methods->tdb_read(tdb, recovery_head, &rec,
+ sizeof(rec), DOCONV()) == -1) {
+ TDB_LOG((tdb, TDB_DEBUG_FATAL, "tdb_transaction_recover: failed to read recovery record\n"));
+ tdb->ecode = TDB_ERR_IO;
+ return -1;
+ }
+
+ if (rec.magic != TDB_RECOVERY_MAGIC) {
+ /* there is no valid recovery data */
+ return 0;
+ }
+
+ if (tdb->read_only) {
+ TDB_LOG((tdb, TDB_DEBUG_FATAL, "tdb_transaction_recover: attempt to recover read only database\n"));
+ tdb->ecode = TDB_ERR_CORRUPT;
+ return -1;
+ }
+
+ recovery_eof = rec.key_len;
+
+ data = (unsigned char *)malloc(rec.data_len);
+ if (data == NULL) {
+ TDB_LOG((tdb, TDB_DEBUG_FATAL, "tdb_transaction_recover: failed to allocate recovery data\n"));
+ tdb->ecode = TDB_ERR_OOM;
+ return -1;
+ }
+
+ /* read the full recovery data */
+ if (tdb->methods->tdb_read(tdb, recovery_head + sizeof(rec), data,
+ rec.data_len, 0) == -1) {
+ TDB_LOG((tdb, TDB_DEBUG_FATAL, "tdb_transaction_recover: failed to read recovery data\n"));
+ tdb->ecode = TDB_ERR_IO;
+ free(data);
+ return -1;
+ }
+
+ /* recover the file data */
+ p = data;
+ while (p+8 < data + rec.data_len) {
+ uint32_t ofs, len;
+ if (DOCONV()) {
+ tdb_convert(p, 8);
+ }
+ memcpy(&ofs, p, 4);
+ memcpy(&len, p+4, 4);
+
+ if (tdb->methods->tdb_write(tdb, ofs, p+8, len) == -1) {
+ free(data);
+ TDB_LOG((tdb, TDB_DEBUG_FATAL, "tdb_transaction_recover: failed to recover %u bytes at offset %u\n", len, ofs));
+ tdb->ecode = TDB_ERR_IO;
+ return -1;
+ }
+ p += 8 + len;
+ }
+
+ free(data);
+
+ if (transaction_sync(tdb, 0, tdb->map_size) == -1) {
+ TDB_LOG((tdb, TDB_DEBUG_FATAL, "tdb_transaction_recover: failed to sync recovery\n"));
+ tdb->ecode = TDB_ERR_IO;
+ return -1;
+ }
+
+ /* if the recovery area is after the recovered eof then remove it */
+ if (recovery_eof <= recovery_head) {
+ if (tdb_ofs_write(tdb, TDB_RECOVERY_HEAD, &zero) == -1) {
+ TDB_LOG((tdb, TDB_DEBUG_FATAL, "tdb_transaction_recover: failed to remove recovery head\n"));
+ tdb->ecode = TDB_ERR_IO;
+ return -1;
+ }
+ }
+
+ /* remove the recovery magic */
+ if (tdb_ofs_write(tdb, recovery_head + offsetof(struct tdb_record, magic),
+ &zero) == -1) {
+ TDB_LOG((tdb, TDB_DEBUG_FATAL, "tdb_transaction_recover: failed to remove recovery magic\n"));
+ tdb->ecode = TDB_ERR_IO;
+ return -1;
+ }
+
+ if (transaction_sync(tdb, 0, recovery_eof) == -1) {
+ TDB_LOG((tdb, TDB_DEBUG_FATAL, "tdb_transaction_recover: failed to sync2 recovery\n"));
+ tdb->ecode = TDB_ERR_IO;
+ return -1;
+ }
+
+ TDB_LOG((tdb, TDB_DEBUG_TRACE, "tdb_transaction_recover: recovered %u byte database\n",
+ recovery_eof));
+
+ /* all done */
+ return 0;
+}
+
+/* Any I/O failures we say "needs recovery". */
+bool tdb_needs_recovery(struct tdb_context *tdb)
+{
+ tdb_off_t recovery_head;
+ struct tdb_record rec;
+
+ /* find the recovery area */
+ if (tdb_ofs_read(tdb, TDB_RECOVERY_HEAD, &recovery_head) == -1) {
+ return true;
+ }
+
+ if (recovery_head == 0) {
+ /* we have never allocated a recovery record */
+ return false;
+ }
+
+ /* read the recovery record */
+ if (tdb->methods->tdb_read(tdb, recovery_head, &rec,
+ sizeof(rec), DOCONV()) == -1) {
+ return true;
+ }
+
+ return (rec.magic == TDB_RECOVERY_MAGIC);
+}
diff --git a/lib/tdb/common/traverse.c b/lib/tdb/common/traverse.c
new file mode 100644
index 0000000..d69e7df
--- /dev/null
+++ b/lib/tdb/common/traverse.c
@@ -0,0 +1,510 @@
+ /*
+ Unix SMB/CIFS implementation.
+
+ trivial database library
+
+ Copyright (C) Andrew Tridgell 1999-2005
+ Copyright (C) Paul `Rusty' Russell 2000
+ Copyright (C) Jeremy Allison 2000-2003
+
+ ** NOTE! The following LGPL license applies to the tdb
+ ** library. This does NOT imply that all of Samba is released
+ ** under the LGPL
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 3 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "tdb_private.h"
+
+#define TDB_NEXT_LOCK_ERR ((tdb_off_t)-1)
+
+/* Uses traverse lock: 0 = finish, TDB_NEXT_LOCK_ERR = error,
+ other = record offset */
+static tdb_off_t tdb_next_lock(struct tdb_context *tdb, struct tdb_traverse_lock *tlock,
+ struct tdb_record *rec)
+{
+ int want_next = (tlock->off != 0);
+
+ /* Lock each chain from the start one. */
+ for (; tlock->list < tdb->hash_size; tlock->list++) {
+ if (!tlock->off && tlock->list != 0) {
+ /* this is an optimisation for the common case where
+ the hash chain is empty, which is particularly
+ common for the use of tdb with ldb, where large
+ hashes are used. In that case we spend most of our
+ time in tdb_brlock(), locking empty hash chains.
+
+ To avoid this, we do an unlocked pre-check to see
+ if the hash chain is empty before starting to look
+ inside it. If it is empty then we can avoid that
+ hash chain. If it isn't empty then we can't believe
+ the value we get back, as we read it without a
+ lock, so instead we get the lock and re-fetch the
+ value below.
+
+ Notice that not doing this optimisation on the
+ first hash chain is critical. We must guarantee
+ that we have done at least one fcntl lock at the
+ start of a search to guarantee that memory is
+ coherent on SMP systems. If records are added by
+ others during the search then thats OK, and we
+ could possibly miss those with this trick, but we
+ could miss them anyway without this trick, so the
+ semantics don't change.
+
+ With a non-indexed ldb search this trick gains us a
+ factor of around 80 in speed on a linux 2.6.x
+ system (testing using ldbtest).
+ */
+ tdb->methods->next_hash_chain(tdb, &tlock->list);
+ if (tlock->list == tdb->hash_size) {
+ continue;
+ }
+ }
+
+ if (tdb_lock(tdb, tlock->list, tlock->lock_rw) == -1)
+ return TDB_NEXT_LOCK_ERR;
+
+ /* No previous record? Start at top of chain. */
+ if (!tlock->off) {
+ if (tdb_ofs_read(tdb, TDB_HASH_TOP(tlock->list),
+ &tlock->off) == -1)
+ goto fail;
+ } else {
+ /* Otherwise unlock the previous record. */
+ if (tdb_unlock_record(tdb, tlock->off) != 0)
+ goto fail;
+ }
+
+ if (want_next) {
+ /* We have offset of old record: grab next */
+ if (tdb_rec_read(tdb, tlock->off, rec) == -1)
+ goto fail;
+ tlock->off = rec->next;
+ }
+
+ /* Iterate through chain */
+ while( tlock->off) {
+ if (tdb_rec_read(tdb, tlock->off, rec) == -1)
+ goto fail;
+
+ /* Detect infinite loops. From "Shlomi Yaakobovich" <Shlomi@exanet.com>. */
+ if (tlock->off == rec->next) {
+ tdb->ecode = TDB_ERR_CORRUPT;
+ TDB_LOG((tdb, TDB_DEBUG_FATAL, "tdb_next_lock: loop detected.\n"));
+ goto fail;
+ }
+
+ if (!TDB_DEAD(rec)) {
+ /* Woohoo: we found one! */
+ if (tdb_lock_record(tdb, tlock->off) != 0)
+ goto fail;
+ return tlock->off;
+ }
+
+ tlock->off = rec->next;
+ }
+ tdb_unlock(tdb, tlock->list, tlock->lock_rw);
+ want_next = 0;
+ }
+ /* We finished iteration without finding anything */
+ tdb->ecode = TDB_SUCCESS;
+ return 0;
+
+ fail:
+ tlock->off = 0;
+ if (tdb_unlock(tdb, tlock->list, tlock->lock_rw) != 0)
+ TDB_LOG((tdb, TDB_DEBUG_FATAL, "tdb_next_lock: On error unlock failed!\n"));
+ return TDB_NEXT_LOCK_ERR;
+}
+
+/* traverse the entire database - calling fn(tdb, key, data) on each element.
+ return -1 on error or the record count traversed
+ if fn is NULL then it is not called
+ a non-zero return value from fn() indicates that the traversal should stop
+ */
+static int tdb_traverse_internal(struct tdb_context *tdb,
+ tdb_traverse_func fn, void *private_data,
+ struct tdb_traverse_lock *tl)
+{
+ TDB_DATA key, dbuf;
+ struct tdb_record rec;
+ int ret = 0, count = 0;
+ tdb_off_t off;
+ size_t recbuf_len;
+
+ recbuf_len = 4096;
+ key.dptr = malloc(recbuf_len);
+ if (key.dptr == NULL) {
+ return -1;
+ }
+
+ /* This was in the initialization, above, but the IRIX compiler
+ * did not like it. crh
+ */
+ tl->next = tdb->travlocks.next;
+
+ /* fcntl locks don't stack: beware traverse inside traverse */
+ tdb->travlocks.next = tl;
+
+ /* tdb_next_lock places locks on the record returned, and its chain */
+ while ((off = tdb_next_lock(tdb, tl, &rec)) != 0) {
+ tdb_len_t full_len;
+ int nread;
+
+ if (off == TDB_NEXT_LOCK_ERR) {
+ ret = -1;
+ goto out;
+ }
+
+ full_len = rec.key_len + rec.data_len;
+
+ if (full_len > recbuf_len) {
+ recbuf_len = full_len;
+
+ /*
+ * No realloc, we don't need the old data and thus can
+ * do without the memcpy
+ */
+ free(key.dptr);
+ key.dptr = malloc(recbuf_len);
+
+ if (key.dptr == NULL) {
+ ret = -1;
+ if (tdb_unlock(tdb, tl->list, tl->lock_rw)
+ != 0) {
+ goto out;
+ }
+ if (tdb_unlock_record(tdb, tl->off) != 0) {
+ TDB_LOG((tdb, TDB_DEBUG_FATAL,
+ "tdb_traverse: malloc "
+ "failed and unlock_record "
+ "failed!\n"));
+ }
+ goto out;
+ }
+ }
+
+ count++;
+ /* now read the full record */
+ nread = tdb->methods->tdb_read(tdb, tl->off + sizeof(rec),
+ key.dptr, full_len, 0);
+ if (nread == -1) {
+ ret = -1;
+ if (tdb_unlock(tdb, tl->list, tl->lock_rw) != 0)
+ goto out;
+ if (tdb_unlock_record(tdb, tl->off) != 0)
+ TDB_LOG((tdb, TDB_DEBUG_FATAL, "tdb_traverse: key.dptr == NULL and unlock_record failed!\n"));
+ goto out;
+ }
+ key.dsize = rec.key_len;
+ dbuf.dptr = key.dptr + rec.key_len;
+ dbuf.dsize = rec.data_len;
+
+ tdb_trace_1rec_retrec(tdb, "traverse", key, dbuf);
+
+ /* Drop chain lock, call out */
+ if (tdb_unlock(tdb, tl->list, tl->lock_rw) != 0) {
+ ret = -1;
+ goto out;
+ }
+ if (fn && fn(tdb, key, dbuf, private_data)) {
+ /* They want us to terminate traversal */
+ tdb_trace_ret(tdb, "tdb_traverse_end", count);
+ if (tdb_unlock_record(tdb, tl->off) != 0) {
+ TDB_LOG((tdb, TDB_DEBUG_FATAL, "tdb_traverse: unlock_record failed!\n"));;
+ ret = -1;
+ }
+ goto out;
+ }
+ }
+ tdb_trace(tdb, "tdb_traverse_end");
+out:
+ SAFE_FREE(key.dptr);
+ tdb->travlocks.next = tl->next;
+ if (ret < 0)
+ return -1;
+ else
+ return count;
+}
+
+
+/*
+ a read style traverse - temporarily marks each record read only
+*/
+_PUBLIC_ int tdb_traverse_read(struct tdb_context *tdb,
+ tdb_traverse_func fn, void *private_data)
+{
+ struct tdb_traverse_lock tl = { NULL, 0, 0, F_RDLCK };
+ int ret;
+
+ tdb->traverse_read++;
+ tdb_trace(tdb, "tdb_traverse_read_start");
+ ret = tdb_traverse_internal(tdb, fn, private_data, &tl);
+ tdb->traverse_read--;
+
+ return ret;
+}
+
+/*
+ a write style traverse - needs to get the transaction lock to
+ prevent deadlocks
+
+ WARNING: The data buffer given to the callback fn does NOT meet the
+ alignment guarantees malloc gives you.
+*/
+_PUBLIC_ int tdb_traverse(struct tdb_context *tdb,
+ tdb_traverse_func fn, void *private_data)
+{
+ struct tdb_traverse_lock tl = { NULL, 0, 0, F_WRLCK };
+ enum tdb_lock_flags lock_flags;
+ int ret;
+
+ if (tdb->read_only || tdb->traverse_read) {
+ return tdb_traverse_read(tdb, fn, private_data);
+ }
+
+ lock_flags = TDB_LOCK_WAIT;
+
+ if (tdb->allrecord_lock.count != 0) {
+ /*
+ * This avoids a deadlock between tdb_lockall() and
+ * tdb_traverse(). See
+ * https://bugzilla.samba.org/show_bug.cgi?id=11381
+ */
+ lock_flags = TDB_LOCK_NOWAIT;
+ }
+
+ if (tdb_transaction_lock(tdb, F_WRLCK, lock_flags)) {
+ return -1;
+ }
+
+ tdb->traverse_write++;
+ tdb_trace(tdb, "tdb_traverse_start");
+ ret = tdb_traverse_internal(tdb, fn, private_data, &tl);
+ tdb->traverse_write--;
+
+ tdb_transaction_unlock(tdb, F_WRLCK);
+
+ return ret;
+}
+
+
+/* find the first entry in the database and return its key */
+_PUBLIC_ TDB_DATA tdb_firstkey(struct tdb_context *tdb)
+{
+ TDB_DATA key;
+ struct tdb_record rec;
+ tdb_off_t off;
+
+ /* release any old lock */
+ if (tdb_unlock_record(tdb, tdb->travlocks.off) != 0)
+ return tdb_null;
+ tdb->travlocks.off = tdb->travlocks.list = 0;
+ tdb->travlocks.lock_rw = F_RDLCK;
+
+ /* Grab first record: locks chain and returned record. */
+ off = tdb_next_lock(tdb, &tdb->travlocks, &rec);
+ if (off == 0 || off == TDB_NEXT_LOCK_ERR) {
+ tdb_trace_retrec(tdb, "tdb_firstkey", tdb_null);
+ return tdb_null;
+ }
+ /* now read the key */
+ key.dsize = rec.key_len;
+ key.dptr =tdb_alloc_read(tdb,tdb->travlocks.off+sizeof(rec),key.dsize);
+
+ tdb_trace_retrec(tdb, "tdb_firstkey", key);
+
+ /* Unlock the hash chain of the record we just read. */
+ if (tdb_unlock(tdb, tdb->travlocks.list, tdb->travlocks.lock_rw) != 0)
+ TDB_LOG((tdb, TDB_DEBUG_FATAL, "tdb_firstkey: error occurred while tdb_unlocking!\n"));
+ return key;
+}
+
+/* find the next entry in the database, returning its key */
+_PUBLIC_ TDB_DATA tdb_nextkey(struct tdb_context *tdb, TDB_DATA oldkey)
+{
+ uint32_t oldlist;
+ TDB_DATA key = tdb_null;
+ struct tdb_record rec;
+ unsigned char *k = NULL;
+ tdb_off_t off;
+
+ /* Is locked key the old key? If so, traverse will be reliable. */
+ if (tdb->travlocks.off) {
+ if (tdb_lock(tdb,tdb->travlocks.list,tdb->travlocks.lock_rw))
+ return tdb_null;
+ if (tdb_rec_read(tdb, tdb->travlocks.off, &rec) == -1
+ || !(k = tdb_alloc_read(tdb,tdb->travlocks.off+sizeof(rec),
+ rec.key_len))
+ || memcmp(k, oldkey.dptr, oldkey.dsize) != 0) {
+ /* No, it wasn't: unlock it and start from scratch */
+ if (tdb_unlock_record(tdb, tdb->travlocks.off) != 0) {
+ tdb_trace_1rec_retrec(tdb, "tdb_nextkey",
+ oldkey, tdb_null);
+ SAFE_FREE(k);
+ return tdb_null;
+ }
+ if (tdb_unlock(tdb, tdb->travlocks.list, tdb->travlocks.lock_rw) != 0) {
+ SAFE_FREE(k);
+ return tdb_null;
+ }
+ tdb->travlocks.off = 0;
+ }
+
+ SAFE_FREE(k);
+ }
+
+ if (!tdb->travlocks.off) {
+ /* No previous element: do normal find, and lock record */
+ tdb->travlocks.off = tdb_find_lock_hash(tdb, oldkey, tdb->hash_fn(&oldkey), tdb->travlocks.lock_rw, &rec);
+ if (!tdb->travlocks.off) {
+ tdb_trace_1rec_retrec(tdb, "tdb_nextkey", oldkey, tdb_null);
+ return tdb_null;
+ }
+ tdb->travlocks.list = BUCKET(rec.full_hash);
+ if (tdb_lock_record(tdb, tdb->travlocks.off) != 0) {
+ TDB_LOG((tdb, TDB_DEBUG_FATAL, "tdb_nextkey: lock_record failed (%s)!\n", strerror(errno)));
+ return tdb_null;
+ }
+ }
+ oldlist = tdb->travlocks.list;
+
+ /* Grab next record: locks chain and returned record,
+ unlocks old record */
+ off = tdb_next_lock(tdb, &tdb->travlocks, &rec);
+ if (off != TDB_NEXT_LOCK_ERR && off != 0) {
+ key.dsize = rec.key_len;
+ key.dptr = tdb_alloc_read(tdb, tdb->travlocks.off+sizeof(rec),
+ key.dsize);
+ /* Unlock the chain of this new record */
+ if (tdb_unlock(tdb, tdb->travlocks.list, tdb->travlocks.lock_rw) != 0)
+ TDB_LOG((tdb, TDB_DEBUG_FATAL, "tdb_nextkey: WARNING tdb_unlock failed!\n"));
+ }
+ /* Unlock the chain of old record */
+ if (tdb_unlock(tdb, oldlist, tdb->travlocks.lock_rw) != 0)
+ TDB_LOG((tdb, TDB_DEBUG_FATAL, "tdb_nextkey: WARNING tdb_unlock failed!\n"));
+ tdb_trace_1rec_retrec(tdb, "tdb_nextkey", oldkey, key);
+ return key;
+}
+
+_PUBLIC_ int tdb_traverse_chain(struct tdb_context *tdb,
+ unsigned chain,
+ tdb_traverse_func fn,
+ void *private_data)
+{
+ tdb_off_t rec_ptr;
+ struct tdb_chainwalk_ctx chainwalk;
+ int count = 0;
+ int ret;
+
+ if (chain >= tdb->hash_size) {
+ tdb->ecode = TDB_ERR_EINVAL;
+ return -1;
+ }
+
+ if (tdb->traverse_read != 0) {
+ tdb->ecode = TDB_ERR_LOCK;
+ return -1;
+ }
+
+ ret = tdb_lock(tdb, chain, F_RDLCK);
+ if (ret == -1) {
+ return -1;
+ }
+
+ tdb->traverse_read += 1;
+
+ ret = tdb_ofs_read(tdb, TDB_HASH_TOP(chain), &rec_ptr);
+ if (ret == -1) {
+ goto fail;
+ }
+
+ tdb_chainwalk_init(&chainwalk, rec_ptr);
+
+ while (rec_ptr != 0) {
+ struct tdb_record rec;
+ bool ok;
+
+ ret = tdb_rec_read(tdb, rec_ptr, &rec);
+ if (ret == -1) {
+ goto fail;
+ }
+
+ if (!TDB_DEAD(&rec)) {
+ /* no overflow checks, tdb_rec_read checked it */
+ tdb_off_t key_ofs = rec_ptr + sizeof(rec);
+ size_t full_len = rec.key_len + rec.data_len;
+ uint8_t *buf = NULL;
+
+ TDB_DATA key = { .dsize = rec.key_len };
+ TDB_DATA data = { .dsize = rec.data_len };
+
+ if ((tdb->transaction == NULL) &&
+ (tdb->map_ptr != NULL)) {
+ ret = tdb_oob(tdb, key_ofs, full_len, 0);
+ if (ret == -1) {
+ goto fail;
+ }
+ key.dptr = (uint8_t *)tdb->map_ptr + key_ofs;
+ } else {
+ buf = tdb_alloc_read(tdb, key_ofs, full_len);
+ if (buf == NULL) {
+ goto fail;
+ }
+ key.dptr = buf;
+ }
+ data.dptr = key.dptr + key.dsize;
+
+ ret = fn(tdb, key, data, private_data);
+ free(buf);
+
+ count += 1;
+
+ if (ret != 0) {
+ break;
+ }
+ }
+
+ rec_ptr = rec.next;
+
+ ok = tdb_chainwalk_check(tdb, &chainwalk, rec_ptr);
+ if (!ok) {
+ goto fail;
+ }
+ }
+ tdb->traverse_read -= 1;
+ tdb_unlock(tdb, chain, F_RDLCK);
+ return count;
+
+fail:
+ tdb->traverse_read -= 1;
+ tdb_unlock(tdb, chain, F_RDLCK);
+ return -1;
+}
+
+_PUBLIC_ int tdb_traverse_key_chain(struct tdb_context *tdb,
+ TDB_DATA key,
+ tdb_traverse_func fn,
+ void *private_data)
+{
+ uint32_t hash, chain;
+ int ret;
+
+ hash = tdb->hash_fn(&key);
+ chain = BUCKET(hash);
+ ret = tdb_traverse_chain(tdb, chain, fn, private_data);
+
+ return ret;
+}
diff --git a/lib/tdb/configure b/lib/tdb/configure
new file mode 100755
index 0000000..af76185
--- /dev/null
+++ b/lib/tdb/configure
@@ -0,0 +1,28 @@
+#!/bin/sh
+
+PREVPATH=$(dirname $0)
+
+if [ -f $PREVPATH/../../buildtools/bin/waf ]; then
+ WAF=../../buildtools/bin/waf
+elif [ -f $PREVPATH/buildtools/bin/waf ]; then
+ WAF=./buildtools/bin/waf
+else
+ echo "replace: Unable to find waf"
+ exit 1
+fi
+
+# using JOBS=1 gives maximum compatibility with
+# systems like AIX which have broken threading in python
+JOBS=1
+export JOBS
+
+# Make sure we don't have any library preloaded.
+unset LD_PRELOAD
+
+# Make sure we get stable hashes
+PYTHONHASHSEED=1
+export PYTHONHASHSEED
+
+cd . || exit 1
+$PYTHON $WAF configure "$@" || exit 1
+cd $PREVPATH
diff --git a/lib/tdb/docs/README b/lib/tdb/docs/README
new file mode 100644
index 0000000..86d46a3
--- /dev/null
+++ b/lib/tdb/docs/README
@@ -0,0 +1,273 @@
+tdb - a trivial database system
+tridge@linuxcare.com December 1999
+==================================
+
+This is a simple database API. It was inspired by the realisation that
+in Samba we have several ad-hoc bits of code that essentially
+implement small databases for sharing structures between parts of
+Samba. As I was about to add another I realised that a generic
+database module was called for to replace all the ad-hoc bits.
+
+I based the interface on gdbm. I couldn't use gdbm as we need to be
+able to have multiple writers to the databases at one time.
+
+Compilation
+-----------
+
+add HAVE_MMAP=1 to use mmap instead of read/write
+add NOLOCK=1 to disable locking code
+
+Testing
+-------
+
+Compile tdbtest.c and link with gdbm for testing. tdbtest will perform
+identical operations via tdb and gdbm then make sure the result is the
+same
+
+Also included is tdbtool, which allows simple database manipulation
+on the commandline.
+
+tdbtest and tdbtool are not built as part of Samba, but are included
+for completeness.
+
+Interface
+---------
+
+The interface is very similar to gdbm except for the following:
+
+- different open interface. The tdb_open call is more similar to a
+ traditional open()
+- no tdbm_reorganise() function
+- no tdbm_sync() function. No operations are cached in the library anyway
+- added a tdb_traverse() function for traversing the whole database
+- added transactions support
+
+A general rule for using tdb is that the caller frees any returned
+TDB_DATA structures. Just call free(p.dptr) to free a TDB_DATA
+return value called p. This is the same as gdbm.
+
+here is a full list of tdb functions with brief descriptions.
+
+
+----------------------------------------------------------------------
+TDB_CONTEXT *tdb_open(char *name, int hash_size, int tdb_flags,
+ int open_flags, mode_t mode)
+
+ open the database, creating it if necessary
+
+ The open_flags and mode are passed straight to the open call on the database
+ file. A flags value of O_WRONLY is invalid
+
+ The hash size is advisory, use zero for a default value.
+
+ return is NULL on error
+
+ possible tdb_flags are:
+ TDB_CLEAR_IF_FIRST - clear database if we are the only one with it open
+ TDB_INTERNAL - don't use a file, instead store the data in
+ memory. The filename is ignored in this case.
+ TDB_NOLOCK - don't do any locking
+ TDB_NOMMAP - don't use mmap
+ TDB_NOSYNC - don't synchronise transactions to disk
+ TDB_SEQNUM - maintain a sequence number
+ TDB_VOLATILE - activate the per-hashchain freelist, default 5
+ TDB_ALLOW_NESTING - allow transactions to nest
+ TDB_DISALLOW_NESTING - disallow transactions to nest
+
+----------------------------------------------------------------------
+TDB_CONTEXT *tdb_open_ex(char *name, int hash_size, int tdb_flags,
+ int open_flags, mode_t mode,
+ const struct tdb_logging_context *log_ctx,
+ tdb_hash_func hash_fn)
+
+This is like tdb_open(), but allows you to pass an initial logging and
+hash function. Be careful when passing a hash function - all users of
+the database must use the same hash function or you will get data
+corruption.
+
+
+----------------------------------------------------------------------
+char *tdb_error(TDB_CONTEXT *tdb);
+
+ return a error string for the last tdb error
+
+----------------------------------------------------------------------
+int tdb_close(TDB_CONTEXT *tdb);
+
+ close a database
+
+----------------------------------------------------------------------
+TDB_DATA tdb_fetch(TDB_CONTEXT *tdb, TDB_DATA key);
+
+ fetch an entry in the database given a key
+ if the return value has a null dptr then a error occurred
+
+ caller must free the resulting data
+
+----------------------------------------------------------------------
+int tdb_parse_record(struct tdb_context *tdb, TDB_DATA key,
+ int (*parser)(TDB_DATA key, TDB_DATA data,
+ void *private_data),
+ void *private_data);
+
+ Hand a record to a parser function without allocating it.
+
+ This function is meant as a fast tdb_fetch alternative for large records
+ that are frequently read. The "key" and "data" arguments point directly
+ into the tdb shared memory, they are not aligned at any boundary.
+
+ WARNING: The parser is called while tdb holds a lock on the record. DO NOT
+ call other tdb routines from within the parser. Also, for good performance
+ you should make the parser fast to allow parallel operations.
+
+ tdb_parse_record returns -1 if the record was not found. If the record was
+ found, the return value of "parser" is passed up to the caller.
+
+----------------------------------------------------------------------
+int tdb_exists(TDB_CONTEXT *tdb, TDB_DATA key);
+
+ check if an entry in the database exists
+
+ note that 1 is returned if the key is found and 0 is returned if not found
+ this doesn't match the conventions in the rest of this module, but is
+ compatible with gdbm
+
+----------------------------------------------------------------------
+int tdb_traverse(TDB_CONTEXT *tdb, int (*fn)(TDB_CONTEXT *tdb,
+ TDB_DATA key, TDB_DATA dbuf, void *state), void *state);
+
+ traverse the entire database - calling fn(tdb, key, data, state) on each
+ element.
+
+ return -1 on error or the record count traversed
+
+ if fn is NULL then it is not called
+
+ a non-zero return value from fn() indicates that the traversal
+ should stop. Traversal callbacks may not start transactions.
+
+ WARNING: The data buffer given to the callback fn does NOT meet the
+ alignment restrictions malloc gives you.
+
+----------------------------------------------------------------------
+int tdb_traverse_read(TDB_CONTEXT *tdb, int (*fn)(TDB_CONTEXT *tdb,
+ TDB_DATA key, TDB_DATA dbuf, void *state), void *state);
+
+ traverse the entire database - calling fn(tdb, key, data, state) on
+ each element, but marking the database read only during the
+ traversal, so any write operations will fail. This allows tdb to
+ use read locks, which increases the parallelism possible during the
+ traversal.
+
+ return -1 on error or the record count traversed
+
+ if fn is NULL then it is not called
+
+ a non-zero return value from fn() indicates that the traversal
+ should stop. Traversal callbacks may not start transactions.
+
+----------------------------------------------------------------------
+TDB_DATA tdb_firstkey(TDB_CONTEXT *tdb);
+
+ find the first entry in the database and return its key
+
+ the caller must free the returned data
+
+----------------------------------------------------------------------
+TDB_DATA tdb_nextkey(TDB_CONTEXT *tdb, TDB_DATA key);
+
+ find the next entry in the database, returning its key
+
+ the caller must free the returned data
+
+----------------------------------------------------------------------
+int tdb_delete(TDB_CONTEXT *tdb, TDB_DATA key);
+
+ delete an entry in the database given a key
+
+----------------------------------------------------------------------
+int tdb_store(TDB_CONTEXT *tdb, TDB_DATA key, TDB_DATA dbuf, int flag);
+
+ store an element in the database, replacing any existing element
+ with the same key
+
+ If flag==TDB_INSERT then don't overwrite an existing entry
+ If flag==TDB_MODIFY then don't create a new entry
+
+ return 0 on success, -1 on failure
+
+----------------------------------------------------------------------
+int tdb_writelock(TDB_CONTEXT *tdb);
+
+ lock the database. If we already have it locked then don't do anything
+
+----------------------------------------------------------------------
+int tdb_writeunlock(TDB_CONTEXT *tdb);
+ unlock the database
+
+----------------------------------------------------------------------
+int tdb_chainlock(TDB_CONTEXT *tdb, TDB_DATA key);
+
+ lock one hash chain. This is meant to be used to reduce locking
+ contention - it cannot guarantee how many records will be locked
+
+----------------------------------------------------------------------
+int tdb_chainunlock(TDB_CONTEXT *tdb, TDB_DATA key);
+
+ unlock one hash chain
+
+----------------------------------------------------------------------
+int tdb_transaction_start(TDB_CONTEXT *tdb)
+
+ start a transaction. All operations after the transaction start can
+ either be committed with tdb_transaction_commit() or cancelled with
+ tdb_transaction_cancel().
+
+ If you call tdb_transaction_start() again on the same tdb context
+ while a transaction is in progress, then the same transaction
+ buffer is re-used. The number of tdb_transaction_{commit,cancel}
+ operations must match the number of successful
+ tdb_transaction_start() calls.
+
+ Note that transactions are by default disk synchronous, and use a
+ recover area in the database to automatically recover the database
+ on the next open if the system crashes during a transaction. You
+ can disable the synchronous transaction recovery setup using the
+ TDB_NOSYNC flag, which will greatly speed up operations at the risk
+ of corrupting your database if the system crashes.
+
+ Operations made within a transaction are not visible to other users
+ of the database until a successful commit.
+
+----------------------------------------------------------------------
+int tdb_transaction_cancel(TDB_CONTEXT *tdb)
+
+ cancel a current transaction, discarding all write and lock
+ operations that have been made since the transaction started.
+
+
+----------------------------------------------------------------------
+int tdb_transaction_commit(TDB_CONTEXT *tdb)
+
+ commit a current transaction, updating the database and releasing
+ the transaction locks.
+
+----------------------------------------------------------------------
+int tdb_transaction_prepare_commit(TDB_CONTEXT *tdb)
+
+ prepare to commit a current transaction, for two-phase commits.
+ Once prepared for commit, the only allowed calls are
+ tdb_transaction_commit() or tdb_transaction_cancel(). Preparing
+ allocates disk space for the pending updates, so a subsequent
+ commit should succeed (barring any hardware failures).
+
+----------------------------------------------------------------------
+int tdb_check(TDB_CONTEXT *tdb,
+ int (*check)(TDB_DATA key, TDB_DATA data, void *private_data),
+ void *private_data);)
+
+ check the consistency of the database, calling back the check function
+ (if non-NULL) with each record. If some consistency check fails, or
+ the supplied check function returns -1, tdb_check returns -1, otherwise
+ 0. Note that logging function (if set) will be called with additional
+ information on the corruption found.
diff --git a/lib/tdb/docs/mainpage.dox b/lib/tdb/docs/mainpage.dox
new file mode 100644
index 0000000..d130769
--- /dev/null
+++ b/lib/tdb/docs/mainpage.dox
@@ -0,0 +1,61 @@
+/**
+
+@mainpage
+
+This is a simple database API. It was inspired by the realisation that in Samba
+we have several ad-hoc bits of code that essentially implement small databases
+for sharing structures between parts of Samba.
+
+The interface is based on gdbm. gdbm couldn't be use as we needed to be able to
+have multiple writers to the databases at one time.
+
+@section tdb_download Download
+
+You can download the latest releases of tdb from the
+<a href="http://samba.org/ftp/tdb">tdb directory</a> on the samba public source
+archive.
+
+You can download the latest code either via git or rsync.
+
+To fetch via git see the following guide:
+
+<a href="http://wiki.samba.org/index.php/Using_Git_for_Samba_Development">Using Git for Samba Development</a>
+Once you have cloned the tree switch to the master branch and cd into the source/lib/tdb directory.
+
+To fetch via rsync use these commands:
+
+<pre>
+ rsync -Pavz samba.org::ftp/unpacked/standalone_projects/lib/tdb .
+ rsync -Pavz samba.org::ftp/unpacked/standalone_projects/lib/replace .
+</pre>
+
+and build in tdb. It will find the replace library in the directory above
+automatically.
+
+@section tdb_bugs Discussion and bug reports
+
+tdb does not currently have its own mailing list or bug tracking system. For now,
+please use the
+<a href="https://lists.samba.org/mailman/listinfo/samba-technical">samba-technical</a>
+mailing list, and the <a href="http://bugzilla.samba.org/">Samba bugzilla</a> bug
+tracking system.
+
+
+@section tdb_compilation Compilation
+
+add HAVE_MMAP=1 to use mmap instead of read/write
+add NOLOCK=1 to disable locking code
+
+@section tdb_testing Testing
+
+Compile tdbtest.c and link with gdbm for testing. tdbtest will perform
+identical operations via tdb and gdbm then make sure the result is the
+same
+
+Also included is tdbtool, which allows simple database manipulation
+on the commandline.
+
+tdbtest and tdbtool are not built as part of Samba, but are included
+for completeness.
+
+*/
diff --git a/lib/tdb/docs/mutex.txt b/lib/tdb/docs/mutex.txt
new file mode 100644
index 0000000..a5a7542
--- /dev/null
+++ b/lib/tdb/docs/mutex.txt
@@ -0,0 +1,136 @@
+Tdb is a hashtable database with multiple concurrent writer and external
+record lock support. For speed reasons, wherever possible tdb uses a shared
+memory mapped area for data access. In its currently released form, it uses
+fcntl byte-range locks to coordinate access to the data itself.
+
+The tdb data is organized as a hashtable. Hash collisions are dealt with by
+forming a linked list of records that share a hash value. The individual
+linked lists are protected across processes with 1-byte fcntl locks on the
+starting pointer of the linked list representing a hash value.
+
+The external locking API of tdb allows one to lock individual records. Instead of
+really locking individual records, the tdb API locks a complete linked list
+with a fcntl lock.
+
+The external locking API of tdb also allows one to lock the complete database, and
+ctdb uses this facility to freeze databases during a recovery. While the
+so-called allrecord lock is held, all linked lists and all individual records
+are frozen alltogether. Tdb achieves this by locking the complete file range
+with a single fcntl lock. Individual 1-byte locks for the linked lists
+conflict with this. Access to records is prevented by the one large fnctl byte
+range lock.
+
+Fcntl locks have been chosen for tdb for two reasons: First they are portable
+across all current unixes. Secondly they provide auto-cleanup. If a process
+dies while holding a fcntl lock, the lock is given up as if it was explicitly
+unlocked. Thus fcntl locks provide a very robust locking scheme, if a process
+dies for any reason the database will not stay blocked until reboot. This
+robustness is very important for long-running services, a reboot is not an
+option for most users of tdb.
+
+Unfortunately, during stress testing, fcntl locks have turned out to be a major
+problem for performance. The particular problem that was seen happens when
+ctdb on a busy server does a recovery. A recovery means that ctdb has to
+freeze all tdb databases for some time, usually a few seconds. This is done
+with the allrecord lock. During the recovery phase on a busy server many smbd
+processes try to access the tdb file with blocking fcntl calls. The specific
+test in question easily reproduces 7,000 processes piling up waiting for
+1-byte fcntl locks. When ctdb is done with the recovery, it gives up the
+allrecord lock, covering the whole file range. All 7,000 processes waiting for
+1-byte fcntl locks are woken up, trying to acquire their lock. The special
+implementation of fcntl locks in Linux (up to 2013-02-12 at least) protects
+all fcntl lock operations with a single system-wide spinlock. If 7,000 process
+waiting for the allrecord lock to become released this leads to a thundering
+herd condition, all CPUs are spinning on that single spinlock.
+
+Functionally the kernel is fine, eventually the thundering herd slows down and
+every process correctly gets his share and locking range, but the performance
+of the system while the herd is active is worse than expected.
+
+The thundering herd is only the worst case scenario for fcntl lock use. The
+single spinlock for fcntl operations is also a performance penalty for normal
+operations. In the cluster case, every read and write SMB request has to do
+two fcntl calls to provide correct SMB mandatory locks. The single spinlock
+is one source of serialization for the SMB read/write requests, limiting the
+parallelism that can be achieved in a multi-core system.
+
+While trying to tune his servers, Ira Cooper, Samba Team member, found fcntl
+locks to be a problem on Solaris as well. Ira pointed out that there is a
+potential alternative locking mechanism that might be more scalable: Process
+shared robust mutexes, as defined by Posix 2008 for example via
+
+http://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_mutexattr_setpshared.html
+http://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_mutexattr_setrobust.html
+
+Pthread mutexes provide one of the core mechanisms in posix threads to protect
+in-process data structures from concurrent access by multiple threads. In the
+Linux implementation, a pthread_mutex_t is represented by a data structure in
+user space that requires no kernel calls in the uncontended case for locking
+and unlocking. Locking and unlocking in the uncontended case is implemented
+purely in user space with atomic CPU instructions and thus are very fast.
+
+The setpshared functions indicate to the kernel that the mutex is about to be
+shared between processes in a common shared memory area.
+
+The process shared posix mutexes have the potential to replace fcntl locking
+to coordinate mmap access for tdbs. However, they are missing the criticial
+auto-cleanup property that fcntl provides when a process dies. A process that
+dies hard while holding a shared mutex has no chance to clean up the protected
+data structures and unlock the shared mutex. Thus with a pure process shared
+mutex the mutex will remain locked forever until the data structures are
+re-initialized from scratch.
+
+With the robust mutexes defined by Posix the process shared mutexes have been
+extended with a limited auto-cleanup property. If a mutex has been declared
+robust, when a process exits while holding that mutex, the next process trying
+to lock the mutex will get the special error message EOWNERDEAD. This informs
+the caller that the data structures the mutex protects are potentially corrupt
+and need to be cleaned up.
+
+The error message EOWNERDEAD when trying to lock a mutex is an extension over
+the fcntl functionality. A process that does a blocking fcntl lock call is not
+informed about whether the lock was explicitly freed by a process still alive
+or due to an unplanned process exit. At the time of this writing (February
+2013), at least Linux and OpenSolaris also implement the robustness feature of
+process-shared mutexes.
+
+Converting the tdb locking mechanism from fcntl to mutexes has to take care of
+both types of locks that are used on tdb files.
+
+The easy part is to use mutexes to replace the 1-byte linked list locks
+covering the individual hashes. Those can be represented by a mutex each.
+
+Covering the allrecord lock is more difficult. The allrecord lock uses a fcntl
+lock spanning all hash list locks simultaneously. This basic functionality is
+not easily possible with mutexes. A mutex carries 1 bit of information, a
+fcntl lock can carry an arbitrary amount of information.
+
+In order to support the allrecord lock, we have an allrecord_lock variable
+protected by an allrecord_mutex. The coordination between the allrecord lock
+and the chainlocks works like this:
+
+- Getting a chain lock works like this:
+
+ 1. get chain mutex
+ 2. return success if allrecord_lock is F_UNLCK (not locked)
+ 3. return success if allrecord_lock is F_RDLCK (locked readonly)
+ and we only need a read lock.
+ 4. release chain mutex
+ 5. wait for allrecord_mutex
+ 6. unlock allrecord_mutex
+ 7. goto 1.
+
+- Getting the allrecord lock:
+
+ 1. get the allrecord mutex
+ 2. return error if allrecord_lock is not F_UNLCK (it's locked)
+ 3. set allrecord_lock to the desired value.
+ 4. in a loop: lock(blocking) / unlock each chain mutex.
+ 5. return success.
+
+- allrecord lock upgrade:
+
+ 1. check we already have the allrecord lock with F_RDLCK.
+ 3. set allrecord_lock to F_WRLCK
+ 4. in a loop: lock(blocking) / unlock each chain mutex.
+ 5. return success.
diff --git a/lib/tdb/docs/tdb.magic b/lib/tdb/docs/tdb.magic
new file mode 100644
index 0000000..f5619e7
--- /dev/null
+++ b/lib/tdb/docs/tdb.magic
@@ -0,0 +1,10 @@
+# Magic file(1) information about tdb files.
+#
+# Install this into /etc/magic or the corresponding location for your
+# system, or pass as a -m argument to file(1).
+
+# You may use and redistribute this file without restriction.
+
+0 string TDB\ file TDB database
+>32 lelong =0x2601196D version 6, little-endian
+>>36 lelong x hash size %d bytes
diff --git a/lib/tdb/docs/tracing.txt b/lib/tdb/docs/tracing.txt
new file mode 100644
index 0000000..a7657ff
--- /dev/null
+++ b/lib/tdb/docs/tracing.txt
@@ -0,0 +1,46 @@
+How And Why To Use TDB Tracing
+==============================
+
+You can trace all TDB operations, using TDB_TRACE. It is not complete
+(error conditions which expect to the logged will not always be traced
+correctly, so you should set up a logging function too), but is designed
+to collect benchmark-style traces to allow us to optimize TDB.
+
+Note: tracing is not efficient, and the trace files are huge: a
+traverse of the database is particularly large! But they compress very
+well with rzip (http://rzip.samba.org)
+
+How to gather trace files:
+--------------------------
+1) Uncomment /* #define TDB_TRACE 1 */ in tdb_private.h.
+2) Rebuild TDB, and everything that uses it.
+3) Run something.
+
+Your trace files will be called <tdbname>.trace.<pid>. These files
+will not be overwritten: if the same process reopens the same TDB, an
+error will be logged and tracing will be disabled.
+
+How to replay trace files:
+--------------------------
+1) For benchmarking, remember to rebuild tdb with #define TDB_TRACE commented
+ out again!
+2) Grab the latest "replace_trace.c" from CCAN's tdb module (tools/ dir):
+ http://ccan.ozlabs.org/tarballs/tdb.tar.bz2
+3) Compile up replay_trace, munging as necessary.
+4) Run replay_trace <scratch-tdb-name> <tracefiles>...
+
+If given more than one trace file (presumably from the same tdb)
+replay_trace will try to figure out the dependencies between the operations
+and fire off a child to run each trace. Occasionally it gets stuck, in
+which case it will add another dependency and retry. Eventually it will
+give a speed value.
+
+replay_trace can intuit the existence of previous data in the tdb (ie.
+activity prior to the trace(s) supplied) and will prepopulate as
+necessary.
+
+You can run --quiet for straight benchmark results, and -n to run multiple
+times (this saves time, since it need only calculate dependencies once).
+
+Good luck!
+Rusty Russell <rusty@rustcorp.com.au>
diff --git a/lib/tdb/doxy.config b/lib/tdb/doxy.config
new file mode 100644
index 0000000..f55e9c3
--- /dev/null
+++ b/lib/tdb/doxy.config
@@ -0,0 +1,1697 @@
+# Doxyfile 1.7.3
+
+# This file describes the settings to be used by the documentation system
+# doxygen (www.doxygen.org) for a project.
+#
+# All text after a hash (#) is considered a comment and will be ignored.
+# The format is:
+# TAG = value [value, ...]
+# For lists items can also be appended using:
+# TAG += value [value, ...]
+# Values that contain spaces should be placed between quotes (" ").
+
+#---------------------------------------------------------------------------
+# Project related configuration options
+#---------------------------------------------------------------------------
+
+# This tag specifies the encoding used for all characters in the config file
+# that follow. The default is UTF-8 which is also the encoding used for all
+# text before the first occurrence of this tag. Doxygen uses libiconv (or the
+# iconv built into libc) for the transcoding. See
+# http://www.gnu.org/software/libiconv for the list of possible encodings.
+
+DOXYFILE_ENCODING = UTF-8
+
+# The PROJECT_NAME tag is a single word (or a sequence of words surrounded
+# by quotes) that should identify the project.
+
+PROJECT_NAME = tdb
+
+# The PROJECT_NUMBER tag can be used to enter a project or revision number.
+# This could be handy for archiving the generated documentation or
+# if some version control system is used.
+
+PROJECT_NUMBER = 1.2.9
+
+# Using the PROJECT_BRIEF tag one can provide an optional one line description for a project that appears at the top of each page and should give viewer a quick idea about the purpose of the project. Keep the description short.
+
+PROJECT_BRIEF =
+
+# With the PROJECT_LOGO tag one can specify an logo or icon that is
+# included in the documentation. The maximum height of the logo should not
+# exceed 55 pixels and the maximum width should not exceed 200 pixels.
+# Doxygen will copy the logo to the output directory.
+
+PROJECT_LOGO =
+
+# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute)
+# base path where the generated documentation will be put.
+# If a relative path is entered, it will be relative to the location
+# where doxygen was started. If left blank the current directory will be used.
+
+OUTPUT_DIRECTORY = docs
+
+# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create
+# 4096 sub-directories (in 2 levels) under the output directory of each output
+# format and will distribute the generated files over these directories.
+# Enabling this option can be useful when feeding doxygen a huge amount of
+# source files, where putting all generated files in the same directory would
+# otherwise cause performance problems for the file system.
+
+CREATE_SUBDIRS = NO
+
+# The OUTPUT_LANGUAGE tag is used to specify the language in which all
+# documentation generated by doxygen is written. Doxygen will use this
+# information to generate all constant output in the proper language.
+# The default language is English, other supported languages are:
+# Afrikaans, Arabic, Brazilian, Catalan, Chinese, Chinese-Traditional,
+# Croatian, Czech, Danish, Dutch, Esperanto, Farsi, Finnish, French, German,
+# Greek, Hungarian, Italian, Japanese, Japanese-en (Japanese with English
+# messages), Korean, Korean-en, Lithuanian, Norwegian, Macedonian, Persian,
+# Polish, Portuguese, Romanian, Russian, Serbian, Serbian-Cyrillic, Slovak,
+# Slovene, Spanish, Swedish, Ukrainian, and Vietnamese.
+
+OUTPUT_LANGUAGE = English
+
+# If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will
+# include brief member descriptions after the members that are listed in
+# the file and class documentation (similar to JavaDoc).
+# Set to NO to disable this.
+
+BRIEF_MEMBER_DESC = YES
+
+# If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend
+# the brief description of a member or function before the detailed description.
+# Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the
+# brief descriptions will be completely suppressed.
+
+REPEAT_BRIEF = YES
+
+# This tag implements a quasi-intelligent brief description abbreviator
+# that is used to form the text in various listings. Each string
+# in this list, if found as the leading text of the brief description, will be
+# stripped from the text and the result after processing the whole list, is
+# used as the annotated text. Otherwise, the brief description is used as-is.
+# If left blank, the following values are used ("$name" is automatically
+# replaced with the name of the entity): "The $name class" "The $name widget"
+# "The $name file" "is" "provides" "specifies" "contains"
+# "represents" "a" "an" "the"
+
+ABBREVIATE_BRIEF = "The $name class" \
+ "The $name widget" \
+ "The $name file" \
+ is \
+ provides \
+ specifies \
+ contains \
+ represents \
+ a \
+ an \
+ the
+
+# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then
+# Doxygen will generate a detailed section even if there is only a brief
+# description.
+
+ALWAYS_DETAILED_SEC = NO
+
+# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all
+# inherited members of a class in the documentation of that class as if those
+# members were ordinary class members. Constructors, destructors and assignment
+# operators of the base classes will not be shown.
+
+INLINE_INHERITED_MEMB = NO
+
+# If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full
+# path before files name in the file list and in the header files. If set
+# to NO the shortest path that makes the file name unique will be used.
+
+FULL_PATH_NAMES = YES
+
+# If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag
+# can be used to strip a user-defined part of the path. Stripping is
+# only done if one of the specified strings matches the left-hand part of
+# the path. The tag can be used to show relative paths in the file list.
+# If left blank the directory from which doxygen is run is used as the
+# path to strip.
+
+STRIP_FROM_PATH =
+
+# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of
+# the path mentioned in the documentation of a class, which tells
+# the reader which header file to include in order to use a class.
+# If left blank only the name of the header file containing the class
+# definition is used. Otherwise one should specify the include paths that
+# are normally passed to the compiler using the -I flag.
+
+STRIP_FROM_INC_PATH =
+
+# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter
+# (but less readable) file names. This can be useful if your file system
+# doesn't support long names like on DOS, Mac, or CD-ROM.
+
+SHORT_NAMES = NO
+
+# If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen
+# will interpret the first line (until the first dot) of a JavaDoc-style
+# comment as the brief description. If set to NO, the JavaDoc
+# comments will behave just like regular Qt-style comments
+# (thus requiring an explicit @brief command for a brief description.)
+
+JAVADOC_AUTOBRIEF = YES
+
+# If the QT_AUTOBRIEF tag is set to YES then Doxygen will
+# interpret the first line (until the first dot) of a Qt-style
+# comment as the brief description. If set to NO, the comments
+# will behave just like regular Qt-style comments (thus requiring
+# an explicit \brief command for a brief description.)
+
+QT_AUTOBRIEF = NO
+
+# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen
+# treat a multi-line C++ special comment block (i.e. a block of //! or ///
+# comments) as a brief description. This used to be the default behaviour.
+# The new default is to treat a multi-line C++ comment block as a detailed
+# description. Set this tag to YES if you prefer the old behaviour instead.
+
+MULTILINE_CPP_IS_BRIEF = NO
+
+# If the INHERIT_DOCS tag is set to YES (the default) then an undocumented
+# member inherits the documentation from any documented member that it
+# re-implements.
+
+INHERIT_DOCS = YES
+
+# If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce
+# a new page for each member. If set to NO, the documentation of a member will
+# be part of the file/class/namespace that contains it.
+
+SEPARATE_MEMBER_PAGES = NO
+
+# The TAB_SIZE tag can be used to set the number of spaces in a tab.
+# Doxygen uses this value to replace tabs by spaces in code fragments.
+
+TAB_SIZE = 8
+
+# This tag can be used to specify a number of aliases that acts
+# as commands in the documentation. An alias has the form "name=value".
+# For example adding "sideeffect=\par Side Effects:\n" will allow you to
+# put the command \sideeffect (or @sideeffect) in the documentation, which
+# will result in a user-defined paragraph with heading "Side Effects:".
+# You can put \n's in the value part of an alias to insert newlines.
+
+ALIASES =
+
+# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C
+# sources only. Doxygen will then generate output that is more tailored for C.
+# For instance, some of the names that are used will be different. The list
+# of all members will be omitted, etc.
+
+OPTIMIZE_OUTPUT_FOR_C = YES
+
+# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java
+# sources only. Doxygen will then generate output that is more tailored for
+# Java. For instance, namespaces will be presented as packages, qualified
+# scopes will look different, etc.
+
+OPTIMIZE_OUTPUT_JAVA = NO
+
+# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran
+# sources only. Doxygen will then generate output that is more tailored for
+# Fortran.
+
+OPTIMIZE_FOR_FORTRAN = NO
+
+# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL
+# sources. Doxygen will then generate output that is tailored for
+# VHDL.
+
+OPTIMIZE_OUTPUT_VHDL = NO
+
+# Doxygen selects the parser to use depending on the extension of the files it
+# parses. With this tag you can assign which parser to use for a given extension.
+# Doxygen has a built-in mapping, but you can override or extend it using this
+# tag. The format is ext=language, where ext is a file extension, and language
+# is one of the parsers supported by doxygen: IDL, Java, Javascript, CSharp, C,
+# C++, D, PHP, Objective-C, Python, Fortran, VHDL, C, C++. For instance to make
+# doxygen treat .inc files as Fortran files (default is PHP), and .f files as C
+# (default is Fortran), use: inc=Fortran f=C. Note that for custom extensions
+# you also need to set FILE_PATTERNS otherwise the files are not read by doxygen.
+
+EXTENSION_MAPPING =
+
+# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want
+# to include (a tag file for) the STL sources as input, then you should
+# set this tag to YES in order to let doxygen match functions declarations and
+# definitions whose arguments contain STL classes (e.g. func(std::string); v.s.
+# func(std::string) {}). This also makes the inheritance and collaboration
+# diagrams that involve STL classes more complete and accurate.
+
+BUILTIN_STL_SUPPORT = NO
+
+# If you use Microsoft's C++/CLI language, you should set this option to YES to
+# enable parsing support.
+
+CPP_CLI_SUPPORT = NO
+
+# Set the SIP_SUPPORT tag to YES if your project consists of sip sources only.
+# Doxygen will parse them like normal C++ but will assume all classes use public
+# instead of private inheritance when no explicit protection keyword is present.
+
+SIP_SUPPORT = NO
+
+# For Microsoft's IDL there are propget and propput attributes to indicate getter
+# and setter methods for a property. Setting this option to YES (the default)
+# will make doxygen replace the get and set methods by a property in the
+# documentation. This will only work if the methods are indeed getting or
+# setting a simple type. If this is not the case, or you want to show the
+# methods anyway, you should set this option to NO.
+
+IDL_PROPERTY_SUPPORT = YES
+
+# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC
+# tag is set to YES, then doxygen will reuse the documentation of the first
+# member in the group (if any) for the other members of the group. By default
+# all members of a group must be documented explicitly.
+
+DISTRIBUTE_GROUP_DOC = NO
+
+# Set the SUBGROUPING tag to YES (the default) to allow class member groups of
+# the same type (for instance a group of public functions) to be put as a
+# subgroup of that type (e.g. under the Public Functions section). Set it to
+# NO to prevent subgrouping. Alternatively, this can be done per class using
+# the \nosubgrouping command.
+
+SUBGROUPING = YES
+
+# When TYPEDEF_HIDES_STRUCT is enabled, a typedef of a struct, union, or enum
+# is documented as struct, union, or enum with the name of the typedef. So
+# typedef struct TypeS {} TypeT, will appear in the documentation as a struct
+# with name TypeT. When disabled the typedef will appear as a member of a file,
+# namespace, or class. And the struct will be named TypeS. This can typically
+# be useful for C code in case the coding convention dictates that all compound
+# types are typedef'ed and only the typedef is referenced, never the tag name.
+
+TYPEDEF_HIDES_STRUCT = NO
+
+# The SYMBOL_CACHE_SIZE determines the size of the internal cache use to
+# determine which symbols to keep in memory and which to flush to disk.
+# When the cache is full, less often used symbols will be written to disk.
+# For small to medium size projects (<1000 input files) the default value is
+# probably good enough. For larger projects a too small cache size can cause
+# doxygen to be busy swapping symbols to and from disk most of the time
+# causing a significant performance penalty.
+# If the system has enough physical memory increasing the cache will improve the
+# performance by keeping more symbols in memory. Note that the value works on
+# a logarithmic scale so increasing the size by one will roughly double the
+# memory usage. The cache size is given by this formula:
+# 2^(16+SYMBOL_CACHE_SIZE). The valid range is 0..9, the default is 0,
+# corresponding to a cache size of 2^16 = 65536 symbols
+
+SYMBOL_CACHE_SIZE = 0
+
+#---------------------------------------------------------------------------
+# Build related configuration options
+#---------------------------------------------------------------------------
+
+# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in
+# documentation are documented, even if no documentation was available.
+# Private class members and static file members will be hidden unless
+# the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES
+
+EXTRACT_ALL = NO
+
+# If the EXTRACT_PRIVATE tag is set to YES all private members of a class
+# will be included in the documentation.
+
+EXTRACT_PRIVATE = NO
+
+# If the EXTRACT_STATIC tag is set to YES all static members of a file
+# will be included in the documentation.
+
+EXTRACT_STATIC = NO
+
+# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs)
+# defined locally in source files will be included in the documentation.
+# If set to NO only classes defined in header files are included.
+
+EXTRACT_LOCAL_CLASSES = NO
+
+# This flag is only useful for Objective-C code. When set to YES local
+# methods, which are defined in the implementation section but not in
+# the interface are included in the documentation.
+# If set to NO (the default) only methods in the interface are included.
+
+EXTRACT_LOCAL_METHODS = NO
+
+# If this flag is set to YES, the members of anonymous namespaces will be
+# extracted and appear in the documentation as a namespace called
+# 'anonymous_namespace{file}', where file will be replaced with the base
+# name of the file that contains the anonymous namespace. By default
+# anonymous namespaces are hidden.
+
+EXTRACT_ANON_NSPACES = NO
+
+# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all
+# undocumented members of documented classes, files or namespaces.
+# If set to NO (the default) these members will be included in the
+# various overviews, but no documentation section is generated.
+# This option has no effect if EXTRACT_ALL is enabled.
+
+HIDE_UNDOC_MEMBERS = YES
+
+# If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all
+# undocumented classes that are normally visible in the class hierarchy.
+# If set to NO (the default) these classes will be included in the various
+# overviews. This option has no effect if EXTRACT_ALL is enabled.
+
+HIDE_UNDOC_CLASSES = YES
+
+# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all
+# friend (class|struct|union) declarations.
+# If set to NO (the default) these declarations will be included in the
+# documentation.
+
+HIDE_FRIEND_COMPOUNDS = NO
+
+# If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any
+# documentation blocks found inside the body of a function.
+# If set to NO (the default) these blocks will be appended to the
+# function's detailed documentation block.
+
+HIDE_IN_BODY_DOCS = NO
+
+# The INTERNAL_DOCS tag determines if documentation
+# that is typed after a \internal command is included. If the tag is set
+# to NO (the default) then the documentation will be excluded.
+# Set it to YES to include the internal documentation.
+
+INTERNAL_DOCS = NO
+
+# If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate
+# file names in lower-case letters. If set to YES upper-case letters are also
+# allowed. This is useful if you have classes or files whose names only differ
+# in case and if your file system supports case sensitive file names. Windows
+# and Mac users are advised to set this option to NO.
+
+CASE_SENSE_NAMES = YES
+
+# If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen
+# will show members with their full class and namespace scopes in the
+# documentation. If set to YES the scope will be hidden.
+
+HIDE_SCOPE_NAMES = NO
+
+# If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen
+# will put a list of the files that are included by a file in the documentation
+# of that file.
+
+SHOW_INCLUDE_FILES = YES
+
+# If the FORCE_LOCAL_INCLUDES tag is set to YES then Doxygen
+# will list include files with double quotes in the documentation
+# rather than with sharp brackets.
+
+FORCE_LOCAL_INCLUDES = NO
+
+# If the INLINE_INFO tag is set to YES (the default) then a tag [inline]
+# is inserted in the documentation for inline members.
+
+INLINE_INFO = YES
+
+# If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen
+# will sort the (detailed) documentation of file and class members
+# alphabetically by member name. If set to NO the members will appear in
+# declaration order.
+
+SORT_MEMBER_DOCS = YES
+
+# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the
+# brief documentation of file, namespace and class members alphabetically
+# by member name. If set to NO (the default) the members will appear in
+# declaration order.
+
+SORT_BRIEF_DOCS = NO
+
+# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen
+# will sort the (brief and detailed) documentation of class members so that
+# constructors and destructors are listed first. If set to NO (the default)
+# the constructors will appear in the respective orders defined by
+# SORT_MEMBER_DOCS and SORT_BRIEF_DOCS.
+# This tag will be ignored for brief docs if SORT_BRIEF_DOCS is set to NO
+# and ignored for detailed docs if SORT_MEMBER_DOCS is set to NO.
+
+SORT_MEMBERS_CTORS_1ST = NO
+
+# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the
+# hierarchy of group names into alphabetical order. If set to NO (the default)
+# the group names will appear in their defined order.
+
+SORT_GROUP_NAMES = NO
+
+# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be
+# sorted by fully-qualified names, including namespaces. If set to
+# NO (the default), the class list will be sorted only by class name,
+# not including the namespace part.
+# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES.
+# Note: This option applies only to the class list, not to the
+# alphabetical list.
+
+SORT_BY_SCOPE_NAME = NO
+
+# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper type resolution of all parameters of a function it will reject a
+# match between the prototype and the implementation of a member function even if there is only one candidate or it is obvious which candidate to choose by doing a simple string match. By disabling STRICT_PROTO_MATCHING doxygen
+# will still accept a match between prototype and implementation in such cases.
+
+STRICT_PROTO_MATCHING = NO
+
+# The GENERATE_TODOLIST tag can be used to enable (YES) or
+# disable (NO) the todo list. This list is created by putting \todo
+# commands in the documentation.
+
+GENERATE_TODOLIST = YES
+
+# The GENERATE_TESTLIST tag can be used to enable (YES) or
+# disable (NO) the test list. This list is created by putting \test
+# commands in the documentation.
+
+GENERATE_TESTLIST = YES
+
+# The GENERATE_BUGLIST tag can be used to enable (YES) or
+# disable (NO) the bug list. This list is created by putting \bug
+# commands in the documentation.
+
+GENERATE_BUGLIST = YES
+
+# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or
+# disable (NO) the deprecated list. This list is created by putting
+# \deprecated commands in the documentation.
+
+GENERATE_DEPRECATEDLIST= YES
+
+# The ENABLED_SECTIONS tag can be used to enable conditional
+# documentation sections, marked by \if sectionname ... \endif.
+
+ENABLED_SECTIONS =
+
+# The MAX_INITIALIZER_LINES tag determines the maximum number of lines
+# the initial value of a variable or macro consists of for it to appear in
+# the documentation. If the initializer consists of more lines than specified
+# here it will be hidden. Use a value of 0 to hide initializers completely.
+# The appearance of the initializer of individual variables and macros in the
+# documentation can be controlled using \showinitializer or \hideinitializer
+# command in the documentation regardless of this setting.
+
+MAX_INITIALIZER_LINES = 30
+
+# Set the SHOW_USED_FILES tag to NO to disable the list of files generated
+# at the bottom of the documentation of classes and structs. If set to YES the
+# list will mention the files that were used to generate the documentation.
+
+SHOW_USED_FILES = YES
+
+# If the sources in your project are distributed over multiple directories
+# then setting the SHOW_DIRECTORIES tag to YES will show the directory hierarchy
+# in the documentation. The default is NO.
+
+SHOW_DIRECTORIES = NO
+
+# Set the SHOW_FILES tag to NO to disable the generation of the Files page.
+# This will remove the Files entry from the Quick Index and from the
+# Folder Tree View (if specified). The default is YES.
+
+SHOW_FILES = YES
+
+# Set the SHOW_NAMESPACES tag to NO to disable the generation of the
+# Namespaces page.
+# This will remove the Namespaces entry from the Quick Index
+# and from the Folder Tree View (if specified). The default is YES.
+
+SHOW_NAMESPACES = YES
+
+# The FILE_VERSION_FILTER tag can be used to specify a program or script that
+# doxygen should invoke to get the current version for each file (typically from
+# the version control system). Doxygen will invoke the program by executing (via
+# popen()) the command <command> <input-file>, where <command> is the value of
+# the FILE_VERSION_FILTER tag, and <input-file> is the name of an input file
+# provided by doxygen. Whatever the program writes to standard output
+# is used as the file version. See the manual for examples.
+
+FILE_VERSION_FILTER =
+
+# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed
+# by doxygen. The layout file controls the global structure of the generated
+# output files in an output format independent way. The create the layout file
+# that represents doxygen's defaults, run doxygen with the -l option.
+# You can optionally specify a file name after the option, if omitted
+# DoxygenLayout.xml will be used as the name of the layout file.
+
+LAYOUT_FILE =
+
+#---------------------------------------------------------------------------
+# configuration options related to warning and progress messages
+#---------------------------------------------------------------------------
+
+# The QUIET tag can be used to turn on/off the messages that are generated
+# by doxygen. Possible values are YES and NO. If left blank NO is used.
+
+QUIET = NO
+
+# The WARNINGS tag can be used to turn on/off the warning messages that are
+# generated by doxygen. Possible values are YES and NO. If left blank
+# NO is used.
+
+WARNINGS = YES
+
+# If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings
+# for undocumented members. If EXTRACT_ALL is set to YES then this flag will
+# automatically be disabled.
+
+WARN_IF_UNDOCUMENTED = YES
+
+# If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for
+# potential errors in the documentation, such as not documenting some
+# parameters in a documented function, or documenting parameters that
+# don't exist or using markup commands wrongly.
+
+WARN_IF_DOC_ERROR = YES
+
+# The WARN_NO_PARAMDOC option can be enabled to get warnings for
+# functions that are documented, but have no documentation for their parameters
+# or return value. If set to NO (the default) doxygen will only warn about
+# wrong or incomplete parameter documentation, but not about the absence of
+# documentation.
+
+WARN_NO_PARAMDOC = NO
+
+# The WARN_FORMAT tag determines the format of the warning messages that
+# doxygen can produce. The string should contain the $file, $line, and $text
+# tags, which will be replaced by the file and line number from which the
+# warning originated and the warning text. Optionally the format may contain
+# $version, which will be replaced by the version of the file (if it could
+# be obtained via FILE_VERSION_FILTER)
+
+WARN_FORMAT = "$file:$line: $text"
+
+# The WARN_LOGFILE tag can be used to specify a file to which warning
+# and error messages should be written. If left blank the output is written
+# to stderr.
+
+WARN_LOGFILE =
+
+#---------------------------------------------------------------------------
+# configuration options related to the input files
+#---------------------------------------------------------------------------
+
+# The INPUT tag can be used to specify the files and/or directories that contain
+# documented source files. You may enter file names like "myfile.cpp" or
+# directories like "/usr/src/myproject". Separate the files or directories
+# with spaces.
+
+INPUT = include \
+ docs
+
+# This tag can be used to specify the character encoding of the source files
+# that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is
+# also the default input encoding. Doxygen uses libiconv (or the iconv built
+# into libc) for the transcoding. See http://www.gnu.org/software/libiconv for
+# the list of possible encodings.
+
+INPUT_ENCODING = UTF-8
+
+# If the value of the INPUT tag contains directories, you can use the
+# FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp
+# and *.h) to filter out the source-files in the directories. If left
+# blank the following patterns are tested:
+# *.c *.cc *.cxx *.cpp *.c++ *.d *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh
+# *.hxx *.hpp *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm *.dox *.py
+# *.f90 *.f *.for *.vhd *.vhdl
+
+FILE_PATTERNS = *.cpp \
+ *.cc \
+ *.c \
+ *.h \
+ *.hh \
+ *.hpp \
+ *.dox
+
+# The RECURSIVE tag can be used to turn specify whether or not subdirectories
+# should be searched for input files as well. Possible values are YES and NO.
+# If left blank NO is used.
+
+RECURSIVE = NO
+
+# The EXCLUDE tag can be used to specify files and/or directories that should
+# excluded from the INPUT source files. This way you can easily exclude a
+# subdirectory from a directory tree whose root is specified with the INPUT tag.
+
+EXCLUDE =
+
+# The EXCLUDE_SYMLINKS tag can be used select whether or not files or
+# directories that are symbolic links (a Unix file system feature) are excluded
+# from the input.
+
+EXCLUDE_SYMLINKS = NO
+
+# If the value of the INPUT tag contains directories, you can use the
+# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude
+# certain files from those directories. Note that the wildcards are matched
+# against the file with absolute path, so to exclude all test directories
+# for example use the pattern */test/*
+
+EXCLUDE_PATTERNS = */.git/*
+
+# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names
+# (namespaces, classes, functions, etc.) that should be excluded from the
+# output. The symbol name can be a fully qualified name, a word, or if the
+# wildcard * is used, a substring. Examples: ANamespace, AClass,
+# AClass::ANamespace, ANamespace::*Test
+
+EXCLUDE_SYMBOLS =
+
+# The EXAMPLE_PATH tag can be used to specify one or more files or
+# directories that contain example code fragments that are included (see
+# the \include command).
+
+EXAMPLE_PATH =
+
+# If the value of the EXAMPLE_PATH tag contains directories, you can use the
+# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp
+# and *.h) to filter out the source-files in the directories. If left
+# blank all files are included.
+
+EXAMPLE_PATTERNS =
+
+# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be
+# searched for input files to be used with the \include or \dontinclude
+# commands irrespective of the value of the RECURSIVE tag.
+# Possible values are YES and NO. If left blank NO is used.
+
+EXAMPLE_RECURSIVE = NO
+
+# The IMAGE_PATH tag can be used to specify one or more files or
+# directories that contain image that are included in the documentation (see
+# the \image command).
+
+IMAGE_PATH =
+
+# The INPUT_FILTER tag can be used to specify a program that doxygen should
+# invoke to filter for each input file. Doxygen will invoke the filter program
+# by executing (via popen()) the command <filter> <input-file>, where <filter>
+# is the value of the INPUT_FILTER tag, and <input-file> is the name of an
+# input file. Doxygen will then use the output that the filter program writes
+# to standard output.
+# If FILTER_PATTERNS is specified, this tag will be
+# ignored.
+
+INPUT_FILTER =
+
+# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern
+# basis.
+# Doxygen will compare the file name with each pattern and apply the
+# filter if there is a match.
+# The filters are a list of the form:
+# pattern=filter (like *.cpp=my_cpp_filter). See INPUT_FILTER for further
+# info on how filters are used. If FILTER_PATTERNS is empty or if
+# non of the patterns match the file name, INPUT_FILTER is applied.
+
+FILTER_PATTERNS =
+
+# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using
+# INPUT_FILTER) will be used to filter the input files when producing source
+# files to browse (i.e. when SOURCE_BROWSER is set to YES).
+
+FILTER_SOURCE_FILES = NO
+
+# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file
+# pattern. A pattern will override the setting for FILTER_PATTERN (if any)
+# and it is also possible to disable source filtering for a specific pattern
+# using *.ext= (so without naming a filter). This option only has effect when
+# FILTER_SOURCE_FILES is enabled.
+
+FILTER_SOURCE_PATTERNS =
+
+#---------------------------------------------------------------------------
+# configuration options related to source browsing
+#---------------------------------------------------------------------------
+
+# If the SOURCE_BROWSER tag is set to YES then a list of source files will
+# be generated. Documented entities will be cross-referenced with these sources.
+# Note: To get rid of all source code in the generated output, make sure also
+# VERBATIM_HEADERS is set to NO.
+
+SOURCE_BROWSER = NO
+
+# Setting the INLINE_SOURCES tag to YES will include the body
+# of functions and classes directly in the documentation.
+
+INLINE_SOURCES = NO
+
+# Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct
+# doxygen to hide any special comment blocks from generated source code
+# fragments. Normal C and C++ comments will always remain visible.
+
+STRIP_CODE_COMMENTS = YES
+
+# If the REFERENCED_BY_RELATION tag is set to YES
+# then for each documented function all documented
+# functions referencing it will be listed.
+
+REFERENCED_BY_RELATION = NO
+
+# If the REFERENCES_RELATION tag is set to YES
+# then for each documented function all documented entities
+# called/used by that function will be listed.
+
+REFERENCES_RELATION = NO
+
+# If the REFERENCES_LINK_SOURCE tag is set to YES (the default)
+# and SOURCE_BROWSER tag is set to YES, then the hyperlinks from
+# functions in REFERENCES_RELATION and REFERENCED_BY_RELATION lists will
+# link to the source code.
+# Otherwise they will link to the documentation.
+
+REFERENCES_LINK_SOURCE = YES
+
+# If the USE_HTAGS tag is set to YES then the references to source code
+# will point to the HTML generated by the htags(1) tool instead of doxygen
+# built-in source browser. The htags tool is part of GNU's global source
+# tagging system (see http://www.gnu.org/software/global/global.html). You
+# will need version 4.8.6 or higher.
+
+USE_HTAGS = NO
+
+# If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen
+# will generate a verbatim copy of the header file for each class for
+# which an include is specified. Set to NO to disable this.
+
+VERBATIM_HEADERS = YES
+
+#---------------------------------------------------------------------------
+# configuration options related to the alphabetical class index
+#---------------------------------------------------------------------------
+
+# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index
+# of all compounds will be generated. Enable this if the project
+# contains a lot of classes, structs, unions or interfaces.
+
+ALPHABETICAL_INDEX = NO
+
+# If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then
+# the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns
+# in which this list will be split (can be a number in the range [1..20])
+
+COLS_IN_ALPHA_INDEX = 5
+
+# In case all classes in a project start with a common prefix, all
+# classes will be put under the same header in the alphabetical index.
+# The IGNORE_PREFIX tag can be used to specify one or more prefixes that
+# should be ignored while generating the index headers.
+
+IGNORE_PREFIX =
+
+#---------------------------------------------------------------------------
+# configuration options related to the HTML output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_HTML tag is set to YES (the default) Doxygen will
+# generate HTML output.
+
+GENERATE_HTML = YES
+
+# The HTML_OUTPUT tag is used to specify where the HTML docs will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `html' will be used as the default path.
+
+HTML_OUTPUT = html
+
+# The HTML_FILE_EXTENSION tag can be used to specify the file extension for
+# each generated HTML page (for example: .htm,.php,.asp). If it is left blank
+# doxygen will generate files with .html extension.
+
+HTML_FILE_EXTENSION = .html
+
+# The HTML_HEADER tag can be used to specify a personal HTML header for
+# each generated HTML page. If it is left blank doxygen will generate a
+# standard header.
+
+HTML_HEADER =
+
+# The HTML_FOOTER tag can be used to specify a personal HTML footer for
+# each generated HTML page. If it is left blank doxygen will generate a
+# standard footer.
+
+HTML_FOOTER =
+
+# The HTML_STYLESHEET tag can be used to specify a user-defined cascading
+# style sheet that is used by each HTML page. It can be used to
+# fine-tune the look of the HTML output. If the tag is left blank doxygen
+# will generate a default style sheet. Note that doxygen will try to copy
+# the style sheet file to the HTML output directory, so don't put your own
+# stylesheet in the HTML output directory as well, or it will be erased!
+
+HTML_STYLESHEET =
+
+# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output.
+# Doxygen will adjust the colors in the stylesheet and background images
+# according to this color. Hue is specified as an angle on a colorwheel,
+# see http://en.wikipedia.org/wiki/Hue for more information.
+# For instance the value 0 represents red, 60 is yellow, 120 is green,
+# 180 is cyan, 240 is blue, 300 purple, and 360 is red again.
+# The allowed range is 0 to 359.
+
+HTML_COLORSTYLE_HUE = 220
+
+# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of
+# the colors in the HTML output. For a value of 0 the output will use
+# grayscales only. A value of 255 will produce the most vivid colors.
+
+HTML_COLORSTYLE_SAT = 100
+
+# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to
+# the luminance component of the colors in the HTML output. Values below
+# 100 gradually make the output lighter, whereas values above 100 make
+# the output darker. The value divided by 100 is the actual gamma applied,
+# so 80 represents a gamma of 0.8, The value 220 represents a gamma of 2.2,
+# and 100 does not change the gamma.
+
+HTML_COLORSTYLE_GAMMA = 80
+
+# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML
+# page will contain the date and time when the page was generated. Setting
+# this to NO can help when comparing the output of multiple runs.
+
+HTML_TIMESTAMP = NO
+
+# If the HTML_ALIGN_MEMBERS tag is set to YES, the members of classes,
+# files or namespaces will be aligned in HTML using tables. If set to
+# NO a bullet list will be used.
+
+HTML_ALIGN_MEMBERS = YES
+
+# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML
+# documentation will contain sections that can be hidden and shown after the
+# page has loaded. For this to work a browser that supports
+# JavaScript and DHTML is required (for instance Mozilla 1.0+, Firefox
+# Netscape 6.0+, Internet explorer 5.0+, Konqueror, or Safari).
+
+HTML_DYNAMIC_SECTIONS = NO
+
+# If the GENERATE_DOCSET tag is set to YES, additional index files
+# will be generated that can be used as input for Apple's Xcode 3
+# integrated development environment, introduced with OSX 10.5 (Leopard).
+# To create a documentation set, doxygen will generate a Makefile in the
+# HTML output directory. Running make will produce the docset in that
+# directory and running "make install" will install the docset in
+# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find
+# it at startup.
+# See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html
+# for more information.
+
+GENERATE_DOCSET = NO
+
+# When GENERATE_DOCSET tag is set to YES, this tag determines the name of the
+# feed. A documentation feed provides an umbrella under which multiple
+# documentation sets from a single provider (such as a company or product suite)
+# can be grouped.
+
+DOCSET_FEEDNAME = "Doxygen generated docs"
+
+# When GENERATE_DOCSET tag is set to YES, this tag specifies a string that
+# should uniquely identify the documentation set bundle. This should be a
+# reverse domain-name style string, e.g. com.mycompany.MyDocSet. Doxygen
+# will append .docset to the name.
+
+DOCSET_BUNDLE_ID = org.doxygen.Project
+
+# When GENERATE_PUBLISHER_ID tag specifies a string that should uniquely identify
+# the documentation publisher. This should be a reverse domain-name style
+# string, e.g. com.mycompany.MyDocSet.documentation.
+
+DOCSET_PUBLISHER_ID = org.doxygen.Publisher
+
+# The GENERATE_PUBLISHER_NAME tag identifies the documentation publisher.
+
+DOCSET_PUBLISHER_NAME = Publisher
+
+# If the GENERATE_HTMLHELP tag is set to YES, additional index files
+# will be generated that can be used as input for tools like the
+# Microsoft HTML help workshop to generate a compiled HTML help file (.chm)
+# of the generated HTML documentation.
+
+GENERATE_HTMLHELP = NO
+
+# If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can
+# be used to specify the file name of the resulting .chm file. You
+# can add a path in front of the file if the result should not be
+# written to the html output directory.
+
+CHM_FILE =
+
+# If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can
+# be used to specify the location (absolute path including file name) of
+# the HTML help compiler (hhc.exe). If non-empty doxygen will try to run
+# the HTML help compiler on the generated index.hhp.
+
+HHC_LOCATION =
+
+# If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag
+# controls if a separate .chi index file is generated (YES) or that
+# it should be included in the master .chm file (NO).
+
+GENERATE_CHI = NO
+
+# If the GENERATE_HTMLHELP tag is set to YES, the CHM_INDEX_ENCODING
+# is used to encode HtmlHelp index (hhk), content (hhc) and project file
+# content.
+
+CHM_INDEX_ENCODING =
+
+# If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag
+# controls whether a binary table of contents is generated (YES) or a
+# normal table of contents (NO) in the .chm file.
+
+BINARY_TOC = NO
+
+# The TOC_EXPAND flag can be set to YES to add extra items for group members
+# to the contents of the HTML help documentation and to the tree view.
+
+TOC_EXPAND = NO
+
+# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and
+# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated
+# that can be used as input for Qt's qhelpgenerator to generate a
+# Qt Compressed Help (.qch) of the generated HTML documentation.
+
+GENERATE_QHP = NO
+
+# If the QHG_LOCATION tag is specified, the QCH_FILE tag can
+# be used to specify the file name of the resulting .qch file.
+# The path specified is relative to the HTML output folder.
+
+QCH_FILE =
+
+# The QHP_NAMESPACE tag specifies the namespace to use when generating
+# Qt Help Project output. For more information please see
+# http://doc.trolltech.com/qthelpproject.html#namespace
+
+QHP_NAMESPACE =
+
+# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating
+# Qt Help Project output. For more information please see
+# http://doc.trolltech.com/qthelpproject.html#virtual-folders
+
+QHP_VIRTUAL_FOLDER = doc
+
+# If QHP_CUST_FILTER_NAME is set, it specifies the name of a custom filter to
+# add. For more information please see
+# http://doc.trolltech.com/qthelpproject.html#custom-filters
+
+QHP_CUST_FILTER_NAME =
+
+# The QHP_CUST_FILT_ATTRS tag specifies the list of the attributes of the
+# custom filter to add. For more information please see
+# <a href="http://doc.trolltech.com/qthelpproject.html#custom-filters">
+# Qt Help Project / Custom Filters</a>.
+
+QHP_CUST_FILTER_ATTRS =
+
+# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this
+# project's
+# filter section matches.
+# <a href="http://doc.trolltech.com/qthelpproject.html#filter-attributes">
+# Qt Help Project / Filter Attributes</a>.
+
+QHP_SECT_FILTER_ATTRS =
+
+# If the GENERATE_QHP tag is set to YES, the QHG_LOCATION tag can
+# be used to specify the location of Qt's qhelpgenerator.
+# If non-empty doxygen will try to run qhelpgenerator on the generated
+# .qhp file.
+
+QHG_LOCATION =
+
+# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files
+# will be generated, which together with the HTML files, form an Eclipse help
+# plugin. To install this plugin and make it available under the help contents
+# menu in Eclipse, the contents of the directory containing the HTML and XML
+# files needs to be copied into the plugins directory of eclipse. The name of
+# the directory within the plugins directory should be the same as
+# the ECLIPSE_DOC_ID value. After copying Eclipse needs to be restarted before
+# the help appears.
+
+GENERATE_ECLIPSEHELP = NO
+
+# A unique identifier for the eclipse help plugin. When installing the plugin
+# the directory name containing the HTML and XML files should also have
+# this name.
+
+ECLIPSE_DOC_ID = org.doxygen.Project
+
+# The DISABLE_INDEX tag can be used to turn on/off the condensed index at
+# top of each HTML page. The value NO (the default) enables the index and
+# the value YES disables it.
+
+DISABLE_INDEX = NO
+
+# This tag can be used to set the number of enum values (range [0,1..20])
+# that doxygen will group on one line in the generated HTML documentation.
+# Note that a value of 0 will completely suppress the enum values from appearing in the overview section.
+
+ENUM_VALUES_PER_LINE = 4
+
+# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index
+# structure should be generated to display hierarchical information.
+# If the tag value is set to YES, a side panel will be generated
+# containing a tree-like index structure (just like the one that
+# is generated for HTML Help). For this to work a browser that supports
+# JavaScript, DHTML, CSS and frames is required (i.e. any modern browser).
+# Windows users are probably better off using the HTML help feature.
+
+GENERATE_TREEVIEW = NONE
+
+# By enabling USE_INLINE_TREES, doxygen will generate the Groups, Directories,
+# and Class Hierarchy pages using a tree view instead of an ordered list.
+
+USE_INLINE_TREES = NO
+
+# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be
+# used to set the initial width (in pixels) of the frame in which the tree
+# is shown.
+
+TREEVIEW_WIDTH = 250
+
+# When the EXT_LINKS_IN_WINDOW option is set to YES doxygen will open
+# links to external symbols imported via tag files in a separate window.
+
+EXT_LINKS_IN_WINDOW = NO
+
+# Use this tag to change the font size of Latex formulas included
+# as images in the HTML documentation. The default is 10. Note that
+# when you change the font size after a successful doxygen run you need
+# to manually remove any form_*.png images from the HTML output directory
+# to force them to be regenerated.
+
+FORMULA_FONTSIZE = 10
+
+# Use the FORMULA_TRANPARENT tag to determine whether or not the images
+# generated for formulas are transparent PNGs. Transparent PNGs are
+# not supported properly for IE 6.0, but are supported on all modern browsers.
+# Note that when changing this option you need to delete any form_*.png files
+# in the HTML output before the changes have effect.
+
+FORMULA_TRANSPARENT = YES
+
+# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax
+# (see http://www.mathjax.org) which uses client side Javascript for the
+# rendering instead of using prerendered bitmaps. Use this if you do not
+# have LaTeX installed or if you want to formulas look prettier in the HTML
+# output. When enabled you also need to install MathJax separately and
+# configure the path to it using the MATHJAX_RELPATH option.
+
+USE_MATHJAX = NO
+
+# When MathJax is enabled you need to specify the location relative to the
+# HTML output directory using the MATHJAX_RELPATH option. The destination
+# directory should contain the MathJax.js script. For instance, if the mathjax
+# directory is located at the same level as the HTML output directory, then
+# MATHJAX_RELPATH should be ../mathjax. The default value points to the mathjax.org site, so you can quickly see the result without installing
+# MathJax, but it is strongly recommended to install a local copy of MathJax
+# before deployment.
+
+MATHJAX_RELPATH = http://www.mathjax.org/mathjax
+
+# When the SEARCHENGINE tag is enabled doxygen will generate a search box
+# for the HTML output. The underlying search engine uses javascript
+# and DHTML and should work on any modern browser. Note that when using
+# HTML help (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets
+# (GENERATE_DOCSET) there is already a search function so this one should
+# typically be disabled. For large projects the javascript based search engine
+# can be slow, then enabling SERVER_BASED_SEARCH may provide a better solution.
+
+SEARCHENGINE = NO
+
+# When the SERVER_BASED_SEARCH tag is enabled the search engine will be
+# implemented using a PHP enabled web server instead of at the web client
+# using Javascript. Doxygen will generate the search PHP script and index
+# file to put on the web server. The advantage of the server
+# based approach is that it scales better to large projects and allows
+# full text search. The disadvantages are that it is more difficult to setup
+# and does not have live searching capabilities.
+
+SERVER_BASED_SEARCH = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the LaTeX output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_LATEX tag is set to YES (the default) Doxygen will
+# generate Latex output.
+
+GENERATE_LATEX = YES
+
+# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `latex' will be used as the default path.
+
+LATEX_OUTPUT = latex
+
+# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be
+# invoked. If left blank `latex' will be used as the default command name.
+# Note that when enabling USE_PDFLATEX this option is only used for
+# generating bitmaps for formulas in the HTML output, but not in the
+# Makefile that is written to the output directory.
+
+LATEX_CMD_NAME = latex
+
+# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to
+# generate index for LaTeX. If left blank `makeindex' will be used as the
+# default command name.
+
+MAKEINDEX_CMD_NAME = makeindex
+
+# If the COMPACT_LATEX tag is set to YES Doxygen generates more compact
+# LaTeX documents. This may be useful for small projects and may help to
+# save some trees in general.
+
+COMPACT_LATEX = NO
+
+# The PAPER_TYPE tag can be used to set the paper type that is used
+# by the printer. Possible values are: a4, letter, legal and
+# executive. If left blank a4wide will be used.
+
+PAPER_TYPE = a4wide
+
+# The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX
+# packages that should be included in the LaTeX output.
+
+EXTRA_PACKAGES =
+
+# The LATEX_HEADER tag can be used to specify a personal LaTeX header for
+# the generated latex document. The header should contain everything until
+# the first chapter. If it is left blank doxygen will generate a
+# standard header. Notice: only use this tag if you know what you are doing!
+
+LATEX_HEADER =
+
+# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated
+# is prepared for conversion to pdf (using ps2pdf). The pdf file will
+# contain links (just like the HTML output) instead of page references
+# This makes the output suitable for online browsing using a pdf viewer.
+
+PDF_HYPERLINKS = YES
+
+# If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of
+# plain latex in the generated Makefile. Set this option to YES to get a
+# higher quality PDF documentation.
+
+USE_PDFLATEX = YES
+
+# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode.
+# command to the generated LaTeX files. This will instruct LaTeX to keep
+# running if errors occur, instead of asking the user for help.
+# This option is also used when generating formulas in HTML.
+
+LATEX_BATCHMODE = NO
+
+# If LATEX_HIDE_INDICES is set to YES then doxygen will not
+# include the index chapters (such as File Index, Compound Index, etc.)
+# in the output.
+
+LATEX_HIDE_INDICES = NO
+
+# If LATEX_SOURCE_CODE is set to YES then doxygen will include
+# source code with syntax highlighting in the LaTeX output.
+# Note that which sources are shown also depends on other settings
+# such as SOURCE_BROWSER.
+
+LATEX_SOURCE_CODE = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the RTF output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output
+# The RTF output is optimized for Word 97 and may not look very pretty with
+# other RTF readers or editors.
+
+GENERATE_RTF = NO
+
+# The RTF_OUTPUT tag is used to specify where the RTF docs will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `rtf' will be used as the default path.
+
+RTF_OUTPUT = rtf
+
+# If the COMPACT_RTF tag is set to YES Doxygen generates more compact
+# RTF documents. This may be useful for small projects and may help to
+# save some trees in general.
+
+COMPACT_RTF = NO
+
+# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated
+# will contain hyperlink fields. The RTF file will
+# contain links (just like the HTML output) instead of page references.
+# This makes the output suitable for online browsing using WORD or other
+# programs which support those fields.
+# Note: wordpad (write) and others do not support links.
+
+RTF_HYPERLINKS = NO
+
+# Load stylesheet definitions from file. Syntax is similar to doxygen's
+# config file, i.e. a series of assignments. You only have to provide
+# replacements, missing definitions are set to their default value.
+
+RTF_STYLESHEET_FILE =
+
+# Set optional variables used in the generation of an rtf document.
+# Syntax is similar to doxygen's config file.
+
+RTF_EXTENSIONS_FILE =
+
+#---------------------------------------------------------------------------
+# configuration options related to the man page output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_MAN tag is set to YES (the default) Doxygen will
+# generate man pages
+
+GENERATE_MAN = YES
+
+# The MAN_OUTPUT tag is used to specify where the man pages will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `man' will be used as the default path.
+
+MAN_OUTPUT = man
+
+# The MAN_EXTENSION tag determines the extension that is added to
+# the generated man pages (default is the subroutine's section .3)
+
+MAN_EXTENSION = .3
+
+# If the MAN_LINKS tag is set to YES and Doxygen generates man output,
+# then it will generate one additional man file for each entity
+# documented in the real man page(s). These additional files
+# only source the real man page, but without them the man command
+# would be unable to find the correct page. The default is NO.
+
+MAN_LINKS = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the XML output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_XML tag is set to YES Doxygen will
+# generate an XML file that captures the structure of
+# the code including all documentation.
+
+GENERATE_XML = NO
+
+# The XML_OUTPUT tag is used to specify where the XML pages will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `xml' will be used as the default path.
+
+XML_OUTPUT = xml
+
+# The XML_SCHEMA tag can be used to specify an XML schema,
+# which can be used by a validating XML parser to check the
+# syntax of the XML files.
+
+XML_SCHEMA =
+
+# The XML_DTD tag can be used to specify an XML DTD,
+# which can be used by a validating XML parser to check the
+# syntax of the XML files.
+
+XML_DTD =
+
+# If the XML_PROGRAMLISTING tag is set to YES Doxygen will
+# dump the program listings (including syntax highlighting
+# and cross-referencing information) to the XML output. Note that
+# enabling this will significantly increase the size of the XML output.
+
+XML_PROGRAMLISTING = YES
+
+#---------------------------------------------------------------------------
+# configuration options for the AutoGen Definitions output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will
+# generate an AutoGen Definitions (see autogen.sf.net) file
+# that captures the structure of the code including all
+# documentation. Note that this feature is still experimental
+# and incomplete at the moment.
+
+GENERATE_AUTOGEN_DEF = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the Perl module output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_PERLMOD tag is set to YES Doxygen will
+# generate a Perl module file that captures the structure of
+# the code including all documentation. Note that this
+# feature is still experimental and incomplete at the
+# moment.
+
+GENERATE_PERLMOD = NO
+
+# If the PERLMOD_LATEX tag is set to YES Doxygen will generate
+# the necessary Makefile rules, Perl scripts and LaTeX code to be able
+# to generate PDF and DVI output from the Perl module output.
+
+PERLMOD_LATEX = NO
+
+# If the PERLMOD_PRETTY tag is set to YES the Perl module output will be
+# nicely formatted so it can be parsed by a human reader.
+# This is useful
+# if you want to understand what is going on.
+# On the other hand, if this
+# tag is set to NO the size of the Perl module output will be much smaller
+# and Perl will parse it just the same.
+
+PERLMOD_PRETTY = YES
+
+# The names of the make variables in the generated doxyrules.make file
+# are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX.
+# This is useful so different doxyrules.make files included by the same
+# Makefile don't overwrite each other's variables.
+
+PERLMOD_MAKEVAR_PREFIX =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the preprocessor
+#---------------------------------------------------------------------------
+
+# If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will
+# evaluate all C-preprocessor directives found in the sources and include
+# files.
+
+ENABLE_PREPROCESSING = YES
+
+# If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro
+# names in the source code. If set to NO (the default) only conditional
+# compilation will be performed. Macro expansion can be done in a controlled
+# way by setting EXPAND_ONLY_PREDEF to YES.
+
+MACRO_EXPANSION = YES
+
+# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES
+# then the macro expansion is limited to the macros specified with the
+# PREDEFINED and EXPAND_AS_DEFINED tags.
+
+EXPAND_ONLY_PREDEF = YES
+
+# If the SEARCH_INCLUDES tag is set to YES (the default) the includes files
+# in the INCLUDE_PATH (see below) will be search if a #include is found.
+
+SEARCH_INCLUDES = YES
+
+# The INCLUDE_PATH tag can be used to specify one or more directories that
+# contain include files that are not input files but should be processed by
+# the preprocessor.
+
+INCLUDE_PATH =
+
+# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard
+# patterns (like *.h and *.hpp) to filter out the header-files in the
+# directories. If left blank, the patterns specified with FILE_PATTERNS will
+# be used.
+
+INCLUDE_FILE_PATTERNS =
+
+# The PREDEFINED tag can be used to specify one or more macro names that
+# are defined before the preprocessor is started (similar to the -D option of
+# gcc). The argument of the tag is a list of macros of the form: name
+# or name=definition (no spaces). If the definition and the = are
+# omitted =1 is assumed. To prevent a macro definition from being
+# undefined via #undef or recursively expanded use the := operator
+# instead of the = operator.
+
+PREDEFINED = DOXYGEN \
+ PRINTF_ATTRIBUTE(x,y)=
+
+# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then
+# this tag can be used to specify a list of macro names that should be expanded.
+# The macro definition that is found in the sources will be used.
+# Use the PREDEFINED tag if you want to use a different macro definition that overrules the definition found in the source code.
+
+EXPAND_AS_DEFINED =
+
+# If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then
+# doxygen's preprocessor will remove all references to function-like macros
+# that are alone on a line, have an all uppercase name, and do not end with a
+# semicolon, because these will confuse the parser if not removed.
+
+SKIP_FUNCTION_MACROS = YES
+
+#---------------------------------------------------------------------------
+# Configuration::additions related to external references
+#---------------------------------------------------------------------------
+
+# The TAGFILES option can be used to specify one or more tagfiles.
+# Optionally an initial location of the external documentation
+# can be added for each tagfile. The format of a tag file without
+# this location is as follows:
+#
+# TAGFILES = file1 file2 ...
+# Adding location for the tag files is done as follows:
+#
+# TAGFILES = file1=loc1 "file2 = loc2" ...
+# where "loc1" and "loc2" can be relative or absolute paths or
+# URLs. If a location is present for each tag, the installdox tool
+# does not have to be run to correct the links.
+# Note that each tag file must have a unique name
+# (where the name does NOT include the path)
+# If a tag file is not located in the directory in which doxygen
+# is run, you must also specify the path to the tagfile here.
+
+TAGFILES =
+
+# When a file name is specified after GENERATE_TAGFILE, doxygen will create
+# a tag file that is based on the input files it reads.
+
+GENERATE_TAGFILE =
+
+# If the ALLEXTERNALS tag is set to YES all external classes will be listed
+# in the class index. If set to NO only the inherited external classes
+# will be listed.
+
+ALLEXTERNALS = NO
+
+# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed
+# in the modules index. If set to NO, only the current project's groups will
+# be listed.
+
+EXTERNAL_GROUPS = YES
+
+# The PERL_PATH should be the absolute path and name of the perl script
+# interpreter (i.e. the result of `which perl').
+
+PERL_PATH = /usr/bin/perl
+
+#---------------------------------------------------------------------------
+# Configuration options related to the dot tool
+#---------------------------------------------------------------------------
+
+# If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will
+# generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base
+# or super classes. Setting the tag to NO turns the diagrams off. Note that
+# this option also works with HAVE_DOT disabled, but it is recommended to
+# install and use dot, since it yields more powerful graphs.
+
+CLASS_DIAGRAMS = YES
+
+# You can define message sequence charts within doxygen comments using the \msc
+# command. Doxygen will then run the mscgen tool (see
+# http://www.mcternan.me.uk/mscgen/) to produce the chart and insert it in the
+# documentation. The MSCGEN_PATH tag allows you to specify the directory where
+# the mscgen tool resides. If left empty the tool is assumed to be found in the
+# default search path.
+
+MSCGEN_PATH =
+
+# If set to YES, the inheritance and collaboration graphs will hide
+# inheritance and usage relations if the target is undocumented
+# or is not a class.
+
+HIDE_UNDOC_RELATIONS = YES
+
+# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is
+# available from the path. This tool is part of Graphviz, a graph visualization
+# toolkit from AT&T and Lucent Bell Labs. The other options in this section
+# have no effect if this option is set to NO (the default)
+
+HAVE_DOT = NO
+
+# The DOT_NUM_THREADS specifies the number of dot invocations doxygen is
+# allowed to run in parallel. When set to 0 (the default) doxygen will
+# base this on the number of processors available in the system. You can set it
+# explicitly to a value larger than 0 to get control over the balance
+# between CPU load and processing speed.
+
+DOT_NUM_THREADS = 0
+
+# By default doxygen will write a font called Helvetica to the output
+# directory and reference it in all dot files that doxygen generates.
+# When you want a differently looking font you can specify the font name
+# using DOT_FONTNAME. You need to make sure dot is able to find the font,
+# which can be done by putting it in a standard location or by setting the
+# DOTFONTPATH environment variable or by setting DOT_FONTPATH to the directory
+# containing the font.
+
+DOT_FONTNAME = FreeSans
+
+# The DOT_FONTSIZE tag can be used to set the size of the font of dot graphs.
+# The default size is 10pt.
+
+DOT_FONTSIZE = 10
+
+# By default doxygen will tell dot to use the output directory to look for the
+# FreeSans.ttf font (which doxygen will put there itself). If you specify a
+# different font using DOT_FONTNAME you can set the path where dot
+# can find it using this tag.
+
+DOT_FONTPATH =
+
+# If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen
+# will generate a graph for each documented class showing the direct and
+# indirect inheritance relations. Setting this tag to YES will force the
+# the CLASS_DIAGRAMS tag to NO.
+
+CLASS_GRAPH = YES
+
+# If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen
+# will generate a graph for each documented class showing the direct and
+# indirect implementation dependencies (inheritance, containment, and
+# class references variables) of the class with other documented classes.
+
+COLLABORATION_GRAPH = YES
+
+# If the GROUP_GRAPHS and HAVE_DOT tags are set to YES then doxygen
+# will generate a graph for groups, showing the direct groups dependencies
+
+GROUP_GRAPHS = YES
+
+# If the UML_LOOK tag is set to YES doxygen will generate inheritance and
+# collaboration diagrams in a style similar to the OMG's Unified Modeling
+# Language.
+
+UML_LOOK = NO
+
+# If set to YES, the inheritance and collaboration graphs will show the
+# relations between templates and their instances.
+
+TEMPLATE_RELATIONS = NO
+
+# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT
+# tags are set to YES then doxygen will generate a graph for each documented
+# file showing the direct and indirect include dependencies of the file with
+# other documented files.
+
+INCLUDE_GRAPH = YES
+
+# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and
+# HAVE_DOT tags are set to YES then doxygen will generate a graph for each
+# documented header file showing the documented files that directly or
+# indirectly include this file.
+
+INCLUDED_BY_GRAPH = YES
+
+# If the CALL_GRAPH and HAVE_DOT options are set to YES then
+# doxygen will generate a call dependency graph for every global function
+# or class method. Note that enabling this option will significantly increase
+# the time of a run. So in most cases it will be better to enable call graphs
+# for selected functions only using the \callgraph command.
+
+CALL_GRAPH = NO
+
+# If the CALLER_GRAPH and HAVE_DOT tags are set to YES then
+# doxygen will generate a caller dependency graph for every global function
+# or class method. Note that enabling this option will significantly increase
+# the time of a run. So in most cases it will be better to enable caller
+# graphs for selected functions only using the \callergraph command.
+
+CALLER_GRAPH = NO
+
+# If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen
+# will generate a graphical hierarchy of all classes instead of a textual one.
+
+GRAPHICAL_HIERARCHY = YES
+
+# If the DIRECTORY_GRAPH, SHOW_DIRECTORIES and HAVE_DOT tags are set to YES
+# then doxygen will show the dependencies a directory has on other directories
+# in a graphical way. The dependency relations are determined by the #include
+# relations between the files in the directories.
+
+DIRECTORY_GRAPH = YES
+
+# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images
+# generated by dot. Possible values are png, svg, gif or svg.
+# If left blank png will be used.
+
+DOT_IMAGE_FORMAT = png
+
+# The tag DOT_PATH can be used to specify the path where the dot tool can be
+# found. If left blank, it is assumed the dot tool can be found in the path.
+
+DOT_PATH =
+
+# The DOTFILE_DIRS tag can be used to specify one or more directories that
+# contain dot files that are included in the documentation (see the
+# \dotfile command).
+
+DOTFILE_DIRS =
+
+# The MSCFILE_DIRS tag can be used to specify one or more directories that
+# contain msc files that are included in the documentation (see the
+# \mscfile command).
+
+MSCFILE_DIRS =
+
+# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of
+# nodes that will be shown in the graph. If the number of nodes in a graph
+# becomes larger than this value, doxygen will truncate the graph, which is
+# visualized by representing a node as a red box. Note that doxygen if the
+# number of direct children of the root node in a graph is already larger than
+# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note
+# that the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH.
+
+DOT_GRAPH_MAX_NODES = 50
+
+# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the
+# graphs generated by dot. A depth value of 3 means that only nodes reachable
+# from the root by following a path via at most 3 edges will be shown. Nodes
+# that lay further from the root node will be omitted. Note that setting this
+# option to 1 or 2 may greatly reduce the computation time needed for large
+# code bases. Also note that the size of a graph can be further restricted by
+# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction.
+
+MAX_DOT_GRAPH_DEPTH = 0
+
+# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent
+# background. This is disabled by default, because dot on Windows does not
+# seem to support this out of the box. Warning: Depending on the platform used,
+# enabling this option may lead to badly anti-aliased labels on the edges of
+# a graph (i.e. they become hard to read).
+
+DOT_TRANSPARENT = YES
+
+# Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output
+# files in one run (i.e. multiple -o and -T options on the command line). This
+# makes dot run faster, but since only newer versions of dot (>1.8.10)
+# support this, this feature is disabled by default.
+
+DOT_MULTI_TARGETS = NO
+
+# If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will
+# generate a legend page explaining the meaning of the various boxes and
+# arrows in the dot generated graphs.
+
+GENERATE_LEGEND = YES
+
+# If the DOT_CLEANUP tag is set to YES (the default) Doxygen will
+# remove the intermediate dot files that are used to generate
+# the various graphs.
+
+DOT_CLEANUP = YES
diff --git a/lib/tdb/include/tdb.h b/lib/tdb/include/tdb.h
new file mode 100644
index 0000000..884171c
--- /dev/null
+++ b/lib/tdb/include/tdb.h
@@ -0,0 +1,1038 @@
+#ifndef __TDB_H__
+#define __TDB_H__
+
+/*
+ Unix SMB/CIFS implementation.
+
+ trivial database library
+
+ Copyright (C) Andrew Tridgell 1999-2004
+
+ ** NOTE! The following LGPL license applies to the tdb
+ ** library. This does NOT imply that all of Samba is released
+ ** under the LGPL
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 3 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <signal.h>
+#include <stdbool.h>
+
+/* for old gcc releases that don't have the feature test macro __has_attribute */
+#ifndef __has_attribute
+#define __has_attribute(x) 0
+#endif
+
+#ifndef _PUBLIC_
+#if __has_attribute(visibility)
+#define _PUBLIC_ __attribute__((visibility("default")))
+#else
+#define _PUBLIC_
+#endif
+#endif
+
+/**
+ * @defgroup tdb The tdb API
+ *
+ * tdb is a Trivial database. In concept, it is very much like GDBM, and BSD's
+ * DB except that it allows multiple simultaneous writers and uses locking
+ * internally to keep writers from trampling on each other. tdb is also
+ * extremely small.
+ *
+ * @section tdb_interface Interface
+ *
+ * The interface is very similar to gdbm except for the following:
+ *
+ * <ul>
+ * <li>different open interface. The tdb_open call is more similar to a
+ * traditional open()</li>
+ * <li>no tdbm_reorganise() function</li>
+ * <li>no tdbm_sync() function. No operations are cached in the library
+ * anyway</li>
+ * <li>added a tdb_traverse() function for traversing the whole database</li>
+ * <li>added transactions support</li>
+ * </ul>
+ *
+ * A general rule for using tdb is that the caller frees any returned TDB_DATA
+ * structures. Just call free(p.dptr) to free a TDB_DATA return value called p.
+ * This is the same as gdbm.
+ *
+ * @{
+ */
+
+/** Flags to tdb_store() */
+#define TDB_REPLACE 1 /** Unused */
+#define TDB_INSERT 2 /** Don't overwrite an existing entry */
+#define TDB_MODIFY 3 /** Don't create an existing entry */
+
+/** Flags for tdb_open() */
+#define TDB_DEFAULT 0 /** just a readability place holder */
+#define TDB_CLEAR_IF_FIRST 1 /** If this is the first open, wipe the db */
+#define TDB_INTERNAL 2 /** Don't store on disk */
+#define TDB_NOLOCK 4 /** Don't do any locking */
+#define TDB_NOMMAP 8 /** Don't use mmap */
+#define TDB_CONVERT 16 /** Convert endian (internal use) */
+#define TDB_BIGENDIAN 32 /** Header is big-endian (internal use) */
+#define TDB_NOSYNC 64 /** Don't use synchronous transactions */
+#define TDB_SEQNUM 128 /** Maintain a sequence number */
+#define TDB_VOLATILE 256 /** Activate the per-hashchain freelist, default 5 */
+#define TDB_ALLOW_NESTING 512 /** Allow transactions to nest */
+#define TDB_DISALLOW_NESTING 1024 /** Disallow transactions to nest */
+#define TDB_INCOMPATIBLE_HASH 2048 /** Better hashing: can't be opened by tdb < 1.2.6. */
+#define TDB_MUTEX_LOCKING 4096 /** optimized locking using robust mutexes if supported,
+ only with tdb >= 1.3.0 and TDB_CLEAR_IF_FIRST
+ after checking tdb_runtime_check_for_robust_mutexes() */
+
+/** The tdb error codes */
+enum TDB_ERROR {TDB_SUCCESS=0, TDB_ERR_CORRUPT, TDB_ERR_IO, TDB_ERR_LOCK,
+ TDB_ERR_OOM, TDB_ERR_EXISTS, TDB_ERR_NOLOCK, TDB_ERR_LOCK_TIMEOUT,
+ TDB_ERR_NOEXIST, TDB_ERR_EINVAL, TDB_ERR_RDONLY,
+ TDB_ERR_NESTING};
+
+/** Debugging uses one of the following levels */
+enum tdb_debug_level {TDB_DEBUG_FATAL = 0, TDB_DEBUG_ERROR,
+ TDB_DEBUG_WARNING, TDB_DEBUG_TRACE};
+
+/** The tdb data structure */
+typedef struct TDB_DATA {
+ unsigned char *dptr;
+ size_t dsize;
+} TDB_DATA;
+
+#ifndef PRINTF_ATTRIBUTE
+#if __has_attribute(format) || (__GNUC__ >= 3)
+/** Use gcc attribute to check printf fns. a1 is the 1-based index of
+ * the parameter containing the format, and a2 the index of the first
+ * argument. Note that some gcc 2.x versions don't handle this
+ * properly **/
+#define PRINTF_ATTRIBUTE(a1, a2) __attribute__ ((format (__printf__, a1, a2)))
+#else
+#define PRINTF_ATTRIBUTE(a1, a2)
+#endif
+#endif
+
+/** This is the context structure that is returned from a db open. */
+typedef struct tdb_context TDB_CONTEXT;
+
+typedef int (*tdb_traverse_func)(struct tdb_context *, TDB_DATA, TDB_DATA, void *);
+typedef void (*tdb_log_func)(struct tdb_context *, enum tdb_debug_level, const char *, ...) PRINTF_ATTRIBUTE(3, 4);
+typedef unsigned int (*tdb_hash_func)(TDB_DATA *key);
+
+struct tdb_logging_context {
+ tdb_log_func log_fn;
+ void *log_private;
+};
+
+/**
+ * @brief Open the database and creating it if necessary.
+ *
+ * @param[in] name The name of the db to open.
+ *
+ * @param[in] hash_size The hash size is advisory, use zero for a default
+ * value.
+ *
+ * @param[in] tdb_flags The flags to use to open the db:\n\n
+ * TDB_CLEAR_IF_FIRST - Clear database if we are the
+ * only one with it open\n
+ * TDB_INTERNAL - Don't use a file, instead store the
+ * data in memory. The filename is
+ * ignored in this case.\n
+ * TDB_NOLOCK - Don't do any locking\n
+ * TDB_NOMMAP - Don't use mmap\n
+ * TDB_NOSYNC - Don't synchronise transactions to disk\n
+ * TDB_SEQNUM - Maintain a sequence number\n
+ * TDB_VOLATILE - activate the per-hashchain freelist,
+ * default 5.\n
+ * TDB_ALLOW_NESTING - Allow transactions to nest.\n
+ * TDB_DISALLOW_NESTING - Disallow transactions to nest.\n
+ * TDB_INCOMPATIBLE_HASH - Better hashing: can't be opened by tdb < 1.2.6.\n
+ * TDB_MUTEX_LOCKING - Optimized locking using robust mutexes if supported,
+ * can't be opened by tdb < 1.3.0.
+ * Only valid in combination with TDB_CLEAR_IF_FIRST
+ * after checking tdb_runtime_check_for_robust_mutexes()\n
+ *
+ * @param[in] open_flags Flags for the open(2) function.
+ *
+ * @param[in] mode The mode for the open(2) function.
+ *
+ * @return A tdb context structure, NULL on error.
+ */
+_PUBLIC_ struct tdb_context *tdb_open(const char *name, int hash_size, int tdb_flags,
+ int open_flags, mode_t mode);
+
+/**
+ * @brief Open the database and creating it if necessary.
+ *
+ * This is like tdb_open(), but allows you to pass an initial logging and
+ * hash function. Be careful when passing a hash function - all users of the
+ * database must use the same hash function or you will get data corruption.
+ *
+ * @param[in] name The name of the db to open.
+ *
+ * @param[in] hash_size The hash size is advisory, use zero for a default
+ * value.
+ *
+ * @param[in] tdb_flags The flags to use to open the db:\n\n
+ * TDB_CLEAR_IF_FIRST - Clear database if we are the
+ * only one with it open\n
+ * TDB_INTERNAL - Don't use a file, instead store the
+ * data in memory. The filename is
+ * ignored in this case.\n
+ * TDB_NOLOCK - Don't do any locking\n
+ * TDB_NOMMAP - Don't use mmap\n
+ * TDB_NOSYNC - Don't synchronise transactions to disk\n
+ * TDB_SEQNUM - Maintain a sequence number\n
+ * TDB_VOLATILE - activate the per-hashchain freelist,
+ * default 5.\n
+ * TDB_ALLOW_NESTING - Allow transactions to nest.\n
+ * TDB_DISALLOW_NESTING - Disallow transactions to nest.\n
+ * TDB_INCOMPATIBLE_HASH - Better hashing: can't be opened by tdb < 1.2.6.\n
+ * TDB_MUTEX_LOCKING - Optimized locking using robust mutexes if supported,
+ * can't be opened by tdb < 1.3.0.
+ * Only valid in combination with TDB_CLEAR_IF_FIRST
+ * after checking tdb_runtime_check_for_robust_mutexes()\n
+ *
+ * @param[in] open_flags Flags for the open(2) function.
+ *
+ * @param[in] mode The mode for the open(2) function.
+ *
+ * @param[in] log_ctx The logging function to use.
+ *
+ * @param[in] hash_fn The hash function you want to use.
+ *
+ * @return A tdb context structure, NULL on error.
+ *
+ * @see tdb_open()
+ */
+_PUBLIC_ struct tdb_context *tdb_open_ex(const char *name, int hash_size, int tdb_flags,
+ int open_flags, mode_t mode,
+ const struct tdb_logging_context *log_ctx,
+ tdb_hash_func hash_fn);
+
+/**
+ * @brief Set the maximum number of dead records per hash chain.
+ *
+ * @param[in] tdb The database handle to set the maximum.
+ *
+ * @param[in] max_dead The maximum number of dead records per hash chain.
+ */
+_PUBLIC_ void tdb_set_max_dead(struct tdb_context *tdb, int max_dead);
+
+/**
+ * @brief Reopen a tdb.
+ *
+ * This can be used after a fork to ensure that we have an independent seek
+ * pointer from our parent and to re-establish locks.
+ *
+ * @param[in] tdb The database to reopen. It will be free'd on error!
+ *
+ * @return 0 on success, -1 on error.
+ *
+ * @note Don't call tdb_error() after this function cause the tdb context will
+ * be freed on error.
+ */
+_PUBLIC_ int tdb_reopen(struct tdb_context *tdb);
+
+/**
+ * @brief Reopen all tdb's
+ *
+ * If the parent is longlived (ie. a parent daemon architecture), we know it
+ * will keep it's active lock on a tdb opened with CLEAR_IF_FIRST. Thus for
+ * child processes we don't have to add an active lock. This is essential to
+ * improve performance on systems that keep POSIX locks as a non-scalable data
+ * structure in the kernel.
+ *
+ * @param[in] parent_longlived Whether the parent is longlived or not.
+ *
+ * @return 0 on success, -1 on error.
+ */
+_PUBLIC_ int tdb_reopen_all(int parent_longlived);
+
+/**
+ * @brief Set a different tdb logging function.
+ *
+ * @param[in] tdb The tdb to set the logging function.
+ *
+ * @param[in] log_ctx The logging function to set.
+ */
+_PUBLIC_ void tdb_set_logging_function(struct tdb_context *tdb, const struct tdb_logging_context *log_ctx);
+
+/**
+ * @brief Get the tdb last error code.
+ *
+ * @param[in] tdb The tdb to get the error code from.
+ *
+ * @return A TDB_ERROR code.
+ *
+ * @see TDB_ERROR
+ */
+_PUBLIC_ enum TDB_ERROR tdb_error(struct tdb_context *tdb);
+
+/**
+ * @brief Get a error string for the last tdb error
+ *
+ * @param[in] tdb The tdb to get the error code from.
+ *
+ * @return An error string.
+ */
+_PUBLIC_ const char *tdb_errorstr(struct tdb_context *tdb);
+
+/**
+ * @brief Fetch an entry in the database given a key.
+ *
+ * The caller must free the resulting data.
+ *
+ * @param[in] tdb The tdb to fetch the key.
+ *
+ * @param[in] key The key to fetch.
+ *
+ * @return The key entry found in the database, NULL on error with
+ * TDB_ERROR set.
+ *
+ * @see tdb_error()
+ * @see tdb_errorstr()
+ */
+_PUBLIC_ TDB_DATA tdb_fetch(struct tdb_context *tdb, TDB_DATA key);
+
+/**
+ * @brief Hand a record to a parser function without allocating it.
+ *
+ * This function is meant as a fast tdb_fetch alternative for large records
+ * that are frequently read. The "key" and "data" arguments point directly
+ * into the tdb shared memory, they are not aligned at any boundary.
+ *
+ * @warning The parser is called while tdb holds a lock on the record. DO NOT
+ * call other tdb routines from within the parser. Also, for good performance
+ * you should make the parser fast to allow parallel operations.
+ *
+ * @param[in] tdb The tdb to parse the record.
+ *
+ * @param[in] key The key to parse.
+ *
+ * @param[in] parser The parser to use to parse the data.
+ *
+ * @param[in] private_data A private data pointer which is passed to the parser
+ * function.
+ *
+ * @return -1 if the record was not found. If the record was found,
+ * the return value of "parser" is passed up to the caller.
+ */
+_PUBLIC_ int tdb_parse_record(struct tdb_context *tdb, TDB_DATA key,
+ int (*parser)(TDB_DATA key, TDB_DATA data,
+ void *private_data),
+ void *private_data);
+
+/**
+ * @brief Delete an entry in the database given a key.
+ *
+ * @param[in] tdb The tdb to delete the key.
+ *
+ * @param[in] key The key to delete.
+ *
+ * @return 0 on success, -1 if the key doesn't exist.
+ */
+_PUBLIC_ int tdb_delete(struct tdb_context *tdb, TDB_DATA key);
+
+/**
+ * @brief Store an element in the database.
+ *
+ * This replaces any existing element with the same key.
+ *
+ * @param[in] tdb The tdb to store the entry.
+ *
+ * @param[in] key The key to use to store the entry.
+ *
+ * @param[in] dbuf The data to store under the key.
+ *
+ * @param[in] flag The flags to store the key:\n\n
+ * TDB_INSERT: Don't overwrite an existing entry.\n
+ * TDB_MODIFY: Don't create a new entry\n
+ *
+ * @return 0 on success, -1 on error with error code set.
+ *
+ * @see tdb_error()
+ * @see tdb_errorstr()
+ */
+_PUBLIC_ int tdb_store(struct tdb_context *tdb, TDB_DATA key, TDB_DATA dbuf, int flag);
+
+
+/**
+ * @brief Store an element in the database.
+ *
+ * This replaces any existing element with the same key.
+ *
+ * @param[in] tdb The tdb to store the entry.
+ *
+ * @param[in] key The key to use to store the entry.
+ *
+ * @param[in] dbufs A vector of memory chunks to write
+ *
+ * @param[in] num_dbufs Length of the dbufs vector
+ *
+ * @param[in] flag The flags to store the key:\n\n
+ * TDB_INSERT: Don't overwrite an existing entry.\n
+ * TDB_MODIFY: Don't create a new entry\n
+ *
+ * @return 0 on success, -1 on error with error code set.
+ *
+ * @see tdb_error()
+ * @see tdb_errorstr()
+ */
+_PUBLIC_ int tdb_storev(struct tdb_context *tdb, TDB_DATA key,
+ const TDB_DATA *dbufs, int num_dbufs, int flag);
+
+/**
+ * @brief Append data to an entry.
+ *
+ * If the entry doesn't exist, it will create a new one.
+ *
+ * @param[in] tdb The database to use.
+ *
+ * @param[in] key The key to append the data.
+ *
+ * @param[in] new_dbuf The data to append to the key.
+ *
+ * @return 0 on success, -1 on error with error code set.
+ *
+ * @see tdb_error()
+ * @see tdb_errorstr()
+ */
+_PUBLIC_ int tdb_append(struct tdb_context *tdb, TDB_DATA key, TDB_DATA new_dbuf);
+
+/**
+ * @brief Close a database.
+ *
+ * @param[in] tdb The database to close. The context will be free'd.
+ *
+ * @return 0 for success, -1 on error.
+ *
+ * @note Don't call tdb_error() after this function cause the tdb context will
+ * be freed on error.
+ */
+_PUBLIC_ int tdb_close(struct tdb_context *tdb);
+
+/**
+ * @brief Find the first entry in the database and return its key.
+ *
+ * The caller must free the returned data.
+ *
+ * @param[in] tdb The database to use.
+ *
+ * @return The first entry of the database, an empty TDB_DATA entry
+ * if the database is empty.
+ */
+_PUBLIC_ TDB_DATA tdb_firstkey(struct tdb_context *tdb);
+
+/**
+ * @brief Find the next entry in the database, returning its key.
+ *
+ * The caller must free the returned data.
+ *
+ * @param[in] tdb The database to use.
+ *
+ * @param[in] key The key from which you want the next key.
+ *
+ * @return The next entry of the current key, an empty TDB_DATA
+ * entry if there is no entry.
+ */
+_PUBLIC_ TDB_DATA tdb_nextkey(struct tdb_context *tdb, TDB_DATA key);
+
+/**
+ * @brief Traverse the entire database.
+ *
+ * While traversing the function fn(tdb, key, data, state) is called on each
+ * element. If fn is NULL then it is not called. A non-zero return value from
+ * fn() indicates that the traversal should stop. Traversal callbacks may not
+ * start transactions.
+ *
+ * @warning The data buffer given to the callback fn does NOT meet the alignment
+ * restrictions malloc gives you.
+ *
+ * @param[in] tdb The database to traverse.
+ *
+ * @param[in] fn The function to call on each entry.
+ *
+ * @param[in] private_data The private data which should be passed to the
+ * traversing function.
+ *
+ * @return The record count traversed, -1 on error.
+ */
+_PUBLIC_ int tdb_traverse(struct tdb_context *tdb, tdb_traverse_func fn, void *private_data);
+
+/**
+ * @brief Traverse the entire database.
+ *
+ * While traversing the database the function fn(tdb, key, data, state) is
+ * called on each element, but marking the database read only during the
+ * traversal, so any write operations will fail. This allows tdb to use read
+ * locks, which increases the parallelism possible during the traversal.
+ *
+ * @param[in] tdb The database to traverse.
+ *
+ * @param[in] fn The function to call on each entry.
+ *
+ * @param[in] private_data The private data which should be passed to the
+ * traversing function.
+ *
+ * @return The record count traversed, -1 on error.
+ */
+_PUBLIC_ int tdb_traverse_read(struct tdb_context *tdb, tdb_traverse_func fn, void *private_data);
+
+/**
+ * @brief Traverse a single hash chain
+ *
+ * Traverse a single hash chain under a single lock operation. No
+ * database modification is possible in the callback.
+ *
+ * This exists for background cleanup of databases. In normal
+ * operations, traversing a complete database can be much too
+ * expensive. Databases can have many chains, which will all have to
+ * be looked at before tdb_traverse finishes. Also tdb_traverse does a
+ * lot of fcntl activity to protect against concurrent record deletes.
+ *
+ * With this you can walk a fraction of the whole tdb, collect the
+ * entries you want to prune, leave the traverse, and then modify or
+ * delete the records in a subsequent step.
+ *
+ * To walk the entire database, call this function tdb_hash_size()
+ * times, with 0<=chain<tdb_hash_size(tdb).
+ *
+ * @param[in] tdb The database to traverse.
+ *
+ * @param[in] chain The hash chain number to traverse.
+ *
+ * @param[in] fn The function to call on each entry.
+ *
+ * @param[in] private_data The private data which should be passed to the
+ * traversing function.
+ *
+ * @return The record count traversed, -1 on error.
+ */
+
+_PUBLIC_ int tdb_traverse_chain(struct tdb_context *tdb,
+ unsigned chain,
+ tdb_traverse_func fn,
+ void *private_data);
+
+/**
+ * @brief Traverse a single hash chain
+ *
+ * This is like tdb_traverse_chain(), but for all records that are in
+ * the same chain as the record corresponding to the key parameter.
+ *
+ * Use it for ongoing database maintenance under a lock. Whenever you
+ * are about to modify a database, you know which record you are going
+ * to write to. For that tdb_store(), an exclusive chainlock is taken
+ * behind the scenes. To utilize this exclusive lock for incremental
+ * database cleanup as well, tdb_chainlock() the key you are about to
+ * modify, then tdb_traverse_key_chain() to do the incremental
+ * maintenance, modify your record and tdb_chainunlock() the key
+ * again.
+ *
+ * @param[in] tdb The database to traverse.
+ *
+ * @param[in] key The record key to walk the chain for.
+ *
+ * @param[in] fn The function to call on each entry.
+ *
+ * @param[in] private_data The private data which should be passed to the
+ * traversing function.
+ *
+ * @return The record count traversed, -1 on error.
+ */
+
+_PUBLIC_ int tdb_traverse_key_chain(struct tdb_context *tdb,
+ TDB_DATA key,
+ tdb_traverse_func fn,
+ void *private_data);
+/**
+ * @brief Check if an entry in the database exists.
+ *
+ * @note 1 is returned if the key is found and 0 is returned if not found this
+ * doesn't match the conventions in the rest of this module, but is compatible
+ * with gdbm.
+ *
+ * @param[in] tdb The database to check if the entry exists.
+ *
+ * @param[in] key The key to check if the entry exists.
+ *
+ * @return 1 if the key is found, 0 if not.
+ */
+_PUBLIC_ int tdb_exists(struct tdb_context *tdb, TDB_DATA key);
+
+/**
+ * @brief Lock entire database with a write lock.
+ *
+ * @param[in] tdb The database to lock.
+ *
+ * @return 0 on success, -1 on error with error code set.
+ *
+ * @see tdb_error()
+ * @see tdb_errorstr()
+ */
+_PUBLIC_ int tdb_lockall(struct tdb_context *tdb);
+
+/**
+ * @brief Lock entire database with a write lock.
+ *
+ * This is the non-blocking call.
+ *
+ * @param[in] tdb The database to lock.
+ *
+ * @return 0 on success, -1 on error with error code set.
+ *
+ * @see tdb_lockall()
+ * @see tdb_error()
+ * @see tdb_errorstr()
+ */
+_PUBLIC_ int tdb_lockall_nonblock(struct tdb_context *tdb);
+
+/**
+ * @brief Unlock entire database with write lock.
+ *
+ * @param[in] tdb The database to unlock.
+ *
+ * @return 0 on success, -1 on error with error code set.
+ *
+ * @see tdb_lockall()
+ * @see tdb_error()
+ * @see tdb_errorstr()
+ */
+_PUBLIC_ int tdb_unlockall(struct tdb_context *tdb);
+
+/**
+ * @brief Lock entire database with a read lock.
+ *
+ * @param[in] tdb The database to lock.
+ *
+ * @return 0 on success, -1 on error with error code set.
+ *
+ * @see tdb_error()
+ * @see tdb_errorstr()
+ */
+_PUBLIC_ int tdb_lockall_read(struct tdb_context *tdb);
+
+/**
+ * @brief Lock entire database with a read lock.
+ *
+ * This is the non-blocking call.
+ *
+ * @param[in] tdb The database to lock.
+ *
+ * @return 0 on success, -1 on error with error code set.
+ *
+ * @see tdb_lockall_read()
+ * @see tdb_error()
+ * @see tdb_errorstr()
+ */
+_PUBLIC_ int tdb_lockall_read_nonblock(struct tdb_context *tdb);
+
+/**
+ * @brief Unlock entire database with read lock.
+ *
+ * @param[in] tdb The database to unlock.
+ *
+ * @return 0 on success, -1 on error with error code set.
+ *
+ * @see tdb_lockall_read()
+ * @see tdb_error()
+ * @see tdb_errorstr()
+ */
+_PUBLIC_ int tdb_unlockall_read(struct tdb_context *tdb);
+
+/**
+ * @brief Lock entire database with write lock - mark only.
+ *
+ * @todo Add more details.
+ *
+ * @param[in] tdb The database to mark.
+ *
+ * @return 0 on success, -1 on error with error code set.
+ *
+ * @see tdb_error()
+ * @see tdb_errorstr()
+ */
+_PUBLIC_ int tdb_lockall_mark(struct tdb_context *tdb);
+
+/**
+ * @brief Lock entire database with write lock - unmark only.
+ *
+ * @todo Add more details.
+ *
+ * @param[in] tdb The database to mark.
+ *
+ * @return 0 on success, -1 on error with error code set.
+ *
+ * @see tdb_error()
+ * @see tdb_errorstr()
+ */
+_PUBLIC_ int tdb_lockall_unmark(struct tdb_context *tdb);
+
+/**
+ * @brief Get the name of the current tdb file.
+ *
+ * This is useful for external logging functions.
+ *
+ * @param[in] tdb The database to get the name from.
+ *
+ * @return The name of the database.
+ */
+_PUBLIC_ const char *tdb_name(struct tdb_context *tdb);
+
+/**
+ * @brief Get the underlying file descriptor being used by tdb.
+ *
+ * This is useful for external routines that want to check the device/inode
+ * of the fd.
+ *
+ * @param[in] tdb The database to get the fd from.
+ *
+ * @return The file descriptor or -1.
+ */
+_PUBLIC_ int tdb_fd(struct tdb_context *tdb);
+
+/**
+ * @brief Get the current logging function.
+ *
+ * This is useful for external tdb routines that wish to log tdb errors.
+ *
+ * @param[in] tdb The database to get the logging function from.
+ *
+ * @return The logging function of the database.
+ *
+ * @see tdb_get_logging_private()
+ */
+_PUBLIC_ tdb_log_func tdb_log_fn(struct tdb_context *tdb);
+
+/**
+ * @brief Get the private data of the logging function.
+ *
+ * @param[in] tdb The database to get the data from.
+ *
+ * @return The private data pointer of the logging function.
+ *
+ * @see tdb_log_fn()
+ */
+_PUBLIC_ void *tdb_get_logging_private(struct tdb_context *tdb);
+
+/**
+ * @brief Is a transaction active?
+ *
+ * It is helpful for the application to know if a transaction is
+ * active, rather than needing to maintain an application-level reference
+ * count.
+ *
+ * @param[in] tdb The database to start the transaction.
+ *
+ * @return true if there is a transaction active, false otherwise
+ *
+ * @see tdb_transaction_start()
+ * @see tdb_transaction_prepare_commit()
+ * @see tdb_transaction_commit()
+ * @see tdb_transaction_cancel()
+ */
+_PUBLIC_ bool tdb_transaction_active(struct tdb_context *tdb);
+
+/**
+ * @brief Start a transaction.
+ *
+ * All operations after the transaction start can either be committed with
+ * tdb_transaction_commit() or cancelled with tdb_transaction_cancel().
+ *
+ * If (the default) TDB_ALLOW_NESTING was specified or
+ * TDB_DISALLOW_NESTING was not specified as a flag via tdb_open() or
+ * tdb_open_ex(), you call tdb_transaction_start() again on the same
+ * tdb context while a transaction is in progress, then the same
+ * transaction buffer is re-used. The number of
+ * tdb_transaction_{commit,cancel} operations must match the number of
+ * successful tdb_transaction_start() calls.
+ *
+ * Note that transactions are by default disk synchronous, and use a recover
+ * area in the database to automatically recover the database on the next open
+ * if the system crashes during a transaction. You can disable the synchronous
+ * transaction recovery setup using the TDB_NOSYNC flag, which will greatly
+ * speed up operations at the risk of corrupting your database if the system
+ * crashes.
+ *
+ * Operations made within a transaction are not visible to other users of the
+ * database until a successful commit.
+ *
+ * @param[in] tdb The database to start the transaction.
+ *
+ * @return 0 on success, -1 on error with error code set.
+ *
+ * @see tdb_error()
+ * @see tdb_errorstr()
+ */
+_PUBLIC_ int tdb_transaction_start(struct tdb_context *tdb);
+
+/**
+ * @brief Start a transaction, non-blocking.
+ *
+ * @param[in] tdb The database to start the transaction.
+ *
+ * @return 0 on success, -1 on error with error code set.
+ *
+ * @see tdb_error()
+ * @see tdb_errorstr()
+ * @see tdb_transaction_start()
+ */
+_PUBLIC_ int tdb_transaction_start_nonblock(struct tdb_context *tdb);
+
+/**
+ * @brief Prepare to commit a current transaction, for two-phase commits.
+ *
+ * Once prepared for commit, the only allowed calls are tdb_transaction_commit()
+ * or tdb_transaction_cancel(). Preparing allocates disk space for the pending
+ * updates, so a subsequent commit should succeed (barring any hardware
+ * failures).
+ *
+ * @param[in] tdb The database to prepare the commit.
+ *
+ * @return 0 on success, -1 on error with error code set.
+ *
+ * @see tdb_error()
+ * @see tdb_errorstr()
+ */
+_PUBLIC_ int tdb_transaction_prepare_commit(struct tdb_context *tdb);
+
+/**
+ * @brief Commit a current transaction.
+ *
+ * This updates the database and releases the current transaction locks.
+ *
+ * @param[in] tdb The database to commit the transaction.
+ *
+ * @return 0 on success, -1 on error with error code set.
+ *
+ * @see tdb_error()
+ * @see tdb_errorstr()
+ */
+_PUBLIC_ int tdb_transaction_commit(struct tdb_context *tdb);
+
+/**
+ * @brief Cancel a current transaction.
+ *
+ * This discards all write and lock operations that have been made since the
+ * transaction started.
+ *
+ * @param[in] tdb The tdb to cancel the transaction on.
+ *
+ * @return 0 on success, -1 on error with error code set.
+ *
+ * @see tdb_error()
+ * @see tdb_errorstr()
+ */
+_PUBLIC_ int tdb_transaction_cancel(struct tdb_context *tdb);
+
+/**
+ * @brief Get the tdb sequence number.
+ *
+ * Only makes sense if the writers opened with TDB_SEQNUM set. Note that this
+ * sequence number will wrap quite quickly, so it should only be used for a
+ * 'has something changed' test, not for code that relies on the count of the
+ * number of changes made. If you want a counter then use a tdb record.
+ *
+ * The aim of this sequence number is to allow for a very lightweight test of a
+ * possible tdb change.
+ *
+ * @param[in] tdb The database to get the sequence number from.
+ *
+ * @return The sequence number or 0.
+ *
+ * @see tdb_open()
+ * @see tdb_enable_seqnum()
+ */
+_PUBLIC_ int tdb_get_seqnum(struct tdb_context *tdb);
+
+/**
+ * @brief Get the hash size.
+ *
+ * @param[in] tdb The database to get the hash size from.
+ *
+ * @return The hash size.
+ */
+_PUBLIC_ int tdb_hash_size(struct tdb_context *tdb);
+
+/**
+ * @brief Get the map size.
+ *
+ * @param[in] tdb The database to get the map size from.
+ *
+ * @return The map size.
+ */
+_PUBLIC_ size_t tdb_map_size(struct tdb_context *tdb);
+
+/**
+ * @brief Get the tdb flags set during open.
+ *
+ * @param[in] tdb The database to get the flags form.
+ *
+ * @return The flags set to on the database.
+ */
+_PUBLIC_ int tdb_get_flags(struct tdb_context *tdb);
+
+/**
+ * @brief Add flags to the database.
+ *
+ * @param[in] tdb The database to add the flags.
+ *
+ * @param[in] flag The tdb flags to add.
+ */
+_PUBLIC_ void tdb_add_flags(struct tdb_context *tdb, unsigned flag);
+
+/**
+ * @brief Remove flags from the database.
+ *
+ * @param[in] tdb The database to remove the flags.
+ *
+ * @param[in] flag The tdb flags to remove.
+ */
+_PUBLIC_ void tdb_remove_flags(struct tdb_context *tdb, unsigned flag);
+
+/**
+ * @brief Enable sequence number handling on an open tdb.
+ *
+ * @param[in] tdb The database to enable sequence number handling.
+ *
+ * @see tdb_get_seqnum()
+ */
+_PUBLIC_ void tdb_enable_seqnum(struct tdb_context *tdb);
+
+/**
+ * @brief Increment the tdb sequence number.
+ *
+ * This only works if the tdb has been opened using the TDB_SEQNUM flag or
+ * enabled using tdb_enable_seqnum().
+ *
+ * @param[in] tdb The database to increment the sequence number.
+ *
+ * @see tdb_enable_seqnum()
+ * @see tdb_get_seqnum()
+ */
+_PUBLIC_ void tdb_increment_seqnum_nonblock(struct tdb_context *tdb);
+
+/**
+ * @brief Create a hash of the key.
+ *
+ * @param[in] key The key to hash
+ *
+ * @return The hash.
+ */
+_PUBLIC_ unsigned int tdb_jenkins_hash(TDB_DATA *key);
+
+/**
+ * @brief Check the consistency of the database.
+ *
+ * This check the consistency of the database calling back the check function
+ * (if non-NULL) on each record. If some consistency check fails, or the
+ * supplied check function returns -1, tdb_check returns -1, otherwise 0.
+ *
+ * @note The logging function (if set) will be called with additional
+ * information on the corruption found.
+ *
+ * @param[in] tdb The database to check.
+ *
+ * @param[in] check The check function to use.
+ *
+ * @param[in] private_data the private data to pass to the check function.
+ *
+ * @return 0 on success, -1 on error with error code set.
+ *
+ * @see tdb_error()
+ * @see tdb_errorstr()
+ */
+_PUBLIC_ int tdb_check(struct tdb_context *tdb,
+ int (*check) (TDB_DATA key, TDB_DATA data, void *private_data),
+ void *private_data);
+
+/**
+ * @brief Dump all possible records in a corrupt database.
+ *
+ * This is the only way to get data out of a database where tdb_check() fails.
+ * It will call walk() with anything which looks like a database record; this
+ * may well include invalid, incomplete or duplicate records.
+ *
+ * @param[in] tdb The database to check.
+ *
+ * @param[in] walk The walk function to use.
+ *
+ * @param[in] private_data the private data to pass to the walk function.
+ *
+ * @return 0 on success, -1 on error with error code set.
+ *
+ * @see tdb_error()
+ * @see tdb_errorstr()
+ */
+_PUBLIC_ int tdb_rescue(struct tdb_context *tdb,
+ void (*walk) (TDB_DATA key, TDB_DATA data, void *private_data),
+ void *private_data);
+
+/**
+ * @brief Check if support for TDB_MUTEX_LOCKING is available at runtime.
+ *
+ * On some systems the API for pthread_mutexattr_setrobust() is not available.
+ * On other systems there are some bugs in the interaction between glibc and
+ * the linux kernel.
+ *
+ * This function provides a runtime check if robust mutexes are really
+ * available.
+ *
+ * This needs to be called and return true before TDB_MUTEX_LOCKING
+ * can be used at runtime.
+ *
+ * @note This calls fork(), but the SIGCHILD handling should be transparent.
+ *
+ * @return true if supported, false otherwise.
+ *
+ * @see TDB_MUTEX_LOCKING
+ */
+_PUBLIC_ bool tdb_runtime_check_for_robust_mutexes(void);
+
+/* @} ******************************************************************/
+
+/* Low level locking functions: use with care */
+_PUBLIC_ int tdb_chainlock(struct tdb_context *tdb, TDB_DATA key);
+_PUBLIC_ int tdb_chainlock_nonblock(struct tdb_context *tdb, TDB_DATA key);
+_PUBLIC_ int tdb_chainunlock(struct tdb_context *tdb, TDB_DATA key);
+_PUBLIC_ int tdb_chainlock_read(struct tdb_context *tdb, TDB_DATA key);
+_PUBLIC_ int tdb_chainlock_read_nonblock(struct tdb_context *tdb, TDB_DATA key);
+_PUBLIC_ int tdb_chainunlock_read(struct tdb_context *tdb, TDB_DATA key);
+_PUBLIC_ int tdb_chainlock_mark(struct tdb_context *tdb, TDB_DATA key);
+_PUBLIC_ int tdb_chainlock_unmark(struct tdb_context *tdb, TDB_DATA key);
+
+_PUBLIC_ void tdb_setalarm_sigptr(struct tdb_context *tdb, volatile sig_atomic_t *sigptr);
+
+/* wipe and repack */
+_PUBLIC_ int tdb_wipe_all(struct tdb_context *tdb);
+_PUBLIC_ int tdb_repack(struct tdb_context *tdb);
+
+/* Debug functions. Not used in production. */
+_PUBLIC_ void tdb_dump_all(struct tdb_context *tdb);
+_PUBLIC_ int tdb_printfreelist(struct tdb_context *tdb);
+_PUBLIC_ int tdb_validate_freelist(struct tdb_context *tdb, int *pnum_entries);
+_PUBLIC_ int tdb_freelist_size(struct tdb_context *tdb);
+_PUBLIC_ char *tdb_summary(struct tdb_context *tdb);
+
+_PUBLIC_ extern TDB_DATA tdb_null;
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* tdb.h */
diff --git a/lib/tdb/man/tdbbackup.8.xml b/lib/tdb/man/tdbbackup.8.xml
new file mode 100644
index 0000000..a73fb56
--- /dev/null
+++ b/lib/tdb/man/tdbbackup.8.xml
@@ -0,0 +1,157 @@
+<?xml version="1.0" encoding="iso-8859-1"?>
+<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN" "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
+<refentry id="tdbbackup.8">
+<refentryinfo><date>2015-04-25</date></refentryinfo>
+
+<refmeta>
+ <refentrytitle>tdbbackup</refentrytitle>
+ <manvolnum>8</manvolnum>
+ <refmiscinfo class="source">Samba</refmiscinfo>
+ <refmiscinfo class="manual">System Administration tools</refmiscinfo>
+ <refmiscinfo class="version">3.6</refmiscinfo>
+</refmeta>
+
+
+<refnamediv>
+ <refname>tdbbackup</refname>
+ <refpurpose>tool for backing up and for validating the integrity of samba .tdb files</refpurpose>
+</refnamediv>
+
+<refsynopsisdiv>
+ <cmdsynopsis>
+ <command>tdbbackup</command>
+ <arg choice="opt">-s suffix</arg>
+ <arg choice="opt">-v</arg>
+ <arg choice="opt">-h</arg>
+ <arg choice="opt">-n hashsize</arg>
+ <arg choice="opt">-l</arg>
+ </cmdsynopsis>
+</refsynopsisdiv>
+
+<refsect1>
+ <title>DESCRIPTION</title>
+
+ <para>This tool is part of the <citerefentry><refentrytitle>samba</refentrytitle>
+ <manvolnum>1</manvolnum></citerefentry> suite.</para>
+
+ <para><command>tdbbackup</command> is a tool that may be used to backup samba .tdb
+ files. This tool may also be used to verify the integrity of the .tdb files prior
+ to samba startup or during normal operation. If it finds file damage and it finds
+ a prior backup the backup file will be restored.
+ </para>
+</refsect1>
+
+
+<refsect1>
+ <title>OPTIONS</title>
+
+ <variablelist>
+
+ <varlistentry>
+ <term>-h</term>
+ <listitem><para>
+ Get help information.
+ </para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>-s suffix</term>
+ <listitem><para>
+ The <command>-s</command> option allows the administrator to specify a file
+ backup extension. This way it is possible to keep a history of tdb backup
+ files by using a new suffix for each backup.
+ </para> </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>-v</term>
+ <listitem><para>
+ The <command>-v</command> will check the database for damages (corrupt data)
+ which if detected causes the backup to be restored.
+ </para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>-n hashsize</term>
+ <listitem><para>
+ The <command>-n</command> option sets the hash size for the new backup tdb.
+ </para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>-l</term>
+ <listitem><para>
+ This options disables any locking, by passing TDB_NOLOCK
+ to tdb_open_ex(). Only use this for database files which
+ are not used by any other process! And also only if it is otherwise not
+ possible to open the database, e.g. databases which were created with
+ mutex locking.
+ </para></listitem>
+ </varlistentry>
+
+ </variablelist>
+</refsect1>
+
+
+<refsect1>
+ <title>COMMANDS</title>
+
+ <para><emphasis>GENERAL INFORMATION</emphasis></para>
+
+ <para>
+ The <command>tdbbackup</command> utility can safely be run at any time. It was designed so
+ that it can be used at any time to validate the integrity of tdb files, even during Samba
+ operation. Typical usage for the command will be:
+ </para>
+
+ <para>tdbbackup [-s suffix] *.tdb</para>
+
+ <para>
+ Before restarting samba the following command may be run to validate .tdb files:
+ </para>
+
+ <para>tdbbackup -v [-s suffix] *.tdb</para>
+
+ <para>
+ Samba .tdb files are stored in various locations, be sure to run backup all
+ .tdb file on the system. Important files includes:
+ </para>
+
+ <itemizedlist>
+ <listitem><para>
+ <command>secrets.tdb</command> - usual location is in the /usr/local/samba/private
+ directory, or on some systems in /etc/samba.
+ </para></listitem>
+
+ <listitem><para>
+ <command>passdb.tdb</command> - usual location is in the /usr/local/samba/private
+ directory, or on some systems in /etc/samba.
+ </para></listitem>
+
+ <listitem><para>
+ <command>*.tdb</command> located in the /usr/local/samba/var directory or on some
+ systems in the /var/cache or /var/lib/samba directories.
+ </para></listitem>
+ </itemizedlist>
+
+</refsect1>
+
+<refsect1>
+ <title>VERSION</title>
+
+ <para>This man page is correct for version 3 of the Samba suite.</para>
+</refsect1>
+
+<refsect1>
+ <title>AUTHOR</title>
+
+ <para>
+ The original Samba software and related utilities were created by Andrew Tridgell.
+ Samba is now developed by the Samba Team as an Open Source project similar to the way
+ the Linux kernel is developed.
+ </para>
+
+ <para>The tdbbackup man page was written by John H Terpstra.</para>
+</refsect1>
+
+</refentry>
diff --git a/lib/tdb/man/tdbdump.8.xml b/lib/tdb/man/tdbdump.8.xml
new file mode 100644
index 0000000..31e6888
--- /dev/null
+++ b/lib/tdb/man/tdbdump.8.xml
@@ -0,0 +1,93 @@
+<?xml version="1.0" encoding="iso-8859-1"?>
+<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN" "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
+<refentry id="tdbdump.8">
+<refentryinfo><date>2015-04-25</date></refentryinfo>
+
+<refmeta>
+ <refentrytitle>tdbdump</refentrytitle>
+ <manvolnum>8</manvolnum>
+ <refmiscinfo class="source">Samba</refmiscinfo>
+ <refmiscinfo class="manual">System Administration tools</refmiscinfo>
+ <refmiscinfo class="version">3.6</refmiscinfo>
+</refmeta>
+
+
+<refnamediv>
+ <refname>tdbdump</refname>
+ <refpurpose>tool for printing the contents of a TDB file</refpurpose>
+</refnamediv>
+
+<refsynopsisdiv>
+ <cmdsynopsis>
+ <command>tdbdump</command>
+ <arg choice="opt">-k <replaceable>keyname</replaceable></arg>
+ <arg choice="opt">-e</arg>
+ <arg choice="opt">-h</arg>
+ <arg choice="req">filename</arg>
+ </cmdsynopsis>
+</refsynopsisdiv>
+
+<refsect1>
+ <title>DESCRIPTION</title>
+
+ <para>This tool is part of the <citerefentry><refentrytitle>samba</refentrytitle>
+ <manvolnum>1</manvolnum></citerefentry> suite.</para>
+
+ <para><command>tdbdump</command> is a very simple utility that 'dumps' the
+ contents of a TDB (Trivial DataBase) file to standard output in a
+ human-readable format.
+ </para>
+
+ <para>This tool can be used when debugging problems with TDB files. It is
+ intended for those who are somewhat familiar with Samba internals.
+ </para>
+</refsect1>
+
+<refsect1>
+ <title>OPTIONS</title>
+
+ <variablelist>
+
+ <varlistentry>
+ <term>-h</term>
+ <listitem><para>
+ Get help information.
+ </para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>-k <replaceable>keyname</replaceable></term>
+ <listitem><para>
+ The <command>-k</command> option restricts dumping to a single key, if found.
+ </para> </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>-e</term>
+ <listitem><para>
+ The <command>-e</command> tries to dump out from a corrupt database. Naturally, such a dump is unreliable, at best.
+ </para></listitem>
+ </varlistentry>
+
+ </variablelist>
+</refsect1>
+
+<refsect1>
+ <title>VERSION</title>
+
+ <para>This man page is correct for version 3 of the Samba suite.</para>
+</refsect1>
+
+<refsect1>
+ <title>AUTHOR</title>
+
+ <para>
+ The original Samba software and related utilities were created by Andrew Tridgell.
+ Samba is now developed by the Samba Team as an Open Source project similar to the way
+ the Linux kernel is developed.
+ </para>
+
+ <para>The tdbdump man page was written by Jelmer Vernooij.</para>
+</refsect1>
+
+</refentry>
diff --git a/lib/tdb/man/tdbrestore.8.xml b/lib/tdb/man/tdbrestore.8.xml
new file mode 100644
index 0000000..034db53
--- /dev/null
+++ b/lib/tdb/man/tdbrestore.8.xml
@@ -0,0 +1,67 @@
+<?xml version="1.0" encoding="iso-8859-1"?>
+<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN" "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
+<refentry id="tdbrestore.8">
+<refentryinfo><date>2015-04-25</date></refentryinfo>
+
+<refmeta>
+ <refentrytitle>tdbrestore</refentrytitle>
+ <manvolnum>8</manvolnum>
+ <refmiscinfo class="source">Samba</refmiscinfo>
+ <refmiscinfo class="manual">System Administration tools</refmiscinfo>
+ <refmiscinfo class="version">3.6</refmiscinfo>
+</refmeta>
+
+
+<refnamediv>
+ <refname>tdbrestore</refname>
+ <refpurpose>tool for creating a TDB file out of a tdbdump output</refpurpose>
+</refnamediv>
+
+<refsynopsisdiv>
+ <cmdsynopsis>
+ <command>tdbrestore</command>
+ <arg choice="req">tdbfilename</arg>
+ </cmdsynopsis>
+</refsynopsisdiv>
+
+<refsect1>
+ <title>DESCRIPTION</title>
+
+ <para>This tool is part of the <citerefentry><refentrytitle>samba</refentrytitle>
+ <manvolnum>1</manvolnum></citerefentry> suite.</para>
+
+ <para><command>tdbrestore</command> is a very simple utility that 'restores' the
+ contents of dump file into TDB (Trivial DataBase) file. The dump file is obtained from the tdbdump
+ command.
+ </para>
+
+ <para>This tool wait on the standard input for the content of the dump and will write the tdb in the tdbfilename
+ parameter.
+ </para>
+ <para>This tool can be used for unpacking the content of tdb as backup mean.
+ </para>
+</refsect1>
+
+
+<refsect1>
+ <title>VERSION</title>
+
+ <para>This man page is correct for version 3 of the Samba suite.</para>
+</refsect1>
+
+<refsect1>
+ <title>AUTHOR</title>
+
+ <para>
+ The original Samba software and related utilities were created by Andrew Tridgell.
+ Samba is now developed by the Samba Team as an Open Source project similar to the way
+ the Linux kernel is developed.
+
+ This tool was initially written by Volker Lendecke based on an
+ idea by Simon McVittie.
+ </para>
+
+ <para>The tdbrestore man page was written by Matthieu Patou.</para>
+</refsect1>
+
+</refentry>
diff --git a/lib/tdb/man/tdbtool.8.xml b/lib/tdb/man/tdbtool.8.xml
new file mode 100644
index 0000000..045cbde
--- /dev/null
+++ b/lib/tdb/man/tdbtool.8.xml
@@ -0,0 +1,275 @@
+<?xml version="1.0" encoding="iso-8859-1"?>
+<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN" "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
+<refentry id="tdbtool.8">
+<refentryinfo><date>2015-04-25</date></refentryinfo>
+
+<refmeta>
+ <refentrytitle>tdbtool</refentrytitle>
+ <manvolnum>8</manvolnum>
+ <refmiscinfo class="source">Samba</refmiscinfo>
+ <refmiscinfo class="manual">System Administration tools</refmiscinfo>
+ <refmiscinfo class="version">4.0</refmiscinfo>
+</refmeta>
+
+
+<refnamediv>
+ <refname>tdbtool</refname>
+ <refpurpose>manipulate the contents TDB files</refpurpose>
+</refnamediv>
+
+<refsynopsisdiv>
+
+ <cmdsynopsis>
+ <command>tdbtool</command>
+ </cmdsynopsis>
+
+ <cmdsynopsis>
+ <command>tdbtool</command>
+ <arg choice="opt">-l</arg>
+ <arg choice="plain">
+ <replaceable>TDBFILE</replaceable>
+ </arg>
+ <arg rep="repeat" choice="opt">
+ <replaceable>COMMANDS</replaceable>
+ </arg>
+ </cmdsynopsis>
+
+</refsynopsisdiv>
+
+<refsect1>
+ <title>DESCRIPTION</title>
+
+ <para>This tool is part of the
+ <citerefentry><refentrytitle>samba</refentrytitle>
+ <manvolnum>1</manvolnum></citerefentry> suite.</para>
+
+ <para><command>tdbtool</command> a tool for displaying and
+ altering the contents of Samba TDB (Trivial DataBase) files. Each
+ of the commands listed below can be entered interactively or
+ provided on the command line.</para>
+
+</refsect1>
+
+<refsect1>
+ <title>OPTIONS</title>
+
+ <variablelist>
+
+ <varlistentry>
+ <term>-l</term>
+ <listitem><para>
+ This options disables any locking, by passing TDB_NOLOCK
+ to tdb_open_ex(). Only use this for database files which
+ are not used by any other process! And also only if it is otherwise not
+ possible to open the database, e.g. databases which were created with
+ mutex locking.
+ </para></listitem>
+ </varlistentry>
+
+ </variablelist>
+</refsect1>
+
+
+
+<refsect1>
+ <title>COMMANDS</title>
+
+ <variablelist>
+
+ <varlistentry>
+ <term><option>create</option>
+ <replaceable>TDBFILE</replaceable></term>
+ <listitem><para>Create a new database named
+ <replaceable>TDBFILE</replaceable>.
+ </para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>open</option>
+ <replaceable>TDBFILE</replaceable></term>
+ <listitem><para>Open an existing database named
+ <replaceable>TDBFILE</replaceable>.
+ </para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>erase</option></term>
+ <listitem><para>Erase the current database.
+ </para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>dump</option></term>
+ <listitem><para>Dump the current database as strings.
+ </para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>cdump</option></term>
+ <listitem><para>Dump the current database as connection records.
+ </para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>keys</option></term>
+ <listitem><para>Dump the current database keys as strings.
+ </para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>hexkeys</option></term>
+ <listitem><para>Dump the current database keys as hex values.
+ </para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>info</option></term>
+ <listitem><para>Print summary information about the
+ current database.
+ </para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>insert</option>
+ <replaceable>KEY</replaceable>
+ <replaceable>DATA</replaceable>
+ </term>
+ <listitem><para>Insert a record into the
+ current database.
+ </para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>move</option>
+ <replaceable>KEY</replaceable>
+ <replaceable>TDBFILE</replaceable>
+ </term>
+ <listitem><para>Move a record from the
+ current database into <replaceable>TDBFILE</replaceable>.
+ </para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>store</option>
+ <replaceable>KEY</replaceable>
+ <replaceable>DATA</replaceable>
+ </term>
+ <listitem><para>Store (replace) a record in the
+ current database.
+ </para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>storehex</option>
+ <replaceable>KEY</replaceable>
+ <replaceable>DATA</replaceable>
+ </term>
+ <listitem><para>Store (replace) a record in the
+ current database where key and data are in hex format.
+ </para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>show</option>
+ <replaceable>KEY</replaceable>
+ </term>
+ <listitem><para>Show a record by key.
+ </para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>delete</option>
+ <replaceable>KEY</replaceable>
+ </term>
+ <listitem><para>Delete a record by key.
+ </para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>list</option>
+ </term>
+ <listitem><para>Print the current database hash table and free list.
+ </para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>free</option>
+ </term>
+ <listitem><para>Print the current database and free list.
+ </para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>!</option>
+ <replaceable>COMMAND</replaceable>
+ </term>
+ <listitem><para>Execute the given system command.
+ </para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>
+ <option>first</option>
+ </term>
+ <listitem><para>Print the first record in the current database.
+ </para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>
+ <option>next</option>
+ </term>
+ <listitem><para>Print the next record in the current database.
+ </para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>
+ <option>check</option>
+ </term>
+ <listitem><para>Check the integrity of the current database.
+ </para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>
+ <option>repack</option>
+ </term>
+ <listitem><para>Repack a database using a temporary file to remove fragmentation.
+ </para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>
+ <option>quit</option>
+ </term>
+ <listitem><para>Exit <command>tdbtool</command>.
+ </para></listitem>
+ </varlistentry>
+
+ </variablelist>
+</refsect1>
+
+<refsect1>
+ <title>CAVEATS</title>
+ <para>The contents of the Samba TDB files are private
+ to the implementation and should not be altered with
+ <command>tdbtool</command>.
+ </para>
+</refsect1>
+
+<refsect1>
+ <title>VERSION</title>
+ <para>This man page is correct for version 3.6 of the Samba suite.</para>
+</refsect1>
+
+<refsect1>
+ <title>AUTHOR</title>
+
+ <para> The original Samba software and related utilities were
+ created by Andrew Tridgell. Samba is now developed by the
+ Samba Team as an Open Source project similar to the way the
+ Linux kernel is developed.</para>
+</refsect1>
+
+</refentry>
diff --git a/lib/tdb/pytdb.c b/lib/tdb/pytdb.c
new file mode 100644
index 0000000..e2f8ace
--- /dev/null
+++ b/lib/tdb/pytdb.c
@@ -0,0 +1,876 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ Python interface to tdb.
+
+ Copyright (C) 2004-2006 Tim Potter <tpot@samba.org>
+ Copyright (C) 2007-2008 Jelmer Vernooij <jelmer@samba.org>
+
+ ** NOTE! The following LGPL license applies to the tdb
+ ** library. This does NOT imply that all of Samba is released
+ ** under the LGPL
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 3 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <Python.h>
+#include "replace.h"
+#include "system/filesys.h"
+
+/* Include tdb headers */
+#include <tdb.h>
+
+#if PY_MAJOR_VERSION >= 3
+#define Py_TPFLAGS_HAVE_ITER 0
+#endif
+
+/* discard signature of 'func' in favour of 'target_sig' */
+#define PY_DISCARD_FUNC_SIG(target_sig, func) (target_sig)(void(*)(void))func
+
+typedef struct {
+ PyObject_HEAD
+ TDB_CONTEXT *ctx;
+ bool closed;
+} PyTdbObject;
+
+static PyTypeObject PyTdb;
+
+static void PyErr_SetTDBError(TDB_CONTEXT *tdb)
+{
+ PyErr_SetObject(PyExc_RuntimeError,
+ Py_BuildValue("(i,s)", tdb_error(tdb), tdb_errorstr(tdb)));
+}
+
+static TDB_DATA PyBytes_AsTDB_DATA(PyObject *data)
+{
+ TDB_DATA ret;
+ ret.dptr = (unsigned char *)PyBytes_AsString(data);
+ ret.dsize = PyBytes_Size(data);
+ return ret;
+}
+
+static PyObject *PyBytes_FromTDB_DATA(TDB_DATA data)
+{
+ if (data.dptr == NULL && data.dsize == 0) {
+ Py_RETURN_NONE;
+ } else {
+ PyObject *ret = PyBytes_FromStringAndSize((const char *)data.dptr,
+ data.dsize);
+ free(data.dptr);
+ return ret;
+ }
+}
+
+#define PyErr_TDB_ERROR_IS_ERR_RAISE(ret, tdb) \
+ if (ret != 0) { \
+ PyErr_SetTDBError(tdb); \
+ return NULL; \
+ }
+
+#define PyErr_TDB_RAISE_IF_CLOSED(self) \
+ if (self->closed) { \
+ PyErr_SetObject(PyExc_RuntimeError, \
+ Py_BuildValue("(i,s)", TDB_ERR_IO, "Database is already closed")); \
+ return NULL; \
+ }
+
+#define PyErr_TDB_RAISE_RETURN_MINUS_1_IF_CLOSED(self) \
+ if (self->closed) { \
+ PyErr_SetObject(PyExc_RuntimeError, \
+ Py_BuildValue("(i,s)", TDB_ERR_IO, "Database is already closed")); \
+ return -1; \
+ }
+
+static PyObject *py_tdb_open(PyTypeObject *type, PyObject *args, PyObject *kwargs)
+{
+ char *name = NULL;
+ int hash_size = 0, tdb_flags = TDB_DEFAULT, flags = O_RDWR, mode = 0600;
+ TDB_CONTEXT *ctx;
+ PyTdbObject *ret;
+ const char *_kwnames[] = { "name", "hash_size", "tdb_flags", "flags", "mode", NULL };
+ char **kwnames = discard_const_p(char *, _kwnames);
+
+ if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|siiii", kwnames, &name, &hash_size, &tdb_flags, &flags, &mode))
+ return NULL;
+
+ if (name == NULL) {
+ tdb_flags |= TDB_INTERNAL;
+ }
+
+ ctx = tdb_open(name, hash_size, tdb_flags, flags, mode);
+ if (ctx == NULL) {
+ PyErr_SetFromErrno(PyExc_IOError);
+ return NULL;
+ }
+
+ ret = PyObject_New(PyTdbObject, &PyTdb);
+ if (!ret) {
+ tdb_close(ctx);
+ return NULL;
+ }
+
+ ret->ctx = ctx;
+ ret->closed = false;
+ return (PyObject *)ret;
+}
+
+static PyObject *obj_transaction_cancel(PyTdbObject *self,
+ PyObject *Py_UNUSED(ignored))
+{
+ int ret;
+
+ PyErr_TDB_RAISE_IF_CLOSED(self);
+
+ ret = tdb_transaction_cancel(self->ctx);
+ PyErr_TDB_ERROR_IS_ERR_RAISE(ret, self->ctx);
+ Py_RETURN_NONE;
+}
+
+static PyObject *obj_transaction_commit(PyTdbObject *self,
+ PyObject *Py_UNUSED(ignored))
+{
+ int ret;
+ PyErr_TDB_RAISE_IF_CLOSED(self);
+ ret = tdb_transaction_commit(self->ctx);
+ PyErr_TDB_ERROR_IS_ERR_RAISE(ret, self->ctx);
+ Py_RETURN_NONE;
+}
+
+static PyObject *obj_transaction_prepare_commit(PyTdbObject *self,
+ PyObject *Py_UNUSED(ignored))
+{
+ int ret;
+ PyErr_TDB_RAISE_IF_CLOSED(self);
+ ret = tdb_transaction_prepare_commit(self->ctx);
+ PyErr_TDB_ERROR_IS_ERR_RAISE(ret, self->ctx);
+ Py_RETURN_NONE;
+}
+
+static PyObject *obj_transaction_start(PyTdbObject *self,
+ PyObject *Py_UNUSED(ignored))
+{
+ int ret;
+ PyErr_TDB_RAISE_IF_CLOSED(self);
+ ret = tdb_transaction_start(self->ctx);
+ PyErr_TDB_ERROR_IS_ERR_RAISE(ret, self->ctx);
+ Py_RETURN_NONE;
+}
+
+static PyObject *obj_reopen(PyTdbObject *self,
+ PyObject *Py_UNUSED(ignored))
+{
+ int ret;
+ PyErr_TDB_RAISE_IF_CLOSED(self);
+ ret = tdb_reopen(self->ctx);
+ if (ret != 0) {
+ self->closed = true;
+ PyErr_SetObject(PyExc_RuntimeError,
+ Py_BuildValue("(i,s)",
+ TDB_ERR_IO,
+ "Failed to reopen database"));
+ return NULL;
+ }
+ Py_RETURN_NONE;
+}
+
+static PyObject *obj_lockall(PyTdbObject *self,
+ PyObject *Py_UNUSED(ignored))
+{
+ int ret;
+ PyErr_TDB_RAISE_IF_CLOSED(self);
+ ret = tdb_lockall(self->ctx);
+ PyErr_TDB_ERROR_IS_ERR_RAISE(ret, self->ctx);
+ Py_RETURN_NONE;
+}
+
+static PyObject *obj_unlockall(PyTdbObject *self,
+ PyObject *Py_UNUSED(ignored))
+{
+ int ret;
+ PyErr_TDB_RAISE_IF_CLOSED(self);
+ ret = tdb_unlockall(self->ctx);
+ PyErr_TDB_ERROR_IS_ERR_RAISE(ret, self->ctx);
+ Py_RETURN_NONE;
+}
+
+static PyObject *obj_lockall_read(PyTdbObject *self,
+ PyObject *Py_UNUSED(ignored))
+{
+ int ret;
+ PyErr_TDB_RAISE_IF_CLOSED(self);
+ ret = tdb_lockall_read(self->ctx);
+ PyErr_TDB_ERROR_IS_ERR_RAISE(ret, self->ctx);
+ Py_RETURN_NONE;
+}
+
+static PyObject *obj_unlockall_read(PyTdbObject *self,
+ PyObject *Py_UNUSED(ignored))
+{
+ int ret = tdb_unlockall_read(self->ctx);
+ PyErr_TDB_ERROR_IS_ERR_RAISE(ret, self->ctx);
+ Py_RETURN_NONE;
+}
+
+static PyObject *obj_close(PyTdbObject *self, PyObject *Py_UNUSED(ignored))
+{
+ int ret;
+ if (self->closed)
+ Py_RETURN_NONE;
+ ret = tdb_close(self->ctx);
+ self->closed = true;
+ if (ret != 0) {
+ PyErr_SetObject(PyExc_RuntimeError,
+ Py_BuildValue("(i,s)",
+ TDB_ERR_IO,
+ "Failed to close database"));
+ return NULL;
+ }
+ Py_RETURN_NONE;
+}
+
+static PyObject *obj_get(PyTdbObject *self, PyObject *args)
+{
+ TDB_DATA key;
+ PyObject *py_key;
+
+ PyErr_TDB_RAISE_IF_CLOSED(self);
+
+ if (!PyArg_ParseTuple(args, "O", &py_key))
+ return NULL;
+
+ key = PyBytes_AsTDB_DATA(py_key);
+ if (!key.dptr)
+ return NULL;
+
+ return PyBytes_FromTDB_DATA(tdb_fetch(self->ctx, key));
+}
+
+static PyObject *obj_append(PyTdbObject *self, PyObject *args)
+{
+ TDB_DATA key, data;
+ PyObject *py_key, *py_data;
+ int ret;
+
+ PyErr_TDB_RAISE_IF_CLOSED(self);
+
+ if (!PyArg_ParseTuple(args, "OO", &py_key, &py_data))
+ return NULL;
+
+ key = PyBytes_AsTDB_DATA(py_key);
+ if (!key.dptr)
+ return NULL;
+ data = PyBytes_AsTDB_DATA(py_data);
+ if (!data.dptr)
+ return NULL;
+
+ ret = tdb_append(self->ctx, key, data);
+ PyErr_TDB_ERROR_IS_ERR_RAISE(ret, self->ctx);
+ Py_RETURN_NONE;
+}
+
+static PyObject *obj_firstkey(PyTdbObject *self, PyObject *Py_UNUSED(ignored))
+{
+ PyErr_TDB_RAISE_IF_CLOSED(self);
+
+ return PyBytes_FromTDB_DATA(tdb_firstkey(self->ctx));
+}
+
+static PyObject *obj_nextkey(PyTdbObject *self, PyObject *args)
+{
+ TDB_DATA key;
+ PyObject *py_key;
+ PyErr_TDB_RAISE_IF_CLOSED(self);
+
+ if (!PyArg_ParseTuple(args, "O", &py_key))
+ return NULL;
+
+ key = PyBytes_AsTDB_DATA(py_key);
+ if (!key.dptr)
+ return NULL;
+
+ return PyBytes_FromTDB_DATA(tdb_nextkey(self->ctx, key));
+}
+
+static PyObject *obj_delete(PyTdbObject *self, PyObject *args)
+{
+ TDB_DATA key;
+ PyObject *py_key;
+ int ret;
+ PyErr_TDB_RAISE_IF_CLOSED(self);
+
+ if (!PyArg_ParseTuple(args, "O", &py_key))
+ return NULL;
+
+ key = PyBytes_AsTDB_DATA(py_key);
+ if (!key.dptr)
+ return NULL;
+ ret = tdb_delete(self->ctx, key);
+ PyErr_TDB_ERROR_IS_ERR_RAISE(ret, self->ctx);
+ Py_RETURN_NONE;
+}
+
+static int obj_contains(PyTdbObject *self, PyObject *py_key)
+{
+ TDB_DATA key;
+ int ret;
+ PyErr_TDB_RAISE_RETURN_MINUS_1_IF_CLOSED(self);
+
+ key = PyBytes_AsTDB_DATA(py_key);
+ if (!key.dptr) {
+ PyErr_BadArgument();
+ return -1;
+ }
+ ret = tdb_exists(self->ctx, key);
+ if (ret)
+ return 1;
+ return 0;
+}
+
+#if PY_MAJOR_VERSION < 3
+static PyObject *obj_has_key(PyTdbObject *self, PyObject *args)
+{
+ int ret;
+ PyObject *py_key;
+ PyErr_TDB_RAISE_IF_CLOSED(self);
+
+ if (!PyArg_ParseTuple(args, "O", &py_key))
+ return NULL;
+
+ ret = obj_contains(self, py_key);
+ if (ret == -1)
+ return NULL;
+ if (ret)
+ Py_RETURN_TRUE;
+ Py_RETURN_FALSE;
+
+}
+#endif
+
+static PyObject *obj_store(PyTdbObject *self, PyObject *args)
+{
+ TDB_DATA key, value;
+ int ret;
+ int flag = TDB_REPLACE;
+ PyObject *py_key, *py_value;
+
+ PyErr_TDB_RAISE_IF_CLOSED(self);
+
+ if (!PyArg_ParseTuple(args, "OO|i", &py_key, &py_value, &flag))
+ return NULL;
+
+ key = PyBytes_AsTDB_DATA(py_key);
+ if (!key.dptr)
+ return NULL;
+ value = PyBytes_AsTDB_DATA(py_value);
+ if (!value.dptr)
+ return NULL;
+
+ ret = tdb_store(self->ctx, key, value, flag);
+ PyErr_TDB_ERROR_IS_ERR_RAISE(ret, self->ctx);
+ Py_RETURN_NONE;
+}
+
+static PyObject *obj_storev(PyTdbObject *self, PyObject *args)
+{
+ TDB_DATA key, *values, value;
+ int ret;
+ int flag = TDB_REPLACE;
+ Py_ssize_t num_values, i;
+ PyObject *py_key, *py_values, *py_value;
+
+ PyErr_TDB_RAISE_IF_CLOSED(self);
+
+ if (!PyArg_ParseTuple(
+ args, "OO!|i", &py_key, &PyList_Type, &py_values, &flag)) {
+ return NULL;
+ }
+
+ num_values = PyList_Size(py_values);
+
+ key = PyBytes_AsTDB_DATA(py_key);
+ if (key.dptr == NULL) {
+ return NULL;
+ }
+
+ if (SSIZE_MAX/sizeof(TDB_DATA) < num_values) {
+ PyErr_SetFromErrno(PyExc_OverflowError);
+ return NULL;
+ }
+ values = malloc(sizeof(TDB_DATA) * num_values);
+ if (values == NULL) {
+ PyErr_NoMemory();
+ return NULL;
+ }
+ for (i=0; i<num_values; i++) {
+ py_value = PyList_GetItem(py_values, i);
+ value = PyBytes_AsTDB_DATA(py_value);
+ if (!value.dptr) {
+ free(values);
+ return NULL;
+ }
+ values[i] = value;
+ }
+
+ ret = tdb_storev(self->ctx, key, values, num_values, flag);
+ free(values);
+ PyErr_TDB_ERROR_IS_ERR_RAISE(ret, self->ctx);
+ Py_RETURN_NONE;
+}
+
+static PyObject *obj_add_flags(PyTdbObject *self, PyObject *args)
+{
+ unsigned flags;
+
+ PyErr_TDB_RAISE_IF_CLOSED(self);
+
+ if (!PyArg_ParseTuple(args, "I", &flags))
+ return NULL;
+
+ tdb_add_flags(self->ctx, flags);
+ Py_RETURN_NONE;
+}
+
+static PyObject *obj_remove_flags(PyTdbObject *self, PyObject *args)
+{
+ unsigned flags;
+
+ PyErr_TDB_RAISE_IF_CLOSED(self);
+
+ if (!PyArg_ParseTuple(args, "I", &flags))
+ return NULL;
+
+ tdb_remove_flags(self->ctx, flags);
+ Py_RETURN_NONE;
+}
+
+typedef struct {
+ PyObject_HEAD
+ TDB_DATA current;
+ PyTdbObject *iteratee;
+} PyTdbIteratorObject;
+
+static PyObject *tdb_iter_next(PyTdbIteratorObject *self)
+{
+ TDB_DATA current;
+ PyObject *ret;
+ if (self->current.dptr == NULL && self->current.dsize == 0)
+ return NULL;
+ current = self->current;
+ self->current = tdb_nextkey(self->iteratee->ctx, self->current);
+ ret = PyBytes_FromTDB_DATA(current);
+ return ret;
+}
+
+static void tdb_iter_dealloc(PyTdbIteratorObject *self)
+{
+ Py_DECREF(self->iteratee);
+ PyObject_Del(self);
+}
+
+PyTypeObject PyTdbIterator = {
+ .tp_name = "Iterator",
+ .tp_basicsize = sizeof(PyTdbIteratorObject),
+ .tp_iternext = (iternextfunc)tdb_iter_next,
+ .tp_dealloc = (destructor)tdb_iter_dealloc,
+ .tp_flags = Py_TPFLAGS_DEFAULT,
+ .tp_iter = PyObject_SelfIter,
+};
+
+static PyObject *tdb_object_iter(PyTdbObject *self,
+ PyObject *Py_UNUSED(ignored))
+{
+ PyTdbIteratorObject *ret;
+
+ PyErr_TDB_RAISE_IF_CLOSED(self);
+
+ ret = PyObject_New(PyTdbIteratorObject, &PyTdbIterator);
+ if (!ret)
+ return NULL;
+ ret->current = tdb_firstkey(self->ctx);
+ ret->iteratee = self;
+ Py_INCREF(self);
+ return (PyObject *)ret;
+}
+
+static PyObject *obj_clear(PyTdbObject *self, PyObject *Py_UNUSED(ignored))
+{
+ int ret;
+ PyErr_TDB_RAISE_IF_CLOSED(self);
+ ret = tdb_wipe_all(self->ctx);
+ PyErr_TDB_ERROR_IS_ERR_RAISE(ret, self->ctx);
+ Py_RETURN_NONE;
+}
+
+static PyObject *obj_repack(PyTdbObject *self, PyObject *Py_UNUSED(ignored))
+{
+ int ret;
+ PyErr_TDB_RAISE_IF_CLOSED(self);
+ ret = tdb_repack(self->ctx);
+ PyErr_TDB_ERROR_IS_ERR_RAISE(ret, self->ctx);
+ Py_RETURN_NONE;
+}
+
+static PyObject *obj_enable_seqnum(PyTdbObject *self,
+ PyObject *Py_UNUSED(ignored))
+{
+ PyErr_TDB_RAISE_IF_CLOSED(self);
+ tdb_enable_seqnum(self->ctx);
+ Py_RETURN_NONE;
+}
+
+static PyObject *obj_increment_seqnum_nonblock(PyTdbObject *self,
+ PyObject *Py_UNUSED(ignored))
+{
+ PyErr_TDB_RAISE_IF_CLOSED(self);
+ tdb_increment_seqnum_nonblock(self->ctx);
+ Py_RETURN_NONE;
+}
+
+static PyMethodDef tdb_object_methods[] = {
+ { "transaction_cancel", (PyCFunction)obj_transaction_cancel, METH_NOARGS,
+ "S.transaction_cancel() -> None\n"
+ "Cancel the currently active transaction." },
+ { "transaction_commit", (PyCFunction)obj_transaction_commit, METH_NOARGS,
+ "S.transaction_commit() -> None\n"
+ "Commit the currently active transaction." },
+ { "transaction_prepare_commit", (PyCFunction)obj_transaction_prepare_commit, METH_NOARGS,
+ "S.transaction_prepare_commit() -> None\n"
+ "Prepare to commit the currently active transaction" },
+ { "transaction_start", (PyCFunction)obj_transaction_start, METH_NOARGS,
+ "S.transaction_start() -> None\n"
+ "Start a new transaction." },
+ { "reopen", (PyCFunction)obj_reopen, METH_NOARGS, "Reopen this file." },
+ { "lock_all", (PyCFunction)obj_lockall, METH_NOARGS, NULL },
+ { "unlock_all", (PyCFunction)obj_unlockall, METH_NOARGS, NULL },
+ { "read_lock_all", (PyCFunction)obj_lockall_read, METH_NOARGS, NULL },
+ { "read_unlock_all", (PyCFunction)obj_unlockall_read, METH_NOARGS, NULL },
+ { "close", (PyCFunction)obj_close, METH_NOARGS, NULL },
+ { "get", (PyCFunction)obj_get, METH_VARARGS, "S.get(key) -> value\n"
+ "Fetch a value." },
+ { "append", (PyCFunction)obj_append, METH_VARARGS, "S.append(key, value) -> None\n"
+ "Append data to an existing key." },
+ { "firstkey", (PyCFunction)obj_firstkey, METH_NOARGS, "S.firstkey() -> data\n"
+ "Return the first key in this database." },
+ { "nextkey", (PyCFunction)obj_nextkey, METH_VARARGS, "S.nextkey(key) -> data\n"
+ "Return the next key in this database." },
+ { "delete", (PyCFunction)obj_delete, METH_VARARGS, "S.delete(key) -> None\n"
+ "Delete an entry." },
+#if PY_MAJOR_VERSION < 3
+ { "has_key", (PyCFunction)obj_has_key, METH_VARARGS, "S.has_key(key) -> None\n"
+ "Check whether key exists in this database." },
+#endif
+ { "store", (PyCFunction)obj_store, METH_VARARGS, "S.store(key, data, flag=REPLACE) -> None"
+ "Store data." },
+ { "storev", (PyCFunction)obj_storev, METH_VARARGS, "S.storev(key, data, flag=REPLACE) -> None"
+ "Store several data." },
+ { "add_flags", (PyCFunction)obj_add_flags, METH_VARARGS, "S.add_flags(flags) -> None" },
+ { "remove_flags", (PyCFunction)obj_remove_flags, METH_VARARGS, "S.remove_flags(flags) -> None" },
+#if PY_MAJOR_VERSION >= 3
+ { "keys", (PyCFunction)tdb_object_iter, METH_NOARGS, "S.keys() -> iterator" },
+#else
+ { "iterkeys", (PyCFunction)tdb_object_iter, METH_NOARGS, "S.iterkeys() -> iterator" },
+#endif
+ { "clear", (PyCFunction)obj_clear, METH_NOARGS, "S.clear() -> None\n"
+ "Wipe the entire database." },
+ { "repack", (PyCFunction)obj_repack, METH_NOARGS, "S.repack() -> None\n"
+ "Repack the entire database." },
+ { "enable_seqnum", (PyCFunction)obj_enable_seqnum, METH_NOARGS,
+ "S.enable_seqnum() -> None" },
+ { "increment_seqnum_nonblock", (PyCFunction)obj_increment_seqnum_nonblock, METH_NOARGS,
+ "S.increment_seqnum_nonblock() -> None" },
+ {0}
+};
+
+static PyObject *obj_get_hash_size(PyTdbObject *self, void *closure)
+{
+ PyErr_TDB_RAISE_IF_CLOSED(self);
+ return PyLong_FromLong(tdb_hash_size(self->ctx));
+}
+
+static int obj_set_max_dead(PyTdbObject *self, PyObject *max_dead, void *closure)
+{
+ PyErr_TDB_RAISE_RETURN_MINUS_1_IF_CLOSED(self);
+ if (!PyLong_Check(max_dead))
+ return -1;
+ tdb_set_max_dead(self->ctx, PyLong_AsLong(max_dead));
+ return 0;
+}
+
+static PyObject *obj_get_map_size(PyTdbObject *self, void *closure)
+{
+ PyErr_TDB_RAISE_IF_CLOSED(self);
+ return PyLong_FromLong(tdb_map_size(self->ctx));
+}
+
+static PyObject *obj_get_freelist_size(PyTdbObject *self, void *closure)
+{
+ PyErr_TDB_RAISE_IF_CLOSED(self);
+ return PyLong_FromLong(tdb_freelist_size(self->ctx));
+}
+
+static PyObject *obj_get_flags(PyTdbObject *self, void *closure)
+{
+ PyErr_TDB_RAISE_IF_CLOSED(self);
+ return PyLong_FromLong(tdb_get_flags(self->ctx));
+}
+
+static PyObject *obj_get_filename(PyTdbObject *self, void *closure)
+{
+ PyErr_TDB_RAISE_IF_CLOSED(self);
+ return PyBytes_FromString(tdb_name(self->ctx));
+}
+
+static PyObject *obj_get_seqnum(PyTdbObject *self, void *closure)
+{
+ PyErr_TDB_RAISE_IF_CLOSED(self);
+ return PyLong_FromLong(tdb_get_seqnum(self->ctx));
+}
+
+static PyObject *obj_get_text(PyTdbObject *self, void *closure)
+{
+ PyObject *mod, *cls, *inst;
+ mod = PyImport_ImportModule("_tdb_text");
+ if (mod == NULL)
+ return NULL;
+ cls = PyObject_GetAttrString(mod, "TdbTextWrapper");
+ if (cls == NULL) {
+ Py_DECREF(mod);
+ return NULL;
+ }
+ inst = PyObject_CallFunction(cls, discard_const_p(char, "O"), self);
+ Py_DECREF(mod);
+ Py_DECREF(cls);
+ return inst;
+}
+
+static PyGetSetDef tdb_object_getsetters[] = {
+ {
+ .name = discard_const_p(char, "hash_size"),
+ .get = (getter)obj_get_hash_size,
+ },
+ {
+ .name = discard_const_p(char, "map_size"),
+ .get = (getter)obj_get_map_size,
+ },
+ {
+ .name = discard_const_p(char, "freelist_size"),
+ .get = (getter)obj_get_freelist_size,
+ },
+ {
+ .name = discard_const_p(char, "flags"),
+ .get = (getter)obj_get_flags,
+ },
+ {
+ .name = discard_const_p(char, "max_dead"),
+ .set = (setter)obj_set_max_dead,
+ },
+ {
+ .name = discard_const_p(char, "filename"),
+ .get = (getter)obj_get_filename,
+ .doc = discard_const_p(char, "The filename of this TDB file."),
+ },
+ {
+ .name = discard_const_p(char, "seqnum"),
+ .get = (getter)obj_get_seqnum,
+ },
+ {
+ .name = discard_const_p(char, "text"),
+ .get = (getter)obj_get_text,
+ },
+ { .name = NULL }
+};
+
+static PyObject *tdb_object_repr(PyTdbObject *self)
+{
+ PyErr_TDB_RAISE_IF_CLOSED(self);
+ if (tdb_get_flags(self->ctx) & TDB_INTERNAL) {
+ return PyUnicode_FromString("Tdb(<internal>)");
+ } else {
+ return PyUnicode_FromFormat("Tdb('%s')", tdb_name(self->ctx));
+ }
+}
+
+static void tdb_object_dealloc(PyTdbObject *self)
+{
+ if (!self->closed)
+ tdb_close(self->ctx);
+ Py_TYPE(self)->tp_free(self);
+}
+
+static PyObject *obj_getitem(PyTdbObject *self, PyObject *key)
+{
+ TDB_DATA tkey, val;
+ PyErr_TDB_RAISE_IF_CLOSED(self);
+ if (!PyBytes_Check(key)) {
+ PyErr_SetString(PyExc_TypeError, "Expected bytestring as key");
+ return NULL;
+ }
+
+ tkey.dptr = (unsigned char *)PyBytes_AsString(key);
+ tkey.dsize = PyBytes_Size(key);
+
+ val = tdb_fetch(self->ctx, tkey);
+ if (val.dptr == NULL) {
+ /*
+ * if the key doesn't exist raise KeyError(key) to be
+ * consistent with python dict
+ */
+ PyErr_SetObject(PyExc_KeyError, key);
+ return NULL;
+ } else {
+ return PyBytes_FromTDB_DATA(val);
+ }
+}
+
+static int obj_setitem(PyTdbObject *self, PyObject *key, PyObject *value)
+{
+ TDB_DATA tkey, tval;
+ int ret;
+ PyErr_TDB_RAISE_RETURN_MINUS_1_IF_CLOSED(self);
+ if (!PyBytes_Check(key)) {
+ PyErr_SetString(PyExc_TypeError, "Expected bytestring as key");
+ return -1;
+ }
+
+ tkey = PyBytes_AsTDB_DATA(key);
+
+ if (value == NULL) {
+ ret = tdb_delete(self->ctx, tkey);
+ } else {
+ if (!PyBytes_Check(value)) {
+ PyErr_SetString(PyExc_TypeError, "Expected string as value");
+ return -1;
+ }
+
+ tval = PyBytes_AsTDB_DATA(value);
+
+ ret = tdb_store(self->ctx, tkey, tval, TDB_REPLACE);
+ }
+
+ if (ret != 0) {
+ PyErr_SetTDBError(self->ctx);
+ return -1;
+ }
+
+ return ret;
+}
+
+static PyMappingMethods tdb_object_mapping = {
+ .mp_subscript = (binaryfunc)obj_getitem,
+ .mp_ass_subscript = (objobjargproc)obj_setitem,
+};
+static PySequenceMethods tdb_object_seq = {
+ .sq_contains = (objobjproc)obj_contains,
+};
+static PyTypeObject PyTdb = {
+ .tp_name = "tdb.Tdb",
+ .tp_basicsize = sizeof(PyTdbObject),
+ .tp_methods = tdb_object_methods,
+ .tp_getset = tdb_object_getsetters,
+ .tp_new = py_tdb_open,
+ .tp_doc = "A TDB file",
+ .tp_repr = (reprfunc)tdb_object_repr,
+ .tp_dealloc = (destructor)tdb_object_dealloc,
+ .tp_as_mapping = &tdb_object_mapping,
+ .tp_as_sequence = &tdb_object_seq,
+ .tp_flags = Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE|Py_TPFLAGS_HAVE_ITER,
+ .tp_iter = PY_DISCARD_FUNC_SIG(getiterfunc,tdb_object_iter),
+};
+
+static PyMethodDef tdb_methods[] = {
+ {
+ .ml_name = "open",
+ .ml_meth = PY_DISCARD_FUNC_SIG(PyCFunction, py_tdb_open),
+ .ml_flags = METH_VARARGS|METH_KEYWORDS,
+ .ml_doc = "open(name, hash_size=0, tdb_flags=TDB_DEFAULT, "
+ "flags=O_RDWR, mode=0600)\nOpen a TDB file."
+ },
+ { .ml_name = NULL }
+};
+
+#define MODULE_DOC "simple key-value database that supports multiple writers."
+
+#if PY_MAJOR_VERSION >= 3
+static struct PyModuleDef moduledef = {
+ PyModuleDef_HEAD_INIT,
+ .m_name = "tdb",
+ .m_doc = MODULE_DOC,
+ .m_size = -1,
+ .m_methods = tdb_methods,
+};
+#endif
+
+PyObject* module_init(void);
+PyObject* module_init(void)
+{
+ PyObject *m;
+
+ if (PyType_Ready(&PyTdb) < 0)
+ return NULL;
+
+ if (PyType_Ready(&PyTdbIterator) < 0)
+ return NULL;
+
+#if PY_MAJOR_VERSION >= 3
+ m = PyModule_Create(&moduledef);
+#else
+ m = Py_InitModule3("tdb", tdb_methods, MODULE_DOC);
+#endif
+ if (m == NULL)
+ return NULL;
+
+ PyModule_AddIntConstant(m, "REPLACE", TDB_REPLACE);
+ PyModule_AddIntConstant(m, "INSERT", TDB_INSERT);
+ PyModule_AddIntConstant(m, "MODIFY", TDB_MODIFY);
+
+ PyModule_AddIntConstant(m, "DEFAULT", TDB_DEFAULT);
+ PyModule_AddIntConstant(m, "CLEAR_IF_FIRST", TDB_CLEAR_IF_FIRST);
+ PyModule_AddIntConstant(m, "INTERNAL", TDB_INTERNAL);
+ PyModule_AddIntConstant(m, "NOLOCK", TDB_NOLOCK);
+ PyModule_AddIntConstant(m, "NOMMAP", TDB_NOMMAP);
+ PyModule_AddIntConstant(m, "CONVERT", TDB_CONVERT);
+ PyModule_AddIntConstant(m, "BIGENDIAN", TDB_BIGENDIAN);
+ PyModule_AddIntConstant(m, "NOSYNC", TDB_NOSYNC);
+ PyModule_AddIntConstant(m, "SEQNUM", TDB_SEQNUM);
+ PyModule_AddIntConstant(m, "VOLATILE", TDB_VOLATILE);
+ PyModule_AddIntConstant(m, "ALLOW_NESTING", TDB_ALLOW_NESTING);
+ PyModule_AddIntConstant(m, "DISALLOW_NESTING", TDB_DISALLOW_NESTING);
+ PyModule_AddIntConstant(m, "INCOMPATIBLE_HASH", TDB_INCOMPATIBLE_HASH);
+
+ PyModule_AddStringConstant(m, "__docformat__", "restructuredText");
+
+ PyModule_AddStringConstant(m, "__version__", PACKAGE_VERSION);
+
+ Py_INCREF(&PyTdb);
+ PyModule_AddObject(m, "Tdb", (PyObject *)&PyTdb);
+
+ Py_INCREF(&PyTdbIterator);
+
+ return m;
+}
+
+
+#if PY_MAJOR_VERSION >= 3
+PyMODINIT_FUNC PyInit_tdb(void);
+PyMODINIT_FUNC PyInit_tdb(void)
+{
+ return module_init();
+}
+#else
+void inittdb(void);
+void inittdb(void)
+{
+ module_init();
+}
+#endif
diff --git a/lib/tdb/python/tdbdump.py b/lib/tdb/python/tdbdump.py
new file mode 100644
index 0000000..afc22d1
--- /dev/null
+++ b/lib/tdb/python/tdbdump.py
@@ -0,0 +1,12 @@
+#!/usr/bin/env python3
+# Trivial reimplementation of tdbdump in Python
+
+import tdb, sys
+
+if len(sys.argv) < 2:
+ print("Usage: tdbdump.py <tdb-file>")
+ sys.exit(1)
+
+db = tdb.Tdb(sys.argv[1])
+for (k, v) in db.items():
+ print("{\nkey(%d) = %r\ndata(%d) = %r\n}" % (len(k), k, len(v), v))
diff --git a/lib/tdb/python/tests/simple.py b/lib/tdb/python/tests/simple.py
new file mode 100644
index 0000000..f7ab024
--- /dev/null
+++ b/lib/tdb/python/tests/simple.py
@@ -0,0 +1,315 @@
+#!/usr/bin/env python3
+# Some simple tests for the Python bindings for TDB
+# Note that this tests the interface of the Python bindings
+# It does not test tdb itself.
+#
+# Copyright (C) 2007-2008 Jelmer Vernooij <jelmer@samba.org>
+# Published under the GNU LGPLv3 or later
+
+import sys
+import os
+import tempfile
+from unittest import TestCase
+
+import tdb
+
+
+class OpenTdbTests(TestCase):
+
+ def test_nonexistent_read(self):
+ self.assertRaises(IOError, tdb.Tdb, "/some/nonexistent/file", 0,
+ tdb.DEFAULT, os.O_RDWR)
+
+class CloseTdbTests(TestCase):
+
+ def test_double_close(self):
+ self.tdb = tdb.Tdb(tempfile.mkstemp()[1], 0, tdb.DEFAULT,
+ os.O_CREAT|os.O_RDWR)
+ self.assertNotEqual(None, self.tdb)
+
+ # ensure that double close does not crash python
+ self.tdb.close()
+ self.tdb.close()
+
+ # Check that further operations do not crash python
+ self.assertRaises(RuntimeError, lambda: self.tdb.transaction_start())
+
+ self.assertRaises(RuntimeError, lambda: self.tdb["bar"])
+
+
+class InternalTdbTests(TestCase):
+
+ def test_repr(self):
+ self.tdb = tdb.Tdb()
+
+ # repr used to crash on internal db
+ self.assertEqual(repr(self.tdb), "Tdb(<internal>)")
+
+
+class CommonTdbTests(TestCase):
+ """Tests common to both the text & bytes interfaces"""
+
+ use_text = False
+
+ def setUp(self):
+ super(CommonTdbTests, self).setUp()
+ self.tdb = tdb.Tdb(tempfile.mkstemp()[1], 0, tdb.DEFAULT,
+ os.O_CREAT|os.O_RDWR)
+ self.assertNotEqual(None, self.tdb)
+ if self.use_text:
+ self.tdb = self.tdb.text
+
+ def test_lockall(self):
+ self.tdb.lock_all()
+
+ def test_max_dead(self):
+ self.tdb.max_dead = 20
+
+ def test_unlockall(self):
+ self.tdb.lock_all()
+ self.tdb.unlock_all()
+
+ def test_lockall_read(self):
+ self.tdb.read_lock_all()
+ self.tdb.read_unlock_all()
+
+ def test_reopen(self):
+ self.tdb.reopen()
+
+ def test_hash_size(self):
+ self.tdb.hash_size
+
+ def test_map_size(self):
+ self.tdb.map_size
+
+ def test_freelist_size(self):
+ self.tdb.freelist_size
+
+ def test_name(self):
+ self.tdb.filename
+
+ def test_add_flags(self):
+ self.tdb.add_flags(tdb.NOMMAP)
+ self.tdb.remove_flags(tdb.NOMMAP)
+
+
+class TextCommonTdbTests(CommonTdbTests):
+
+ use_text = True
+
+
+class SimpleTdbTests(TestCase):
+
+ def setUp(self):
+ super(SimpleTdbTests, self).setUp()
+ self.tdb = tdb.Tdb(tempfile.mkstemp()[1], 0, tdb.DEFAULT,
+ os.O_CREAT|os.O_RDWR)
+ self.assertNotEqual(None, self.tdb)
+
+ def test_repr(self):
+ self.assertTrue(repr(self.tdb).startswith("Tdb('"))
+
+ def test_store(self):
+ self.tdb.store(b"bar", b"bla")
+ self.assertEqual(b"bla", self.tdb.get(b"bar"))
+
+ def test_storev(self):
+ self.tdb.storev(b"bar", [b"first", b"second", b"third"])
+ self.assertEqual(b"firstsecondthird", self.tdb.get(b"bar"))
+
+ def test_getitem(self):
+ self.tdb[b"bar"] = b"foo"
+ self.tdb.reopen()
+ self.assertEqual(b"foo", self.tdb[b"bar"])
+
+ def test_delete(self):
+ self.tdb[b"bar"] = b"foo"
+ del self.tdb[b"bar"]
+ self.assertRaises(KeyError, lambda: self.tdb[b"bar"])
+
+ def test_contains(self):
+ self.tdb[b"bla"] = b"bloe"
+ self.assertTrue(b"bla" in self.tdb)
+ self.assertFalse(b"qwertyuiop" in self.tdb)
+ if sys.version_info < (3, 0):
+ self.assertTrue(self.tdb.has_key(b"bla"))
+ self.assertFalse(self.tdb.has_key(b"qwertyuiop"))
+
+ def test_keyerror(self):
+ self.assertRaises(KeyError, lambda: self.tdb[b"bla"])
+
+ def test_iterator(self):
+ self.tdb[b"bla"] = b"1"
+ self.tdb[b"brainslug"] = b"2"
+ l = list(self.tdb)
+ l.sort()
+ self.assertEqual([b"bla", b"brainslug"], l)
+
+ def test_transaction_cancel(self):
+ self.tdb[b"bloe"] = b"2"
+ self.tdb.transaction_start()
+ self.tdb[b"bloe"] = b"1"
+ self.tdb.transaction_cancel()
+ self.assertEqual(b"2", self.tdb[b"bloe"])
+
+ def test_transaction_commit(self):
+ self.tdb[b"bloe"] = b"2"
+ self.tdb.transaction_start()
+ self.tdb[b"bloe"] = b"1"
+ self.tdb.transaction_commit()
+ self.assertEqual(b"1", self.tdb[b"bloe"])
+
+ def test_transaction_prepare_commit(self):
+ self.tdb[b"bloe"] = b"2"
+ self.tdb.transaction_start()
+ self.tdb[b"bloe"] = b"1"
+ self.tdb.transaction_prepare_commit()
+ self.tdb.transaction_commit()
+ self.assertEqual(b"1", self.tdb[b"bloe"])
+
+ def test_iterkeys(self):
+ self.tdb[b"bloe"] = b"2"
+ self.tdb[b"bla"] = b"25"
+ if sys.version_info >= (3, 0):
+ i = self.tdb.keys()
+ else:
+ i = self.tdb.iterkeys()
+ self.assertEqual(set([b"bloe", b"bla"]), set([next(i), next(i)]))
+
+ def test_clear(self):
+ self.tdb[b"bloe"] = b"2"
+ self.tdb[b"bla"] = b"25"
+ self.assertEqual(2, len(list(self.tdb)))
+ self.tdb.clear()
+ self.assertEqual(0, len(list(self.tdb)))
+
+ def test_repack(self):
+ self.tdb[b"foo"] = b"abc"
+ self.tdb[b"bar"] = b"def"
+ del self.tdb[b"foo"]
+ self.tdb.repack()
+
+ def test_seqnum(self):
+ self.tdb.enable_seqnum()
+ seq1 = self.tdb.seqnum
+ self.tdb.increment_seqnum_nonblock()
+ seq2 = self.tdb.seqnum
+ self.assertEqual(seq2-seq1, 1)
+
+ def test_len(self):
+ self.assertEqual(0, len(list(self.tdb)))
+ self.tdb[b"entry"] = b"value"
+ self.assertEqual(1, len(list(self.tdb)))
+
+
+class TdbTextTests(TestCase):
+
+ def setUp(self):
+ super(TdbTextTests, self).setUp()
+ self.tdb = tdb.Tdb(tempfile.mkstemp()[1], 0, tdb.DEFAULT,
+ os.O_CREAT|os.O_RDWR)
+ self.assertNotEqual(None, self.tdb)
+
+ def test_repr(self):
+ self.assertTrue(repr(self.tdb).startswith("Tdb('"))
+
+ def test_store(self):
+ self.tdb.text.store("bar", "bla")
+ self.assertEqual("bla", self.tdb.text.get("bar"))
+
+ def test_getitem(self):
+ self.tdb.text["bar"] = "foo"
+ self.tdb.reopen()
+ self.assertEqual("foo", self.tdb.text["bar"])
+
+ def test_delete(self):
+ self.tdb.text["bar"] = "foo"
+ del self.tdb.text["bar"]
+ self.assertRaises(KeyError, lambda: self.tdb.text["bar"])
+
+ def test_contains(self):
+ self.tdb.text["bla"] = "bloe"
+ self.assertTrue("bla" in self.tdb.text)
+ self.assertFalse("qwertyuiop" in self.tdb.text)
+ if sys.version_info < (3, 0):
+ self.assertTrue(self.tdb.text.has_key("bla"))
+ self.assertFalse(self.tdb.text.has_key("qwertyuiop"))
+
+ def test_keyerror(self):
+ self.assertRaises(KeyError, lambda: self.tdb.text["bla"])
+
+ def test_iterator(self):
+ self.tdb.text["bla"] = "1"
+ self.tdb.text["brainslug"] = "2"
+ l = list(self.tdb.text)
+ l.sort()
+ self.assertEqual(["bla", "brainslug"], l)
+
+ def test_transaction_cancel(self):
+ self.tdb.text["bloe"] = "2"
+ self.tdb.transaction_start()
+ self.tdb.text["bloe"] = "1"
+ self.tdb.transaction_cancel()
+ self.assertEqual("2", self.tdb.text["bloe"])
+
+ def test_transaction_commit(self):
+ self.tdb.text["bloe"] = "2"
+ self.tdb.transaction_start()
+ self.tdb.text["bloe"] = "1"
+ self.tdb.transaction_commit()
+ self.assertEqual("1", self.tdb.text["bloe"])
+
+ def test_transaction_prepare_commit(self):
+ self.tdb.text["bloe"] = "2"
+ self.tdb.transaction_start()
+ self.tdb.text["bloe"] = "1"
+ self.tdb.transaction_prepare_commit()
+ self.tdb.transaction_commit()
+ self.assertEqual("1", self.tdb.text["bloe"])
+
+ def test_iterkeys(self):
+ self.tdb.text["bloe"] = "2"
+ self.tdb.text["bla"] = "25"
+ if sys.version_info >= (3, 0):
+ i = self.tdb.text.keys()
+ else:
+ i = self.tdb.text.iterkeys()
+ self.assertEqual(set(["bloe", "bla"]), set([next(i), next(i)]))
+
+ def test_clear(self):
+ self.tdb.text["bloe"] = "2"
+ self.tdb.text["bla"] = "25"
+ self.assertEqual(2, len(list(self.tdb)))
+ self.tdb.clear()
+ self.assertEqual(0, len(list(self.tdb)))
+
+ def test_repack(self):
+ self.tdb.text["foo"] = "abc"
+ self.tdb.text["bar"] = "def"
+ del self.tdb.text["foo"]
+ self.tdb.repack()
+
+ def test_len(self):
+ self.assertEqual(0, len(list(self.tdb.text)))
+ self.tdb.text["entry"] = "value"
+ self.assertEqual(1, len(list(self.tdb.text)))
+
+ def test_text_and_binary(self):
+ text = u'\xfa\u0148\xef\xe7\xf8\xf0\xea'
+ bytestr = text.encode('utf-8')
+ self.tdb[b"entry"] = bytestr
+ self.tdb.text[u"entry2"] = text
+ self.assertEqual(self.tdb.text["entry"], text)
+ self.assertEqual(self.tdb[b"entry2"], bytestr)
+ assert self.tdb.text.raw == self.tdb
+
+
+class VersionTests(TestCase):
+
+ def test_present(self):
+ self.assertTrue(isinstance(tdb.__version__, str))
+
+
+if __name__ == '__main__':
+ import unittest
+ unittest.TestProgram()
diff --git a/lib/tdb/tdb.pc.in b/lib/tdb/tdb.pc.in
new file mode 100644
index 0000000..b78419e
--- /dev/null
+++ b/lib/tdb/tdb.pc.in
@@ -0,0 +1,11 @@
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+libdir=@libdir@
+includedir=@includedir@
+
+Name: tdb
+Description: A trivial database
+Version: @PACKAGE_VERSION@
+Libs: @LIB_RPATH@ -L${libdir} -ltdb
+Cflags: -I${includedir}
+URL: http://tdb.samba.org/
diff --git a/lib/tdb/test/circular_chain.tdb b/lib/tdb/test/circular_chain.tdb
new file mode 100644
index 0000000..1e143d3
--- /dev/null
+++ b/lib/tdb/test/circular_chain.tdb
Binary files differ
diff --git a/lib/tdb/test/circular_freelist.tdb b/lib/tdb/test/circular_freelist.tdb
new file mode 100644
index 0000000..f1c85e5
--- /dev/null
+++ b/lib/tdb/test/circular_freelist.tdb
Binary files differ
diff --git a/lib/tdb/test/external-agent.c b/lib/tdb/test/external-agent.c
new file mode 100644
index 0000000..3c59c06
--- /dev/null
+++ b/lib/tdb/test/external-agent.c
@@ -0,0 +1,224 @@
+#include "external-agent.h"
+#include "lock-tracking.h"
+#include "logging.h"
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <limits.h>
+#include <string.h>
+#include <errno.h>
+#include "../common/tdb_private.h"
+#include "tap-interface.h"
+#include <stdio.h>
+#include <stdarg.h>
+
+static struct tdb_context *tdb;
+
+static enum agent_return do_operation(enum operation op, const char *name)
+{
+ TDB_DATA k;
+ enum agent_return ret;
+ TDB_DATA data;
+
+ if (op != OPEN && op != OPEN_WITH_CLEAR_IF_FIRST && !tdb) {
+ diag("external: No tdb open!");
+ return OTHER_FAILURE;
+ }
+
+ k.dptr = discard_const_p(uint8_t, name);
+ k.dsize = strlen(name);
+
+ locking_would_block = 0;
+ switch (op) {
+ case OPEN:
+ if (tdb) {
+ diag("Already have tdb %s open", tdb_name(tdb));
+ return OTHER_FAILURE;
+ }
+ tdb = tdb_open_ex(name, 0, TDB_DEFAULT, O_RDWR, 0,
+ &taplogctx, NULL);
+ if (!tdb) {
+ if (!locking_would_block)
+ diag("Opening tdb gave %s", strerror(errno));
+ ret = OTHER_FAILURE;
+ } else
+ ret = SUCCESS;
+ break;
+ case OPEN_WITH_CLEAR_IF_FIRST:
+ if (tdb)
+ return OTHER_FAILURE;
+ tdb = tdb_open_ex(name, 0, TDB_CLEAR_IF_FIRST, O_RDWR, 0,
+ &taplogctx, NULL);
+ ret = tdb ? SUCCESS : OTHER_FAILURE;
+ break;
+ case TRANSACTION_START:
+ ret = tdb_transaction_start(tdb) == 0 ? SUCCESS : OTHER_FAILURE;
+ break;
+ case FETCH:
+ data = tdb_fetch(tdb, k);
+ if (data.dptr == NULL) {
+ if (tdb_error(tdb) == TDB_ERR_NOEXIST)
+ ret = FAILED;
+ else
+ ret = OTHER_FAILURE;
+ } else if (data.dsize != k.dsize
+ || memcmp(data.dptr, k.dptr, k.dsize) != 0) {
+ ret = OTHER_FAILURE;
+ } else {
+ ret = SUCCESS;
+ }
+ free(data.dptr);
+ break;
+ case STORE:
+ ret = tdb_store(tdb, k, k, 0) == 0 ? SUCCESS : OTHER_FAILURE;
+ break;
+ case TRANSACTION_COMMIT:
+ ret = tdb_transaction_commit(tdb)==0 ? SUCCESS : OTHER_FAILURE;
+ break;
+ case CHECK:
+ ret = tdb_check(tdb, NULL, NULL) == 0 ? SUCCESS : OTHER_FAILURE;
+ break;
+ case NEEDS_RECOVERY:
+ ret = tdb_needs_recovery(tdb) ? SUCCESS : FAILED;
+ break;
+ case CLOSE:
+ ret = tdb_close(tdb) == 0 ? SUCCESS : OTHER_FAILURE;
+ tdb = NULL;
+ break;
+ case PING:
+ ret = SUCCESS;
+ break;
+ case UNMAP:
+ ret = tdb_munmap(tdb) == 0 ? SUCCESS : OTHER_FAILURE;
+ if (ret == SUCCESS) {
+ tdb->flags |= TDB_NOMMAP;
+ }
+ break;
+ default:
+ ret = OTHER_FAILURE;
+ }
+
+ if (locking_would_block)
+ ret = WOULD_HAVE_BLOCKED;
+
+ return ret;
+}
+
+struct agent {
+ int cmdfd, responsefd;
+ pid_t pid;
+};
+
+/* Do this before doing any tdb stuff. Return handle, or NULL. */
+struct agent *prepare_external_agent(void)
+{
+ int ret;
+ int command[2], response[2];
+ char name[1+PATH_MAX];
+ struct agent *agent = malloc(sizeof(*agent));
+
+ if (pipe(command) != 0 || pipe(response) != 0) {
+ fprintf(stderr, "pipe failed: %s\n", strerror(errno));
+ exit(1);
+ }
+
+ agent->pid = fork();
+ if (agent->pid < 0) {
+ fprintf(stderr, "fork failed: %s\n", strerror(errno));
+ exit(1);
+ }
+
+ if (agent->pid != 0) {
+ close(command[0]);
+ close(response[1]);
+ agent->cmdfd = command[1];
+ agent->responsefd = response[0];
+ return agent;
+ }
+
+ close(command[1]);
+ close(response[0]);
+
+ /* We want to fail, not block. */
+ nonblocking_locks = true;
+ log_prefix = "external: ";
+ while ((ret = read(command[0], name, sizeof(name))) > 0) {
+ enum agent_return result;
+
+ result = do_operation(name[0], name+1);
+ if (write(response[1], &result, sizeof(result))
+ != sizeof(result))
+ abort();
+ }
+ exit(0);
+}
+
+void shutdown_agent(struct agent *agent)
+{
+ pid_t p;
+
+ close(agent->cmdfd);
+ close(agent->responsefd);
+ p = waitpid(agent->pid, NULL, WNOHANG);
+ if (p == 0) {
+ kill(agent->pid, SIGKILL);
+ }
+ waitpid(agent->pid, NULL, 0);
+ free(agent);
+}
+
+/* Ask the external agent to try to do an operation. */
+enum agent_return external_agent_operation(struct agent *agent,
+ enum operation op,
+ const char *name)
+{
+ enum agent_return res;
+ unsigned int len;
+ char *string;
+
+ if (!name)
+ name = "";
+ len = 1 + strlen(name) + 1;
+ string = malloc(len);
+
+ string[0] = op;
+ strncpy(string+1, name, len - 1);
+ string[len-1] = '\0';
+
+ if (write(agent->cmdfd, string, len) != len
+ || read(agent->responsefd, &res, sizeof(res)) != sizeof(res))
+ res = AGENT_DIED;
+
+ free(string);
+ return res;
+}
+
+const char *agent_return_name(enum agent_return ret)
+{
+ return ret == SUCCESS ? "SUCCESS"
+ : ret == WOULD_HAVE_BLOCKED ? "WOULD_HAVE_BLOCKED"
+ : ret == AGENT_DIED ? "AGENT_DIED"
+ : ret == FAILED ? "FAILED"
+ : ret == OTHER_FAILURE ? "OTHER_FAILURE"
+ : "**INVALID**";
+}
+
+const char *operation_name(enum operation op)
+{
+ switch (op) {
+ case OPEN: return "OPEN";
+ case OPEN_WITH_CLEAR_IF_FIRST: return "OPEN_WITH_CLEAR_IF_FIRST";
+ case TRANSACTION_START: return "TRANSACTION_START";
+ case FETCH: return "FETCH";
+ case STORE: return "STORE";
+ case TRANSACTION_COMMIT: return "TRANSACTION_COMMIT";
+ case CHECK: return "CHECK";
+ case NEEDS_RECOVERY: return "NEEDS_RECOVERY";
+ case CLOSE: return "CLOSE";
+ case PING: return "PING";
+ case UNMAP: return "UNMAP";
+ }
+ return "**INVALID**";
+}
diff --git a/lib/tdb/test/external-agent.h b/lib/tdb/test/external-agent.h
new file mode 100644
index 0000000..de9d0ac
--- /dev/null
+++ b/lib/tdb/test/external-agent.h
@@ -0,0 +1,44 @@
+#ifndef TDB_TEST_EXTERNAL_AGENT_H
+#define TDB_TEST_EXTERNAL_AGENT_H
+
+/* For locking tests, we need a different process to try things at
+ * various times. */
+enum operation {
+ OPEN,
+ OPEN_WITH_CLEAR_IF_FIRST,
+ TRANSACTION_START,
+ FETCH,
+ STORE,
+ TRANSACTION_COMMIT,
+ CHECK,
+ NEEDS_RECOVERY,
+ CLOSE,
+ PING,
+ UNMAP,
+};
+
+/* Do this before doing any tdb stuff. Return handle, or -1. */
+struct agent *prepare_external_agent(void);
+void shutdown_agent(struct agent *agent);
+
+enum agent_return {
+ SUCCESS,
+ WOULD_HAVE_BLOCKED,
+ AGENT_DIED,
+ FAILED, /* For fetch, or NEEDS_RECOVERY */
+ OTHER_FAILURE,
+};
+
+/* Ask the external agent to try to do an operation.
+ * name == tdb name for OPEN/OPEN_WITH_CLEAR_IF_FIRST,
+ * record name for FETCH/STORE (store stores name as data too)
+ */
+enum agent_return external_agent_operation(struct agent *handle,
+ enum operation op,
+ const char *name);
+
+/* Mapping enum -> string. */
+const char *agent_return_name(enum agent_return ret);
+const char *operation_name(enum operation op);
+
+#endif /* TDB_TEST_EXTERNAL_AGENT_H */
diff --git a/lib/tdb/test/jenkins-be-hash.tdb b/lib/tdb/test/jenkins-be-hash.tdb
new file mode 100644
index 0000000..b652840
--- /dev/null
+++ b/lib/tdb/test/jenkins-be-hash.tdb
Binary files differ
diff --git a/lib/tdb/test/jenkins-le-hash.tdb b/lib/tdb/test/jenkins-le-hash.tdb
new file mode 100644
index 0000000..007e0a3
--- /dev/null
+++ b/lib/tdb/test/jenkins-le-hash.tdb
Binary files differ
diff --git a/lib/tdb/test/lock-tracking.c b/lib/tdb/test/lock-tracking.c
new file mode 100644
index 0000000..b2f092c
--- /dev/null
+++ b/lib/tdb/test/lock-tracking.c
@@ -0,0 +1,157 @@
+/* We save the locks so we can reaquire them. */
+#include "../common/tdb_private.h"
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include "tap-interface.h"
+#include "lock-tracking.h"
+
+struct testlock {
+ struct testlock *next;
+ unsigned int off;
+ unsigned int len;
+ int type;
+};
+static struct testlock *testlocks;
+int locking_errors = 0;
+bool suppress_lockcheck = false;
+bool nonblocking_locks;
+int locking_would_block = 0;
+void (*unlock_callback)(int fd);
+
+int fcntl_with_lockcheck(int fd, int cmd, ... /* arg */ )
+{
+ va_list ap;
+ int ret, arg3;
+ struct flock *fl;
+ bool may_block = false;
+
+ if (cmd != F_SETLK && cmd != F_SETLKW) {
+ /* This may be totally bogus, but we don't know in general. */
+ va_start(ap, cmd);
+ arg3 = va_arg(ap, int);
+ va_end(ap);
+
+ return fcntl(fd, cmd, arg3);
+ }
+
+ va_start(ap, cmd);
+ fl = va_arg(ap, struct flock *);
+ va_end(ap);
+
+ if (cmd == F_SETLKW && nonblocking_locks) {
+ cmd = F_SETLK;
+ may_block = true;
+ }
+ ret = fcntl(fd, cmd, fl);
+
+ /* Detect when we failed, but might have been OK if we waited. */
+ if (may_block && ret == -1 && (errno == EAGAIN || errno == EACCES)) {
+ locking_would_block++;
+ }
+
+ if (fl->l_type == F_UNLCK) {
+ struct testlock **l;
+ struct testlock *old = NULL;
+
+ for (l = &testlocks; *l; l = &(*l)->next) {
+ if ((*l)->off == fl->l_start
+ && (*l)->len == fl->l_len) {
+ if (ret == 0) {
+ old = *l;
+ *l = (*l)->next;
+ free(old);
+ }
+ break;
+ }
+ if (((*l)->off == fl->l_start)
+ && ((*l)->len == 0)
+ && (ret == 0)) {
+ /*
+ * Remove a piece from the start of the
+ * allrecord_lock
+ */
+ old = *l;
+ (*l)->off += fl->l_len;
+ break;
+ }
+ }
+ if (!old && !suppress_lockcheck) {
+ diag("Unknown unlock %u@%u - %i",
+ (int)fl->l_len, (int)fl->l_start, ret);
+ locking_errors++;
+ }
+ } else {
+ struct testlock *new, *i;
+ unsigned int fl_end = fl->l_start + fl->l_len;
+ if (fl->l_len == 0)
+ fl_end = (unsigned int)-1;
+
+ /* Check for overlaps: we shouldn't do this. */
+ for (i = testlocks; i; i = i->next) {
+ unsigned int i_end = i->off + i->len;
+ if (i->len == 0)
+ i_end = (unsigned int)-1;
+
+ if (fl->l_start >= i->off && fl->l_start < i_end)
+ break;
+ if (fl_end >= i->off && fl_end < i_end)
+ break;
+
+ /* tdb_allrecord_lock does this, handle adjacent: */
+ if (fl->l_start == i_end && fl->l_type == i->type) {
+ if (ret == 0) {
+ i->len = fl->l_len
+ ? i->len + fl->l_len
+ : 0;
+ }
+ goto done;
+ }
+ }
+ if (i) {
+ /* Special case: upgrade of allrecord lock. */
+ if (i->type == F_RDLCK && fl->l_type == F_WRLCK
+ && i->off == FREELIST_TOP
+ && fl->l_start == FREELIST_TOP
+ && i->len == 0
+ && fl->l_len == 0) {
+ if (ret == 0)
+ i->type = F_WRLCK;
+ goto done;
+ }
+ if (!suppress_lockcheck) {
+ diag("%s testlock %u@%u overlaps %u@%u",
+ fl->l_type == F_WRLCK ? "write" : "read",
+ (int)fl->l_len, (int)fl->l_start,
+ i->len, (int)i->off);
+ locking_errors++;
+ }
+ }
+
+ if (ret == 0) {
+ new = malloc(sizeof *new);
+ new->off = fl->l_start;
+ new->len = fl->l_len;
+ new->type = fl->l_type;
+ new->next = testlocks;
+ testlocks = new;
+ }
+ }
+done:
+ if (ret == 0 && fl->l_type == F_UNLCK && unlock_callback)
+ unlock_callback(fd);
+ return ret;
+}
+
+unsigned int forget_locking(void)
+{
+ unsigned int num = 0;
+ while (testlocks) {
+ struct testlock *next = testlocks->next;
+ free(testlocks);
+ testlocks = next;
+ num++;
+ }
+ return num;
+}
diff --git a/lib/tdb/test/lock-tracking.h b/lib/tdb/test/lock-tracking.h
new file mode 100644
index 0000000..f2c9c44
--- /dev/null
+++ b/lib/tdb/test/lock-tracking.h
@@ -0,0 +1,25 @@
+#ifndef LOCK_TRACKING_H
+#define LOCK_TRACKING_H
+#include <stdbool.h>
+
+/* Set this if you want a callback after fnctl unlock. */
+extern void (*unlock_callback)(int fd);
+
+/* Replacement fcntl. */
+int fcntl_with_lockcheck(int fd, int cmd, ... /* arg */ );
+
+/* Discard locking info: returns number of locks outstanding. */
+unsigned int forget_locking(void);
+
+/* Number of errors in locking. */
+extern int locking_errors;
+
+/* Suppress lock checking. */
+extern bool suppress_lockcheck;
+
+/* Make all locks non-blocking. */
+extern bool nonblocking_locks;
+
+/* Number of times we failed a lock because we made it non-blocking. */
+extern int locking_would_block;
+#endif /* LOCK_TRACKING_H */
diff --git a/lib/tdb/test/logging.c b/lib/tdb/test/logging.c
new file mode 100644
index 0000000..dfab486
--- /dev/null
+++ b/lib/tdb/test/logging.c
@@ -0,0 +1,33 @@
+#include "logging.h"
+#include "tap-interface.h"
+#include <stdarg.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+
+bool suppress_logging = false;
+const char *log_prefix = "";
+
+/* Turn log messages into tap diag messages. */
+static void taplog(struct tdb_context *tdb,
+ enum tdb_debug_level level,
+ const char *fmt, ...)
+{
+ va_list ap;
+ char line[200];
+
+ if (suppress_logging)
+ return;
+
+ va_start(ap, fmt);
+ vsprintf(line, fmt, ap);
+ va_end(ap);
+
+ /* Strip trailing \n: diag adds it. */
+ if (line[0] && line[strlen(line)-1] == '\n')
+ diag("%s%.*s", log_prefix, (unsigned)strlen(line)-1, line);
+ else
+ diag("%s%s", log_prefix, line);
+}
+
+struct tdb_logging_context taplogctx = { taplog, NULL };
diff --git a/lib/tdb/test/logging.h b/lib/tdb/test/logging.h
new file mode 100644
index 0000000..89e77b2
--- /dev/null
+++ b/lib/tdb/test/logging.h
@@ -0,0 +1,11 @@
+#ifndef TDB_TEST_LOGGING_H
+#define TDB_TEST_LOGGING_H
+#include "replace.h"
+#include "../include/tdb.h"
+#include <stdbool.h>
+
+extern bool suppress_logging;
+extern const char *log_prefix;
+extern struct tdb_logging_context taplogctx;
+
+#endif /* TDB_TEST_LOGGING_H */
diff --git a/lib/tdb/test/old-nohash-be.tdb b/lib/tdb/test/old-nohash-be.tdb
new file mode 100644
index 0000000..1c49116
--- /dev/null
+++ b/lib/tdb/test/old-nohash-be.tdb
Binary files differ
diff --git a/lib/tdb/test/old-nohash-le.tdb b/lib/tdb/test/old-nohash-le.tdb
new file mode 100644
index 0000000..0655072
--- /dev/null
+++ b/lib/tdb/test/old-nohash-le.tdb
Binary files differ
diff --git a/lib/tdb/test/run-3G-file.c b/lib/tdb/test/run-3G-file.c
new file mode 100644
index 0000000..79e291b
--- /dev/null
+++ b/lib/tdb/test/run-3G-file.c
@@ -0,0 +1,145 @@
+#include "../common/tdb_private.h"
+#include "../common/io.c"
+#include "../common/tdb.c"
+#include "../common/lock.c"
+#include "../common/freelist.c"
+#include "../common/traverse.c"
+#include "../common/transaction.c"
+#include "../common/error.c"
+#include "../common/open.c"
+#include "../common/check.c"
+#include "../common/hash.c"
+#include "../common/mutex.c"
+#include "tap-interface.h"
+#include <stdlib.h>
+#include "logging.h"
+
+static int tdb_expand_file_sparse(struct tdb_context *tdb,
+ tdb_off_t size,
+ tdb_off_t addition)
+{
+ if (tdb->read_only || tdb->traverse_read) {
+ tdb->ecode = TDB_ERR_RDONLY;
+ return -1;
+ }
+
+ if (tdb_ftruncate(tdb, size+addition) == -1) {
+ char b = 0;
+ ssize_t written = tdb_pwrite(tdb, &b, 1, (size+addition) - 1);
+ if (written == 0) {
+ /* try once more, potentially revealing errno */
+ written = tdb_pwrite(tdb, &b, 1, (size+addition) - 1);
+ }
+ if (written == 0) {
+ /* again - give up, guessing errno */
+ errno = ENOSPC;
+ }
+ if (written != 1) {
+ TDB_LOG((tdb, TDB_DEBUG_FATAL, "expand_file to %d failed (%s)\n",
+ size+addition, strerror(errno)));
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+static const struct tdb_methods large_io_methods = {
+ tdb_read,
+ tdb_write,
+ tdb_next_hash_chain,
+ tdb_notrans_oob,
+ tdb_expand_file_sparse
+};
+
+static int test_traverse(struct tdb_context *tdb, TDB_DATA key, TDB_DATA data,
+ void *_data)
+{
+ TDB_DATA *expect = _data;
+ ok1(key.dsize == strlen("hi"));
+ ok1(memcmp(key.dptr, "hi", strlen("hi")) == 0);
+ ok1(data.dsize == expect->dsize);
+ ok1(memcmp(data.dptr, expect->dptr, data.dsize) == 0);
+ return 0;
+}
+
+int main(int argc, char *argv[])
+{
+ struct tdb_context *tdb;
+ TDB_DATA key, orig_data, data;
+ uint32_t hashval;
+ tdb_off_t rec_ptr;
+ struct tdb_record rec;
+ int ret;
+
+ plan_tests(24);
+ tdb = tdb_open_ex("run-36-file.tdb", 1024, TDB_CLEAR_IF_FIRST,
+ O_CREAT|O_TRUNC|O_RDWR, 0600, &taplogctx, NULL);
+
+ ok1(tdb);
+ tdb->methods = &large_io_methods;
+
+ key.dsize = strlen("hi");
+ key.dptr = discard_const_p(uint8_t, "hi");
+ orig_data.dsize = strlen("world");
+ orig_data.dptr = discard_const_p(uint8_t, "world");
+
+ /* Enlarge the file (internally multiplies by 2). */
+ ret = tdb_expand(tdb, 1500000000);
+#ifdef HAVE_INCOHERENT_MMAP
+ /* This can fail due to mmap failure on 32 bit systems. */
+ if (ret == -1) {
+ /* These should now fail. */
+ ok1(tdb_store(tdb, key, orig_data, TDB_INSERT) == -1);
+ data = tdb_fetch(tdb, key);
+ ok1(data.dptr == NULL);
+ ok1(tdb_traverse(tdb, test_traverse, &orig_data) == -1);
+ ok1(tdb_delete(tdb, key) == -1);
+ ok1(tdb_traverse(tdb, test_traverse, NULL) == -1);
+ /* Skip the rest... */
+ for (ret = 0; ret < 24 - 6; ret++)
+ ok1(1);
+ tdb_close(tdb);
+ return exit_status();
+ }
+#endif
+ ok1(ret == 0);
+
+ /* Put an entry in, and check it. */
+ ok1(tdb_store(tdb, key, orig_data, TDB_INSERT) == 0);
+
+ data = tdb_fetch(tdb, key);
+ ok1(data.dsize == strlen("world"));
+ ok1(memcmp(data.dptr, "world", strlen("world")) == 0);
+ free(data.dptr);
+
+ /* That currently fills at the end, make sure that's true. */
+ hashval = tdb->hash_fn(&key);
+ rec_ptr = tdb_find_lock_hash(tdb, key, hashval, F_RDLCK, &rec);
+ ok1(rec_ptr);
+ ok1(rec_ptr > 2U*1024*1024*1024);
+ tdb_unlock(tdb, BUCKET(rec.full_hash), F_RDLCK);
+
+ /* Traverse must work. */
+ ok1(tdb_traverse(tdb, test_traverse, &orig_data) == 1);
+
+ /* Delete should work. */
+ ok1(tdb_delete(tdb, key) == 0);
+
+ ok1(tdb_traverse(tdb, test_traverse, NULL) == 0);
+
+ /* Transactions should work. */
+ ok1(tdb_transaction_start(tdb) == 0);
+ ok1(tdb_store(tdb, key, orig_data, TDB_INSERT) == 0);
+
+ data = tdb_fetch(tdb, key);
+ ok1(data.dsize == strlen("world"));
+ ok1(memcmp(data.dptr, "world", strlen("world")) == 0);
+ free(data.dptr);
+ ok1(tdb_transaction_commit(tdb) == 0);
+
+ ok1(tdb_traverse(tdb, test_traverse, &orig_data) == 1);
+ tdb_close(tdb);
+
+ return exit_status();
+}
diff --git a/lib/tdb/test/run-allrecord-traverse-deadlock.c b/lib/tdb/test/run-allrecord-traverse-deadlock.c
new file mode 100644
index 0000000..2c58206
--- /dev/null
+++ b/lib/tdb/test/run-allrecord-traverse-deadlock.c
@@ -0,0 +1,203 @@
+#include "../common/tdb_private.h"
+#include "../common/io.c"
+#include "../common/tdb.c"
+#include "../common/lock.c"
+#include "../common/freelist.c"
+#include "../common/traverse.c"
+#include "../common/transaction.c"
+#include "../common/error.c"
+#include "../common/open.c"
+#include "../common/check.c"
+#include "../common/hash.c"
+#include "../common/mutex.c"
+#include "tap-interface.h"
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <stdarg.h>
+#include "logging.h"
+
+static void do_allrecord_lock(const char *name, int tdb_flags, int up,
+ int down)
+{
+ struct tdb_context *tdb;
+ int ret;
+ ssize_t nread, nwritten;
+ char c = 0;
+
+ tdb = tdb_open_ex(name, 3, tdb_flags,
+ O_RDWR|O_CREAT, 0755, &taplogctx, NULL);
+ ok(tdb, "tdb_open_ex should succeed");
+
+ ret = tdb_lockall(tdb);
+ ok(ret == 0, "tdb_lockall should succeed");
+
+ nwritten = write(up, &c, sizeof(c));
+ ok(nwritten == sizeof(c), "write should succeed");
+
+ nread = read(down, &c, sizeof(c));
+ ok(nread == sizeof(c), "read should succeed");
+
+ ret = tdb_traverse(tdb, NULL, NULL);
+ ok(ret == -1, "do_allrecord_lock: traverse should fail");
+
+ nwritten = write(up, &c, sizeof(c));
+ ok(nwritten == sizeof(c), "write should succeed");
+
+ exit(0);
+}
+
+static void do_traverse(const char *name, int tdb_flags, int up, int down)
+{
+ struct tdb_context *tdb;
+ int ret;
+ ssize_t nread, nwritten;
+ char c = 0;
+
+ tdb = tdb_open_ex(name, 3, tdb_flags,
+ O_RDWR|O_CREAT, 0755, &taplogctx, NULL);
+ ok(tdb, "tdb_open_ex should succeed");
+
+ ret = tdb_traverse(tdb, NULL, NULL);
+ ok(ret == 1, "do_traverse: tdb_traverse should return 1 record");
+
+ nwritten = write(up, &c, sizeof(c));
+ ok(nwritten == sizeof(c), "write should succeed");
+
+ nread = read(down, &c, sizeof(c));
+ ok(nread == sizeof(c), "read should succeed");
+
+ exit(0);
+}
+
+/*
+ * Process 1: get the allrecord_lock on a tdb.
+ * Process 2: start a traverse, this will stall waiting for the
+ * first chainlock: That is taken by the allrecord_lock
+ * Process 1: start a traverse: This will get EDEADLK in trying to
+ * get the TRANSACTION_LOCK. It will deadlock for mutexes,
+ * which don't have built-in deadlock detection.
+ */
+
+static int do_tests(const char *name, int tdb_flags)
+{
+ struct tdb_context *tdb;
+ int ret;
+ pid_t traverse_child, allrecord_child;
+ int traverse_down[2];
+ int traverse_up[2];
+ int allrecord_down[2];
+ int allrecord_up[2];
+ char c;
+ ssize_t nread, nwritten;
+ TDB_DATA key, data;
+
+ key.dsize = strlen("hi");
+ key.dptr = discard_const_p(uint8_t, "hi");
+ data.dsize = strlen("world");
+ data.dptr = discard_const_p(uint8_t, "world");
+
+ tdb = tdb_open_ex(name, 3, tdb_flags,
+ O_RDWR|O_CREAT, 0755, &taplogctx, NULL);
+ ok(tdb, "tdb_open_ex should succeed");
+
+ ret = tdb_store(tdb, key, data, TDB_INSERT);
+ ok(ret == 0, "tdb_store should succeed");
+
+ ret = pipe(traverse_down);
+ ok(ret == 0, "pipe should succeed");
+
+ ret = pipe(traverse_up);
+ ok(ret == 0, "pipe should succeed");
+
+ ret = pipe(allrecord_down);
+ ok(ret == 0, "pipe should succeed");
+
+ ret = pipe(allrecord_up);
+ ok(ret == 0, "pipe should succeed");
+
+ allrecord_child = fork();
+ ok(allrecord_child != -1, "fork should succeed");
+
+ if (allrecord_child == 0) {
+ tdb_close(tdb);
+ close(traverse_up[0]);
+ close(traverse_up[1]);
+ close(traverse_down[0]);
+ close(traverse_down[1]);
+ close(allrecord_up[0]);
+ close(allrecord_down[1]);
+ do_allrecord_lock(name, tdb_flags,
+ allrecord_up[1], allrecord_down[0]);
+ exit(0);
+ }
+ close(allrecord_up[1]);
+ close(allrecord_down[0]);
+
+ nread = read(allrecord_up[0], &c, sizeof(c));
+ ok(nread == sizeof(c), "read should succeed");
+
+ traverse_child = fork();
+ ok(traverse_child != -1, "fork should succeed");
+
+ if (traverse_child == 0) {
+ tdb_close(tdb);
+ close(traverse_up[0]);
+ close(traverse_down[1]);
+ close(allrecord_up[0]);
+ close(allrecord_down[1]);
+ do_traverse(name, tdb_flags,
+ traverse_up[1], traverse_down[0]);
+ exit(0);
+ }
+ close(traverse_up[1]);
+ close(traverse_down[0]);
+
+ poll(NULL, 0, 1000);
+
+ nwritten = write(allrecord_down[1], &c, sizeof(c));
+ ok(nwritten == sizeof(c), "write should succeed");
+
+ nread = read(traverse_up[0], &c, sizeof(c));
+ ok(nread == sizeof(c), "read should succeed");
+
+ nwritten = write(traverse_down[1], &c, sizeof(c));
+ ok(nwritten == sizeof(c), "write should succeed");
+
+ nread = read(allrecord_up[0], &c, sizeof(c));
+ ok(nread == sizeof(c), "ret should succeed");
+
+ close(traverse_up[0]);
+ close(traverse_down[1]);
+ close(allrecord_up[0]);
+ close(allrecord_down[1]);
+ diag("%s tests done", name);
+ return exit_status();
+}
+
+int main(int argc, char *argv[])
+{
+ int ret;
+ bool mutex_support;
+
+ mutex_support = tdb_runtime_check_for_robust_mutexes();
+
+ ret = do_tests("marklock-deadlock-fcntl.tdb",
+ TDB_CLEAR_IF_FIRST |
+ TDB_INCOMPATIBLE_HASH);
+ ok(ret == 0, "marklock-deadlock-fcntl.tdb tests should succeed");
+
+ if (!mutex_support) {
+ skip(1, "No robust mutex support, "
+ "skipping marklock-deadlock-mutex.tdb tests");
+ return exit_status();
+ }
+
+ ret = do_tests("marklock-deadlock-mutex.tdb",
+ TDB_CLEAR_IF_FIRST |
+ TDB_MUTEX_LOCKING |
+ TDB_INCOMPATIBLE_HASH);
+ ok(ret == 0, "marklock-deadlock-mutex.tdb tests should succeed");
+
+ return exit_status();
+}
diff --git a/lib/tdb/test/run-bad-tdb-header.c b/lib/tdb/test/run-bad-tdb-header.c
new file mode 100644
index 0000000..9d29fdf
--- /dev/null
+++ b/lib/tdb/test/run-bad-tdb-header.c
@@ -0,0 +1,59 @@
+#include "../common/tdb_private.h"
+#include "../common/io.c"
+#include "../common/tdb.c"
+#include "../common/lock.c"
+#include "../common/freelist.c"
+#include "../common/traverse.c"
+#include "../common/transaction.c"
+#include "../common/error.c"
+#include "../common/open.c"
+#include "../common/check.c"
+#include "../common/hash.c"
+#include "../common/mutex.c"
+#include "tap-interface.h"
+#include <stdlib.h>
+#include "logging.h"
+
+int main(int argc, char *argv[])
+{
+ struct tdb_context *tdb;
+ struct tdb_header hdr;
+ int fd;
+
+ plan_tests(11);
+ /* Can open fine if complete crap, as long as O_CREAT. */
+ fd = open("run-bad-tdb-header.tdb", O_RDWR|O_CREAT|O_TRUNC, 0600);
+ ok1(fd >= 0);
+ ok1(write(fd, "hello world", 11) == 11);
+ close(fd);
+ tdb = tdb_open_ex("run-bad-tdb-header.tdb", 1024, 0, O_RDWR, 0,
+ &taplogctx, NULL);
+ ok1(!tdb);
+ tdb = tdb_open_ex("run-bad-tdb-header.tdb", 1024, 0, O_CREAT|O_RDWR,
+ 0600, &taplogctx, NULL);
+ ok1(tdb);
+ tdb_close(tdb);
+
+ /* Now, with wrong version it should *not* overwrite. */
+ fd = open("run-bad-tdb-header.tdb", O_RDWR);
+ ok1(fd >= 0);
+ ok1(read(fd, &hdr, sizeof(hdr)) == sizeof(hdr));
+ ok1(hdr.version == TDB_VERSION);
+ hdr.version++;
+ lseek(fd, 0, SEEK_SET);
+ ok1(write(fd, &hdr, sizeof(hdr)) == sizeof(hdr));
+ close(fd);
+
+ tdb = tdb_open_ex("run-bad-tdb-header.tdb", 1024, 0, O_RDWR|O_CREAT,
+ 0600, &taplogctx, NULL);
+ ok1(errno == EIO);
+ ok1(!tdb);
+
+ /* With truncate, will be fine. */
+ tdb = tdb_open_ex("run-bad-tdb-header.tdb", 1024, 0,
+ O_RDWR|O_CREAT|O_TRUNC, 0600, &taplogctx, NULL);
+ ok1(tdb);
+ tdb_close(tdb);
+
+ return exit_status();
+}
diff --git a/lib/tdb/test/run-check.c b/lib/tdb/test/run-check.c
new file mode 100644
index 0000000..ce389a2
--- /dev/null
+++ b/lib/tdb/test/run-check.c
@@ -0,0 +1,65 @@
+#include "../common/tdb_private.h"
+#include "../common/io.c"
+#include "../common/tdb.c"
+#include "../common/lock.c"
+#include "../common/freelist.c"
+#include "../common/traverse.c"
+#include "../common/transaction.c"
+#include "../common/error.c"
+#include "../common/open.c"
+#include "../common/check.c"
+#include "../common/hash.c"
+#include "../common/mutex.c"
+#include "tap-interface.h"
+#include <stdlib.h>
+#include "logging.h"
+
+int main(int argc, char *argv[])
+{
+ struct tdb_context *tdb;
+ TDB_DATA key, data;
+
+ plan_tests(13);
+ tdb = tdb_open_ex("run-check.tdb", 1, TDB_CLEAR_IF_FIRST,
+ O_CREAT|O_TRUNC|O_RDWR, 0600, &taplogctx, NULL);
+
+ ok1(tdb);
+ ok1(tdb_check(tdb, NULL, NULL) == 0);
+
+ key.dsize = strlen("hi");
+ key.dptr = discard_const_p(uint8_t, "hi");
+ data.dsize = strlen("world");
+ data.dptr = discard_const_p(uint8_t, "world");
+
+ ok1(tdb_store(tdb, key, data, TDB_INSERT) == 0);
+ ok1(tdb_check(tdb, NULL, NULL) == 0);
+ tdb_close(tdb);
+
+ tdb = tdb_open_ex("run-check.tdb", 1024, 0, O_RDWR, 0,
+ &taplogctx, NULL);
+ ok1(tdb);
+ ok1(tdb_check(tdb, NULL, NULL) == 0);
+ tdb_close(tdb);
+
+ tdb = tdb_open_ex("test/tdb.corrupt", 1024, 0, O_RDWR, 0,
+ &taplogctx, NULL);
+ ok1(tdb);
+ ok1(tdb_check(tdb, NULL, NULL) == -1);
+ ok1(tdb_error(tdb) == TDB_ERR_CORRUPT);
+ tdb_close(tdb);
+
+ /* Big and little endian should work! */
+ tdb = tdb_open_ex("test/old-nohash-le.tdb", 1024, 0, O_RDWR, 0,
+ &taplogctx, NULL);
+ ok1(tdb);
+ ok1(tdb_check(tdb, NULL, NULL) == 0);
+ tdb_close(tdb);
+
+ tdb = tdb_open_ex("test/old-nohash-be.tdb", 1024, 0, O_RDWR, 0,
+ &taplogctx, NULL);
+ ok1(tdb);
+ ok1(tdb_check(tdb, NULL, NULL) == 0);
+ tdb_close(tdb);
+
+ return exit_status();
+}
diff --git a/lib/tdb/test/run-circular-chain.c b/lib/tdb/test/run-circular-chain.c
new file mode 100644
index 0000000..4fb32a2
--- /dev/null
+++ b/lib/tdb/test/run-circular-chain.c
@@ -0,0 +1,42 @@
+#include "../common/tdb_private.h"
+#include "../common/io.c"
+#include "../common/tdb.c"
+#include "../common/lock.c"
+#include "../common/freelist.c"
+#include "../common/traverse.c"
+#include "../common/transaction.c"
+#include "../common/error.c"
+#include "../common/open.c"
+#include "../common/check.c"
+#include "../common/hash.c"
+#include "../common/mutex.c"
+#include "tap-interface.h"
+#include <stdlib.h>
+#include "logging.h"
+
+int main(int argc, char *argv[])
+{
+ struct tdb_context *tdb;
+ TDB_DATA key;
+
+ plan_tests(3);
+ tdb = tdb_open_ex(
+ "test/circular_chain.tdb",
+ 0,
+ TDB_DEFAULT,
+ O_RDONLY,
+ 0600,
+ &taplogctx,
+ NULL);
+
+ ok1(tdb);
+ key.dsize = strlen("x");
+ key.dptr = discard_const_p(uint8_t, "x");
+
+ ok1(tdb_exists(tdb, key) == 0);
+ ok1(tdb_error(tdb) == TDB_ERR_CORRUPT);
+
+ tdb_close(tdb);
+
+ return exit_status();
+}
diff --git a/lib/tdb/test/run-circular-freelist.c b/lib/tdb/test/run-circular-freelist.c
new file mode 100644
index 0000000..f1bec87
--- /dev/null
+++ b/lib/tdb/test/run-circular-freelist.c
@@ -0,0 +1,50 @@
+#include "../common/tdb_private.h"
+#include "../common/io.c"
+#include "../common/tdb.c"
+#include "../common/lock.c"
+#include "../common/freelist.c"
+#include "../common/traverse.c"
+#include "../common/transaction.c"
+#include "../common/error.c"
+#include "../common/open.c"
+#include "../common/check.c"
+#include "../common/hash.c"
+#include "../common/mutex.c"
+#include "tap-interface.h"
+#include <stdlib.h>
+#include "logging.h"
+
+int main(int argc, char *argv[])
+{
+ struct tdb_context *tdb;
+ TDB_DATA key, data;
+
+ plan_tests(3);
+ tdb = tdb_open_ex(
+ "test/circular_freelist.tdb",
+ 0,
+ TDB_DEFAULT,
+ O_RDWR,
+ 0600,
+ &taplogctx,
+ NULL);
+
+ ok1(tdb);
+
+ /*
+ * All freelist records are just 1 byte key and value. Insert
+ * something that will walk the whole freelist and hit the
+ * circle.
+ */
+ key.dsize = strlen("x");
+ key.dptr = discard_const_p(uint8_t, "x");
+ data.dsize = strlen("too long");
+ data.dptr = discard_const_p(uint8_t, "too long");
+
+ ok1(tdb_store(tdb, key, data, TDB_INSERT) == -1);
+ ok1(tdb_error(tdb) == TDB_ERR_CORRUPT);
+
+ tdb_close(tdb);
+
+ return exit_status();
+}
diff --git a/lib/tdb/test/run-corrupt.c b/lib/tdb/test/run-corrupt.c
new file mode 100644
index 0000000..e6fc751
--- /dev/null
+++ b/lib/tdb/test/run-corrupt.c
@@ -0,0 +1,132 @@
+#include "../common/tdb_private.h"
+#include "../common/io.c"
+#include "../common/tdb.c"
+#include "../common/lock.c"
+#include "../common/freelist.c"
+#include "../common/traverse.c"
+#include "../common/transaction.c"
+#include "../common/error.c"
+#include "../common/open.c"
+#include "../common/check.c"
+#include "../common/hash.c"
+#include "../common/mutex.c"
+#include "tap-interface.h"
+#include <stdlib.h>
+#include "logging.h"
+
+static int check(TDB_DATA key, TDB_DATA data, void *private)
+{
+ unsigned int *sizes = private;
+
+ if (key.dsize > strlen("hello"))
+ return -1;
+ if (memcmp(key.dptr, "hello", key.dsize) != 0)
+ return -1;
+
+ if (data.dsize != strlen("world"))
+ return -1;
+ if (memcmp(data.dptr, "world", data.dsize) != 0)
+ return -1;
+
+ sizes[0] += key.dsize;
+ sizes[1] += data.dsize;
+ return 0;
+}
+
+static void tdb_flip_bit(struct tdb_context *tdb, unsigned int bit)
+{
+ unsigned int off = bit / CHAR_BIT;
+ unsigned char mask = (1 << (bit % CHAR_BIT));
+
+ if (tdb->map_ptr)
+ ((unsigned char *)tdb->map_ptr)[off] ^= mask;
+ else {
+ unsigned char c;
+ if (pread(tdb->fd, &c, 1, off) != 1) {
+ fprintf(stderr, "pread: %s\n", strerror(errno));
+ exit(1);
+ }
+ c ^= mask;
+ if (pwrite(tdb->fd, &c, 1, off) != 1) {
+ fprintf(stderr, "pwrite: %s\n", strerror(errno));
+ exit(1);
+ }
+ }
+}
+
+static void check_test(struct tdb_context *tdb)
+{
+ TDB_DATA key, data;
+ unsigned int i, verifiable, corrupt, sizes[2], dsize, ksize;
+
+ ok1(tdb_check(tdb, NULL, NULL) == 0);
+
+ key.dptr = discard_const_p(uint8_t, "hello");
+ data.dsize = strlen("world");
+ data.dptr = discard_const_p(uint8_t, "world");
+
+ /* Key and data size respectively. */
+ dsize = ksize = 0;
+
+ /* 5 keys in hash size 2 means we'll have multichains. */
+ for (key.dsize = 1; key.dsize <= 5; key.dsize++) {
+ ksize += key.dsize;
+ dsize += data.dsize;
+ if (tdb_store(tdb, key, data, TDB_INSERT) != 0)
+ abort();
+ }
+
+ /* This is how many bytes we expect to be verifiable. */
+ /* From the file header. */
+ verifiable = strlen(TDB_MAGIC_FOOD) + 1
+ + 2 * sizeof(uint32_t) + 2 * sizeof(tdb_off_t)
+ + 2 * sizeof(uint32_t);
+ /* From the free list chain and hash chains. */
+ verifiable += 3 * sizeof(tdb_off_t);
+ /* From the record headers & tailer */
+ verifiable += 5 * (sizeof(struct tdb_record) + sizeof(uint32_t));
+ /* The free block: we ignore datalen, keylen, full_hash. */
+ verifiable += sizeof(struct tdb_record) - 3*sizeof(uint32_t) +
+ sizeof(uint32_t);
+ /* Our check function verifies the key and data. */
+ verifiable += ksize + dsize;
+
+ /* Flip one bit at a time, make sure it detects verifiable bytes. */
+ for (i = 0, corrupt = 0; i < tdb->map_size * CHAR_BIT; i++) {
+ tdb_flip_bit(tdb, i);
+ memset(sizes, 0, sizeof(sizes));
+ if (tdb_check(tdb, check, sizes) != 0)
+ corrupt++;
+ else if (sizes[0] != ksize || sizes[1] != dsize)
+ corrupt++;
+ tdb_flip_bit(tdb, i);
+ }
+ ok(corrupt == verifiable * CHAR_BIT, "corrupt %u should be %u",
+ corrupt, verifiable * CHAR_BIT);
+}
+
+int main(int argc, char *argv[])
+{
+ struct tdb_context *tdb;
+
+ plan_tests(4);
+ /* This should use mmap. */
+ tdb = tdb_open_ex("run-corrupt.tdb", 2, TDB_CLEAR_IF_FIRST,
+ O_CREAT|O_TRUNC|O_RDWR, 0600, &taplogctx, NULL);
+
+ if (!tdb)
+ abort();
+ check_test(tdb);
+ tdb_close(tdb);
+
+ /* This should not. */
+ tdb = tdb_open_ex("run-corrupt.tdb", 2, TDB_CLEAR_IF_FIRST|TDB_NOMMAP,
+ O_CREAT|O_TRUNC|O_RDWR, 0600, &taplogctx, NULL);
+
+ if (!tdb)
+ abort();
+ check_test(tdb);
+ tdb_close(tdb);
+
+ return exit_status();
+}
diff --git a/lib/tdb/test/run-die-during-transaction.c b/lib/tdb/test/run-die-during-transaction.c
new file mode 100644
index 0000000..c636d87
--- /dev/null
+++ b/lib/tdb/test/run-die-during-transaction.c
@@ -0,0 +1,232 @@
+#include "../common/tdb_private.h"
+#include "lock-tracking.h"
+static ssize_t pwrite_check(int fd, const void *buf, size_t count, off_t offset);
+static ssize_t write_check(int fd, const void *buf, size_t count);
+static int ftruncate_check(int fd, off_t length);
+
+#define pwrite pwrite_check
+#define write write_check
+#define fcntl fcntl_with_lockcheck
+#define ftruncate ftruncate_check
+
+#include "../common/io.c"
+#include "../common/tdb.c"
+#include "../common/lock.c"
+#include "../common/freelist.c"
+#include "../common/traverse.c"
+#include "../common/transaction.c"
+#include "../common/error.c"
+#include "../common/open.c"
+#include "../common/check.c"
+#include "../common/hash.c"
+#include "../common/mutex.c"
+#include "tap-interface.h"
+#include <stdlib.h>
+#include <stdbool.h>
+#include <stdarg.h>
+#include <setjmp.h>
+#include "external-agent.h"
+#include "logging.h"
+
+#undef write
+#undef pwrite
+#undef fcntl
+#undef ftruncate
+
+static bool in_transaction;
+static int target, current;
+static jmp_buf jmpbuf;
+#define TEST_DBNAME "run-die-during-transaction.tdb"
+#define KEY_STRING "helloworld"
+
+static void maybe_die(int fd)
+{
+ if (in_transaction && current++ == target) {
+ longjmp(jmpbuf, 1);
+ }
+}
+
+static ssize_t pwrite_check(int fd,
+ const void *buf, size_t count, off_t offset)
+{
+ ssize_t ret;
+
+ maybe_die(fd);
+
+ ret = pwrite(fd, buf, count, offset);
+ if (ret != count)
+ return ret;
+
+ maybe_die(fd);
+ return ret;
+}
+
+static ssize_t write_check(int fd, const void *buf, size_t count)
+{
+ ssize_t ret;
+
+ maybe_die(fd);
+
+ ret = write(fd, buf, count);
+ if (ret != count)
+ return ret;
+
+ maybe_die(fd);
+ return ret;
+}
+
+static int ftruncate_check(int fd, off_t length)
+{
+ int ret;
+
+ maybe_die(fd);
+
+ ret = ftruncate(fd, length);
+
+ maybe_die(fd);
+ return ret;
+}
+
+static bool test_death(enum operation op, struct agent *agent)
+{
+ struct tdb_context *tdb = NULL;
+ TDB_DATA key;
+ enum agent_return ret;
+ int needed_recovery = 0;
+
+ current = target = 0;
+reset:
+ unlink(TEST_DBNAME);
+ tdb = tdb_open_ex(TEST_DBNAME, 1024, TDB_NOMMAP,
+ O_CREAT|O_TRUNC|O_RDWR, 0600, &taplogctx, NULL);
+
+ if (setjmp(jmpbuf) != 0) {
+ /* We're partway through. Simulate our death. */
+ close(tdb->fd);
+ forget_locking();
+ in_transaction = false;
+
+ ret = external_agent_operation(agent, NEEDS_RECOVERY, "");
+ if (ret == SUCCESS)
+ needed_recovery++;
+ else if (ret != FAILED) {
+ diag("Step %u agent NEEDS_RECOVERY = %s", current,
+ agent_return_name(ret));
+ return false;
+ }
+
+ ret = external_agent_operation(agent, op, KEY_STRING);
+ if (ret != SUCCESS) {
+ diag("Step %u op %s failed = %s", current,
+ operation_name(op),
+ agent_return_name(ret));
+ return false;
+ }
+
+ ret = external_agent_operation(agent, NEEDS_RECOVERY, "");
+ if (ret != FAILED) {
+ diag("Still needs recovery after step %u = %s",
+ current, agent_return_name(ret));
+ return false;
+ }
+
+ ret = external_agent_operation(agent, CHECK, "");
+ if (ret != SUCCESS) {
+ diag("Step %u check failed = %s", current,
+ agent_return_name(ret));
+ return false;
+ }
+
+ ret = external_agent_operation(agent, CLOSE, "");
+ if (ret != SUCCESS) {
+ diag("Step %u close failed = %s", current,
+ agent_return_name(ret));
+ return false;
+ }
+
+ /* Suppress logging as this tries to use closed fd. */
+ suppress_logging = true;
+ suppress_lockcheck = true;
+ tdb_close(tdb);
+ suppress_logging = false;
+ suppress_lockcheck = false;
+ target++;
+ current = 0;
+ goto reset;
+ }
+
+ /* Put key for agent to fetch. */
+ key.dsize = strlen(KEY_STRING);
+ key.dptr = discard_const_p(uint8_t, KEY_STRING);
+ if (tdb_store(tdb, key, key, TDB_INSERT) != 0)
+ return false;
+
+ /* This is the key we insert in transaction. */
+ key.dsize--;
+
+ ret = external_agent_operation(agent, OPEN, TEST_DBNAME);
+ if (ret != SUCCESS) {
+ fprintf(stderr, "Agent failed to open: %s\n",
+ agent_return_name(ret));
+ exit(1);
+ }
+
+ ret = external_agent_operation(agent, FETCH, KEY_STRING);
+ if (ret != SUCCESS) {
+ fprintf(stderr, "Agent failed find key: %s\n",
+ agent_return_name(ret));
+ exit(1);
+ }
+
+ in_transaction = true;
+ if (tdb_transaction_start(tdb) != 0)
+ return false;
+
+ if (tdb_store(tdb, key, key, TDB_INSERT) != 0)
+ return false;
+
+ if (tdb_transaction_commit(tdb) != 0)
+ return false;
+
+ in_transaction = false;
+
+ /* We made it! */
+ diag("Completed %u runs", current);
+ tdb_close(tdb);
+ ret = external_agent_operation(agent, CLOSE, "");
+ if (ret != SUCCESS) {
+ diag("Step %u close failed = %s", current,
+ agent_return_name(ret));
+ return false;
+ }
+
+#ifdef HAVE_INCOHERENT_MMAP
+ /* This means we always mmap, which makes this test a noop. */
+ ok1(1);
+#else
+ ok1(needed_recovery);
+#endif
+ ok1(locking_errors == 0);
+ ok1(forget_locking() == 0);
+ locking_errors = 0;
+ return true;
+}
+
+int main(int argc, char *argv[])
+{
+ enum operation ops[] = { FETCH, STORE, TRANSACTION_START };
+ struct agent *agent;
+ int i;
+
+ plan_tests(12);
+ unlock_callback = maybe_die;
+
+ agent = prepare_external_agent();
+
+ for (i = 0; i < sizeof(ops)/sizeof(ops[0]); i++) {
+ diag("Testing %s after death", operation_name(ops[i]));
+ ok1(test_death(ops[i], agent));
+ }
+
+ return exit_status();
+}
diff --git a/lib/tdb/test/run-endian.c b/lib/tdb/test/run-endian.c
new file mode 100644
index 0000000..9d4d5f5
--- /dev/null
+++ b/lib/tdb/test/run-endian.c
@@ -0,0 +1,64 @@
+#include "../common/tdb_private.h"
+#include "../common/io.c"
+#include "../common/tdb.c"
+#include "../common/lock.c"
+#include "../common/freelist.c"
+#include "../common/traverse.c"
+#include "../common/transaction.c"
+#include "../common/error.c"
+#include "../common/open.c"
+#include "../common/check.c"
+#include "../common/hash.c"
+#include "../common/mutex.c"
+#include "tap-interface.h"
+#include <stdlib.h>
+#include "logging.h"
+
+int main(int argc, char *argv[])
+{
+ struct tdb_context *tdb;
+ TDB_DATA key, data;
+
+ plan_tests(13);
+ tdb = tdb_open_ex("run-endian.tdb", 1024,
+ TDB_CLEAR_IF_FIRST|TDB_CONVERT,
+ O_CREAT|O_TRUNC|O_RDWR, 0600, &taplogctx, NULL);
+
+ ok1(tdb);
+ key.dsize = strlen("hi");
+ key.dptr = discard_const_p(uint8_t, "hi");
+ data.dsize = strlen("world");
+ data.dptr = discard_const_p(uint8_t, "world");
+
+ ok1(tdb_store(tdb, key, data, TDB_MODIFY) < 0);
+ ok1(tdb_error(tdb) == TDB_ERR_NOEXIST);
+ ok1(tdb_store(tdb, key, data, TDB_INSERT) == 0);
+ ok1(tdb_store(tdb, key, data, TDB_INSERT) < 0);
+ ok1(tdb_error(tdb) == TDB_ERR_EXISTS);
+ ok1(tdb_store(tdb, key, data, TDB_MODIFY) == 0);
+
+ data = tdb_fetch(tdb, key);
+ ok1(data.dsize == strlen("world"));
+ ok1(memcmp(data.dptr, "world", strlen("world")) == 0);
+ free(data.dptr);
+
+ key.dsize++;
+ data = tdb_fetch(tdb, key);
+ ok1(data.dptr == NULL);
+ tdb_close(tdb);
+
+ /* Reopen: should read it */
+ tdb = tdb_open_ex("run-endian.tdb", 1024, 0, O_RDWR, 0,
+ &taplogctx, NULL);
+ ok1(tdb);
+
+ key.dsize = strlen("hi");
+ key.dptr = discard_const_p(uint8_t, "hi");
+ data = tdb_fetch(tdb, key);
+ ok1(data.dsize == strlen("world"));
+ ok1(memcmp(data.dptr, "world", strlen("world")) == 0);
+ free(data.dptr);
+ tdb_close(tdb);
+
+ return exit_status();
+}
diff --git a/lib/tdb/test/run-fcntl-deadlock.c b/lib/tdb/test/run-fcntl-deadlock.c
new file mode 100644
index 0000000..0a328af
--- /dev/null
+++ b/lib/tdb/test/run-fcntl-deadlock.c
@@ -0,0 +1,202 @@
+#include "../common/tdb_private.h"
+#include "../common/io.c"
+#include "../common/tdb.c"
+#include "../common/lock.c"
+#include "../common/freelist.c"
+#include "../common/traverse.c"
+#include "../common/transaction.c"
+#include "../common/error.c"
+#include "../common/open.c"
+#include "../common/check.c"
+#include "../common/hash.c"
+#include "../common/mutex.c"
+#include "replace.h"
+#include "system/filesys.h"
+#include "system/time.h"
+#include <errno.h>
+#include "tap-interface.h"
+
+/*
+ * This tests the low level locking requirement
+ * for the allrecord lock/prepare_commit and traverse_read interaction.
+ *
+ * The pattern with the traverse_read and prepare_commit interaction is
+ * the following:
+ *
+ * 1. transaction_start got the allrecord lock with F_RDLCK.
+ *
+ * 2. the traverse_read code walks the database in a sequence like this
+ * (per chain):
+ * 2.1 chainlock(chainX, F_RDLCK)
+ * 2.2 recordlock(chainX.record1, F_RDLCK)
+ * 2.3 chainunlock(chainX, F_RDLCK)
+ * 2.4 callback(chainX.record1)
+ * 2.5 chainlock(chainX, F_RDLCK)
+ * 2.6 recordunlock(chainX.record1, F_RDLCK)
+ * 2.7 recordlock(chainX.record2, F_RDLCK)
+ * 2.8 chainunlock(chainX, F_RDLCK)
+ * 2.9 callback(chainX.record2)
+ * 2.10 chainlock(chainX, F_RDLCK)
+ * 2.11 recordunlock(chainX.record2, F_RDLCK)
+ * 2.12 chainunlock(chainX, F_RDLCK)
+ * 2.13 goto next chain
+ *
+ * So it has always one record locked in F_RDLCK mode and tries to
+ * get the 2nd one before it releases the first one.
+ *
+ * 3. prepare_commit tries to upgrade the allrecord lock to F_RWLCK
+ * If that happens at the time of 2.4, the operation of
+ * 2.5 may deadlock with the allrecord lock upgrade.
+ * On Linux step 2.5 works in order to make some progress with the
+ * locking, but on solaris it might fail because the kernel
+ * wants to satisfy the 1st lock requester before the 2nd one.
+ *
+ * I think the first step is a standalone test that does this:
+ *
+ * process1: F_RDLCK for ofs=0 len=2
+ * process2: F_RDLCK for ofs=0 len=1
+ * process1: upgrade ofs=0 len=2 to F_RWLCK (in blocking mode)
+ * process2: F_RDLCK for ofs=1 len=1
+ * process2: unlock ofs=0 len=2
+ * process1: should continue at that point
+ *
+ * Such a test follows here...
+ */
+
+static int raw_fcntl_lock(int fd, int rw, off_t off, off_t len, bool waitflag)
+{
+ struct flock fl;
+ int cmd;
+ fl.l_type = rw;
+ fl.l_whence = SEEK_SET;
+ fl.l_start = off;
+ fl.l_len = len;
+ fl.l_pid = 0;
+
+ cmd = waitflag ? F_SETLKW : F_SETLK;
+
+ return fcntl(fd, cmd, &fl);
+}
+
+static int raw_fcntl_unlock(int fd, off_t off, off_t len)
+{
+ struct flock fl;
+ fl.l_type = F_UNLCK;
+ fl.l_whence = SEEK_SET;
+ fl.l_start = off;
+ fl.l_len = len;
+ fl.l_pid = 0;
+
+ return fcntl(fd, F_SETLKW, &fl);
+}
+
+
+int pipe_r;
+int pipe_w;
+char buf[2];
+
+static void expect_char(char c)
+{
+ read(pipe_r, buf, 1);
+ if (*buf != c) {
+ fail("We were expecting %c, but got %c", c, buf[0]);
+ }
+}
+
+static void send_char(char c)
+{
+ write(pipe_w, &c, 1);
+}
+
+
+int main(int argc, char *argv[])
+{
+ int process;
+ int fd;
+ const char *filename = "run-fcntl-deadlock.lck";
+ int pid;
+ int pipes_1_2[2];
+ int pipes_2_1[2];
+ int ret;
+
+ pipe(pipes_1_2);
+ pipe(pipes_2_1);
+ fd = open(filename, O_RDWR | O_CREAT, 0755);
+
+ pid = fork();
+ if (pid == 0) {
+ pipe_r = pipes_1_2[0];
+ pipe_w = pipes_2_1[1];
+ process = 2;
+ alarm(15);
+ } else {
+ pipe_r = pipes_2_1[0];
+ pipe_w = pipes_1_2[1];
+ process = 1;
+ alarm(15);
+ }
+
+ /* a: process1: F_RDLCK for ofs=0 len=2 */
+ if (process == 1) {
+ ret = raw_fcntl_lock(fd, F_RDLCK, 0, 2, true);
+ ok(ret == 0,
+ "process 1 lock ofs=0 len=2: %d - %s",
+ ret, strerror(errno));
+ diag("process 1 took read lock on range 0,2");
+ send_char('a');
+ }
+
+ /* process2: F_RDLCK for ofs=0 len=1 */
+ if (process == 2) {
+ expect_char('a');
+ ret = raw_fcntl_lock(fd, F_RDLCK, 0, 1, true);
+ ok(ret == 0,
+ "process 2 lock ofs=0 len=1: %d - %s",
+ ret, strerror(errno));;
+ diag("process 2 took read lock on range 0,1");
+ send_char('b');
+ }
+
+ /* process1: upgrade ofs=0 len=2 to F_RWLCK (in blocking mode) */
+ if (process == 1) {
+ expect_char('b');
+ send_char('c');
+ diag("process 1 starts upgrade on range 0,2");
+ ret = raw_fcntl_lock(fd, F_WRLCK, 0, 2, true);
+ ok(ret == 0,
+ "process 1 RW lock ofs=0 len=2: %d - %s",
+ ret, strerror(errno));
+ diag("process 1 got read upgrade done");
+ /* at this point process 1 is blocked on 2 releasing the
+ read lock */
+ }
+
+ /*
+ * process2: F_RDLCK for ofs=1 len=1
+ * process2: unlock ofs=0 len=2
+ */
+ if (process == 2) {
+ expect_char('c'); /* we know process 1 is *about* to lock */
+ sleep(1);
+ ret = raw_fcntl_lock(fd, F_RDLCK, 1, 1, true);
+ ok(ret == 0,
+ "process 2 lock ofs=1 len=1: %d - %s",
+ ret, strerror(errno));
+ diag("process 2 got read lock on 1,1\n");
+ ret = raw_fcntl_unlock(fd, 0, 2);
+ ok(ret == 0,
+ "process 2 unlock ofs=0 len=2: %d - %s",
+ ret, strerror(errno));
+ diag("process 2 released read lock on 0,2\n");
+ sleep(1);
+ send_char('d');
+ }
+
+ if (process == 1) {
+ expect_char('d');
+ }
+
+ diag("process %d has got to the end\n", process);
+
+ return 0;
+}
diff --git a/lib/tdb/test/run-incompatible.c b/lib/tdb/test/run-incompatible.c
new file mode 100644
index 0000000..5f1b586
--- /dev/null
+++ b/lib/tdb/test/run-incompatible.c
@@ -0,0 +1,188 @@
+#include "../common/tdb_private.h"
+#include "../common/io.c"
+#include "../common/tdb.c"
+#include "../common/lock.c"
+#include "../common/freelist.c"
+#include "../common/traverse.c"
+#include "../common/transaction.c"
+#include "../common/error.c"
+#include "../common/open.c"
+#include "../common/check.c"
+#include "../common/hash.c"
+#include "../common/mutex.c"
+#include "tap-interface.h"
+#include <stdlib.h>
+
+static unsigned int tdb_dumb_hash(TDB_DATA *key)
+{
+ return key->dsize;
+}
+
+static void log_fn(struct tdb_context *tdb, enum tdb_debug_level level, const char *fmt, ...)
+{
+ unsigned int *count = tdb_get_logging_private(tdb);
+ if (strstr(fmt, "hash"))
+ (*count)++;
+}
+
+static unsigned int hdr_rwlocks(const char *fname)
+{
+ struct tdb_header hdr;
+ ssize_t nread;
+
+ int fd = open(fname, O_RDONLY);
+ if (fd == -1)
+ return -1;
+
+ nread = read(fd, &hdr, sizeof(hdr));
+ close(fd);
+ if (nread != sizeof(hdr)) {
+ return -1;
+ }
+ return hdr.rwlocks;
+}
+
+int main(int argc, char *argv[])
+{
+ struct tdb_context *tdb;
+ unsigned int log_count, flags;
+ TDB_DATA d, r;
+ struct tdb_logging_context log_ctx = { log_fn, &log_count };
+
+ plan_tests(38 * 2);
+
+ for (flags = 0; flags <= TDB_CONVERT; flags += TDB_CONVERT) {
+ unsigned int rwmagic = TDB_HASH_RWLOCK_MAGIC;
+
+ if (flags & TDB_CONVERT)
+ tdb_convert(&rwmagic, sizeof(rwmagic));
+
+ /* Create an old-style hash. */
+ log_count = 0;
+ tdb = tdb_open_ex("run-incompatible.tdb", 0, flags,
+ O_CREAT|O_RDWR|O_TRUNC, 0600, &log_ctx,
+ NULL);
+ ok1(tdb);
+ ok1(log_count == 0);
+ d.dptr = discard_const_p(uint8_t, "Hello");
+ d.dsize = 5;
+ ok1(tdb_store(tdb, d, d, TDB_INSERT) == 0);
+ tdb_close(tdb);
+
+ /* Should not have marked rwlocks field. */
+ ok1(hdr_rwlocks("run-incompatible.tdb") == 0);
+
+ /* We can still open any old-style with incompat flag. */
+ log_count = 0;
+ tdb = tdb_open_ex("run-incompatible.tdb", 0,
+ TDB_INCOMPATIBLE_HASH,
+ O_RDWR, 0600, &log_ctx, NULL);
+ ok1(tdb);
+ ok1(log_count == 0);
+ r = tdb_fetch(tdb, d);
+ ok1(r.dsize == 5);
+ free(r.dptr);
+ ok1(tdb_check(tdb, NULL, NULL) == 0);
+ tdb_close(tdb);
+
+ log_count = 0;
+ tdb = tdb_open_ex("test/jenkins-le-hash.tdb", 0, 0, O_RDONLY,
+ 0, &log_ctx, tdb_jenkins_hash);
+ ok1(tdb);
+ ok1(log_count == 0);
+ ok1(tdb_check(tdb, NULL, NULL) == 0);
+ tdb_close(tdb);
+
+ log_count = 0;
+ tdb = tdb_open_ex("test/jenkins-be-hash.tdb", 0, 0, O_RDONLY,
+ 0, &log_ctx, tdb_jenkins_hash);
+ ok1(tdb);
+ ok1(log_count == 0);
+ ok1(tdb_check(tdb, NULL, NULL) == 0);
+ tdb_close(tdb);
+
+ /* OK, now create with incompatible flag, default hash. */
+ log_count = 0;
+ tdb = tdb_open_ex("run-incompatible.tdb", 0,
+ flags|TDB_INCOMPATIBLE_HASH,
+ O_CREAT|O_RDWR|O_TRUNC, 0600, &log_ctx,
+ NULL);
+ ok1(tdb);
+ ok1(log_count == 0);
+ d.dptr = discard_const_p(uint8_t, "Hello");
+ d.dsize = 5;
+ ok1(tdb_store(tdb, d, d, TDB_INSERT) == 0);
+ tdb_close(tdb);
+
+ /* Should have marked rwlocks field. */
+ ok1(hdr_rwlocks("run-incompatible.tdb") == rwmagic);
+
+ /* Cannot open with old hash. */
+ log_count = 0;
+ tdb = tdb_open_ex("run-incompatible.tdb", 0, 0,
+ O_RDWR, 0600, &log_ctx, tdb_old_hash);
+ ok1(!tdb);
+ ok1(log_count == 1);
+
+ /* Can open with jenkins hash. */
+ log_count = 0;
+ tdb = tdb_open_ex("run-incompatible.tdb", 0, 0,
+ O_RDWR, 0600, &log_ctx, tdb_jenkins_hash);
+ ok1(tdb);
+ ok1(log_count == 0);
+ r = tdb_fetch(tdb, d);
+ ok1(r.dsize == 5);
+ free(r.dptr);
+ ok1(tdb_check(tdb, NULL, NULL) == 0);
+ tdb_close(tdb);
+
+ /* Can open by letting it figure it out itself. */
+ log_count = 0;
+ tdb = tdb_open_ex("run-incompatible.tdb", 0, 0,
+ O_RDWR, 0600, &log_ctx, NULL);
+ ok1(tdb);
+ ok1(log_count == 0);
+ r = tdb_fetch(tdb, d);
+ ok1(r.dsize == 5);
+ free(r.dptr);
+ ok1(tdb_check(tdb, NULL, NULL) == 0);
+ tdb_close(tdb);
+
+ /* We can also use incompatible hash with other hashes. */
+ log_count = 0;
+ tdb = tdb_open_ex("run-incompatible.tdb", 0,
+ flags|TDB_INCOMPATIBLE_HASH,
+ O_CREAT|O_RDWR|O_TRUNC, 0600, &log_ctx,
+ tdb_dumb_hash);
+ ok1(tdb);
+ ok1(log_count == 0);
+ d.dptr = discard_const_p(uint8_t, "Hello");
+ d.dsize = 5;
+ ok1(tdb_store(tdb, d, d, TDB_INSERT) == 0);
+ tdb_close(tdb);
+
+ /* Should have marked rwlocks field. */
+ ok1(hdr_rwlocks("run-incompatible.tdb") == rwmagic);
+
+ /* It should not open if we don't specify. */
+ log_count = 0;
+ tdb = tdb_open_ex("run-incompatible.tdb", 0, 0, O_RDWR, 0,
+ &log_ctx, NULL);
+ ok1(!tdb);
+ ok1(log_count == 1);
+
+ /* Should reopen with correct hash. */
+ log_count = 0;
+ tdb = tdb_open_ex("run-incompatible.tdb", 0, 0, O_RDWR, 0,
+ &log_ctx, tdb_dumb_hash);
+ ok1(tdb);
+ ok1(log_count == 0);
+ r = tdb_fetch(tdb, d);
+ ok1(r.dsize == 5);
+ free(r.dptr);
+ ok1(tdb_check(tdb, NULL, NULL) == 0);
+ tdb_close(tdb);
+ }
+
+ return exit_status();
+}
diff --git a/lib/tdb/test/run-marklock-deadlock.c b/lib/tdb/test/run-marklock-deadlock.c
new file mode 100644
index 0000000..37e959f
--- /dev/null
+++ b/lib/tdb/test/run-marklock-deadlock.c
@@ -0,0 +1,278 @@
+#include "../common/tdb_private.h"
+#include "../common/io.c"
+#include "../common/tdb.c"
+#include "../common/lock.c"
+#include "../common/freelist.c"
+#include "../common/traverse.c"
+#include "../common/transaction.c"
+#include "../common/error.c"
+#include "../common/open.c"
+#include "../common/check.c"
+#include "../common/hash.c"
+#include "../common/mutex.c"
+#include "tap-interface.h"
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <stdarg.h>
+#include "logging.h"
+
+static TDB_DATA key, data;
+
+static void do_chainlock(const char *name, int tdb_flags, int up, int down)
+{
+ struct tdb_context *tdb;
+ int ret;
+ ssize_t nread, nwritten;
+ char c = 0;
+
+ tdb = tdb_open_ex(name, 3, tdb_flags,
+ O_RDWR|O_CREAT, 0755, &taplogctx, NULL);
+ ok(tdb, "tdb_open_ex should succeed");
+
+ ret = tdb_chainlock(tdb, key);
+ ok(ret == 0, "tdb_chainlock should succeed");
+
+ nwritten = write(up, &c, sizeof(c));
+ ok(nwritten == sizeof(c), "write should succeed");
+
+ nread = read(down, &c, sizeof(c));
+ ok(nread == sizeof(c), "read should succeed");
+
+ exit(0);
+}
+
+static void do_allrecord_lock(const char *name, int tdb_flags, int up, int down)
+{
+ struct tdb_context *tdb;
+ int ret;
+ ssize_t nread, nwritten;
+ char c = 0;
+
+ tdb = tdb_open_ex(name, 3, tdb_flags,
+ O_RDWR|O_CREAT, 0755, &taplogctx, NULL);
+ ok(tdb, "tdb_open_ex should succeed");
+
+ ret = tdb_allrecord_lock(tdb, F_WRLCK, TDB_LOCK_WAIT, false);
+ ok(ret == 0, "tdb_allrecord_lock should succeed");
+
+ nwritten = write(up, &c, sizeof(c));
+ ok(nwritten == sizeof(c), "write should succeed");
+
+ nread = read(down, &c, sizeof(c));
+ ok(nread == sizeof(c), "read should succeed");
+
+ exit(0);
+}
+
+/* The code should barf on TDBs created with rwlocks. */
+static int do_tests(const char *name, int tdb_flags)
+{
+ struct tdb_context *tdb;
+ int ret;
+ pid_t chainlock_child, allrecord_child;
+ int chainlock_down[2];
+ int chainlock_up[2];
+ int allrecord_down[2];
+ int allrecord_up[2];
+ char c;
+ ssize_t nread, nwritten;
+
+ key.dsize = strlen("hi");
+ key.dptr = discard_const_p(uint8_t, "hi");
+ data.dsize = strlen("world");
+ data.dptr = discard_const_p(uint8_t, "world");
+
+ ret = pipe(chainlock_down);
+ ok(ret == 0, "pipe should succeed");
+
+ ret = pipe(chainlock_up);
+ ok(ret == 0, "pipe should succeed");
+
+ ret = pipe(allrecord_down);
+ ok(ret == 0, "pipe should succeed");
+
+ ret = pipe(allrecord_up);
+ ok(ret == 0, "pipe should succeed");
+
+ chainlock_child = fork();
+ ok(chainlock_child != -1, "fork should succeed");
+
+ if (chainlock_child == 0) {
+ close(chainlock_up[0]);
+ close(chainlock_down[1]);
+ close(allrecord_up[0]);
+ close(allrecord_up[1]);
+ close(allrecord_down[0]);
+ close(allrecord_down[1]);
+ do_chainlock(name, tdb_flags,
+ chainlock_up[1], chainlock_down[0]);
+ exit(0);
+ }
+ close(chainlock_up[1]);
+ close(chainlock_down[0]);
+
+ nread = read(chainlock_up[0], &c, sizeof(c));
+ ok(nread == sizeof(c), "read should succeed");
+
+ /*
+ * Now we have a process holding a chainlock. Start another process
+ * trying the allrecord lock. This will block.
+ */
+
+ allrecord_child = fork();
+ ok(allrecord_child != -1, "fork should succeed");
+
+ if (allrecord_child == 0) {
+ close(chainlock_up[0]);
+ close(chainlock_up[1]);
+ close(chainlock_down[0]);
+ close(chainlock_down[1]);
+ close(allrecord_up[0]);
+ close(allrecord_down[1]);
+ do_allrecord_lock(name, tdb_flags,
+ allrecord_up[1], allrecord_down[0]);
+ exit(0);
+ }
+ close(allrecord_up[1]);
+ close(allrecord_down[0]);
+
+ poll(NULL, 0, 500);
+
+ tdb = tdb_open_ex(name, 3, tdb_flags,
+ O_RDWR|O_CREAT, 0755, &taplogctx, NULL);
+ ok(tdb, "tdb_open_ex should succeed");
+
+ /*
+ * Someone already holds a chainlock, but we're able to get the
+ * freelist lock.
+ *
+ * The freelist lock/mutex is independent from the allrecord lock/mutex.
+ */
+
+ ret = tdb_chainlock_nonblock(tdb, key);
+ ok(ret == -1, "tdb_chainlock_nonblock should not succeed");
+
+ ret = tdb_lock_nonblock(tdb, -1, F_WRLCK);
+ ok(ret == 0, "tdb_lock_nonblock should succeed");
+
+ ret = tdb_unlock(tdb, -1, F_WRLCK);
+ ok(ret == 0, "tdb_unlock should succeed");
+
+ /*
+ * We have someone else having done the lock for us. Just mark it.
+ */
+
+ ret = tdb_chainlock_mark(tdb, key);
+ ok(ret == 0, "tdb_chainlock_mark should succeed");
+
+ /*
+ * The tdb_store below will block the freelist. In one version of the
+ * mutex patches, the freelist was already blocked here by the
+ * allrecord child, which was waiting for the chainlock child to give
+ * up its chainlock. Make sure that we don't run into this
+ * deadlock. To exercise the deadlock, just comment out the "ok"
+ * line.
+ *
+ * The freelist lock/mutex is independent from the allrecord lock/mutex.
+ */
+
+ ret = tdb_lock_nonblock(tdb, -1, F_WRLCK);
+ ok(ret == 0, "tdb_lock_nonblock should succeed");
+
+ ret = tdb_unlock(tdb, -1, F_WRLCK);
+ ok(ret == 0, "tdb_unlock should succeed");
+
+ ret = tdb_store(tdb, key, data, TDB_INSERT);
+ ok(ret == 0, "tdb_store should succeed");
+
+ ret = tdb_chainlock_unmark(tdb, key);
+ ok(ret == 0, "tdb_chainlock_unmark should succeed");
+
+ nwritten = write(chainlock_down[1], &c, sizeof(c));
+ ok(nwritten == sizeof(c), "write should succeed");
+
+ nread = read(chainlock_up[0], &c, sizeof(c));
+ ok(nread == 0, "read should succeed");
+
+ nread = read(allrecord_up[0], &c, sizeof(c));
+ ok(nread == sizeof(c), "read should succeed");
+
+ /*
+ * Someone already holds the allrecord lock, but we're able to get the
+ * freelist lock.
+ *
+ * The freelist lock/mutex is independent from the allrecord lock/mutex.
+ */
+
+ ret = tdb_chainlock_nonblock(tdb, key);
+ ok(ret == -1, "tdb_chainlock_nonblock should not succeed");
+
+ ret = tdb_lockall_nonblock(tdb);
+ ok(ret == -1, "tdb_lockall_nonblock should not succeed");
+
+ ret = tdb_lock_nonblock(tdb, -1, F_WRLCK);
+ ok(ret == 0, "tdb_lock_nonblock should succeed");
+
+ ret = tdb_unlock(tdb, -1, F_WRLCK);
+ ok(ret == 0, "tdb_unlock should succeed");
+
+ /*
+ * We have someone else having done the lock for us. Just mark it.
+ */
+
+ ret = tdb_lockall_mark(tdb);
+ ok(ret == 0, "tdb_lockall_mark should succeed");
+
+ ret = tdb_lock_nonblock(tdb, -1, F_WRLCK);
+ ok(ret == 0, "tdb_lock_nonblock should succeed");
+
+ ret = tdb_unlock(tdb, -1, F_WRLCK);
+ ok(ret == 0, "tdb_unlock should succeed");
+
+ ret = tdb_store(tdb, key, data, TDB_REPLACE);
+ ok(ret == 0, "tdb_store should succeed");
+
+ ret = tdb_lockall_unmark(tdb);
+ ok(ret == 0, "tdb_lockall_unmark should succeed");
+
+ nwritten = write(allrecord_down[1], &c, sizeof(c));
+ ok(nwritten == sizeof(c), "write should succeed");
+
+ nread = read(allrecord_up[0], &c, sizeof(c));
+ ok(nread == 0, "read should succeed");
+
+ close(chainlock_up[0]);
+ close(chainlock_down[1]);
+ close(allrecord_up[0]);
+ close(allrecord_down[1]);
+ diag("%s tests done", name);
+ return exit_status();
+}
+
+int main(int argc, char *argv[])
+{
+ int ret;
+ bool mutex_support;
+
+ mutex_support = tdb_runtime_check_for_robust_mutexes();
+
+ ret = do_tests("marklock-deadlock-fcntl.tdb",
+ TDB_CLEAR_IF_FIRST |
+ TDB_INCOMPATIBLE_HASH);
+ ok(ret == 0, "marklock-deadlock-fcntl.tdb tests should succeed");
+
+ if (!mutex_support) {
+ skip(1, "No robust mutex support, "
+ "skipping marklock-deadlock-mutex.tdb tests");
+ return exit_status();
+ }
+
+ ret = do_tests("marklock-deadlock-mutex.tdb",
+ TDB_CLEAR_IF_FIRST |
+ TDB_MUTEX_LOCKING |
+ TDB_INCOMPATIBLE_HASH);
+ ok(ret == 0, "marklock-deadlock-mutex.tdb tests should succeed");
+
+ return exit_status();
+}
diff --git a/lib/tdb/test/run-mutex-allrecord-bench.c b/lib/tdb/test/run-mutex-allrecord-bench.c
new file mode 100644
index 0000000..b81e597
--- /dev/null
+++ b/lib/tdb/test/run-mutex-allrecord-bench.c
@@ -0,0 +1,82 @@
+#include "../common/tdb_private.h"
+#include "../common/io.c"
+#include "../common/tdb.c"
+#include "../common/lock.c"
+#include "../common/freelist.c"
+#include "../common/traverse.c"
+#include "../common/transaction.c"
+#include "../common/error.c"
+#include "../common/open.c"
+#include "../common/check.c"
+#include "../common/hash.c"
+#include "../common/mutex.c"
+#include "tap-interface.h"
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <stdarg.h>
+
+static TDB_DATA key, data;
+
+static void log_fn(struct tdb_context *tdb, enum tdb_debug_level level,
+ const char *fmt, ...)
+{
+ va_list ap;
+ va_start(ap, fmt);
+ vfprintf(stderr, fmt, ap);
+ va_end(ap);
+}
+
+static double timeval_elapsed2(const struct timeval *tv1, const struct timeval *tv2)
+{
+ return (tv2->tv_sec - tv1->tv_sec) +
+ (tv2->tv_usec - tv1->tv_usec)*1.0e-6;
+}
+
+static double timeval_elapsed(const struct timeval *tv)
+{
+ struct timeval tv2;
+ gettimeofday(&tv2, NULL);
+ return timeval_elapsed2(tv, &tv2);
+}
+
+/* The code should barf on TDBs created with rwlocks. */
+int main(int argc, char *argv[])
+{
+ struct tdb_context *tdb;
+ unsigned int log_count;
+ struct tdb_logging_context log_ctx = { log_fn, &log_count };
+ int ret;
+ struct timeval start;
+ double elapsed;
+ bool runtime_support;
+
+ runtime_support = tdb_runtime_check_for_robust_mutexes();
+
+ if (!runtime_support) {
+ skip(1, "No robust mutex support");
+ return exit_status();
+ }
+
+ key.dsize = strlen("hi");
+ key.dptr = discard_const_p(uint8_t, "hi");
+ data.dsize = strlen("world");
+ data.dptr = discard_const_p(uint8_t, "world");
+
+ tdb = tdb_open_ex("mutex-allrecord-bench.tdb", 1000000,
+ TDB_INCOMPATIBLE_HASH|
+ TDB_MUTEX_LOCKING|
+ TDB_CLEAR_IF_FIRST,
+ O_RDWR|O_CREAT, 0755, &log_ctx, NULL);
+ ok(tdb, "tdb_open_ex should succeed");
+
+ gettimeofday(&start, NULL);
+ ret = tdb_allrecord_lock(tdb, F_WRLCK, TDB_LOCK_WAIT, false);
+ elapsed = timeval_elapsed(&start);
+
+ ok(ret == 0, "tdb_allrecord_lock should succeed");
+
+ diag("allrecord_lock took %f seconds", elapsed);
+
+ return exit_status();
+}
diff --git a/lib/tdb/test/run-mutex-allrecord-block.c b/lib/tdb/test/run-mutex-allrecord-block.c
new file mode 100644
index 0000000..fcd3b4f
--- /dev/null
+++ b/lib/tdb/test/run-mutex-allrecord-block.c
@@ -0,0 +1,120 @@
+#include "../common/tdb_private.h"
+#include "../common/io.c"
+#include "../common/tdb.c"
+#include "../common/lock.c"
+#include "../common/freelist.c"
+#include "../common/traverse.c"
+#include "../common/transaction.c"
+#include "../common/error.c"
+#include "../common/open.c"
+#include "../common/check.c"
+#include "../common/hash.c"
+#include "../common/mutex.c"
+#include "tap-interface.h"
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <stdarg.h>
+
+static TDB_DATA key, data;
+
+static void log_fn(struct tdb_context *tdb, enum tdb_debug_level level,
+ const char *fmt, ...)
+{
+ va_list ap;
+ va_start(ap, fmt);
+ vfprintf(stderr, fmt, ap);
+ va_end(ap);
+}
+
+static int do_child(int tdb_flags, int to, int from)
+{
+ struct tdb_context *tdb;
+ unsigned int log_count;
+ struct tdb_logging_context log_ctx = { log_fn, &log_count };
+ int ret;
+ char c = 0;
+
+ tdb = tdb_open_ex("mutex-allrecord-block.tdb", 3, tdb_flags,
+ O_RDWR|O_CREAT, 0755, &log_ctx, NULL);
+ ok(tdb, "tdb_open_ex should succeed");
+
+ ret = tdb_allrecord_lock(tdb, F_WRLCK, TDB_LOCK_WAIT, false);
+ ok(ret == 0, "tdb_allrecord_lock should succeed");
+
+ write(to, &c, sizeof(c));
+
+ read(from, &c, sizeof(c));
+
+ ret = tdb_allrecord_unlock(tdb, F_WRLCK, false);
+ ok(ret == 0, "tdb_allrecord_unlock should succeed");
+
+ return 0;
+}
+
+/* The code should barf on TDBs created with rwlocks. */
+int main(int argc, char *argv[])
+{
+ struct tdb_context *tdb;
+ unsigned int log_count;
+ struct tdb_logging_context log_ctx = { log_fn, &log_count };
+ int ret, status;
+ pid_t child, wait_ret;
+ int fromchild[2];
+ int tochild[2];
+ char c;
+ int tdb_flags;
+ bool runtime_support;
+
+ runtime_support = tdb_runtime_check_for_robust_mutexes();
+
+ if (!runtime_support) {
+ skip(1, "No robust mutex support");
+ return exit_status();
+ }
+
+ key.dsize = strlen("hi");
+ key.dptr = discard_const_p(uint8_t, "hi");
+ data.dsize = strlen("world");
+ data.dptr = discard_const_p(uint8_t, "world");
+
+ pipe(fromchild);
+ pipe(tochild);
+
+ tdb_flags = TDB_INCOMPATIBLE_HASH|
+ TDB_MUTEX_LOCKING|
+ TDB_CLEAR_IF_FIRST;
+
+ child = fork();
+ if (child == 0) {
+ close(fromchild[0]);
+ close(tochild[1]);
+ return do_child(tdb_flags, fromchild[1], tochild[0]);
+ }
+ close(fromchild[1]);
+ close(tochild[0]);
+
+ read(fromchild[0], &c, sizeof(c));
+
+ tdb = tdb_open_ex("mutex-allrecord-block.tdb", 0,
+ tdb_flags, O_RDWR|O_CREAT, 0755,
+ &log_ctx, NULL);
+ ok(tdb, "tdb_open_ex should succeed");
+
+ ret = tdb_chainlock_nonblock(tdb, key);
+ ok(ret == -1, "tdb_chainlock_nonblock should not succeed");
+
+ write(tochild[1], &c, sizeof(c));
+
+ ret = tdb_chainlock(tdb, key);
+ ok(ret == 0, "tdb_chainlock should not succeed");
+
+ ret = tdb_chainunlock(tdb, key);
+ ok(ret == 0, "tdb_chainunlock should succeed");
+
+ wait_ret = wait(&status);
+ ok(wait_ret == child, "child should have exited correctly");
+
+ diag("done");
+ return exit_status();
+}
diff --git a/lib/tdb/test/run-mutex-allrecord-trylock.c b/lib/tdb/test/run-mutex-allrecord-trylock.c
new file mode 100644
index 0000000..4b683db
--- /dev/null
+++ b/lib/tdb/test/run-mutex-allrecord-trylock.c
@@ -0,0 +1,113 @@
+#include "../common/tdb_private.h"
+#include "../common/io.c"
+#include "../common/tdb.c"
+#include "../common/lock.c"
+#include "../common/freelist.c"
+#include "../common/traverse.c"
+#include "../common/transaction.c"
+#include "../common/error.c"
+#include "../common/open.c"
+#include "../common/check.c"
+#include "../common/hash.c"
+#include "../common/mutex.c"
+#include "tap-interface.h"
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <stdarg.h>
+
+static TDB_DATA key, data;
+
+static void log_fn(struct tdb_context *tdb, enum tdb_debug_level level,
+ const char *fmt, ...)
+{
+ va_list ap;
+ va_start(ap, fmt);
+ vfprintf(stderr, fmt, ap);
+ va_end(ap);
+}
+
+static int do_child(int tdb_flags, int to, int from)
+{
+ struct tdb_context *tdb;
+ unsigned int log_count;
+ struct tdb_logging_context log_ctx = { log_fn, &log_count };
+ int ret;
+ char c = 0;
+
+ tdb = tdb_open_ex("mutex-allrecord-trylock.tdb", 3, tdb_flags,
+ O_RDWR|O_CREAT, 0755, &log_ctx, NULL);
+ ok(tdb, "tdb_open_ex should succeed");
+
+ ret = tdb_chainlock(tdb, key);
+ ok(ret == 0, "tdb_chainlock should succeed");
+
+ write(to, &c, sizeof(c));
+
+ read(from, &c, sizeof(c));
+
+ ret = tdb_chainunlock(tdb, key);
+ ok(ret == 0, "tdb_chainunlock should succeed");
+
+ return 0;
+}
+
+/* The code should barf on TDBs created with rwlocks. */
+int main(int argc, char *argv[])
+{
+ struct tdb_context *tdb;
+ unsigned int log_count;
+ struct tdb_logging_context log_ctx = { log_fn, &log_count };
+ int ret, status;
+ pid_t child, wait_ret;
+ int fromchild[2];
+ int tochild[2];
+ char c;
+ int tdb_flags;
+ bool runtime_support;
+
+ runtime_support = tdb_runtime_check_for_robust_mutexes();
+
+ if (!runtime_support) {
+ skip(1, "No robust mutex support");
+ return exit_status();
+ }
+
+ key.dsize = strlen("hi");
+ key.dptr = discard_const_p(uint8_t, "hi");
+ data.dsize = strlen("world");
+ data.dptr = discard_const_p(uint8_t, "world");
+
+ pipe(fromchild);
+ pipe(tochild);
+
+ tdb_flags = TDB_INCOMPATIBLE_HASH|
+ TDB_MUTEX_LOCKING|
+ TDB_CLEAR_IF_FIRST;
+
+ child = fork();
+ if (child == 0) {
+ close(fromchild[0]);
+ close(tochild[1]);
+ return do_child(tdb_flags, fromchild[1], tochild[0]);
+ }
+ close(fromchild[1]);
+ close(tochild[0]);
+
+ read(fromchild[0], &c, sizeof(c));
+
+ tdb = tdb_open_ex("mutex-allrecord-trylock.tdb", 0, tdb_flags,
+ O_RDWR|O_CREAT, 0755, &log_ctx, NULL);
+ ok(tdb, "tdb_open_ex should succeed");
+
+ ret = tdb_allrecord_lock(tdb, F_WRLCK, TDB_LOCK_NOWAIT, false);
+ ok(ret == -1, "tdb_allrecord_lock (nowait) should not succeed");
+
+ write(tochild[1], &c, sizeof(c));
+
+ wait_ret = wait(&status);
+ ok(wait_ret == child, "child should have exited correctly");
+
+ diag("done");
+ return exit_status();
+}
diff --git a/lib/tdb/test/run-mutex-die.c b/lib/tdb/test/run-mutex-die.c
new file mode 100644
index 0000000..4b8eac1
--- /dev/null
+++ b/lib/tdb/test/run-mutex-die.c
@@ -0,0 +1,269 @@
+#include "../common/tdb_private.h"
+#include "lock-tracking.h"
+static ssize_t pwrite_check(int fd, const void *buf, size_t count, off_t offset);
+static ssize_t write_check(int fd, const void *buf, size_t count);
+static int ftruncate_check(int fd, off_t length);
+
+#define pwrite pwrite_check
+#define write write_check
+#define fcntl fcntl_with_lockcheck
+#define ftruncate ftruncate_check
+
+#include "../common/io.c"
+#include "../common/tdb.c"
+#include "../common/lock.c"
+#include "../common/freelist.c"
+#include "../common/traverse.c"
+#include "../common/transaction.c"
+#include "../common/error.c"
+#include "../common/open.c"
+#include "../common/check.c"
+#include "../common/hash.c"
+#include "../common/mutex.c"
+#include "tap-interface.h"
+#include <stdlib.h>
+#include <stdbool.h>
+#include <stdarg.h>
+#include "external-agent.h"
+#include "logging.h"
+
+#undef write
+#undef pwrite
+#undef fcntl
+#undef ftruncate
+
+static int target, current;
+#define TEST_DBNAME "run-mutex-die.tdb"
+#define KEY_STRING "helloworld"
+
+static void maybe_die(int fd)
+{
+ if (target == 0) {
+ return;
+ }
+ current += 1;
+ if (current == target) {
+ _exit(1);
+ }
+}
+
+static ssize_t pwrite_check(int fd,
+ const void *buf, size_t count, off_t offset)
+{
+ ssize_t ret;
+
+ maybe_die(fd);
+
+ ret = pwrite(fd, buf, count, offset);
+ if (ret != count)
+ return ret;
+
+ maybe_die(fd);
+ return ret;
+}
+
+static ssize_t write_check(int fd, const void *buf, size_t count)
+{
+ ssize_t ret;
+
+ maybe_die(fd);
+
+ ret = write(fd, buf, count);
+ if (ret != count)
+ return ret;
+
+ maybe_die(fd);
+ return ret;
+}
+
+static int ftruncate_check(int fd, off_t length)
+{
+ int ret;
+
+ maybe_die(fd);
+
+ ret = ftruncate(fd, length);
+
+ maybe_die(fd);
+ return ret;
+}
+
+static enum agent_return flakey_ops(struct agent *a)
+{
+ enum agent_return ret;
+
+ /*
+ * Run in the external agent child
+ */
+
+ ret = external_agent_operation(a, OPEN_WITH_CLEAR_IF_FIRST, TEST_DBNAME);
+ if (ret != SUCCESS) {
+ fprintf(stderr, "Agent failed to open: %s\n",
+ agent_return_name(ret));
+ return ret;
+ }
+ ret = external_agent_operation(a, UNMAP, "");
+ if (ret != SUCCESS) {
+ fprintf(stderr, "Agent failed to unmap: %s\n",
+ agent_return_name(ret));
+ return ret;
+ }
+ ret = external_agent_operation(a, STORE, "xyz");
+ if (ret != SUCCESS) {
+ fprintf(stderr, "Agent failed to store: %s\n",
+ agent_return_name(ret));
+ return ret;
+ }
+ ret = external_agent_operation(a, STORE, KEY_STRING);
+ if (ret != SUCCESS) {
+ fprintf(stderr, "Agent failed store: %s\n",
+ agent_return_name(ret));
+ return ret;
+ }
+ ret = external_agent_operation(a, FETCH, KEY_STRING);
+ if (ret != SUCCESS) {
+ fprintf(stderr, "Agent failed find key: %s\n",
+ agent_return_name(ret));
+ return ret;
+ }
+ ret = external_agent_operation(a, PING, "");
+ if (ret != SUCCESS) {
+ fprintf(stderr, "Agent failed ping: %s\n",
+ agent_return_name(ret));
+ return ret;
+ }
+ return ret;
+}
+
+static bool prep_db(void) {
+ struct tdb_context *tdb;
+ TDB_DATA key;
+ TDB_DATA data;
+
+ key.dptr = discard_const_p(uint8_t, KEY_STRING);
+ key.dsize = strlen((char *)key.dptr);
+ data.dptr = discard_const_p(uint8_t, "foo");
+ data.dsize = strlen((char *)data.dptr);
+
+ unlink(TEST_DBNAME);
+
+ tdb = tdb_open_ex(
+ TEST_DBNAME, 2,
+ TDB_INCOMPATIBLE_HASH|TDB_MUTEX_LOCKING|TDB_CLEAR_IF_FIRST,
+ O_CREAT|O_TRUNC|O_RDWR, 0600, &taplogctx, NULL);
+ if (tdb == NULL) {
+ return false;
+ }
+
+ if (tdb_store(tdb, key, data, TDB_INSERT) != 0) {
+ return false;
+ }
+
+ tdb_close(tdb);
+ tdb = NULL;
+
+ forget_locking();
+
+ return true;
+}
+
+static bool test_db(void) {
+ struct tdb_context *tdb;
+ int ret;
+
+ tdb = tdb_open_ex(
+ TEST_DBNAME, 1024, TDB_INCOMPATIBLE_HASH,
+ O_RDWR, 0600, &taplogctx, NULL);
+
+ if (tdb == NULL) {
+ perror("tdb_open_ex failed");
+ return false;
+ }
+
+ ret = tdb_traverse(tdb, NULL, NULL);
+ if (ret == -1) {
+ perror("traverse failed");
+ goto fail;
+ }
+
+ tdb_close(tdb);
+
+ forget_locking();
+
+ return true;
+
+fail:
+ tdb_close(tdb);
+ return false;
+}
+
+static bool test_one(void)
+{
+ enum agent_return ret;
+
+ ret = AGENT_DIED;
+ target = 19;
+
+ while (ret != SUCCESS) {
+ struct agent *agent;
+
+ {
+ int child_target = target;
+ bool pret;
+ target = 0;
+ pret = prep_db();
+ ok1(pret);
+ target = child_target;
+ }
+
+ agent = prepare_external_agent();
+
+ ret = flakey_ops(agent);
+
+ diag("Agent (target=%d) returns %s",
+ target, agent_return_name(ret));
+
+ if (ret == SUCCESS) {
+ ok((target > 19), "At least one AGENT_DIED expected");
+ } else {
+ ok(ret == AGENT_DIED, "AGENT_DIED expected");
+ }
+
+ shutdown_agent(agent);
+
+ {
+ int child_target = target;
+ bool tret;
+ target = 0;
+ tret = test_db();
+ ok1(tret);
+ target = child_target;
+ }
+
+ target += 1;
+ }
+
+ return true;
+}
+
+int main(int argc, char *argv[])
+{
+ bool ret;
+ bool runtime_support;
+
+ runtime_support = tdb_runtime_check_for_robust_mutexes();
+
+ if (!runtime_support) {
+ skip(1, "No robust mutex support");
+ return exit_status();
+ }
+
+ plan_tests(12);
+ unlock_callback = maybe_die;
+
+ ret = test_one();
+ ok1(ret);
+
+ diag("done");
+ return exit_status();
+}
diff --git a/lib/tdb/test/run-mutex-openflags2.c b/lib/tdb/test/run-mutex-openflags2.c
new file mode 100644
index 0000000..89603e6
--- /dev/null
+++ b/lib/tdb/test/run-mutex-openflags2.c
@@ -0,0 +1,146 @@
+#include "../common/tdb_private.h"
+#include "../common/io.c"
+#include "../common/tdb.c"
+#include "../common/lock.c"
+#include "../common/freelist.c"
+#include "../common/traverse.c"
+#include "../common/transaction.c"
+#include "../common/error.c"
+#include "../common/open.c"
+#include "../common/check.c"
+#include "../common/hash.c"
+#include "../common/mutex.c"
+#include "tap-interface.h"
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <poll.h>
+#include <stdarg.h>
+
+static TDB_DATA key, data;
+
+static void log_void(struct tdb_context *tdb, enum tdb_debug_level level,
+ const char *fmt, ...)
+{
+}
+
+static void log_fn(struct tdb_context *tdb, enum tdb_debug_level level,
+ const char *fmt, ...)
+{
+ va_list ap;
+ va_start(ap, fmt);
+ vfprintf(stderr, fmt, ap);
+ va_end(ap);
+}
+
+static int do_child(int fd)
+{
+ struct tdb_context *tdb;
+ unsigned int log_count;
+ struct tdb_logging_context log_ctx = { log_fn, &log_count };
+ struct tdb_logging_context nolog_ctx = { log_void, NULL };
+ char c;
+
+ read(fd, &c, 1);
+
+ tdb = tdb_open_ex("mutex-openflags2.tdb", 0,
+ TDB_DEFAULT,
+ O_RDWR|O_CREAT, 0755, &nolog_ctx, NULL);
+ ok((tdb == NULL) && (errno == EINVAL), "TDB_DEFAULT without "
+ "TDB_MUTEX_LOCKING should fail with EINVAL - %d", errno);
+
+ tdb = tdb_open_ex("mutex-openflags2.tdb", 0,
+ TDB_CLEAR_IF_FIRST,
+ O_RDWR|O_CREAT, 0755, &nolog_ctx, NULL);
+ ok((tdb == NULL) && (errno == EINVAL), "TDB_CLEAR_IF_FIRST without "
+ "TDB_MUTEX_LOCKING should fail with EINVAL - %d", errno);
+
+ tdb = tdb_open_ex("mutex-openflags2.tdb", 0,
+ TDB_CLEAR_IF_FIRST |
+ TDB_MUTEX_LOCKING |
+ TDB_INTERNAL,
+ O_RDWR|O_CREAT, 0755, &nolog_ctx, NULL);
+ ok((tdb == NULL) && (errno == EINVAL), "TDB_MUTEX_LOCKING with "
+ "TDB_INTERNAL should fail with EINVAL - %d", errno);
+
+ tdb = tdb_open_ex("mutex-openflags2.tdb", 0,
+ TDB_CLEAR_IF_FIRST |
+ TDB_MUTEX_LOCKING |
+ TDB_NOMMAP,
+ O_RDWR|O_CREAT, 0755, &nolog_ctx, NULL);
+ ok((tdb == NULL) && (errno == EINVAL), "TDB_MUTEX_LOCKING with "
+ "TDB_NOMMAP should fail with EINVAL - %d", errno);
+
+ tdb = tdb_open_ex("mutex-openflags2.tdb", 0,
+ TDB_CLEAR_IF_FIRST |
+ TDB_MUTEX_LOCKING,
+ O_RDONLY, 0755, &nolog_ctx, NULL);
+ ok((tdb != NULL), "TDB_MUTEX_LOCKING with "
+ "O_RDONLY should work - %d", errno);
+ tdb_close(tdb);
+
+ tdb = tdb_open_ex("mutex-openflags2.tdb", 0,
+ TDB_CLEAR_IF_FIRST |
+ TDB_MUTEX_LOCKING,
+ O_RDWR|O_CREAT, 0755, &log_ctx, NULL);
+ ok((tdb != NULL), "TDB_MUTEX_LOCKING with TDB_CLEAR_IF_FIRST"
+ "TDB_NOMMAP should work - %d", errno);
+
+ return 0;
+}
+
+/* The code should barf on TDBs created with rwlocks. */
+int main(int argc, char *argv[])
+{
+ struct tdb_context *tdb;
+ unsigned int log_count;
+ struct tdb_logging_context log_ctx = { log_fn, &log_count };
+ struct tdb_logging_context nolog_ctx = { log_void, NULL };
+ int ret, status;
+ pid_t child, wait_ret;
+ int pipefd[2];
+ char c = 0;
+ bool runtime_support;
+
+ runtime_support = tdb_runtime_check_for_robust_mutexes();
+
+ ret = pipe(pipefd);
+ ok1(ret == 0);
+
+ key.dsize = strlen("hi");
+ key.dptr = discard_const_p(uint8_t, "hi");
+ data.dsize = strlen("world");
+ data.dptr = discard_const_p(uint8_t, "world");
+
+ if (!runtime_support) {
+ tdb = tdb_open_ex("mutex-openflags2.tdb", 0,
+ TDB_CLEAR_IF_FIRST|
+ TDB_MUTEX_LOCKING,
+ O_RDWR|O_CREAT, 0755, &nolog_ctx, NULL);
+ ok((tdb == NULL) && (errno == ENOSYS), "TDB_MUTEX_LOCKING without "
+ "runtime support should fail with ENOSYS - %d", errno);
+
+ skip(1, "No robust mutex support");
+ return exit_status();
+ }
+
+ child = fork();
+ if (child == 0) {
+ return do_child(pipefd[0]);
+ }
+
+ tdb = tdb_open_ex("mutex-openflags2.tdb", 0,
+ TDB_CLEAR_IF_FIRST|
+ TDB_MUTEX_LOCKING,
+ O_RDWR|O_CREAT, 0755, &log_ctx, NULL);
+ ok((tdb != NULL), "tdb_open_ex with mutexes should succeed");
+
+ write(pipefd[1], &c, 1);
+
+ wait_ret = wait(&status);
+ ok((wait_ret == child) && (status == 0),
+ "child should have exited correctly");
+
+ diag("done");
+ return exit_status();
+}
diff --git a/lib/tdb/test/run-mutex-transaction1.c b/lib/tdb/test/run-mutex-transaction1.c
new file mode 100644
index 0000000..7b9f7b1
--- /dev/null
+++ b/lib/tdb/test/run-mutex-transaction1.c
@@ -0,0 +1,236 @@
+#include "../common/tdb_private.h"
+#include "../common/io.c"
+#include "../common/tdb.c"
+#include "../common/lock.c"
+#include "../common/freelist.c"
+#include "../common/traverse.c"
+#include "../common/transaction.c"
+#include "../common/error.c"
+#include "../common/open.c"
+#include "../common/check.c"
+#include "../common/hash.c"
+#include "../common/mutex.c"
+#include "tap-interface.h"
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <stdarg.h>
+
+static TDB_DATA key, data;
+
+static void log_fn(struct tdb_context *tdb, enum tdb_debug_level level,
+ const char *fmt, ...)
+{
+ va_list ap;
+ va_start(ap, fmt);
+ vfprintf(stderr, fmt, ap);
+ va_end(ap);
+}
+
+static int do_child(int tdb_flags, int to, int from)
+{
+ struct tdb_context *tdb;
+ unsigned int log_count;
+ struct tdb_logging_context log_ctx = { log_fn, &log_count };
+ int ret;
+ char c = 0;
+
+ tdb = tdb_open_ex("mutex-transaction1.tdb", 3, tdb_flags,
+ O_RDWR|O_CREAT, 0755, &log_ctx, NULL);
+ ok(tdb, "tdb_open_ex should succeed");
+
+ ret = tdb_transaction_start(tdb);
+ ok(ret == 0, "tdb_transaction_start should succeed");
+
+ ret = tdb_store(tdb, key, data, TDB_INSERT);
+ ok(ret == 0, "tdb_store(tdb, key, data, TDB_INSERT) should succeed");
+
+ write(to, &c, sizeof(c));
+ read(from, &c, sizeof(c));
+
+ ret = tdb_transaction_cancel(tdb);
+ ok(ret == 0, "tdb_transaction_cancel should succeed");
+
+ write(to, &c, sizeof(c));
+ read(from, &c, sizeof(c));
+
+ ret = tdb_transaction_start(tdb);
+ ok(ret == 0, "tdb_transaction_start should succeed");
+
+ ret = tdb_store(tdb, key, data, TDB_INSERT);
+ ok(ret == 0, "tdb_store(tdb, key, data, TDB_INSERT) should succeed");
+
+ write(to, &c, sizeof(c));
+ read(from, &c, sizeof(c));
+
+ ret = tdb_transaction_commit(tdb);
+ ok(ret == 0, "tdb_transaction_commit should succeed");
+
+ write(to, &c, sizeof(c));
+ read(from, &c, sizeof(c));
+
+ ret = tdb_transaction_start(tdb);
+ ok(ret == 0, "tdb_transaction_start should succeed");
+
+ ret = tdb_store(tdb, key, key, TDB_REPLACE);
+ ok(ret == 0, "tdb_store(tdb, key, data, TDB_REPLACE) should succeed");
+
+ write(to, &c, sizeof(c));
+ read(from, &c, sizeof(c));
+
+ ret = tdb_transaction_commit(tdb);
+ ok(ret == 0, "tdb_transaction_commit should succeed");
+
+ write(to, &c, sizeof(c));
+ read(from, &c, sizeof(c));
+
+ return 0;
+}
+
+/* The code should barf on TDBs created with rwlocks. */
+int main(int argc, char *argv[])
+{
+ struct tdb_context *tdb;
+ unsigned int log_count;
+ struct tdb_logging_context log_ctx = { log_fn, &log_count };
+ int ret, status;
+ pid_t child, wait_ret;
+ int fromchild[2];
+ int tochild[2];
+ TDB_DATA val;
+ char c;
+ int tdb_flags;
+ bool runtime_support;
+
+ runtime_support = tdb_runtime_check_for_robust_mutexes();
+
+ if (!runtime_support) {
+ skip(1, "No robust mutex support");
+ return exit_status();
+ }
+
+ key.dsize = strlen("hi");
+ key.dptr = discard_const_p(uint8_t, "hi");
+ data.dsize = strlen("world");
+ data.dptr = discard_const_p(uint8_t, "world");
+
+ pipe(fromchild);
+ pipe(tochild);
+
+ tdb_flags = TDB_INCOMPATIBLE_HASH|
+ TDB_MUTEX_LOCKING|
+ TDB_CLEAR_IF_FIRST;
+
+ child = fork();
+ if (child == 0) {
+ close(fromchild[0]);
+ close(tochild[1]);
+ return do_child(tdb_flags, fromchild[1], tochild[0]);
+ }
+ close(fromchild[1]);
+ close(tochild[0]);
+
+ read(fromchild[0], &c, sizeof(c));
+
+ tdb = tdb_open_ex("mutex-transaction1.tdb", 0,
+ tdb_flags, O_RDWR|O_CREAT, 0755,
+ &log_ctx, NULL);
+ ok(tdb, "tdb_open_ex should succeed");
+
+ /*
+ * The child has the transaction running
+ */
+ ret = tdb_transaction_start_nonblock(tdb);
+ ok(ret == -1, "tdb_transaction_start_nonblock not succeed");
+
+ ret = tdb_chainlock_nonblock(tdb, key);
+ ok(ret == -1, "tdb_chainlock_nonblock should not succeed");
+
+ /*
+ * We can still read
+ */
+ ret = tdb_exists(tdb, key);
+ ok(ret == 0, "tdb_exists(tdb, key) should return 0");
+
+ val = tdb_fetch(tdb, key);
+ ok(val.dsize == 0, "tdb_fetch(tdb, key) should return an empty value");
+
+ write(tochild[1], &c, sizeof(c));
+
+ /*
+ * When the child canceled we can start...
+ */
+ ret = tdb_transaction_start(tdb);
+ ok(ret == 0, "tdb_transaction_start should succeed");
+
+ read(fromchild[0], &c, sizeof(c));
+ write(tochild[1], &c, sizeof(c));
+
+ ret = tdb_transaction_cancel(tdb);
+ ok(ret == 0, "tdb_transaction_cancel should succeed");
+
+ /*
+ * When we canceled the child can start and store...
+ */
+ read(fromchild[0], &c, sizeof(c));
+
+ /*
+ * We still see the old values before the child commits...
+ */
+ ret = tdb_exists(tdb, key);
+ ok(ret == 0, "tdb_exists(tdb, key) should return 0");
+
+ val = tdb_fetch(tdb, key);
+ ok(val.dsize == 0, "tdb_fetch(tdb, key) should return an empty value");
+
+ write(tochild[1], &c, sizeof(c));
+ read(fromchild[0], &c, sizeof(c));
+
+ /*
+ * We see the new values after the commit...
+ */
+ ret = tdb_exists(tdb, key);
+ ok(ret == 1, "tdb_exists(tdb, key) should return 1");
+
+ val = tdb_fetch(tdb, key);
+ ok(val.dsize != 0, "tdb_fetch(tdb, key) should return a value");
+ ok(val.dsize == data.dsize, "tdb_fetch(tdb, key) should return a value");
+ ok(memcmp(val.dptr, data.dptr, data.dsize) == 0, "tdb_fetch(tdb, key) should return a value");
+
+ write(tochild[1], &c, sizeof(c));
+ read(fromchild[0], &c, sizeof(c));
+
+ /*
+ * The child started a new transaction and replaces the value,
+ * but we still see the old values before the child commits...
+ */
+ ret = tdb_exists(tdb, key);
+ ok(ret == 1, "tdb_exists(tdb, key) should return 1");
+
+ val = tdb_fetch(tdb, key);
+ ok(val.dsize != 0, "tdb_fetch(tdb, key) should return a value");
+ ok(val.dsize == data.dsize, "tdb_fetch(tdb, key) should return a value");
+ ok(memcmp(val.dptr, data.dptr, data.dsize) == 0, "tdb_fetch(tdb, key) should return a value");
+
+ write(tochild[1], &c, sizeof(c));
+ read(fromchild[0], &c, sizeof(c));
+
+ /*
+ * We see the new values after the commit...
+ */
+ ret = tdb_exists(tdb, key);
+ ok(ret == 1, "tdb_exists(tdb, key) should return 1");
+
+ val = tdb_fetch(tdb, key);
+ ok(val.dsize != 0, "tdb_fetch(tdb, key) should return a value");
+ ok(val.dsize == key.dsize, "tdb_fetch(tdb, key) should return a value");
+ ok(memcmp(val.dptr, key.dptr, key.dsize) == 0, "tdb_fetch(tdb, key) should return a value");
+
+ write(tochild[1], &c, sizeof(c));
+
+ wait_ret = wait(&status);
+ ok(wait_ret == child, "child should have exited correctly");
+
+ diag("done");
+ return exit_status();
+}
diff --git a/lib/tdb/test/run-mutex-trylock.c b/lib/tdb/test/run-mutex-trylock.c
new file mode 100644
index 0000000..c96b635
--- /dev/null
+++ b/lib/tdb/test/run-mutex-trylock.c
@@ -0,0 +1,122 @@
+#include "../common/tdb_private.h"
+#include "../common/io.c"
+#include "../common/tdb.c"
+#include "../common/lock.c"
+#include "../common/freelist.c"
+#include "../common/traverse.c"
+#include "../common/transaction.c"
+#include "../common/error.c"
+#include "../common/open.c"
+#include "../common/check.c"
+#include "../common/hash.c"
+#include "../common/mutex.c"
+#include "tap-interface.h"
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <stdarg.h>
+
+static TDB_DATA key, data;
+
+static void log_fn(struct tdb_context *tdb, enum tdb_debug_level level,
+ const char *fmt, ...)
+{
+ va_list ap;
+ va_start(ap, fmt);
+ vfprintf(stderr, fmt, ap);
+ va_end(ap);
+}
+
+static int do_child(int tdb_flags, int to, int from)
+{
+ struct tdb_context *tdb;
+ unsigned int log_count;
+ struct tdb_logging_context log_ctx = { log_fn, &log_count };
+ int ret;
+ char c = 0;
+
+ tdb = tdb_open_ex("mutex-trylock.tdb", 0, tdb_flags,
+ O_RDWR|O_CREAT, 0755, &log_ctx, NULL);
+ ok(tdb, "tdb_open_ex should succeed");
+
+ ret = tdb_chainlock(tdb, key);
+ ok(ret == 0, "tdb_chainlock should succeed");
+
+ write(to, &c, sizeof(c));
+
+ read(from, &c, sizeof(c));
+
+ ret = tdb_chainunlock(tdb, key);
+ ok(ret == 0, "tdb_chainunlock should succeed");
+
+ write(to, &c, sizeof(c));
+
+ return 0;
+}
+
+/* The code should barf on TDBs created with rwlocks. */
+int main(int argc, char *argv[])
+{
+ struct tdb_context *tdb;
+ unsigned int log_count;
+ struct tdb_logging_context log_ctx = { log_fn, &log_count };
+ int ret, status;
+ pid_t child, wait_ret;
+ int fromchild[2];
+ int tochild[2];
+ char c;
+ int tdb_flags;
+ bool runtime_support;
+
+ runtime_support = tdb_runtime_check_for_robust_mutexes();
+
+ if (!runtime_support) {
+ skip(1, "No robust mutex support");
+ return exit_status();
+ }
+
+ key.dsize = strlen("hi");
+ key.dptr = discard_const_p(uint8_t, "hi");
+ data.dsize = strlen("world");
+ data.dptr = discard_const_p(uint8_t, "world");
+
+ pipe(fromchild);
+ pipe(tochild);
+
+ tdb_flags = TDB_INCOMPATIBLE_HASH|
+ TDB_MUTEX_LOCKING|
+ TDB_CLEAR_IF_FIRST;
+
+ child = fork();
+ if (child == 0) {
+ close(fromchild[0]);
+ close(tochild[1]);
+ return do_child(tdb_flags, fromchild[1], tochild[0]);
+ }
+ close(fromchild[1]);
+ close(tochild[0]);
+
+ read(fromchild[0], &c, sizeof(c));
+
+ tdb = tdb_open_ex("mutex-trylock.tdb", 0, tdb_flags,
+ O_RDWR|O_CREAT, 0755, &log_ctx, NULL);
+ ok(tdb, "tdb_open_ex should succeed");
+
+ ret = tdb_chainlock_nonblock(tdb, key);
+ ok(ret == -1, "tdb_chainlock_nonblock should not succeed");
+
+ write(tochild[1], &c, sizeof(c));
+
+ read(fromchild[0], &c, sizeof(c));
+
+ ret = tdb_chainlock_nonblock(tdb, key);
+ ok(ret == 0, "tdb_chainlock_nonblock should succeed");
+ ret = tdb_chainunlock(tdb, key);
+ ok(ret == 0, "tdb_chainunlock should succeed");
+
+ wait_ret = wait(&status);
+ ok(wait_ret == child, "child should have exited correctly");
+
+ diag("done");
+ return exit_status();
+}
diff --git a/lib/tdb/test/run-mutex1.c b/lib/tdb/test/run-mutex1.c
new file mode 100644
index 0000000..eb75946
--- /dev/null
+++ b/lib/tdb/test/run-mutex1.c
@@ -0,0 +1,138 @@
+#include "../common/tdb_private.h"
+#include "../common/io.c"
+#include "../common/tdb.c"
+#include "../common/lock.c"
+#include "../common/freelist.c"
+#include "../common/traverse.c"
+#include "../common/transaction.c"
+#include "../common/error.c"
+#include "../common/open.c"
+#include "../common/check.c"
+#include "../common/hash.c"
+#include "../common/mutex.c"
+#include "tap-interface.h"
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <stdarg.h>
+
+static TDB_DATA key, data;
+
+static void log_fn(struct tdb_context *tdb, enum tdb_debug_level level,
+ const char *fmt, ...)
+{
+ va_list ap;
+ va_start(ap, fmt);
+ vfprintf(stderr, fmt, ap);
+ va_end(ap);
+}
+
+static int do_child(int tdb_flags, int to, int from)
+{
+ struct tdb_context *tdb;
+ unsigned int log_count;
+ struct tdb_logging_context log_ctx = { log_fn, &log_count };
+ int ret;
+ char c = 0;
+
+ tdb = tdb_open_ex("mutex1.tdb", 0, tdb_flags,
+ O_RDWR|O_CREAT, 0755, &log_ctx, NULL);
+ ok(tdb, "tdb_open_ex should succeed");
+
+ ret = tdb_chainlock(tdb, key);
+ ok(ret == 0, "tdb_chainlock should succeed");
+
+ write(to, &c, sizeof(c));
+ read(from, &c, sizeof(c));
+
+ ret = tdb_chainunlock(tdb, key);
+ ok(ret == 0, "tdb_chainunlock should succeed");
+
+ write(to, &c, sizeof(c));
+ read(from, &c, sizeof(c));
+
+ ret = tdb_allrecord_lock(tdb, F_WRLCK, TDB_LOCK_WAIT, false);
+ ok(ret == 0, "tdb_allrecord_lock should succeed");
+
+ write(to, &c, sizeof(c));
+ read(from, &c, sizeof(c));
+
+ ret = tdb_allrecord_unlock(tdb, F_WRLCK, false);
+ ok(ret == 0, "tdb_allrecord_lock should succeed");
+
+ return 0;
+}
+
+/* The code should barf on TDBs created with rwlocks. */
+int main(int argc, char *argv[])
+{
+ struct tdb_context *tdb;
+ unsigned int log_count;
+ struct tdb_logging_context log_ctx = { log_fn, &log_count };
+ int ret, status;
+ pid_t child, wait_ret;
+ int fromchild[2];
+ int tochild[2];
+ char c;
+ int tdb_flags;
+ bool runtime_support;
+
+ runtime_support = tdb_runtime_check_for_robust_mutexes();
+
+ if (!runtime_support) {
+ skip(1, "No robust mutex support");
+ return exit_status();
+ }
+
+ key.dsize = strlen("hi");
+ key.dptr = discard_const_p(uint8_t, "hi");
+ data.dsize = strlen("world");
+ data.dptr = discard_const_p(uint8_t, "world");
+
+ pipe(fromchild);
+ pipe(tochild);
+
+ tdb_flags = TDB_INCOMPATIBLE_HASH|
+ TDB_MUTEX_LOCKING|
+ TDB_CLEAR_IF_FIRST;
+
+ child = fork();
+ if (child == 0) {
+ close(fromchild[0]);
+ close(tochild[1]);
+ return do_child(tdb_flags, fromchild[1], tochild[0]);
+ }
+ close(fromchild[1]);
+ close(tochild[0]);
+
+ read(fromchild[0], &c, sizeof(c));
+
+ tdb = tdb_open_ex("mutex1.tdb", 0, tdb_flags,
+ O_RDWR|O_CREAT, 0755, &log_ctx, NULL);
+ ok(tdb, "tdb_open_ex should succeed");
+
+ write(tochild[1], &c, sizeof(c));
+ read(fromchild[0], &c, sizeof(c));
+
+ ret = tdb_allrecord_lock(tdb, F_WRLCK, TDB_LOCK_WAIT, false);
+ ok(ret == 0, "tdb_allrecord_lock should succeed");
+
+ ret = tdb_store(tdb, key, data, 0);
+ ok(ret == 0, "tdb_store should succeed");
+
+ ret = tdb_allrecord_unlock(tdb, F_WRLCK, false);
+ ok(ret == 0, "tdb_allrecord_unlock should succeed");
+
+ write(tochild[1], &c, sizeof(c));
+ read(fromchild[0], &c, sizeof(c));
+ write(tochild[1], &c, sizeof(c));
+
+ ret = tdb_delete(tdb, key);
+ ok(ret == 0, "tdb_delete should succeed");
+
+ wait_ret = wait(&status);
+ ok(wait_ret == child, "child should have exited correctly");
+
+ diag("done");
+ return exit_status();
+}
diff --git a/lib/tdb/test/run-nested-transactions.c b/lib/tdb/test/run-nested-transactions.c
new file mode 100644
index 0000000..864adf2
--- /dev/null
+++ b/lib/tdb/test/run-nested-transactions.c
@@ -0,0 +1,79 @@
+#include "../common/tdb_private.h"
+#include "../common/io.c"
+#include "../common/tdb.c"
+#include "../common/lock.c"
+#include "../common/freelist.c"
+#include "../common/traverse.c"
+#include "../common/transaction.c"
+#include "../common/error.c"
+#include "../common/open.c"
+#include "../common/check.c"
+#include "../common/hash.c"
+#include "../common/mutex.c"
+#include "tap-interface.h"
+#include <stdlib.h>
+#include <stdbool.h>
+#include "logging.h"
+
+int main(int argc, char *argv[])
+{
+ struct tdb_context *tdb;
+ TDB_DATA key, data;
+
+ plan_tests(27);
+ key.dsize = strlen("hi");
+ key.dptr = discard_const_p(uint8_t, "hi");
+
+ tdb = tdb_open_ex("run-nested-transactions.tdb",
+ 1024, TDB_CLEAR_IF_FIRST|TDB_DISALLOW_NESTING,
+ O_CREAT|O_TRUNC|O_RDWR, 0600, &taplogctx, NULL);
+ ok1(tdb);
+
+ /* Nesting disallowed. */
+ ok1(tdb_transaction_start(tdb) == 0);
+ data.dptr = discard_const_p(uint8_t, "world");
+ data.dsize = strlen("world");
+ ok1(tdb_store(tdb, key, data, TDB_INSERT) == 0);
+ data = tdb_fetch(tdb, key);
+ ok1(data.dsize == strlen("world"));
+ ok1(memcmp(data.dptr, "world", strlen("world")) == 0);
+ free(data.dptr);
+ ok1(tdb_transaction_start(tdb) != 0);
+ ok1(tdb_error(tdb) == TDB_ERR_NESTING);
+
+ data = tdb_fetch(tdb, key);
+ ok1(data.dsize == strlen("world"));
+ ok1(memcmp(data.dptr, "world", strlen("world")) == 0);
+ free(data.dptr);
+ ok1(tdb_transaction_commit(tdb) == 0);
+ data = tdb_fetch(tdb, key);
+ ok1(data.dsize == strlen("world"));
+ ok1(memcmp(data.dptr, "world", strlen("world")) == 0);
+ free(data.dptr);
+ tdb_close(tdb);
+
+ /* Nesting allowed by default */
+ tdb = tdb_open_ex("run-nested-transactions.tdb",
+ 1024, TDB_DEFAULT, O_RDWR, 0, &taplogctx, NULL);
+ ok1(tdb);
+
+ ok1(tdb_transaction_start(tdb) == 0);
+ ok1(tdb_transaction_start(tdb) == 0);
+ ok1(tdb_delete(tdb, key) == 0);
+ ok1(tdb_transaction_commit(tdb) == 0);
+ ok1(!tdb_exists(tdb, key));
+ ok1(tdb_transaction_cancel(tdb) == 0);
+ /* Surprise! Kills inner "committed" transaction. */
+ ok1(tdb_exists(tdb, key));
+
+ ok1(tdb_transaction_start(tdb) == 0);
+ ok1(tdb_transaction_start(tdb) == 0);
+ ok1(tdb_delete(tdb, key) == 0);
+ ok1(tdb_transaction_commit(tdb) == 0);
+ ok1(!tdb_exists(tdb, key));
+ ok1(tdb_transaction_commit(tdb) == 0);
+ ok1(!tdb_exists(tdb, key));
+ tdb_close(tdb);
+
+ return exit_status();
+}
diff --git a/lib/tdb/test/run-nested-traverse.c b/lib/tdb/test/run-nested-traverse.c
new file mode 100644
index 0000000..aeaa085
--- /dev/null
+++ b/lib/tdb/test/run-nested-traverse.c
@@ -0,0 +1,111 @@
+#include "../common/tdb_private.h"
+#include "lock-tracking.h"
+#define fcntl fcntl_with_lockcheck
+#include "../common/io.c"
+#include "../common/tdb.c"
+#include "../common/lock.c"
+#include "../common/freelist.c"
+#include "../common/traverse.c"
+#include "../common/transaction.c"
+#include "../common/error.c"
+#include "../common/open.c"
+#include "../common/check.c"
+#include "../common/hash.c"
+#include "../common/mutex.c"
+#include "tap-interface.h"
+#undef fcntl
+#include <stdlib.h>
+#include <stdbool.h>
+#include "external-agent.h"
+#include "logging.h"
+
+static struct agent *agent;
+
+static bool correct_key(TDB_DATA key)
+{
+ return key.dsize == strlen("hi")
+ && memcmp(key.dptr, "hi", key.dsize) == 0;
+}
+
+static bool correct_data(TDB_DATA data)
+{
+ return data.dsize == strlen("world")
+ && memcmp(data.dptr, "world", data.dsize) == 0;
+}
+
+static int traverse2(struct tdb_context *tdb, TDB_DATA key, TDB_DATA data,
+ void *p)
+{
+ ok1(correct_key(key));
+ ok1(correct_data(data));
+ return 0;
+}
+
+static int traverse1r(struct tdb_context *tdb, TDB_DATA key, TDB_DATA data,
+ void *p)
+{
+ ok1(correct_key(key));
+ ok1(correct_data(data));
+ ok1(external_agent_operation(agent, TRANSACTION_START, tdb_name(tdb))
+ == SUCCESS);
+ ok1(external_agent_operation(agent, STORE, tdb_name(tdb))
+ == SUCCESS);
+ ok1(external_agent_operation(agent, TRANSACTION_COMMIT, tdb_name(tdb))
+ == WOULD_HAVE_BLOCKED);
+ tdb_traverse(tdb, traverse2, NULL);
+
+ /* That should *not* release the all-records lock! */
+ ok1(external_agent_operation(agent, TRANSACTION_START, tdb_name(tdb))
+ == SUCCESS);
+ ok1(external_agent_operation(agent, STORE, tdb_name(tdb))
+ == SUCCESS);
+ ok1(external_agent_operation(agent, TRANSACTION_COMMIT, tdb_name(tdb))
+ == WOULD_HAVE_BLOCKED);
+ return 0;
+}
+
+static int traverse1w(struct tdb_context *tdb, TDB_DATA key, TDB_DATA data,
+ void *p)
+{
+ ok1(correct_key(key));
+ ok1(correct_data(data));
+ ok1(external_agent_operation(agent, TRANSACTION_START, tdb_name(tdb))
+ == WOULD_HAVE_BLOCKED);
+ tdb_traverse(tdb, traverse2, NULL);
+
+ /* That should *not* release the all-records lock! */
+ ok1(external_agent_operation(agent, TRANSACTION_START, tdb_name(tdb))
+ == WOULD_HAVE_BLOCKED);
+ return 0;
+}
+
+int main(int argc, char *argv[])
+{
+ struct tdb_context *tdb;
+ TDB_DATA key, data;
+
+ plan_tests(17);
+ agent = prepare_external_agent();
+
+ tdb = tdb_open_ex("run-nested-traverse.tdb", 1024, TDB_CLEAR_IF_FIRST,
+ O_CREAT|O_TRUNC|O_RDWR, 0600, &taplogctx, NULL);
+ ok1(tdb);
+
+ ok1(external_agent_operation(agent, OPEN, tdb_name(tdb)) == SUCCESS);
+ ok1(external_agent_operation(agent, TRANSACTION_START, tdb_name(tdb))
+ == SUCCESS);
+ ok1(external_agent_operation(agent, TRANSACTION_COMMIT, tdb_name(tdb))
+ == SUCCESS);
+
+ key.dsize = strlen("hi");
+ key.dptr = discard_const_p(uint8_t, "hi");
+ data.dptr = discard_const_p(uint8_t, "world");
+ data.dsize = strlen("world");
+
+ ok1(tdb_store(tdb, key, data, TDB_INSERT) == 0);
+ tdb_traverse(tdb, traverse1w, NULL);
+ tdb_traverse_read(tdb, traverse1r, NULL);
+ tdb_close(tdb);
+
+ return exit_status();
+}
diff --git a/lib/tdb/test/run-no-lock-during-traverse.c b/lib/tdb/test/run-no-lock-during-traverse.c
new file mode 100644
index 0000000..737a32f
--- /dev/null
+++ b/lib/tdb/test/run-no-lock-during-traverse.c
@@ -0,0 +1,114 @@
+#include "../common/tdb_private.h"
+#include "lock-tracking.h"
+
+#define fcntl fcntl_with_lockcheck
+
+#include "../common/io.c"
+#include "../common/tdb.c"
+#include "../common/lock.c"
+#include "../common/freelist.c"
+#include "../common/traverse.c"
+#include "../common/transaction.c"
+#include "../common/error.c"
+#include "../common/open.c"
+#include "../common/check.c"
+#include "../common/hash.c"
+#include "../common/mutex.c"
+#include "tap-interface.h"
+#include <stdlib.h>
+#include "logging.h"
+
+#undef fcntl
+
+#define NUM_ENTRIES 10
+
+static bool prepare_entries(struct tdb_context *tdb)
+{
+ unsigned int i;
+ TDB_DATA key, data;
+
+ for (i = 0; i < NUM_ENTRIES; i++) {
+ key.dsize = sizeof(i);
+ key.dptr = (void *)&i;
+ data.dsize = strlen("world");
+ data.dptr = discard_const_p(uint8_t, "world");
+
+ if (tdb_store(tdb, key, data, 0) != 0)
+ return false;
+ }
+ return true;
+}
+
+static void delete_entries(struct tdb_context *tdb)
+{
+ unsigned int i;
+ TDB_DATA key;
+
+ for (i = 0; i < NUM_ENTRIES; i++) {
+ key.dsize = sizeof(i);
+ key.dptr = (void *)&i;
+
+ ok1(tdb_delete(tdb, key) == 0);
+ }
+}
+
+/* We don't know how many times this will run. */
+static int delete_other(struct tdb_context *tdb, TDB_DATA key, TDB_DATA data,
+ void *private_data)
+{
+ unsigned int i;
+ memcpy(&i, key.dptr, 4);
+ i = (i + 1) % NUM_ENTRIES;
+ key.dptr = (void *)&i;
+ if (tdb_delete(tdb, key) != 0)
+ (*(int *)private_data)++;
+ return 0;
+}
+
+static int delete_self(struct tdb_context *tdb, TDB_DATA key, TDB_DATA data,
+ void *private_data)
+{
+ ok1(tdb_delete(tdb, key) == 0);
+ return 0;
+}
+
+int main(int argc, char *argv[])
+{
+ struct tdb_context *tdb;
+ int errors = 0;
+
+ plan_tests(41);
+ tdb = tdb_open_ex("run-no-lock-during-traverse.tdb",
+ 1024, TDB_CLEAR_IF_FIRST, O_CREAT|O_TRUNC|O_RDWR,
+ 0600, &taplogctx, NULL);
+
+ ok1(tdb);
+ ok1(prepare_entries(tdb));
+ ok1(locking_errors == 0);
+ ok1(tdb_lockall(tdb) == 0);
+ ok1(locking_errors == 0);
+ tdb_traverse(tdb, delete_other, &errors);
+ ok1(errors == 0);
+ ok1(locking_errors == 0);
+ ok1(tdb_unlockall(tdb) == 0);
+
+ ok1(prepare_entries(tdb));
+ ok1(locking_errors == 0);
+ ok1(tdb_lockall(tdb) == 0);
+ ok1(locking_errors == 0);
+ tdb_traverse(tdb, delete_self, NULL);
+ ok1(locking_errors == 0);
+ ok1(tdb_unlockall(tdb) == 0);
+
+ ok1(prepare_entries(tdb));
+ ok1(locking_errors == 0);
+ ok1(tdb_lockall(tdb) == 0);
+ ok1(locking_errors == 0);
+ delete_entries(tdb);
+ ok1(locking_errors == 0);
+ ok1(tdb_unlockall(tdb) == 0);
+
+ ok1(tdb_close(tdb) == 0);
+
+ return exit_status();
+}
diff --git a/lib/tdb/test/run-oldhash.c b/lib/tdb/test/run-oldhash.c
new file mode 100644
index 0000000..aaee6f6
--- /dev/null
+++ b/lib/tdb/test/run-oldhash.c
@@ -0,0 +1,50 @@
+#include "../common/tdb_private.h"
+#include "../common/io.c"
+#include "../common/tdb.c"
+#include "../common/lock.c"
+#include "../common/freelist.c"
+#include "../common/traverse.c"
+#include "../common/transaction.c"
+#include "../common/error.c"
+#include "../common/open.c"
+#include "../common/check.c"
+#include "../common/hash.c"
+#include "../common/mutex.c"
+#include "tap-interface.h"
+#include <stdlib.h>
+#include "logging.h"
+
+int main(int argc, char *argv[])
+{
+ struct tdb_context *tdb;
+
+ plan_tests(8);
+
+ /* Old format (with zeroes in the hash magic fields) should
+ * open with any hash (since we don't know what hash they used). */
+ tdb = tdb_open_ex("test/old-nohash-le.tdb", 0, 0, O_RDWR, 0,
+ &taplogctx, NULL);
+ ok1(tdb);
+ ok1(tdb_check(tdb, NULL, NULL) == 0);
+ tdb_close(tdb);
+
+ tdb = tdb_open_ex("test/old-nohash-be.tdb", 0, 0, O_RDWR, 0,
+ &taplogctx, NULL);
+ ok1(tdb);
+ ok1(tdb_check(tdb, NULL, NULL) == 0);
+ tdb_close(tdb);
+
+ tdb = tdb_open_ex("test/old-nohash-le.tdb", 0, 0, O_RDWR, 0,
+ &taplogctx, tdb_jenkins_hash);
+ ok1(tdb);
+ ok1(tdb_check(tdb, NULL, NULL) == 0);
+ tdb_close(tdb);
+
+ tdb = tdb_open_ex("test/old-nohash-be.tdb", 0, 0, O_RDWR, 0,
+ &taplogctx, tdb_jenkins_hash);
+ ok1(tdb);
+ ok1(tdb_check(tdb, NULL, NULL) == 0);
+ tdb_close(tdb);
+
+ return exit_status();
+}
diff --git a/lib/tdb/test/run-open-during-transaction.c b/lib/tdb/test/run-open-during-transaction.c
new file mode 100644
index 0000000..9a6c6c1
--- /dev/null
+++ b/lib/tdb/test/run-open-during-transaction.c
@@ -0,0 +1,183 @@
+#include "../common/tdb_private.h"
+#include "lock-tracking.h"
+
+static ssize_t pwrite_check(int fd, const void *buf, size_t count, off_t offset);
+static ssize_t write_check(int fd, const void *buf, size_t count);
+static int ftruncate_check(int fd, off_t length);
+
+#define pwrite pwrite_check
+#define write write_check
+#define fcntl fcntl_with_lockcheck
+#define ftruncate ftruncate_check
+
+#include "../common/io.c"
+#include "../common/tdb.c"
+#include "../common/lock.c"
+#include "../common/freelist.c"
+#include "../common/traverse.c"
+#include "../common/transaction.c"
+#include "../common/error.c"
+#include "../common/open.c"
+#include "../common/check.c"
+#include "../common/hash.c"
+#include "../common/mutex.c"
+#include "tap-interface.h"
+#include <stdlib.h>
+#include <stdbool.h>
+#include <stdarg.h>
+#include "external-agent.h"
+#include "logging.h"
+
+static struct agent *agent;
+static bool opened;
+static int errors = 0;
+static bool clear_if_first;
+#define TEST_DBNAME "run-open-during-transaction.tdb"
+
+#undef write
+#undef pwrite
+#undef fcntl
+#undef ftruncate
+
+static bool is_same(const char *snapshot, const char *latest, off_t len)
+{
+ unsigned i;
+
+ for (i = 0; i < len; i++) {
+ if (snapshot[i] != latest[i])
+ return false;
+ }
+ return true;
+}
+
+static bool compare_file(int fd, const char *snapshot, off_t snapshot_len)
+{
+ char *contents;
+ bool same;
+
+ /* over-length read serves as length check. */
+ contents = malloc(snapshot_len+1);
+ same = pread(fd, contents, snapshot_len+1, 0) == snapshot_len
+ && is_same(snapshot, contents, snapshot_len);
+ free(contents);
+ return same;
+}
+
+static void check_file_intact(int fd)
+{
+ enum agent_return ret;
+ struct stat st;
+ char *contents;
+
+ fstat(fd, &st);
+ contents = malloc(st.st_size);
+ if (pread(fd, contents, st.st_size, 0) != st.st_size) {
+ diag("Read fail");
+ errors++;
+ free(contents);
+ return;
+ }
+
+ /* Ask agent to open file. */
+ ret = external_agent_operation(agent, clear_if_first ?
+ OPEN_WITH_CLEAR_IF_FIRST :
+ OPEN,
+ TEST_DBNAME);
+
+ /* It's OK to open it, but it must not have changed! */
+ if (!compare_file(fd, contents, st.st_size)) {
+ diag("Agent changed file after opening %s",
+ agent_return_name(ret));
+ errors++;
+ }
+
+ if (ret == SUCCESS) {
+ ret = external_agent_operation(agent, CLOSE, NULL);
+ if (ret != SUCCESS) {
+ diag("Agent failed to close tdb: %s",
+ agent_return_name(ret));
+ errors++;
+ }
+ } else if (ret != WOULD_HAVE_BLOCKED) {
+ diag("Agent opening file gave %s",
+ agent_return_name(ret));
+ errors++;
+ }
+
+ free(contents);
+}
+
+static void after_unlock(int fd)
+{
+ if (opened)
+ check_file_intact(fd);
+}
+
+static ssize_t pwrite_check(int fd,
+ const void *buf, size_t count, off_t offset)
+{
+ if (opened)
+ check_file_intact(fd);
+
+ return pwrite(fd, buf, count, offset);
+}
+
+static ssize_t write_check(int fd, const void *buf, size_t count)
+{
+ if (opened)
+ check_file_intact(fd);
+
+ return write(fd, buf, count);
+}
+
+static int ftruncate_check(int fd, off_t length)
+{
+ if (opened)
+ check_file_intact(fd);
+
+ return ftruncate(fd, length);
+
+}
+
+int main(int argc, char *argv[])
+{
+ const int flags[] = { TDB_DEFAULT,
+ TDB_CLEAR_IF_FIRST,
+ TDB_NOMMAP,
+ TDB_CLEAR_IF_FIRST | TDB_NOMMAP };
+ int i;
+ struct tdb_context *tdb;
+ TDB_DATA key, data;
+
+ plan_tests(20);
+ agent = prepare_external_agent();
+
+ unlock_callback = after_unlock;
+ for (i = 0; i < sizeof(flags)/sizeof(flags[0]); i++) {
+ clear_if_first = (flags[i] & TDB_CLEAR_IF_FIRST);
+ diag("Test with %s and %s",
+ clear_if_first ? "CLEAR" : "DEFAULT",
+ (flags[i] & TDB_NOMMAP) ? "no mmap" : "mmap");
+ unlink(TEST_DBNAME);
+ tdb = tdb_open_ex(TEST_DBNAME, 1024, flags[i],
+ O_CREAT|O_TRUNC|O_RDWR, 0600,
+ &taplogctx, NULL);
+ ok1(tdb);
+
+ opened = true;
+ ok1(tdb_transaction_start(tdb) == 0);
+ key.dsize = strlen("hi");
+ key.dptr = discard_const_p(uint8_t, "hi");
+ data.dptr = discard_const_p(uint8_t, "world");
+ data.dsize = strlen("world");
+
+ ok1(tdb_store(tdb, key, data, TDB_INSERT) == 0);
+ ok1(tdb_transaction_commit(tdb) == 0);
+ ok(!errors, "We had %u open errors", errors);
+
+ opened = false;
+ tdb_close(tdb);
+ }
+
+ return exit_status();
+}
diff --git a/lib/tdb/test/run-rdlock-upgrade.c b/lib/tdb/test/run-rdlock-upgrade.c
new file mode 100644
index 0000000..042001b
--- /dev/null
+++ b/lib/tdb/test/run-rdlock-upgrade.c
@@ -0,0 +1,166 @@
+#include "../common/io.c"
+#include "../common/tdb.c"
+#include "../common/lock.c"
+#include "../common/freelist.c"
+#include "../common/traverse.c"
+#include "../common/transaction.c"
+#include "../common/error.c"
+#include "../common/open.c"
+#include "../common/check.c"
+#include "../common/hash.c"
+#include "../common/mutex.c"
+#include "tap-interface.h"
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <stdarg.h>
+#include "logging.h"
+
+static TDB_DATA key, data;
+
+static void do_chainlock(const char *name, int tdb_flags, int up, int down)
+{
+ struct tdb_context *tdb;
+ int ret;
+ ssize_t nread, nwritten;
+ char c = 0;
+
+ tdb = tdb_open_ex(name, 3, tdb_flags,
+ O_RDWR|O_CREAT, 0755, &taplogctx, NULL);
+ ok(tdb, "tdb_open_ex should succeed");
+
+ ret = tdb_chainlock_read(tdb, key);
+ ok(ret == 0, "tdb_chainlock_read should succeed");
+
+ nwritten = write(up, &c, sizeof(c));
+ ok(nwritten == sizeof(c), "write should succeed");
+
+ nread = read(down, &c, sizeof(c));
+ ok(nread == 0, "read should succeed");
+
+ exit(0);
+}
+
+static void do_trylock(const char *name, int tdb_flags, int up, int down)
+{
+ struct tdb_context *tdb;
+ int ret;
+ ssize_t nread, nwritten;
+ char c = 0;
+
+ tdb = tdb_open_ex(name, 3, tdb_flags,
+ O_RDWR|O_CREAT, 0755, &taplogctx, NULL);
+ ok(tdb, "tdb_open_ex should succeed");
+
+ /*
+ * tdb used to have a bug where with fcntl locks an upgrade
+ * from a readlock to writelock did not check for the
+ * underlying fcntl lock. Mutexes don't distinguish between
+ * readlocks and writelocks, so that bug does not apply here.
+ */
+
+ ret = tdb_chainlock_read(tdb, key);
+ ok(ret == 0, "tdb_chainlock_read should succeed");
+
+ ret = tdb_chainlock_nonblock(tdb, key);
+ ok(ret == -1, "tdb_chainlock_nonblock should fail");
+
+ nwritten = write(up, &c, sizeof(c));
+ ok(nwritten == sizeof(c), "write should succeed");
+
+ nread = read(down, &c, sizeof(c));
+ ok(nread == 0, "read should succeed");
+
+ exit(0);
+}
+
+static int do_tests(const char *name, int tdb_flags)
+{
+ int ret;
+ pid_t chainlock_child, store_child;
+ int chainlock_down[2];
+ int chainlock_up[2];
+ int store_down[2];
+ int store_up[2];
+ char c;
+ ssize_t nread;
+
+ key.dsize = strlen("hi");
+ key.dptr = discard_const_p(uint8_t, "hi");
+ data.dsize = strlen("world");
+ data.dptr = discard_const_p(uint8_t, "world");
+
+ ret = pipe(chainlock_down);
+ ok(ret == 0, "pipe should succeed");
+
+ ret = pipe(chainlock_up);
+ ok(ret == 0, "pipe should succeed");
+
+ ret = pipe(store_down);
+ ok(ret == 0, "pipe should succeed");
+
+ ret = pipe(store_up);
+ ok(ret == 0, "pipe should succeed");
+
+ chainlock_child = fork();
+ ok(chainlock_child != -1, "fork should succeed");
+
+ if (chainlock_child == 0) {
+ close(chainlock_up[0]);
+ close(chainlock_down[1]);
+ close(store_up[0]);
+ close(store_up[1]);
+ close(store_down[0]);
+ close(store_down[1]);
+ do_chainlock(name, tdb_flags,
+ chainlock_up[1], chainlock_down[0]);
+ exit(0);
+ }
+ close(chainlock_up[1]);
+ close(chainlock_down[0]);
+
+ nread = read(chainlock_up[0], &c, sizeof(c));
+ ok(nread == sizeof(c), "read should succeed");
+
+ /*
+ * Now we have a process holding a chain read lock. Start
+ * another process trying to write lock. This should fail.
+ */
+
+ store_child = fork();
+ ok(store_child != -1, "fork should succeed");
+
+ if (store_child == 0) {
+ close(chainlock_up[0]);
+ close(chainlock_down[1]);
+ close(store_up[0]);
+ close(store_down[1]);
+ do_trylock(name, tdb_flags,
+ store_up[1], store_down[0]);
+ exit(0);
+ }
+ close(store_up[1]);
+ close(store_down[0]);
+
+ nread = read(store_up[0], &c, sizeof(c));
+ ok(nread == sizeof(c), "read should succeed");
+
+ close(chainlock_up[0]);
+ close(chainlock_down[1]);
+ close(store_up[0]);
+ close(store_down[1]);
+ diag("%s tests done", name);
+ return exit_status();
+}
+
+int main(int argc, char *argv[])
+{
+ int ret;
+
+ ret = do_tests("rdlock-upgrade.tdb",
+ TDB_CLEAR_IF_FIRST |
+ TDB_INCOMPATIBLE_HASH);
+ ok(ret == 0, "rdlock-upgrade.tdb tests should succeed");
+
+ return exit_status();
+}
diff --git a/lib/tdb/test/run-readonly-check.c b/lib/tdb/test/run-readonly-check.c
new file mode 100644
index 0000000..c5e0f7d
--- /dev/null
+++ b/lib/tdb/test/run-readonly-check.c
@@ -0,0 +1,53 @@
+/* We should be able to tdb_check a O_RDONLY tdb, and we were previously allowed
+ * to tdb_check() inside a transaction (though that's paranoia!). */
+#include "../common/tdb_private.h"
+#include "../common/io.c"
+#include "../common/tdb.c"
+#include "../common/lock.c"
+#include "../common/freelist.c"
+#include "../common/traverse.c"
+#include "../common/transaction.c"
+#include "../common/error.c"
+#include "../common/open.c"
+#include "../common/check.c"
+#include "../common/hash.c"
+#include "../common/mutex.c"
+#include "tap-interface.h"
+#include <stdlib.h>
+#include "logging.h"
+
+int main(int argc, char *argv[])
+{
+ struct tdb_context *tdb;
+ TDB_DATA key, data;
+
+ plan_tests(11);
+ tdb = tdb_open_ex("run-readonly-check.tdb", 1024,
+ TDB_DEFAULT,
+ O_CREAT|O_TRUNC|O_RDWR, 0600, &taplogctx, NULL);
+
+ ok1(tdb);
+ key.dsize = strlen("hi");
+ key.dptr = discard_const_p(uint8_t, "hi");
+ data.dsize = strlen("world");
+ data.dptr = discard_const_p(uint8_t, "world");
+
+ ok1(tdb_store(tdb, key, data, TDB_INSERT) == 0);
+ ok1(tdb_check(tdb, NULL, NULL) == 0);
+
+ /* We are also allowed to do a check inside a transaction. */
+ ok1(tdb_transaction_start(tdb) == 0);
+ ok1(tdb_check(tdb, NULL, NULL) == 0);
+ ok1(tdb_close(tdb) == 0);
+
+ tdb = tdb_open_ex("run-readonly-check.tdb", 1024,
+ TDB_DEFAULT, O_RDONLY, 0, &taplogctx, NULL);
+
+ ok1(tdb);
+ ok1(tdb_store(tdb, key, data, TDB_MODIFY) == -1);
+ ok1(tdb_error(tdb) == TDB_ERR_RDONLY);
+ ok1(tdb_check(tdb, NULL, NULL) == 0);
+ ok1(tdb_close(tdb) == 0);
+
+ return exit_status();
+}
diff --git a/lib/tdb/test/run-rescue-find_entry.c b/lib/tdb/test/run-rescue-find_entry.c
new file mode 100644
index 0000000..5d6f8f7
--- /dev/null
+++ b/lib/tdb/test/run-rescue-find_entry.c
@@ -0,0 +1,51 @@
+#include "../common/tdb_private.h"
+#include "../common/io.c"
+#include "../common/tdb.c"
+#include "../common/lock.c"
+#include "../common/freelist.c"
+#include "../common/traverse.c"
+#include "../common/transaction.c"
+#include "../common/error.c"
+#include "../common/open.c"
+#include "../common/check.c"
+#include "../common/hash.c"
+#include "../common/rescue.c"
+#include "../common/mutex.c"
+#include "tap-interface.h"
+#include <stdlib.h>
+#include "logging.h"
+
+#define NUM 20
+
+/* Binary searches are deceptively simple: easy to screw up! */
+int main(int argc, char *argv[])
+{
+ unsigned int i, j, n;
+ struct found f[NUM+1];
+ struct found_table table;
+
+ /* Set up array for searching. */
+ for (i = 0; i < NUM+1; i++) {
+ f[i].head = i * 3;
+ }
+ table.arr = f;
+
+ for (i = 0; i < NUM; i++) {
+ table.num = i;
+ for (j = 0; j < (i + 2) * 3; j++) {
+ n = find_entry(&table, j);
+ ok1(n <= i);
+
+ /* If we were searching for something too large... */
+ if (j > i*3)
+ ok1(n == i);
+ else {
+ /* It must give us something after j */
+ ok1(f[n].head >= j);
+ ok1(n == 0 || f[n-1].head < j);
+ }
+ }
+ }
+
+ return exit_status();
+}
diff --git a/lib/tdb/test/run-rescue.c b/lib/tdb/test/run-rescue.c
new file mode 100644
index 0000000..e43f53b
--- /dev/null
+++ b/lib/tdb/test/run-rescue.c
@@ -0,0 +1,127 @@
+#include "../common/tdb_private.h"
+#include "../common/io.c"
+#include "../common/tdb.c"
+#include "../common/lock.c"
+#include "../common/freelist.c"
+#include "../common/traverse.c"
+#include "../common/transaction.c"
+#include "../common/error.c"
+#include "../common/open.c"
+#include "../common/check.c"
+#include "../common/hash.c"
+#include "../common/rescue.c"
+#include "../common/mutex.c"
+#include "tap-interface.h"
+#include <stdlib.h>
+#include "logging.h"
+
+struct walk_data {
+ TDB_DATA key;
+ TDB_DATA data;
+ bool fail;
+ unsigned count;
+};
+
+static inline bool tdb_deq(TDB_DATA a, TDB_DATA b)
+{
+ return a.dsize == b.dsize && memcmp(a.dptr, b.dptr, a.dsize) == 0;
+}
+
+static inline TDB_DATA tdb_mkdata(const void *p, size_t len)
+{
+ TDB_DATA d;
+ d.dptr = discard_const_p(uint8_t, p);
+ d.dsize = len;
+ return d;
+}
+
+static void walk(TDB_DATA key, TDB_DATA data, void *_wd)
+{
+ struct walk_data *wd = _wd;
+
+ if (!tdb_deq(key, wd->key)) {
+ wd->fail = true;
+ }
+
+ if (!tdb_deq(data, wd->data)) {
+ wd->fail = true;
+ }
+ wd->count++;
+}
+
+static void count_records(TDB_DATA key, TDB_DATA data, void *_wd)
+{
+ struct walk_data *wd = _wd;
+
+ if (!tdb_deq(key, wd->key) || !tdb_deq(data, wd->data))
+ diag("%.*s::%.*s",
+ (int)key.dsize, key.dptr, (int)data.dsize, data.dptr);
+ wd->count++;
+}
+
+static void log_fn(struct tdb_context *tdb, enum tdb_debug_level level, const char *fmt, ...)
+{
+ unsigned int *count = tdb_get_logging_private(tdb);
+ (*count)++;
+}
+
+int main(int argc, char *argv[])
+{
+ struct tdb_context *tdb;
+ struct walk_data wd;
+ unsigned int i, size, log_count = 0;
+ struct tdb_logging_context log_ctx = { log_fn, &log_count };
+
+ plan_tests(8);
+ tdb = tdb_open_ex("run-rescue.tdb", 1, TDB_CLEAR_IF_FIRST,
+ O_CREAT|O_TRUNC|O_RDWR, 0600, &log_ctx, NULL);
+
+ wd.key.dsize = strlen("hi");
+ wd.key.dptr = discard_const_p(uint8_t, "hi");
+ wd.data.dsize = strlen("world");
+ wd.data.dptr = discard_const_p(uint8_t, "world");
+ wd.count = 0;
+ wd.fail = false;
+
+ ok1(tdb_store(tdb, wd.key, wd.data, TDB_INSERT) == 0);
+
+ ok1(tdb_rescue(tdb, walk, &wd) == 0);
+ ok1(!wd.fail);
+ ok1(wd.count == 1);
+
+ /* Corrupt the database, walk should either get it or not. */
+ size = tdb->map_size;
+ for (i = sizeof(struct tdb_header); i < size; i++) {
+ char c;
+ if (tdb->methods->tdb_read(tdb, i, &c, 1, false) != 0)
+ fail("Reading offset %i", i);
+ if (tdb->methods->tdb_write(tdb, i, "X", 1) != 0)
+ fail("Writing X at offset %i", i);
+
+ wd.count = 0;
+ if (tdb_rescue(tdb, count_records, &wd) != 0) {
+ wd.fail = true;
+ break;
+ }
+ /* Could be 0 or 1. */
+ if (wd.count > 1) {
+ wd.fail = true;
+ break;
+ }
+ if (tdb->methods->tdb_write(tdb, i, &c, 1) != 0)
+ fail("Restoring offset %i", i);
+ }
+ ok1(log_count == 0);
+ ok1(!wd.fail);
+ tdb_close(tdb);
+
+ /* Now try our known-corrupt db. */
+ tdb = tdb_open_ex("test/tdb.corrupt", 1024, 0, O_RDWR, 0,
+ &taplogctx, NULL);
+ wd.count = 0;
+ ok1(tdb_rescue(tdb, count_records, &wd) == 0);
+ ok1(wd.count == 1627);
+ tdb_close(tdb);
+
+ return exit_status();
+}
diff --git a/lib/tdb/test/run-rwlock-check.c b/lib/tdb/test/run-rwlock-check.c
new file mode 100644
index 0000000..2ac9dc3
--- /dev/null
+++ b/lib/tdb/test/run-rwlock-check.c
@@ -0,0 +1,46 @@
+#include "../common/tdb_private.h"
+#include "../common/io.c"
+#include "../common/tdb.c"
+#include "../common/lock.c"
+#include "../common/freelist.c"
+#include "../common/traverse.c"
+#include "../common/transaction.c"
+#include "../common/error.c"
+#include "../common/open.c"
+#include "../common/check.c"
+#include "../common/hash.c"
+#include "../common/mutex.c"
+#include "tap-interface.h"
+#include <stdlib.h>
+
+static void log_fn(struct tdb_context *tdb, enum tdb_debug_level level, const char *fmt, ...)
+{
+ unsigned int *count = tdb_get_logging_private(tdb);
+ if (strstr(fmt, "spinlocks"))
+ (*count)++;
+}
+
+/* The code should barf on TDBs created with rwlocks. */
+int main(int argc, char *argv[])
+{
+ struct tdb_context *tdb;
+ unsigned int log_count;
+ struct tdb_logging_context log_ctx = { log_fn, &log_count };
+
+ plan_tests(4);
+
+ /* We should fail to open rwlock-using tdbs of either endian. */
+ log_count = 0;
+ tdb = tdb_open_ex("test/rwlock-le.tdb", 0, 0, O_RDWR, 0,
+ &log_ctx, NULL);
+ ok1(!tdb);
+ ok1(log_count == 1);
+
+ log_count = 0;
+ tdb = tdb_open_ex("test/rwlock-be.tdb", 0, 0, O_RDWR, 0,
+ &log_ctx, NULL);
+ ok1(!tdb);
+ ok1(log_count == 1);
+
+ return exit_status();
+}
diff --git a/lib/tdb/test/run-summary.c b/lib/tdb/test/run-summary.c
new file mode 100644
index 0000000..8b9a1a0
--- /dev/null
+++ b/lib/tdb/test/run-summary.c
@@ -0,0 +1,65 @@
+#include "../common/tdb_private.h"
+#include "../common/io.c"
+#include "../common/tdb.c"
+#include "../common/lock.c"
+#include "../common/freelist.c"
+#include "../common/traverse.c"
+#include "../common/transaction.c"
+#include "../common/error.c"
+#include "../common/open.c"
+#include "../common/check.c"
+#include "../common/hash.c"
+#include "../common/summary.c"
+#include "../common/mutex.c"
+#include "tap-interface.h"
+#include <stdlib.h>
+
+int main(int argc, char *argv[])
+{
+ unsigned int i, j;
+ struct tdb_context *tdb;
+ int flags[] = { TDB_INTERNAL, TDB_DEFAULT, TDB_NOMMAP,
+ TDB_INTERNAL|TDB_CONVERT, TDB_CONVERT,
+ TDB_NOMMAP|TDB_CONVERT };
+ TDB_DATA key = { (unsigned char *)&j, sizeof(j) };
+ TDB_DATA data = { (unsigned char *)&j, sizeof(j) };
+ char *summary;
+
+ plan_tests(sizeof(flags) / sizeof(flags[0]) * 14);
+ for (i = 0; i < sizeof(flags) / sizeof(flags[0]); i++) {
+ tdb = tdb_open("run-summary.tdb", 131, flags[i],
+ O_RDWR|O_CREAT|O_TRUNC, 0600);
+ ok1(tdb);
+ if (!tdb)
+ continue;
+
+ /* Put some stuff in there. */
+ for (j = 0; j < 500; j++) {
+ /* Make sure padding varies to we get some graphs! */
+ data.dsize = j % (sizeof(j) + 1);
+ if (tdb_store(tdb, key, data, TDB_REPLACE) != 0)
+ fail("Storing in tdb");
+ }
+
+ summary = tdb_summary(tdb);
+ diag("%s", summary);
+ ok1(strstr(summary, "Size of file/data: "));
+ ok1(strstr(summary, "Number of records: 500\n"));
+ ok1(strstr(summary, "Smallest/average/largest keys: 4/4/4\n"));
+ ok1(strstr(summary, "Smallest/average/largest data: 0/2/4\n"));
+ ok1(strstr(summary, "Smallest/average/largest padding: "));
+ ok1(strstr(summary, "Number of dead records: 0\n"));
+ ok1(strstr(summary, "Number of free records: 1\n"));
+ ok1(strstr(summary, "Smallest/average/largest free records: "));
+ ok1(strstr(summary, "Number of hash chains: 131\n"));
+ ok1(strstr(summary, "Smallest/average/largest hash chains: "));
+ ok1(strstr(summary, "Number of uncoalesced records: 0\n"));
+ ok1(strstr(summary, "Smallest/average/largest uncoalesced runs: 0/0/0\n"));
+ ok1(strstr(summary, "Percentage keys/data/padding/free/dead/rechdrs&tailers/hashes: "));
+
+ free(summary);
+ tdb_close(tdb);
+ }
+
+ return exit_status();
+}
diff --git a/lib/tdb/test/run-transaction-expand.c b/lib/tdb/test/run-transaction-expand.c
new file mode 100644
index 0000000..d36b894
--- /dev/null
+++ b/lib/tdb/test/run-transaction-expand.c
@@ -0,0 +1,125 @@
+#include "../common/tdb_private.h"
+
+/* Speed up the tests, but do the actual sync tests. */
+static unsigned int sync_counts = 0;
+static inline int fake_fsync(int fd)
+{
+ sync_counts++;
+ return 0;
+}
+#define fsync fake_fsync
+
+#ifdef MS_SYNC
+static inline int fake_msync(void *addr, size_t length, int flags)
+{
+ sync_counts++;
+ return 0;
+}
+#define msync fake_msync
+#endif
+
+#ifdef HAVE_FDATASYNC
+static inline int fake_fdatasync(int fd)
+{
+ sync_counts++;
+ return 0;
+}
+#define fdatasync fake_fdatasync
+#endif
+
+#include "../common/io.c"
+#include "../common/tdb.c"
+#include "../common/lock.c"
+#include "../common/freelist.c"
+#include "../common/traverse.c"
+#include "../common/transaction.c"
+#include "../common/error.c"
+#include "../common/open.c"
+#include "../common/check.c"
+#include "../common/hash.c"
+#include "../common/mutex.c"
+#include "tap-interface.h"
+#include <stdlib.h>
+#include "logging.h"
+
+static void write_record(struct tdb_context *tdb, size_t extra_len,
+ TDB_DATA *data)
+{
+ TDB_DATA key;
+ key.dsize = strlen("hi");
+ key.dptr = discard_const_p(uint8_t, "hi");
+
+ data->dsize += extra_len;
+ tdb_transaction_start(tdb);
+ tdb_store(tdb, key, *data, TDB_REPLACE);
+ tdb_transaction_commit(tdb);
+}
+
+int main(int argc, char *argv[])
+{
+ struct tdb_context *tdb;
+ size_t i;
+ TDB_DATA data;
+ struct tdb_record rec;
+ tdb_off_t off;
+
+ /* Do *not* suppress sync for this test; we do it ourselves. */
+ unsetenv("TDB_NO_FSYNC");
+
+ plan_tests(5);
+ tdb = tdb_open_ex("run-transaction-expand.tdb",
+ 1024, TDB_CLEAR_IF_FIRST,
+ O_CREAT|O_TRUNC|O_RDWR, 0600, &taplogctx, NULL);
+ ok1(tdb);
+
+ data.dsize = 0;
+ data.dptr = calloc(1000, getpagesize());
+ if (data.dptr == NULL) {
+ diag("Unable to allocate memory for data.dptr");
+ tdb_close(tdb);
+ exit(1);
+ }
+
+ /* Simulate a slowly growing record. */
+ for (i = 0; i < 1000; i++)
+ write_record(tdb, getpagesize(), &data);
+
+ tdb_ofs_read(tdb, TDB_RECOVERY_HEAD, &off);
+ tdb_read(tdb, off, &rec, sizeof(rec), DOCONV());
+ diag("TDB size = %zu, recovery = %llu-%llu",
+ (size_t)tdb->map_size, (unsigned long long)off, (unsigned long long)(off + sizeof(rec) + rec.rec_len));
+
+ /* We should only be about 5 times larger than largest record. */
+ ok1(tdb->map_size < 6 * i * getpagesize());
+ tdb_close(tdb);
+
+ tdb = tdb_open_ex("run-transaction-expand.tdb",
+ 1024, TDB_CLEAR_IF_FIRST,
+ O_CREAT|O_TRUNC|O_RDWR, 0600, &taplogctx, NULL);
+ ok1(tdb);
+
+ data.dsize = 0;
+
+ /* Simulate a slowly growing record, repacking to keep
+ * recovery area at end. */
+ for (i = 0; i < 1000; i++) {
+ write_record(tdb, getpagesize(), &data);
+ if (i % 10 == 0)
+ tdb_repack(tdb);
+ }
+
+ tdb_ofs_read(tdb, TDB_RECOVERY_HEAD, &off);
+ tdb_read(tdb, off, &rec, sizeof(rec), DOCONV());
+ diag("TDB size = %zu, recovery = %llu-%llu",
+ (size_t)tdb->map_size, (unsigned long long)off, (unsigned long long)(off + sizeof(rec) + rec.rec_len));
+
+ /* We should only be about 4 times larger than largest record. */
+ ok1(tdb->map_size < 5 * i * getpagesize());
+
+ /* We should have synchronized multiple times. */
+ ok1(sync_counts);
+ tdb_close(tdb);
+ free(data.dptr);
+
+ return exit_status();
+}
diff --git a/lib/tdb/test/run-traverse-chain.c b/lib/tdb/test/run-traverse-chain.c
new file mode 100644
index 0000000..2a25bec
--- /dev/null
+++ b/lib/tdb/test/run-traverse-chain.c
@@ -0,0 +1,94 @@
+#include "../common/tdb_private.h"
+#include "../common/io.c"
+#include "../common/tdb.c"
+#include "../common/lock.c"
+#include "../common/freelist.c"
+#include "../common/traverse.c"
+#include "../common/transaction.c"
+#include "../common/error.c"
+#include "../common/open.c"
+#include "../common/check.c"
+#include "../common/hash.c"
+#include "../common/mutex.c"
+#include "tap-interface.h"
+#include <stdlib.h>
+#include "logging.h"
+
+static char keystr0[] = "x";
+static TDB_DATA key0 = { .dptr = (uint8_t *)keystr0,
+ .dsize = sizeof(keystr0) };
+static char valuestr0[] = "y";
+static TDB_DATA value0 = { .dptr = (uint8_t *)valuestr0,
+ .dsize = sizeof(valuestr0) };
+
+static char keystr1[] = "aaa";
+static TDB_DATA key1 = { .dptr = (uint8_t *)keystr1,
+ .dsize = sizeof(keystr1) };
+static char valuestr1[] = "bbbbb";
+static TDB_DATA value1 = { .dptr = (uint8_t *)valuestr1,
+ .dsize = sizeof(valuestr1) };
+
+static TDB_DATA *keys[] = { &key0, &key1 };
+static TDB_DATA *values[] = { &value0, &value1 };
+
+static bool tdb_data_same(TDB_DATA d1, TDB_DATA d2)
+{
+ if (d1.dsize != d2.dsize) {
+ return false;
+ }
+ return (memcmp(d1.dptr, d2.dptr, d1.dsize) == 0);
+}
+
+struct traverse_chain_state {
+ size_t idx;
+ bool ok;
+};
+
+static int traverse_chain_fn(struct tdb_context *tdb,
+ TDB_DATA key,
+ TDB_DATA data,
+ void *private_data)
+{
+ struct traverse_chain_state *state = private_data;
+
+ state->ok &= tdb_data_same(key, *keys[state->idx]);
+ state->ok &= tdb_data_same(data, *values[state->idx]);
+ state->idx += 1;
+
+ return 0;
+}
+
+int main(int argc, char *argv[])
+{
+ struct tdb_context *tdb;
+ struct traverse_chain_state state = { .ok = true };
+ int ret;
+
+ plan_tests(4);
+
+ tdb = tdb_open_ex(
+ "traverse_chain.tdb",
+ 1,
+ TDB_CLEAR_IF_FIRST,
+ O_RDWR|O_CREAT,
+ 0600,
+ &taplogctx,
+ NULL);
+ ok1(tdb);
+
+ /* add in reverse order, tdb_store adds to the front of the list */
+ ret = tdb_store(tdb, key1, value1, TDB_INSERT);
+ ok1(ret == 0);
+ ret = tdb_store(tdb, key0, value0, TDB_INSERT);
+ ok1(ret == 0);
+
+ ret = tdb_traverse_key_chain(tdb, key0, traverse_chain_fn, &state);
+ ok1(ret == 2);
+ ok1(state.ok);
+
+ unlink(tdb_name(tdb));
+
+ tdb_close(tdb);
+
+ return exit_status();
+}
diff --git a/lib/tdb/test/run-traverse-in-transaction.c b/lib/tdb/test/run-traverse-in-transaction.c
new file mode 100644
index 0000000..d187b9b
--- /dev/null
+++ b/lib/tdb/test/run-traverse-in-transaction.c
@@ -0,0 +1,90 @@
+#include "lock-tracking.h"
+#include "../common/tdb_private.h"
+#define fcntl fcntl_with_lockcheck
+#include "../common/io.c"
+#include "../common/tdb.c"
+#include "../common/lock.c"
+#include "../common/freelist.c"
+#include "../common/traverse.c"
+#include "../common/transaction.c"
+#include "../common/error.c"
+#include "../common/open.c"
+#include "../common/check.c"
+#include "../common/hash.c"
+#include "../common/mutex.c"
+#include "tap-interface.h"
+#undef fcntl_with_lockcheck
+#include <stdlib.h>
+#include <stdbool.h>
+#include "external-agent.h"
+#include "logging.h"
+
+static struct agent *agent;
+
+static bool correct_key(TDB_DATA key)
+{
+ return key.dsize == strlen("hi")
+ && memcmp(key.dptr, "hi", key.dsize) == 0;
+}
+
+static bool correct_data(TDB_DATA data)
+{
+ return data.dsize == strlen("world")
+ && memcmp(data.dptr, "world", data.dsize) == 0;
+}
+
+static int traverse(struct tdb_context *tdb, TDB_DATA key, TDB_DATA data,
+ void *p)
+{
+ ok1(correct_key(key));
+ ok1(correct_data(data));
+ return 0;
+}
+
+int main(int argc, char *argv[])
+{
+ struct tdb_context *tdb;
+ TDB_DATA key, data;
+
+ plan_tests(13);
+ agent = prepare_external_agent();
+
+ tdb = tdb_open_ex("run-traverse-in-transaction.tdb",
+ 1024, TDB_CLEAR_IF_FIRST, O_CREAT|O_TRUNC|O_RDWR,
+ 0600, &taplogctx, NULL);
+ ok1(tdb);
+
+ key.dsize = strlen("hi");
+ key.dptr = discard_const_p(uint8_t, "hi");
+ data.dptr = discard_const_p(uint8_t, "world");
+ data.dsize = strlen("world");
+
+ ok1(tdb_store(tdb, key, data, TDB_INSERT) == 0);
+
+ ok1(external_agent_operation(agent, OPEN, tdb_name(tdb)) == SUCCESS);
+
+ ok1(tdb_transaction_active(tdb) == 0);
+ ok1(tdb_transaction_start(tdb) == 0);
+ ok1(tdb_transaction_active(tdb) == 1);
+ ok1(external_agent_operation(agent, TRANSACTION_START, tdb_name(tdb))
+ == WOULD_HAVE_BLOCKED);
+ tdb_traverse(tdb, traverse, NULL);
+
+ /* That should *not* release the transaction lock! */
+ ok1(external_agent_operation(agent, TRANSACTION_START, tdb_name(tdb))
+ == WOULD_HAVE_BLOCKED);
+ tdb_traverse_read(tdb, traverse, NULL);
+
+ /* That should *not* release the transaction lock! */
+ ok1(external_agent_operation(agent, TRANSACTION_START, tdb_name(tdb))
+ == WOULD_HAVE_BLOCKED);
+ ok1(tdb_transaction_commit(tdb) == 0);
+ ok1(tdb_transaction_active(tdb) == 0);
+ /* Now we should be fine. */
+ ok1(external_agent_operation(agent, TRANSACTION_START, tdb_name(tdb))
+ == SUCCESS);
+
+ tdb_close(tdb);
+
+ return exit_status();
+}
diff --git a/lib/tdb/test/run-wronghash-fail.c b/lib/tdb/test/run-wronghash-fail.c
new file mode 100644
index 0000000..c44b0f5
--- /dev/null
+++ b/lib/tdb/test/run-wronghash-fail.c
@@ -0,0 +1,121 @@
+#include "../common/tdb_private.h"
+#include "../common/io.c"
+#include "../common/tdb.c"
+#include "../common/lock.c"
+#include "../common/freelist.c"
+#include "../common/traverse.c"
+#include "../common/transaction.c"
+#include "../common/error.c"
+#include "../common/open.c"
+#include "../common/check.c"
+#include "../common/hash.c"
+#include "../common/mutex.c"
+#include "tap-interface.h"
+#include <stdlib.h>
+
+static void log_fn(struct tdb_context *tdb, enum tdb_debug_level level, const char *fmt, ...)
+{
+ unsigned int *count = tdb_get_logging_private(tdb);
+ if (strstr(fmt, "hash"))
+ (*count)++;
+}
+
+int main(int argc, char *argv[])
+{
+ struct tdb_context *tdb;
+ unsigned int log_count;
+ TDB_DATA d;
+ struct tdb_logging_context log_ctx = { log_fn, &log_count };
+
+ plan_tests(28);
+
+ /* Create with default hash. */
+ log_count = 0;
+ tdb = tdb_open_ex("run-wronghash-fail.tdb", 0, 0,
+ O_CREAT|O_RDWR|O_TRUNC, 0600, &log_ctx, NULL);
+ ok1(tdb);
+ ok1(log_count == 0);
+ d.dptr = discard_const_p(uint8_t, "Hello");
+ d.dsize = 5;
+ ok1(tdb_store(tdb, d, d, TDB_INSERT) == 0);
+ tdb_close(tdb);
+
+ /* Fail to open with different hash. */
+ tdb = tdb_open_ex("run-wronghash-fail.tdb", 0, 0, O_RDWR, 0,
+ &log_ctx, tdb_jenkins_hash);
+ ok1(!tdb);
+ ok1(log_count == 1);
+
+ /* Create with different hash. */
+ log_count = 0;
+ tdb = tdb_open_ex("run-wronghash-fail.tdb", 0, 0,
+ O_CREAT|O_RDWR|O_TRUNC,
+ 0600, &log_ctx, tdb_jenkins_hash);
+ ok1(tdb);
+ ok1(log_count == 0);
+ tdb_close(tdb);
+
+ /* Endian should be no problem. */
+ log_count = 0;
+ tdb = tdb_open_ex("test/jenkins-le-hash.tdb", 0, 0, O_RDWR, 0,
+ &log_ctx, tdb_old_hash);
+ ok1(!tdb);
+ ok1(log_count == 1);
+
+ log_count = 0;
+ tdb = tdb_open_ex("test/jenkins-be-hash.tdb", 0, 0, O_RDWR, 0,
+ &log_ctx, tdb_old_hash);
+ ok1(!tdb);
+ ok1(log_count == 1);
+
+ log_count = 0;
+ /* Fail to open with old default hash. */
+ tdb = tdb_open_ex("run-wronghash-fail.tdb", 0, 0, O_RDWR, 0,
+ &log_ctx, tdb_old_hash);
+ ok1(!tdb);
+ ok1(log_count == 1);
+
+ log_count = 0;
+ tdb = tdb_open_ex("test/jenkins-le-hash.tdb", 0, 0, O_RDONLY,
+ 0, &log_ctx, tdb_jenkins_hash);
+ ok1(tdb);
+ ok1(log_count == 0);
+ ok1(tdb_check(tdb, NULL, NULL) == 0);
+ tdb_close(tdb);
+
+ log_count = 0;
+ tdb = tdb_open_ex("test/jenkins-be-hash.tdb", 0, 0, O_RDONLY,
+ 0, &log_ctx, tdb_jenkins_hash);
+ ok1(tdb);
+ ok1(log_count == 0);
+ ok1(tdb_check(tdb, NULL, NULL) == 0);
+ tdb_close(tdb);
+
+ /* It should open with jenkins hash if we don't specify. */
+ log_count = 0;
+ tdb = tdb_open_ex("test/jenkins-le-hash.tdb", 0, 0, O_RDWR, 0,
+ &log_ctx, NULL);
+ ok1(tdb);
+ ok1(log_count == 0);
+ ok1(tdb_check(tdb, NULL, NULL) == 0);
+ tdb_close(tdb);
+
+ log_count = 0;
+ tdb = tdb_open_ex("test/jenkins-be-hash.tdb", 0, 0, O_RDWR, 0,
+ &log_ctx, NULL);
+ ok1(tdb);
+ ok1(log_count == 0);
+ ok1(tdb_check(tdb, NULL, NULL) == 0);
+ tdb_close(tdb);
+
+ log_count = 0;
+ tdb = tdb_open_ex("run-wronghash-fail.tdb", 0, 0, O_RDONLY,
+ 0, &log_ctx, NULL);
+ ok1(tdb);
+ ok1(log_count == 0);
+ ok1(tdb_check(tdb, NULL, NULL) == 0);
+ tdb_close(tdb);
+
+
+ return exit_status();
+}
diff --git a/lib/tdb/test/run-zero-append.c b/lib/tdb/test/run-zero-append.c
new file mode 100644
index 0000000..f9eba1b
--- /dev/null
+++ b/lib/tdb/test/run-zero-append.c
@@ -0,0 +1,41 @@
+#include "../common/tdb_private.h"
+#include "../common/io.c"
+#include "../common/tdb.c"
+#include "../common/lock.c"
+#include "../common/freelist.c"
+#include "../common/traverse.c"
+#include "../common/transaction.c"
+#include "../common/error.c"
+#include "../common/open.c"
+#include "../common/check.c"
+#include "../common/hash.c"
+#include "../common/mutex.c"
+#include "tap-interface.h"
+#include <stdlib.h>
+#include "logging.h"
+
+int main(int argc, char *argv[])
+{
+ struct tdb_context *tdb;
+ TDB_DATA key, data;
+
+ plan_tests(4);
+ tdb = tdb_open_ex(NULL, 1024, TDB_INTERNAL, O_CREAT|O_TRUNC|O_RDWR,
+ 0600, &taplogctx, NULL);
+ ok1(tdb);
+
+ /* Tickle bug on appending zero length buffer to zero length buffer. */
+ key.dsize = strlen("hi");
+ key.dptr = discard_const_p(uint8_t, "hi");
+ data.dptr = discard_const_p(uint8_t, "world");
+ data.dsize = 0;
+
+ ok1(tdb_append(tdb, key, data) == 0);
+ ok1(tdb_append(tdb, key, data) == 0);
+ data = tdb_fetch(tdb, key);
+ ok1(data.dsize == 0);
+ tdb_close(tdb);
+ free(data.dptr);
+
+ return exit_status();
+}
diff --git a/lib/tdb/test/run.c b/lib/tdb/test/run.c
new file mode 100644
index 0000000..c744c4d
--- /dev/null
+++ b/lib/tdb/test/run.c
@@ -0,0 +1,50 @@
+#include "../common/tdb_private.h"
+#include "../common/io.c"
+#include "../common/tdb.c"
+#include "../common/lock.c"
+#include "../common/freelist.c"
+#include "../common/traverse.c"
+#include "../common/transaction.c"
+#include "../common/error.c"
+#include "../common/open.c"
+#include "../common/check.c"
+#include "../common/hash.c"
+#include "../common/mutex.c"
+#include "tap-interface.h"
+#include <stdlib.h>
+#include "logging.h"
+
+int main(int argc, char *argv[])
+{
+ struct tdb_context *tdb;
+ TDB_DATA key, data;
+
+ plan_tests(10);
+ tdb = tdb_open_ex("run.tdb", 1024, TDB_CLEAR_IF_FIRST,
+ O_CREAT|O_TRUNC|O_RDWR, 0600, &taplogctx, NULL);
+
+ ok1(tdb);
+ key.dsize = strlen("hi");
+ key.dptr = discard_const_p(uint8_t, "hi");
+ data.dsize = strlen("world");
+ data.dptr = discard_const_p(uint8_t, "world");
+
+ ok1(tdb_store(tdb, key, data, TDB_MODIFY) < 0);
+ ok1(tdb_error(tdb) == TDB_ERR_NOEXIST);
+ ok1(tdb_store(tdb, key, data, TDB_INSERT) == 0);
+ ok1(tdb_store(tdb, key, data, TDB_INSERT) < 0);
+ ok1(tdb_error(tdb) == TDB_ERR_EXISTS);
+ ok1(tdb_store(tdb, key, data, TDB_MODIFY) == 0);
+
+ data = tdb_fetch(tdb, key);
+ ok1(data.dsize == strlen("world"));
+ ok1(memcmp(data.dptr, "world", strlen("world")) == 0);
+ free(data.dptr);
+
+ key.dsize++;
+ data = tdb_fetch(tdb, key);
+ ok1(data.dptr == NULL);
+ tdb_close(tdb);
+
+ return exit_status();
+}
diff --git a/lib/tdb/test/rwlock-be.tdb b/lib/tdb/test/rwlock-be.tdb
new file mode 100644
index 0000000..45b5f09
--- /dev/null
+++ b/lib/tdb/test/rwlock-be.tdb
Binary files differ
diff --git a/lib/tdb/test/rwlock-le.tdb b/lib/tdb/test/rwlock-le.tdb
new file mode 100644
index 0000000..45b5f09
--- /dev/null
+++ b/lib/tdb/test/rwlock-le.tdb
Binary files differ
diff --git a/lib/tdb/test/sample_tdb.tdb b/lib/tdb/test/sample_tdb.tdb
new file mode 100644
index 0000000..a40e50c
--- /dev/null
+++ b/lib/tdb/test/sample_tdb.tdb
Binary files differ
diff --git a/lib/tdb/test/tap-interface.h b/lib/tdb/test/tap-interface.h
new file mode 100644
index 0000000..8f742d8
--- /dev/null
+++ b/lib/tdb/test/tap-interface.h
@@ -0,0 +1,58 @@
+/*
+ Unix SMB/CIFS implementation.
+ Simplistic implementation of tap interface.
+
+ Copyright (C) Rusty Russell 2012
+
+ ** NOTE! The following LGPL license applies to the talloc
+ ** library. This does NOT imply that all of Samba is released
+ ** under the LGPL
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 3 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, see <http://www.gnu.org/licenses/>.
+*/
+#include <stdio.h>
+
+#ifndef __location__
+#define __TAP_STRING_LINE1__(s) #s
+#define __TAP_STRING_LINE2__(s) __TAP_STRING_LINE1__(s)
+#define __TAP_STRING_LINE3__ __TAP_STRING_LINE2__(__LINE__)
+#define __location__ __FILE__ ":" __TAP_STRING_LINE3__
+#endif
+
+#define plan_tests(num)
+#define fail(...) do { \
+ fprintf(stderr, __VA_ARGS__); \
+ fprintf(stderr, "\n"); \
+ fflush(stderr); \
+ exit(1); \
+} while(0)
+#define diag(...) do { \
+ fprintf(stdout, __VA_ARGS__); \
+ fprintf(stdout, "\n"); \
+ fflush(stdout); \
+} while(0)
+#define pass(...) do { \
+ fprintf(stdout, "."); \
+ fflush(stdout); \
+} while(0)
+#define ok(e, ...) do { \
+ if (e) { \
+ pass(); \
+ } else { \
+ fail(__VA_ARGS__); \
+ } \
+} while(0)
+#define ok1(e) ok((e), "%s:%s", __location__, #e)
+#define skip(n, ...) diag(__VA_ARGS__)
+#define exit_status() 0
diff --git a/lib/tdb/test/tap-to-subunit.h b/lib/tdb/test/tap-to-subunit.h
new file mode 100644
index 0000000..a5cf74f
--- /dev/null
+++ b/lib/tdb/test/tap-to-subunit.h
@@ -0,0 +1,155 @@
+#ifndef TAP_TO_SUBUNIT_H
+#define TAP_TO_SUBUNIT_H
+/*
+ * tap-style wrapper for subunit.
+ *
+ * Copyright (c) 2011 Rusty Russell
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+#include "replace.h"
+
+/**
+ * plan_tests - announce the number of tests you plan to run
+ * @tests: the number of tests
+ *
+ * This should be the first call in your test program: it allows tracing
+ * of failures which mean that not all tests are run.
+ *
+ * If you don't know how many tests will actually be run, assume all of them
+ * and use skip() if you don't actually run some tests.
+ *
+ * Example:
+ * plan_tests(13);
+ */
+void plan_tests(unsigned int tests);
+
+/**
+ * ok1 - Simple conditional test
+ * @e: the expression which we expect to be true.
+ *
+ * This is the simplest kind of test: if the expression is true, the
+ * test passes. The name of the test which is printed will simply be
+ * file name, line number, and the expression itself.
+ *
+ * Example:
+ * ok1(somefunc() == 1);
+ */
+# define ok1(e) ((e) ? \
+ _gen_result(1, __func__, __FILE__, __LINE__, "%s", #e) : \
+ _gen_result(0, __func__, __FILE__, __LINE__, "%s", #e))
+
+/**
+ * ok - Conditional test with a name
+ * @e: the expression which we expect to be true.
+ * @...: the printf-style name of the test.
+ *
+ * If the expression is true, the test passes. The name of the test will be
+ * the filename, line number, and the printf-style string. This can be clearer
+ * than simply the expression itself.
+ *
+ * Example:
+ * ok1(somefunc() == 1);
+ * ok(somefunc() == 0, "Second somefunc() should fail");
+ */
+# define ok(e, ...) ((e) ? \
+ _gen_result(1, __func__, __FILE__, __LINE__, \
+ __VA_ARGS__) : \
+ _gen_result(0, __func__, __FILE__, __LINE__, \
+ __VA_ARGS__))
+
+/**
+ * pass - Note that a test passed
+ * @...: the printf-style name of the test.
+ *
+ * For complicated code paths, it can be easiest to simply call pass() in one
+ * branch and fail() in another.
+ *
+ * Example:
+ * int x = somefunc();
+ * if (x > 0)
+ * pass("somefunc() returned a valid value");
+ * else
+ * fail("somefunc() returned an invalid value");
+ */
+# define pass(...) ok(1, __VA_ARGS__)
+
+/**
+ * fail - Note that a test failed
+ * @...: the printf-style name of the test.
+ *
+ * For complicated code paths, it can be easiest to simply call pass() in one
+ * branch and fail() in another.
+ */
+# define fail(...) ok(0, __VA_ARGS__)
+
+unsigned int _gen_result(int, const char *, const char *, unsigned int,
+ const char *, ...) PRINTF_ATTRIBUTE(5, 6);
+
+/**
+ * diag - print a diagnostic message (use instead of printf/fprintf)
+ * @fmt: the format of the printf-style message
+ *
+ * diag ensures that the output will not be considered to be a test
+ * result by the TAP test harness. It will append '\n' for you.
+ *
+ * Example:
+ * diag("Now running complex tests");
+ */
+void diag(const char *fmt, ...) PRINTF_ATTRIBUTE(1, 2);
+
+/**
+ * skip - print a diagnostic message (use instead of printf/fprintf)
+ * @n: number of tests you're skipping.
+ * @fmt: the format of the reason you're skipping the tests.
+ *
+ * Sometimes tests cannot be run because the test system lacks some feature:
+ * you should explicitly document that you're skipping tests using skip().
+ *
+ * From the Test::More documentation:
+ * If it's something the user might not be able to do, use SKIP. This
+ * includes optional modules that aren't installed, running under an OS that
+ * doesn't have some feature (like fork() or symlinks), or maybe you need an
+ * Internet connection and one isn't available.
+ *
+ * Example:
+ * #ifdef HAVE_SOME_FEATURE
+ * ok1(somefunc());
+ * #else
+ * skip(1, "Don't have SOME_FEATURE");
+ * #endif
+ */
+void skip(unsigned int n, const char *fmt, ...) PRINTF_ATTRIBUTE(2, 3);
+
+/**
+ * exit_status - the value that main should return.
+ *
+ * For maximum compatibility your test program should return a particular exit
+ * code (ie. 0 if all tests were run, and every test which was expected to
+ * succeed succeeded).
+ *
+ * Example:
+ * exit(exit_status());
+ */
+int exit_status(void);
+#endif /* CCAN_TAP_H */
diff --git a/lib/tdb/test/tdb.corrupt b/lib/tdb/test/tdb.corrupt
new file mode 100644
index 0000000..83d6677
--- /dev/null
+++ b/lib/tdb/test/tdb.corrupt
Binary files differ
diff --git a/lib/tdb/test/test_tdbbackup.sh b/lib/tdb/test/test_tdbbackup.sh
new file mode 100755
index 0000000..8552ea1
--- /dev/null
+++ b/lib/tdb/test/test_tdbbackup.sh
@@ -0,0 +1,57 @@
+#!/bin/sh
+# Blackbox test for tdbbackup of given ldb or tdb database
+# Copyright (C) 2018 Andrew Bartlett <abartlet@samba.org>
+
+if [ $# -lt 1 ]; then
+ echo "Usage: $0 LDBFILE"
+ exit 1
+fi
+
+LDBFILE=$1
+
+timestamp()
+{
+ date -u +'time: %Y-%m-%d %H:%M:%S.%6NZ' | sed 's/\..*NZ$/.000000Z/'
+}
+
+subunit_fail_test()
+{
+ timestamp
+ printf 'failure: %s [\n' "$1"
+ cat -
+ echo "]"
+}
+
+testit()
+{
+ name="$1"
+ shift
+ cmdline="$@"
+ timestamp
+ printf 'test: %s\n' "$1"
+ output=$($cmdline 2>&1)
+ status=$?
+ if [ x$status = x0 ]; then
+ timestamp
+ printf 'success: %s\n' "$name"
+ else
+ echo "$output" | subunit_fail_test "$name"
+ fi
+ return $status
+}
+
+$BINDIR/tdbdump $LDBFILE | sort >orig_dump
+
+testit "normal tdbbackup on tdb file" $BINDIR/tdbbackup $LDBFILE -s .bak
+$BINDIR/tdbdump $LDBFILE.bak | sort >bak_dump
+testit "cmp between tdbdumps of original and backup" cmp orig_dump bak_dump
+rm $LDBFILE.bak
+rm bak_dump
+
+testit "readonly tdbbackup on tdb file" $BINDIR/tdbbackup $LDBFILE -s .bak -r
+$BINDIR/tdbdump $LDBFILE.bak | sort >bak_dump
+testit "cmp between tdbdumps of original and back dbs" cmp orig_dump bak_dump
+rm $LDBFILE.bak
+rm bak_dump
+
+rm orig_dump
diff --git a/lib/tdb/tools/tdbbackup.c b/lib/tdb/tools/tdbbackup.c
new file mode 100644
index 0000000..1125987
--- /dev/null
+++ b/lib/tdb/tools/tdbbackup.c
@@ -0,0 +1,370 @@
+/*
+ Unix SMB/CIFS implementation.
+ low level tdb backup and restore utility
+ Copyright (C) Andrew Tridgell 2002
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+/*
+
+ This program is meant for backup/restore of tdb databases. Typical usage would be:
+ tdbbackup *.tdb
+ when Samba shuts down cleanly, which will make a backup of all the local databases
+ to *.bak files. Then on Samba startup you would use:
+ tdbbackup -v *.tdb
+ and this will check the databases for corruption and if corruption is detected then
+ the backup will be restored.
+
+ You may also like to do a backup on a regular basis while Samba is
+ running, perhaps using cron.
+
+ The reason this program is needed is to cope with power failures
+ while Samba is running. A power failure could lead to database
+ corruption and Samba will then not start correctly.
+
+ Note that many of the databases in Samba are transient and thus
+ don't need to be backed up, so you can optimise the above a little
+ by only running the backup on the critical databases.
+
+ */
+
+#include "replace.h"
+#include "system/locale.h"
+#include "system/time.h"
+#include "system/filesys.h"
+#include "system/wait.h"
+#include "tdb.h"
+
+#ifdef HAVE_GETOPT_H
+#include <getopt.h>
+#endif
+
+static int failed;
+
+static struct tdb_logging_context log_ctx;
+
+#ifdef PRINTF_ATTRIBUTE
+static void tdb_log(struct tdb_context *tdb, enum tdb_debug_level level, const char *format, ...) PRINTF_ATTRIBUTE(3,4);
+#endif
+static void tdb_log(struct tdb_context *tdb, enum tdb_debug_level level, const char *format, ...)
+{
+ va_list ap;
+
+ va_start(ap, format);
+ vfprintf(stdout, format, ap);
+ va_end(ap);
+ fflush(stdout);
+}
+
+static char *add_suffix(const char *name, const char *suffix)
+{
+ char *ret;
+ int len = strlen(name) + strlen(suffix) + 1;
+ ret = (char *)malloc(len);
+ if (!ret) {
+ fprintf(stderr,"Out of memory!\n");
+ exit(1);
+ }
+ snprintf(ret, len, "%s%s", name, suffix);
+ return ret;
+}
+
+static int copy_fn(TDB_CONTEXT *tdb, TDB_DATA key, TDB_DATA dbuf, void *state)
+{
+ TDB_CONTEXT *tdb_new = (TDB_CONTEXT *)state;
+
+ if (tdb_store(tdb_new, key, dbuf, TDB_INSERT) != 0) {
+ fprintf(stderr,"Failed to insert into %s\n", tdb_name(tdb_new));
+ failed = 1;
+ return 1;
+ }
+ return 0;
+}
+
+
+static int test_fn(TDB_CONTEXT *tdb, TDB_DATA key, TDB_DATA dbuf, void *state)
+{
+ return 0;
+}
+
+/*
+ carefully backup a tdb, validating the contents and
+ only doing the backup if its OK
+ this function is also used for restore
+*/
+static int backup_tdb(const char *old_name, const char *new_name,
+ int hash_size, int nolock, bool readonly)
+{
+ TDB_CONTEXT *tdb;
+ TDB_CONTEXT *tdb_new;
+ char *tmp_name;
+ struct stat st;
+ int count1, count2;
+
+ tmp_name = add_suffix(new_name, ".tmp");
+
+ /* stat the old tdb to find its permissions */
+ if (stat(old_name, &st) != 0) {
+ perror(old_name);
+ free(tmp_name);
+ return 1;
+ }
+
+ /* open the old tdb */
+ tdb = tdb_open_ex(old_name, 0,
+ TDB_DEFAULT | (nolock ? TDB_NOLOCK : 0),
+ O_RDWR, 0, &log_ctx, NULL);
+ if (!tdb) {
+ printf("Failed to open %s\n", old_name);
+ free(tmp_name);
+ return 1;
+ }
+
+ /* create the new tdb */
+ unlink(tmp_name);
+ tdb_new = tdb_open_ex(tmp_name,
+ hash_size ? hash_size : tdb_hash_size(tdb),
+ TDB_DEFAULT,
+ O_RDWR|O_CREAT|O_EXCL, st.st_mode & 0777,
+ &log_ctx, NULL);
+ if (!tdb_new) {
+ perror(tmp_name);
+ free(tmp_name);
+ return 1;
+ }
+
+ if (readonly) {
+ if (tdb_lockall_read(tdb) != 0) {
+ printf("Failed to obtain read only lock on old tdb\n");
+ tdb_close(tdb);
+ tdb_close(tdb_new);
+ unlink(tmp_name);
+ free(tmp_name);
+ return 1;
+ }
+ } else if (tdb_transaction_start(tdb) != 0) {
+ printf("Failed to start transaction on db\n");
+ tdb_close(tdb);
+ tdb_close(tdb_new);
+ unlink(tmp_name);
+ free(tmp_name);
+ return 1;
+ }
+
+ /* lock the backup tdb so that nobody else can change it */
+ if (tdb_lockall(tdb_new) != 0) {
+ printf("Failed to lock backup tdb\n");
+ tdb_close(tdb);
+ tdb_close(tdb_new);
+ unlink(tmp_name);
+ free(tmp_name);
+ return 1;
+ }
+
+ failed = 0;
+
+ /* traverse and copy */
+ if (readonly) {
+ count1 = tdb_traverse_read(tdb,
+ copy_fn,
+ (void *)tdb_new);
+ } else {
+ count1 = tdb_traverse(tdb,
+ copy_fn,
+ (void *)tdb_new);
+ }
+ if (count1 < 0 || failed) {
+ fprintf(stderr,"failed to copy %s\n", old_name);
+ tdb_close(tdb);
+ tdb_close(tdb_new);
+ unlink(tmp_name);
+ free(tmp_name);
+ return 1;
+ }
+
+ /* close the old tdb */
+ tdb_close(tdb);
+
+ /* copy done, unlock the backup tdb */
+ tdb_unlockall(tdb_new);
+
+#ifdef HAVE_FDATASYNC
+ if (fdatasync(tdb_fd(tdb_new)) != 0) {
+#else
+ if (fsync(tdb_fd(tdb_new)) != 0) {
+#endif
+ /* not fatal */
+ fprintf(stderr, "failed to fsync backup file\n");
+ }
+
+ /* close the new tdb and re-open read-only */
+ tdb_close(tdb_new);
+ tdb_new = tdb_open_ex(tmp_name,
+ 0,
+ TDB_DEFAULT,
+ O_RDONLY, 0,
+ &log_ctx, NULL);
+ if (!tdb_new) {
+ fprintf(stderr,"failed to reopen %s\n", tmp_name);
+ unlink(tmp_name);
+ perror(tmp_name);
+ free(tmp_name);
+ return 1;
+ }
+
+ /* traverse the new tdb to confirm */
+ count2 = tdb_traverse(tdb_new, test_fn, NULL);
+ if (count2 != count1) {
+ fprintf(stderr,"failed to copy %s\n", old_name);
+ tdb_close(tdb_new);
+ unlink(tmp_name);
+ free(tmp_name);
+ return 1;
+ }
+
+ /* close the new tdb and rename it to .bak */
+ tdb_close(tdb_new);
+ if (rename(tmp_name, new_name) != 0) {
+ perror(new_name);
+ free(tmp_name);
+ return 1;
+ }
+
+ free(tmp_name);
+
+ return 0;
+}
+
+/*
+ verify a tdb and if it is corrupt then restore from *.bak
+*/
+static int verify_tdb(const char *fname, const char *bak_name)
+{
+ TDB_CONTEXT *tdb;
+ int count = -1;
+
+ /* open the tdb */
+ tdb = tdb_open_ex(fname, 0, 0,
+ O_RDONLY, 0, &log_ctx, NULL);
+
+ /* traverse the tdb, then close it */
+ if (tdb) {
+ count = tdb_traverse(tdb, test_fn, NULL);
+ tdb_close(tdb);
+ }
+
+ /* count is < 0 means an error */
+ if (count < 0) {
+ printf("restoring %s\n", fname);
+ return backup_tdb(bak_name, fname, 0, 0, 0);
+ }
+
+ printf("%s : %d records\n", fname, count);
+
+ return 0;
+}
+
+/*
+ see if one file is newer than another
+*/
+static int file_newer(const char *fname1, const char *fname2)
+{
+ struct stat st1, st2;
+ if (stat(fname1, &st1) != 0) {
+ return 0;
+ }
+ if (stat(fname2, &st2) != 0) {
+ return 1;
+ }
+ return (st1.st_mtime > st2.st_mtime);
+}
+
+static void usage(void)
+{
+ printf("Usage: tdbbackup [options] <fname...>\n\n");
+ printf(" -h this help message\n");
+ printf(" -s suffix set the backup suffix\n");
+ printf(" -v verify mode (restore if corrupt)\n");
+ printf(" -n hashsize set the new hash size for the backup\n");
+ printf(" -l open without locking to back up mutex dbs\n");
+ printf(" -r open with read only locking\n");
+}
+
+ int main(int argc, char *argv[])
+{
+ int i;
+ int ret = 0;
+ int c;
+ int verify = 0;
+ int hashsize = 0;
+ int nolock = 0;
+ bool readonly = false;
+ const char *suffix = ".bak";
+
+ log_ctx.log_fn = tdb_log;
+
+ while ((c = getopt(argc, argv, "vhs:n:lr")) != -1) {
+ switch (c) {
+ case 'h':
+ usage();
+ exit(0);
+ case 'v':
+ verify = 1;
+ break;
+ case 's':
+ suffix = optarg;
+ break;
+ case 'n':
+ hashsize = atoi(optarg);
+ break;
+ case 'l':
+ nolock = 1;
+ break;
+ case 'r':
+ readonly = true;
+ }
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ if (argc < 1) {
+ usage();
+ exit(1);
+ }
+
+ for (i=0; i<argc; i++) {
+ const char *fname = argv[i];
+ char *bak_name;
+
+ bak_name = add_suffix(fname, suffix);
+
+ if (verify) {
+ if (verify_tdb(fname, bak_name) != 0) {
+ ret = 1;
+ }
+ } else {
+ if (file_newer(fname, bak_name) &&
+ backup_tdb(fname, bak_name, hashsize,
+ nolock, readonly) != 0) {
+ ret = 1;
+ }
+ }
+
+ free(bak_name);
+ }
+
+ return ret;
+}
diff --git a/lib/tdb/tools/tdbdump.c b/lib/tdb/tools/tdbdump.c
new file mode 100644
index 0000000..9f1ab91
--- /dev/null
+++ b/lib/tdb/tools/tdbdump.c
@@ -0,0 +1,181 @@
+/*
+ Unix SMB/CIFS implementation.
+ simple tdb dump util
+ Copyright (C) Andrew Tridgell 2001
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "replace.h"
+#include "system/locale.h"
+#include "system/time.h"
+#include "system/filesys.h"
+#include "system/wait.h"
+#include "tdb.h"
+
+static void print_data(TDB_DATA d)
+{
+ unsigned char *p = (unsigned char *)d.dptr;
+ int len = d.dsize;
+ while (len--) {
+ if (isprint(*p) && !strchr("\"\\", *p)) {
+ fputc(*p, stdout);
+ } else {
+ printf("\\%02X", *p);
+ }
+ p++;
+ }
+}
+
+static int traverse_fn(TDB_CONTEXT *tdb, TDB_DATA key, TDB_DATA dbuf, void *state)
+{
+ printf("{\n");
+ printf("key(%zu) = \"", key.dsize);
+ print_data(key);
+ printf("\"\n");
+ printf("data(%zu) = \"", dbuf.dsize);
+ print_data(dbuf);
+ printf("\"\n");
+ printf("}\n");
+ return 0;
+}
+
+static void log_stderr(struct tdb_context *tdb, enum tdb_debug_level level,
+ const char *fmt, ...) PRINTF_ATTRIBUTE(3,4);
+
+static void log_stderr(struct tdb_context *tdb, enum tdb_debug_level level,
+ const char *fmt, ...)
+{
+ va_list ap;
+ const char *name = tdb_name(tdb);
+ const char *prefix = "";
+
+ if (!name)
+ name = "unnamed";
+
+ switch (level) {
+ case TDB_DEBUG_ERROR:
+ prefix = "ERROR: ";
+ break;
+ case TDB_DEBUG_WARNING:
+ prefix = "WARNING: ";
+ break;
+ case TDB_DEBUG_TRACE:
+ return;
+
+ default:
+ case TDB_DEBUG_FATAL:
+ prefix = "FATAL: ";
+ break;
+ }
+
+ va_start(ap, fmt);
+ fprintf(stderr, "tdb(%s): %s", name, prefix);
+ vfprintf(stderr, fmt, ap);
+ va_end(ap);
+}
+
+static void emergency_walk(TDB_DATA key, TDB_DATA dbuf, void *keyname)
+{
+ if (keyname) {
+ if (key.dsize != strlen(keyname))
+ return;
+ if (memcmp(key.dptr, keyname, key.dsize) != 0)
+ return;
+ }
+ traverse_fn(NULL, key, dbuf, NULL);
+}
+
+static int dump_tdb(const char *fname, const char *keyname, bool emergency)
+{
+ TDB_CONTEXT *tdb;
+ TDB_DATA key, value;
+ struct tdb_logging_context logfn = {
+ .log_fn = log_stderr,
+ };
+ int tdb_flags = TDB_DEFAULT;
+
+ /*
+ * Note: that O_RDONLY implies TDB_NOLOCK, but we want to make it
+ * explicit as it's important when working on databases which were
+ * created with mutex locking.
+ */
+ tdb_flags |= TDB_NOLOCK;
+
+ tdb = tdb_open_ex(fname, 0, tdb_flags, O_RDONLY, 0, &logfn, NULL);
+ if (!tdb) {
+ printf("Failed to open %s\n", fname);
+ return 1;
+ }
+
+ if (emergency) {
+ return tdb_rescue(tdb, emergency_walk, discard_const(keyname)) == 0;
+ }
+ if (!keyname) {
+ return tdb_traverse(tdb, traverse_fn, NULL) == -1 ? 1 : 0;
+ } else {
+ key.dptr = discard_const_p(uint8_t, keyname);
+ key.dsize = strlen(keyname);
+ value = tdb_fetch(tdb, key);
+ if (!value.dptr) {
+ return 1;
+ } else {
+ print_data(value);
+ free(value.dptr);
+ }
+ }
+
+ return 0;
+}
+
+static void usage( void)
+{
+ printf( "Usage: tdbdump [options] <filename>\n\n");
+ printf( " -h this help message\n");
+ printf( " -k keyname dumps value of keyname\n");
+ printf( " -e emergency dump, for corrupt databases\n");
+}
+
+ int main(int argc, char *argv[])
+{
+ char *fname, *keyname=NULL;
+ bool emergency = false;
+ int c;
+
+ if (argc < 2) {
+ printf("Usage: tdbdump <fname>\n");
+ exit(1);
+ }
+
+ while ((c = getopt( argc, argv, "hk:e")) != -1) {
+ switch (c) {
+ case 'h':
+ usage();
+ exit( 0);
+ case 'k':
+ keyname = optarg;
+ break;
+ case 'e':
+ emergency = true;
+ break;
+ default:
+ usage();
+ exit( 1);
+ }
+ }
+
+ fname = argv[optind];
+
+ return dump_tdb(fname, keyname, emergency);
+}
diff --git a/lib/tdb/tools/tdbrestore.c b/lib/tdb/tools/tdbrestore.c
new file mode 100644
index 0000000..1435758
--- /dev/null
+++ b/lib/tdb/tools/tdbrestore.c
@@ -0,0 +1,202 @@
+/*
+ tdbrestore -- construct a tdb from tdbdump output.
+ Copyright (C) Volker Lendecke 2010
+ Copyright (C) Simon McVittie 2005
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "replace.h"
+#include <assert.h>
+#include "system/locale.h"
+#include "system/time.h"
+#include "system/filesys.h"
+#include "system/wait.h"
+#include "tdb.h"
+
+static int read_linehead(FILE *f)
+{
+ size_t i;
+ int c;
+ int num_bytes;
+ char prefix[128];
+
+ while (1) {
+ c = getc(f);
+ if (c == EOF) {
+ return -1;
+ }
+ if (c == '(') {
+ break;
+ }
+ }
+ for (i=0; i<sizeof(prefix); i++) {
+ c = getc(f);
+ if (c == EOF) {
+ return -1;
+ }
+ prefix[i] = c;
+ if (c == '"') {
+ break;
+ }
+ }
+ if (i == sizeof(prefix)) {
+ return -1;
+ }
+ prefix[i] = '\0';
+
+ if (sscanf(prefix, "%d) = ", &num_bytes) != 1) {
+ return -1;
+ }
+ return num_bytes;
+}
+
+static int read_data(FILE *f, TDB_DATA *d, size_t size) {
+ size_t i;
+
+ d->dptr = (unsigned char *)malloc(size);
+ if (d->dptr == NULL) {
+ return -1;
+ }
+ d->dsize = size;
+
+ for (i=0; i<size; i++) {
+ int c = getc(f);
+ if (c == EOF) {
+ fprintf(stderr, "Unexpected EOF in data\n");
+ return 1;
+ } else if (c == '"') {
+ return 0;
+ } else if (c == '\\') {
+ char in[3] = {0};
+ size_t n;
+ bool ok;
+
+ n = fread(in, 1, 2, stdin);
+ if (n != 2) {
+ return -1;
+ }
+ ok = hex_byte(in, &d->dptr[i]);
+ if (!ok) {
+ fprintf(stderr, "Invalid hex: .2%s\n", in);
+ return -1;
+ }
+ } else {
+ d->dptr[i] = c;
+ }
+ }
+ return 0;
+}
+
+static int swallow(FILE *f, const char *s, int *eof)
+{
+ char line[128];
+
+ if (fgets(line, sizeof(line), f) == NULL) {
+ if (eof != NULL) {
+ *eof = 1;
+ }
+ return -1;
+ }
+ if (strcmp(line, s) != 0) {
+ return -1;
+ }
+ return 0;
+}
+
+static int read_rec(FILE *f, TDB_CONTEXT *tdb, int *eof)
+{
+ int length;
+ TDB_DATA key, data;
+ int ret = -1;
+
+ key.dptr = NULL;
+ data.dptr = NULL;
+
+ if (swallow(f, "{\n", eof) == -1) {
+ goto fail;
+ }
+ length = read_linehead(f);
+ if (length == -1) {
+ goto fail;
+ }
+ if (read_data(f, &key, length) == -1) {
+ goto fail;
+ }
+ if (swallow(f, "\"\n", NULL) == -1) {
+ goto fail;
+ }
+ length = read_linehead(f);
+ if (length == -1) {
+ goto fail;
+ }
+ if (read_data(f, &data, length) == -1) {
+ goto fail;
+ }
+ if ((swallow(f, "\"\n", NULL) == -1)
+ || (swallow(f, "}\n", NULL) == -1)) {
+ goto fail;
+ }
+ if (tdb_store(tdb, key, data, TDB_INSERT) != 0) {
+ fprintf(stderr, "TDB error: %s\n", tdb_errorstr(tdb));
+ goto fail;
+ }
+
+ ret = 0;
+fail:
+ free(key.dptr);
+ free(data.dptr);
+ return ret;
+}
+
+static int restore_tdb(const char *fname)
+{
+ TDB_CONTEXT *tdb;
+
+ tdb = tdb_open(fname, 0, 0, O_RDWR|O_CREAT|O_EXCL, 0666);
+ if (!tdb) {
+ perror("tdb_open");
+ fprintf(stderr, "Failed to open %s\n", fname);
+ return 1;
+ }
+
+ while (1) {
+ int eof = 0;
+ if (read_rec(stdin, tdb, &eof) == -1) {
+ if (eof) {
+ break;
+ }
+ return 1;
+ }
+ }
+ if (tdb_close(tdb)) {
+ fprintf(stderr, "Error closing tdb\n");
+ return 1;
+ }
+ return 0;
+}
+
+int main(int argc, char *argv[])
+{
+ char *fname;
+
+ if (argc < 2) {
+ printf("Usage: %s dbname < tdbdump_output\n", argv[0]);
+ exit(1);
+ }
+
+ fname = argv[1];
+
+ return restore_tdb(fname);
+}
diff --git a/lib/tdb/tools/tdbtest.c b/lib/tdb/tools/tdbtest.c
new file mode 100644
index 0000000..0be35dc
--- /dev/null
+++ b/lib/tdb/tools/tdbtest.c
@@ -0,0 +1,290 @@
+/* a test program for tdb - the trivial database */
+
+#include "replace.h"
+#include "tdb.h"
+#include "system/filesys.h"
+#include "system/time.h"
+
+#include <gdbm.h>
+
+
+#define DELETE_PROB 7
+#define STORE_PROB 5
+
+static struct tdb_context *db;
+static GDBM_FILE gdbm;
+
+struct timeval tp1,tp2;
+
+static void _start_timer(void)
+{
+ gettimeofday(&tp1,NULL);
+}
+
+static double _end_timer(void)
+{
+ gettimeofday(&tp2,NULL);
+ return((tp2.tv_sec - tp1.tv_sec) +
+ (tp2.tv_usec - tp1.tv_usec)*1.0e-6);
+}
+
+static void fatal(const char *why)
+{
+ perror(why);
+ exit(1);
+}
+
+#ifdef PRINTF_ATTRIBUTE
+static void tdb_log(struct tdb_context *tdb, int level, const char *format, ...) PRINTF_ATTRIBUTE(3,4);
+#endif
+static void tdb_log(struct tdb_context *tdb, int level, const char *format, ...)
+{
+ va_list ap;
+
+ va_start(ap, format);
+ vfprintf(stdout, format, ap);
+ va_end(ap);
+ fflush(stdout);
+}
+
+static void compare_db(void)
+{
+ TDB_DATA d, key, nextkey;
+ datum gd, gkey, gnextkey;
+
+ key = tdb_firstkey(db);
+ while (key.dptr) {
+ d = tdb_fetch(db, key);
+ gkey.dptr = key.dptr;
+ gkey.dsize = key.dsize;
+
+ gd = gdbm_fetch(gdbm, gkey);
+
+ if (!gd.dptr) fatal("key not in gdbm");
+ if (gd.dsize != d.dsize) fatal("data sizes differ");
+ if (memcmp(gd.dptr, d.dptr, d.dsize)) {
+ fatal("data differs");
+ }
+
+ nextkey = tdb_nextkey(db, key);
+ free(key.dptr);
+ free(d.dptr);
+ free(gd.dptr);
+ key = nextkey;
+ }
+
+ gkey = gdbm_firstkey(gdbm);
+ while (gkey.dptr) {
+ gd = gdbm_fetch(gdbm, gkey);
+ key.dptr = gkey.dptr;
+ key.dsize = gkey.dsize;
+
+ d = tdb_fetch(db, key);
+
+ if (!d.dptr) fatal("key not in db");
+ if (d.dsize != gd.dsize) fatal("data sizes differ");
+ if (memcmp(d.dptr, gd.dptr, gd.dsize)) {
+ fatal("data differs");
+ }
+
+ gnextkey = gdbm_nextkey(gdbm, gkey);
+ free(gkey.dptr);
+ free(gd.dptr);
+ free(d.dptr);
+ gkey = gnextkey;
+ }
+}
+
+static char *randbuf(int len)
+{
+ char *buf;
+ int i;
+ buf = (char *)malloc(len+1);
+
+ for (i=0;i<len;i++) {
+ buf[i] = 'a' + (rand() % 26);
+ }
+ buf[i] = 0;
+ return buf;
+}
+
+static void addrec_db(void)
+{
+ int klen, dlen;
+ char *k, *d;
+ TDB_DATA key, data;
+
+ klen = 1 + (rand() % 4);
+ dlen = 1 + (rand() % 100);
+
+ k = randbuf(klen);
+ d = randbuf(dlen);
+
+ key.dptr = k;
+ key.dsize = klen+1;
+
+ data.dptr = d;
+ data.dsize = dlen+1;
+
+ if (rand() % DELETE_PROB == 0) {
+ tdb_delete(db, key);
+ } else if (rand() % STORE_PROB == 0) {
+ if (tdb_store(db, key, data, TDB_REPLACE) != 0) {
+ fatal("tdb_store failed");
+ }
+ } else {
+ data = tdb_fetch(db, key);
+ if (data.dptr) free(data.dptr);
+ }
+
+ free(k);
+ free(d);
+}
+
+static void addrec_gdbm(void)
+{
+ int klen, dlen;
+ char *k, *d;
+ datum key, data;
+
+ klen = 1 + (rand() % 4);
+ dlen = 1 + (rand() % 100);
+
+ k = randbuf(klen);
+ d = randbuf(dlen);
+
+ key.dptr = k;
+ key.dsize = klen+1;
+
+ data.dptr = d;
+ data.dsize = dlen+1;
+
+ if (rand() % DELETE_PROB == 0) {
+ gdbm_delete(gdbm, key);
+ } else if (rand() % STORE_PROB == 0) {
+ if (gdbm_store(gdbm, key, data, GDBM_REPLACE) != 0) {
+ fatal("gdbm_store failed");
+ }
+ } else {
+ data = gdbm_fetch(gdbm, key);
+ if (data.dptr) free(data.dptr);
+ }
+
+ free(k);
+ free(d);
+}
+
+static int traverse_fn(struct tdb_context *tdb, TDB_DATA key, TDB_DATA dbuf, void *state)
+{
+#if 0
+ printf("[%s] [%s]\n", key.dptr, dbuf.dptr);
+#endif
+ tdb_delete(tdb, key);
+ return 0;
+}
+
+static void merge_test(void)
+{
+ int i;
+ char keys[5][2];
+ char tdata[] = "test";
+ TDB_DATA key, data;
+
+ for (i = 0; i < 5; i++) {
+ snprintf(keys[i],2, "%d", i);
+ key.dptr = keys[i];
+ key.dsize = 2;
+
+ data.dptr = tdata;
+ data.dsize = 4;
+
+ if (tdb_store(db, key, data, TDB_REPLACE) != 0) {
+ fatal("tdb_store failed");
+ }
+ }
+
+ key.dptr = keys[0];
+ tdb_delete(db, key);
+ key.dptr = keys[4];
+ tdb_delete(db, key);
+ key.dptr = keys[2];
+ tdb_delete(db, key);
+ key.dptr = keys[1];
+ tdb_delete(db, key);
+ key.dptr = keys[3];
+ tdb_delete(db, key);
+}
+
+static char *test_path(const char *filename)
+{
+ const char *prefix = getenv("TEST_DATA_PREFIX");
+
+ if (prefix) {
+ char *path = NULL;
+ int ret;
+
+ ret = asprintf(&path, "%s/%s", prefix, filename);
+ if (ret == -1) {
+ return NULL;
+ }
+ return path;
+ }
+
+ return strdup(filename);
+}
+
+ int main(int argc, const char *argv[])
+{
+ int i, seed=0;
+ int loops = 10000;
+ int num_entries;
+ char test_gdbm[1] = "test.gdbm";
+ char *test_tdb;
+
+ test_gdbm[0] = test_path("test.gdbm");
+ test_tdb = test_path("test.tdb");
+
+ unlink(test_gdbm[0]);
+
+ db = tdb_open(test_tdb, 0, TDB_CLEAR_IF_FIRST,
+ O_RDWR | O_CREAT | O_TRUNC, 0600);
+ gdbm = gdbm_open(test_gdbm, 512, GDBM_WRITER|GDBM_NEWDB|GDBM_FAST,
+ 0600, NULL);
+
+ if (!db || !gdbm) {
+ fatal("db open failed");
+ }
+
+#if 1
+ srand(seed);
+ _start_timer();
+ for (i=0;i<loops;i++) addrec_gdbm();
+ printf("gdbm got %.2f ops/sec\n", i/_end_timer());
+#endif
+
+ merge_test();
+
+ srand(seed);
+ _start_timer();
+ for (i=0;i<loops;i++) addrec_db();
+ printf("tdb got %.2f ops/sec\n", i/_end_timer());
+
+ if (tdb_validate_freelist(db, &num_entries) == -1) {
+ printf("tdb freelist is corrupt\n");
+ } else {
+ printf("tdb freelist is good (%d entries)\n", num_entries);
+ }
+
+ compare_db();
+
+ printf("traversed %d records\n", tdb_traverse(db, traverse_fn, NULL));
+ printf("traversed %d records\n", tdb_traverse(db, traverse_fn, NULL));
+
+ tdb_close(db);
+ gdbm_close(gdbm);
+
+ free(test_gdbm[0]);
+ free(test_tdb);
+
+ return 0;
+}
diff --git a/lib/tdb/tools/tdbtool.c b/lib/tdb/tools/tdbtool.c
new file mode 100644
index 0000000..fca28a1
--- /dev/null
+++ b/lib/tdb/tools/tdbtool.c
@@ -0,0 +1,924 @@
+/*
+ Unix SMB/CIFS implementation.
+ Samba database functions
+ Copyright (C) Andrew Tridgell 1999-2000
+ Copyright (C) Paul `Rusty' Russell 2000
+ Copyright (C) Jeremy Allison 2000
+ Copyright (C) Andrew Esh 2001
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "replace.h"
+#include "system/locale.h"
+#include "system/time.h"
+#include "system/filesys.h"
+#include "system/wait.h"
+#include "tdb.h"
+
+static int do_command(void);
+const char *cmdname;
+char *arg1, *arg2;
+size_t arg1len, arg2len;
+int bIterate = 0;
+char *line;
+TDB_DATA iterate_kbuf;
+char cmdline[1024];
+static int disable_mmap;
+static int _disable_lock;
+
+enum commands {
+ CMD_CREATE_TDB,
+ CMD_OPEN_TDB,
+ CMD_TRANSACTION_START,
+ CMD_TRANSACTION_COMMIT,
+ CMD_TRANSACTION_CANCEL,
+ CMD_ERASE,
+ CMD_DUMP,
+ CMD_INSERT,
+ CMD_MOVE,
+ CMD_STOREHEX,
+ CMD_STORE,
+ CMD_SHOW,
+ CMD_KEYS,
+ CMD_HEXKEYS,
+ CMD_DELETE,
+ CMD_LIST_HASH_FREE,
+ CMD_LIST_FREE,
+ CMD_FREELIST_SIZE,
+ CMD_INFO,
+ CMD_MMAP,
+ CMD_SPEED,
+ CMD_FIRST,
+ CMD_NEXT,
+ CMD_SYSTEM,
+ CMD_CHECK,
+ CMD_REPACK,
+ CMD_QUIT,
+ CMD_HELP
+};
+
+typedef struct {
+ const char *name;
+ enum commands cmd;
+} COMMAND_TABLE;
+
+COMMAND_TABLE cmd_table[] = {
+ {"create", CMD_CREATE_TDB},
+ {"open", CMD_OPEN_TDB},
+ {"transaction_start", CMD_TRANSACTION_START},
+ {"transaction_commit", CMD_TRANSACTION_COMMIT},
+ {"transaction_cancel", CMD_TRANSACTION_CANCEL},
+ {"erase", CMD_ERASE},
+ {"dump", CMD_DUMP},
+ {"insert", CMD_INSERT},
+ {"move", CMD_MOVE},
+ {"storehex", CMD_STOREHEX},
+ {"store", CMD_STORE},
+ {"show", CMD_SHOW},
+ {"keys", CMD_KEYS},
+ {"hexkeys", CMD_HEXKEYS},
+ {"delete", CMD_DELETE},
+ {"list", CMD_LIST_HASH_FREE},
+ {"free", CMD_LIST_FREE},
+ {"freelist_size", CMD_FREELIST_SIZE},
+ {"info", CMD_INFO},
+ {"speed", CMD_SPEED},
+ {"mmap", CMD_MMAP},
+ {"first", CMD_FIRST},
+ {"1", CMD_FIRST},
+ {"next", CMD_NEXT},
+ {"n", CMD_NEXT},
+ {"check", CMD_CHECK},
+ {"quit", CMD_QUIT},
+ {"q", CMD_QUIT},
+ {"!", CMD_SYSTEM},
+ {"repack", CMD_REPACK},
+ {NULL, CMD_HELP}
+};
+
+struct timeval tp1,tp2;
+
+static void _start_timer(void)
+{
+ gettimeofday(&tp1,NULL);
+}
+
+static double _end_timer(void)
+{
+ gettimeofday(&tp2,NULL);
+ return((tp2.tv_sec - tp1.tv_sec) +
+ (tp2.tv_usec - tp1.tv_usec)*1.0e-6);
+}
+
+#ifdef PRINTF_ATTRIBUTE
+static void tdb_log_open(struct tdb_context *tdb, enum tdb_debug_level level,
+ const char *format, ...) PRINTF_ATTRIBUTE(3,4);
+#endif
+static void tdb_log_open(struct tdb_context *tdb, enum tdb_debug_level level,
+ const char *format, ...)
+{
+ const char *mutex_msg =
+ "Can use mutexes only with MUTEX_LOCKING or NOLOCK\n";
+ char *p;
+ va_list ap;
+
+ p = strstr(format, mutex_msg);
+ if (p != NULL) {
+ /*
+ * Yes, this is a hack, but we don't want to see this
+ * message on first open, but we want to see
+ * everything else.
+ */
+ return;
+ }
+
+ va_start(ap, format);
+ vfprintf(stderr, format, ap);
+ va_end(ap);
+}
+
+#ifdef PRINTF_ATTRIBUTE
+static void tdb_log(struct tdb_context *tdb, enum tdb_debug_level level, const char *format, ...) PRINTF_ATTRIBUTE(3,4);
+#endif
+static void tdb_log(struct tdb_context *tdb, enum tdb_debug_level level, const char *format, ...)
+{
+ va_list ap;
+
+ va_start(ap, format);
+ vfprintf(stderr, format, ap);
+ va_end(ap);
+}
+
+/* a tdb tool for manipulating a tdb database */
+
+static TDB_CONTEXT *tdb;
+
+static int print_rec(TDB_CONTEXT *the_tdb, TDB_DATA key, TDB_DATA dbuf, void *state);
+static int print_key(TDB_CONTEXT *the_tdb, TDB_DATA key, TDB_DATA dbuf, void *state);
+static int print_hexkey(TDB_CONTEXT *the_tdb, TDB_DATA key, TDB_DATA dbuf, void *state);
+
+static void print_asc(const char *buf,int len)
+{
+ int i;
+
+ /* We're probably printing ASCII strings so don't try to display
+ the trailing NULL character. */
+
+ if (buf[len - 1] == 0)
+ len--;
+
+ for (i=0;i<len;i++)
+ printf("%c",isprint(buf[i])?buf[i]:'.');
+}
+
+static void print_data(const char *buf,int len)
+{
+ int i=0;
+ if (len<=0) return;
+ printf("[%03X] ",i);
+ for (i=0;i<len;) {
+ printf("%02X ",(int)((unsigned char)buf[i]));
+ i++;
+ if (i%8 == 0) printf(" ");
+ if (i%16 == 0) {
+ print_asc(&buf[i-16],8); printf(" ");
+ print_asc(&buf[i-8],8); printf("\n");
+ if (i<len) printf("[%03X] ",i);
+ }
+ }
+ if (i%16) {
+ int n;
+
+ n = 16 - (i%16);
+ printf(" ");
+ if (n>8) printf(" ");
+ while (n--) printf(" ");
+
+ n = i%16;
+ if (n > 8) n = 8;
+ print_asc(&buf[i-(i%16)],n); printf(" ");
+ n = (i%16) - n;
+ if (n>0) print_asc(&buf[i-n],n);
+ printf("\n");
+ }
+}
+
+static void help(void)
+{
+ printf("\n"
+"tdbtool: \n"
+" create dbname : create a database\n"
+" open dbname : open an existing database\n"
+" transaction_start : start a transaction\n"
+" transaction_commit : commit a transaction\n"
+" transaction_cancel : cancel a transaction\n"
+" erase : erase the database\n"
+" dump : dump the database as strings\n"
+" keys : dump the database keys as strings\n"
+" hexkeys : dump the database keys as hex values\n"
+" info : print summary info about the database\n"
+" insert key data : insert a record\n"
+" move key file : move a record to a destination tdb\n"
+" storehex key data : store a record (replace), key/value in hex format\n"
+" store key data : store a record (replace)\n"
+" show key : show a record by key\n"
+" delete key : delete a record by key\n"
+" list : print the database hash table and freelist\n"
+" free : print the database freelist\n"
+" freelist_size : print the number of records in the freelist\n"
+" check : check the integrity of an opened database\n"
+" repack : repack the database\n"
+" speed : perform speed tests on the database\n"
+" ! command : execute system command\n"
+" 1 | first : print the first record\n"
+" n | next : print the next record\n"
+" q | quit : terminate\n"
+" \\n : repeat 'next' command\n"
+"\n");
+}
+
+static void terror(const char *why)
+{
+ printf("%s\n", why);
+}
+
+static void create_tdb(const char *tdbname)
+{
+ struct tdb_logging_context log_ctx = { NULL, NULL};
+ log_ctx.log_fn = tdb_log;
+
+ if (tdb) tdb_close(tdb);
+ tdb = tdb_open_ex(tdbname, 0,
+ TDB_CLEAR_IF_FIRST |
+ (disable_mmap?TDB_NOMMAP:0) |
+ (_disable_lock?TDB_NOLOCK:0),
+ O_RDWR | O_CREAT | O_TRUNC, 0600, &log_ctx, NULL);
+ if (!tdb) {
+ printf("Could not create %s: %s\n", tdbname, strerror(errno));
+ }
+}
+
+static void open_tdb(const char *tdbname)
+{
+ struct tdb_logging_context log_ctx = { NULL, NULL };
+ log_ctx.log_fn = tdb_log_open;
+
+ if (tdb) tdb_close(tdb);
+ tdb = tdb_open_ex(tdbname, 0,
+ (disable_mmap?TDB_NOMMAP:0) |
+ (_disable_lock?TDB_NOLOCK:0),
+ O_RDWR, 0600,
+ &log_ctx, NULL);
+
+ log_ctx.log_fn = tdb_log;
+ if (tdb != NULL) {
+ tdb_set_logging_function(tdb, &log_ctx);
+ }
+
+ if ((tdb == NULL) && (errno == EINVAL)) {
+ /*
+ * Retry NOLOCK and readonly. There we want to see all
+ * error messages.
+ */
+ tdb = tdb_open_ex(tdbname, 0,
+ (disable_mmap?TDB_NOMMAP:0) |TDB_NOLOCK,
+ O_RDONLY, 0600,
+ &log_ctx, NULL);
+ }
+
+ if (!tdb) {
+ printf("Could not open %s: %s\n", tdbname, strerror(errno));
+ }
+}
+
+static void insert_tdb(char *keyname, size_t keylen, char* data, size_t datalen)
+{
+ TDB_DATA key, dbuf;
+
+ if ((keyname == NULL) || (keylen == 0)) {
+ terror("need key");
+ return;
+ }
+
+ key.dptr = (unsigned char *)keyname;
+ key.dsize = keylen;
+ dbuf.dptr = (unsigned char *)data;
+ dbuf.dsize = datalen;
+
+ if (tdb_store(tdb, key, dbuf, TDB_INSERT) != 0) {
+ terror("insert failed");
+ }
+}
+
+static void store_tdb(char *keyname, size_t keylen, char* data, size_t datalen)
+{
+ TDB_DATA key, dbuf;
+
+ if ((keyname == NULL) || (keylen == 0)) {
+ terror("need key");
+ return;
+ }
+
+ if ((data == NULL) || (datalen == 0)) {
+ terror("need data");
+ return;
+ }
+
+ key.dptr = (unsigned char *)keyname;
+ key.dsize = keylen;
+ dbuf.dptr = (unsigned char *)data;
+ dbuf.dsize = datalen;
+
+ printf("Storing key:\n");
+ print_rec(tdb, key, dbuf, NULL);
+
+ if (tdb_store(tdb, key, dbuf, TDB_REPLACE) != 0) {
+ terror("store failed");
+ }
+}
+
+static bool parse_hex(const char *src, size_t srclen, uint8_t *dst)
+{
+ size_t i=0;
+
+ if ((srclen % 2) != 0) {
+ return false;
+ }
+
+ while (i<srclen) {
+ bool ok = hex_byte(src, dst);
+ if (!ok) {
+ return false;
+ }
+ src += 2;
+ dst += 1;
+ }
+
+ return true;
+}
+
+static void store_hex_tdb(char *keystr, size_t keylen,
+ char *datastr, size_t datalen)
+{
+ if ((keystr == NULL) || (keylen == 0)) {
+ terror("need key");
+ return;
+ }
+ if ((datastr == NULL) || (datalen == 0)) {
+ terror("need data");
+ return;
+ }
+
+ {
+ uint8_t keybuf[keylen/2];
+ TDB_DATA key = { .dptr = keybuf, .dsize = sizeof(keybuf) };
+ uint8_t databuf[datalen/2];
+ TDB_DATA data = { .dptr = databuf, .dsize = sizeof(databuf) };
+ bool ok;
+
+ ok = parse_hex(keystr, keylen, keybuf);
+ if (!ok) {
+ terror("need hex key");
+ return;
+ }
+ ok = parse_hex(datastr, datalen, databuf);
+ if (!ok) {
+ terror("need hex data");
+ return;
+ }
+
+ printf("storing key/data:\n");
+ print_data((char *)key.dptr, key.dsize);
+ print_data((char *)data.dptr, data.dsize);
+
+ if (tdb_store(tdb, key, data, TDB_REPLACE) != 0) {
+ terror("store failed");
+ }
+ }
+}
+
+static void show_tdb(char *keyname, size_t keylen)
+{
+ TDB_DATA key, dbuf;
+
+ if ((keyname == NULL) || (keylen == 0)) {
+ terror("need key");
+ return;
+ }
+
+ key.dptr = (unsigned char *)keyname;
+ key.dsize = keylen;
+
+ dbuf = tdb_fetch(tdb, key);
+ if (!dbuf.dptr) {
+ terror("fetch failed");
+ return;
+ }
+
+ print_rec(tdb, key, dbuf, NULL);
+
+ free( dbuf.dptr );
+
+ return;
+}
+
+static void delete_tdb(char *keyname, size_t keylen)
+{
+ TDB_DATA key;
+
+ if ((keyname == NULL) || (keylen == 0)) {
+ terror("need key");
+ return;
+ }
+
+ key.dptr = (unsigned char *)keyname;
+ key.dsize = keylen;
+
+ if (tdb_delete(tdb, key) != 0) {
+ terror("delete failed");
+ }
+}
+
+static void move_rec(char *keyname, size_t keylen, char* tdbname)
+{
+ TDB_DATA key, dbuf;
+ TDB_CONTEXT *dst_tdb;
+
+ if ((keyname == NULL) || (keylen == 0)) {
+ terror("need key");
+ return;
+ }
+
+ if ( !tdbname ) {
+ terror("need destination tdb name");
+ return;
+ }
+
+ key.dptr = (unsigned char *)keyname;
+ key.dsize = keylen;
+
+ dbuf = tdb_fetch(tdb, key);
+ if (!dbuf.dptr) {
+ terror("fetch failed");
+ return;
+ }
+
+ print_rec(tdb, key, dbuf, NULL);
+
+ dst_tdb = tdb_open(tdbname, 0, 0, O_RDWR, 0600);
+ if ( !dst_tdb ) {
+ terror("unable to open destination tdb");
+ return;
+ }
+
+ if (tdb_store( dst_tdb, key, dbuf, TDB_REPLACE ) != 0) {
+ terror("failed to move record");
+ }
+ else
+ printf("record moved\n");
+
+ tdb_close( dst_tdb );
+
+ return;
+}
+
+static int print_rec(TDB_CONTEXT *the_tdb, TDB_DATA key, TDB_DATA dbuf, void *state)
+{
+ printf("\nkey %d bytes\n", (int)key.dsize);
+ print_asc((const char *)key.dptr, key.dsize);
+ printf("\ndata %d bytes\n", (int)dbuf.dsize);
+ print_data((const char *)dbuf.dptr, dbuf.dsize);
+ return 0;
+}
+
+static int print_key(TDB_CONTEXT *the_tdb, TDB_DATA key, TDB_DATA dbuf, void *state)
+{
+ printf("key %d bytes: ", (int)key.dsize);
+ print_asc((const char *)key.dptr, key.dsize);
+ printf("\n");
+ return 0;
+}
+
+static int print_hexkey(TDB_CONTEXT *the_tdb, TDB_DATA key, TDB_DATA dbuf, void *state)
+{
+ printf("key %d bytes\n", (int)key.dsize);
+ print_data((const char *)key.dptr, key.dsize);
+ printf("\n");
+ return 0;
+}
+
+static int total_bytes;
+
+static int traverse_fn(TDB_CONTEXT *the_tdb, TDB_DATA key, TDB_DATA dbuf, void *state)
+{
+ total_bytes += dbuf.dsize;
+ return 0;
+}
+
+static void info_tdb(void)
+{
+ char *summary = tdb_summary(tdb);
+
+ if (!summary) {
+ printf("Error = %s\n", tdb_errorstr(tdb));
+ } else {
+ printf("%s", summary);
+ free(summary);
+ }
+}
+
+static void speed_tdb(const char *tlimit)
+{
+ const char *str = "store test", *str2 = "transaction test";
+ unsigned timelimit = tlimit?atoi(tlimit):0;
+ double t;
+ int ops;
+ if (timelimit == 0) timelimit = 5;
+
+ ops = 0;
+ printf("Testing store speed for %u seconds\n", timelimit);
+ _start_timer();
+ do {
+ long int r = random();
+ TDB_DATA key, dbuf;
+ key.dptr = discard_const_p(uint8_t, str);
+ key.dsize = strlen((char *)key.dptr);
+ dbuf.dptr = (uint8_t *) &r;
+ dbuf.dsize = sizeof(r);
+ tdb_store(tdb, key, dbuf, TDB_REPLACE);
+ t = _end_timer();
+ ops++;
+ } while (t < timelimit);
+ printf("%10.3f ops/sec\n", ops/t);
+
+ ops = 0;
+ printf("Testing fetch speed for %u seconds\n", timelimit);
+ _start_timer();
+ do {
+ TDB_DATA key;
+ key.dptr = discard_const_p(uint8_t, str);
+ key.dsize = strlen((char *)key.dptr);
+ tdb_fetch(tdb, key);
+ t = _end_timer();
+ ops++;
+ } while (t < timelimit);
+ printf("%10.3f ops/sec\n", ops/t);
+
+ ops = 0;
+ printf("Testing transaction speed for %u seconds\n", timelimit);
+ _start_timer();
+ do {
+ long int r = random();
+ TDB_DATA key, dbuf;
+ key.dptr = discard_const_p(uint8_t, str2);
+ key.dsize = strlen((char *)key.dptr);
+ dbuf.dptr = (uint8_t *) &r;
+ dbuf.dsize = sizeof(r);
+ tdb_transaction_start(tdb);
+ tdb_store(tdb, key, dbuf, TDB_REPLACE);
+ tdb_transaction_commit(tdb);
+ t = _end_timer();
+ ops++;
+ } while (t < timelimit);
+ printf("%10.3f ops/sec\n", ops/t);
+
+ ops = 0;
+ printf("Testing traverse speed for %u seconds\n", timelimit);
+ _start_timer();
+ do {
+ tdb_traverse(tdb, traverse_fn, NULL);
+ t = _end_timer();
+ ops++;
+ } while (t < timelimit);
+ printf("%10.3f ops/sec\n", ops/t);
+}
+
+static void toggle_mmap(void)
+{
+ disable_mmap = !disable_mmap;
+ if (disable_mmap) {
+ printf("mmap is disabled\n");
+ } else {
+ printf("mmap is enabled\n");
+ }
+}
+
+static char *tdb_getline(const char *prompt)
+{
+ static char thisline[1024];
+ char *p;
+ fputs(prompt, stdout);
+ thisline[0] = 0;
+ p = fgets(thisline, sizeof(thisline)-1, stdin);
+ if (p) p = strchr(p, '\n');
+ if (p) *p = 0;
+ return p?thisline:NULL;
+}
+
+static void first_record(TDB_CONTEXT *the_tdb, TDB_DATA *pkey)
+{
+ TDB_DATA dbuf;
+ *pkey = tdb_firstkey(the_tdb);
+
+ dbuf = tdb_fetch(the_tdb, *pkey);
+ if (!dbuf.dptr) terror("fetch failed");
+ else {
+ print_rec(the_tdb, *pkey, dbuf, NULL);
+ }
+}
+
+static void next_record(TDB_CONTEXT *the_tdb, TDB_DATA *pkey)
+{
+ TDB_DATA dbuf;
+ *pkey = tdb_nextkey(the_tdb, *pkey);
+
+ dbuf = tdb_fetch(the_tdb, *pkey);
+ if (!dbuf.dptr)
+ terror("fetch failed");
+ else
+ print_rec(the_tdb, *pkey, dbuf, NULL);
+}
+
+static int count(TDB_DATA key, TDB_DATA data, void *private_data)
+{
+ (*(unsigned int *)private_data)++;
+ return 0;
+}
+
+static void check_db(TDB_CONTEXT *the_tdb)
+{
+ int tdbcount = 0;
+ if (!the_tdb)
+ printf("Error: No database opened!\n");
+ else if (tdb_check(the_tdb, count, &tdbcount) == -1)
+ printf("Integrity check for the opened database failed.\n");
+ else
+ printf("Database integrity is OK and has %d records.\n",
+ tdbcount);
+}
+
+static int do_command(void)
+{
+ COMMAND_TABLE *ctp = cmd_table;
+ enum commands mycmd = CMD_HELP;
+ int cmd_len;
+
+ if (cmdname != NULL) {
+ if (strlen(cmdname) == 0) {
+ mycmd = CMD_NEXT;
+ } else {
+ while (ctp->name) {
+ cmd_len = strlen(ctp->name);
+ if (strncmp(ctp->name,cmdname,cmd_len) == 0) {
+ mycmd = ctp->cmd;
+ break;
+ }
+ ctp++;
+ }
+ }
+ }
+
+ switch (mycmd) {
+ case CMD_CREATE_TDB:
+ bIterate = 0;
+ create_tdb(arg1);
+ return 0;
+ case CMD_OPEN_TDB:
+ bIterate = 0;
+ open_tdb(arg1);
+ return 0;
+ case CMD_SYSTEM:
+ /* Shell command */
+ if (system(arg1) == -1) {
+ terror("system() call failed\n");
+ }
+ return 0;
+ case CMD_QUIT:
+ return 1;
+ default:
+ /* all the rest require a open database */
+ if (!tdb) {
+ bIterate = 0;
+ terror("database not open");
+ help();
+ return 0;
+ }
+ switch (mycmd) {
+ case CMD_TRANSACTION_START:
+ bIterate = 0;
+ tdb_transaction_start(tdb);
+ return 0;
+ case CMD_TRANSACTION_COMMIT:
+ bIterate = 0;
+ tdb_transaction_commit(tdb);
+ return 0;
+ case CMD_REPACK:
+ bIterate = 0;
+ tdb_repack(tdb);
+ return 0;
+ case CMD_TRANSACTION_CANCEL:
+ bIterate = 0;
+ tdb_transaction_cancel(tdb);
+ return 0;
+ case CMD_ERASE:
+ bIterate = 0;
+ tdb_wipe_all(tdb);
+ return 0;
+ case CMD_DUMP:
+ bIterate = 0;
+ tdb_traverse(tdb, print_rec, NULL);
+ return 0;
+ case CMD_INSERT:
+ bIterate = 0;
+ insert_tdb(arg1, arg1len,arg2,arg2len);
+ return 0;
+ case CMD_MOVE:
+ bIterate = 0;
+ move_rec(arg1,arg1len,arg2);
+ return 0;
+ case CMD_STORE:
+ bIterate = 0;
+ store_tdb(arg1,arg1len,arg2,arg2len);
+ return 0;
+ case CMD_STOREHEX:
+ bIterate = 0;
+ store_hex_tdb(arg1,arg1len,arg2,arg2len);
+ return 0;
+ case CMD_SHOW:
+ bIterate = 0;
+ show_tdb(arg1, arg1len);
+ return 0;
+ case CMD_KEYS:
+ tdb_traverse(tdb, print_key, NULL);
+ return 0;
+ case CMD_HEXKEYS:
+ tdb_traverse(tdb, print_hexkey, NULL);
+ return 0;
+ case CMD_DELETE:
+ bIterate = 0;
+ delete_tdb(arg1,arg1len);
+ return 0;
+ case CMD_LIST_HASH_FREE:
+ tdb_dump_all(tdb);
+ return 0;
+ case CMD_LIST_FREE:
+ tdb_printfreelist(tdb);
+ return 0;
+ case CMD_FREELIST_SIZE: {
+ int size;
+
+ size = tdb_freelist_size(tdb);
+ if (size < 0) {
+ printf("Error getting freelist size.\n");
+ } else {
+ printf("freelist size: %d\n", size);
+ }
+
+ return 0;
+ }
+ case CMD_INFO:
+ info_tdb();
+ return 0;
+ case CMD_SPEED:
+ speed_tdb(arg1);
+ return 0;
+ case CMD_MMAP:
+ toggle_mmap();
+ return 0;
+ case CMD_FIRST:
+ bIterate = 1;
+ first_record(tdb, &iterate_kbuf);
+ return 0;
+ case CMD_NEXT:
+ if (bIterate)
+ next_record(tdb, &iterate_kbuf);
+ return 0;
+ case CMD_CHECK:
+ check_db(tdb);
+ return 0;
+ case CMD_HELP:
+ help();
+ return 0;
+ case CMD_CREATE_TDB:
+ case CMD_OPEN_TDB:
+ case CMD_SYSTEM:
+ case CMD_QUIT:
+ /*
+ * unhandled commands. cases included here to avoid compiler
+ * warnings.
+ */
+ return 0;
+ }
+ }
+
+ return 0;
+}
+
+static char *tdb_convert_string(char *instring, size_t *sizep)
+{
+ size_t length = 0;
+ char *outp, *inp;
+ char temp[3];
+
+ outp = inp = instring;
+
+ while (*inp) {
+ if (*inp == '\\') {
+ inp++;
+ if (*inp && strchr("0123456789abcdefABCDEF",(int)*inp)) {
+ temp[0] = *inp++;
+ temp[1] = '\0';
+ if (*inp && strchr("0123456789abcdefABCDEF",(int)*inp)) {
+ temp[1] = *inp++;
+ temp[2] = '\0';
+ }
+ *outp++ = (char)strtol((const char *)temp,NULL,16);
+ } else {
+ *outp++ = *inp++;
+ }
+ } else {
+ *outp++ = *inp++;
+ }
+ length++;
+ }
+ *sizep = length;
+ return instring;
+}
+
+int main(int argc, char *argv[])
+{
+ cmdname = "";
+ arg1 = NULL;
+ arg1len = 0;
+ arg2 = NULL;
+ arg2len = 0;
+
+ if (argv[1] && (strcmp(argv[1], "-l") == 0)) {
+ _disable_lock = 1;
+ argv[1] = argv[0];
+ argv += 1;
+ argc -= 1;
+ }
+
+ if (argv[1]) {
+ cmdname = "open";
+ arg1 = argv[1];
+ do_command();
+ cmdname = "";
+ arg1 = NULL;
+ }
+
+ switch (argc) {
+ case 1:
+ case 2:
+ /* Interactive mode */
+ while ((cmdname = tdb_getline("tdb> "))) {
+ arg2 = arg1 = NULL;
+ if ((arg1 = strchr((const char *)cmdname,' ')) != NULL) {
+ arg1++;
+ arg2 = arg1;
+ while (*arg2) {
+ if (*arg2 == ' ') {
+ *arg2++ = '\0';
+ break;
+ }
+ if ((*arg2++ == '\\') && (*arg2 == ' ')) {
+ arg2++;
+ }
+ }
+ }
+ if (arg1) arg1 = tdb_convert_string(arg1,&arg1len);
+ if (arg2) arg2 = tdb_convert_string(arg2,&arg2len);
+ if (do_command()) break;
+ }
+ break;
+ case 5:
+ arg2 = tdb_convert_string(argv[4],&arg2len);
+ FALL_THROUGH;
+ case 4:
+ arg1 = tdb_convert_string(argv[3],&arg1len);
+ FALL_THROUGH;
+ case 3:
+ cmdname = argv[2];
+ FALL_THROUGH;
+ default:
+ do_command();
+ break;
+ }
+
+ if (tdb) tdb_close(tdb);
+
+ return 0;
+}
diff --git a/lib/tdb/tools/tdbtortseq.c b/lib/tdb/tools/tdbtortseq.c
new file mode 100644
index 0000000..9423b46
--- /dev/null
+++ b/lib/tdb/tools/tdbtortseq.c
@@ -0,0 +1,123 @@
+/*
+ * Unix SMB/CIFS implementation.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "replace.h"
+#include "system/filesys.h"
+#include <tdb.h>
+#include <stdio.h>
+#include <errno.h>
+#include "system/wait.h"
+
+#define NPROCS 500
+#define NUMOPS 1000000
+
+int main(void)
+{
+ pid_t pids[NPROCS];
+ struct tdb_context *tdb = NULL;
+ int i;
+ uint32_t seqnum_before, seqnum_after;
+
+ tdb = tdb_open("seqnum_test.tdb",
+ 10000,
+ TDB_CLEAR_IF_FIRST|
+ TDB_SEQNUM|
+ TDB_INCOMPATIBLE_HASH|
+ TDB_MUTEX_LOCKING,
+ O_CREAT|O_RDWR,
+ 0644);
+ if (tdb == NULL) {
+ perror("tdb_open failed");
+ return 1;
+ }
+ seqnum_before = tdb_get_seqnum(tdb);
+
+ for (i=0; i<NPROCS; i++) {
+ pids[i] = fork();
+ if (pids[i] == -1) {
+ perror("fork failed");
+ return 1;
+ }
+ if (pids[i] == 0) {
+ pid_t mypid = getpid();
+ int ret;
+ int j;
+
+ ret = tdb_reopen(tdb);
+ if (ret != 0) {
+ perror("tdb_reopen failed");
+ return 1;
+ }
+
+ for (j=0; j<NUMOPS; j++) {
+ TDB_DATA key = {
+ .dptr = (uint8_t *)&mypid,
+ .dsize = sizeof(mypid),
+ };
+ TDB_DATA value = {
+ .dptr = (uint8_t *)&j,
+ .dsize = sizeof(j),
+ };
+ ret = tdb_store(tdb, key, value, 0);
+ if (ret == -1) {
+ perror("tdb_store failed");
+ return 1;
+ }
+ }
+
+ return 0;
+ }
+ }
+
+ for (i=0; i<NPROCS; i++) {
+ int wstatus;
+ pid_t ret = waitpid(pids[i], &wstatus, 0);
+
+ if (ret == -1) {
+ perror("waitpid failed");
+ return 1;
+ }
+
+ if (!WIFEXITED(wstatus)) {
+ fprintf(stderr,
+ "pid %d did not exit properly\n",
+ (int)pids[i]);
+ return 1;
+ }
+
+ if (WEXITSTATUS(wstatus) != 0) {
+ fprintf(stderr,
+ "pid %d returned %d\n",
+ (int)pids[i],
+ WEXITSTATUS(wstatus));
+ return 1;
+ }
+ }
+
+ seqnum_after = tdb_get_seqnum(tdb);
+
+ printf("seqnum_before=%"PRIu32", seqnum_after=%"PRIu32"\n",
+ seqnum_before,
+ seqnum_after);
+
+ if ((seqnum_after - seqnum_before) != (NPROCS*NUMOPS)) {
+ perror("incrementing seqnum failed");
+ return 1;
+ }
+
+ return 0;
+}
diff --git a/lib/tdb/tools/tdbtorture.c b/lib/tdb/tools/tdbtorture.c
new file mode 100644
index 0000000..1063f14
--- /dev/null
+++ b/lib/tdb/tools/tdbtorture.c
@@ -0,0 +1,500 @@
+/* this tests tdb by doing lots of ops from several simultaneous
+ writers - that stresses the locking code.
+*/
+
+#include "replace.h"
+#include "system/time.h"
+#include "system/wait.h"
+#include "system/filesys.h"
+#include "tdb.h"
+
+#ifdef HAVE_GETOPT_H
+#include <getopt.h>
+#endif
+
+
+#define REOPEN_PROB 30
+#define DELETE_PROB 8
+#define STORE_PROB 4
+#define APPEND_PROB 6
+#define TRANSACTION_PROB 10
+#define TRANSACTION_PREPARE_PROB 2
+#define LOCKSTORE_PROB 5
+#define TRAVERSE_PROB 20
+#define TRAVERSE_READ_PROB 20
+#define CULL_PROB 100
+#define KEYLEN 3
+#define DATALEN 100
+
+static struct tdb_context *db;
+static int in_transaction;
+static int error_count;
+static int always_transaction = 0;
+static int hash_size = 2;
+static unsigned loopnum;
+static int count_pipe;
+static bool mutex = false;
+static struct tdb_logging_context log_ctx;
+
+#ifdef PRINTF_ATTRIBUTE
+static void tdb_log(struct tdb_context *tdb, enum tdb_debug_level level, const char *format, ...) PRINTF_ATTRIBUTE(3,4);
+#endif
+static void tdb_log(struct tdb_context *tdb, enum tdb_debug_level level, const char *format, ...)
+{
+ va_list ap;
+
+ /* trace level messages do not indicate an error */
+ if (level != TDB_DEBUG_TRACE) {
+ error_count++;
+ }
+
+ va_start(ap, format);
+ vfprintf(stdout, format, ap);
+ va_end(ap);
+ fflush(stdout);
+#if 0
+ if (level != TDB_DEBUG_TRACE) {
+ char *ptr;
+ signal(SIGUSR1, SIG_IGN);
+ asprintf(&ptr,"xterm -e gdb /proc/%d/exe %d", getpid(), getpid());
+ system(ptr);
+ free(ptr);
+ }
+#endif
+}
+
+static void fatal(const char *why)
+{
+ perror(why);
+ error_count++;
+}
+
+static char *randbuf(int len)
+{
+ char *buf;
+ int i;
+ buf = (char *)malloc(len+1);
+
+ for (i=0;i<len;i++) {
+ buf[i] = 'a' + (rand() % 26);
+ }
+ buf[i] = 0;
+ return buf;
+}
+
+static int cull_traverse(struct tdb_context *tdb, TDB_DATA key, TDB_DATA dbuf,
+ void *state)
+{
+#if CULL_PROB
+ if (random() % CULL_PROB == 0) {
+ tdb_delete(tdb, key);
+ }
+#endif
+ return 0;
+}
+
+static bool do_transaction(void)
+{
+#if TRANSACTION_PROB
+ if (mutex) {
+ return false;
+ }
+ if (random() % TRANSACTION_PROB == 0) {
+ return true;
+ }
+#endif
+ return false;
+}
+
+static void addrec_db(void)
+{
+ int klen, dlen;
+ char *k, *d;
+ TDB_DATA key, data;
+
+ klen = 1 + (rand() % KEYLEN);
+ dlen = 1 + (rand() % DATALEN);
+
+ k = randbuf(klen);
+ d = randbuf(dlen);
+
+ key.dptr = (unsigned char *)k;
+ key.dsize = klen+1;
+
+ data.dptr = (unsigned char *)d;
+ data.dsize = dlen+1;
+
+#if REOPEN_PROB
+ if (in_transaction == 0 && random() % REOPEN_PROB == 0) {
+ tdb_reopen_all(0);
+ goto next;
+ }
+#endif
+
+ if (in_transaction == 0 &&
+ (always_transaction || do_transaction())) {
+ if (tdb_transaction_start(db) != 0) {
+ fatal("tdb_transaction_start failed");
+ }
+ in_transaction++;
+ goto next;
+ }
+ if (in_transaction && do_transaction()) {
+ if (random() % TRANSACTION_PREPARE_PROB == 0) {
+ if (tdb_transaction_prepare_commit(db) != 0) {
+ fatal("tdb_transaction_prepare_commit failed");
+ }
+ }
+ if (tdb_transaction_commit(db) != 0) {
+ fatal("tdb_transaction_commit failed");
+ }
+ in_transaction--;
+ goto next;
+ }
+ if (in_transaction && do_transaction()) {
+ if (tdb_transaction_cancel(db) != 0) {
+ fatal("tdb_transaction_cancel failed");
+ }
+ in_transaction--;
+ goto next;
+ }
+
+#if DELETE_PROB
+ if (random() % DELETE_PROB == 0) {
+ tdb_delete(db, key);
+ goto next;
+ }
+#endif
+
+#if STORE_PROB
+ if (random() % STORE_PROB == 0) {
+ if (tdb_store(db, key, data, TDB_REPLACE) != 0) {
+ fatal("tdb_store failed");
+ }
+ goto next;
+ }
+#endif
+
+#if APPEND_PROB
+ if (random() % APPEND_PROB == 0) {
+ if (tdb_append(db, key, data) != 0) {
+ fatal("tdb_append failed");
+ }
+ goto next;
+ }
+#endif
+
+#if LOCKSTORE_PROB
+ if (random() % LOCKSTORE_PROB == 0) {
+ tdb_chainlock(db, key);
+ data = tdb_fetch(db, key);
+ if (tdb_store(db, key, data, TDB_REPLACE) != 0) {
+ fatal("tdb_store failed");
+ }
+ if (data.dptr) free(data.dptr);
+ tdb_chainunlock(db, key);
+ goto next;
+ }
+#endif
+
+#if TRAVERSE_PROB
+ if (random() % TRAVERSE_PROB == 0) {
+ tdb_traverse(db, cull_traverse, NULL);
+ goto next;
+ }
+#endif
+
+#if TRAVERSE_READ_PROB
+ if (random() % TRAVERSE_READ_PROB == 0) {
+ tdb_traverse_read(db, NULL, NULL);
+ goto next;
+ }
+#endif
+
+ data = tdb_fetch(db, key);
+ if (data.dptr) free(data.dptr);
+
+next:
+ free(k);
+ free(d);
+}
+
+static int traverse_fn(struct tdb_context *tdb, TDB_DATA key, TDB_DATA dbuf,
+ void *state)
+{
+ tdb_delete(tdb, key);
+ return 0;
+}
+
+static void usage(void)
+{
+ printf("Usage: tdbtorture [-t] [-k] [-m] [-n NUM_PROCS] [-l NUM_LOOPS] [-s SEED] [-H HASH_SIZE]\n");
+ exit(0);
+}
+
+static void send_count_and_suicide(int sig)
+{
+ ssize_t ret;
+
+ /* This ensures our successor can continue where we left off. */
+ do {
+ ret = write(count_pipe, &loopnum, sizeof(loopnum));
+ } while (ret == -1 && errno == EINTR);
+ /* This gives a unique signature. */
+ kill(getpid(), SIGUSR2);
+}
+
+static int run_child(const char *filename, int i, int seed, unsigned num_loops, unsigned start)
+{
+ int tdb_flags = TDB_DEFAULT|TDB_CLEAR_IF_FIRST|TDB_INCOMPATIBLE_HASH;
+
+ if (mutex) {
+ tdb_flags |= TDB_MUTEX_LOCKING;
+ }
+
+ db = tdb_open_ex(filename, hash_size, tdb_flags,
+ O_RDWR | O_CREAT, 0600, &log_ctx, NULL);
+ if (!db) {
+ fatal("db open failed");
+ }
+
+ srand(seed + i);
+ srandom(seed + i);
+
+ /* Set global, then we're ready to handle being killed. */
+ loopnum = start;
+ signal(SIGUSR1, send_count_and_suicide);
+
+ for (;loopnum<num_loops && error_count == 0;loopnum++) {
+ addrec_db();
+ }
+
+ if (error_count == 0) {
+ tdb_traverse_read(db, NULL, NULL);
+ if (always_transaction) {
+ while (in_transaction) {
+ tdb_transaction_cancel(db);
+ in_transaction--;
+ }
+ if (tdb_transaction_start(db) != 0)
+ fatal("tdb_transaction_start failed");
+ }
+ tdb_traverse(db, traverse_fn, NULL);
+ tdb_traverse(db, traverse_fn, NULL);
+ if (always_transaction) {
+ if (tdb_transaction_commit(db) != 0)
+ fatal("tdb_transaction_commit failed");
+ }
+ }
+
+ tdb_close(db);
+
+ return (error_count < 100 ? error_count : 100);
+}
+
+static char *test_path(const char *filename)
+{
+ const char *prefix = getenv("TEST_DATA_PREFIX");
+
+ if (prefix) {
+ char *path = NULL;
+ int ret;
+
+ ret = asprintf(&path, "%s/%s", prefix, filename);
+ if (ret == -1) {
+ return NULL;
+ }
+ return path;
+ }
+
+ return strdup(filename);
+}
+
+int main(int argc, char * const *argv)
+{
+ int i, seed = -1;
+ int num_loops = 5000;
+ int num_procs = 3;
+ int c, pfds[2];
+ extern char *optarg;
+ pid_t *pids;
+ int kill_random = 0;
+ int *done;
+ char *test_tdb;
+
+ log_ctx.log_fn = tdb_log;
+
+ while ((c = getopt(argc, argv, "n:l:s:H:thkm")) != -1) {
+ switch (c) {
+ case 'n':
+ num_procs = strtol(optarg, NULL, 0);
+ break;
+ case 'l':
+ num_loops = strtol(optarg, NULL, 0);
+ break;
+ case 'H':
+ hash_size = strtol(optarg, NULL, 0);
+ break;
+ case 's':
+ seed = strtol(optarg, NULL, 0);
+ break;
+ case 't':
+ always_transaction = 1;
+ break;
+ case 'k':
+ kill_random = 1;
+ break;
+ case 'm':
+ mutex = tdb_runtime_check_for_robust_mutexes();
+ if (!mutex) {
+ printf("tdb_runtime_check_for_robust_mutexes() returned false\n");
+ exit(1);
+ }
+ break;
+ default:
+ usage();
+ }
+ }
+
+ test_tdb = test_path("torture.tdb");
+
+ unlink(test_tdb);
+
+ if (seed == -1) {
+ seed = (getpid() + time(NULL)) & 0x7FFFFFFF;
+ }
+
+ printf("Testing with %d processes, %d loops, %d hash_size, seed=%d%s\n",
+ num_procs, num_loops, hash_size, seed,
+ (always_transaction ? " (all within transactions)" : ""));
+
+ if (num_procs == 1 && !kill_random) {
+ /* Don't fork for this case, makes debugging easier. */
+ error_count = run_child(test_tdb, 0, seed, num_loops, 0);
+ goto done;
+ }
+
+ pids = (pid_t *)calloc(sizeof(pid_t), num_procs);
+ if (pids == NULL) {
+ perror("Unable to allocate memory for pids");
+ exit(1);
+ }
+ done = (int *)calloc(sizeof(int), num_procs);
+ if (done == NULL) {
+ perror("Unable to allocate memory for done");
+ exit(1);
+ }
+
+ if (pipe(pfds) != 0) {
+ perror("Creating pipe");
+ exit(1);
+ }
+ count_pipe = pfds[1];
+
+ for (i=0;i<num_procs;i++) {
+ if ((pids[i]=fork()) == 0) {
+ close(pfds[0]);
+ exit(run_child(test_tdb, i, seed, num_loops, 0));
+ }
+ }
+
+ while (num_procs) {
+ int status, j;
+ pid_t pid;
+
+ if (error_count != 0) {
+ /* try and stop the test on any failure */
+ for (j=0;j<num_procs;j++) {
+ if (pids[j] != 0) {
+ kill(pids[j], SIGTERM);
+ }
+ }
+ }
+
+ pid = waitpid(-1, &status, kill_random ? WNOHANG : 0);
+ if (pid == 0) {
+ struct timeval tv;
+
+ /* Sleep for 1/10 second. */
+ tv.tv_sec = 0;
+ tv.tv_usec = 100000;
+ select(0, NULL, NULL, NULL, &tv);
+
+ /* Kill someone. */
+ kill(pids[random() % num_procs], SIGUSR1);
+ continue;
+ }
+
+ if (pid == -1) {
+ perror("failed to wait for child\n");
+ exit(1);
+ }
+
+ for (j=0;j<num_procs;j++) {
+ if (pids[j] == pid) break;
+ }
+ if (j == num_procs) {
+ printf("unknown child %d exited!?\n", (int)pid);
+ exit(1);
+ }
+ if (WIFSIGNALED(status)) {
+ if (WTERMSIG(status) == SIGUSR2
+ || WTERMSIG(status) == SIGUSR1) {
+ /* SIGUSR2 means they wrote to pipe. */
+ if (WTERMSIG(status) == SIGUSR2) {
+ ssize_t ret;
+
+ do {
+ ret = read(pfds[0], &done[j],
+ sizeof(done[j]));
+ } while (ret == -1 && errno == EINTR);
+ }
+ pids[j] = fork();
+ if (pids[j] == 0)
+ exit(run_child(test_tdb, j, seed,
+ num_loops, done[j]));
+ printf("Restarting child %i for %u-%u\n",
+ j, done[j], num_loops);
+ continue;
+ }
+ printf("child %d exited with signal %d\n",
+ (int)pid, WTERMSIG(status));
+ error_count++;
+ } else {
+ if (WEXITSTATUS(status) != 0) {
+ printf("child %d exited with status %d\n",
+ (int)pid, WEXITSTATUS(status));
+ error_count++;
+ }
+ }
+ ARRAY_DEL_ELEMENT(pids, j, num_procs);
+ num_procs--;
+ }
+
+ free(pids);
+
+done:
+ if (error_count == 0) {
+ int tdb_flags = TDB_DEFAULT;
+
+ if (mutex) {
+ tdb_flags |= TDB_NOLOCK;
+ }
+
+ db = tdb_open_ex(test_tdb, hash_size, tdb_flags,
+ O_RDWR, 0, &log_ctx, NULL);
+ if (!db) {
+ fatal("db open failed\n");
+ exit(1);
+ }
+ if (tdb_check(db, NULL, NULL) == -1) {
+ printf("db check failed\n");
+ exit(1);
+ }
+ tdb_close(db);
+ printf("OK\n");
+ }
+
+ free(test_tdb);
+ return error_count;
+}
diff --git a/lib/tdb/web/index.html b/lib/tdb/web/index.html
new file mode 100644
index 0000000..99e8a2f
--- /dev/null
+++ b/lib/tdb/web/index.html
@@ -0,0 +1,48 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2//EN">
+<HTML>
+<HEAD>
+<TITLE>ldb</TITLE>
+</HEAD>
+<BODY BGCOLOR="#ffffff" TEXT="#000000" VLINK="#292555" LINK="#292555" ALINK="#cc0033">
+
+<h1>tdb</h1>
+
+TDB is a Trivial Database. In concept, it is very much like GDBM, and BSD's DB
+except that it allows multiple simultaneous writers and uses locking
+internally to keep writers from trampling on each other. TDB is also extremely
+small.
+
+<h2>Download</h2>
+You can download the latest releases of tdb from the <a
+href="http://samba.org/ftp/tdb">tdb directory</a> on the samba public
+source archive.
+
+
+<h2>Discussion and bug reports</h2>
+
+tdb does not currently have its own mailing list or bug tracking
+system. For now, please use the <a
+href="https://lists.samba.org/mailman/listinfo/samba-technical">samba-technical</a>
+mailing list, and the <a href="http://bugzilla.samba.org/">Samba
+bugzilla</a> bug tracking system.
+
+<h2>Download</h2>
+
+You can download the latest code either via git or rsync.<br>
+<br>
+To fetch via git see the following guide:<br>
+<a href="http://wiki.samba.org/index.php/Using_Git_for_Samba_Development">Using Git for Samba Development</a><br>
+Once you have cloned the tree switch to the master branch and cd into the source/lib/tdb directory.<br>
+<br>
+To fetch via rsync use these commands:
+
+<pre>
+ rsync -Pavz samba.org::ftp/unpacked/standalone_projects/lib/tdb .
+ rsync -Pavz samba.org::ftp/unpacked/standalone_projects/lib/replace .
+</pre>
+
+and build in tdb. It will find the replace library in the directory
+above automatically.
+
+</BODY>
+</HTML>
diff --git a/lib/tdb/wscript b/lib/tdb/wscript
new file mode 100644
index 0000000..71ada31
--- /dev/null
+++ b/lib/tdb/wscript
@@ -0,0 +1,264 @@
+#!/usr/bin/env python
+
+APPNAME = 'tdb'
+VERSION = '1.4.7'
+
+import sys, os
+
+# find the buildtools directory
+top = '.'
+while not os.path.exists(top+'/buildtools') and len(top.split('/')) < 5:
+ top = top + '/..'
+sys.path.insert(0, top + '/buildtools/wafsamba')
+
+out = 'bin'
+
+import wafsamba
+from wafsamba import samba_dist, samba_utils
+from waflib import Options, Logs, Context
+import shutil
+
+samba_dist.DIST_DIRS('lib/tdb:. lib/replace:lib/replace buildtools:buildtools third_party/waf:third_party/waf')
+
+tdb1_unit_tests = [
+ 'run-3G-file',
+ 'run-bad-tdb-header',
+ 'run',
+ 'run-check',
+ 'run-corrupt',
+ 'run-die-during-transaction',
+ 'run-endian',
+ 'run-incompatible',
+ 'run-nested-transactions',
+ 'run-nested-traverse',
+ 'run-no-lock-during-traverse',
+ 'run-oldhash',
+ 'run-open-during-transaction',
+ 'run-readonly-check',
+ 'run-rescue',
+ 'run-rescue-find_entry',
+ 'run-rdlock-upgrade',
+ 'run-rwlock-check',
+ 'run-summary',
+ 'run-transaction-expand',
+ 'run-traverse-in-transaction',
+ 'run-wronghash-fail',
+ 'run-zero-append',
+ 'run-fcntl-deadlock',
+ 'run-marklock-deadlock',
+ 'run-allrecord-traverse-deadlock',
+ 'run-mutex-openflags2',
+ 'run-mutex-trylock',
+ 'run-mutex-allrecord-bench',
+ 'run-mutex-allrecord-trylock',
+ 'run-mutex-allrecord-block',
+ 'run-mutex-transaction1',
+ 'run-mutex-die',
+ 'run-mutex1',
+ 'run-circular-chain',
+ 'run-circular-freelist',
+ 'run-traverse-chain',
+]
+
+def options(opt):
+ opt.BUILTIN_DEFAULT('replace')
+ opt.PRIVATE_EXTENSION_DEFAULT('tdb', noextension='tdb')
+ opt.RECURSE('lib/replace')
+ opt.add_option('--disable-tdb-mutex-locking',
+ help=("Disable the use of pthread robust mutexes"),
+ action="store_true", dest='disable_tdb_mutex_locking',
+ default=False)
+
+
+def configure(conf):
+ conf.env.disable_tdb_mutex_locking = getattr(Options.options,
+ 'disable_tdb_mutex_locking',
+ False)
+ if not conf.env.disable_tdb_mutex_locking:
+ conf.env.replace_add_global_pthread = True
+ conf.RECURSE('lib/replace')
+
+ conf.env.standalone_tdb = conf.IN_LAUNCH_DIR()
+ conf.env.building_tdb = True
+
+ if not conf.env.standalone_tdb:
+ if conf.CHECK_BUNDLED_SYSTEM_PKG('tdb', minversion=VERSION,
+ implied_deps='replace'):
+ conf.define('USING_SYSTEM_TDB', 1)
+ conf.env.building_tdb = False
+ if not conf.env.disable_python and \
+ conf.CHECK_BUNDLED_SYSTEM_PYTHON('pytdb', 'tdb', minversion=VERSION):
+ conf.define('USING_SYSTEM_PYTDB', 1)
+
+ if (conf.CONFIG_SET('HAVE_ROBUST_MUTEXES') and
+ conf.env.building_tdb and
+ not conf.env.disable_tdb_mutex_locking):
+ conf.define('USE_TDB_MUTEX_LOCKING', 1)
+
+ conf.CHECK_XSLTPROC_MANPAGES()
+
+ conf.SAMBA_CHECK_PYTHON()
+ conf.SAMBA_CHECK_PYTHON_HEADERS()
+
+ conf.SAMBA_CONFIG_H()
+
+ conf.SAMBA_CHECK_UNDEFINED_SYMBOL_FLAGS()
+
+def build(bld):
+ bld.RECURSE('lib/replace')
+
+ COMMON_FILES='''check.c error.c tdb.c traverse.c
+ freelistcheck.c lock.c dump.c freelist.c
+ io.c open.c transaction.c hash.c summary.c rescue.c
+ mutex.c'''
+
+ COMMON_SRC = bld.SUBDIR('common', COMMON_FILES)
+
+ if bld.env.standalone_tdb:
+ bld.env.PKGCONFIGDIR = '${LIBDIR}/pkgconfig'
+ private_library = False
+ else:
+ private_library = True
+
+ if not bld.CONFIG_SET('USING_SYSTEM_TDB'):
+
+ tdb_deps = 'replace'
+
+ if bld.CONFIG_SET('USE_TDB_MUTEX_LOCKING'):
+ tdb_deps += ' pthread'
+
+ bld.SAMBA_LIBRARY('tdb',
+ COMMON_SRC,
+ deps=tdb_deps,
+ includes='include',
+ abi_directory='ABI',
+ abi_match='tdb_*',
+ hide_symbols=True,
+ vnum=VERSION,
+ public_headers=('' if private_library else 'include/tdb.h'),
+ public_headers_install=not private_library,
+ pc_files='tdb.pc',
+ private_library=private_library)
+
+ bld.SAMBA_BINARY('tdbtorture',
+ 'tools/tdbtorture.c',
+ 'tdb',
+ install=False)
+
+ bld.SAMBA_BINARY('tdbtortseq',
+ 'tools/tdbtortseq.c',
+ 'tdb',
+ install=False)
+
+ bld.SAMBA_BINARY('tdbrestore',
+ 'tools/tdbrestore.c',
+ 'tdb', manpages='man/tdbrestore.8')
+
+ bld.SAMBA_BINARY('tdbdump',
+ 'tools/tdbdump.c',
+ 'tdb', manpages='man/tdbdump.8')
+
+ bld.SAMBA_BINARY('tdbbackup',
+ 'tools/tdbbackup.c',
+ 'tdb',
+ manpages='man/tdbbackup.8')
+
+ bld.SAMBA_BINARY('tdbtool',
+ 'tools/tdbtool.c',
+ 'tdb', manpages='man/tdbtool.8')
+
+ if bld.env.standalone_tdb:
+ # FIXME: This hardcoded list is stupid, stupid, stupid.
+ bld.SAMBA_SUBSYSTEM('tdb-test-helpers',
+ 'test/external-agent.c test/lock-tracking.c test/logging.c',
+ tdb_deps,
+ includes='include')
+
+ for t in tdb1_unit_tests:
+ b = "tdb1-" + t
+ s = "test/" + t + ".c"
+ bld.SAMBA_BINARY(b, s, 'replace tdb-test-helpers',
+ includes='include', install=False)
+
+ if not bld.CONFIG_SET('USING_SYSTEM_PYTDB'):
+ bld.SAMBA_PYTHON('pytdb',
+ 'pytdb.c',
+ deps='tdb',
+ enabled=not bld.env.disable_python,
+ realname='tdb.so',
+ cflags='-DPACKAGE_VERSION=\"%s\"' % VERSION)
+
+ if not bld.env.disable_python:
+ bld.SAMBA_SCRIPT('_tdb_text.py',
+ pattern='_tdb_text.py',
+ installdir='python')
+
+ bld.INSTALL_FILES('${PYTHONARCHDIR}', '_tdb_text.py')
+
+def testonly(ctx):
+ '''run tdb testsuite'''
+ ecode = 0
+
+ blddir = Context.g_module.out
+ test_prefix = "%s/st" % (blddir)
+ shutil.rmtree(test_prefix, ignore_errors=True)
+ os.makedirs(test_prefix)
+ os.environ['TEST_DATA_PREFIX'] = test_prefix
+
+ env = samba_utils.LOAD_ENVIRONMENT()
+ # FIXME: This is horrible :(
+ if env.building_tdb:
+ # Create scratch directory for tests.
+ testdir = os.path.join(test_prefix, 'tdb-tests')
+ samba_utils.mkdir_p(testdir)
+ # Symlink back to source dir so it can find tests in test/
+ link = os.path.join(testdir, 'test')
+ if not os.path.exists(link):
+ os.symlink(ctx.path.make_node('test').abspath(), link)
+
+ sh_tests = ["test/test_tdbbackup.sh test/jenkins-be-hash.tdb"]
+
+ for sh_test in sh_tests:
+ cmd = "BINDIR=%s %s" % (blddir, sh_test)
+ print("shell test: " + cmd)
+ ret = samba_utils.RUN_COMMAND(cmd)
+ if ret != 0:
+ print("%s sh test failed" % cmd)
+ ecode = ret
+ break
+
+ for t in tdb1_unit_tests:
+ f = "tdb1-" + t
+ cmd = "cd " + testdir + " && " + os.path.abspath(os.path.join(blddir, f)) + " > test-output 2>&1"
+ print("..." + f)
+ ret = samba_utils.RUN_COMMAND(cmd)
+ if ret != 0:
+ print("%s failed:" % f)
+ samba_utils.RUN_COMMAND("cat " + os.path.join(testdir, 'test-output'))
+ ecode = ret
+ break
+
+ if ecode == 0:
+ cmd = os.path.join(blddir, 'tdbtorture')
+ ret = samba_utils.RUN_COMMAND(cmd)
+ print("testsuite returned %d" % ret)
+ if ret != 0:
+ ecode = ret
+
+ pyret = samba_utils.RUN_PYTHON_TESTS(['python/tests/simple.py'])
+ print("python testsuite returned %d" % pyret)
+ sys.exit(ecode or pyret)
+
+# WAF doesn't build the unit tests for this, maybe because they don't link with tdb?
+# This forces it
+def test(ctx):
+ Options.commands.append('build')
+ Options.commands.append('testonly')
+
+def dist():
+ '''makes a tarball for distribution'''
+ samba_dist.dist()
+
+def reconfigure(ctx):
+ '''reconfigure if config scripts have changed'''
+ samba_utils.reconfigure(ctx)
diff --git a/lib/tdb_wrap/tdb_wrap.c b/lib/tdb_wrap/tdb_wrap.c
new file mode 100644
index 0000000..49585aa
--- /dev/null
+++ b/lib/tdb_wrap/tdb_wrap.c
@@ -0,0 +1,183 @@
+/*
+ Unix SMB/CIFS implementation.
+ TDB wrap functions
+
+ Copyright (C) Andrew Tridgell 2004
+ Copyright (C) Jelmer Vernooij <jelmer@samba.org> 2007
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "replace.h"
+#include "system/filesys.h"
+#include "lib/util/dlinklist.h"
+#include "lib/util/debug.h"
+#include "tdb_wrap.h"
+
+/*
+ Log tdb messages via DEBUG().
+*/
+static void tdb_wrap_log(TDB_CONTEXT *tdb, enum tdb_debug_level level,
+ const char *format, ...) PRINTF_ATTRIBUTE(3,4);
+
+static void tdb_wrap_log(TDB_CONTEXT *tdb, enum tdb_debug_level level,
+ const char *format, ...)
+{
+ va_list ap;
+ char *ptr = NULL;
+ int debuglevel = 0;
+ int ret;
+
+ switch (level) {
+ case TDB_DEBUG_FATAL:
+ debuglevel = 0;
+ break;
+ case TDB_DEBUG_ERROR:
+ debuglevel = 1;
+ break;
+ case TDB_DEBUG_WARNING:
+ debuglevel = 2;
+ break;
+ case TDB_DEBUG_TRACE:
+ debuglevel = 5;
+ break;
+ default:
+ debuglevel = 0;
+ }
+
+ va_start(ap, format);
+ ret = vasprintf(&ptr, format, ap);
+ va_end(ap);
+
+ if (ret != -1) {
+ const char *name = tdb_name(tdb);
+ DEBUG(debuglevel, ("tdb(%s): %s", name ? name : "unnamed", ptr));
+ free(ptr);
+ }
+}
+
+struct tdb_wrap_private {
+ struct tdb_context *tdb;
+ const char *name;
+ struct tdb_wrap_private *next, *prev;
+};
+
+static struct tdb_wrap_private *tdb_list;
+
+/* destroy the last connection to a tdb */
+static int tdb_wrap_private_destructor(struct tdb_wrap_private *w)
+{
+ tdb_close(w->tdb);
+ DLIST_REMOVE(tdb_list, w);
+ return 0;
+}
+
+static struct tdb_wrap_private *tdb_wrap_private_open(TALLOC_CTX *mem_ctx,
+ const char *name,
+ int hash_size,
+ int tdb_flags,
+ int open_flags,
+ mode_t mode)
+{
+ struct tdb_wrap_private *result;
+ struct tdb_logging_context lctx = { .log_fn = tdb_wrap_log };
+
+ result = talloc_pooled_object(mem_ctx, struct tdb_wrap_private,
+ 1, strlen(name)+1);
+ if (result == NULL) {
+ return NULL;
+ }
+ /* Doesn't fail, see talloc_pooled_object */
+ result->name = talloc_strdup(result, name);
+
+ /*
+ * TDB files don't make sense after execve()
+ */
+ open_flags |= O_CLOEXEC;
+
+ result->tdb = tdb_open_ex(name, hash_size, tdb_flags,
+ open_flags, mode, &lctx, NULL);
+ if (result->tdb == NULL) {
+ goto fail;
+ }
+ talloc_set_destructor(result, tdb_wrap_private_destructor);
+ DLIST_ADD(tdb_list, result);
+ return result;
+
+fail:
+ TALLOC_FREE(result);
+ return NULL;
+}
+
+/*
+ wrapped connection to a tdb database
+ to close just talloc_free() the tdb_wrap pointer
+ */
+struct tdb_wrap *tdb_wrap_open(TALLOC_CTX *mem_ctx,
+ const char *name, int hash_size, int tdb_flags,
+ int open_flags, mode_t mode)
+{
+ struct tdb_wrap *result;
+ struct tdb_wrap_private *w;
+
+ if (name == NULL) {
+ errno = EINVAL;
+ return NULL;
+ }
+
+ result = talloc(mem_ctx, struct tdb_wrap);
+ if (result == NULL) {
+ return NULL;
+ }
+
+ for (w=tdb_list;w;w=w->next) {
+ if (strcmp(name, w->name) == 0) {
+ break;
+ }
+ }
+
+ if (w == NULL) {
+
+ if (tdb_flags & TDB_MUTEX_LOCKING) {
+ if (!tdb_runtime_check_for_robust_mutexes()) {
+ tdb_flags &= ~TDB_MUTEX_LOCKING;
+ }
+ }
+
+ w = tdb_wrap_private_open(result, name, hash_size, tdb_flags,
+ open_flags, mode);
+ } else {
+ /*
+ * Correctly use talloc_reference: The tdb will be
+ * closed when "w" is being freed. The caller never
+ * sees "w", so an incorrect use of talloc_free(w)
+ * instead of calling talloc_unlink is not possible.
+ * To avoid having to refcount ourselves, "w" will
+ * have multiple parents that hang off all the
+ * tdb_wrap's being returned from here. Those parents
+ * can be freed without problem.
+ */
+ if (talloc_reference(result, w) == NULL) {
+ goto fail;
+ }
+ }
+ if (w == NULL) {
+ goto fail;
+ }
+ result->tdb = w->tdb;
+ return result;
+fail:
+ TALLOC_FREE(result);
+ return NULL;
+}
diff --git a/lib/tdb_wrap/tdb_wrap.h b/lib/tdb_wrap/tdb_wrap.h
new file mode 100644
index 0000000..de7e332
--- /dev/null
+++ b/lib/tdb_wrap/tdb_wrap.h
@@ -0,0 +1,43 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ database wrap headers
+
+ Copyright (C) Andrew Tridgell 2004
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+/* IMPORTANT: tdb_wrap should be always preferred over tdb_context for end consumer functions
+ it's because if the code will be running inside smbd, then we must use the linked list
+ of open tdb files, to determine if the tdb we desire is already open
+ as otherwise, when you close the tdb (even on a different file descriptor),
+ ALL LOCKS are lost (due to a real screwup in the POSIX specification that nobody has been able to get fixed)
+*/
+
+#ifndef _TDB_WRAP_H_
+#define _TDB_WRAP_H_
+
+#include <talloc.h>
+#include <tdb.h>
+
+struct tdb_wrap {
+ struct tdb_context *tdb;
+};
+
+struct tdb_wrap *tdb_wrap_open(TALLOC_CTX *mem_ctx,
+ const char *name, int hash_size, int tdb_flags,
+ int open_flags, mode_t mode);
+
+#endif /* _TDB_WRAP_H_ */
diff --git a/lib/tdb_wrap/wscript_build b/lib/tdb_wrap/wscript_build
new file mode 100644
index 0000000..7e113c3
--- /dev/null
+++ b/lib/tdb_wrap/wscript_build
@@ -0,0 +1,8 @@
+#!/usr/bin/python
+
+bld.SAMBA_LIBRARY('tdb-wrap',
+ source='tdb_wrap.c',
+ deps='tdb talloc samba-debug',
+ private_library=True,
+ local_include=False
+ )