diff options
author | Rusty Russell <rusty@rustcorp.com.au> | 2012-06-18 22:30:26 +0930 |
---|---|---|
committer | Rusty Russell <rusty@rustcorp.com.au> | 2012-06-19 05:38:06 +0200 |
commit | 16cc345d4f84367e70e133200f7aa335c2aae8c6 (patch) | |
tree | 955a33c25c19f3127e24ba6b0e108da6b1f7f804 /lib/ntdb/test | |
parent | 76758b9767fad45ff144bbfef3ab84bca5d4650e (diff) | |
download | samba-16cc345d4f84367e70e133200f7aa335c2aae8c6.tar.gz samba-16cc345d4f84367e70e133200f7aa335c2aae8c6.tar.bz2 samba-16cc345d4f84367e70e133200f7aa335c2aae8c6.zip |
TDB2: Goodbye TDB2, Hello NTDB.
This renames everything from tdb2 to ntdb: importantly, we no longer
use the tdb_ namespace, so you can link against both ntdb and tdb if
you want to.
This also enables building of standalone ntdb by the autobuild script.
Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
Diffstat (limited to 'lib/ntdb/test')
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 *)# + 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 *)# + 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 *)# + 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) |