summaryrefslogtreecommitdiff
path: root/lib/ntdb/test
diff options
context:
space:
mode:
Diffstat (limited to 'lib/ntdb/test')
-rw-r--r--lib/ntdb/test/api-12-store.c57
-rw-r--r--lib/ntdb/test/api-13-delete.c205
-rw-r--r--lib/ntdb/test/api-14-exists.c54
-rw-r--r--lib/ntdb/test/api-16-wipe_all.c46
-rw-r--r--lib/ntdb/test/api-21-parse_record.c67
-rw-r--r--lib/ntdb/test/api-55-transaction.c73
-rw-r--r--lib/ntdb/test/api-80-tdb_fd.c32
-rw-r--r--lib/ntdb/test/api-81-seqnum.c69
-rw-r--r--lib/ntdb/test/api-82-lockattr.c237
-rw-r--r--lib/ntdb/test/api-83-openhook.c96
-rw-r--r--lib/ntdb/test/api-91-get-stats.c57
-rw-r--r--lib/ntdb/test/api-92-get-set-readonly.c105
-rw-r--r--lib/ntdb/test/api-93-repack.c80
-rw-r--r--lib/ntdb/test/api-add-remove-flags.c89
-rw-r--r--lib/ntdb/test/api-check-callback.c86
-rw-r--r--lib/ntdb/test/api-firstkey-nextkey.c159
-rw-r--r--lib/ntdb/test/api-fork-test.c179
-rw-r--r--lib/ntdb/test/api-locktimeout.c193
-rw-r--r--lib/ntdb/test/api-missing-entries.c44
-rw-r--r--lib/ntdb/test/api-open-multiple-times.c83
-rw-r--r--lib/ntdb/test/api-record-expand.c51
-rw-r--r--lib/ntdb/test/api-simple-delete.c39
-rw-r--r--lib/ntdb/test/api-summary.c58
-rw-r--r--lib/ntdb/test/external-agent.c252
-rw-r--r--lib/ntdb/test/external-agent.h51
-rw-r--r--lib/ntdb/test/failtest_helper.c96
-rw-r--r--lib/ntdb/test/failtest_helper.h19
-rw-r--r--lib/ntdb/test/helpapi-external-agent.c7
-rw-r--r--lib/ntdb/test/helprun-external-agent.c7
-rw-r--r--lib/ntdb/test/helprun-layout.c402
-rw-r--r--lib/ntdb/test/layout.h87
-rw-r--r--lib/ntdb/test/lock-tracking.c147
-rw-r--r--lib/ntdb/test/lock-tracking.h25
-rw-r--r--lib/ntdb/test/logging.c30
-rw-r--r--lib/ntdb/test/logging.h17
-rw-r--r--lib/ntdb/test/ntdb-source.h11
-rw-r--r--lib/ntdb/test/run-001-encode.c41
-rw-r--r--lib/ntdb/test/run-001-fls.c33
-rw-r--r--lib/ntdb/test/run-01-new_database.c34
-rw-r--r--lib/ntdb/test/run-02-expand.c62
-rw-r--r--lib/ntdb/test/run-03-coalesce.c178
-rw-r--r--lib/ntdb/test/run-04-basichash.c260
-rw-r--r--lib/ntdb/test/run-05-readonly-open.c71
-rw-r--r--lib/ntdb/test/run-10-simple-store.c58
-rw-r--r--lib/ntdb/test/run-11-simple-fetch.c58
-rw-r--r--lib/ntdb/test/run-12-check.c46
-rw-r--r--lib/ntdb/test/run-15-append.c130
-rw-r--r--lib/ntdb/test/run-20-growhash.c137
-rw-r--r--lib/ntdb/test/run-25-hashoverload.c113
-rw-r--r--lib/ntdb/test/run-30-exhaust-before-expand.c71
-rw-r--r--lib/ntdb/test/run-35-convert.c54
-rw-r--r--lib/ntdb/test/run-50-multiple-freelists.c70
-rw-r--r--lib/ntdb/test/run-56-open-during-transaction.c165
-rw-r--r--lib/ntdb/test/run-57-die-during-transaction.c294
-rw-r--r--lib/ntdb/test/run-64-bit-tdb.c72
-rw-r--r--lib/ntdb/test/run-90-get-set-attributes.c159
-rw-r--r--lib/ntdb/test/run-capabilities.c271
-rw-r--r--lib/ntdb/test/run-expand-in-transaction.c36
-rw-r--r--lib/ntdb/test/run-features.c62
-rw-r--r--lib/ntdb/test/run-lockall.c70
-rw-r--r--lib/ntdb/test/run-remap-in-read_traverse.c57
-rw-r--r--lib/ntdb/test/run-seed.c61
-rw-r--r--lib/ntdb/test/run-tdb_errorstr.c52
-rw-r--r--lib/ntdb/test/run-tdb_foreach.c86
-rw-r--r--lib/ntdb/test/run-traverse.c203
-rw-r--r--lib/ntdb/test/tap-interface.c3
-rw-r--r--lib/ntdb/test/tap-interface.h41
67 files changed, 6358 insertions, 0 deletions
diff --git a/lib/ntdb/test/api-12-store.c b/lib/ntdb/test/api-12-store.c
new file mode 100644
index 0000000000..24d9498755
--- /dev/null
+++ b/lib/ntdb/test/api-12-store.c
@@ -0,0 +1,57 @@
+#include "config.h"
+#include "ntdb.h"
+#include "tap-interface.h"
+#include <ccan/hash/hash.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+#include "logging.h"
+
+/* We use the same seed which we saw a failure on. */
+static uint64_t fixedhash(const void *key, size_t len, uint64_t seed, void *p)
+{
+ return hash64_stable((const unsigned char *)key, len,
+ *(uint64_t *)p);
+}
+
+int main(int argc, char *argv[])
+{
+ unsigned int i, j;
+ struct ntdb_context *ntdb;
+ uint64_t seed = 16014841315512641303ULL;
+ union ntdb_attribute fixed_hattr
+ = { .hash = { .base = { NTDB_ATTRIBUTE_HASH },
+ .fn = fixedhash,
+ .data = &seed } };
+ int flags[] = { NTDB_INTERNAL, NTDB_DEFAULT, NTDB_NOMMAP,
+ NTDB_INTERNAL|NTDB_CONVERT, NTDB_CONVERT,
+ NTDB_NOMMAP|NTDB_CONVERT };
+ NTDB_DATA key = { (unsigned char *)&j, sizeof(j) };
+ NTDB_DATA data = { (unsigned char *)&j, sizeof(j) };
+
+ fixed_hattr.base.next = &tap_log_attr;
+
+ plan_tests(sizeof(flags) / sizeof(flags[0]) * (1 + 500 * 3) + 1);
+ for (i = 0; i < sizeof(flags) / sizeof(flags[0]); i++) {
+ ntdb = ntdb_open("run-12-store.ntdb", flags[i],
+ O_RDWR|O_CREAT|O_TRUNC, 0600, &fixed_hattr);
+ ok1(ntdb);
+ if (!ntdb)
+ continue;
+
+ /* We seemed to lose some keys.
+ * Insert and check they're in there! */
+ for (j = 0; j < 500; j++) {
+ NTDB_DATA d = { NULL, 0 }; /* Bogus GCC warning */
+ ok1(ntdb_store(ntdb, key, data, NTDB_REPLACE) == 0);
+ ok1(ntdb_fetch(ntdb, key, &d) == NTDB_SUCCESS);
+ ok1(ntdb_deq(d, data));
+ free(d.dptr);
+ }
+ ntdb_close(ntdb);
+ }
+
+ ok1(tap_log_messages == 0);
+ return exit_status();
+}
diff --git a/lib/ntdb/test/api-13-delete.c b/lib/ntdb/test/api-13-delete.c
new file mode 100644
index 0000000000..182252b109
--- /dev/null
+++ b/lib/ntdb/test/api-13-delete.c
@@ -0,0 +1,205 @@
+#include "private.h" // For NTDB_TOPLEVEL_HASH_BITS
+#include <ccan/hash/hash.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include "ntdb.h"
+#include "tap-interface.h"
+#include "logging.h"
+
+/* We rig the hash so adjacent-numbered records always clash. */
+static uint64_t clash(const void *key, size_t len, uint64_t seed, void *priv)
+{
+ return ((uint64_t)*(const unsigned int *)key)
+ << (64 - NTDB_TOPLEVEL_HASH_BITS - 1);
+}
+
+/* We use the same seed which we saw a failure on. */
+static uint64_t fixedhash(const void *key, size_t len, uint64_t seed, void *p)
+{
+ return hash64_stable((const unsigned char *)key, len,
+ *(uint64_t *)p);
+}
+
+static bool store_records(struct ntdb_context *ntdb)
+{
+ int i;
+ NTDB_DATA key = { (unsigned char *)&i, sizeof(i) };
+ NTDB_DATA d, data = { (unsigned char *)&i, sizeof(i) };
+
+ for (i = 0; i < 1000; i++) {
+ if (ntdb_store(ntdb, key, data, NTDB_REPLACE) != 0)
+ return false;
+ ntdb_fetch(ntdb, key, &d);
+ if (!ntdb_deq(d, data))
+ return false;
+ free(d.dptr);
+ }
+ return true;
+}
+
+static void test_val(struct ntdb_context *ntdb, uint64_t val)
+{
+ uint64_t v;
+ NTDB_DATA key = { (unsigned char *)&v, sizeof(v) };
+ NTDB_DATA d, data = { (unsigned char *)&v, sizeof(v) };
+
+ /* Insert an entry, then delete it. */
+ v = val;
+ /* Delete should fail. */
+ ok1(ntdb_delete(ntdb, key) == NTDB_ERR_NOEXIST);
+ ok1(ntdb_check(ntdb, NULL, NULL) == 0);
+
+ /* Insert should succeed. */
+ ok1(ntdb_store(ntdb, key, data, NTDB_INSERT) == 0);
+ ok1(ntdb_check(ntdb, NULL, NULL) == 0);
+
+ /* Delete should succeed. */
+ ok1(ntdb_delete(ntdb, key) == 0);
+ ok1(ntdb_check(ntdb, NULL, NULL) == 0);
+
+ /* Re-add it, then add collision. */
+ ok1(ntdb_store(ntdb, key, data, NTDB_INSERT) == 0);
+ v = val + 1;
+ ok1(ntdb_store(ntdb, key, data, NTDB_INSERT) == 0);
+ ok1(ntdb_check(ntdb, NULL, NULL) == 0);
+
+ /* Can find both? */
+ ok1(ntdb_fetch(ntdb, key, &d) == NTDB_SUCCESS);
+ ok1(d.dsize == data.dsize);
+ free(d.dptr);
+ v = val;
+ ok1(ntdb_fetch(ntdb, key, &d) == NTDB_SUCCESS);
+ ok1(d.dsize == data.dsize);
+ free(d.dptr);
+
+ /* Delete second one. */
+ v = val + 1;
+ ok1(ntdb_delete(ntdb, key) == 0);
+ ok1(ntdb_check(ntdb, NULL, NULL) == 0);
+
+ /* Re-add */
+ ok1(ntdb_store(ntdb, key, data, NTDB_INSERT) == 0);
+ ok1(ntdb_check(ntdb, NULL, NULL) == 0);
+
+ /* Now, try deleting first one. */
+ v = val;
+ ok1(ntdb_delete(ntdb, key) == 0);
+ ok1(ntdb_check(ntdb, NULL, NULL) == 0);
+
+ /* Can still find second? */
+ v = val + 1;
+ ok1(ntdb_fetch(ntdb, key, &d) == NTDB_SUCCESS);
+ ok1(d.dsize == data.dsize);
+ free(d.dptr);
+
+ /* Now, this will be ideally placed. */
+ v = val + 2;
+ ok1(ntdb_store(ntdb, key, data, NTDB_INSERT) == 0);
+ ok1(ntdb_check(ntdb, NULL, NULL) == 0);
+
+ /* This will collide with both. */
+ v = val;
+ ok1(ntdb_store(ntdb, key, data, NTDB_INSERT) == 0);
+
+ /* We can still find them all, right? */
+ ok1(ntdb_fetch(ntdb, key, &d) == NTDB_SUCCESS);
+ ok1(d.dsize == data.dsize);
+ free(d.dptr);
+ v = val + 1;
+ ok1(ntdb_fetch(ntdb, key, &d) == NTDB_SUCCESS);
+ ok1(d.dsize == data.dsize);
+ free(d.dptr);
+ v = val + 2;
+ ok1(ntdb_fetch(ntdb, key, &d) == NTDB_SUCCESS);
+ ok1(d.dsize == data.dsize);
+ free(d.dptr);
+
+ /* And if we delete val + 1, that val + 2 should not move! */
+ v = val + 1;
+ ok1(ntdb_delete(ntdb, key) == 0);
+ ok1(ntdb_check(ntdb, NULL, NULL) == 0);
+
+ v = val;
+ ok1(ntdb_fetch(ntdb, key, &d) == NTDB_SUCCESS);
+ ok1(d.dsize == data.dsize);
+ free(d.dptr);
+ v = val + 2;
+ ok1(ntdb_fetch(ntdb, key, &d) == NTDB_SUCCESS);
+ ok1(d.dsize == data.dsize);
+ free(d.dptr);
+
+ /* Delete those two, so we are empty. */
+ ok1(ntdb_delete(ntdb, key) == 0);
+ v = val;
+ ok1(ntdb_delete(ntdb, key) == 0);
+
+ ok1(ntdb_check(ntdb, NULL, NULL) == 0);
+}
+
+int main(int argc, char *argv[])
+{
+ unsigned int i, j;
+ struct ntdb_context *ntdb;
+ uint64_t seed = 16014841315512641303ULL;
+ union ntdb_attribute clash_hattr
+ = { .hash = { .base = { NTDB_ATTRIBUTE_HASH },
+ .fn = clash } };
+ union ntdb_attribute fixed_hattr
+ = { .hash = { .base = { NTDB_ATTRIBUTE_HASH },
+ .fn = fixedhash,
+ .data = &seed } };
+ int flags[] = { NTDB_INTERNAL, NTDB_DEFAULT, NTDB_NOMMAP,
+ NTDB_INTERNAL|NTDB_CONVERT, NTDB_CONVERT,
+ NTDB_NOMMAP|NTDB_CONVERT };
+ /* These two values gave trouble before. */
+ int vals[] = { 755, 837 };
+
+ clash_hattr.base.next = &tap_log_attr;
+ fixed_hattr.base.next = &tap_log_attr;
+
+ plan_tests(sizeof(flags) / sizeof(flags[0])
+ * (39 * 3 + 5 + sizeof(vals)/sizeof(vals[0])*2) + 1);
+ for (i = 0; i < sizeof(flags) / sizeof(flags[0]); i++) {
+ ntdb = ntdb_open("run-13-delete.ntdb", flags[i],
+ O_RDWR|O_CREAT|O_TRUNC, 0600, &clash_hattr);
+ ok1(ntdb);
+ if (!ntdb)
+ continue;
+
+ /* Check start of hash table. */
+ test_val(ntdb, 0);
+
+ /* Check end of hash table. */
+ test_val(ntdb, -1ULL);
+
+ /* Check mixed bitpattern. */
+ test_val(ntdb, 0x123456789ABCDEF0ULL);
+
+ ok1(!ntdb->file || (ntdb->file->allrecord_lock.count == 0
+ && ntdb->file->num_lockrecs == 0));
+ ntdb_close(ntdb);
+
+ /* Deleting these entries in the db gave problems. */
+ ntdb = ntdb_open("run-13-delete.ntdb", flags[i],
+ O_RDWR|O_CREAT|O_TRUNC, 0600, &fixed_hattr);
+ ok1(ntdb);
+ if (!ntdb)
+ continue;
+
+ ok1(store_records(ntdb));
+ ok1(ntdb_check(ntdb, NULL, NULL) == 0);
+ for (j = 0; j < sizeof(vals)/sizeof(vals[0]); j++) {
+ NTDB_DATA key;
+
+ key.dptr = (unsigned char *)&vals[j];
+ key.dsize = sizeof(vals[j]);
+ ok1(ntdb_delete(ntdb, key) == 0);
+ ok1(ntdb_check(ntdb, NULL, NULL) == 0);
+ }
+ ntdb_close(ntdb);
+ }
+
+ ok1(tap_log_messages == 0);
+ return exit_status();
+}
diff --git a/lib/ntdb/test/api-14-exists.c b/lib/ntdb/test/api-14-exists.c
new file mode 100644
index 0000000000..88663cad65
--- /dev/null
+++ b/lib/ntdb/test/api-14-exists.c
@@ -0,0 +1,54 @@
+#include "config.h"
+#include "ntdb.h"
+#include "tap-interface.h"
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include "logging.h"
+
+static bool test_records(struct ntdb_context *ntdb)
+{
+ int i;
+ NTDB_DATA key = { (unsigned char *)&i, sizeof(i) };
+ NTDB_DATA data = { (unsigned char *)&i, sizeof(i) };
+
+ for (i = 0; i < 1000; i++) {
+ if (ntdb_exists(ntdb, key))
+ return false;
+ if (ntdb_store(ntdb, key, data, NTDB_REPLACE) != 0)
+ return false;
+ if (!ntdb_exists(ntdb, key))
+ return false;
+ }
+
+ for (i = 0; i < 1000; i++) {
+ if (!ntdb_exists(ntdb, key))
+ return false;
+ if (ntdb_delete(ntdb, key) != 0)
+ return false;
+ if (ntdb_exists(ntdb, key))
+ return false;
+ }
+ return true;
+}
+
+int main(int argc, char *argv[])
+{
+ unsigned int i;
+ struct ntdb_context *ntdb;
+ int flags[] = { NTDB_INTERNAL, NTDB_DEFAULT, NTDB_NOMMAP,
+ NTDB_INTERNAL|NTDB_CONVERT, NTDB_CONVERT,
+ NTDB_NOMMAP|NTDB_CONVERT };
+
+ plan_tests(sizeof(flags) / sizeof(flags[0]) * 2 + 1);
+ for (i = 0; i < sizeof(flags) / sizeof(flags[0]); i++) {
+ ntdb = ntdb_open("run-14-exists.ntdb", flags[i],
+ O_RDWR|O_CREAT|O_TRUNC, 0600, &tap_log_attr);
+ if (ok1(ntdb))
+ ok1(test_records(ntdb));
+ ntdb_close(ntdb);
+ }
+
+ ok1(tap_log_messages == 0);
+ return exit_status();
+}
diff --git a/lib/ntdb/test/api-16-wipe_all.c b/lib/ntdb/test/api-16-wipe_all.c
new file mode 100644
index 0000000000..c1bda8e4f4
--- /dev/null
+++ b/lib/ntdb/test/api-16-wipe_all.c
@@ -0,0 +1,46 @@
+#include "config.h"
+#include "ntdb.h"
+#include "tap-interface.h"
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include "logging.h"
+
+static bool add_records(struct ntdb_context *ntdb)
+{
+ int i;
+ NTDB_DATA key = { (unsigned char *)&i, sizeof(i) };
+ NTDB_DATA data = { (unsigned char *)&i, sizeof(i) };
+
+ for (i = 0; i < 1000; i++) {
+ if (ntdb_store(ntdb, key, data, NTDB_REPLACE) != 0)
+ return false;
+ }
+ return true;
+}
+
+
+int main(int argc, char *argv[])
+{
+ unsigned int i;
+ struct ntdb_context *ntdb;
+ int flags[] = { NTDB_INTERNAL, NTDB_DEFAULT, NTDB_NOMMAP,
+ NTDB_INTERNAL|NTDB_CONVERT, NTDB_CONVERT,
+ NTDB_NOMMAP|NTDB_CONVERT };
+
+ plan_tests(sizeof(flags) / sizeof(flags[0]) * 4 + 1);
+ for (i = 0; i < sizeof(flags) / sizeof(flags[0]); i++) {
+ ntdb = ntdb_open("run-16-wipe_all.ntdb", flags[i],
+ O_RDWR|O_CREAT|O_TRUNC, 0600, &tap_log_attr);
+ if (ok1(ntdb)) {
+ NTDB_DATA key;
+ ok1(add_records(ntdb));
+ ok1(ntdb_wipe_all(ntdb) == NTDB_SUCCESS);
+ ok1(ntdb_firstkey(ntdb, &key) == NTDB_ERR_NOEXIST);
+ ntdb_close(ntdb);
+ }
+ }
+
+ ok1(tap_log_messages == 0);
+ return exit_status();
+}
diff --git a/lib/ntdb/test/api-21-parse_record.c b/lib/ntdb/test/api-21-parse_record.c
new file mode 100644
index 0000000000..fa48562e17
--- /dev/null
+++ b/lib/ntdb/test/api-21-parse_record.c
@@ -0,0 +1,67 @@
+#include "config.h"
+#include "ntdb.h"
+#include "tap-interface.h"
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include "logging.h"
+
+static enum NTDB_ERROR parse(NTDB_DATA key, NTDB_DATA data, NTDB_DATA *expected)
+{
+ if (!ntdb_deq(data, *expected))
+ return NTDB_ERR_EINVAL;
+ return NTDB_SUCCESS;
+}
+
+static enum NTDB_ERROR parse_err(NTDB_DATA key, NTDB_DATA data, void *unused)
+{
+ return 100;
+}
+
+static bool test_records(struct ntdb_context *ntdb)
+{
+ int i;
+ NTDB_DATA key = { (unsigned char *)&i, sizeof(i) };
+ NTDB_DATA data = { (unsigned char *)&i, sizeof(i) };
+
+ for (i = 0; i < 1000; i++) {
+ if (ntdb_store(ntdb, key, data, NTDB_REPLACE) != 0)
+ return false;
+ }
+
+ for (i = 0; i < 1000; i++) {
+ if (ntdb_parse_record(ntdb, key, parse, &data) != NTDB_SUCCESS)
+ return false;
+ }
+
+ if (ntdb_parse_record(ntdb, key, parse, &data) != NTDB_ERR_NOEXIST)
+ return false;
+
+ /* Test error return from parse function. */
+ i = 0;
+ if (ntdb_parse_record(ntdb, key, parse_err, NULL) != 100)
+ return false;
+
+ return true;
+}
+
+int main(int argc, char *argv[])
+{
+ unsigned int i;
+ struct ntdb_context *ntdb;
+ int flags[] = { NTDB_INTERNAL, NTDB_DEFAULT, NTDB_NOMMAP,
+ NTDB_INTERNAL|NTDB_CONVERT, NTDB_CONVERT,
+ NTDB_NOMMAP|NTDB_CONVERT };
+
+ plan_tests(sizeof(flags) / sizeof(flags[0]) * 2 + 1);
+ for (i = 0; i < sizeof(flags) / sizeof(flags[0]); i++) {
+ ntdb = ntdb_open("api-21-parse_record.ntdb", flags[i],
+ O_RDWR|O_CREAT|O_TRUNC, 0600, &tap_log_attr);
+ if (ok1(ntdb))
+ ok1(test_records(ntdb));
+ ntdb_close(ntdb);
+ }
+
+ ok1(tap_log_messages == 0);
+ return exit_status();
+}
diff --git a/lib/ntdb/test/api-55-transaction.c b/lib/ntdb/test/api-55-transaction.c
new file mode 100644
index 0000000000..d51dd0b13e
--- /dev/null
+++ b/lib/ntdb/test/api-55-transaction.c
@@ -0,0 +1,73 @@
+#include "private.h" // struct ntdb_context
+#include "ntdb.h"
+#include "tap-interface.h"
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include "logging.h"
+
+int main(int argc, char *argv[])
+{
+ unsigned int i;
+ struct ntdb_context *ntdb;
+ unsigned char *buffer;
+ int flags[] = { NTDB_DEFAULT, NTDB_NOMMAP,
+ NTDB_CONVERT, NTDB_NOMMAP|NTDB_CONVERT };
+ NTDB_DATA key = ntdb_mkdata("key", 3);
+ NTDB_DATA data;
+
+ buffer = malloc(1000);
+ for (i = 0; i < 1000; i++)
+ buffer[i] = i;
+
+ plan_tests(sizeof(flags) / sizeof(flags[0]) * 20 + 1);
+
+ for (i = 0; i < sizeof(flags) / sizeof(flags[0]); i++) {
+ ntdb = ntdb_open("run-55-transaction.ntdb", flags[i],
+ O_RDWR|O_CREAT|O_TRUNC, 0600, &tap_log_attr);
+ ok1(ntdb);
+ if (!ntdb)
+ continue;
+
+ ok1(ntdb_transaction_start(ntdb) == 0);
+ data.dptr = buffer;
+ data.dsize = 1000;
+ ok1(ntdb_store(ntdb, key, data, NTDB_INSERT) == 0);
+ ok1(ntdb_fetch(ntdb, key, &data) == NTDB_SUCCESS);
+ ok1(data.dsize == 1000);
+ ok1(memcmp(data.dptr, buffer, data.dsize) == 0);
+ free(data.dptr);
+
+ /* Cancelling a transaction means no store */
+ ntdb_transaction_cancel(ntdb);
+ ok1(ntdb->file->allrecord_lock.count == 0
+ && ntdb->file->num_lockrecs == 0);
+ ok1(ntdb_check(ntdb, NULL, NULL) == 0);
+ ok1(ntdb_fetch(ntdb, key, &data) == NTDB_ERR_NOEXIST);
+
+ /* Commit the transaction. */
+ ok1(ntdb_transaction_start(ntdb) == 0);
+ data.dptr = buffer;
+ data.dsize = 1000;
+ ok1(ntdb_store(ntdb, key, data, NTDB_INSERT) == 0);
+ ok1(ntdb_fetch(ntdb, key, &data) == NTDB_SUCCESS);
+ ok1(data.dsize == 1000);
+ ok1(memcmp(data.dptr, buffer, data.dsize) == 0);
+ free(data.dptr);
+ ok1(ntdb_transaction_commit(ntdb) == 0);
+ ok1(ntdb->file->allrecord_lock.count == 0
+ && ntdb->file->num_lockrecs == 0);
+ ok1(ntdb_check(ntdb, NULL, NULL) == 0);
+ ok1(ntdb_fetch(ntdb, key, &data) == NTDB_SUCCESS);
+ ok1(data.dsize == 1000);
+ ok1(memcmp(data.dptr, buffer, data.dsize) == 0);
+ free(data.dptr);
+
+ ntdb_close(ntdb);
+ }
+
+ ok1(tap_log_messages == 0);
+ free(buffer);
+ return exit_status();
+}
diff --git a/lib/ntdb/test/api-80-tdb_fd.c b/lib/ntdb/test/api-80-tdb_fd.c
new file mode 100644
index 0000000000..39a9df414e
--- /dev/null
+++ b/lib/ntdb/test/api-80-tdb_fd.c
@@ -0,0 +1,32 @@
+#include "config.h"
+#include "ntdb.h"
+#include "tap-interface.h"
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include "logging.h"
+
+int main(int argc, char *argv[])
+{
+ unsigned int i;
+ struct ntdb_context *ntdb;
+ int flags[] = { NTDB_INTERNAL, NTDB_DEFAULT, NTDB_NOMMAP,
+ NTDB_INTERNAL|NTDB_CONVERT, NTDB_CONVERT,
+ NTDB_NOMMAP|NTDB_CONVERT };
+
+ plan_tests(sizeof(flags) / sizeof(flags[0]) * 3);
+ for (i = 0; i < sizeof(flags) / sizeof(flags[0]); i++) {
+ ntdb = ntdb_open("api-80-ntdb_fd.ntdb", flags[i],
+ O_RDWR|O_CREAT|O_TRUNC, 0600, &tap_log_attr);
+ if (!ok1(ntdb))
+ continue;
+
+ if (flags[i] & NTDB_INTERNAL)
+ ok1(ntdb_fd(ntdb) == -1);
+ else
+ ok1(ntdb_fd(ntdb) > 2);
+ ntdb_close(ntdb);
+ ok1(tap_log_messages == 0);
+ }
+ return exit_status();
+}
diff --git a/lib/ntdb/test/api-81-seqnum.c b/lib/ntdb/test/api-81-seqnum.c
new file mode 100644
index 0000000000..93ad53ab07
--- /dev/null
+++ b/lib/ntdb/test/api-81-seqnum.c
@@ -0,0 +1,69 @@
+#include "config.h"
+#include "ntdb.h"
+#include "tap-interface.h"
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include "logging.h"
+
+int main(int argc, char *argv[])
+{
+ unsigned int i, seq;
+ struct ntdb_context *ntdb;
+ NTDB_DATA d = { NULL, 0 }; /* Bogus GCC warning */
+ NTDB_DATA key = ntdb_mkdata("key", 3);
+ NTDB_DATA data = ntdb_mkdata("data", 4);
+ int flags[] = { NTDB_INTERNAL, NTDB_DEFAULT, NTDB_NOMMAP,
+ NTDB_INTERNAL|NTDB_CONVERT, NTDB_CONVERT,
+ NTDB_NOMMAP|NTDB_CONVERT };
+
+ plan_tests(sizeof(flags) / sizeof(flags[0]) * 15 + 4 * 13);
+ for (i = 0; i < sizeof(flags) / sizeof(flags[0]); i++) {
+ ntdb = ntdb_open("api-81-seqnum.ntdb", flags[i]|NTDB_SEQNUM,
+ O_RDWR|O_CREAT|O_TRUNC, 0600, &tap_log_attr);
+ if (!ok1(ntdb))
+ continue;
+
+ seq = 0;
+ ok1(ntdb_get_seqnum(ntdb) == seq);
+ ok1(ntdb_store(ntdb, key, data, NTDB_INSERT) == 0);
+ ok1(ntdb_get_seqnum(ntdb) == ++seq);
+ /* Fetch doesn't change seqnum */
+ if (ok1(ntdb_fetch(ntdb, key, &d) == NTDB_SUCCESS))
+ free(d.dptr);
+ ok1(ntdb_get_seqnum(ntdb) == seq);
+ ok1(ntdb_append(ntdb, key, data) == NTDB_SUCCESS);
+ ok1(ntdb_get_seqnum(ntdb) == ++seq);
+
+ ok1(ntdb_delete(ntdb, key) == NTDB_SUCCESS);
+ ok1(ntdb_get_seqnum(ntdb) == ++seq);
+ /* Empty append works */
+ ok1(ntdb_append(ntdb, key, data) == NTDB_SUCCESS);
+ ok1(ntdb_get_seqnum(ntdb) == ++seq);
+
+ ok1(ntdb_wipe_all(ntdb) == NTDB_SUCCESS);
+ ok1(ntdb_get_seqnum(ntdb) == ++seq);
+
+ if (!(flags[i] & NTDB_INTERNAL)) {
+ ok1(ntdb_transaction_start(ntdb) == NTDB_SUCCESS);
+ ok1(ntdb_store(ntdb, key, data, NTDB_INSERT) == 0);
+ ok1(ntdb_get_seqnum(ntdb) == ++seq);
+ ok1(ntdb_append(ntdb, key, data) == NTDB_SUCCESS);
+ ok1(ntdb_get_seqnum(ntdb) == ++seq);
+ ok1(ntdb_delete(ntdb, key) == NTDB_SUCCESS);
+ ok1(ntdb_get_seqnum(ntdb) == ++seq);
+ ok1(ntdb_transaction_commit(ntdb) == NTDB_SUCCESS);
+ ok1(ntdb_get_seqnum(ntdb) == seq);
+
+ ok1(ntdb_transaction_start(ntdb) == NTDB_SUCCESS);
+ ok1(ntdb_store(ntdb, key, data, NTDB_INSERT) == 0);
+ ok1(ntdb_get_seqnum(ntdb) == seq + 1);
+ ntdb_transaction_cancel(ntdb);
+ ok1(ntdb_get_seqnum(ntdb) == seq);
+ }
+ ntdb_close(ntdb);
+ ok1(tap_log_messages == 0);
+ }
+ return exit_status();
+}
diff --git a/lib/ntdb/test/api-82-lockattr.c b/lib/ntdb/test/api-82-lockattr.c
new file mode 100644
index 0000000000..51bb939f59
--- /dev/null
+++ b/lib/ntdb/test/api-82-lockattr.c
@@ -0,0 +1,237 @@
+#include "private.h" // for ntdb_fcntl_unlock
+#include "ntdb.h"
+#include "tap-interface.h"
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <errno.h>
+#include "logging.h"
+
+static int mylock(int fd, int rw, off_t off, off_t len, bool waitflag,
+ void *_err)
+{
+ int *lock_err = _err;
+ struct flock fl;
+ int ret;
+
+ if (*lock_err) {
+ errno = *lock_err;
+ return -1;
+ }
+
+ do {
+ fl.l_type = rw;
+ fl.l_whence = SEEK_SET;
+ fl.l_start = off;
+ fl.l_len = len;
+
+ if (waitflag)
+ ret = fcntl(fd, F_SETLKW, &fl);
+ else
+ ret = fcntl(fd, F_SETLK, &fl);
+ } while (ret != 0 && errno == EINTR);
+
+ return ret;
+}
+
+static int trav_err;
+static int trav(struct ntdb_context *ntdb, NTDB_DATA k, NTDB_DATA d, int *terr)
+{
+ *terr = trav_err;
+ return 0;
+}
+
+int main(int argc, char *argv[])
+{
+ unsigned int i;
+ struct ntdb_context *ntdb;
+ int flags[] = { NTDB_DEFAULT, NTDB_NOMMAP,
+ NTDB_CONVERT, NTDB_NOMMAP|NTDB_CONVERT };
+ union ntdb_attribute lock_attr;
+ NTDB_DATA key = ntdb_mkdata("key", 3);
+ NTDB_DATA data = ntdb_mkdata("data", 4);
+ int lock_err;
+
+ lock_attr.base.attr = NTDB_ATTRIBUTE_FLOCK;
+ lock_attr.base.next = &tap_log_attr;
+ lock_attr.flock.lock = mylock;
+ lock_attr.flock.unlock = ntdb_fcntl_unlock;
+ lock_attr.flock.data = &lock_err;
+
+ plan_tests(sizeof(flags) / sizeof(flags[0]) * 80);
+
+ for (i = 0; i < sizeof(flags) / sizeof(flags[0]); i++) {
+ NTDB_DATA d;
+
+ /* Nonblocking open; expect no error message. */
+ lock_err = EAGAIN;
+ ntdb = ntdb_open("run-82-lockattr.ntdb", flags[i],
+ O_RDWR|O_CREAT|O_TRUNC, 0600, &lock_attr);
+ ok(errno == lock_err, "Errno is %u", errno);
+ ok1(!ntdb);
+ ok1(tap_log_messages == 0);
+
+ lock_err = EINTR;
+ ntdb = ntdb_open("run-82-lockattr.ntdb", flags[i],
+ O_RDWR|O_CREAT|O_TRUNC, 0600, &lock_attr);
+ ok(errno == lock_err, "Errno is %u", errno);
+ ok1(!ntdb);
+ ok1(tap_log_messages == 0);
+
+ /* Forced fail open. */
+ lock_err = ENOMEM;
+ ntdb = ntdb_open("run-82-lockattr.ntdb", flags[i],
+ O_RDWR|O_CREAT|O_TRUNC, 0600, &lock_attr);
+ ok1(errno == lock_err);
+ ok1(!ntdb);
+ ok1(tap_log_messages == 1);
+ tap_log_messages = 0;
+
+ lock_err = 0;
+ ntdb = ntdb_open("run-82-lockattr.ntdb", flags[i],
+ O_RDWR|O_CREAT|O_TRUNC, 0600, &lock_attr);
+ if (!ok1(ntdb))
+ continue;
+ ok1(tap_log_messages == 0);
+
+ /* Nonblocking store. */
+ lock_err = EAGAIN;
+ ok1(ntdb_store(ntdb, key, data, NTDB_REPLACE) == NTDB_ERR_LOCK);
+ ok1(tap_log_messages == 0);
+ lock_err = EINTR;
+ ok1(ntdb_store(ntdb, key, data, NTDB_REPLACE) == NTDB_ERR_LOCK);
+ ok1(tap_log_messages == 0);
+ lock_err = ENOMEM;
+ ok1(ntdb_store(ntdb, key, data, NTDB_REPLACE) == NTDB_ERR_LOCK);
+ ok1(tap_log_messages == 1);
+ tap_log_messages = 0;
+
+ /* Nonblocking fetch. */
+ lock_err = EAGAIN;
+ ok1(!ntdb_exists(ntdb, key));
+ ok1(tap_log_messages == 0);
+ lock_err = EINTR;
+ ok1(!ntdb_exists(ntdb, key));
+ ok1(tap_log_messages == 0);
+ lock_err = ENOMEM;
+ ok1(!ntdb_exists(ntdb, key));
+ ok1(tap_log_messages == 1);
+ tap_log_messages = 0;
+
+ lock_err = EAGAIN;
+ ok1(ntdb_fetch(ntdb, key, &d) == NTDB_ERR_LOCK);
+ ok1(tap_log_messages == 0);
+ lock_err = EINTR;
+ ok1(ntdb_fetch(ntdb, key, &d) == NTDB_ERR_LOCK);
+ ok1(tap_log_messages == 0);
+ lock_err = ENOMEM;
+ ok1(ntdb_fetch(ntdb, key, &d) == NTDB_ERR_LOCK);
+ ok1(tap_log_messages == 1);
+ tap_log_messages = 0;
+
+ /* Nonblocking delete. */
+ lock_err = EAGAIN;
+ ok1(ntdb_delete(ntdb, key) == NTDB_ERR_LOCK);
+ ok1(tap_log_messages == 0);
+ lock_err = EINTR;
+ ok1(ntdb_delete(ntdb, key) == NTDB_ERR_LOCK);
+ ok1(tap_log_messages == 0);
+ lock_err = ENOMEM;
+ ok1(ntdb_delete(ntdb, key) == NTDB_ERR_LOCK);
+ ok1(tap_log_messages == 1);
+ tap_log_messages = 0;
+
+ /* Nonblocking locks. */
+ lock_err = EAGAIN;
+ ok1(ntdb_chainlock(ntdb, key) == NTDB_ERR_LOCK);
+ ok1(tap_log_messages == 0);
+ lock_err = EINTR;
+ ok1(ntdb_chainlock(ntdb, key) == NTDB_ERR_LOCK);
+ ok1(tap_log_messages == 0);
+ lock_err = ENOMEM;
+ ok1(ntdb_chainlock(ntdb, key) == NTDB_ERR_LOCK);
+ ok1(tap_log_messages == 1);
+ tap_log_messages = 0;
+
+ lock_err = EAGAIN;
+ ok1(ntdb_chainlock_read(ntdb, key) == NTDB_ERR_LOCK);
+ ok1(tap_log_messages == 0);
+ lock_err = EINTR;
+ ok1(ntdb_chainlock_read(ntdb, key) == NTDB_ERR_LOCK);
+ ok1(tap_log_messages == 0);
+ lock_err = ENOMEM;
+ ok1(ntdb_chainlock_read(ntdb, key) == NTDB_ERR_LOCK);
+ ok1(tap_log_messages == 1);
+ tap_log_messages = 0;
+
+ lock_err = EAGAIN;
+ ok1(ntdb_lockall(ntdb) == NTDB_ERR_LOCK);
+ ok1(tap_log_messages == 0);
+ lock_err = EINTR;
+ ok1(ntdb_lockall(ntdb) == NTDB_ERR_LOCK);
+ ok1(tap_log_messages == 0);
+ lock_err = ENOMEM;
+ ok1(ntdb_lockall(ntdb) == NTDB_ERR_LOCK);
+ /* This actually does divide and conquer. */
+ ok1(tap_log_messages > 0);
+ tap_log_messages = 0;
+
+ lock_err = EAGAIN;
+ ok1(ntdb_lockall_read(ntdb) == NTDB_ERR_LOCK);
+ ok1(tap_log_messages == 0);
+ lock_err = EINTR;
+ ok1(ntdb_lockall_read(ntdb) == NTDB_ERR_LOCK);
+ ok1(tap_log_messages == 0);
+ lock_err = ENOMEM;
+ ok1(ntdb_lockall_read(ntdb) == NTDB_ERR_LOCK);
+ ok1(tap_log_messages > 0);
+ tap_log_messages = 0;
+
+ /* Nonblocking traverse; go nonblock partway through. */
+ lock_err = 0;
+ ok1(ntdb_store(ntdb, key, data, NTDB_REPLACE) == 0);
+ trav_err = EAGAIN;
+ ok1(ntdb_traverse(ntdb, trav, &lock_err) == NTDB_ERR_LOCK);
+ ok1(tap_log_messages == 0);
+ trav_err = EINTR;
+ lock_err = 0;
+ ok1(ntdb_traverse(ntdb, trav, &lock_err) == NTDB_ERR_LOCK);
+ ok1(tap_log_messages == 0);
+ trav_err = ENOMEM;
+ lock_err = 0;
+ ok1(ntdb_traverse(ntdb, trav, &lock_err) == NTDB_ERR_LOCK);
+ ok1(tap_log_messages == 1);
+ tap_log_messages = 0;
+
+ /* Nonblocking transactions. */
+ lock_err = EAGAIN;
+ ok1(ntdb_transaction_start(ntdb) == NTDB_ERR_LOCK);
+ ok1(tap_log_messages == 0);
+ lock_err = EINTR;
+ ok1(ntdb_transaction_start(ntdb) == NTDB_ERR_LOCK);
+ ok1(tap_log_messages == 0);
+ lock_err = ENOMEM;
+ ok1(ntdb_transaction_start(ntdb) == NTDB_ERR_LOCK);
+ ok1(tap_log_messages == 1);
+ tap_log_messages = 0;
+
+ /* Nonblocking transaction prepare. */
+ lock_err = 0;
+ ok1(ntdb_transaction_start(ntdb) == 0);
+ ok1(ntdb_delete(ntdb, key) == 0);
+
+ lock_err = EAGAIN;
+ ok1(ntdb_transaction_prepare_commit(ntdb) == NTDB_ERR_LOCK);
+ ok1(tap_log_messages == 0);
+
+ lock_err = 0;
+ ok1(ntdb_transaction_prepare_commit(ntdb) == 0);
+ ok1(ntdb_transaction_commit(ntdb) == 0);
+
+ /* And the transaction was committed, right? */
+ ok1(!ntdb_exists(ntdb, key));
+ ntdb_close(ntdb);
+ ok1(tap_log_messages == 0);
+ }
+ return exit_status();
+}
diff --git a/lib/ntdb/test/api-83-openhook.c b/lib/ntdb/test/api-83-openhook.c
new file mode 100644
index 0000000000..9f474c9ab8
--- /dev/null
+++ b/lib/ntdb/test/api-83-openhook.c
@@ -0,0 +1,96 @@
+#include "config.h"
+#include "ntdb.h"
+#include "tap-interface.h"
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <stdarg.h>
+#include <unistd.h>
+#include "external-agent.h"
+#include "logging.h"
+
+static enum NTDB_ERROR clear_if_first(int fd, void *arg)
+{
+/* We hold a lock offset 4 always, so we can tell if anyone is holding it.
+ * (This is compatible with tdb's TDB_CLEAR_IF_FIRST flag). */
+ struct flock fl;
+
+ if (arg != clear_if_first)
+ return NTDB_ERR_CORRUPT;
+
+ fl.l_type = F_WRLCK;
+ fl.l_whence = SEEK_SET;
+ fl.l_start = 4;
+ fl.l_len = 1;
+
+ if (fcntl(fd, F_SETLK, &fl) == 0) {
+ /* We must be first ones to open it! */
+ diag("truncating file!");
+ if (ftruncate(fd, 0) != 0) {
+ return NTDB_ERR_IO;
+ }
+ }
+ fl.l_type = F_RDLCK;
+ if (fcntl(fd, F_SETLKW, &fl) != 0) {
+ return NTDB_ERR_IO;
+ }
+ return NTDB_SUCCESS;
+}
+
+int main(int argc, char *argv[])
+{
+ unsigned int i;
+ struct ntdb_context *ntdb;
+ struct agent *agent;
+ union ntdb_attribute cif;
+ NTDB_DATA key = ntdb_mkdata("key", 3);
+ int flags[] = { NTDB_DEFAULT, NTDB_NOMMAP,
+ NTDB_CONVERT, NTDB_NOMMAP|NTDB_CONVERT };
+
+ cif.openhook.base.attr = NTDB_ATTRIBUTE_OPENHOOK;
+ cif.openhook.base.next = &tap_log_attr;
+ cif.openhook.fn = clear_if_first;
+ cif.openhook.data = clear_if_first;
+
+ agent = prepare_external_agent();
+ plan_tests(sizeof(flags) / sizeof(flags[0]) * 13);
+ for (i = 0; i < sizeof(flags) / sizeof(flags[0]); i++) {
+ /* Create it */
+ ntdb = ntdb_open("run-83-openhook.ntdb", flags[i],
+ O_RDWR|O_CREAT|O_TRUNC, 0600, NULL);
+ ok1(ntdb);
+ ok1(ntdb_store(ntdb, key, key, NTDB_REPLACE) == 0);
+ ntdb_close(ntdb);
+
+ /* Now, open with CIF, should clear it. */
+ ntdb = ntdb_open("run-83-openhook.ntdb", flags[i],
+ O_RDWR, 0, &cif);
+ ok1(ntdb);
+ ok1(!ntdb_exists(ntdb, key));
+ ok1(ntdb_store(ntdb, key, key, NTDB_REPLACE) == 0);
+
+ /* Agent should not clear it, since it's still open. */
+ ok1(external_agent_operation(agent, OPEN_WITH_HOOK,
+ "run-83-openhook.ntdb") == SUCCESS);
+ ok1(external_agent_operation(agent, FETCH, "key") == SUCCESS);
+ ok1(external_agent_operation(agent, CLOSE, "") == SUCCESS);
+
+ /* Still exists for us too. */
+ ok1(ntdb_exists(ntdb, key));
+
+ /* Close it, now agent should clear it. */
+ ntdb_close(ntdb);
+
+ ok1(external_agent_operation(agent, OPEN_WITH_HOOK,
+ "run-83-openhook.ntdb") == SUCCESS);
+ ok1(external_agent_operation(agent, FETCH, "key") == FAILED);
+ ok1(external_agent_operation(agent, CLOSE, "") == SUCCESS);
+
+ ok1(tap_log_messages == 0);
+ }
+
+ free_external_agent(agent);
+ return exit_status();
+}
diff --git a/lib/ntdb/test/api-91-get-stats.c b/lib/ntdb/test/api-91-get-stats.c
new file mode 100644
index 0000000000..786885b44c
--- /dev/null
+++ b/lib/ntdb/test/api-91-get-stats.c
@@ -0,0 +1,57 @@
+#include "config.h"
+#include "ntdb.h"
+#include "tap-interface.h"
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include "logging.h"
+
+int main(int argc, char *argv[])
+{
+ unsigned int i;
+ struct ntdb_context *ntdb;
+ int flags[] = { NTDB_DEFAULT, NTDB_NOMMAP,
+ NTDB_CONVERT, NTDB_NOMMAP|NTDB_CONVERT };
+
+ plan_tests(sizeof(flags) / sizeof(flags[0]) * 11);
+
+ for (i = 0; i < sizeof(flags) / sizeof(flags[0]); i++) {
+ union ntdb_attribute *attr;
+ NTDB_DATA key = ntdb_mkdata("key", 3);
+
+ ntdb = ntdb_open("run-91-get-stats.ntdb", flags[i],
+ O_RDWR|O_CREAT|O_TRUNC, 0600, &tap_log_attr);
+ ok1(ntdb);
+ ok1(ntdb_store(ntdb, key, key, NTDB_REPLACE) == 0);
+
+ /* Use malloc so valgrind will catch overruns. */
+ attr = malloc(sizeof *attr);
+ attr->stats.base.attr = NTDB_ATTRIBUTE_STATS;
+ attr->stats.size = sizeof(*attr);
+
+ ok1(ntdb_get_attribute(ntdb, attr) == 0);
+ ok1(attr->stats.size == sizeof(*attr));
+ ok1(attr->stats.allocs > 0);
+ ok1(attr->stats.expands > 0);
+ ok1(attr->stats.locks > 0);
+ free(attr);
+
+ /* Try short one. */
+ attr = malloc(offsetof(struct ntdb_attribute_stats, allocs)
+ + sizeof(attr->stats.allocs));
+ attr->stats.base.attr = NTDB_ATTRIBUTE_STATS;
+ attr->stats.size = offsetof(struct ntdb_attribute_stats, allocs)
+ + sizeof(attr->stats.allocs);
+ ok1(ntdb_get_attribute(ntdb, attr) == 0);
+ ok1(attr->stats.size == sizeof(*attr));
+ ok1(attr->stats.allocs > 0);
+ free(attr);
+ ok1(tap_log_messages == 0);
+
+ ntdb_close(ntdb);
+
+ }
+ return exit_status();
+}
diff --git a/lib/ntdb/test/api-92-get-set-readonly.c b/lib/ntdb/test/api-92-get-set-readonly.c
new file mode 100644
index 0000000000..7abd304eef
--- /dev/null
+++ b/lib/ntdb/test/api-92-get-set-readonly.c
@@ -0,0 +1,105 @@
+#include "config.h"
+#include "ntdb.h"
+#include "tap-interface.h"
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include "logging.h"
+
+int main(int argc, char *argv[])
+{
+ unsigned int i;
+ struct ntdb_context *ntdb;
+ NTDB_DATA key = ntdb_mkdata("key", 3);
+ NTDB_DATA data = ntdb_mkdata("data", 4);
+ int flags[] = { NTDB_DEFAULT, NTDB_NOMMAP,
+ NTDB_CONVERT, NTDB_NOMMAP|NTDB_CONVERT };
+
+ plan_tests(sizeof(flags) / sizeof(flags[0]) * 48);
+
+ for (i = 0; i < sizeof(flags) / sizeof(flags[0]); i++) {
+ /* RW -> R0 */
+ ntdb = ntdb_open("run-92-get-set-readonly.ntdb", flags[i],
+ O_RDWR|O_CREAT|O_TRUNC, 0600, &tap_log_attr);
+ ok1(ntdb);
+ ok1(!(ntdb_get_flags(ntdb) & NTDB_RDONLY));
+
+ ok1(ntdb_store(ntdb, key, data, NTDB_INSERT) == NTDB_SUCCESS);
+
+ ntdb_add_flag(ntdb, NTDB_RDONLY);
+ ok1(ntdb_get_flags(ntdb) & NTDB_RDONLY);
+
+ /* Can't store, append, delete. */
+ ok1(ntdb_store(ntdb, key, data, NTDB_MODIFY) == NTDB_ERR_RDONLY);
+ ok1(tap_log_messages == 1);
+ ok1(ntdb_append(ntdb, key, data) == NTDB_ERR_RDONLY);
+ ok1(tap_log_messages == 2);
+ ok1(ntdb_delete(ntdb, key) == NTDB_ERR_RDONLY);
+ ok1(tap_log_messages == 3);
+
+ /* Can't start a transaction, or any write lock. */
+ ok1(ntdb_transaction_start(ntdb) == NTDB_ERR_RDONLY);
+ ok1(tap_log_messages == 4);
+ ok1(ntdb_chainlock(ntdb, key) == NTDB_ERR_RDONLY);
+ ok1(tap_log_messages == 5);
+ ok1(ntdb_lockall(ntdb) == NTDB_ERR_RDONLY);
+ ok1(tap_log_messages == 6);
+ ok1(ntdb_wipe_all(ntdb) == NTDB_ERR_RDONLY);
+ ok1(tap_log_messages == 7);
+
+ /* Back to RW. */
+ ntdb_remove_flag(ntdb, NTDB_RDONLY);
+ ok1(!(ntdb_get_flags(ntdb) & NTDB_RDONLY));
+
+ ok1(ntdb_store(ntdb, key, data, NTDB_MODIFY) == NTDB_SUCCESS);
+ ok1(ntdb_append(ntdb, key, data) == NTDB_SUCCESS);
+ ok1(ntdb_delete(ntdb, key) == NTDB_SUCCESS);
+
+ ok1(ntdb_transaction_start(ntdb) == NTDB_SUCCESS);
+ ok1(ntdb_store(ntdb, key, data, NTDB_INSERT) == NTDB_SUCCESS);
+ ok1(ntdb_transaction_commit(ntdb) == NTDB_SUCCESS);
+
+ ok1(ntdb_chainlock(ntdb, key) == NTDB_SUCCESS);
+ ntdb_chainunlock(ntdb, key);
+ ok1(ntdb_lockall(ntdb) == NTDB_SUCCESS);
+ ntdb_unlockall(ntdb);
+ ok1(ntdb_wipe_all(ntdb) == NTDB_SUCCESS);
+ ok1(tap_log_messages == 7);
+
+ ntdb_close(ntdb);
+
+ /* R0 -> RW */
+ ntdb = ntdb_open("run-92-get-set-readonly.ntdb", flags[i],
+ O_RDONLY, 0600, &tap_log_attr);
+ ok1(ntdb);
+ ok1(ntdb_get_flags(ntdb) & NTDB_RDONLY);
+
+ /* Can't store, append, delete. */
+ ok1(ntdb_store(ntdb, key, data, NTDB_INSERT) == NTDB_ERR_RDONLY);
+ ok1(tap_log_messages == 8);
+ ok1(ntdb_append(ntdb, key, data) == NTDB_ERR_RDONLY);
+ ok1(tap_log_messages == 9);
+ ok1(ntdb_delete(ntdb, key) == NTDB_ERR_RDONLY);
+ ok1(tap_log_messages == 10);
+
+ /* Can't start a transaction, or any write lock. */
+ ok1(ntdb_transaction_start(ntdb) == NTDB_ERR_RDONLY);
+ ok1(tap_log_messages == 11);
+ ok1(ntdb_chainlock(ntdb, key) == NTDB_ERR_RDONLY);
+ ok1(tap_log_messages == 12);
+ ok1(ntdb_lockall(ntdb) == NTDB_ERR_RDONLY);
+ ok1(tap_log_messages == 13);
+ ok1(ntdb_wipe_all(ntdb) == NTDB_ERR_RDONLY);
+ ok1(tap_log_messages == 14);
+
+ /* Can't remove NTDB_RDONLY since we opened with O_RDONLY */
+ ntdb_remove_flag(ntdb, NTDB_RDONLY);
+ ok1(tap_log_messages == 15);
+ ok1(ntdb_get_flags(ntdb) & NTDB_RDONLY);
+ ntdb_close(ntdb);
+
+ ok1(tap_log_messages == 15);
+ tap_log_messages = 0;
+ }
+ return exit_status();
+}
diff --git a/lib/ntdb/test/api-93-repack.c b/lib/ntdb/test/api-93-repack.c
new file mode 100644
index 0000000000..168bc24c0a
--- /dev/null
+++ b/lib/ntdb/test/api-93-repack.c
@@ -0,0 +1,80 @@
+#include "config.h"
+#include "ntdb.h"
+#include "tap-interface.h"
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include "logging.h"
+
+#define NUM_TESTS 1000
+
+static bool store_all(struct ntdb_context *ntdb)
+{
+ unsigned int i;
+ NTDB_DATA key = { (unsigned char *)&i, sizeof(i) };
+ NTDB_DATA dbuf = { (unsigned char *)&i, sizeof(i) };
+
+ for (i = 0; i < NUM_TESTS; i++) {
+ if (ntdb_store(ntdb, key, dbuf, NTDB_INSERT) != NTDB_SUCCESS)
+ return false;
+ }
+ return true;
+}
+
+static int mark_entry(struct ntdb_context *ntdb,
+ NTDB_DATA key, NTDB_DATA data, bool found[])
+{
+ unsigned int num;
+
+ if (key.dsize != sizeof(num))
+ return -1;
+ memcpy(&num, key.dptr, key.dsize);
+ if (num >= NUM_TESTS)
+ return -1;
+ if (found[num])
+ return -1;
+ found[num] = true;
+ return 0;
+}
+
+static bool is_all_set(bool found[], unsigned int num)
+{
+ unsigned int i;
+
+ for (i = 0; i < num; i++)
+ if (!found[i])
+ return false;
+ return true;
+}
+
+int main(int argc, char *argv[])
+{
+ unsigned int i;
+ bool found[NUM_TESTS];
+ struct ntdb_context *ntdb;
+ int flags[] = { NTDB_DEFAULT, NTDB_NOMMAP,
+ NTDB_CONVERT, NTDB_NOMMAP|NTDB_CONVERT
+ };
+
+ plan_tests(sizeof(flags) / sizeof(flags[0]) * 6 + 1);
+
+ for (i = 0; i < sizeof(flags) / sizeof(flags[0]); i++) {
+ ntdb = ntdb_open("run-93-repack.ntdb", flags[i],
+ O_RDWR|O_CREAT|O_TRUNC, 0600, &tap_log_attr);
+ ok1(ntdb);
+ if (!ntdb)
+ break;
+
+ ok1(store_all(ntdb));
+
+ ok1(ntdb_repack(ntdb) == NTDB_SUCCESS);
+ memset(found, 0, sizeof(found));
+ ok1(ntdb_check(ntdb, NULL, NULL) == NTDB_SUCCESS);
+ ok1(ntdb_traverse(ntdb, mark_entry, found) == NUM_TESTS);
+ ok1(is_all_set(found, NUM_TESTS));
+ ntdb_close(ntdb);
+ }
+
+ ok1(tap_log_messages == 0);
+ return exit_status();
+}
diff --git a/lib/ntdb/test/api-add-remove-flags.c b/lib/ntdb/test/api-add-remove-flags.c
new file mode 100644
index 0000000000..4888c32f06
--- /dev/null
+++ b/lib/ntdb/test/api-add-remove-flags.c
@@ -0,0 +1,89 @@
+#include "private.h" // for ntdb_context
+#include "ntdb.h"
+#include "tap-interface.h"
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include "logging.h"
+
+int main(int argc, char *argv[])
+{
+ unsigned int i;
+ struct ntdb_context *ntdb;
+ int flags[] = { NTDB_INTERNAL, NTDB_DEFAULT, NTDB_NOMMAP,
+ NTDB_INTERNAL|NTDB_CONVERT, NTDB_CONVERT,
+ NTDB_NOMMAP|NTDB_CONVERT };
+
+ plan_tests(87);
+ for (i = 0; i < sizeof(flags) / sizeof(flags[0]); i++) {
+ ntdb = ntdb_open("run-add-remove-flags.ntdb", flags[i],
+ O_RDWR|O_CREAT|O_TRUNC, 0600, &tap_log_attr);
+ ok1(ntdb);
+ if (!ntdb)
+ continue;
+
+ ok1(ntdb_get_flags(ntdb) == ntdb->flags);
+ tap_log_messages = 0;
+ ntdb_add_flag(ntdb, NTDB_NOLOCK);
+ if (flags[i] & NTDB_INTERNAL)
+ ok1(tap_log_messages == 1);
+ else {
+ ok1(tap_log_messages == 0);
+ ok1(ntdb_get_flags(ntdb) & NTDB_NOLOCK);
+ }
+
+ tap_log_messages = 0;
+ ntdb_add_flag(ntdb, NTDB_NOMMAP);
+ if (flags[i] & NTDB_INTERNAL)
+ ok1(tap_log_messages == 1);
+ else {
+ ok1(tap_log_messages == 0);
+ ok1(ntdb_get_flags(ntdb) & NTDB_NOMMAP);
+ ok1(ntdb->file->map_ptr == NULL);
+ }
+
+ tap_log_messages = 0;
+ ntdb_add_flag(ntdb, NTDB_NOSYNC);
+ if (flags[i] & NTDB_INTERNAL)
+ ok1(tap_log_messages == 1);
+ else {
+ ok1(tap_log_messages == 0);
+ ok1(ntdb_get_flags(ntdb) & NTDB_NOSYNC);
+ }
+
+ ok1(ntdb_get_flags(ntdb) == ntdb->flags);
+
+ tap_log_messages = 0;
+ ntdb_remove_flag(ntdb, NTDB_NOLOCK);
+ if (flags[i] & NTDB_INTERNAL)
+ ok1(tap_log_messages == 1);
+ else {
+ ok1(tap_log_messages == 0);
+ ok1(!(ntdb_get_flags(ntdb) & NTDB_NOLOCK));
+ }
+
+ tap_log_messages = 0;
+ ntdb_remove_flag(ntdb, NTDB_NOMMAP);
+ if (flags[i] & NTDB_INTERNAL)
+ ok1(tap_log_messages == 1);
+ else {
+ ok1(tap_log_messages == 0);
+ ok1(!(ntdb_get_flags(ntdb) & NTDB_NOMMAP));
+ ok1(ntdb->file->map_ptr != NULL);
+ }
+
+ tap_log_messages = 0;
+ ntdb_remove_flag(ntdb, NTDB_NOSYNC);
+ if (flags[i] & NTDB_INTERNAL)
+ ok1(tap_log_messages == 1);
+ else {
+ ok1(tap_log_messages == 0);
+ ok1(!(ntdb_get_flags(ntdb) & NTDB_NOSYNC));
+ }
+
+ ntdb_close(ntdb);
+ }
+
+ ok1(tap_log_messages == 0);
+ return exit_status();
+}
diff --git a/lib/ntdb/test/api-check-callback.c b/lib/ntdb/test/api-check-callback.c
new file mode 100644
index 0000000000..f74f04b598
--- /dev/null
+++ b/lib/ntdb/test/api-check-callback.c
@@ -0,0 +1,86 @@
+#include "config.h"
+#include "ntdb.h"
+#include "tap-interface.h"
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include "logging.h"
+
+#define NUM_RECORDS 1000
+
+static bool store_records(struct ntdb_context *ntdb)
+{
+ int i;
+ NTDB_DATA key = { (unsigned char *)&i, sizeof(i) };
+ NTDB_DATA data = { (unsigned char *)&i, sizeof(i) };
+
+ for (i = 0; i < NUM_RECORDS; i++)
+ if (ntdb_store(ntdb, key, data, NTDB_REPLACE) != 0)
+ return false;
+ return true;
+}
+
+static enum NTDB_ERROR check(NTDB_DATA key,
+ NTDB_DATA data,
+ bool *array)
+{
+ int val;
+
+ if (key.dsize != sizeof(val)) {
+ diag("Wrong key size: %u\n", key.dsize);
+ return NTDB_ERR_CORRUPT;
+ }
+
+ if (key.dsize != data.dsize
+ || memcmp(key.dptr, data.dptr, sizeof(val)) != 0) {
+ diag("Key and data differ\n");
+ return NTDB_ERR_CORRUPT;
+ }
+
+ memcpy(&val, key.dptr, sizeof(val));
+ if (val >= NUM_RECORDS || val < 0) {
+ diag("check value %i\n", val);
+ return NTDB_ERR_CORRUPT;
+ }
+
+ if (array[val]) {
+ diag("Value %i already seen\n", val);
+ return NTDB_ERR_CORRUPT;
+ }
+
+ array[val] = true;
+ return NTDB_SUCCESS;
+}
+
+int main(int argc, char *argv[])
+{
+ unsigned int i, j;
+ struct ntdb_context *ntdb;
+ int flags[] = { NTDB_INTERNAL, NTDB_DEFAULT, NTDB_NOMMAP,
+ NTDB_INTERNAL|NTDB_CONVERT, NTDB_CONVERT,
+ NTDB_NOMMAP|NTDB_CONVERT };
+
+ plan_tests(sizeof(flags) / sizeof(flags[0]) * 4 + 1);
+ for (i = 0; i < sizeof(flags) / sizeof(flags[0]); i++) {
+ bool array[NUM_RECORDS];
+
+ ntdb = ntdb_open("run-check-callback.ntdb", flags[i],
+ O_RDWR|O_CREAT|O_TRUNC, 0600, &tap_log_attr);
+ ok1(ntdb);
+ if (!ntdb)
+ continue;
+
+ ok1(store_records(ntdb));
+ for (j = 0; j < NUM_RECORDS; j++)
+ array[j] = false;
+ ok1(ntdb_check(ntdb, check, array) == NTDB_SUCCESS);
+ for (j = 0; j < NUM_RECORDS; j++)
+ if (!array[j])
+ break;
+ ok1(j == NUM_RECORDS);
+ ntdb_close(ntdb);
+ }
+
+ ok1(tap_log_messages == 0);
+ return exit_status();
+}
diff --git a/lib/ntdb/test/api-firstkey-nextkey.c b/lib/ntdb/test/api-firstkey-nextkey.c
new file mode 100644
index 0000000000..da1a68043b
--- /dev/null
+++ b/lib/ntdb/test/api-firstkey-nextkey.c
@@ -0,0 +1,159 @@
+#include "config.h"
+#include "ntdb.h"
+#include "tap-interface.h"
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include "logging.h"
+
+#define NUM_RECORDS 1000
+
+static bool store_records(struct ntdb_context *ntdb)
+{
+ int i;
+ NTDB_DATA key = { (unsigned char *)&i, sizeof(i) };
+ NTDB_DATA data = { (unsigned char *)&i, sizeof(i) };
+
+ for (i = 0; i < NUM_RECORDS; i++)
+ if (ntdb_store(ntdb, key, data, NTDB_REPLACE) != 0)
+ return false;
+ return true;
+}
+
+struct trav_data {
+ unsigned int records[NUM_RECORDS];
+ unsigned int calls;
+};
+
+static int trav(struct ntdb_context *ntdb, NTDB_DATA key, NTDB_DATA dbuf, void *p)
+{
+ struct trav_data *td = p;
+ int val;
+
+ memcpy(&val, dbuf.dptr, dbuf.dsize);
+ td->records[td->calls++] = val;
+ return 0;
+}
+
+/* Since ntdb_nextkey frees dptr, we need to clone it. */
+static NTDB_DATA dup_key(NTDB_DATA key)
+{
+ void *p = malloc(key.dsize);
+ memcpy(p, key.dptr, key.dsize);
+ key.dptr = p;
+ return key;
+}
+
+int main(int argc, char *argv[])
+{
+ unsigned int i, j;
+ int num;
+ struct trav_data td;
+ NTDB_DATA k;
+ struct ntdb_context *ntdb;
+ union ntdb_attribute seed_attr;
+ enum NTDB_ERROR ecode;
+ int flags[] = { NTDB_INTERNAL, NTDB_DEFAULT, NTDB_NOMMAP,
+ NTDB_INTERNAL|NTDB_CONVERT, NTDB_CONVERT,
+ NTDB_NOMMAP|NTDB_CONVERT };
+
+ seed_attr.base.attr = NTDB_ATTRIBUTE_SEED;
+ seed_attr.base.next = &tap_log_attr;
+ seed_attr.seed.seed = 6334326220117065685ULL;
+
+ plan_tests(sizeof(flags) / sizeof(flags[0])
+ * (NUM_RECORDS*6 + (NUM_RECORDS-1)*3 + 22) + 1);
+ for (i = 0; i < sizeof(flags) / sizeof(flags[0]); i++) {
+ ntdb = ntdb_open("api-firstkey-nextkey.ntdb", flags[i],
+ O_RDWR|O_CREAT|O_TRUNC, 0600,
+ &seed_attr);
+ ok1(ntdb);
+ if (!ntdb)
+ continue;
+
+ ok1(ntdb_firstkey(ntdb, &k) == NTDB_ERR_NOEXIST);
+
+ /* One entry... */
+ k.dptr = (unsigned char *)&num;
+ k.dsize = sizeof(num);
+ num = 0;
+ ok1(ntdb_store(ntdb, k, k, NTDB_INSERT) == 0);
+ ok1(ntdb_firstkey(ntdb, &k) == NTDB_SUCCESS);
+ ok1(k.dsize == sizeof(num));
+ ok1(memcmp(k.dptr, &num, sizeof(num)) == 0);
+ ok1(ntdb_nextkey(ntdb, &k) == NTDB_ERR_NOEXIST);
+
+ /* Two entries. */
+ k.dptr = (unsigned char *)&num;
+ k.dsize = sizeof(num);
+ num = 1;
+ ok1(ntdb_store(ntdb, k, k, NTDB_INSERT) == 0);
+ ok1(ntdb_firstkey(ntdb, &k) == NTDB_SUCCESS);
+ ok1(k.dsize == sizeof(num));
+ memcpy(&num, k.dptr, sizeof(num));
+ ok1(num == 0 || num == 1);
+ ok1(ntdb_nextkey(ntdb, &k) == NTDB_SUCCESS);
+ ok1(k.dsize == sizeof(j));
+ memcpy(&j, k.dptr, sizeof(j));
+ ok1(j == 0 || j == 1);
+ ok1(j != num);
+ ok1(ntdb_nextkey(ntdb, &k) == NTDB_ERR_NOEXIST);
+
+ /* Clean up. */
+ k.dptr = (unsigned char *)&num;
+ k.dsize = sizeof(num);
+ num = 0;
+ ok1(ntdb_delete(ntdb, k) == 0);
+ num = 1;
+ ok1(ntdb_delete(ntdb, k) == 0);
+
+ /* Now lots of records. */
+ ok1(store_records(ntdb));
+ td.calls = 0;
+
+ num = ntdb_traverse(ntdb, trav, &td);
+ ok1(num == NUM_RECORDS);
+ ok1(td.calls == NUM_RECORDS);
+
+ /* Simple loop should match ntdb_traverse */
+ for (j = 0, ecode = ntdb_firstkey(ntdb, &k); j < td.calls; j++) {
+ int val;
+
+ ok1(ecode == NTDB_SUCCESS);
+ ok1(k.dsize == sizeof(val));
+ memcpy(&val, k.dptr, k.dsize);
+ ok1(td.records[j] == val);
+ ecode = ntdb_nextkey(ntdb, &k);
+ }
+
+ /* But arbitrary orderings should work too. */
+ for (j = td.calls-1; j > 0; j--) {
+ k.dptr = (unsigned char *)&td.records[j-1];
+ k.dsize = sizeof(td.records[j-1]);
+ k = dup_key(k);
+ ok1(ntdb_nextkey(ntdb, &k) == NTDB_SUCCESS);
+ ok1(k.dsize == sizeof(td.records[j]));
+ ok1(memcmp(k.dptr, &td.records[j], k.dsize) == 0);
+ free(k.dptr);
+ }
+
+ /* Even delete should work. */
+ for (j = 0, ecode = ntdb_firstkey(ntdb, &k);
+ ecode != NTDB_ERR_NOEXIST;
+ j++) {
+ ok1(ecode == NTDB_SUCCESS);
+ ok1(k.dsize == 4);
+ ok1(ntdb_delete(ntdb, k) == 0);
+ ecode = ntdb_nextkey(ntdb, &k);
+ }
+
+ diag("delete using first/nextkey gave %u of %u records",
+ j, NUM_RECORDS);
+ ok1(j == NUM_RECORDS);
+ ntdb_close(ntdb);
+ }
+
+ ok1(tap_log_messages == 0);
+ return exit_status();
+}
diff --git a/lib/ntdb/test/api-fork-test.c b/lib/ntdb/test/api-fork-test.c
new file mode 100644
index 0000000000..57bd686282
--- /dev/null
+++ b/lib/ntdb/test/api-fork-test.c
@@ -0,0 +1,179 @@
+/* Test forking while holding lock.
+ *
+ * There are only five ways to do this currently:
+ * (1) grab a ntdb_chainlock, then fork.
+ * (2) grab a ntdb_lockall, then fork.
+ * (3) grab a ntdb_lockall_read, then fork.
+ * (4) start a transaction, then fork.
+ * (5) fork from inside a ntdb_parse() callback.
+ *
+ * Note that we don't hold a lock across ntdb_traverse callbacks, so
+ * that doesn't matter.
+ */
+#include "config.h"
+#include "ntdb.h"
+#include "tap-interface.h"
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include "logging.h"
+
+static enum NTDB_ERROR fork_in_parse(NTDB_DATA key, NTDB_DATA data,
+ struct ntdb_context *ntdb)
+{
+ int status;
+
+ if (fork() == 0) {
+ /* We expect this to fail. */
+ if (ntdb_store(ntdb, key, data, NTDB_REPLACE) != NTDB_ERR_LOCK)
+ exit(1);
+
+ if (ntdb_fetch(ntdb, key, &data) != NTDB_ERR_LOCK)
+ exit(1);
+
+ if (tap_log_messages != 2)
+ exit(2);
+
+ ntdb_close(ntdb);
+ if (tap_log_messages != 2)
+ exit(3);
+ exit(0);
+ }
+ wait(&status);
+ ok1(WIFEXITED(status) && WEXITSTATUS(status) == 0);
+ return NTDB_SUCCESS;
+}
+
+int main(int argc, char *argv[])
+{
+ unsigned int i;
+ struct ntdb_context *ntdb;
+ int flags[] = { NTDB_DEFAULT, NTDB_NOMMAP,
+ NTDB_CONVERT, NTDB_NOMMAP|NTDB_CONVERT };
+ NTDB_DATA key = ntdb_mkdata("key", 3);
+ NTDB_DATA data = ntdb_mkdata("data", 4);
+
+ plan_tests(sizeof(flags) / sizeof(flags[0]) * 14);
+ for (i = 0; i < sizeof(flags) / sizeof(flags[0]); i++) {
+ int status;
+
+ tap_log_messages = 0;
+
+ ntdb = ntdb_open("run-fork-test.ntdb", flags[i],
+ O_RDWR|O_CREAT|O_TRUNC, 0600, &tap_log_attr);
+ if (!ok1(ntdb))
+ continue;
+
+ /* Put a record in here. */
+ ok1(ntdb_store(ntdb, key, data, NTDB_REPLACE) == NTDB_SUCCESS);
+
+ ok1(ntdb_chainlock(ntdb, key) == NTDB_SUCCESS);
+ if (fork() == 0) {
+ /* We expect this to fail. */
+ if (ntdb_store(ntdb, key, data, NTDB_REPLACE) != NTDB_ERR_LOCK)
+ return 1;
+
+ if (ntdb_fetch(ntdb, key, &data) != NTDB_ERR_LOCK)
+ return 1;
+
+ if (tap_log_messages != 2)
+ return 2;
+
+ ntdb_chainunlock(ntdb, key);
+ if (tap_log_messages != 3)
+ return 3;
+ ntdb_close(ntdb);
+ if (tap_log_messages != 3)
+ return 4;
+ return 0;
+ }
+ wait(&status);
+ ok1(WIFEXITED(status) && WEXITSTATUS(status) == 0);
+ ntdb_chainunlock(ntdb, key);
+
+ ok1(ntdb_lockall(ntdb) == NTDB_SUCCESS);
+ if (fork() == 0) {
+ /* We expect this to fail. */
+ if (ntdb_store(ntdb, key, data, NTDB_REPLACE) != NTDB_ERR_LOCK)
+ return 1;
+
+ if (ntdb_fetch(ntdb, key, &data) != NTDB_ERR_LOCK)
+ return 1;
+
+ if (tap_log_messages != 2)
+ return 2;
+
+ ntdb_unlockall(ntdb);
+ if (tap_log_messages != 2)
+ return 3;
+ ntdb_close(ntdb);
+ if (tap_log_messages != 2)
+ return 4;
+ return 0;
+ }
+ wait(&status);
+ ok1(WIFEXITED(status) && WEXITSTATUS(status) == 0);
+ ntdb_unlockall(ntdb);
+
+ ok1(ntdb_lockall_read(ntdb) == NTDB_SUCCESS);
+ if (fork() == 0) {
+ /* We expect this to fail. */
+ /* This would always fail anyway... */
+ if (ntdb_store(ntdb, key, data, NTDB_REPLACE) != NTDB_ERR_LOCK)
+ return 1;
+
+ if (ntdb_fetch(ntdb, key, &data) != NTDB_ERR_LOCK)
+ return 1;
+
+ if (tap_log_messages != 2)
+ return 2;
+
+ ntdb_unlockall_read(ntdb);
+ if (tap_log_messages != 2)
+ return 3;
+ ntdb_close(ntdb);
+ if (tap_log_messages != 2)
+ return 4;
+ return 0;
+ }
+ wait(&status);
+ ok1(WIFEXITED(status) && WEXITSTATUS(status) == 0);
+ ntdb_unlockall_read(ntdb);
+
+ ok1(ntdb_transaction_start(ntdb) == NTDB_SUCCESS);
+ /* If transactions is empty, noop "commit" succeeds. */
+ ok1(ntdb_delete(ntdb, key) == NTDB_SUCCESS);
+ if (fork() == 0) {
+ /* We expect this to fail. */
+ if (ntdb_store(ntdb, key, data, NTDB_REPLACE) != NTDB_ERR_LOCK)
+ return 1;
+
+ if (ntdb_fetch(ntdb, key, &data) != NTDB_ERR_LOCK)
+ return 1;
+
+ if (tap_log_messages != 2)
+ return 2;
+
+ if (ntdb_transaction_commit(ntdb) != NTDB_ERR_LOCK)
+ return 3;
+
+ ntdb_close(ntdb);
+ if (tap_log_messages < 3)
+ return 4;
+ return 0;
+ }
+ wait(&status);
+ ok1(WIFEXITED(status) && WEXITSTATUS(status) == 0);
+ ntdb_transaction_cancel(ntdb);
+
+ ok1(ntdb_parse_record(ntdb, key, fork_in_parse, ntdb)
+ == NTDB_SUCCESS);
+ ntdb_close(ntdb);
+ ok1(tap_log_messages == 0);
+ }
+ return exit_status();
+}
diff --git a/lib/ntdb/test/api-locktimeout.c b/lib/ntdb/test/api-locktimeout.c
new file mode 100644
index 0000000000..cafe067d0b
--- /dev/null
+++ b/lib/ntdb/test/api-locktimeout.c
@@ -0,0 +1,193 @@
+#include "config.h"
+#include "ntdb.h"
+#include "tap-interface.h"
+#include "system/wait.h"
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <errno.h>
+#include "logging.h"
+#include "external-agent.h"
+
+#undef alarm
+#define alarm fast_alarm
+
+/* Speed things up by doing things in milliseconds. */
+static unsigned int fast_alarm(unsigned int milli_seconds)
+{
+ struct itimerval it;
+
+ it.it_interval.tv_sec = it.it_interval.tv_usec = 0;
+ it.it_value.tv_sec = milli_seconds / 1000;
+ it.it_value.tv_usec = milli_seconds * 1000;
+ setitimer(ITIMER_REAL, &it, NULL);
+ return 0;
+}
+
+#define CatchSignal(sig, handler) signal((sig), (handler))
+
+static void do_nothing(int signum)
+{
+}
+
+/* This example code is taken from SAMBA, so try not to change it. */
+static struct flock flock_struct;
+
+/* Return a value which is none of v1, v2 or v3. */
+static inline short int invalid_value(short int v1, short int v2, short int v3)
+{
+ short int try = (v1+v2+v3)^((v1+v2+v3) << 16);
+ while (try == v1 || try == v2 || try == v3)
+ try++;
+ return try;
+}
+
+/* We invalidate in as many ways as we can, so the OS rejects it */
+static void invalidate_flock_struct(int signum)
+{
+ flock_struct.l_type = invalid_value(F_RDLCK, F_WRLCK, F_UNLCK);
+ flock_struct.l_whence = invalid_value(SEEK_SET, SEEK_CUR, SEEK_END);
+ flock_struct.l_start = -1;
+ /* A large negative. */
+ flock_struct.l_len = (((off_t)1 << (sizeof(off_t)*CHAR_BIT - 1)) + 1);
+}
+
+static int timeout_lock(int fd, int rw, off_t off, off_t len, bool waitflag,
+ void *_timeout)
+{
+ int ret, saved_errno = errno;
+ unsigned int timeout = *(unsigned int *)_timeout;
+
+ flock_struct.l_type = rw;
+ flock_struct.l_whence = SEEK_SET;
+ flock_struct.l_start = off;
+ flock_struct.l_len = len;
+
+ CatchSignal(SIGALRM, invalidate_flock_struct);
+ alarm(timeout);
+
+ for (;;) {
+ if (waitflag)
+ ret = fcntl(fd, F_SETLKW, &flock_struct);
+ else
+ ret = fcntl(fd, F_SETLK, &flock_struct);
+
+ if (ret == 0)
+ break;
+
+ /* Not signalled? Something else went wrong. */
+ if (flock_struct.l_len == len) {
+ if (errno == EAGAIN || errno == EINTR)
+ continue;
+ saved_errno = errno;
+ break;
+ } else {
+ saved_errno = EINTR;
+ break;
+ }
+ }
+
+ alarm(0);
+ errno = saved_errno;
+ return ret;
+}
+
+static int ntdb_chainlock_with_timeout_internal(struct ntdb_context *ntdb,
+ NTDB_DATA key,
+ unsigned int timeout,
+ int rw_type)
+{
+ union ntdb_attribute locking;
+ enum NTDB_ERROR ecode;
+
+ if (timeout) {
+ locking.base.attr = NTDB_ATTRIBUTE_FLOCK;
+ ecode = ntdb_get_attribute(ntdb, &locking);
+ if (ecode != NTDB_SUCCESS)
+ return ecode;
+
+ /* Replace locking function with our own. */
+ locking.flock.data = &timeout;
+ locking.flock.lock = timeout_lock;
+
+ ecode = ntdb_set_attribute(ntdb, &locking);
+ if (ecode != NTDB_SUCCESS)
+ return ecode;
+ }
+ if (rw_type == F_RDLCK)
+ ecode = ntdb_chainlock_read(ntdb, key);
+ else
+ ecode = ntdb_chainlock(ntdb, key);
+
+ if (timeout) {
+ ntdb_unset_attribute(ntdb, NTDB_ATTRIBUTE_FLOCK);
+ }
+ return ecode;
+}
+
+int main(int argc, char *argv[])
+{
+ unsigned int i;
+ struct ntdb_context *ntdb;
+ NTDB_DATA key = ntdb_mkdata("hello", 5);
+ int flags[] = { NTDB_DEFAULT, NTDB_NOMMAP,
+ NTDB_CONVERT, NTDB_NOMMAP|NTDB_CONVERT };
+ struct agent *agent;
+
+ plan_tests(sizeof(flags) / sizeof(flags[0]) * 15);
+
+ agent = prepare_external_agent();
+
+ for (i = 0; i < sizeof(flags) / sizeof(flags[0]); i++) {
+ enum NTDB_ERROR ecode;
+ ntdb = ntdb_open("run-locktimeout.ntdb", flags[i],
+ O_RDWR|O_CREAT|O_TRUNC, 0600, &tap_log_attr);
+ if (!ok1(ntdb))
+ break;
+
+ /* Simple cases: should succeed. */
+ ecode = ntdb_chainlock_with_timeout_internal(ntdb, key, 20,
+ F_RDLCK);
+ ok1(ecode == NTDB_SUCCESS);
+ ok1(tap_log_messages == 0);
+
+ ntdb_chainunlock_read(ntdb, key);
+ ok1(tap_log_messages == 0);
+
+ ecode = ntdb_chainlock_with_timeout_internal(ntdb, key, 20,
+ F_WRLCK);
+ ok1(ecode == NTDB_SUCCESS);
+ ok1(tap_log_messages == 0);
+
+ ntdb_chainunlock(ntdb, key);
+ ok1(tap_log_messages == 0);
+
+ /* OK, get agent to start transaction, then we should time out. */
+ ok1(external_agent_operation(agent, OPEN, "run-locktimeout.ntdb")
+ == SUCCESS);
+ ok1(external_agent_operation(agent, TRANSACTION_START, "")
+ == SUCCESS);
+ ecode = ntdb_chainlock_with_timeout_internal(ntdb, key, 20,
+ F_WRLCK);
+ ok1(ecode == NTDB_ERR_LOCK);
+ ok1(tap_log_messages == 0);
+
+ /* Even if we get a different signal, should be fine. */
+ CatchSignal(SIGUSR1, do_nothing);
+ external_agent_operation(agent, SEND_SIGNAL, "");
+ ecode = ntdb_chainlock_with_timeout_internal(ntdb, key, 20,
+ F_WRLCK);
+ ok1(ecode == NTDB_ERR_LOCK);
+ ok1(tap_log_messages == 0);
+
+ ok1(external_agent_operation(agent, TRANSACTION_COMMIT, "")
+ == SUCCESS);
+ ok1(external_agent_operation(agent, CLOSE, "")
+ == SUCCESS);
+ ntdb_close(ntdb);
+ }
+ free_external_agent(agent);
+ return exit_status();
+}
diff --git a/lib/ntdb/test/api-missing-entries.c b/lib/ntdb/test/api-missing-entries.c
new file mode 100644
index 0000000000..1c8064f945
--- /dev/null
+++ b/lib/ntdb/test/api-missing-entries.c
@@ -0,0 +1,44 @@
+/* Another test revealed that we lost an entry. This reproduces it. */
+#include "config.h"
+#include "ntdb.h"
+#include <ccan/hash/hash.h>
+#include "tap-interface.h"
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include "logging.h"
+
+#define NUM_RECORDS 1189
+
+/* We use the same seed which we saw this failure on. */
+static uint64_t failhash(const void *key, size_t len, uint64_t seed, void *p)
+{
+ seed = 699537674708983027ULL;
+ return hash64_stable((const unsigned char *)key, len, seed);
+}
+
+int main(int argc, char *argv[])
+{
+ int i;
+ struct ntdb_context *ntdb;
+ NTDB_DATA key = { (unsigned char *)&i, sizeof(i) };
+ NTDB_DATA data = { (unsigned char *)&i, sizeof(i) };
+ union ntdb_attribute hattr = { .hash = { .base = { NTDB_ATTRIBUTE_HASH },
+ .fn = failhash } };
+
+ hattr.base.next = &tap_log_attr;
+ plan_tests(1 + NUM_RECORDS + 2);
+
+ ntdb = ntdb_open("run-missing-entries.ntdb", NTDB_INTERNAL,
+ O_RDWR|O_CREAT|O_TRUNC, 0600, &hattr);
+ if (ok1(ntdb)) {
+ for (i = 0; i < NUM_RECORDS; i++) {
+ ok1(ntdb_store(ntdb, key, data, NTDB_REPLACE) == 0);
+ }
+ ok1(ntdb_check(ntdb, NULL, NULL) == 0);
+ ntdb_close(ntdb);
+ }
+
+ ok1(tap_log_messages == 0);
+ return exit_status();
+}
diff --git a/lib/ntdb/test/api-open-multiple-times.c b/lib/ntdb/test/api-open-multiple-times.c
new file mode 100644
index 0000000000..70bad00568
--- /dev/null
+++ b/lib/ntdb/test/api-open-multiple-times.c
@@ -0,0 +1,83 @@
+#include "config.h"
+#include "ntdb.h"
+#include "tap-interface.h"
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include "logging.h"
+
+int main(int argc, char *argv[])
+{
+ unsigned int i;
+ struct ntdb_context *ntdb, *ntdb2;
+ NTDB_DATA key = { (unsigned char *)&i, sizeof(i) };
+ NTDB_DATA data = { (unsigned char *)&i, sizeof(i) };
+ NTDB_DATA d = { NULL, 0 }; /* Bogus GCC warning */
+ int flags[] = { NTDB_DEFAULT, NTDB_NOMMAP,
+ NTDB_CONVERT, NTDB_NOMMAP|NTDB_CONVERT };
+
+ plan_tests(sizeof(flags) / sizeof(flags[0]) * 28);
+ for (i = 0; i < sizeof(flags) / sizeof(flags[0]); i++) {
+ ntdb = ntdb_open("run-open-multiple-times.ntdb", flags[i],
+ O_RDWR|O_CREAT|O_TRUNC, 0600, &tap_log_attr);
+ ok1(ntdb);
+ if (!ntdb)
+ continue;
+
+ ntdb2 = ntdb_open("run-open-multiple-times.ntdb", flags[i],
+ O_RDWR|O_CREAT, 0600, &tap_log_attr);
+ ok1(ntdb_check(ntdb, NULL, NULL) == 0);
+ ok1(ntdb_check(ntdb2, NULL, NULL) == 0);
+
+ /* Store in one, fetch in the other. */
+ ok1(ntdb_store(ntdb, key, data, NTDB_REPLACE) == 0);
+ ok1(ntdb_fetch(ntdb2, key, &d) == NTDB_SUCCESS);
+ ok1(ntdb_deq(d, data));
+ free(d.dptr);
+
+ /* Vice versa, with delete. */
+ ok1(ntdb_delete(ntdb2, key) == 0);
+ ok1(ntdb_fetch(ntdb, key, &d) == NTDB_ERR_NOEXIST);
+
+ /* OK, now close first one, check second still good. */
+ ok1(ntdb_close(ntdb) == 0);
+
+ ok1(ntdb_store(ntdb2, key, data, NTDB_REPLACE) == 0);
+ ok1(ntdb_fetch(ntdb2, key, &d) == NTDB_SUCCESS);
+ ok1(ntdb_deq(d, data));
+ free(d.dptr);
+
+ /* Reopen */
+ ntdb = ntdb_open("run-open-multiple-times.ntdb", flags[i],
+ O_RDWR|O_CREAT, 0600, &tap_log_attr);
+ ok1(ntdb);
+
+ ok1(ntdb_transaction_start(ntdb2) == 0);
+
+ /* Anything in the other one should fail. */
+ ok1(ntdb_fetch(ntdb, key, &d) == NTDB_ERR_LOCK);
+ ok1(tap_log_messages == 1);
+ ok1(ntdb_store(ntdb, key, data, NTDB_REPLACE) == NTDB_ERR_LOCK);
+ ok1(tap_log_messages == 2);
+ ok1(ntdb_transaction_start(ntdb) == NTDB_ERR_LOCK);
+ ok1(tap_log_messages == 3);
+ ok1(ntdb_chainlock(ntdb, key) == NTDB_ERR_LOCK);
+ ok1(tap_log_messages == 4);
+
+ /* Transaciton should work as normal. */
+ ok1(ntdb_store(ntdb2, key, data, NTDB_REPLACE) == NTDB_SUCCESS);
+
+ /* Now... try closing with locks held. */
+ ok1(ntdb_close(ntdb2) == 0);
+
+ ok1(ntdb_fetch(ntdb, key, &d) == NTDB_SUCCESS);
+ ok1(ntdb_deq(d, data));
+ free(d.dptr);
+ ok1(ntdb_close(ntdb) == 0);
+ ok1(tap_log_messages == 4);
+ tap_log_messages = 0;
+ }
+
+ return exit_status();
+}
diff --git a/lib/ntdb/test/api-record-expand.c b/lib/ntdb/test/api-record-expand.c
new file mode 100644
index 0000000000..cea5a10bfb
--- /dev/null
+++ b/lib/ntdb/test/api-record-expand.c
@@ -0,0 +1,51 @@
+#include "config.h"
+#include "ntdb.h"
+#include "tap-interface.h"
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include "logging.h"
+
+#define MAX_SIZE 10000
+#define SIZE_STEP 131
+
+int main(int argc, char *argv[])
+{
+ unsigned int i;
+ struct ntdb_context *ntdb;
+ int flags[] = { NTDB_INTERNAL, NTDB_DEFAULT, NTDB_NOMMAP,
+ NTDB_INTERNAL|NTDB_CONVERT, NTDB_CONVERT,
+ NTDB_NOMMAP|NTDB_CONVERT };
+ NTDB_DATA key = ntdb_mkdata("key", 3);
+ NTDB_DATA data;
+
+ data.dptr = malloc(MAX_SIZE);
+ memset(data.dptr, 0x24, MAX_SIZE);
+
+ plan_tests(sizeof(flags) / sizeof(flags[0])
+ * (3 + (1 + (MAX_SIZE/SIZE_STEP)) * 2) + 1);
+ for (i = 0; i < sizeof(flags) / sizeof(flags[0]); i++) {
+ ntdb = ntdb_open("run-record-expand.ntdb", flags[i],
+ O_RDWR|O_CREAT|O_TRUNC, 0600, &tap_log_attr);
+ ok1(ntdb);
+ if (!ntdb)
+ continue;
+
+ data.dsize = 0;
+ ok1(ntdb_store(ntdb, key, data, NTDB_INSERT) == 0);
+ ok1(ntdb_check(ntdb, NULL, NULL) == 0);
+ for (data.dsize = 0;
+ data.dsize < MAX_SIZE;
+ data.dsize += SIZE_STEP) {
+ memset(data.dptr, data.dsize, data.dsize);
+ ok1(ntdb_store(ntdb, key, data, NTDB_MODIFY) == 0);
+ ok1(ntdb_check(ntdb, NULL, NULL) == 0);
+ }
+ ntdb_close(ntdb);
+ }
+ ok1(tap_log_messages == 0);
+ free(data.dptr);
+
+ return exit_status();
+}
diff --git a/lib/ntdb/test/api-simple-delete.c b/lib/ntdb/test/api-simple-delete.c
new file mode 100644
index 0000000000..2b20e199ee
--- /dev/null
+++ b/lib/ntdb/test/api-simple-delete.c
@@ -0,0 +1,39 @@
+#include "config.h"
+#include "ntdb.h"
+#include "tap-interface.h"
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include "logging.h"
+
+int main(int argc, char *argv[])
+{
+ unsigned int i;
+ struct ntdb_context *ntdb;
+ int flags[] = { NTDB_INTERNAL, NTDB_DEFAULT, NTDB_NOMMAP,
+ NTDB_INTERNAL|NTDB_CONVERT, NTDB_CONVERT,
+ NTDB_NOMMAP|NTDB_CONVERT };
+ NTDB_DATA key = ntdb_mkdata("key", 3);
+ NTDB_DATA data = ntdb_mkdata("data", 4);
+
+ plan_tests(sizeof(flags) / sizeof(flags[0]) * 7 + 1);
+ for (i = 0; i < sizeof(flags) / sizeof(flags[0]); i++) {
+ ntdb = ntdb_open("run-simple-delete.ntdb", flags[i],
+ O_RDWR|O_CREAT|O_TRUNC, 0600, &tap_log_attr);
+ ok1(ntdb);
+ if (ntdb) {
+ /* Delete should fail. */
+ ok1(ntdb_delete(ntdb, key) == NTDB_ERR_NOEXIST);
+ ok1(ntdb_check(ntdb, NULL, NULL) == 0);
+ /* Insert should succeed. */
+ ok1(ntdb_store(ntdb, key, data, NTDB_INSERT) == 0);
+ ok1(ntdb_check(ntdb, NULL, NULL) == 0);
+ /* Delete should now work. */
+ ok1(ntdb_delete(ntdb, key) == 0);
+ ok1(ntdb_check(ntdb, NULL, NULL) == 0);
+ ntdb_close(ntdb);
+ }
+ }
+ ok1(tap_log_messages == 0);
+ return exit_status();
+}
diff --git a/lib/ntdb/test/api-summary.c b/lib/ntdb/test/api-summary.c
new file mode 100644
index 0000000000..8060ef29be
--- /dev/null
+++ b/lib/ntdb/test/api-summary.c
@@ -0,0 +1,58 @@
+#include "config.h"
+#include "ntdb.h"
+#include "tap-interface.h"
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include "logging.h"
+
+int main(int argc, char *argv[])
+{
+ unsigned int i, j;
+ struct ntdb_context *ntdb;
+ int flags[] = { NTDB_INTERNAL, NTDB_DEFAULT, NTDB_NOMMAP,
+ NTDB_INTERNAL|NTDB_CONVERT, NTDB_CONVERT,
+ NTDB_NOMMAP|NTDB_CONVERT };
+ NTDB_DATA key = { (unsigned char *)&j, sizeof(j) };
+ NTDB_DATA data = { (unsigned char *)&j, sizeof(j) };
+ char *summary;
+
+ plan_tests(sizeof(flags) / sizeof(flags[0]) * (1 + 2 * 5) + 1);
+ for (i = 0; i < sizeof(flags) / sizeof(flags[0]); i++) {
+ ntdb = ntdb_open("run-summary.ntdb", flags[i],
+ O_RDWR|O_CREAT|O_TRUNC, 0600, &tap_log_attr);
+ ok1(ntdb);
+ if (!ntdb)
+ 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 (ntdb_store(ntdb, key, data, NTDB_REPLACE) != 0)
+ fail("Storing in ntdb");
+ }
+
+ for (j = 0;
+ j <= NTDB_SUMMARY_HISTOGRAMS;
+ j += NTDB_SUMMARY_HISTOGRAMS) {
+ ok1(ntdb_summary(ntdb, j, &summary) == NTDB_SUCCESS);
+ 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"));
+ if (j == NTDB_SUMMARY_HISTOGRAMS) {
+ ok1(strstr(summary, "|")
+ && strstr(summary, "*"));
+ } else {
+ ok1(!strstr(summary, "|")
+ && !strstr(summary, "*"));
+ }
+ free(summary);
+ }
+ ntdb_close(ntdb);
+ }
+
+ ok1(tap_log_messages == 0);
+ return exit_status();
+}
diff --git a/lib/ntdb/test/external-agent.c b/lib/ntdb/test/external-agent.c
new file mode 100644
index 0000000000..098d0cb595
--- /dev/null
+++ b/lib/ntdb/test/external-agent.c
@@ -0,0 +1,252 @@
+#include "external-agent.h"
+#include "logging.h"
+#include "lock-tracking.h"
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+#include <ccan/err/err.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <limits.h>
+#include <string.h>
+#include <errno.h>
+#include "tap-interface.h"
+#include <stdio.h>
+#include <stdarg.h>
+
+static struct ntdb_context *ntdb;
+
+void (*external_agent_free)(void *) = free;
+
+static enum NTDB_ERROR clear_if_first(int fd, void *arg)
+{
+/* We hold a lock offset 4 always, so we can tell if anyone is holding it.
+ * (This is compatible with tdb's TDB_CLEAR_IF_FIRST flag). */
+ struct flock fl;
+
+ fl.l_type = F_WRLCK;
+ fl.l_whence = SEEK_SET;
+ fl.l_start = 4;
+ fl.l_len = 1;
+
+ if (fcntl(fd, F_SETLK, &fl) == 0) {
+ /* We must be first ones to open it! */
+ diag("agent truncating file!");
+ if (ftruncate(fd, 0) != 0) {
+ return NTDB_ERR_IO;
+ }
+ }
+ fl.l_type = F_RDLCK;
+ if (fcntl(fd, F_SETLKW, &fl) != 0) {
+ return NTDB_ERR_IO;
+ }
+ return NTDB_SUCCESS;
+}
+
+static enum agent_return do_operation(enum operation op, const char *name)
+{
+ NTDB_DATA k;
+ enum agent_return ret;
+ NTDB_DATA data;
+ enum NTDB_ERROR ecode;
+ union ntdb_attribute cif;
+
+ if (op != OPEN && op != OPEN_WITH_HOOK && !ntdb) {
+ diag("external: No ntdb open!");
+ return OTHER_FAILURE;
+ }
+
+ diag("external: %s", operation_name(op));
+
+ k = ntdb_mkdata(name, strlen(name));
+
+ locking_would_block = 0;
+ switch (op) {
+ case OPEN:
+ if (ntdb) {
+ diag("Already have ntdb %s open", ntdb_name(ntdb));
+ return OTHER_FAILURE;
+ }
+ ntdb = ntdb_open(name, NTDB_DEFAULT, O_RDWR, 0, &tap_log_attr);
+ if (!ntdb) {
+ if (!locking_would_block)
+ diag("Opening ntdb gave %s", strerror(errno));
+ forget_locking();
+ ret = OTHER_FAILURE;
+ } else
+ ret = SUCCESS;
+ break;
+ case OPEN_WITH_HOOK:
+ if (ntdb) {
+ diag("Already have ntdb %s open", ntdb_name(ntdb));
+ return OTHER_FAILURE;
+ }
+ cif.openhook.base.attr = NTDB_ATTRIBUTE_OPENHOOK;
+ cif.openhook.base.next = &tap_log_attr;
+ cif.openhook.fn = clear_if_first;
+ ntdb = ntdb_open(name, NTDB_DEFAULT, O_RDWR, 0, &cif);
+ if (!ntdb) {
+ if (!locking_would_block)
+ diag("Opening ntdb gave %s", strerror(errno));
+ forget_locking();
+ ret = OTHER_FAILURE;
+ } else
+ ret = SUCCESS;
+ break;
+ case FETCH:
+ ecode = ntdb_fetch(ntdb, k, &data);
+ if (ecode == NTDB_ERR_NOEXIST) {
+ ret = FAILED;
+ } else if (ecode < 0) {
+ ret = OTHER_FAILURE;
+ } else if (!ntdb_deq(data, k)) {
+ ret = OTHER_FAILURE;
+ external_agent_free(data.dptr);
+ } else {
+ ret = SUCCESS;
+ external_agent_free(data.dptr);
+ }
+ break;
+ case STORE:
+ ret = ntdb_store(ntdb, k, k, 0) == 0 ? SUCCESS : OTHER_FAILURE;
+ break;
+ case TRANSACTION_START:
+ ret = ntdb_transaction_start(ntdb) == 0 ? SUCCESS : OTHER_FAILURE;
+ break;
+ case TRANSACTION_COMMIT:
+ ret = ntdb_transaction_commit(ntdb)==0 ? SUCCESS : OTHER_FAILURE;
+ break;
+ case NEEDS_RECOVERY:
+ ret = external_agent_needs_rec(ntdb);
+ break;
+ case CHECK:
+ ret = ntdb_check(ntdb, NULL, NULL) == 0 ? SUCCESS : OTHER_FAILURE;
+ break;
+ case CLOSE:
+ ret = ntdb_close(ntdb) == 0 ? SUCCESS : OTHER_FAILURE;
+ ntdb = NULL;
+ break;
+ case SEND_SIGNAL:
+ /* We do this async */
+ ret = SUCCESS;
+ break;
+ default:
+ ret = OTHER_FAILURE;
+ }
+
+ if (locking_would_block)
+ ret = WOULD_HAVE_BLOCKED;
+
+ return ret;
+}
+
+struct agent {
+ int cmdfd, responsefd;
+};
+
+/* Do this before doing any ntdb stuff. Return handle, or NULL. */
+struct agent *prepare_external_agent(void)
+{
+ int pid, ret;
+ int command[2], response[2];
+ char name[1+PATH_MAX];
+
+ if (pipe(command) != 0 || pipe(response) != 0)
+ return NULL;
+
+ pid = fork();
+ if (pid < 0)
+ return NULL;
+
+ if (pid != 0) {
+ struct agent *agent = malloc(sizeof(*agent));
+
+ 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))
+ err(1, "Writing response");
+ if (name[0] == SEND_SIGNAL) {
+ struct timeval ten_ms;
+ ten_ms.tv_sec = 0;
+ ten_ms.tv_usec = 10000;
+ select(0, NULL, NULL, NULL, &ten_ms);
+ kill(getppid(), SIGUSR1);
+ }
+ }
+ exit(0);
+}
+
+/* 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;
+ strcpy(string+1, name);
+
+ 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_HOOK: return "OPEN_WITH_HOOK";
+ case FETCH: return "FETCH";
+ case STORE: return "STORE";
+ case CHECK: return "CHECK";
+ case TRANSACTION_START: return "TRANSACTION_START";
+ case TRANSACTION_COMMIT: return "TRANSACTION_COMMIT";
+ case NEEDS_RECOVERY: return "NEEDS_RECOVERY";
+ case SEND_SIGNAL: return "SEND_SIGNAL";
+ case CLOSE: return "CLOSE";
+ }
+ return "**INVALID**";
+}
+
+void free_external_agent(struct agent *agent)
+{
+ close(agent->cmdfd);
+ close(agent->responsefd);
+ free(agent);
+}
diff --git a/lib/ntdb/test/external-agent.h b/lib/ntdb/test/external-agent.h
new file mode 100644
index 0000000000..c6b83d5b49
--- /dev/null
+++ b/lib/ntdb/test/external-agent.h
@@ -0,0 +1,51 @@
+#ifndef NTDB_TEST_EXTERNAL_AGENT_H
+#define NTDB_TEST_EXTERNAL_AGENT_H
+
+/* For locking tests, we need a different process to try things at
+ * various times. */
+enum operation {
+ OPEN,
+ OPEN_WITH_HOOK,
+ FETCH,
+ STORE,
+ TRANSACTION_START,
+ TRANSACTION_COMMIT,
+ NEEDS_RECOVERY,
+ CHECK,
+ SEND_SIGNAL,
+ CLOSE,
+};
+
+/* Do this before doing any ntdb stuff. Return handle, or -1. */
+struct agent *prepare_external_agent(void);
+
+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 == ntdb 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);
+
+/* Hook into free() on ntdb_data in external agent. */
+extern void (*external_agent_free)(void *);
+
+/* Mapping enum -> string. */
+const char *agent_return_name(enum agent_return ret);
+const char *operation_name(enum operation op);
+
+void free_external_agent(struct agent *agent);
+
+/* Internal use: */
+struct ntdb_context;
+enum agent_return external_agent_needs_rec(struct ntdb_context *ntdb);
+
+#endif /* NTDB_TEST_EXTERNAL_AGENT_H */
diff --git a/lib/ntdb/test/failtest_helper.c b/lib/ntdb/test/failtest_helper.c
new file mode 100644
index 0000000000..cc110919c3
--- /dev/null
+++ b/lib/ntdb/test/failtest_helper.c
@@ -0,0 +1,96 @@
+#include "failtest_helper.h"
+#include "logging.h"
+#include <string.h>
+#include "tap-interface.h"
+
+bool failtest_suppress = false;
+
+/* FIXME: From ccan/str */
+static inline bool strends(const char *str, const char *postfix)
+{
+ if (strlen(str) < strlen(postfix))
+ return false;
+
+ return !strcmp(str + strlen(str) - strlen(postfix), postfix);
+}
+
+bool failmatch(const struct failtest_call *call,
+ const char *file, int line, enum failtest_call_type type)
+{
+ return call->type == type
+ && call->line == line
+ && ((strcmp(call->file, file) == 0)
+ || (strends(call->file, file)
+ && (call->file[strlen(call->file) - strlen(file) - 1]
+ == '/')));
+}
+
+static bool is_nonblocking_lock(const struct failtest_call *call)
+{
+ return call->type == FAILTEST_FCNTL && call->u.fcntl.cmd == F_SETLK;
+}
+
+static bool is_unlock(const struct failtest_call *call)
+{
+ return call->type == FAILTEST_FCNTL
+ && call->u.fcntl.arg.fl.l_type == F_UNLCK;
+}
+
+bool exit_check_log(struct tlist_calls *history)
+{
+ const struct failtest_call *i;
+
+ tlist_for_each(history, i, list) {
+ if (!i->fail)
+ continue;
+ /* Failing the /dev/urandom open doesn't count: we fall back. */
+ if (failmatch(i, URANDOM_OPEN))
+ continue;
+
+ /* Similarly with read fail. */
+ if (failmatch(i, URANDOM_READ))
+ continue;
+
+ /* Initial allocation of ntdb doesn't log. */
+ if (failmatch(i, INITIAL_NTDB_MALLOC))
+ continue;
+
+ /* We don't block "failures" on non-blocking locks. */
+ if (is_nonblocking_lock(i))
+ continue;
+
+ if (!tap_log_messages)
+ diag("We didn't log for %s:%u", i->file, i->line);
+ return tap_log_messages != 0;
+ }
+ return true;
+}
+
+/* Some places we soldier on despite errors: only fail them once. */
+enum failtest_result
+block_repeat_failures(struct tlist_calls *history)
+{
+ const struct failtest_call *last;
+
+ last = tlist_tail(history, list);
+
+ if (failtest_suppress)
+ return FAIL_DONT_FAIL;
+
+ if (failmatch(last, INITIAL_NTDB_MALLOC)
+ || failmatch(last, URANDOM_OPEN)
+ || failmatch(last, URANDOM_READ)) {
+ return FAIL_PROBE;
+ }
+
+ /* We handle mmap failing, by falling back to read/write, so
+ * don't try all possible paths. */
+ if (last->type == FAILTEST_MMAP)
+ return FAIL_PROBE;
+
+ /* Unlock or non-blocking lock is fail-once. */
+ if (is_unlock(last) || is_nonblocking_lock(last))
+ return FAIL_PROBE;
+
+ return FAIL_OK;
+}
diff --git a/lib/ntdb/test/failtest_helper.h b/lib/ntdb/test/failtest_helper.h
new file mode 100644
index 0000000000..e754636402
--- /dev/null
+++ b/lib/ntdb/test/failtest_helper.h
@@ -0,0 +1,19 @@
+#ifndef NTDB_TEST_FAILTEST_HELPER_H
+#define NTDB_TEST_FAILTEST_HELPER_H
+#include <ccan/failtest/failtest.h>
+#include <stdbool.h>
+
+/* FIXME: Check these! */
+#define INITIAL_NTDB_MALLOC "open.c", 403, FAILTEST_MALLOC
+#define URANDOM_OPEN "open.c", 62, FAILTEST_OPEN
+#define URANDOM_READ "open.c", 42, FAILTEST_READ
+
+bool exit_check_log(struct tlist_calls *history);
+bool failmatch(const struct failtest_call *call,
+ const char *file, int line, enum failtest_call_type type);
+enum failtest_result block_repeat_failures(struct tlist_calls *history);
+
+/* Set this to suppress failure. */
+extern bool failtest_suppress;
+
+#endif /* NTDB_TEST_LOGGING_H */
diff --git a/lib/ntdb/test/helpapi-external-agent.c b/lib/ntdb/test/helpapi-external-agent.c
new file mode 100644
index 0000000000..eb81399072
--- /dev/null
+++ b/lib/ntdb/test/helpapi-external-agent.c
@@ -0,0 +1,7 @@
+#include "external-agent.h"
+
+/* This isn't possible with via the ntdb API, but this makes it link. */
+enum agent_return external_agent_needs_rec(struct ntdb_context *ntdb)
+{
+ return FAILED;
+}
diff --git a/lib/ntdb/test/helprun-external-agent.c b/lib/ntdb/test/helprun-external-agent.c
new file mode 100644
index 0000000000..81a3fe881d
--- /dev/null
+++ b/lib/ntdb/test/helprun-external-agent.c
@@ -0,0 +1,7 @@
+#include "external-agent.h"
+#include "private.h"
+
+enum agent_return external_agent_needs_rec(struct ntdb_context *ntdb)
+{
+ return ntdb_needs_recovery(ntdb) ? SUCCESS : FAILED;
+}
diff --git a/lib/ntdb/test/helprun-layout.c b/lib/ntdb/test/helprun-layout.c
new file mode 100644
index 0000000000..c8f1fd03c4
--- /dev/null
+++ b/lib/ntdb/test/helprun-layout.c
@@ -0,0 +1,402 @@
+/* NTDB tools to create various canned database layouts. */
+#include "layout.h"
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include <ccan/err/err.h>
+#include "logging.h"
+
+struct ntdb_layout *new_ntdb_layout(void)
+{
+ struct ntdb_layout *layout = malloc(sizeof(*layout));
+ layout->num_elems = 0;
+ layout->elem = NULL;
+ return layout;
+}
+
+static void add(struct ntdb_layout *layout, union ntdb_layout_elem elem)
+{
+ layout->elem = realloc(layout->elem,
+ sizeof(layout->elem[0])
+ * (layout->num_elems+1));
+ layout->elem[layout->num_elems++] = elem;
+}
+
+void ntdb_layout_add_freetable(struct ntdb_layout *layout)
+{
+ union ntdb_layout_elem elem;
+ elem.base.type = FREETABLE;
+ add(layout, elem);
+}
+
+void ntdb_layout_add_free(struct ntdb_layout *layout, ntdb_len_t len,
+ unsigned ftable)
+{
+ union ntdb_layout_elem elem;
+ elem.base.type = FREE;
+ elem.free.len = len;
+ elem.free.ftable_num = ftable;
+ add(layout, elem);
+}
+
+void ntdb_layout_add_capability(struct ntdb_layout *layout,
+ uint64_t type,
+ bool write_breaks,
+ bool check_breaks,
+ bool open_breaks,
+ ntdb_len_t extra)
+{
+ union ntdb_layout_elem elem;
+ elem.base.type = CAPABILITY;
+ elem.capability.type = type;
+ if (write_breaks)
+ elem.capability.type |= NTDB_CAP_NOWRITE;
+ if (open_breaks)
+ elem.capability.type |= NTDB_CAP_NOOPEN;
+ if (check_breaks)
+ elem.capability.type |= NTDB_CAP_NOCHECK;
+ elem.capability.extra = extra;
+ add(layout, elem);
+}
+
+static NTDB_DATA dup_key(NTDB_DATA key)
+{
+ NTDB_DATA ret;
+ ret.dsize = key.dsize;
+ ret.dptr = malloc(ret.dsize);
+ memcpy(ret.dptr, key.dptr, ret.dsize);
+ return ret;
+}
+
+void ntdb_layout_add_used(struct ntdb_layout *layout,
+ NTDB_DATA key, NTDB_DATA data,
+ ntdb_len_t extra)
+{
+ union ntdb_layout_elem elem;
+ elem.base.type = DATA;
+ elem.used.key = dup_key(key);
+ elem.used.data = dup_key(data);
+ elem.used.extra = extra;
+ add(layout, elem);
+}
+
+static ntdb_len_t free_record_len(ntdb_len_t len)
+{
+ return sizeof(struct ntdb_used_record) + len;
+}
+
+static ntdb_len_t data_record_len(struct tle_used *used)
+{
+ ntdb_len_t len;
+ len = sizeof(struct ntdb_used_record)
+ + used->key.dsize + used->data.dsize + used->extra;
+ assert(len >= sizeof(struct ntdb_free_record));
+ return len;
+}
+
+static ntdb_len_t hashtable_len(struct tle_hashtable *htable)
+{
+ return sizeof(struct ntdb_used_record)
+ + (sizeof(ntdb_off_t) << NTDB_SUBLEVEL_HASH_BITS)
+ + htable->extra;
+}
+
+static ntdb_len_t capability_len(struct tle_capability *cap)
+{
+ return sizeof(struct ntdb_capability) + cap->extra;
+}
+
+static ntdb_len_t freetable_len(struct tle_freetable *ftable)
+{
+ return sizeof(struct ntdb_freetable);
+}
+
+static void set_free_record(void *mem, ntdb_len_t len)
+{
+ /* We do all the work in add_to_freetable */
+}
+
+static void add_zero_pad(struct ntdb_used_record *u, size_t len, size_t extra)
+{
+ if (extra)
+ ((char *)(u + 1))[len] = '\0';
+}
+
+static void set_data_record(void *mem, struct ntdb_context *ntdb,
+ struct tle_used *used)
+{
+ struct ntdb_used_record *u = mem;
+
+ set_header(ntdb, u, NTDB_USED_MAGIC, used->key.dsize, used->data.dsize,
+ used->key.dsize + used->data.dsize + used->extra,
+ ntdb_hash(ntdb, used->key.dptr, used->key.dsize));
+ memcpy(u + 1, used->key.dptr, used->key.dsize);
+ memcpy((char *)(u + 1) + used->key.dsize,
+ used->data.dptr, used->data.dsize);
+ add_zero_pad(u, used->key.dsize + used->data.dsize, used->extra);
+}
+
+static void set_hashtable(void *mem, struct ntdb_context *ntdb,
+ struct tle_hashtable *htable)
+{
+ struct ntdb_used_record *u = mem;
+ ntdb_len_t len = sizeof(ntdb_off_t) << NTDB_SUBLEVEL_HASH_BITS;
+
+ set_header(ntdb, u, NTDB_HTABLE_MAGIC, 0, len, len + htable->extra, 0);
+ memset(u + 1, 0, len);
+ add_zero_pad(u, len, htable->extra);
+}
+
+static void set_capability(void *mem, struct ntdb_context *ntdb,
+ struct tle_capability *cap, struct ntdb_header *hdr,
+ ntdb_off_t last_cap)
+{
+ struct ntdb_capability *c = mem;
+ ntdb_len_t len = sizeof(*c) - sizeof(struct ntdb_used_record) + cap->extra;
+
+ c->type = cap->type;
+ c->next = 0;
+ set_header(ntdb, &c->hdr, NTDB_CAP_MAGIC, 0, len, len, 0);
+
+ /* Append to capability list. */
+ if (!last_cap) {
+ hdr->capabilities = cap->base.off;
+ } else {
+ c = (struct ntdb_capability *)((char *)hdr + last_cap);
+ c->next = cap->base.off;
+ }
+}
+
+static void set_freetable(void *mem, struct ntdb_context *ntdb,
+ struct tle_freetable *freetable, struct ntdb_header *hdr,
+ ntdb_off_t last_ftable)
+{
+ struct ntdb_freetable *ftable = mem;
+ memset(ftable, 0, sizeof(*ftable));
+ set_header(ntdb, &ftable->hdr, NTDB_FTABLE_MAGIC, 0,
+ sizeof(*ftable) - sizeof(ftable->hdr),
+ sizeof(*ftable) - sizeof(ftable->hdr), 0);
+
+ if (last_ftable) {
+ ftable = (struct ntdb_freetable *)((char *)hdr + last_ftable);
+ ftable->next = freetable->base.off;
+ } else {
+ hdr->free_table = freetable->base.off;
+ }
+}
+
+static void add_to_freetable(struct ntdb_context *ntdb,
+ ntdb_off_t eoff,
+ ntdb_off_t elen,
+ unsigned ftable,
+ struct tle_freetable *freetable)
+{
+ ntdb->ftable_off = freetable->base.off;
+ ntdb->ftable = ftable;
+ add_free_record(ntdb, eoff, sizeof(struct ntdb_used_record) + elen,
+ NTDB_LOCK_WAIT, false);
+}
+
+static ntdb_off_t hbucket_off(ntdb_off_t group_start, unsigned ingroup)
+{
+ return group_start
+ + (ingroup % (1 << NTDB_HASH_GROUP_BITS)) * sizeof(ntdb_off_t);
+}
+
+/* Get bits from a value. */
+static uint32_t bits(uint64_t val, unsigned start, unsigned num)
+{
+ assert(num <= 32);
+ return (val >> start) & ((1U << num) - 1);
+}
+
+/* We take bits from the top: that way we can lock whole sections of the hash
+ * by using lock ranges. */
+static uint32_t use_bits(uint64_t h, unsigned num, unsigned *used)
+{
+ *used += num;
+ return bits(h, 64 - *used, num);
+}
+
+static ntdb_off_t encode_offset(ntdb_off_t new_off, unsigned bucket,
+ uint64_t h)
+{
+ return bucket
+ | new_off
+ | ((uint64_t)bits(h, 64 - NTDB_OFF_UPPER_STEAL_EXTRA,
+ NTDB_OFF_UPPER_STEAL_EXTRA)
+ << NTDB_OFF_HASH_EXTRA_BIT);
+}
+
+/* FIXME: Our hash table handling here is primitive: we don't expand! */
+static void add_to_hashtable(struct ntdb_context *ntdb,
+ ntdb_off_t eoff,
+ NTDB_DATA key)
+{
+ uint64_t h = ntdb_hash(ntdb, key.dptr, key.dsize);
+ ntdb_off_t b_off, group_start;
+ unsigned i, group, in_group;
+ unsigned used = 0;
+
+ group = use_bits(h, NTDB_TOPLEVEL_HASH_BITS-NTDB_HASH_GROUP_BITS, &used);
+ in_group = use_bits(h, NTDB_HASH_GROUP_BITS, &used);
+
+ group_start = offsetof(struct ntdb_header, hashtable)
+ + group * (sizeof(ntdb_off_t) << NTDB_HASH_GROUP_BITS);
+
+ for (i = 0; i < (1 << NTDB_HASH_GROUP_BITS); i++) {
+ unsigned bucket = (in_group + i) % (1 << NTDB_HASH_GROUP_BITS);
+
+ b_off = hbucket_off(group_start, bucket);
+ if (ntdb_read_off(ntdb, b_off) == 0) {
+ ntdb_write_off(ntdb, b_off,
+ encode_offset(eoff, in_group, h));
+ return;
+ }
+ }
+ abort();
+}
+
+static struct tle_freetable *find_ftable(struct ntdb_layout *layout, unsigned num)
+{
+ unsigned i;
+
+ for (i = 0; i < layout->num_elems; i++) {
+ if (layout->elem[i].base.type != FREETABLE)
+ continue;
+ if (num == 0)
+ return &layout->elem[i].ftable;
+ num--;
+ }
+ abort();
+}
+
+/* FIXME: Support NTDB_CONVERT */
+struct ntdb_context *ntdb_layout_get(struct ntdb_layout *layout,
+ void (*freefn)(void *),
+ union ntdb_attribute *attr)
+{
+ unsigned int i;
+ ntdb_off_t off, len, last_ftable, last_cap;
+ char *mem;
+ struct ntdb_context *ntdb;
+
+ off = sizeof(struct ntdb_header);
+
+ /* First pass of layout: calc lengths */
+ for (i = 0; i < layout->num_elems; i++) {
+ union ntdb_layout_elem *e = &layout->elem[i];
+ e->base.off = off;
+ switch (e->base.type) {
+ case FREETABLE:
+ len = freetable_len(&e->ftable);
+ break;
+ case FREE:
+ len = free_record_len(e->free.len);
+ break;
+ case DATA:
+ len = data_record_len(&e->used);
+ break;
+ case HASHTABLE:
+ len = hashtable_len(&e->hashtable);
+ break;
+ case CAPABILITY:
+ len = capability_len(&e->capability);
+ break;
+ default:
+ abort();
+ }
+ off += len;
+ }
+
+ mem = malloc(off);
+ /* Fill with some weird pattern. */
+ memset(mem, 0x99, off);
+ /* Now populate our header, cribbing from a real NTDB header. */
+ ntdb = ntdb_open(NULL, NTDB_INTERNAL, O_RDWR, 0, attr);
+ memcpy(mem, ntdb->file->map_ptr, sizeof(struct ntdb_header));
+
+ /* Mug the ntdb we have to make it use this. */
+ freefn(ntdb->file->map_ptr);
+ ntdb->file->map_ptr = mem;
+ ntdb->file->map_size = off;
+
+ last_ftable = 0;
+ last_cap = 0;
+ for (i = 0; i < layout->num_elems; i++) {
+ union ntdb_layout_elem *e = &layout->elem[i];
+ switch (e->base.type) {
+ case FREETABLE:
+ set_freetable(mem + e->base.off, ntdb, &e->ftable,
+ (struct ntdb_header *)mem, last_ftable);
+ last_ftable = e->base.off;
+ break;
+ case FREE:
+ set_free_record(mem + e->base.off, e->free.len);
+ break;
+ case DATA:
+ set_data_record(mem + e->base.off, ntdb, &e->used);
+ break;
+ case HASHTABLE:
+ set_hashtable(mem + e->base.off, ntdb, &e->hashtable);
+ break;
+ case CAPABILITY:
+ set_capability(mem + e->base.off, ntdb, &e->capability,
+ (struct ntdb_header *)mem, last_cap);
+ last_cap = e->base.off;
+ break;
+ }
+ }
+ /* Must have a free table! */
+ assert(last_ftable);
+
+ /* Now fill the free and hash tables. */
+ for (i = 0; i < layout->num_elems; i++) {
+ union ntdb_layout_elem *e = &layout->elem[i];
+ switch (e->base.type) {
+ case FREE:
+ add_to_freetable(ntdb, e->base.off, e->free.len,
+ e->free.ftable_num,
+ find_ftable(layout, e->free.ftable_num));
+ break;
+ case DATA:
+ add_to_hashtable(ntdb, e->base.off, e->used.key);
+ break;
+ default:
+ break;
+ }
+ }
+
+ ntdb->ftable_off = find_ftable(layout, 0)->base.off;
+ return ntdb;
+}
+
+void ntdb_layout_write(struct ntdb_layout *layout, void (*freefn)(void *),
+ union ntdb_attribute *attr, const char *filename)
+{
+ struct ntdb_context *ntdb = ntdb_layout_get(layout, freefn, attr);
+ int fd;
+
+ fd = open(filename, O_WRONLY|O_TRUNC|O_CREAT, 0600);
+ if (fd < 0)
+ err(1, "opening %s for writing", filename);
+ if (write(fd, ntdb->file->map_ptr, ntdb->file->map_size)
+ != ntdb->file->map_size)
+ err(1, "writing %s", filename);
+ close(fd);
+ ntdb_close(ntdb);
+}
+
+void ntdb_layout_free(struct ntdb_layout *layout)
+{
+ unsigned int i;
+
+ for (i = 0; i < layout->num_elems; i++) {
+ if (layout->elem[i].base.type == DATA) {
+ free(layout->elem[i].used.key.dptr);
+ free(layout->elem[i].used.data.dptr);
+ }
+ }
+ free(layout->elem);
+ free(layout);
+}
diff --git a/lib/ntdb/test/layout.h b/lib/ntdb/test/layout.h
new file mode 100644
index 0000000000..bcd20b8965
--- /dev/null
+++ b/lib/ntdb/test/layout.h
@@ -0,0 +1,87 @@
+#ifndef NTDB_TEST_LAYOUT_H
+#define NTDB_TEST_LAYOUT_H
+#include "private.h"
+
+struct ntdb_layout *new_ntdb_layout(void);
+void ntdb_layout_add_freetable(struct ntdb_layout *layout);
+void ntdb_layout_add_free(struct ntdb_layout *layout, ntdb_len_t len,
+ unsigned ftable);
+void ntdb_layout_add_used(struct ntdb_layout *layout,
+ NTDB_DATA key, NTDB_DATA data,
+ ntdb_len_t extra);
+void ntdb_layout_add_capability(struct ntdb_layout *layout,
+ uint64_t type,
+ bool write_breaks,
+ bool check_breaks,
+ bool open_breaks,
+ ntdb_len_t extra);
+
+#if 0 /* FIXME: Allow allocation of subtables */
+void ntdb_layout_add_hashtable(struct ntdb_layout *layout,
+ int htable_parent, /* -1 == toplevel */
+ unsigned int bucket,
+ ntdb_len_t extra);
+#endif
+/* freefn is needed if we're using failtest_free. */
+struct ntdb_context *ntdb_layout_get(struct ntdb_layout *layout,
+ void (*freefn)(void *),
+ union ntdb_attribute *attr);
+void ntdb_layout_write(struct ntdb_layout *layout, void (*freefn)(void *),
+ union ntdb_attribute *attr, const char *filename);
+
+void ntdb_layout_free(struct ntdb_layout *layout);
+
+enum layout_type {
+ FREETABLE, FREE, DATA, HASHTABLE, CAPABILITY
+};
+
+/* Shared by all union members. */
+struct tle_base {
+ enum layout_type type;
+ ntdb_off_t off;
+};
+
+struct tle_freetable {
+ struct tle_base base;
+};
+
+struct tle_free {
+ struct tle_base base;
+ ntdb_len_t len;
+ unsigned ftable_num;
+};
+
+struct tle_used {
+ struct tle_base base;
+ NTDB_DATA key;
+ NTDB_DATA data;
+ ntdb_len_t extra;
+};
+
+struct tle_hashtable {
+ struct tle_base base;
+ int parent;
+ unsigned int bucket;
+ ntdb_len_t extra;
+};
+
+struct tle_capability {
+ struct tle_base base;
+ uint64_t type;
+ ntdb_len_t extra;
+};
+
+union ntdb_layout_elem {
+ struct tle_base base;
+ struct tle_freetable ftable;
+ struct tle_free free;
+ struct tle_used used;
+ struct tle_hashtable hashtable;
+ struct tle_capability capability;
+};
+
+struct ntdb_layout {
+ unsigned int num_elems;
+ union ntdb_layout_elem *elem;
+};
+#endif /* NTDB_TEST_LAYOUT_H */
diff --git a/lib/ntdb/test/lock-tracking.c b/lib/ntdb/test/lock-tracking.c
new file mode 100644
index 0000000000..525a5c4ca7
--- /dev/null
+++ b/lib/ntdb/test/lock-tracking.c
@@ -0,0 +1,147 @@
+/* We save the locks so we can reaquire them. */
+#include "private.h" /* For NTDB_HASH_LOCK_START, etc. */
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include "tap-interface.h"
+#include "lock-tracking.h"
+
+struct lock {
+ struct lock *next;
+ unsigned int off;
+ unsigned int len;
+ int type;
+};
+static struct lock *locks;
+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 lock **l;
+ struct lock *old = NULL;
+
+ for (l = &locks; *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 (!old && !suppress_lockcheck) {
+ diag("Unknown unlock %u@%u - %i",
+ (int)fl->l_len, (int)fl->l_start, ret);
+ locking_errors++;
+ }
+ } else {
+ struct lock *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 = locks; 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;
+
+ /* ntdb_allrecord_lock does this, handle adjacent: */
+ if (fl->l_start > NTDB_HASH_LOCK_START
+ && 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 == NTDB_HASH_LOCK_START
+ && fl->l_start == NTDB_HASH_LOCK_START
+ && i->len == 0
+ && fl->l_len == 0) {
+ if (ret == 0)
+ i->type = F_WRLCK;
+ goto done;
+ }
+ if (!suppress_lockcheck) {
+ diag("%s lock %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 = locks;
+ locks = 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 (locks) {
+ struct lock *next = locks->next;
+ free(locks);
+ locks = next;
+ num++;
+ }
+ return num;
+}
diff --git a/lib/ntdb/test/lock-tracking.h b/lib/ntdb/test/lock-tracking.h
new file mode 100644
index 0000000000..f2c9c44653
--- /dev/null
+++ b/lib/ntdb/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/ntdb/test/logging.c b/lib/ntdb/test/logging.c
new file mode 100644
index 0000000000..2819dd7cad
--- /dev/null
+++ b/lib/ntdb/test/logging.c
@@ -0,0 +1,30 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include "tap-interface.h"
+#include "logging.h"
+
+unsigned tap_log_messages;
+const char *log_prefix = "";
+char *log_last = NULL;
+bool suppress_logging;
+
+union ntdb_attribute tap_log_attr = {
+ .log = { .base = { .attr = NTDB_ATTRIBUTE_LOG },
+ .fn = tap_log_fn }
+};
+
+void tap_log_fn(struct ntdb_context *ntdb,
+ enum ntdb_log_level level,
+ enum NTDB_ERROR ecode,
+ const char *message, void *priv)
+{
+ if (suppress_logging)
+ return;
+
+ diag("ntdb log level %u: %s: %s%s",
+ level, ntdb_errorstr(ecode), log_prefix, message);
+ if (log_last)
+ free(log_last);
+ log_last = strdup(message);
+ tap_log_messages++;
+}
diff --git a/lib/ntdb/test/logging.h b/lib/ntdb/test/logging.h
new file mode 100644
index 0000000000..0336ccaba3
--- /dev/null
+++ b/lib/ntdb/test/logging.h
@@ -0,0 +1,17 @@
+#ifndef NTDB_TEST_LOGGING_H
+#define NTDB_TEST_LOGGING_H
+#include "ntdb.h"
+#include <stdbool.h>
+#include <string.h>
+
+extern bool suppress_logging;
+extern const char *log_prefix;
+extern unsigned tap_log_messages;
+extern union ntdb_attribute tap_log_attr;
+extern char *log_last;
+
+void tap_log_fn(struct ntdb_context *ntdb,
+ enum ntdb_log_level level,
+ enum NTDB_ERROR ecode,
+ const char *message, void *priv);
+#endif /* NTDB_TEST_LOGGING_H */
diff --git a/lib/ntdb/test/ntdb-source.h b/lib/ntdb/test/ntdb-source.h
new file mode 100644
index 0000000000..52268440d2
--- /dev/null
+++ b/lib/ntdb/test/ntdb-source.h
@@ -0,0 +1,11 @@
+#include "config.h"
+#include "check.c"
+#include "free.c"
+#include "hash.c"
+#include "io.c"
+#include "lock.c"
+#include "open.c"
+#include "summary.c"
+#include "ntdb.c"
+#include "transaction.c"
+#include "traverse.c"
diff --git a/lib/ntdb/test/run-001-encode.c b/lib/ntdb/test/run-001-encode.c
new file mode 100644
index 0000000000..12965676a2
--- /dev/null
+++ b/lib/ntdb/test/run-001-encode.c
@@ -0,0 +1,41 @@
+#include "ntdb-source.h"
+#include "tap-interface.h"
+#include "logging.h"
+
+int main(int argc, char *argv[])
+{
+ unsigned int i;
+ struct ntdb_used_record rec;
+ struct ntdb_context ntdb = { .log_fn = tap_log_fn };
+
+ plan_tests(64 + 32 + 48*6 + 1);
+
+ /* We should be able to encode any data value. */
+ for (i = 0; i < 64; i++)
+ ok1(set_header(&ntdb, &rec, NTDB_USED_MAGIC, 0, 1ULL << i,
+ 1ULL << i, 0) == 0);
+
+ /* And any key and data with < 64 bits between them. */
+ for (i = 0; i < 32; i++) {
+ ntdb_len_t dlen = 1ULL >> (63 - i), klen = 1ULL << i;
+ ok1(set_header(&ntdb, &rec, NTDB_USED_MAGIC, klen, dlen,
+ klen + dlen, 0) == 0);
+ }
+
+ /* We should neatly encode all values. */
+ for (i = 0; i < 48; i++) {
+ uint64_t h = 1ULL << (i < 5 ? i : 4);
+ uint64_t klen = 1ULL << (i < 16 ? i : 15);
+ uint64_t dlen = 1ULL << i;
+ uint64_t xlen = 1ULL << (i < 32 ? i : 31);
+ ok1(set_header(&ntdb, &rec, NTDB_USED_MAGIC, klen, dlen,
+ klen+dlen+xlen, h) == 0);
+ ok1(rec_key_length(&rec) == klen);
+ ok1(rec_data_length(&rec) == dlen);
+ ok1(rec_extra_padding(&rec) == xlen);
+ ok1((uint64_t)rec_hash(&rec) == h);
+ ok1(rec_magic(&rec) == NTDB_USED_MAGIC);
+ }
+ ok1(tap_log_messages == 0);
+ return exit_status();
+}
diff --git a/lib/ntdb/test/run-001-fls.c b/lib/ntdb/test/run-001-fls.c
new file mode 100644
index 0000000000..ec61294c6f
--- /dev/null
+++ b/lib/ntdb/test/run-001-fls.c
@@ -0,0 +1,33 @@
+#include "ntdb-source.h"
+#include "tap-interface.h"
+
+static unsigned int dumb_fls(uint64_t num)
+{
+ int i;
+
+ for (i = 63; i >= 0; i--) {
+ if (num & (1ULL << i))
+ break;
+ }
+ return i + 1;
+}
+
+int main(int argc, char *argv[])
+{
+ unsigned int i, j;
+
+ plan_tests(64 * 64 + 2);
+
+ ok1(fls64(0) == 0);
+ ok1(dumb_fls(0) == 0);
+
+ for (i = 0; i < 64; i++) {
+ for (j = 0; j < 64; j++) {
+ uint64_t val = (1ULL << i) | (1ULL << j);
+ ok(fls64(val) == dumb_fls(val),
+ "%llu -> %u should be %u", (long long)val,
+ fls64(val), dumb_fls(val));
+ }
+ }
+ return exit_status();
+}
diff --git a/lib/ntdb/test/run-01-new_database.c b/lib/ntdb/test/run-01-new_database.c
new file mode 100644
index 0000000000..ae70e86e07
--- /dev/null
+++ b/lib/ntdb/test/run-01-new_database.c
@@ -0,0 +1,34 @@
+#include <ccan/failtest/failtest_override.h>
+#include "ntdb-source.h"
+#include "tap-interface.h"
+#include <ccan/failtest/failtest.h>
+#include "logging.h"
+#include "failtest_helper.h"
+
+int main(int argc, char *argv[])
+{
+ unsigned int i;
+ struct ntdb_context *ntdb;
+ int flags[] = { NTDB_INTERNAL, NTDB_DEFAULT, NTDB_NOMMAP,
+ NTDB_INTERNAL|NTDB_CONVERT, NTDB_CONVERT,
+ NTDB_NOMMAP|NTDB_CONVERT };
+
+ failtest_init(argc, argv);
+ failtest_hook = block_repeat_failures;
+ failtest_exit_check = exit_check_log;
+ plan_tests(sizeof(flags) / sizeof(flags[0]) * 3);
+ for (i = 0; i < sizeof(flags) / sizeof(flags[0]); i++) {
+ ntdb = ntdb_open("run-new_database.ntdb", flags[i],
+ O_RDWR|O_CREAT|O_TRUNC, 0600, &tap_log_attr);
+ if (!ok1(ntdb))
+ failtest_exit(exit_status());
+
+ failtest_suppress = true;
+ ok1(ntdb_check(ntdb, NULL, NULL) == 0);
+ failtest_suppress = false;
+ ntdb_close(ntdb);
+ if (!ok1(tap_log_messages == 0))
+ break;
+ }
+ failtest_exit(exit_status());
+}
diff --git a/lib/ntdb/test/run-02-expand.c b/lib/ntdb/test/run-02-expand.c
new file mode 100644
index 0000000000..abf1569388
--- /dev/null
+++ b/lib/ntdb/test/run-02-expand.c
@@ -0,0 +1,62 @@
+#include <ccan/failtest/failtest_override.h>
+#include "ntdb-source.h"
+#include "tap-interface.h"
+#include <ccan/failtest/failtest.h>
+#include "logging.h"
+#include "failtest_helper.h"
+
+int main(int argc, char *argv[])
+{
+ unsigned int i;
+ uint64_t val;
+ struct ntdb_context *ntdb;
+ int flags[] = { NTDB_INTERNAL, NTDB_DEFAULT, NTDB_NOMMAP,
+ NTDB_INTERNAL|NTDB_CONVERT, NTDB_CONVERT,
+ NTDB_NOMMAP|NTDB_CONVERT };
+
+ plan_tests(sizeof(flags) / sizeof(flags[0]) * 11 + 1);
+
+ failtest_init(argc, argv);
+ failtest_hook = block_repeat_failures;
+ failtest_exit_check = exit_check_log;
+
+ for (i = 0; i < sizeof(flags) / sizeof(flags[0]); i++) {
+ failtest_suppress = true;
+ ntdb = ntdb_open("run-expand.ntdb", flags[i],
+ O_RDWR|O_CREAT|O_TRUNC, 0600, &tap_log_attr);
+ if (!ok1(ntdb))
+ break;
+
+ val = ntdb->file->map_size;
+ /* Need some hash lock for expand. */
+ ok1(ntdb_lock_hashes(ntdb, 0, 1, F_WRLCK, NTDB_LOCK_WAIT) == 0);
+ failtest_suppress = false;
+ if (!ok1(ntdb_expand(ntdb, 1) == 0)) {
+ failtest_suppress = true;
+ ntdb_close(ntdb);
+ break;
+ }
+ failtest_suppress = true;
+
+ ok1(ntdb->file->map_size >= val + 1 * NTDB_EXTENSION_FACTOR);
+ ok1(ntdb_unlock_hashes(ntdb, 0, 1, F_WRLCK) == 0);
+ ok1(ntdb_check(ntdb, NULL, NULL) == 0);
+
+ val = ntdb->file->map_size;
+ ok1(ntdb_lock_hashes(ntdb, 0, 1, F_WRLCK, NTDB_LOCK_WAIT) == 0);
+ failtest_suppress = false;
+ if (!ok1(ntdb_expand(ntdb, 1024) == 0)) {
+ failtest_suppress = true;
+ ntdb_close(ntdb);
+ break;
+ }
+ failtest_suppress = true;
+ ok1(ntdb_unlock_hashes(ntdb, 0, 1, F_WRLCK) == 0);
+ ok1(ntdb->file->map_size >= val + 1024 * NTDB_EXTENSION_FACTOR);
+ ok1(ntdb_check(ntdb, NULL, NULL) == 0);
+ ntdb_close(ntdb);
+ }
+
+ ok1(tap_log_messages == 0);
+ failtest_exit(exit_status());
+}
diff --git a/lib/ntdb/test/run-03-coalesce.c b/lib/ntdb/test/run-03-coalesce.c
new file mode 100644
index 0000000000..f93b33a1c3
--- /dev/null
+++ b/lib/ntdb/test/run-03-coalesce.c
@@ -0,0 +1,178 @@
+#include "ntdb-source.h"
+#include "tap-interface.h"
+#include "logging.h"
+#include "layout.h"
+
+static ntdb_len_t free_record_length(struct ntdb_context *ntdb, ntdb_off_t off)
+{
+ struct ntdb_free_record f;
+ enum NTDB_ERROR ecode;
+
+ ecode = ntdb_read_convert(ntdb, off, &f, sizeof(f));
+ if (ecode != NTDB_SUCCESS)
+ return ecode;
+ if (frec_magic(&f) != NTDB_FREE_MAGIC)
+ return NTDB_ERR_CORRUPT;
+ return frec_len(&f);
+}
+
+int main(int argc, char *argv[])
+{
+ ntdb_off_t b_off, test;
+ struct ntdb_context *ntdb;
+ struct ntdb_layout *layout;
+ NTDB_DATA data, key;
+ ntdb_len_t len;
+
+ /* FIXME: Test NTDB_CONVERT */
+ /* FIXME: Test lock order fail. */
+
+ plan_tests(42);
+ data = ntdb_mkdata("world", 5);
+ key = ntdb_mkdata("hello", 5);
+
+ /* No coalescing can be done due to EOF */
+ layout = new_ntdb_layout();
+ ntdb_layout_add_freetable(layout);
+ len = 1024;
+ ntdb_layout_add_free(layout, len, 0);
+ ntdb_layout_write(layout, free, &tap_log_attr, "run-03-coalesce.ntdb");
+ /* NOMMAP is for lockcheck. */
+ ntdb = ntdb_open("run-03-coalesce.ntdb", NTDB_NOMMAP, O_RDWR, 0,
+ &tap_log_attr);
+ ok1(ntdb_check(ntdb, NULL, NULL) == 0);
+ ok1(free_record_length(ntdb, layout->elem[1].base.off) == len);
+
+ /* Figure out which bucket free entry is. */
+ b_off = bucket_off(ntdb->ftable_off, size_to_bucket(len));
+ /* Lock and fail to coalesce. */
+ ok1(ntdb_lock_free_bucket(ntdb, b_off, NTDB_LOCK_WAIT) == 0);
+ test = layout->elem[1].base.off;
+ ok1(coalesce(ntdb, layout->elem[1].base.off, b_off, len, &test)
+ == 0);
+ ntdb_unlock_free_bucket(ntdb, b_off);
+ ok1(free_record_length(ntdb, layout->elem[1].base.off) == len);
+ ok1(test == layout->elem[1].base.off);
+ ok1(ntdb_check(ntdb, NULL, NULL) == 0);
+ ntdb_close(ntdb);
+ ntdb_layout_free(layout);
+
+ /* No coalescing can be done due to used record */
+ layout = new_ntdb_layout();
+ ntdb_layout_add_freetable(layout);
+ ntdb_layout_add_free(layout, 1024, 0);
+ ntdb_layout_add_used(layout, key, data, 6);
+ ntdb_layout_write(layout, free, &tap_log_attr, "run-03-coalesce.ntdb");
+ /* NOMMAP is for lockcheck. */
+ ntdb = ntdb_open("run-03-coalesce.ntdb", NTDB_NOMMAP, O_RDWR, 0,
+ &tap_log_attr);
+ ok1(free_record_length(ntdb, layout->elem[1].base.off) == 1024);
+ ok1(ntdb_check(ntdb, NULL, NULL) == 0);
+
+ /* Figure out which bucket free entry is. */
+ b_off = bucket_off(ntdb->ftable_off, size_to_bucket(1024));
+ /* Lock and fail to coalesce. */
+ ok1(ntdb_lock_free_bucket(ntdb, b_off, NTDB_LOCK_WAIT) == 0);
+ test = layout->elem[1].base.off;
+ ok1(coalesce(ntdb, layout->elem[1].base.off, b_off, 1024, &test)
+ == 0);
+ ntdb_unlock_free_bucket(ntdb, b_off);
+ ok1(free_record_length(ntdb, layout->elem[1].base.off) == 1024);
+ ok1(test == layout->elem[1].base.off);
+ ok1(ntdb_check(ntdb, NULL, NULL) == 0);
+ ntdb_close(ntdb);
+ ntdb_layout_free(layout);
+
+ /* Coalescing can be done due to two free records, then EOF */
+ layout = new_ntdb_layout();
+ ntdb_layout_add_freetable(layout);
+ ntdb_layout_add_free(layout, 1024, 0);
+ ntdb_layout_add_free(layout, 2048, 0);
+ ntdb_layout_write(layout, free, &tap_log_attr, "run-03-coalesce.ntdb");
+ /* NOMMAP is for lockcheck. */
+ ntdb = ntdb_open("run-03-coalesce.ntdb", NTDB_NOMMAP, O_RDWR, 0,
+ &tap_log_attr);
+ ok1(free_record_length(ntdb, layout->elem[1].base.off) == 1024);
+ ok1(free_record_length(ntdb, layout->elem[2].base.off) == 2048);
+ ok1(ntdb_check(ntdb, NULL, NULL) == 0);
+
+ /* Figure out which bucket (first) free entry is. */
+ b_off = bucket_off(ntdb->ftable_off, size_to_bucket(1024));
+ /* Lock and coalesce. */
+ ok1(ntdb_lock_free_bucket(ntdb, b_off, NTDB_LOCK_WAIT) == 0);
+ test = layout->elem[2].base.off;
+ ok1(coalesce(ntdb, layout->elem[1].base.off, b_off, 1024, &test)
+ == 1024 + sizeof(struct ntdb_used_record) + 2048);
+ /* Should tell us it's erased this one... */
+ ok1(test == NTDB_ERR_NOEXIST);
+ ok1(ntdb->file->allrecord_lock.count == 0 && ntdb->file->num_lockrecs == 0);
+ ok1(free_record_length(ntdb, layout->elem[1].base.off)
+ == 1024 + sizeof(struct ntdb_used_record) + 2048);
+ ok1(ntdb_check(ntdb, NULL, NULL) == 0);
+ ntdb_close(ntdb);
+ ntdb_layout_free(layout);
+
+ /* Coalescing can be done due to two free records, then data */
+ layout = new_ntdb_layout();
+ ntdb_layout_add_freetable(layout);
+ ntdb_layout_add_free(layout, 1024, 0);
+ ntdb_layout_add_free(layout, 512, 0);
+ ntdb_layout_add_used(layout, key, data, 6);
+ ntdb_layout_write(layout, free, &tap_log_attr, "run-03-coalesce.ntdb");
+ /* NOMMAP is for lockcheck. */
+ ntdb = ntdb_open("run-03-coalesce.ntdb", NTDB_NOMMAP, O_RDWR, 0,
+ &tap_log_attr);
+ ok1(free_record_length(ntdb, layout->elem[1].base.off) == 1024);
+ ok1(free_record_length(ntdb, layout->elem[2].base.off) == 512);
+ ok1(ntdb_check(ntdb, NULL, NULL) == 0);
+
+ /* Figure out which bucket free entry is. */
+ b_off = bucket_off(ntdb->ftable_off, size_to_bucket(1024));
+ /* Lock and coalesce. */
+ ok1(ntdb_lock_free_bucket(ntdb, b_off, NTDB_LOCK_WAIT) == 0);
+ test = layout->elem[2].base.off;
+ ok1(coalesce(ntdb, layout->elem[1].base.off, b_off, 1024, &test)
+ == 1024 + sizeof(struct ntdb_used_record) + 512);
+ ok1(ntdb->file->allrecord_lock.count == 0 && ntdb->file->num_lockrecs == 0);
+ ok1(free_record_length(ntdb, layout->elem[1].base.off)
+ == 1024 + sizeof(struct ntdb_used_record) + 512);
+ ok1(test == NTDB_ERR_NOEXIST);
+ ok1(ntdb_check(ntdb, NULL, NULL) == 0);
+ ntdb_close(ntdb);
+ ntdb_layout_free(layout);
+
+ /* Coalescing can be done due to three free records, then EOF */
+ layout = new_ntdb_layout();
+ ntdb_layout_add_freetable(layout);
+ ntdb_layout_add_free(layout, 1024, 0);
+ ntdb_layout_add_free(layout, 512, 0);
+ ntdb_layout_add_free(layout, 256, 0);
+ ntdb_layout_write(layout, free, &tap_log_attr, "run-03-coalesce.ntdb");
+ /* NOMMAP is for lockcheck. */
+ ntdb = ntdb_open("run-03-coalesce.ntdb", NTDB_NOMMAP, O_RDWR, 0,
+ &tap_log_attr);
+ ok1(free_record_length(ntdb, layout->elem[1].base.off) == 1024);
+ ok1(free_record_length(ntdb, layout->elem[2].base.off) == 512);
+ ok1(free_record_length(ntdb, layout->elem[3].base.off) == 256);
+ ok1(ntdb_check(ntdb, NULL, NULL) == 0);
+
+ /* Figure out which bucket free entry is. */
+ b_off = bucket_off(ntdb->ftable_off, size_to_bucket(1024));
+ /* Lock and coalesce. */
+ ok1(ntdb_lock_free_bucket(ntdb, b_off, NTDB_LOCK_WAIT) == 0);
+ test = layout->elem[2].base.off;
+ ok1(coalesce(ntdb, layout->elem[1].base.off, b_off, 1024, &test)
+ == 1024 + sizeof(struct ntdb_used_record) + 512
+ + sizeof(struct ntdb_used_record) + 256);
+ ok1(ntdb->file->allrecord_lock.count == 0
+ && ntdb->file->num_lockrecs == 0);
+ ok1(free_record_length(ntdb, layout->elem[1].base.off)
+ == 1024 + sizeof(struct ntdb_used_record) + 512
+ + sizeof(struct ntdb_used_record) + 256);
+ ok1(ntdb_check(ntdb, NULL, NULL) == 0);
+ ntdb_close(ntdb);
+ ntdb_layout_free(layout);
+
+ ok1(tap_log_messages == 0);
+ return exit_status();
+}
diff --git a/lib/ntdb/test/run-04-basichash.c b/lib/ntdb/test/run-04-basichash.c
new file mode 100644
index 0000000000..6e3bdc012d
--- /dev/null
+++ b/lib/ntdb/test/run-04-basichash.c
@@ -0,0 +1,260 @@
+#include "ntdb-source.h"
+#include "tap-interface.h"
+#include "logging.h"
+
+/* We rig the hash so adjacent-numbered records always clash. */
+static uint64_t clash(const void *key, size_t len, uint64_t seed, void *priv)
+{
+ return ((uint64_t)*(const unsigned int *)key)
+ << (64 - NTDB_TOPLEVEL_HASH_BITS - 1);
+}
+
+int main(int argc, char *argv[])
+{
+ unsigned int i, j;
+ struct ntdb_context *ntdb;
+ unsigned int v;
+ struct ntdb_used_record rec;
+ NTDB_DATA key = { (unsigned char *)&v, sizeof(v) };
+ NTDB_DATA dbuf = { (unsigned char *)&v, sizeof(v) };
+ union ntdb_attribute hattr = { .hash = { .base = { NTDB_ATTRIBUTE_HASH },
+ .fn = clash } };
+ int flags[] = { NTDB_INTERNAL, NTDB_DEFAULT, NTDB_NOMMAP,
+ NTDB_INTERNAL|NTDB_CONVERT, NTDB_CONVERT,
+ NTDB_NOMMAP|NTDB_CONVERT,
+ };
+
+ hattr.base.next = &tap_log_attr;
+
+ plan_tests(sizeof(flags) / sizeof(flags[0])
+ * (91 + (2 * ((1 << NTDB_HASH_GROUP_BITS) - 1))) + 1);
+ for (i = 0; i < sizeof(flags) / sizeof(flags[0]); i++) {
+ struct hash_info h;
+ ntdb_off_t new_off, off, subhash;
+
+ ntdb = ntdb_open("run-04-basichash.ntdb", flags[i],
+ O_RDWR|O_CREAT|O_TRUNC, 0600, &hattr);
+ ok1(ntdb);
+ if (!ntdb)
+ continue;
+
+ v = 0;
+ /* Should not find it. */
+ ok1(find_and_lock(ntdb, key, F_WRLCK, &h, &rec, NULL) == 0);
+ /* Should have created correct hash. */
+ ok1(h.h == ntdb_hash(ntdb, key.dptr, key.dsize));
+ /* Should have located space in group 0, bucket 0. */
+ ok1(h.group_start == offsetof(struct ntdb_header, hashtable));
+ ok1(h.home_bucket == 0);
+ ok1(h.found_bucket == 0);
+ ok1(h.hash_used == NTDB_TOPLEVEL_HASH_BITS);
+
+ /* Should have lock on bucket 0 */
+ ok1(h.hlock_start == 0);
+ ok1(h.hlock_range ==
+ 1ULL << (64-(NTDB_TOPLEVEL_HASH_BITS-NTDB_HASH_GROUP_BITS)));
+ ok1((ntdb->flags & NTDB_NOLOCK) || ntdb->file->num_lockrecs == 1);
+ ok1((ntdb->flags & NTDB_NOLOCK)
+ || ntdb->file->lockrecs[0].off == NTDB_HASH_LOCK_START);
+ /* FIXME: Check lock length */
+
+ /* Allocate a new record. */
+ new_off = alloc(ntdb, key.dsize, dbuf.dsize, h.h,
+ NTDB_USED_MAGIC, false);
+ ok1(!NTDB_OFF_IS_ERR(new_off));
+
+ /* We should be able to add it now. */
+ ok1(add_to_hash(ntdb, &h, new_off) == 0);
+
+ /* Make sure we fill it in for later finding. */
+ off = new_off + sizeof(struct ntdb_used_record);
+ ok1(!ntdb->io->twrite(ntdb, off, key.dptr, key.dsize));
+ off += key.dsize;
+ ok1(!ntdb->io->twrite(ntdb, off, dbuf.dptr, dbuf.dsize));
+
+ /* We should be able to unlock that OK. */
+ ok1(ntdb_unlock_hashes(ntdb, h.hlock_start, h.hlock_range,
+ F_WRLCK) == 0);
+
+ /* Database should be consistent. */
+ ok1(ntdb_check(ntdb, NULL, NULL) == 0);
+
+ /* Now, this should give a successful lookup. */
+ ok1(find_and_lock(ntdb, key, F_WRLCK, &h, &rec, NULL)
+ == new_off);
+ /* Should have created correct hash. */
+ ok1(h.h == ntdb_hash(ntdb, key.dptr, key.dsize));
+ /* Should have located space in group 0, bucket 0. */
+ ok1(h.group_start == offsetof(struct ntdb_header, hashtable));
+ ok1(h.home_bucket == 0);
+ ok1(h.found_bucket == 0);
+ ok1(h.hash_used == NTDB_TOPLEVEL_HASH_BITS);
+
+ /* Should have lock on bucket 0 */
+ ok1(h.hlock_start == 0);
+ ok1(h.hlock_range ==
+ 1ULL << (64-(NTDB_TOPLEVEL_HASH_BITS-NTDB_HASH_GROUP_BITS)));
+ ok1((ntdb->flags & NTDB_NOLOCK) || ntdb->file->num_lockrecs == 1);
+ ok1((ntdb->flags & NTDB_NOLOCK)
+ || ntdb->file->lockrecs[0].off == NTDB_HASH_LOCK_START);
+ /* FIXME: Check lock length */
+
+ ok1(ntdb_unlock_hashes(ntdb, h.hlock_start, h.hlock_range,
+ F_WRLCK) == 0);
+
+ /* Database should be consistent. */
+ ok1(ntdb_check(ntdb, NULL, NULL) == 0);
+
+ /* Test expansion. */
+ v = 1;
+ ok1(find_and_lock(ntdb, key, F_WRLCK, &h, &rec, NULL) == 0);
+ /* Should have created correct hash. */
+ ok1(h.h == ntdb_hash(ntdb, key.dptr, key.dsize));
+ /* Should have located space in group 0, bucket 1. */
+ ok1(h.group_start == offsetof(struct ntdb_header, hashtable));
+ ok1(h.home_bucket == 0);
+ ok1(h.found_bucket == 1);
+ ok1(h.hash_used == NTDB_TOPLEVEL_HASH_BITS);
+
+ /* Should have lock on bucket 0 */
+ ok1(h.hlock_start == 0);
+ ok1(h.hlock_range ==
+ 1ULL << (64-(NTDB_TOPLEVEL_HASH_BITS-NTDB_HASH_GROUP_BITS)));
+ ok1((ntdb->flags & NTDB_NOLOCK) || ntdb->file->num_lockrecs == 1);
+ ok1((ntdb->flags & NTDB_NOLOCK)
+ || ntdb->file->lockrecs[0].off == NTDB_HASH_LOCK_START);
+ /* FIXME: Check lock length */
+
+ /* Make it expand 0'th bucket. */
+ ok1(expand_group(ntdb, &h) == 0);
+ /* First one should be subhash, next should be empty. */
+ ok1(is_subhash(h.group[0]));
+ subhash = (h.group[0] & NTDB_OFF_MASK);
+ for (j = 1; j < (1 << NTDB_HASH_GROUP_BITS); j++)
+ ok1(h.group[j] == 0);
+
+ ok1(ntdb_write_convert(ntdb, h.group_start,
+ h.group, sizeof(h.group)) == 0);
+ ok1(ntdb_unlock_hashes(ntdb, h.hlock_start, h.hlock_range,
+ F_WRLCK) == 0);
+
+ /* Should be happy with expansion. */
+ ok1(ntdb_check(ntdb, NULL, NULL) == 0);
+
+ /* Should be able to find it. */
+ v = 0;
+ ok1(find_and_lock(ntdb, key, F_WRLCK, &h, &rec, NULL)
+ == new_off);
+ /* Should have created correct hash. */
+ ok1(h.h == ntdb_hash(ntdb, key.dptr, key.dsize));
+ /* Should have located space in expanded group 0, bucket 0. */
+ ok1(h.group_start == subhash + sizeof(struct ntdb_used_record));
+ ok1(h.home_bucket == 0);
+ ok1(h.found_bucket == 0);
+ ok1(h.hash_used == NTDB_TOPLEVEL_HASH_BITS
+ + NTDB_SUBLEVEL_HASH_BITS);
+
+ /* Should have lock on bucket 0 */
+ ok1(h.hlock_start == 0);
+ ok1(h.hlock_range ==
+ 1ULL << (64-(NTDB_TOPLEVEL_HASH_BITS-NTDB_HASH_GROUP_BITS)));
+ ok1((ntdb->flags & NTDB_NOLOCK) || ntdb->file->num_lockrecs == 1);
+ ok1((ntdb->flags & NTDB_NOLOCK)
+ || ntdb->file->lockrecs[0].off == NTDB_HASH_LOCK_START);
+ /* FIXME: Check lock length */
+
+ /* Simple delete should work. */
+ ok1(delete_from_hash(ntdb, &h) == 0);
+ ok1(add_free_record(ntdb, new_off,
+ sizeof(struct ntdb_used_record)
+ + rec_key_length(&rec)
+ + rec_data_length(&rec)
+ + rec_extra_padding(&rec),
+ NTDB_LOCK_NOWAIT, false) == 0);
+ ok1(ntdb_unlock_hashes(ntdb, h.hlock_start, h.hlock_range,
+ F_WRLCK) == 0);
+ ok1(ntdb_check(ntdb, NULL, NULL) == 0);
+
+ /* Test second-level expansion: should expand 0th bucket. */
+ v = 0;
+ ok1(find_and_lock(ntdb, key, F_WRLCK, &h, &rec, NULL) == 0);
+ /* Should have created correct hash. */
+ ok1(h.h == ntdb_hash(ntdb, key.dptr, key.dsize));
+ /* Should have located space in group 0, bucket 0. */
+ ok1(h.group_start == subhash + sizeof(struct ntdb_used_record));
+ ok1(h.home_bucket == 0);
+ ok1(h.found_bucket == 0);
+ ok1(h.hash_used == NTDB_TOPLEVEL_HASH_BITS+NTDB_SUBLEVEL_HASH_BITS);
+
+ /* Should have lock on bucket 0 */
+ ok1(h.hlock_start == 0);
+ ok1(h.hlock_range ==
+ 1ULL << (64-(NTDB_TOPLEVEL_HASH_BITS-NTDB_HASH_GROUP_BITS)));
+ ok1((ntdb->flags & NTDB_NOLOCK) || ntdb->file->num_lockrecs == 1);
+ ok1((ntdb->flags & NTDB_NOLOCK)
+ || ntdb->file->lockrecs[0].off == NTDB_HASH_LOCK_START);
+ /* FIXME: Check lock length */
+
+ ok1(expand_group(ntdb, &h) == 0);
+ /* First one should be subhash, next should be empty. */
+ ok1(is_subhash(h.group[0]));
+ subhash = (h.group[0] & NTDB_OFF_MASK);
+ for (j = 1; j < (1 << NTDB_HASH_GROUP_BITS); j++)
+ ok1(h.group[j] == 0);
+ ok1(ntdb_write_convert(ntdb, h.group_start,
+ h.group, sizeof(h.group)) == 0);
+ ok1(ntdb_unlock_hashes(ntdb, h.hlock_start, h.hlock_range,
+ F_WRLCK) == 0);
+
+ /* Should be happy with expansion. */
+ ok1(ntdb_check(ntdb, NULL, NULL) == 0);
+
+ ok1(find_and_lock(ntdb, key, F_WRLCK, &h, &rec, NULL) == 0);
+ /* Should have created correct hash. */
+ ok1(h.h == ntdb_hash(ntdb, key.dptr, key.dsize));
+ /* Should have located space in group 0, bucket 0. */
+ ok1(h.group_start == subhash + sizeof(struct ntdb_used_record));
+ ok1(h.home_bucket == 0);
+ ok1(h.found_bucket == 0);
+ ok1(h.hash_used == NTDB_TOPLEVEL_HASH_BITS
+ + NTDB_SUBLEVEL_HASH_BITS * 2);
+
+ /* We should be able to add it now. */
+ /* Allocate a new record. */
+ new_off = alloc(ntdb, key.dsize, dbuf.dsize, h.h,
+ NTDB_USED_MAGIC, false);
+ ok1(!NTDB_OFF_IS_ERR(new_off));
+ ok1(add_to_hash(ntdb, &h, new_off) == 0);
+
+ /* Make sure we fill it in for later finding. */
+ off = new_off + sizeof(struct ntdb_used_record);
+ ok1(!ntdb->io->twrite(ntdb, off, key.dptr, key.dsize));
+ off += key.dsize;
+ ok1(!ntdb->io->twrite(ntdb, off, dbuf.dptr, dbuf.dsize));
+
+ /* We should be able to unlock that OK. */
+ ok1(ntdb_unlock_hashes(ntdb, h.hlock_start, h.hlock_range,
+ F_WRLCK) == 0);
+
+ /* Database should be consistent. */
+ ok1(ntdb_check(ntdb, NULL, NULL) == 0);
+
+ /* Should be able to find it. */
+ v = 0;
+ ok1(find_and_lock(ntdb, key, F_WRLCK, &h, &rec, NULL)
+ == new_off);
+ /* Should have created correct hash. */
+ ok1(h.h == ntdb_hash(ntdb, key.dptr, key.dsize));
+ /* Should have located space in expanded group 0, bucket 0. */
+ ok1(h.group_start == subhash + sizeof(struct ntdb_used_record));
+ ok1(h.home_bucket == 0);
+ ok1(h.found_bucket == 0);
+ ok1(h.hash_used == NTDB_TOPLEVEL_HASH_BITS
+ + NTDB_SUBLEVEL_HASH_BITS * 2);
+
+ ntdb_close(ntdb);
+ }
+
+ ok1(tap_log_messages == 0);
+ return exit_status();
+}
diff --git a/lib/ntdb/test/run-05-readonly-open.c b/lib/ntdb/test/run-05-readonly-open.c
new file mode 100644
index 0000000000..dd5aa26d0d
--- /dev/null
+++ b/lib/ntdb/test/run-05-readonly-open.c
@@ -0,0 +1,71 @@
+#include <ccan/failtest/failtest_override.h>
+#include "ntdb-source.h"
+#include "tap-interface.h"
+#include <ccan/failtest/failtest.h>
+#include "logging.h"
+#include "failtest_helper.h"
+
+int main(int argc, char *argv[])
+{
+ unsigned int i;
+ struct ntdb_context *ntdb;
+ int flags[] = { NTDB_DEFAULT, NTDB_NOMMAP,
+ NTDB_CONVERT, NTDB_NOMMAP|NTDB_CONVERT };
+ NTDB_DATA key = ntdb_mkdata("key", 3);
+ NTDB_DATA data = ntdb_mkdata("data", 4), d;
+ union ntdb_attribute seed_attr;
+ unsigned int msgs = 0;
+
+ failtest_init(argc, argv);
+ failtest_hook = block_repeat_failures;
+ failtest_exit_check = exit_check_log;
+
+ seed_attr.base.attr = NTDB_ATTRIBUTE_SEED;
+ seed_attr.base.next = &tap_log_attr;
+ seed_attr.seed.seed = 0;
+
+ failtest_suppress = true;
+ plan_tests(sizeof(flags) / sizeof(flags[0]) * 11);
+ for (i = 0; i < sizeof(flags) / sizeof(flags[0]); i++) {
+ ntdb = ntdb_open("run-05-readonly-open.ntdb", flags[i],
+ O_RDWR|O_CREAT|O_TRUNC, 0600,
+ &seed_attr);
+ ok1(ntdb_store(ntdb, key, data, NTDB_INSERT) == 0);
+ ntdb_close(ntdb);
+
+ failtest_suppress = false;
+ ntdb = ntdb_open("run-05-readonly-open.ntdb", flags[i],
+ O_RDONLY, 0600, &tap_log_attr);
+ if (!ok1(ntdb))
+ break;
+ ok1(tap_log_messages == msgs);
+ /* Fetch should succeed, stores should fail. */
+ if (!ok1(ntdb_fetch(ntdb, key, &d) == 0))
+ goto fail;
+ ok1(ntdb_deq(d, data));
+ free(d.dptr);
+ if (!ok1(ntdb_store(ntdb, key, data, NTDB_MODIFY)
+ == NTDB_ERR_RDONLY))
+ goto fail;
+ ok1(tap_log_messages == ++msgs);
+ if (!ok1(ntdb_store(ntdb, key, data, NTDB_INSERT)
+ == NTDB_ERR_RDONLY))
+ goto fail;
+ ok1(tap_log_messages == ++msgs);
+ failtest_suppress = true;
+ ok1(ntdb_check(ntdb, NULL, NULL) == 0);
+ ntdb_close(ntdb);
+ ok1(tap_log_messages == msgs);
+ /* SIGH: failtest bug, it doesn't save the ntdb file because
+ * we have it read-only. If we go around again, it gets
+ * changed underneath us and things get screwy. */
+ if (failtest_has_failed())
+ break;
+ }
+ failtest_exit(exit_status());
+
+fail:
+ failtest_suppress = true;
+ ntdb_close(ntdb);
+ failtest_exit(exit_status());
+}
diff --git a/lib/ntdb/test/run-10-simple-store.c b/lib/ntdb/test/run-10-simple-store.c
new file mode 100644
index 0000000000..6e718bf61f
--- /dev/null
+++ b/lib/ntdb/test/run-10-simple-store.c
@@ -0,0 +1,58 @@
+#include <ccan/failtest/failtest_override.h>
+#include "ntdb-source.h"
+#include "tap-interface.h"
+#include <ccan/failtest/failtest.h>
+#include "logging.h"
+#include "failtest_helper.h"
+
+int main(int argc, char *argv[])
+{
+ unsigned int i;
+ struct ntdb_context *ntdb;
+ int flags[] = { NTDB_INTERNAL, NTDB_DEFAULT, NTDB_NOMMAP,
+ NTDB_INTERNAL|NTDB_CONVERT, NTDB_CONVERT,
+ NTDB_NOMMAP|NTDB_CONVERT };
+ NTDB_DATA key = ntdb_mkdata("key", 3);
+ NTDB_DATA data = ntdb_mkdata("data", 4);
+
+ failtest_init(argc, argv);
+ failtest_hook = block_repeat_failures;
+ failtest_exit_check = exit_check_log;
+
+ failtest_suppress = true;
+ plan_tests(sizeof(flags) / sizeof(flags[0]) * 7 + 1);
+ for (i = 0; i < sizeof(flags) / sizeof(flags[0]); i++) {
+ ntdb = ntdb_open("run-10-simple-store.ntdb", flags[i],
+ O_RDWR|O_CREAT|O_TRUNC, 0600, &tap_log_attr);
+ if (!ok1(ntdb))
+ break;
+ /* Modify should fail. */
+ failtest_suppress = false;
+ if (!ok1(ntdb_store(ntdb, key, data, NTDB_MODIFY)
+ == NTDB_ERR_NOEXIST))
+ goto fail;
+ failtest_suppress = true;
+ ok1(ntdb_check(ntdb, NULL, NULL) == 0);
+ /* Insert should succeed. */
+ failtest_suppress = false;
+ if (!ok1(ntdb_store(ntdb, key, data, NTDB_INSERT) == 0))
+ goto fail;
+ failtest_suppress = true;
+ ok1(ntdb_check(ntdb, NULL, NULL) == 0);
+ /* Second insert should fail. */
+ failtest_suppress = false;
+ if (!ok1(ntdb_store(ntdb, key, data, NTDB_INSERT)
+ == NTDB_ERR_EXISTS))
+ goto fail;
+ failtest_suppress = true;
+ ok1(ntdb_check(ntdb, NULL, NULL) == 0);
+ ntdb_close(ntdb);
+ }
+ ok1(tap_log_messages == 0);
+ failtest_exit(exit_status());
+
+fail:
+ failtest_suppress = true;
+ ntdb_close(ntdb);
+ failtest_exit(exit_status());
+}
diff --git a/lib/ntdb/test/run-11-simple-fetch.c b/lib/ntdb/test/run-11-simple-fetch.c
new file mode 100644
index 0000000000..525cf46444
--- /dev/null
+++ b/lib/ntdb/test/run-11-simple-fetch.c
@@ -0,0 +1,58 @@
+#include <ccan/failtest/failtest_override.h>
+#include "ntdb-source.h"
+#include "tap-interface.h"
+#include <ccan/failtest/failtest.h>
+#include "logging.h"
+#include "failtest_helper.h"
+
+int main(int argc, char *argv[])
+{
+ unsigned int i;
+ struct ntdb_context *ntdb;
+ int flags[] = { NTDB_INTERNAL, NTDB_DEFAULT, NTDB_NOMMAP,
+ NTDB_INTERNAL|NTDB_CONVERT, NTDB_CONVERT,
+ NTDB_NOMMAP|NTDB_CONVERT };
+ NTDB_DATA key = ntdb_mkdata("key", 3);
+ NTDB_DATA data = ntdb_mkdata("data", 4);
+
+ failtest_init(argc, argv);
+ failtest_hook = block_repeat_failures;
+ failtest_exit_check = exit_check_log;
+
+ failtest_suppress = true;
+ plan_tests(sizeof(flags) / sizeof(flags[0]) * 8 + 1);
+ for (i = 0; i < sizeof(flags) / sizeof(flags[0]); i++) {
+ ntdb = ntdb_open("run-11-simple-fetch.ntdb", flags[i],
+ O_RDWR|O_CREAT|O_TRUNC, 0600, &tap_log_attr);
+ ok1(ntdb);
+ if (ntdb) {
+ NTDB_DATA d = { NULL, 0 }; /* Bogus GCC warning */
+
+ /* fetch should fail. */
+ failtest_suppress = false;
+ if (!ok1(ntdb_fetch(ntdb, key, &d) == NTDB_ERR_NOEXIST))
+ goto fail;
+ failtest_suppress = true;
+ ok1(ntdb_check(ntdb, NULL, NULL) == 0);
+ /* Insert should succeed. */
+ ok1(ntdb_store(ntdb, key, data, NTDB_INSERT) == 0);
+ ok1(ntdb_check(ntdb, NULL, NULL) == 0);
+ /* Fetch should now work. */
+ failtest_suppress = false;
+ if (!ok1(ntdb_fetch(ntdb, key, &d) == NTDB_SUCCESS))
+ goto fail;
+ failtest_suppress = true;
+ ok1(ntdb_deq(d, data));
+ free(d.dptr);
+ ok1(ntdb_check(ntdb, NULL, NULL) == 0);
+ ntdb_close(ntdb);
+ }
+ }
+ ok1(tap_log_messages == 0);
+ failtest_exit(exit_status());
+
+fail:
+ failtest_suppress = true;
+ ntdb_close(ntdb);
+ failtest_exit(exit_status());
+}
diff --git a/lib/ntdb/test/run-12-check.c b/lib/ntdb/test/run-12-check.c
new file mode 100644
index 0000000000..6040637048
--- /dev/null
+++ b/lib/ntdb/test/run-12-check.c
@@ -0,0 +1,46 @@
+#include "private.h"
+#include <ccan/failtest/failtest_override.h>
+#include "ntdb-source.h"
+#include "tap-interface.h"
+#include <ccan/failtest/failtest.h>
+#include "logging.h"
+#include "failtest_helper.h"
+
+int main(int argc, char *argv[])
+{
+ unsigned int i;
+ struct ntdb_context *ntdb;
+ int flags[] = { NTDB_INTERNAL,
+ NTDB_INTERNAL|NTDB_CONVERT,
+ NTDB_CONVERT };
+ NTDB_DATA key = ntdb_mkdata("key", 3);
+ NTDB_DATA data = ntdb_mkdata("data", 4);
+
+ failtest_init(argc, argv);
+ failtest_hook = block_repeat_failures;
+ failtest_exit_check = exit_check_log;
+
+ failtest_suppress = true;
+ plan_tests(sizeof(flags) / sizeof(flags[0]) * 3 + 1);
+ for (i = 0; i < sizeof(flags) / sizeof(flags[0]); i++) {
+ ntdb = ntdb_open("run-12-check.ntdb", flags[i],
+ O_RDWR|O_CREAT|O_TRUNC, 0600, &tap_log_attr);
+ ok1(ntdb);
+ ok1(ntdb_store(ntdb, key, data, NTDB_INSERT) == 0);
+
+ /* This is what we really want to test: ntdb_check(). */
+ failtest_suppress = false;
+ if (!ok1(ntdb_check(ntdb, NULL, NULL) == 0))
+ goto fail;
+ failtest_suppress = true;
+
+ ntdb_close(ntdb);
+ }
+ ok1(tap_log_messages == 0);
+ failtest_exit(exit_status());
+
+fail:
+ failtest_suppress = true;
+ ntdb_close(ntdb);
+ failtest_exit(exit_status());
+}
diff --git a/lib/ntdb/test/run-15-append.c b/lib/ntdb/test/run-15-append.c
new file mode 100644
index 0000000000..3c208137f2
--- /dev/null
+++ b/lib/ntdb/test/run-15-append.c
@@ -0,0 +1,130 @@
+#include "ntdb-source.h"
+#include "tap-interface.h"
+#include <ccan/ilog/ilog.h>
+#include "logging.h"
+
+#define MAX_SIZE 13100
+#define SIZE_STEP 131
+
+static ntdb_off_t ntdb_offset(struct ntdb_context *ntdb, NTDB_DATA key)
+{
+ ntdb_off_t off;
+ struct ntdb_used_record urec;
+ struct hash_info h;
+
+ off = find_and_lock(ntdb, key, F_RDLCK, &h, &urec, NULL);
+ if (NTDB_OFF_IS_ERR(off))
+ return 0;
+ ntdb_unlock_hashes(ntdb, h.hlock_start, h.hlock_range, F_RDLCK);
+ return off;
+}
+
+int main(int argc, char *argv[])
+{
+ unsigned int i, j, moves;
+ struct ntdb_context *ntdb;
+ unsigned char *buffer;
+ ntdb_off_t oldoff = 0, newoff;
+ int flags[] = { NTDB_INTERNAL, NTDB_DEFAULT, NTDB_NOMMAP,
+ NTDB_INTERNAL|NTDB_CONVERT, NTDB_CONVERT,
+ NTDB_NOMMAP|NTDB_CONVERT };
+ NTDB_DATA key = ntdb_mkdata("key", 3);
+ NTDB_DATA data;
+
+ buffer = malloc(MAX_SIZE);
+ for (i = 0; i < MAX_SIZE; i++)
+ buffer[i] = i;
+
+ plan_tests(sizeof(flags) / sizeof(flags[0])
+ * ((3 + MAX_SIZE/SIZE_STEP * 5) * 2 + 7)
+ + 1);
+
+ /* Using ntdb_store. */
+ for (i = 0; i < sizeof(flags) / sizeof(flags[0]); i++) {
+ ntdb = ntdb_open("run-append.ntdb", flags[i],
+ O_RDWR|O_CREAT|O_TRUNC, 0600, &tap_log_attr);
+ ok1(ntdb);
+ if (!ntdb)
+ continue;
+
+ moves = 0;
+ for (j = 0; j < MAX_SIZE; j += SIZE_STEP) {
+ data.dptr = buffer;
+ data.dsize = j;
+ ok1(ntdb_store(ntdb, key, data, NTDB_REPLACE) == 0);
+ ok1(ntdb_check(ntdb, NULL, NULL) == 0);
+ ok1(ntdb_fetch(ntdb, key, &data) == NTDB_SUCCESS);
+ ok1(data.dsize == j);
+ ok1(memcmp(data.dptr, buffer, data.dsize) == 0);
+ free(data.dptr);
+ newoff = ntdb_offset(ntdb, key);
+ if (newoff != oldoff)
+ moves++;
+ oldoff = newoff;
+ }
+ ok1(!ntdb->file || (ntdb->file->allrecord_lock.count == 0
+ && ntdb->file->num_lockrecs == 0));
+ /* We should increase by 50% each time... */
+ ok(moves <= ilog64(j / SIZE_STEP)*2,
+ "Moved %u times", moves);
+ ntdb_close(ntdb);
+ }
+
+ /* Using ntdb_append. */
+ for (i = 0; i < sizeof(flags) / sizeof(flags[0]); i++) {
+ size_t prev_len = 0;
+ ntdb = ntdb_open("run-append.ntdb", flags[i],
+ O_RDWR|O_CREAT|O_TRUNC, 0600, &tap_log_attr);
+ ok1(ntdb);
+ if (!ntdb)
+ continue;
+
+ moves = 0;
+ for (j = 0; j < MAX_SIZE; j += SIZE_STEP) {
+ data.dptr = buffer + prev_len;
+ data.dsize = j - prev_len;
+ ok1(ntdb_append(ntdb, key, data) == 0);
+ ok1(ntdb_check(ntdb, NULL, NULL) == 0);
+ ok1(ntdb_fetch(ntdb, key, &data) == NTDB_SUCCESS);
+ ok1(data.dsize == j);
+ ok1(memcmp(data.dptr, buffer, data.dsize) == 0);
+ free(data.dptr);
+ prev_len = data.dsize;
+ newoff = ntdb_offset(ntdb, key);
+ if (newoff != oldoff)
+ moves++;
+ oldoff = newoff;
+ }
+ ok1(!ntdb->file || (ntdb->file->allrecord_lock.count == 0
+ && ntdb->file->num_lockrecs == 0));
+ /* We should increase by 50% each time... */
+ ok(moves <= ilog64(j / SIZE_STEP)*2,
+ "Moved %u times", moves);
+ ntdb_close(ntdb);
+ }
+
+ for (i = 0; i < sizeof(flags) / sizeof(flags[0]); i++) {
+ ntdb = ntdb_open("run-append.ntdb", flags[i],
+ O_RDWR|O_CREAT|O_TRUNC, 0600, &tap_log_attr);
+ ok1(ntdb);
+ if (!ntdb)
+ continue;
+
+ /* Huge initial store. */
+ data.dptr = buffer;
+ data.dsize = MAX_SIZE;
+ ok1(ntdb_append(ntdb, key, data) == 0);
+ ok1(ntdb_check(ntdb, NULL, NULL) == 0);
+ ok1(ntdb_fetch(ntdb, key, &data) == NTDB_SUCCESS);
+ ok1(data.dsize == MAX_SIZE);
+ ok1(memcmp(data.dptr, buffer, data.dsize) == 0);
+ free(data.dptr);
+ ok1(!ntdb->file || (ntdb->file->allrecord_lock.count == 0
+ && ntdb->file->num_lockrecs == 0));
+ ntdb_close(ntdb);
+ }
+
+ ok1(tap_log_messages == 0);
+ free(buffer);
+ return exit_status();
+}
diff --git a/lib/ntdb/test/run-20-growhash.c b/lib/ntdb/test/run-20-growhash.c
new file mode 100644
index 0000000000..5559370f2a
--- /dev/null
+++ b/lib/ntdb/test/run-20-growhash.c
@@ -0,0 +1,137 @@
+#include "ntdb-source.h"
+#include "tap-interface.h"
+#include "logging.h"
+
+static uint64_t myhash(const void *key, size_t len, uint64_t seed, void *priv)
+{
+ return *(const uint64_t *)key;
+}
+
+static void add_bits(uint64_t *val, unsigned new, unsigned new_bits,
+ unsigned *done)
+{
+ *done += new_bits;
+ *val |= ((uint64_t)new << (64 - *done));
+}
+
+static uint64_t make_key(unsigned topgroup, unsigned topbucket,
+ unsigned subgroup1, unsigned subbucket1,
+ unsigned subgroup2, unsigned subbucket2)
+{
+ uint64_t key = 0;
+ unsigned done = 0;
+
+ add_bits(&key, topgroup, NTDB_TOPLEVEL_HASH_BITS - NTDB_HASH_GROUP_BITS,
+ &done);
+ add_bits(&key, topbucket, NTDB_HASH_GROUP_BITS, &done);
+ add_bits(&key, subgroup1, NTDB_SUBLEVEL_HASH_BITS - NTDB_HASH_GROUP_BITS,
+ &done);
+ add_bits(&key, subbucket1, NTDB_HASH_GROUP_BITS, &done);
+ add_bits(&key, subgroup2, NTDB_SUBLEVEL_HASH_BITS - NTDB_HASH_GROUP_BITS,
+ &done);
+ add_bits(&key, subbucket2, NTDB_HASH_GROUP_BITS, &done);
+ return key;
+}
+
+int main(int argc, char *argv[])
+{
+ unsigned int i, j;
+ struct ntdb_context *ntdb;
+ uint64_t kdata;
+ struct ntdb_used_record rec;
+ NTDB_DATA key = { (unsigned char *)&kdata, sizeof(kdata) };
+ NTDB_DATA dbuf = { (unsigned char *)&kdata, sizeof(kdata) };
+ union ntdb_attribute hattr = { .hash = { .base = { NTDB_ATTRIBUTE_HASH },
+ .fn = myhash } };
+ int flags[] = { NTDB_INTERNAL, NTDB_DEFAULT, NTDB_NOMMAP,
+ NTDB_INTERNAL|NTDB_CONVERT, NTDB_CONVERT,
+ NTDB_NOMMAP|NTDB_CONVERT,
+ };
+
+ hattr.base.next = &tap_log_attr;
+
+ plan_tests(sizeof(flags) / sizeof(flags[0])
+ * (9 + (20 + 2 * ((1 << NTDB_HASH_GROUP_BITS) - 2))
+ * (1 << NTDB_HASH_GROUP_BITS)) + 1);
+ for (i = 0; i < sizeof(flags) / sizeof(flags[0]); i++) {
+ struct hash_info h;
+
+ ntdb = ntdb_open("run-20-growhash.ntdb", flags[i],
+ O_RDWR|O_CREAT|O_TRUNC, 0600, &hattr);
+ ok1(ntdb);
+ if (!ntdb)
+ continue;
+
+ /* Fill a group. */
+ for (j = 0; j < (1 << NTDB_HASH_GROUP_BITS); j++) {
+ kdata = make_key(0, j, 0, 0, 0, 0);
+ ok1(ntdb_store(ntdb, key, dbuf, NTDB_INSERT) == 0);
+ }
+ ok1(ntdb_check(ntdb, NULL, NULL) == 0);
+
+ /* Check first still exists. */
+ kdata = make_key(0, 0, 0, 0, 0, 0);
+ ok1(find_and_lock(ntdb, key, F_RDLCK, &h, &rec, NULL) != 0);
+ /* Should have created correct hash. */
+ ok1(h.h == ntdb_hash(ntdb, key.dptr, key.dsize));
+ /* Should have located space in group 0, bucket 0. */
+ ok1(h.group_start == offsetof(struct ntdb_header, hashtable));
+ ok1(h.home_bucket == 0);
+ ok1(h.found_bucket == 0);
+ ok1(h.hash_used == NTDB_TOPLEVEL_HASH_BITS);
+ /* Entire group should be full! */
+ for (j = 0; j < (1 << NTDB_HASH_GROUP_BITS); j++)
+ ok1(h.group[j] != 0);
+
+ ok1(ntdb_unlock_hashes(ntdb, h.hlock_start, h.hlock_range,
+ F_RDLCK) == 0);
+
+ /* Now, add one more to each should expand (that) bucket. */
+ for (j = 0; j < (1 << NTDB_HASH_GROUP_BITS); j++) {
+ unsigned int k;
+ kdata = make_key(0, j, 0, 1, 0, 0);
+ ok1(ntdb_store(ntdb, key, dbuf, NTDB_INSERT) == 0);
+ ok1(ntdb_check(ntdb, NULL, NULL) == 0);
+
+ ok1(find_and_lock(ntdb, key, F_RDLCK, &h, &rec, NULL));
+ /* Should have created correct hash. */
+ ok1(h.h == ntdb_hash(ntdb, key.dptr, key.dsize));
+ /* Should have moved to subhash */
+ ok1(h.group_start >= sizeof(struct ntdb_header));
+ ok1(h.home_bucket == 1);
+ ok1(h.found_bucket == 1);
+ ok1(h.hash_used == NTDB_TOPLEVEL_HASH_BITS
+ + NTDB_SUBLEVEL_HASH_BITS);
+ ok1(ntdb_unlock_hashes(ntdb, h.hlock_start, h.hlock_range,
+ F_RDLCK) == 0);
+
+ /* Keep adding, make it expand again. */
+ for (k = 2; k < (1 << NTDB_HASH_GROUP_BITS); k++) {
+ kdata = make_key(0, j, 0, k, 0, 0);
+ ok1(ntdb_store(ntdb, key, dbuf, NTDB_INSERT) == 0);
+ ok1(ntdb_check(ntdb, NULL, NULL) == 0);
+ }
+
+ /* This should tip it over to sub-sub-hash. */
+ kdata = make_key(0, j, 0, 0, 0, 1);
+ ok1(ntdb_store(ntdb, key, dbuf, NTDB_INSERT) == 0);
+ ok1(ntdb_check(ntdb, NULL, NULL) == 0);
+
+ ok1(find_and_lock(ntdb, key, F_RDLCK, &h, &rec, NULL));
+ /* Should have created correct hash. */
+ ok1(h.h == ntdb_hash(ntdb, key.dptr, key.dsize));
+ /* Should have moved to subhash */
+ ok1(h.group_start >= sizeof(struct ntdb_header));
+ ok1(h.home_bucket == 1);
+ ok1(h.found_bucket == 1);
+ ok1(h.hash_used == NTDB_TOPLEVEL_HASH_BITS
+ + NTDB_SUBLEVEL_HASH_BITS + NTDB_SUBLEVEL_HASH_BITS);
+ ok1(ntdb_unlock_hashes(ntdb, h.hlock_start, h.hlock_range,
+ F_RDLCK) == 0);
+ }
+ ntdb_close(ntdb);
+ }
+
+ ok1(tap_log_messages == 0);
+ return exit_status();
+}
diff --git a/lib/ntdb/test/run-25-hashoverload.c b/lib/ntdb/test/run-25-hashoverload.c
new file mode 100644
index 0000000000..611eb71bf6
--- /dev/null
+++ b/lib/ntdb/test/run-25-hashoverload.c
@@ -0,0 +1,113 @@
+#include "ntdb-source.h"
+#include "tap-interface.h"
+#include "logging.h"
+
+static uint64_t badhash(const void *key, size_t len, uint64_t seed, void *priv)
+{
+ return 0;
+}
+
+static int trav(struct ntdb_context *ntdb, NTDB_DATA key, NTDB_DATA dbuf, void *p)
+{
+ if (p)
+ return ntdb_delete(ntdb, key);
+ return 0;
+}
+
+int main(int argc, char *argv[])
+{
+ unsigned int i, j;
+ struct ntdb_context *ntdb;
+ NTDB_DATA key = { (unsigned char *)&j, sizeof(j) };
+ NTDB_DATA dbuf = { (unsigned char *)&j, sizeof(j) };
+ union ntdb_attribute hattr = { .hash = { .base = { NTDB_ATTRIBUTE_HASH },
+ .fn = badhash } };
+ int flags[] = { NTDB_INTERNAL, NTDB_DEFAULT, NTDB_NOMMAP,
+ NTDB_INTERNAL|NTDB_CONVERT, NTDB_CONVERT,
+ NTDB_NOMMAP|NTDB_CONVERT,
+ };
+
+ hattr.base.next = &tap_log_attr;
+
+ plan_tests(6883);
+ for (i = 0; i < sizeof(flags) / sizeof(flags[0]); i++) {
+ NTDB_DATA d = { NULL, 0 }; /* Bogus GCC warning */
+
+ ntdb = ntdb_open("run-25-hashoverload.ntdb", flags[i],
+ O_RDWR|O_CREAT|O_TRUNC, 0600, &hattr);
+ ok1(ntdb);
+ if (!ntdb)
+ continue;
+
+ /* Fill a group. */
+ for (j = 0; j < (1 << NTDB_HASH_GROUP_BITS); j++) {
+ ok1(ntdb_store(ntdb, key, dbuf, NTDB_INSERT) == 0);
+ }
+ ok1(ntdb_check(ntdb, NULL, NULL) == 0);
+
+ /* Now store one last value: should form chain. */
+ ok1(ntdb_store(ntdb, key, dbuf, NTDB_INSERT) == 0);
+ ok1(ntdb_check(ntdb, NULL, NULL) == 0);
+
+ /* Check we can find them all. */
+ for (j = 0; j < (1 << NTDB_HASH_GROUP_BITS) + 1; j++) {
+ ok1(ntdb_fetch(ntdb, key, &d) == NTDB_SUCCESS);
+ ok1(d.dsize == sizeof(j));
+ ok1(d.dptr != NULL);
+ ok1(d.dptr && memcmp(d.dptr, &j, d.dsize) == 0);
+ free(d.dptr);
+ }
+
+ /* Now add a *lot* more. */
+ for (j = (1 << NTDB_HASH_GROUP_BITS) + 1;
+ j < (16 << NTDB_HASH_GROUP_BITS);
+ j++) {
+ ok1(ntdb_store(ntdb, key, dbuf, NTDB_INSERT) == 0);
+ ok1(ntdb_fetch(ntdb, key, &d) == NTDB_SUCCESS);
+ ok1(d.dsize == sizeof(j));
+ ok1(d.dptr != NULL);
+ ok1(d.dptr && memcmp(d.dptr, &j, d.dsize) == 0);
+ free(d.dptr);
+ }
+ ok1(ntdb_check(ntdb, NULL, NULL) == 0);
+
+ /* Traverse through them. */
+ ok1(ntdb_traverse(ntdb, trav, NULL) == j);
+
+ /* Empty the first chain-worth. */
+ for (j = 0; j < (1 << NTDB_HASH_GROUP_BITS); j++)
+ ok1(ntdb_delete(ntdb, key) == 0);
+
+ ok1(ntdb_check(ntdb, NULL, NULL) == 0);
+
+ for (j = (1 << NTDB_HASH_GROUP_BITS);
+ j < (16 << NTDB_HASH_GROUP_BITS);
+ j++) {
+ ok1(ntdb_fetch(ntdb, key, &d) == NTDB_SUCCESS);
+ ok1(d.dsize == sizeof(j));
+ ok1(d.dptr != NULL);
+ ok1(d.dptr && memcmp(d.dptr, &j, d.dsize) == 0);
+ free(d.dptr);
+ }
+
+ /* Traverse through them. */
+ ok1(ntdb_traverse(ntdb, trav, NULL)
+ == (15 << NTDB_HASH_GROUP_BITS));
+
+ /* Re-add */
+ for (j = 0; j < (1 << NTDB_HASH_GROUP_BITS); j++) {
+ ok1(ntdb_store(ntdb, key, dbuf, NTDB_INSERT) == 0);
+ }
+ ok1(ntdb_check(ntdb, NULL, NULL) == 0);
+
+ /* Now try deleting as we go. */
+ ok1(ntdb_traverse(ntdb, trav, trav)
+ == (16 << NTDB_HASH_GROUP_BITS));
+ ok1(ntdb_check(ntdb, NULL, NULL) == 0);
+ ok1(ntdb_traverse(ntdb, trav, NULL) == 0);
+ ntdb_close(ntdb);
+ }
+
+ ok1(tap_log_messages == 0);
+ return exit_status();
+}
diff --git a/lib/ntdb/test/run-30-exhaust-before-expand.c b/lib/ntdb/test/run-30-exhaust-before-expand.c
new file mode 100644
index 0000000000..b94bc01bff
--- /dev/null
+++ b/lib/ntdb/test/run-30-exhaust-before-expand.c
@@ -0,0 +1,71 @@
+#include "ntdb-source.h"
+#include "tap-interface.h"
+#include "logging.h"
+
+static bool empty_freetable(struct ntdb_context *ntdb)
+{
+ struct ntdb_freetable ftab;
+ unsigned int i;
+
+ /* Now, free table should be completely exhausted in zone 0 */
+ if (ntdb_read_convert(ntdb, ntdb->ftable_off, &ftab, sizeof(ftab)) != 0)
+ abort();
+
+ for (i = 0; i < sizeof(ftab.buckets)/sizeof(ftab.buckets[0]); i++) {
+ if (ftab.buckets[i])
+ return false;
+ }
+ return true;
+}
+
+
+int main(int argc, char *argv[])
+{
+ unsigned int i, j;
+ struct ntdb_context *ntdb;
+ int flags[] = { NTDB_INTERNAL, NTDB_DEFAULT, NTDB_NOMMAP,
+ NTDB_INTERNAL|NTDB_CONVERT, NTDB_CONVERT,
+ NTDB_NOMMAP|NTDB_CONVERT };
+
+ plan_tests(sizeof(flags) / sizeof(flags[0]) * 9 + 1);
+
+ for (i = 0; i < sizeof(flags) / sizeof(flags[0]); i++) {
+ NTDB_DATA k;
+ uint64_t size;
+ bool was_empty = false;
+
+ k.dptr = (void *)&j;
+ k.dsize = sizeof(j);
+
+ ntdb = ntdb_open("run-30-exhaust-before-expand.ntdb", flags[i],
+ O_RDWR|O_CREAT|O_TRUNC, 0600, &tap_log_attr);
+ ok1(ntdb);
+ if (!ntdb)
+ continue;
+
+ ok1(empty_freetable(ntdb));
+ /* Need some hash lock for expand. */
+ ok1(ntdb_lock_hashes(ntdb, 0, 1, F_WRLCK, NTDB_LOCK_WAIT) == 0);
+ /* Create some free space. */
+ ok1(ntdb_expand(ntdb, 1) == 0);
+ ok1(ntdb_unlock_hashes(ntdb, 0, 1, F_WRLCK) == 0);
+ ok1(ntdb_check(ntdb, NULL, NULL) == 0);
+ ok1(!empty_freetable(ntdb));
+
+ size = ntdb->file->map_size;
+ /* Insert minimal-length records until we expand. */
+ for (j = 0; ntdb->file->map_size == size; j++) {
+ was_empty = empty_freetable(ntdb);
+ if (ntdb_store(ntdb, k, k, NTDB_INSERT) != 0)
+ err(1, "Failed to store record %i", j);
+ }
+
+ /* Would have been empty before expansion, but no longer. */
+ ok1(was_empty);
+ ok1(!empty_freetable(ntdb));
+ ntdb_close(ntdb);
+ }
+
+ ok1(tap_log_messages == 0);
+ return exit_status();
+}
diff --git a/lib/ntdb/test/run-35-convert.c b/lib/ntdb/test/run-35-convert.c
new file mode 100644
index 0000000000..6a38d425cb
--- /dev/null
+++ b/lib/ntdb/test/run-35-convert.c
@@ -0,0 +1,54 @@
+#include "private.h"
+#include <ccan/failtest/failtest_override.h>
+#include "ntdb-source.h"
+#include "tap-interface.h"
+#include <ccan/failtest/failtest.h>
+#include "logging.h"
+#include "failtest_helper.h"
+
+int main(int argc, char *argv[])
+{
+ unsigned int i, messages = 0;
+ struct ntdb_context *ntdb;
+ int flags[] = { NTDB_DEFAULT, NTDB_NOMMAP,
+ NTDB_CONVERT, NTDB_NOMMAP|NTDB_CONVERT };
+
+ failtest_init(argc, argv);
+ failtest_hook = block_repeat_failures;
+ failtest_exit_check = exit_check_log;
+ plan_tests(sizeof(flags) / sizeof(flags[0]) * 4);
+ for (i = 0; i < sizeof(flags) / sizeof(flags[0]); i++) {
+ ntdb = ntdb_open("run-35-convert.ntdb", flags[i],
+ O_RDWR|O_CREAT|O_TRUNC, 0600, &tap_log_attr);
+ if (!ok1(ntdb))
+ failtest_exit(exit_status());
+
+ ntdb_close(ntdb);
+ /* If we say NTDB_CONVERT, it must be converted */
+ ntdb = ntdb_open("run-35-convert.ntdb",
+ flags[i]|NTDB_CONVERT,
+ O_RDWR, 0600, &tap_log_attr);
+ if (flags[i] & NTDB_CONVERT) {
+ if (!ntdb)
+ failtest_exit(exit_status());
+ ok1(ntdb_get_flags(ntdb) & NTDB_CONVERT);
+ ntdb_close(ntdb);
+ } else {
+ if (!ok1(!ntdb && errno == EIO))
+ failtest_exit(exit_status());
+ ok1(tap_log_messages == ++messages);
+ if (!ok1(log_last && strstr(log_last, "NTDB_CONVERT")))
+ failtest_exit(exit_status());
+ }
+
+ /* If don't say NTDB_CONVERT, it *may* be converted */
+ ntdb = ntdb_open("run-35-convert.ntdb",
+ flags[i] & ~NTDB_CONVERT,
+ O_RDWR, 0600, &tap_log_attr);
+ if (!ntdb)
+ failtest_exit(exit_status());
+ ok1(ntdb_get_flags(ntdb) == flags[i]);
+ ntdb_close(ntdb);
+ }
+ failtest_exit(exit_status());
+}
diff --git a/lib/ntdb/test/run-50-multiple-freelists.c b/lib/ntdb/test/run-50-multiple-freelists.c
new file mode 100644
index 0000000000..962462e5d4
--- /dev/null
+++ b/lib/ntdb/test/run-50-multiple-freelists.c
@@ -0,0 +1,70 @@
+#include "ntdb-source.h"
+#include "tap-interface.h"
+#include "logging.h"
+#include "layout.h"
+
+int main(int argc, char *argv[])
+{
+ ntdb_off_t off;
+ struct ntdb_context *ntdb;
+ struct ntdb_layout *layout;
+ NTDB_DATA key, data;
+ union ntdb_attribute seed;
+
+ /* This seed value previously tickled a layout.c bug. */
+ seed.base.attr = NTDB_ATTRIBUTE_SEED;
+ seed.seed.seed = 0xb1142bc054d035b4ULL;
+ seed.base.next = &tap_log_attr;
+
+ plan_tests(11);
+ key = ntdb_mkdata("Hello", 5);
+ data = ntdb_mkdata("world", 5);
+
+ /* Create a NTDB with three free tables. */
+ layout = new_ntdb_layout();
+ ntdb_layout_add_freetable(layout);
+ ntdb_layout_add_freetable(layout);
+ ntdb_layout_add_freetable(layout);
+ ntdb_layout_add_free(layout, 80, 0);
+ /* Used record prevent coalescing. */
+ ntdb_layout_add_used(layout, key, data, 6);
+ ntdb_layout_add_free(layout, 160, 1);
+ key.dsize--;
+ ntdb_layout_add_used(layout, key, data, 7);
+ ntdb_layout_add_free(layout, 320, 2);
+ key.dsize--;
+ ntdb_layout_add_used(layout, key, data, 8);
+ ntdb_layout_add_free(layout, 40, 0);
+ ntdb = ntdb_layout_get(layout, free, &seed);
+ ok1(ntdb_check(ntdb, NULL, NULL) == 0);
+
+ off = get_free(ntdb, 0, 80 - sizeof(struct ntdb_used_record), 0,
+ NTDB_USED_MAGIC, 0);
+ ok1(off == layout->elem[3].base.off);
+ ok1(ntdb->ftable_off == layout->elem[0].base.off);
+
+ off = get_free(ntdb, 0, 160 - sizeof(struct ntdb_used_record), 0,
+ NTDB_USED_MAGIC, 0);
+ ok1(off == layout->elem[5].base.off);
+ ok1(ntdb->ftable_off == layout->elem[1].base.off);
+
+ off = get_free(ntdb, 0, 320 - sizeof(struct ntdb_used_record), 0,
+ NTDB_USED_MAGIC, 0);
+ ok1(off == layout->elem[7].base.off);
+ ok1(ntdb->ftable_off == layout->elem[2].base.off);
+
+ off = get_free(ntdb, 0, 40 - sizeof(struct ntdb_used_record), 0,
+ NTDB_USED_MAGIC, 0);
+ ok1(off == layout->elem[9].base.off);
+ ok1(ntdb->ftable_off == layout->elem[0].base.off);
+
+ /* Now we fail. */
+ off = get_free(ntdb, 0, 0, 1, NTDB_USED_MAGIC, 0);
+ ok1(off == 0);
+
+ ntdb_close(ntdb);
+ ntdb_layout_free(layout);
+
+ ok1(tap_log_messages == 0);
+ return exit_status();
+}
diff --git a/lib/ntdb/test/run-56-open-during-transaction.c b/lib/ntdb/test/run-56-open-during-transaction.c
new file mode 100644
index 0000000000..f585aa13c8
--- /dev/null
+++ b/lib/ntdb/test/run-56-open-during-transaction.c
@@ -0,0 +1,165 @@
+#include "private.h"
+#include <unistd.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 "ntdb-source.h"
+#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;
+#define TEST_DBNAME "run-56-open-during-transaction.ntdb"
+
+#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 ret;
+
+ /* over-length read serves as length check. */
+ contents = malloc(snapshot_len+1);
+ ret = pread(fd, contents, snapshot_len+1, 0) == snapshot_len
+ && is_same(snapshot, contents, snapshot_len);
+ free(contents);
+ return ret;
+}
+
+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++;
+ return;
+ }
+
+ /* Ask agent to open file. */
+ ret = external_agent_operation(agent, 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 ntdb: %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[] = { NTDB_DEFAULT, NTDB_NOMMAP,
+ NTDB_CONVERT, NTDB_NOMMAP|NTDB_CONVERT };
+ int i;
+ struct ntdb_context *ntdb;
+ NTDB_DATA key, data;
+
+ plan_tests(sizeof(flags)/sizeof(flags[0]) * 5);
+ agent = prepare_external_agent();
+ if (!agent)
+ err(1, "preparing agent");
+
+ unlock_callback = after_unlock;
+ for (i = 0; i < sizeof(flags)/sizeof(flags[0]); i++) {
+ diag("Test with %s and %s\n",
+ (flags[i] & NTDB_CONVERT) ? "CONVERT" : "DEFAULT",
+ (flags[i] & NTDB_NOMMAP) ? "no mmap" : "mmap");
+ unlink(TEST_DBNAME);
+ ntdb = ntdb_open(TEST_DBNAME, flags[i],
+ O_RDWR|O_CREAT|O_TRUNC, 0600, &tap_log_attr);
+ ok1(ntdb);
+
+ opened = true;
+ ok1(ntdb_transaction_start(ntdb) == 0);
+ key = ntdb_mkdata("hi", strlen("hi"));
+ data = ntdb_mkdata("world", strlen("world"));
+
+ ok1(ntdb_store(ntdb, key, data, NTDB_INSERT) == 0);
+ ok1(ntdb_transaction_commit(ntdb) == 0);
+ ok(!errors, "We had %u open errors", errors);
+
+ opened = false;
+ ntdb_close(ntdb);
+ }
+
+ return exit_status();
+}
diff --git a/lib/ntdb/test/run-57-die-during-transaction.c b/lib/ntdb/test/run-57-die-during-transaction.c
new file mode 100644
index 0000000000..98ec9dd63a
--- /dev/null
+++ b/lib/ntdb/test/run-57-die-during-transaction.c
@@ -0,0 +1,294 @@
+#include "private.h"
+#include <unistd.h>
+#include "lock-tracking.h"
+#include "tap-interface.h"
+#include <stdlib.h>
+#include <assert.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
+
+/* There's a malloc inside transaction_setup_recovery, and valgrind complains
+ * when we longjmp and leak it. */
+#define MAX_ALLOCATIONS 10
+static void *allocated[MAX_ALLOCATIONS];
+static unsigned max_alloc = 0;
+
+static void *malloc_noleak(size_t len)
+{
+ unsigned int i;
+
+ for (i = 0; i < MAX_ALLOCATIONS; i++)
+ if (!allocated[i]) {
+ allocated[i] = malloc(len);
+ if (i > max_alloc) {
+ max_alloc = i;
+ diag("max_alloc: %i", max_alloc);
+ }
+ return allocated[i];
+ }
+ diag("Too many allocations!");
+ abort();
+}
+
+static void *realloc_noleak(void *p, size_t size)
+{
+ unsigned int i;
+
+ for (i = 0; i < MAX_ALLOCATIONS; i++) {
+ if (allocated[i] == p) {
+ if (i > max_alloc) {
+ max_alloc = i;
+ diag("max_alloc: %i", max_alloc);
+ }
+ return allocated[i] = realloc(p, size);
+ }
+ }
+ diag("Untracked realloc!");
+ abort();
+}
+
+static void free_noleak(void *p)
+{
+ unsigned int i;
+
+ /* We don't catch asprintf, so don't complain if we miss one. */
+ for (i = 0; i < MAX_ALLOCATIONS; i++) {
+ if (allocated[i] == p) {
+ allocated[i] = NULL;
+ break;
+ }
+ }
+ free(p);
+}
+
+static void free_all(void)
+{
+ unsigned int i;
+
+ for (i = 0; i < MAX_ALLOCATIONS; i++) {
+ free(allocated[i]);
+ allocated[i] = NULL;
+ }
+}
+
+#define malloc malloc_noleak
+#define free free_noleak
+#define realloc realloc_noleak
+
+#include "ntdb-source.h"
+
+#undef malloc
+#undef free
+#undef realloc
+#undef write
+#undef pwrite
+#undef fcntl
+#undef ftruncate
+
+#include <stdbool.h>
+#include <stdarg.h>
+#include <ccan/err/err.h>
+#include <setjmp.h>
+#include "external-agent.h"
+#include "logging.h"
+
+static bool in_transaction;
+static int target, current;
+static jmp_buf jmpbuf;
+#define TEST_DBNAME "run-57-die-during-transaction.ntdb"
+#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 ntdb_context *ntdb = NULL;
+ NTDB_DATA key;
+ enum agent_return ret;
+ int needed_recovery = 0;
+
+ current = target = 0;
+reset:
+ unlink(TEST_DBNAME);
+ ntdb = ntdb_open(TEST_DBNAME, NTDB_NOMMAP,
+ O_CREAT|O_TRUNC|O_RDWR, 0600, &tap_log_attr);
+ if (!ntdb) {
+ diag("Failed opening NTDB: %s", strerror(errno));
+ return false;
+ }
+
+ if (setjmp(jmpbuf) != 0) {
+ /* We're partway through. Simulate our death. */
+ close(ntdb->file->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;
+ ntdb_close(ntdb);
+ suppress_logging = false;
+ suppress_lockcheck = false;
+ target++;
+ current = 0;
+ free_all();
+ goto reset;
+ }
+
+ /* Put key for agent to fetch. */
+ key = ntdb_mkdata(KEY_STRING, strlen(KEY_STRING));
+ if (ntdb_store(ntdb, key, key, NTDB_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)
+ errx(1, "Agent failed to open: %s", agent_return_name(ret));
+
+ ret = external_agent_operation(agent, FETCH, KEY_STRING);
+ if (ret != SUCCESS)
+ errx(1, "Agent failed find key: %s", agent_return_name(ret));
+
+ in_transaction = true;
+ if (ntdb_transaction_start(ntdb) != 0)
+ return false;
+
+ if (ntdb_store(ntdb, key, key, NTDB_INSERT) != 0)
+ return false;
+
+ if (ntdb_transaction_commit(ntdb) != 0)
+ return false;
+
+ in_transaction = false;
+
+ /* We made it! */
+ diag("Completed %u runs", current);
+ ntdb_close(ntdb);
+ ret = external_agent_operation(agent, CLOSE, "");
+ if (ret != SUCCESS) {
+ diag("Step %u close failed = %s", current,
+ agent_return_name(ret));
+ return false;
+ }
+
+ ok1(needed_recovery);
+ 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;
+
+ external_agent_free = free_noleak;
+ agent = prepare_external_agent();
+ if (!agent)
+ err(1, "preparing 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));
+ }
+
+ free_external_agent(agent);
+ return exit_status();
+}
diff --git a/lib/ntdb/test/run-64-bit-tdb.c b/lib/ntdb/test/run-64-bit-tdb.c
new file mode 100644
index 0000000000..6a146cb1cf
--- /dev/null
+++ b/lib/ntdb/test/run-64-bit-tdb.c
@@ -0,0 +1,72 @@
+#include "ntdb-source.h"
+#include "tap-interface.h"
+#include "logging.h"
+
+int main(int argc, char *argv[])
+{
+ unsigned int i;
+ struct ntdb_context *ntdb;
+ int flags[] = { NTDB_DEFAULT, NTDB_NOMMAP,
+ NTDB_CONVERT,
+ NTDB_NOMMAP|NTDB_CONVERT };
+
+ if (sizeof(off_t) <= 4) {
+ plan_tests(1);
+ pass("No 64 bit off_t");
+ return exit_status();
+ }
+
+ plan_tests(sizeof(flags) / sizeof(flags[0]) * 14);
+ for (i = 0; i < sizeof(flags) / sizeof(flags[0]); i++) {
+ off_t old_size;
+ NTDB_DATA k, d;
+ struct hash_info h;
+ struct ntdb_used_record rec;
+ ntdb_off_t off;
+
+ ntdb = ntdb_open("run-64-bit-ntdb.ntdb", flags[i],
+ O_RDWR|O_CREAT|O_TRUNC, 0600, &tap_log_attr);
+ ok1(ntdb);
+ if (!ntdb)
+ continue;
+
+ old_size = ntdb->file->map_size;
+
+ /* This makes a sparse file */
+ ok1(ftruncate(ntdb->file->fd, 0xFFFFFFF0) == 0);
+ ok1(add_free_record(ntdb, old_size, 0xFFFFFFF0 - old_size,
+ NTDB_LOCK_WAIT, false) == NTDB_SUCCESS);
+
+ /* Now add a little record past the 4G barrier. */
+ ok1(ntdb_expand_file(ntdb, 100) == NTDB_SUCCESS);
+ ok1(add_free_record(ntdb, 0xFFFFFFF0, 100, NTDB_LOCK_WAIT, false)
+ == NTDB_SUCCESS);
+
+ ok1(ntdb_check(ntdb, NULL, NULL) == NTDB_SUCCESS);
+
+ /* Test allocation path. */
+ k = ntdb_mkdata("key", 4);
+ d = ntdb_mkdata("data", 5);
+ ok1(ntdb_store(ntdb, k, d, NTDB_INSERT) == 0);
+ ok1(ntdb_check(ntdb, NULL, NULL) == NTDB_SUCCESS);
+
+ /* Make sure it put it at end as we expected. */
+ off = find_and_lock(ntdb, k, F_RDLCK, &h, &rec, NULL);
+ ok1(off >= 0xFFFFFFF0);
+ ntdb_unlock_hashes(ntdb, h.hlock_start, h.hlock_range, F_RDLCK);
+
+ ok1(ntdb_fetch(ntdb, k, &d) == 0);
+ ok1(d.dsize == 5);
+ ok1(strcmp((char *)d.dptr, "data") == 0);
+ free(d.dptr);
+
+ ok1(ntdb_delete(ntdb, k) == 0);
+ ok1(ntdb_check(ntdb, NULL, NULL) == NTDB_SUCCESS);
+
+ ntdb_close(ntdb);
+ }
+
+ /* We might get messages about mmap failing, so don't test
+ * tap_log_messages */
+ return exit_status();
+}
diff --git a/lib/ntdb/test/run-90-get-set-attributes.c b/lib/ntdb/test/run-90-get-set-attributes.c
new file mode 100644
index 0000000000..fc265b0729
--- /dev/null
+++ b/lib/ntdb/test/run-90-get-set-attributes.c
@@ -0,0 +1,159 @@
+#include "ntdb-source.h"
+#include "tap-interface.h"
+#include "logging.h"
+
+static int mylock(int fd, int rw, off_t off, off_t len, bool waitflag,
+ void *unused)
+{
+ return 0;
+}
+
+static int myunlock(int fd, int rw, off_t off, off_t len, void *unused)
+{
+ return 0;
+}
+
+static uint64_t hash_fn(const void *key, size_t len, uint64_t seed,
+ void *priv)
+{
+ return 0;
+}
+
+int main(int argc, char *argv[])
+{
+ unsigned int i;
+ struct ntdb_context *ntdb;
+ int flags[] = { NTDB_DEFAULT, NTDB_NOMMAP,
+ NTDB_CONVERT, NTDB_NOMMAP|NTDB_CONVERT };
+ union ntdb_attribute seed_attr;
+ union ntdb_attribute hash_attr;
+ union ntdb_attribute lock_attr;
+
+ seed_attr.base.attr = NTDB_ATTRIBUTE_SEED;
+ seed_attr.base.next = &hash_attr;
+ seed_attr.seed.seed = 100;
+
+ hash_attr.base.attr = NTDB_ATTRIBUTE_HASH;
+ hash_attr.base.next = &lock_attr;
+ hash_attr.hash.fn = hash_fn;
+ hash_attr.hash.data = &hash_attr;
+
+ lock_attr.base.attr = NTDB_ATTRIBUTE_FLOCK;
+ lock_attr.base.next = &tap_log_attr;
+ lock_attr.flock.lock = mylock;
+ lock_attr.flock.unlock = myunlock;
+ lock_attr.flock.data = &lock_attr;
+
+ plan_tests(sizeof(flags) / sizeof(flags[0]) * 50);
+
+ for (i = 0; i < sizeof(flags) / sizeof(flags[0]); i++) {
+ union ntdb_attribute attr;
+
+ /* First open with no attributes. */
+ ntdb = ntdb_open("run-90-get-set-attributes.ntdb", flags[i],
+ O_RDWR|O_CREAT|O_TRUNC, 0600, NULL);
+ ok1(ntdb);
+
+ /* Get log on no attributes will fail */
+ attr.base.attr = NTDB_ATTRIBUTE_LOG;
+ ok1(ntdb_get_attribute(ntdb, &attr) == NTDB_ERR_NOEXIST);
+ /* These always work. */
+ attr.base.attr = NTDB_ATTRIBUTE_HASH;
+ ok1(ntdb_get_attribute(ntdb, &attr) == 0);
+ ok1(attr.base.attr == NTDB_ATTRIBUTE_HASH);
+ ok1(attr.hash.fn == ntdb_jenkins_hash);
+ attr.base.attr = NTDB_ATTRIBUTE_FLOCK;
+ ok1(ntdb_get_attribute(ntdb, &attr) == 0);
+ ok1(attr.base.attr == NTDB_ATTRIBUTE_FLOCK);
+ ok1(attr.flock.lock == ntdb_fcntl_lock);
+ ok1(attr.flock.unlock == ntdb_fcntl_unlock);
+ attr.base.attr = NTDB_ATTRIBUTE_SEED;
+ ok1(ntdb_get_attribute(ntdb, &attr) == 0);
+ ok1(attr.base.attr == NTDB_ATTRIBUTE_SEED);
+ /* This is possible, just astronomically unlikely. */
+ ok1(attr.seed.seed != 0);
+
+ /* Unset attributes. */
+ ntdb_unset_attribute(ntdb, NTDB_ATTRIBUTE_LOG);
+ ntdb_unset_attribute(ntdb, NTDB_ATTRIBUTE_FLOCK);
+
+ /* Set them. */
+ ok1(ntdb_set_attribute(ntdb, &tap_log_attr) == 0);
+ ok1(ntdb_set_attribute(ntdb, &lock_attr) == 0);
+ /* These should fail. */
+ ok1(ntdb_set_attribute(ntdb, &seed_attr) == NTDB_ERR_EINVAL);
+ ok1(tap_log_messages == 1);
+ ok1(ntdb_set_attribute(ntdb, &hash_attr) == NTDB_ERR_EINVAL);
+ ok1(tap_log_messages == 2);
+ tap_log_messages = 0;
+
+ /* Getting them should work as expected. */
+ attr.base.attr = NTDB_ATTRIBUTE_LOG;
+ ok1(ntdb_get_attribute(ntdb, &attr) == 0);
+ ok1(attr.base.attr == NTDB_ATTRIBUTE_LOG);
+ ok1(attr.log.fn == tap_log_attr.log.fn);
+ ok1(attr.log.data == tap_log_attr.log.data);
+
+ attr.base.attr = NTDB_ATTRIBUTE_FLOCK;
+ ok1(ntdb_get_attribute(ntdb, &attr) == 0);
+ ok1(attr.base.attr == NTDB_ATTRIBUTE_FLOCK);
+ ok1(attr.flock.lock == mylock);
+ ok1(attr.flock.unlock == myunlock);
+ ok1(attr.flock.data == &lock_attr);
+
+ /* Unset them again. */
+ ntdb_unset_attribute(ntdb, NTDB_ATTRIBUTE_FLOCK);
+ ok1(tap_log_messages == 0);
+ ntdb_unset_attribute(ntdb, NTDB_ATTRIBUTE_LOG);
+ ok1(tap_log_messages == 0);
+
+ ntdb_close(ntdb);
+ ok1(tap_log_messages == 0);
+
+ /* Now open with all attributes. */
+ ntdb = ntdb_open("run-90-get-set-attributes.ntdb", flags[i],
+ O_RDWR|O_CREAT|O_TRUNC, 0600,
+ &seed_attr);
+
+ ok1(ntdb);
+
+ /* Get will succeed */
+ attr.base.attr = NTDB_ATTRIBUTE_LOG;
+ ok1(ntdb_get_attribute(ntdb, &attr) == 0);
+ ok1(attr.base.attr == NTDB_ATTRIBUTE_LOG);
+ ok1(attr.log.fn == tap_log_attr.log.fn);
+ ok1(attr.log.data == tap_log_attr.log.data);
+
+ attr.base.attr = NTDB_ATTRIBUTE_HASH;
+ ok1(ntdb_get_attribute(ntdb, &attr) == 0);
+ ok1(attr.base.attr == NTDB_ATTRIBUTE_HASH);
+ ok1(attr.hash.fn == hash_fn);
+ ok1(attr.hash.data == &hash_attr);
+
+ attr.base.attr = NTDB_ATTRIBUTE_FLOCK;
+ ok1(ntdb_get_attribute(ntdb, &attr) == 0);
+ ok1(attr.base.attr == NTDB_ATTRIBUTE_FLOCK);
+ ok1(attr.flock.lock == mylock);
+ ok1(attr.flock.unlock == myunlock);
+ ok1(attr.flock.data == &lock_attr);
+
+ attr.base.attr = NTDB_ATTRIBUTE_SEED;
+ ok1(ntdb_get_attribute(ntdb, &attr) == 0);
+ ok1(attr.base.attr == NTDB_ATTRIBUTE_SEED);
+ ok1(attr.seed.seed == seed_attr.seed.seed);
+
+ /* Unset attributes. */
+ ntdb_unset_attribute(ntdb, NTDB_ATTRIBUTE_HASH);
+ ok1(tap_log_messages == 1);
+ ntdb_unset_attribute(ntdb, NTDB_ATTRIBUTE_SEED);
+ ok1(tap_log_messages == 2);
+ ntdb_unset_attribute(ntdb, NTDB_ATTRIBUTE_FLOCK);
+ ntdb_unset_attribute(ntdb, NTDB_ATTRIBUTE_LOG);
+ ok1(tap_log_messages == 2);
+ tap_log_messages = 0;
+
+ ntdb_close(ntdb);
+
+ }
+ return exit_status();
+}
diff --git a/lib/ntdb/test/run-capabilities.c b/lib/ntdb/test/run-capabilities.c
new file mode 100644
index 0000000000..c2c6aa15db
--- /dev/null
+++ b/lib/ntdb/test/run-capabilities.c
@@ -0,0 +1,271 @@
+#include <ccan/failtest/failtest_override.h>
+#include "ntdb-source.h"
+#include "tap-interface.h"
+#include "logging.h"
+#include "layout.h"
+#include "failtest_helper.h"
+#include <stdarg.h>
+
+static size_t len_of(bool breaks_check, bool breaks_write, bool breaks_open)
+{
+ size_t len = 0;
+ if (breaks_check)
+ len += 8;
+ if (breaks_write)
+ len += 16;
+ if (breaks_open)
+ len += 32;
+ return len;
+}
+
+/* Creates a NTDB with various capabilities. */
+static void create_ntdb(const char *name,
+ unsigned int cap,
+ bool breaks_check,
+ bool breaks_write,
+ bool breaks_open, ...)
+{
+ NTDB_DATA key, data;
+ va_list ap;
+ struct ntdb_layout *layout;
+ struct ntdb_context *ntdb;
+ int fd;
+
+ key = ntdb_mkdata("Hello", 5);
+ data = ntdb_mkdata("world", 5);
+
+ /* Create a NTDB with some data, and some capabilities */
+ layout = new_ntdb_layout();
+ ntdb_layout_add_freetable(layout);
+ ntdb_layout_add_used(layout, key, data, 6);
+ ntdb_layout_add_free(layout, 80, 0);
+ ntdb_layout_add_capability(layout, cap,
+ breaks_write, breaks_check, breaks_open,
+ len_of(breaks_check, breaks_write, breaks_open));
+
+ va_start(ap, breaks_open);
+ while ((cap = va_arg(ap, int)) != 0) {
+ breaks_check = va_arg(ap, int);
+ breaks_write = va_arg(ap, int);
+ breaks_open = va_arg(ap, int);
+
+ key.dsize--;
+ ntdb_layout_add_used(layout, key, data, 11 - key.dsize);
+ ntdb_layout_add_free(layout, 80, 0);
+ ntdb_layout_add_capability(layout, cap,
+ breaks_write, breaks_check,
+ breaks_open,
+ len_of(breaks_check, breaks_write,
+ breaks_open));
+ }
+ va_end(ap);
+
+ /* We open-code this, because we need to use the failtest write. */
+ ntdb = ntdb_layout_get(layout, failtest_free, &tap_log_attr);
+
+ fd = open(name, O_RDWR|O_TRUNC|O_CREAT, 0600);
+ if (fd < 0)
+ err(1, "opening %s for writing", name);
+ if (write(fd, ntdb->file->map_ptr, ntdb->file->map_size)
+ != ntdb->file->map_size)
+ err(1, "writing %s", name);
+ close(fd);
+ ntdb_close(ntdb);
+ ntdb_layout_free(layout);
+}
+
+/* Note all the "goto out" early exits: they're to shorten failtest time. */
+int main(int argc, char *argv[])
+{
+ struct ntdb_context *ntdb;
+ char *summary;
+
+ failtest_init(argc, argv);
+ failtest_hook = block_repeat_failures;
+ failtest_exit_check = exit_check_log;
+ plan_tests(60);
+
+ failtest_suppress = true;
+ /* Capability says you can ignore it? */
+ create_ntdb("run-capabilities.ntdb", 1, false, false, false, 0);
+
+ failtest_suppress = false;
+ ntdb = ntdb_open("run-capabilities.ntdb", NTDB_DEFAULT, O_RDWR, 0,
+ &tap_log_attr);
+ failtest_suppress = true;
+ if (!ok1(ntdb))
+ goto out;
+ ok1(tap_log_messages == 0);
+ ok1(ntdb_check(ntdb, NULL, NULL) == NTDB_SUCCESS);
+ ok1(tap_log_messages == 0);
+ ntdb_close(ntdb);
+
+ /* Two capabilitues say you can ignore them? */
+ create_ntdb("run-capabilities.ntdb",
+ 1, false, false, false,
+ 2, false, false, false, 0);
+
+ failtest_suppress = false;
+ ntdb = ntdb_open("run-capabilities.ntdb", NTDB_DEFAULT, O_RDWR, 0,
+ &tap_log_attr);
+ failtest_suppress = true;
+ if (!ok1(ntdb))
+ goto out;
+ ok1(tap_log_messages == 0);
+ ok1(ntdb_check(ntdb, NULL, NULL) == NTDB_SUCCESS);
+ ok1(tap_log_messages == 0);
+ ok1(ntdb_summary(ntdb, 0, &summary) == NTDB_SUCCESS);
+ ok1(strstr(summary, "Capability 1\n"));
+ free(summary);
+ ntdb_close(ntdb);
+
+ /* Capability says you can't check. */
+ create_ntdb("run-capabilities.ntdb",
+ 1, false, false, false,
+ 2, true, false, false, 0);
+
+ failtest_suppress = false;
+ ntdb = ntdb_open("run-capabilities.ntdb", NTDB_DEFAULT, O_RDWR, 0,
+ &tap_log_attr);
+ failtest_suppress = true;
+ if (!ok1(ntdb))
+ goto out;
+ ok1(tap_log_messages == 0);
+ ok1(ntdb_get_flags(ntdb) & NTDB_CANT_CHECK);
+ ok1(ntdb_check(ntdb, NULL, NULL) == NTDB_SUCCESS);
+ /* We expect a warning! */
+ ok1(tap_log_messages == 1);
+ ok1(strstr(log_last, "capabilit"));
+ ok1(ntdb_summary(ntdb, 0, &summary) == NTDB_SUCCESS);
+ ok1(strstr(summary, "Capability 1\n"));
+ ok1(strstr(summary, "Capability 2 (uncheckable)\n"));
+ free(summary);
+ ntdb_close(ntdb);
+
+ /* Capability says you can't write. */
+ create_ntdb("run-capabilities.ntdb",
+ 1, false, false, false,
+ 2, false, true, false, 0);
+
+ failtest_suppress = false;
+ ntdb = ntdb_open("run-capabilities.ntdb", NTDB_DEFAULT, O_RDWR, 0,
+ &tap_log_attr);
+ failtest_suppress = true;
+ /* We expect a message. */
+ ok1(!ntdb);
+ if (!ok1(tap_log_messages == 2))
+ goto out;
+ if (!ok1(strstr(log_last, "unknown")))
+ goto out;
+ ok1(strstr(log_last, "write"));
+
+ /* We can open it read-only though! */
+ failtest_suppress = false;
+ ntdb = ntdb_open("run-capabilities.ntdb", NTDB_DEFAULT, O_RDONLY, 0,
+ &tap_log_attr);
+ failtest_suppress = true;
+ if (!ok1(ntdb))
+ goto out;
+ ok1(tap_log_messages == 2);
+ ok1(ntdb_check(ntdb, NULL, NULL) == NTDB_SUCCESS);
+ ok1(tap_log_messages == 2);
+ ok1(ntdb_summary(ntdb, 0, &summary) == NTDB_SUCCESS);
+ ok1(strstr(summary, "Capability 1\n"));
+ ok1(strstr(summary, "Capability 2 (read-only)\n"));
+ free(summary);
+ ntdb_close(ntdb);
+
+ /* Capability says you can't open. */
+ create_ntdb("run-capabilities.ntdb",
+ 1, false, false, false,
+ 2, false, false, true, 0);
+
+ failtest_suppress = false;
+ ntdb = ntdb_open("run-capabilities.ntdb", NTDB_DEFAULT, O_RDWR, 0,
+ &tap_log_attr);
+ failtest_suppress = true;
+ /* We expect a message. */
+ ok1(!ntdb);
+ if (!ok1(tap_log_messages == 3))
+ goto out;
+ if (!ok1(strstr(log_last, "unknown")))
+ goto out;
+
+ /* Combine capabilities correctly. */
+ create_ntdb("run-capabilities.ntdb",
+ 1, false, false, false,
+ 2, true, false, false,
+ 3, false, true, false, 0);
+
+ failtest_suppress = false;
+ ntdb = ntdb_open("run-capabilities.ntdb", NTDB_DEFAULT, O_RDWR, 0,
+ &tap_log_attr);
+ failtest_suppress = true;
+ /* We expect a message. */
+ ok1(!ntdb);
+ if (!ok1(tap_log_messages == 4))
+ goto out;
+ if (!ok1(strstr(log_last, "unknown")))
+ goto out;
+ ok1(strstr(log_last, "write"));
+
+ /* We can open it read-only though! */
+ failtest_suppress = false;
+ ntdb = ntdb_open("run-capabilities.ntdb", NTDB_DEFAULT, O_RDONLY, 0,
+ &tap_log_attr);
+ failtest_suppress = true;
+ if (!ok1(ntdb))
+ goto out;
+ ok1(tap_log_messages == 4);
+ ok1(ntdb_get_flags(ntdb) & NTDB_CANT_CHECK);
+ ok1(ntdb_check(ntdb, NULL, NULL) == NTDB_SUCCESS);
+ /* We expect a warning! */
+ ok1(tap_log_messages == 5);
+ ok1(strstr(log_last, "unknown"));
+ ok1(ntdb_summary(ntdb, 0, &summary) == NTDB_SUCCESS);
+ ok1(strstr(summary, "Capability 1\n"));
+ ok1(strstr(summary, "Capability 2 (uncheckable)\n"));
+ ok1(strstr(summary, "Capability 3 (read-only)\n"));
+ free(summary);
+ ntdb_close(ntdb);
+
+ /* Two capability flags in one. */
+ create_ntdb("run-capabilities.ntdb",
+ 1, false, false, false,
+ 2, true, true, false,
+ 0);
+
+ failtest_suppress = false;
+ ntdb = ntdb_open("run-capabilities.ntdb", NTDB_DEFAULT, O_RDWR, 0,
+ &tap_log_attr);
+ failtest_suppress = true;
+ /* We expect a message. */
+ ok1(!ntdb);
+ if (!ok1(tap_log_messages == 6))
+ goto out;
+ if (!ok1(strstr(log_last, "unknown")))
+ goto out;
+ ok1(strstr(log_last, "write"));
+
+ /* We can open it read-only though! */
+ failtest_suppress = false;
+ ntdb = ntdb_open("run-capabilities.ntdb", NTDB_DEFAULT, O_RDONLY, 0,
+ &tap_log_attr);
+ failtest_suppress = true;
+ if (!ok1(ntdb))
+ goto out;
+ ok1(tap_log_messages == 6);
+ ok1(ntdb_get_flags(ntdb) & NTDB_CANT_CHECK);
+ ok1(ntdb_check(ntdb, NULL, NULL) == NTDB_SUCCESS);
+ /* We expect a warning! */
+ ok1(tap_log_messages == 7);
+ ok1(strstr(log_last, "unknown"));
+ ok1(ntdb_summary(ntdb, 0, &summary) == NTDB_SUCCESS);
+ ok1(strstr(summary, "Capability 1\n"));
+ ok1(strstr(summary, "Capability 2 (uncheckable,read-only)\n"));
+ free(summary);
+ ntdb_close(ntdb);
+
+out:
+ failtest_exit(exit_status());
+}
diff --git a/lib/ntdb/test/run-expand-in-transaction.c b/lib/ntdb/test/run-expand-in-transaction.c
new file mode 100644
index 0000000000..dadbec7922
--- /dev/null
+++ b/lib/ntdb/test/run-expand-in-transaction.c
@@ -0,0 +1,36 @@
+#include "ntdb-source.h"
+#include "tap-interface.h"
+#include "logging.h"
+
+int main(int argc, char *argv[])
+{
+ unsigned int i;
+ struct ntdb_context *ntdb;
+ int flags[] = { NTDB_DEFAULT, NTDB_NOMMAP,
+ NTDB_CONVERT, NTDB_NOMMAP|NTDB_CONVERT };
+ NTDB_DATA key = ntdb_mkdata("key", 3);
+ NTDB_DATA data = ntdb_mkdata("data", 4);
+
+ plan_tests(sizeof(flags) / sizeof(flags[0]) * 7 + 1);
+
+ for (i = 0; i < sizeof(flags) / sizeof(flags[0]); i++) {
+ size_t size;
+ ntdb = ntdb_open("run-expand-in-transaction.ntdb", flags[i],
+ O_RDWR|O_CREAT|O_TRUNC, 0600, &tap_log_attr);
+ ok1(ntdb);
+ if (!ntdb)
+ continue;
+
+ size = ntdb->file->map_size;
+ ok1(ntdb_transaction_start(ntdb) == 0);
+ ok1(ntdb_store(ntdb, key, data, NTDB_INSERT) == 0);
+ ok1(ntdb->file->map_size > size);
+ ok1(ntdb_transaction_commit(ntdb) == 0);
+ ok1(ntdb->file->map_size > size);
+ ok1(ntdb_check(ntdb, NULL, NULL) == 0);
+ ntdb_close(ntdb);
+ }
+
+ ok1(tap_log_messages == 0);
+ return exit_status();
+}
diff --git a/lib/ntdb/test/run-features.c b/lib/ntdb/test/run-features.c
new file mode 100644
index 0000000000..0d6b3bce76
--- /dev/null
+++ b/lib/ntdb/test/run-features.c
@@ -0,0 +1,62 @@
+#include "ntdb-source.h"
+#include "tap-interface.h"
+#include "logging.h"
+
+int main(int argc, char *argv[])
+{
+ unsigned int i, j;
+ struct ntdb_context *ntdb;
+ int flags[] = { NTDB_DEFAULT, NTDB_NOMMAP,
+ NTDB_CONVERT, NTDB_NOMMAP|NTDB_CONVERT };
+ NTDB_DATA key = { (unsigned char *)&j, sizeof(j) };
+ NTDB_DATA data = { (unsigned char *)&j, sizeof(j) };
+
+ plan_tests(sizeof(flags) / sizeof(flags[0]) * 8 + 1);
+ for (i = 0; i < sizeof(flags) / sizeof(flags[0]); i++) {
+ uint64_t features;
+ ntdb = ntdb_open("run-features.ntdb", flags[i],
+ O_RDWR|O_CREAT|O_TRUNC, 0600, &tap_log_attr);
+ ok1(ntdb);
+ if (!ntdb)
+ continue;
+
+ /* Put some stuff in there. */
+ for (j = 0; j < 100; j++) {
+ if (ntdb_store(ntdb, key, data, NTDB_REPLACE) != 0)
+ fail("Storing in ntdb");
+ }
+
+ /* Mess with features fields in hdr. */
+ features = (~NTDB_FEATURE_MASK ^ 1);
+ ok1(ntdb_write_convert(ntdb, offsetof(struct ntdb_header,
+ features_used),
+ &features, sizeof(features)) == 0);
+ ok1(ntdb_write_convert(ntdb, offsetof(struct ntdb_header,
+ features_offered),
+ &features, sizeof(features)) == 0);
+ ntdb_close(ntdb);
+
+ ntdb = ntdb_open("run-features.ntdb", flags[i], O_RDWR, 0,
+ &tap_log_attr);
+ ok1(ntdb);
+ if (!ntdb)
+ continue;
+
+ /* Should not have changed features offered. */
+ ok1(ntdb_read_convert(ntdb, offsetof(struct ntdb_header,
+ features_offered),
+ &features, sizeof(features)) == 0);
+ ok1(features == (~NTDB_FEATURE_MASK ^ 1));
+
+ /* Should have cleared unknown bits in features_used. */
+ ok1(ntdb_read_convert(ntdb, offsetof(struct ntdb_header,
+ features_used),
+ &features, sizeof(features)) == 0);
+ ok1(features == (1 & NTDB_FEATURE_MASK));
+
+ ntdb_close(ntdb);
+ }
+
+ ok1(tap_log_messages == 0);
+ return exit_status();
+}
diff --git a/lib/ntdb/test/run-lockall.c b/lib/ntdb/test/run-lockall.c
new file mode 100644
index 0000000000..964164e20b
--- /dev/null
+++ b/lib/ntdb/test/run-lockall.c
@@ -0,0 +1,70 @@
+#include "private.h"
+#include <unistd.h>
+#include "lock-tracking.h"
+
+#define fcntl fcntl_with_lockcheck
+#include "ntdb-source.h"
+
+#include "tap-interface.h"
+#include <stdlib.h>
+#include <stdbool.h>
+#include <stdarg.h>
+#include "external-agent.h"
+#include "logging.h"
+
+#define TEST_DBNAME "run-lockall.ntdb"
+
+#undef fcntl
+
+int main(int argc, char *argv[])
+{
+ struct agent *agent;
+ int flags[] = { NTDB_DEFAULT, NTDB_NOMMAP,
+ NTDB_CONVERT, NTDB_NOMMAP|NTDB_CONVERT };
+ int i;
+
+ plan_tests(13 * sizeof(flags)/sizeof(flags[0]) + 1);
+ agent = prepare_external_agent();
+ if (!agent)
+ err(1, "preparing agent");
+
+ for (i = 0; i < sizeof(flags)/sizeof(flags[0]); i++) {
+ enum agent_return ret;
+ struct ntdb_context *ntdb;
+
+ ntdb = ntdb_open(TEST_DBNAME, flags[i],
+ O_RDWR|O_CREAT|O_TRUNC, 0600, &tap_log_attr);
+ ok1(ntdb);
+
+ ret = external_agent_operation(agent, OPEN, TEST_DBNAME);
+ ok1(ret == SUCCESS);
+
+ ok1(ntdb_lockall(ntdb) == NTDB_SUCCESS);
+ ok1(external_agent_operation(agent, STORE, "key")
+ == WOULD_HAVE_BLOCKED);
+ ok1(external_agent_operation(agent, FETCH, "key")
+ == WOULD_HAVE_BLOCKED);
+ /* Test nesting. */
+ ok1(ntdb_lockall(ntdb) == NTDB_SUCCESS);
+ ntdb_unlockall(ntdb);
+ ntdb_unlockall(ntdb);
+
+ ok1(external_agent_operation(agent, STORE, "key") == SUCCESS);
+
+ ok1(ntdb_lockall_read(ntdb) == NTDB_SUCCESS);
+ ok1(external_agent_operation(agent, STORE, "key")
+ == WOULD_HAVE_BLOCKED);
+ ok1(external_agent_operation(agent, FETCH, "key") == SUCCESS);
+ ok1(ntdb_lockall_read(ntdb) == NTDB_SUCCESS);
+ ntdb_unlockall_read(ntdb);
+ ntdb_unlockall_read(ntdb);
+
+ ok1(external_agent_operation(agent, STORE, "key") == SUCCESS);
+ ok1(external_agent_operation(agent, CLOSE, NULL) == SUCCESS);
+ ntdb_close(ntdb);
+ }
+
+ free_external_agent(agent);
+ ok1(tap_log_messages == 0);
+ return exit_status();
+}
diff --git a/lib/ntdb/test/run-remap-in-read_traverse.c b/lib/ntdb/test/run-remap-in-read_traverse.c
new file mode 100644
index 0000000000..2d817c2d73
--- /dev/null
+++ b/lib/ntdb/test/run-remap-in-read_traverse.c
@@ -0,0 +1,57 @@
+#include "ntdb-source.h"
+/* We had a bug where we marked the ntdb read-only for a ntdb_traverse_read.
+ * If we then expanded the ntdb, we would remap read-only, and later SEGV. */
+#include "tap-interface.h"
+#include "external-agent.h"
+#include "logging.h"
+
+static bool file_larger(int fd, ntdb_len_t size)
+{
+ struct stat st;
+
+ fstat(fd, &st);
+ return st.st_size != size;
+}
+
+static unsigned add_records_to_grow(struct agent *agent, int fd, ntdb_len_t size)
+{
+ unsigned int i;
+
+ for (i = 0; !file_larger(fd, size); i++) {
+ char data[20];
+ sprintf(data, "%i", i);
+ if (external_agent_operation(agent, STORE, data) != SUCCESS)
+ return 0;
+ }
+ diag("Added %u records to grow file", i);
+ return i;
+}
+
+int main(int argc, char *argv[])
+{
+ unsigned int i;
+ struct agent *agent;
+ struct ntdb_context *ntdb;
+ NTDB_DATA d = ntdb_mkdata("hello", 5);
+ const char filename[] = "run-remap-in-read_traverse.ntdb";
+
+ plan_tests(4);
+
+ agent = prepare_external_agent();
+
+ ntdb = ntdb_open(filename, NTDB_DEFAULT,
+ O_RDWR|O_CREAT|O_TRUNC, 0600, &tap_log_attr);
+
+ ok1(external_agent_operation(agent, OPEN, filename) == SUCCESS);
+ i = add_records_to_grow(agent, ntdb->file->fd, ntdb->file->map_size);
+
+ /* Do a traverse. */
+ ok1(ntdb_traverse(ntdb, NULL, NULL) == i);
+
+ /* Now store something! */
+ ok1(ntdb_store(ntdb, d, d, NTDB_INSERT) == 0);
+ ok1(tap_log_messages == 0);
+ ntdb_close(ntdb);
+ free_external_agent(agent);
+ return exit_status();
+}
diff --git a/lib/ntdb/test/run-seed.c b/lib/ntdb/test/run-seed.c
new file mode 100644
index 0000000000..2514f728ac
--- /dev/null
+++ b/lib/ntdb/test/run-seed.c
@@ -0,0 +1,61 @@
+#include "ntdb-source.h"
+#include "tap-interface.h"
+#include "logging.h"
+
+static int log_count = 0;
+
+/* Normally we get a log when setting random seed. */
+static void my_log_fn(struct ntdb_context *ntdb,
+ enum ntdb_log_level level,
+ enum NTDB_ERROR ecode,
+ const char *message, void *priv)
+{
+ log_count++;
+}
+
+static union ntdb_attribute log_attr = {
+ .log = { .base = { .attr = NTDB_ATTRIBUTE_LOG },
+ .fn = my_log_fn }
+};
+
+int main(int argc, char *argv[])
+{
+ unsigned int i;
+ struct ntdb_context *ntdb;
+ union ntdb_attribute attr;
+ int flags[] = { NTDB_INTERNAL, NTDB_DEFAULT, NTDB_NOMMAP,
+ NTDB_INTERNAL|NTDB_CONVERT, NTDB_CONVERT,
+ NTDB_NOMMAP|NTDB_CONVERT };
+
+ attr.seed.base.attr = NTDB_ATTRIBUTE_SEED;
+ attr.seed.base.next = &log_attr;
+ attr.seed.seed = 42;
+
+ plan_tests(sizeof(flags) / sizeof(flags[0]) * 4 + 4 * 3);
+ for (i = 0; i < sizeof(flags) / sizeof(flags[0]); i++) {
+ struct ntdb_header hdr;
+ int fd;
+ ntdb = ntdb_open("run-seed.ntdb", flags[i],
+ O_RDWR|O_CREAT|O_TRUNC, 0600, &attr);
+ ok1(ntdb);
+ if (!ntdb)
+ continue;
+ ok1(ntdb_check(ntdb, NULL, NULL) == 0);
+ ok1(ntdb->hash_seed == 42);
+ ok1(log_count == 0);
+ ntdb_close(ntdb);
+
+ if (flags[i] & NTDB_INTERNAL)
+ continue;
+
+ fd = open("run-seed.ntdb", O_RDONLY);
+ ok1(fd >= 0);
+ ok1(read(fd, &hdr, sizeof(hdr)) == sizeof(hdr));
+ if (flags[i] & NTDB_CONVERT)
+ ok1(bswap_64(hdr.hash_seed) == 42);
+ else
+ ok1(hdr.hash_seed == 42);
+ close(fd);
+ }
+ return exit_status();
+}
diff --git a/lib/ntdb/test/run-tdb_errorstr.c b/lib/ntdb/test/run-tdb_errorstr.c
new file mode 100644
index 0000000000..5b023140a7
--- /dev/null
+++ b/lib/ntdb/test/run-tdb_errorstr.c
@@ -0,0 +1,52 @@
+#include "ntdb-source.h"
+#include "tap-interface.h"
+
+int main(int argc, char *argv[])
+{
+ enum NTDB_ERROR e;
+ plan_tests(NTDB_ERR_RDONLY*-1 + 2);
+
+ for (e = NTDB_SUCCESS; e >= NTDB_ERR_RDONLY; e--) {
+ switch (e) {
+ case NTDB_SUCCESS:
+ ok1(!strcmp(ntdb_errorstr(e),
+ "Success"));
+ break;
+ case NTDB_ERR_IO:
+ ok1(!strcmp(ntdb_errorstr(e),
+ "IO Error"));
+ break;
+ case NTDB_ERR_LOCK:
+ ok1(!strcmp(ntdb_errorstr(e),
+ "Locking error"));
+ break;
+ case NTDB_ERR_OOM:
+ ok1(!strcmp(ntdb_errorstr(e),
+ "Out of memory"));
+ break;
+ case NTDB_ERR_EXISTS:
+ ok1(!strcmp(ntdb_errorstr(e),
+ "Record exists"));
+ break;
+ case NTDB_ERR_EINVAL:
+ ok1(!strcmp(ntdb_errorstr(e),
+ "Invalid parameter"));
+ break;
+ case NTDB_ERR_NOEXIST:
+ ok1(!strcmp(ntdb_errorstr(e),
+ "Record does not exist"));
+ break;
+ case NTDB_ERR_RDONLY:
+ ok1(!strcmp(ntdb_errorstr(e),
+ "write not permitted"));
+ break;
+ case NTDB_ERR_CORRUPT:
+ ok1(!strcmp(ntdb_errorstr(e),
+ "Corrupt database"));
+ break;
+ }
+ }
+ ok1(!strcmp(ntdb_errorstr(e), "Invalid error code"));
+
+ return exit_status();
+}
diff --git a/lib/ntdb/test/run-tdb_foreach.c b/lib/ntdb/test/run-tdb_foreach.c
new file mode 100644
index 0000000000..f1a2d00919
--- /dev/null
+++ b/lib/ntdb/test/run-tdb_foreach.c
@@ -0,0 +1,86 @@
+#include "ntdb-source.h"
+#include "tap-interface.h"
+#include "logging.h"
+
+static int drop_count(struct ntdb_context *ntdb, unsigned int *count)
+{
+ if (--(*count) == 0)
+ return 1;
+ return 0;
+}
+
+static int set_found(struct ntdb_context *ntdb, bool found[3])
+{
+ unsigned int idx;
+
+ if (strcmp(ntdb_name(ntdb), "run-ntdb_foreach0.ntdb") == 0)
+ idx = 0;
+ else if (strcmp(ntdb_name(ntdb), "run-ntdb_foreach1.ntdb") == 0)
+ idx = 1;
+ else if (strcmp(ntdb_name(ntdb), "run-ntdb_foreach2.ntdb") == 0)
+ idx = 2;
+ else
+ abort();
+
+ if (found[idx])
+ abort();
+ found[idx] = true;
+ return 0;
+}
+
+int main(int argc, char *argv[])
+{
+ unsigned int i, count;
+ bool found[3];
+ struct ntdb_context *ntdb0, *ntdb1, *ntdb;
+ int flags[] = { NTDB_DEFAULT, NTDB_NOMMAP,
+ NTDB_CONVERT, NTDB_NOMMAP|NTDB_CONVERT };
+
+ plan_tests(sizeof(flags) / sizeof(flags[0]) * 8);
+ for (i = 0; i < sizeof(flags) / sizeof(flags[0]); i++) {
+ ntdb0 = ntdb_open("run-ntdb_foreach0.ntdb", flags[i],
+ O_RDWR|O_CREAT|O_TRUNC, 0600, &tap_log_attr);
+ ntdb1 = ntdb_open("run-ntdb_foreach1.ntdb", flags[i],
+ O_RDWR|O_CREAT|O_TRUNC, 0600, &tap_log_attr);
+ ntdb = ntdb_open("run-ntdb_foreach2.ntdb", flags[i],
+ O_RDWR|O_CREAT|O_TRUNC, 0600, &tap_log_attr);
+
+ memset(found, 0, sizeof(found));
+ ntdb_foreach(set_found, found);
+ ok1(found[0] && found[1] && found[2]);
+
+ /* Test premature iteration termination */
+ count = 1;
+ ntdb_foreach(drop_count, &count);
+ ok1(count == 0);
+
+ ntdb_close(ntdb1);
+ memset(found, 0, sizeof(found));
+ ntdb_foreach(set_found, found);
+ ok1(found[0] && !found[1] && found[2]);
+
+ ntdb_close(ntdb);
+ memset(found, 0, sizeof(found));
+ ntdb_foreach(set_found, found);
+ ok1(found[0] && !found[1] && !found[2]);
+
+ ntdb1 = ntdb_open("run-ntdb_foreach1.ntdb", flags[i],
+ O_RDWR, 0600, &tap_log_attr);
+ memset(found, 0, sizeof(found));
+ ntdb_foreach(set_found, found);
+ ok1(found[0] && found[1] && !found[2]);
+
+ ntdb_close(ntdb0);
+ memset(found, 0, sizeof(found));
+ ntdb_foreach(set_found, found);
+ ok1(!found[0] && found[1] && !found[2]);
+
+ ntdb_close(ntdb1);
+ memset(found, 0, sizeof(found));
+ ntdb_foreach(set_found, found);
+ ok1(!found[0] && !found[1] && !found[2]);
+ ok1(tap_log_messages == 0);
+ }
+
+ return exit_status();
+}
diff --git a/lib/ntdb/test/run-traverse.c b/lib/ntdb/test/run-traverse.c
new file mode 100644
index 0000000000..9dfc94d3b3
--- /dev/null
+++ b/lib/ntdb/test/run-traverse.c
@@ -0,0 +1,203 @@
+#include "ntdb-source.h"
+#include "tap-interface.h"
+#include "logging.h"
+
+#define NUM_RECORDS 1000
+
+/* We use the same seed which we saw a failure on. */
+static uint64_t fixedhash(const void *key, size_t len, uint64_t seed, void *p)
+{
+ return hash64_stable((const unsigned char *)key, len,
+ *(uint64_t *)p);
+}
+
+static bool store_records(struct ntdb_context *ntdb)
+{
+ int i;
+ NTDB_DATA key = { (unsigned char *)&i, sizeof(i) };
+ NTDB_DATA data = { (unsigned char *)&i, sizeof(i) };
+
+ for (i = 0; i < NUM_RECORDS; i++)
+ if (ntdb_store(ntdb, key, data, NTDB_REPLACE) != 0)
+ return false;
+ return true;
+}
+
+struct trav_data {
+ unsigned int calls, call_limit;
+ int low, high;
+ bool mismatch;
+ bool delete;
+ enum NTDB_ERROR delete_error;
+};
+
+static int trav(struct ntdb_context *ntdb, NTDB_DATA key, NTDB_DATA dbuf,
+ struct trav_data *td)
+{
+ int val;
+
+ td->calls++;
+ if (key.dsize != sizeof(val) || dbuf.dsize != sizeof(val)
+ || memcmp(key.dptr, dbuf.dptr, key.dsize) != 0) {
+ td->mismatch = true;
+ return -1;
+ }
+ memcpy(&val, dbuf.dptr, dbuf.dsize);
+ if (val < td->low)
+ td->low = val;
+ if (val > td->high)
+ td->high = val;
+
+ if (td->delete) {
+ td->delete_error = ntdb_delete(ntdb, key);
+ if (td->delete_error != NTDB_SUCCESS) {
+ return -1;
+ }
+ }
+
+ if (td->calls == td->call_limit)
+ return 1;
+ return 0;
+}
+
+struct trav_grow_data {
+ unsigned int calls;
+ unsigned int num_large;
+ bool mismatch;
+ enum NTDB_ERROR error;
+};
+
+static int trav_grow(struct ntdb_context *ntdb, NTDB_DATA key, NTDB_DATA dbuf,
+ struct trav_grow_data *tgd)
+{
+ int val;
+ unsigned char buffer[128] = { 0 };
+
+ tgd->calls++;
+ if (key.dsize != sizeof(val) || dbuf.dsize < sizeof(val)
+ || memcmp(key.dptr, dbuf.dptr, key.dsize) != 0) {
+ tgd->mismatch = true;
+ return -1;
+ }
+
+ if (dbuf.dsize > sizeof(val))
+ /* We must have seen this before! */
+ tgd->num_large++;
+
+ /* Make a big difference to the database. */
+ dbuf.dptr = buffer;
+ dbuf.dsize = sizeof(buffer);
+ tgd->error = ntdb_append(ntdb, key, dbuf);
+ if (tgd->error != NTDB_SUCCESS) {
+ return -1;
+ }
+ return 0;
+}
+
+int main(int argc, char *argv[])
+{
+ unsigned int i;
+ int num;
+ struct trav_data td;
+ struct trav_grow_data tgd;
+ struct ntdb_context *ntdb;
+ uint64_t seed = 16014841315512641303ULL;
+ int flags[] = { NTDB_INTERNAL, NTDB_DEFAULT, NTDB_NOMMAP,
+ NTDB_INTERNAL|NTDB_CONVERT, NTDB_CONVERT,
+ NTDB_NOMMAP|NTDB_CONVERT };
+ union ntdb_attribute hattr = { .hash = { .base = { NTDB_ATTRIBUTE_HASH },
+ .fn = fixedhash,
+ .data = &seed } };
+
+ hattr.base.next = &tap_log_attr;
+
+ plan_tests(sizeof(flags) / sizeof(flags[0]) * 32 + 1);
+ for (i = 0; i < sizeof(flags) / sizeof(flags[0]); i++) {
+ ntdb = ntdb_open("run-traverse.ntdb", flags[i],
+ O_RDWR|O_CREAT|O_TRUNC, 0600, &hattr);
+ ok1(ntdb);
+ if (!ntdb)
+ continue;
+
+ ok1(ntdb_traverse(ntdb, NULL, NULL) == 0);
+
+ ok1(store_records(ntdb));
+ num = ntdb_traverse(ntdb, NULL, NULL);
+ ok1(num == NUM_RECORDS);
+
+ /* Full traverse. */
+ td.calls = 0;
+ td.call_limit = UINT_MAX;
+ td.low = INT_MAX;
+ td.high = INT_MIN;
+ td.mismatch = false;
+ td.delete = false;
+
+ num = ntdb_traverse(ntdb, trav, &td);
+ ok1(num == NUM_RECORDS);
+ ok1(!td.mismatch);
+ ok1(td.calls == NUM_RECORDS);
+ ok1(td.low == 0);
+ ok1(td.high == NUM_RECORDS-1);
+
+ /* Short traverse. */
+ td.calls = 0;
+ td.call_limit = NUM_RECORDS / 2;
+ td.low = INT_MAX;
+ td.high = INT_MIN;
+ td.mismatch = false;
+ td.delete = false;
+
+ num = ntdb_traverse(ntdb, trav, &td);
+ ok1(num == NUM_RECORDS / 2);
+ ok1(!td.mismatch);
+ ok1(td.calls == NUM_RECORDS / 2);
+ ok1(td.low <= NUM_RECORDS / 2);
+ ok1(td.high > NUM_RECORDS / 2);
+ ok1(ntdb_check(ntdb, NULL, NULL) == 0);
+ ok1(tap_log_messages == 0);
+
+ /* Deleting traverse (delete everything). */
+ td.calls = 0;
+ td.call_limit = UINT_MAX;
+ td.low = INT_MAX;
+ td.high = INT_MIN;
+ td.mismatch = false;
+ td.delete = true;
+ td.delete_error = NTDB_SUCCESS;
+ num = ntdb_traverse(ntdb, trav, &td);
+ ok1(num == NUM_RECORDS);
+ ok1(td.delete_error == NTDB_SUCCESS);
+ ok1(!td.mismatch);
+ ok1(td.calls == NUM_RECORDS);
+ ok1(td.low == 0);
+ ok1(td.high == NUM_RECORDS - 1);
+ ok1(ntdb_check(ntdb, NULL, NULL) == 0);
+
+ /* Now it's empty! */
+ ok1(ntdb_traverse(ntdb, NULL, NULL) == 0);
+
+ /* Re-add. */
+ ok1(store_records(ntdb));
+ ok1(ntdb_traverse(ntdb, NULL, NULL) == NUM_RECORDS);
+ ok1(ntdb_check(ntdb, NULL, NULL) == 0);
+
+ /* Grow. This will cause us to be reshuffled. */
+ tgd.calls = 0;
+ tgd.num_large = 0;
+ tgd.mismatch = false;
+ tgd.error = NTDB_SUCCESS;
+ ok1(ntdb_traverse(ntdb, trav_grow, &tgd) > 1);
+ ok1(tgd.error == 0);
+ ok1(!tgd.mismatch);
+ ok1(ntdb_check(ntdb, NULL, NULL) == 0);
+ ok1(tgd.num_large < tgd.calls);
+ diag("growing db: %u calls, %u repeats",
+ tgd.calls, tgd.num_large);
+
+ ntdb_close(ntdb);
+ }
+
+ ok1(tap_log_messages == 0);
+ return exit_status();
+}
diff --git a/lib/ntdb/test/tap-interface.c b/lib/ntdb/test/tap-interface.c
new file mode 100644
index 0000000000..077ec2cd9a
--- /dev/null
+++ b/lib/ntdb/test/tap-interface.c
@@ -0,0 +1,3 @@
+#include "tap-interface.h"
+
+unsigned tap_ok_count, tap_ok_target = -1U;
diff --git a/lib/ntdb/test/tap-interface.h b/lib/ntdb/test/tap-interface.h
new file mode 100644
index 0000000000..f3d4ec2545
--- /dev/null
+++ b/lib/ntdb/test/tap-interface.h
@@ -0,0 +1,41 @@
+/*
+ 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>
+#include <ccan/err/err.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
+
+extern unsigned tap_ok_count, tap_ok_target;
+#define plan_tests(num) do { tap_ok_target = (num); } while(0)
+#define ok(e, ...) ((e) ? (printf("."), tap_ok_count++, true) : (warnx(__VA_ARGS__), false))
+#define ok1(e) ok((e), "%s:%s", __location__, #e)
+#define pass(...) (printf("."), tap_ok_count++)
+#define fail(...) warnx(__VA_ARGS__)
+#define diag printf
+#define exit_status() (tap_ok_count == tap_ok_target ? 0 : 1)