diff options
author | Rusty Russell <rusty@rustcorp.com.au> | 2011-09-14 07:24:13 +0930 |
---|---|---|
committer | Rusty Russell <rusty@rustcorp.com.au> | 2011-09-14 07:24:13 +0930 |
commit | 9660546a18cc9af508e6e594349d869bdcefafb2 (patch) | |
tree | d4730be4e2663201ed8f6c6820180e930bb40d89 /lib | |
parent | bfcd0ebd980eb5ebc56e6b57fd158c2fad89fc78 (diff) | |
download | samba-9660546a18cc9af508e6e594349d869bdcefafb2.tar.gz samba-9660546a18cc9af508e6e594349d869bdcefafb2.tar.bz2 samba-9660546a18cc9af508e6e594349d869bdcefafb2.zip |
tdb2: test: import tdb1's tests.
The main change is to s/tdb/tdb1_/ everywhere.
Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
(Imported from CCAN commit fab544c24c1ad6523f95893abcaec4e6cce6c2b4)
Diffstat (limited to 'lib')
33 files changed, 2102 insertions, 0 deletions
diff --git a/lib/tdb2/test/jenkins-be-hash.tdb1 b/lib/tdb2/test/jenkins-be-hash.tdb1 Binary files differnew file mode 100644 index 0000000000..b652840414 --- /dev/null +++ b/lib/tdb2/test/jenkins-be-hash.tdb1 diff --git a/lib/tdb2/test/jenkins-le-hash.tdb1 b/lib/tdb2/test/jenkins-le-hash.tdb1 Binary files differnew file mode 100644 index 0000000000..007e0a3368 --- /dev/null +++ b/lib/tdb2/test/jenkins-le-hash.tdb1 diff --git a/lib/tdb2/test/old-nohash-be.tdb1 b/lib/tdb2/test/old-nohash-be.tdb1 Binary files differnew file mode 100644 index 0000000000..1c49116c1d --- /dev/null +++ b/lib/tdb2/test/old-nohash-be.tdb1 diff --git a/lib/tdb2/test/old-nohash-le.tdb1 b/lib/tdb2/test/old-nohash-le.tdb1 Binary files differnew file mode 100644 index 0000000000..0655072d88 --- /dev/null +++ b/lib/tdb2/test/old-nohash-le.tdb1 diff --git a/lib/tdb2/test/run-tdb1-3G-file.c b/lib/tdb2/test/run-tdb1-3G-file.c new file mode 100644 index 0000000000..6509ca3ab7 --- /dev/null +++ b/lib/tdb2/test/run-tdb1-3G-file.c @@ -0,0 +1,118 @@ +/* We need this otherwise fcntl locking fails. */ +#define _FILE_OFFSET_BITS 64 +#include "tdb2-source.h" +#include <ccan/tap/tap.h> +#include <stdlib.h> +#include <err.h> +#include "tdb1-logging.h" + +static int tdb1_expand_file_sparse(struct tdb1_context *tdb, + tdb1_off_t size, + tdb1_off_t addition) +{ + if (tdb->read_only || tdb->traverse_read) { + tdb->ecode = TDB1_ERR_RDONLY; + return -1; + } + + if (ftruncate(tdb->fd, size+addition) == -1) { + char b = 0; + ssize_t written = pwrite(tdb->fd, &b, 1, (size+addition) - 1); + if (written == 0) { + /* try once more, potentially revealing errno */ + written = pwrite(tdb->fd, &b, 1, (size+addition) - 1); + } + if (written == 0) { + /* again - give up, guessing errno */ + errno = ENOSPC; + } + if (written != 1) { + TDB1_LOG((tdb, TDB1_DEBUG_FATAL, "expand_file to %d failed (%s)\n", + size+addition, strerror(errno))); + return -1; + } + } + + return 0; +} + +static const struct tdb1_methods large_io_methods = { + tdb1_read, + tdb1_write, + tdb1_next_hash_chain, + tdb1_oob, + tdb1_expand_file_sparse +}; + +static int test_traverse(struct tdb1_context *tdb, TDB1_DATA key, TDB1_DATA data, + void *_data) +{ + TDB1_DATA *expect = _data; + ok1(key.dsize == strlen("hi")); + ok1(memcmp(key.dptr, "hi", strlen("hi")) == 0); + ok1(data.dsize == expect->dsize); + ok1(memcmp(data.dptr, expect->dptr, data.dsize) == 0); + return 0; +} + +int main(int argc, char *argv[]) +{ + struct tdb1_context *tdb; + TDB1_DATA key, orig_data, data; + uint32_t hash; + tdb1_off_t rec_ptr; + struct tdb1_record rec; + + plan_tests(24); + tdb = tdb1_open_ex("run-36-file.tdb", 1024, TDB1_CLEAR_IF_FIRST, + O_CREAT|O_TRUNC|O_RDWR, 0600, &taplogctx, NULL); + + ok1(tdb); + tdb->methods = &large_io_methods; + + /* Enlarge the file (internally multiplies by 2). */ + ok1(tdb1_expand(tdb, 1500000000) == 0); + + /* Put an entry in, and check it. */ + key.dsize = strlen("hi"); + key.dptr = (void *)"hi"; + orig_data.dsize = strlen("world"); + orig_data.dptr = (void *)"world"; + + ok1(tdb1_store(tdb, key, orig_data, TDB1_INSERT) == 0); + + data = tdb1_fetch(tdb, key); + ok1(data.dsize == strlen("world")); + ok1(memcmp(data.dptr, "world", strlen("world")) == 0); + free(data.dptr); + + /* That currently fills at the end, make sure that's true. */ + hash = tdb->hash_fn(&key); + rec_ptr = tdb1_find_lock_hash(tdb, key, hash, F_RDLCK, &rec); + ok1(rec_ptr); + ok1(rec_ptr > 2U*1024*1024*1024); + tdb1_unlock(tdb, TDB1_BUCKET(rec.full_hash), F_RDLCK); + + /* Traverse must work. */ + ok1(tdb1_traverse(tdb, test_traverse, &orig_data) == 1); + + /* Delete should work. */ + ok1(tdb1_delete(tdb, key) == 0); + + ok1(tdb1_traverse(tdb, test_traverse, NULL) == 0); + + /* Transactions should work. */ + ok1(tdb1_transaction_start(tdb) == 0); + ok1(tdb1_store(tdb, key, orig_data, TDB1_INSERT) == 0); + + data = tdb1_fetch(tdb, key); + ok1(data.dsize == strlen("world")); + ok1(memcmp(data.dptr, "world", strlen("world")) == 0); + free(data.dptr); + ok1(tdb1_transaction_commit(tdb) == 0); + + ok1(tdb1_traverse(tdb, test_traverse, &orig_data) == 1); + tdb1_close(tdb); + + return exit_status(); +} diff --git a/lib/tdb2/test/run-tdb1-bad-tdb-header.c b/lib/tdb2/test/run-tdb1-bad-tdb-header.c new file mode 100644 index 0000000000..9e23e97970 --- /dev/null +++ b/lib/tdb2/test/run-tdb1-bad-tdb-header.c @@ -0,0 +1,49 @@ +#include "tdb2-source.h" +#include <ccan/tap/tap.h> +#include <stdlib.h> +#include <err.h> +#include "tdb1-logging.h" + +int main(int argc, char *argv[]) +{ + struct tdb1_context *tdb; + struct tdb1_header hdr; + int fd; + + plan_tests(11); + /* Can open fine if complete crap, as long as O_CREAT. */ + fd = open("run-bad-tdb-header.tdb", O_RDWR|O_CREAT|O_TRUNC, 0600); + ok1(fd >= 0); + ok1(write(fd, "hello world", 11) == 11); + close(fd); + tdb = tdb1_open_ex("run-bad-tdb-header.tdb", 1024, 0, O_RDWR, 0, + &taplogctx, NULL); + ok1(!tdb); + tdb = tdb1_open_ex("run-bad-tdb-header.tdb", 1024, 0, O_CREAT|O_RDWR, + 0600, &taplogctx, NULL); + ok1(tdb); + tdb1_close(tdb); + + /* Now, with wrong version it should *not* overwrite. */ + fd = open("run-bad-tdb-header.tdb", O_RDWR); + ok1(fd >= 0); + ok1(read(fd, &hdr, sizeof(hdr)) == sizeof(hdr)); + ok1(hdr.version == TDB1_VERSION); + hdr.version++; + lseek(fd, 0, SEEK_SET); + ok1(write(fd, &hdr, sizeof(hdr)) == sizeof(hdr)); + close(fd); + + tdb = tdb1_open_ex("run-bad-tdb-header.tdb", 1024, 0, O_RDWR|O_CREAT, + 0600, &taplogctx, NULL); + ok1(errno == EIO); + ok1(!tdb); + + /* With truncate, will be fine. */ + tdb = tdb1_open_ex("run-bad-tdb-header.tdb", 1024, 0, + O_RDWR|O_CREAT|O_TRUNC, 0600, &taplogctx, NULL); + ok1(tdb); + tdb1_close(tdb); + + return exit_status(); +} diff --git a/lib/tdb2/test/run-tdb1-check.c b/lib/tdb2/test/run-tdb1-check.c new file mode 100644 index 0000000000..03b0191a17 --- /dev/null +++ b/lib/tdb2/test/run-tdb1-check.c @@ -0,0 +1,55 @@ +#include "tdb2-source.h" +#include <ccan/tap/tap.h> +#include <stdlib.h> +#include <err.h> +#include "tdb1-logging.h" + +int main(int argc, char *argv[]) +{ + struct tdb1_context *tdb; + TDB1_DATA key, data; + + plan_tests(13); + tdb = tdb1_open_ex("run-check.tdb", 1, TDB1_CLEAR_IF_FIRST, + O_CREAT|O_TRUNC|O_RDWR, 0600, &taplogctx, NULL); + + ok1(tdb); + ok1(tdb1_check(tdb, NULL, NULL) == 0); + + key.dsize = strlen("hi"); + key.dptr = (void *)"hi"; + data.dsize = strlen("world"); + data.dptr = (void *)"world"; + + ok1(tdb1_store(tdb, key, data, TDB1_INSERT) == 0); + ok1(tdb1_check(tdb, NULL, NULL) == 0); + tdb1_close(tdb); + + tdb = tdb1_open_ex("run-check.tdb", 1024, 0, O_RDWR, 0, + &taplogctx, NULL); + ok1(tdb); + ok1(tdb1_check(tdb, NULL, NULL) == 0); + tdb1_close(tdb); + + tdb = tdb1_open_ex("test/tdb1.corrupt", 1024, 0, O_RDWR, 0, + &taplogctx, NULL); + ok1(tdb); + ok1(tdb1_check(tdb, NULL, NULL) == -1); + ok1(tdb1_error(tdb) == TDB1_ERR_CORRUPT); + tdb1_close(tdb); + + /* Big and little endian should work! */ + tdb = tdb1_open_ex("test/old-nohash-le.tdb1", 1024, 0, O_RDWR, 0, + &taplogctx, NULL); + ok1(tdb); + ok1(tdb1_check(tdb, NULL, NULL) == 0); + tdb1_close(tdb); + + tdb = tdb1_open_ex("test/old-nohash-be.tdb1", 1024, 0, O_RDWR, 0, + &taplogctx, NULL); + ok1(tdb); + ok1(tdb1_check(tdb, NULL, NULL) == 0); + tdb1_close(tdb); + + return exit_status(); +} diff --git a/lib/tdb2/test/run-tdb1-corrupt.c b/lib/tdb2/test/run-tdb1-corrupt.c new file mode 100644 index 0000000000..b2dcafadc0 --- /dev/null +++ b/lib/tdb2/test/run-tdb1-corrupt.c @@ -0,0 +1,118 @@ +#include "tdb2-source.h" +#include <ccan/tap/tap.h> +#include <stdlib.h> +#include <err.h> +#include "tdb1-logging.h" + +static int check(TDB1_DATA key, TDB1_DATA data, void *private) +{ + unsigned int *sizes = private; + + if (key.dsize > strlen("hello")) + return -1; + if (memcmp(key.dptr, "hello", key.dsize) != 0) + return -1; + + if (data.dsize != strlen("world")) + return -1; + if (memcmp(data.dptr, "world", data.dsize) != 0) + return -1; + + sizes[0] += key.dsize; + sizes[1] += data.dsize; + return 0; +} + +static void tdb1_flip_bit(struct tdb1_context *tdb, unsigned int bit) +{ + unsigned int off = bit / CHAR_BIT; + unsigned char mask = (1 << (bit % CHAR_BIT)); + + if (tdb->map_ptr) + ((unsigned char *)tdb->map_ptr)[off] ^= mask; + else { + unsigned char c; + if (pread(tdb->fd, &c, 1, off) != 1) + err(1, "pread"); + c ^= mask; + if (pwrite(tdb->fd, &c, 1, off) != 1) + err(1, "pwrite"); + } +} + +static void check_test(struct tdb1_context *tdb) +{ + TDB1_DATA key, data; + unsigned int i, verifiable, corrupt, sizes[2], dsize, ksize; + + ok1(tdb1_check(tdb, NULL, NULL) == 0); + + key.dptr = (void *)"hello"; + data.dsize = strlen("world"); + data.dptr = (void *)"world"; + + /* Key and data size respectively. */ + dsize = ksize = 0; + + /* 5 keys in hash size 2 means we'll have multichains. */ + for (key.dsize = 1; key.dsize <= 5; key.dsize++) { + ksize += key.dsize; + dsize += data.dsize; + if (tdb1_store(tdb, key, data, TDB1_INSERT) != 0) + abort(); + } + + /* This is how many bytes we expect to be verifiable. */ + /* From the file header. */ + verifiable = strlen(TDB1_MAGIC_FOOD) + 1 + + 2 * sizeof(uint32_t) + 2 * sizeof(tdb1_off_t) + + 2 * sizeof(uint32_t); + /* From the free list chain and hash chains. */ + verifiable += 3 * sizeof(tdb1_off_t); + /* From the record headers & tailer */ + verifiable += 5 * (sizeof(struct tdb1_record) + sizeof(uint32_t)); + /* The free block: we ignore datalen, keylen, full_hash. */ + verifiable += sizeof(struct tdb1_record) - 3*sizeof(uint32_t) + + sizeof(uint32_t); + /* Our check function verifies the key and data. */ + verifiable += ksize + dsize; + + /* Flip one bit at a time, make sure it detects verifiable bytes. */ + for (i = 0, corrupt = 0; i < tdb->map_size * CHAR_BIT; i++) { + tdb1_flip_bit(tdb, i); + memset(sizes, 0, sizeof(sizes)); + if (tdb1_check(tdb, check, sizes) != 0) + corrupt++; + else if (sizes[0] != ksize || sizes[1] != dsize) + corrupt++; + tdb1_flip_bit(tdb, i); + } + ok(corrupt == verifiable * CHAR_BIT, "corrupt %u should be %u", + corrupt, verifiable * CHAR_BIT); +} + +int main(int argc, char *argv[]) +{ + struct tdb1_context *tdb; + + plan_tests(4); + /* This should use mmap. */ + tdb = tdb1_open_ex("run-corrupt.tdb", 2, TDB1_CLEAR_IF_FIRST, + O_CREAT|O_TRUNC|O_RDWR, 0600, &taplogctx, NULL); + + if (!tdb) + abort(); + check_test(tdb); + tdb1_close(tdb); + + /* This should not. */ + tdb = tdb1_open_ex("run-corrupt.tdb", 2, TDB1_CLEAR_IF_FIRST|TDB1_NOMMAP, + O_CREAT|O_TRUNC|O_RDWR, 0600, &taplogctx, NULL); + + if (!tdb) + abort(); + check_test(tdb); + tdb1_close(tdb); + + return exit_status(); +} diff --git a/lib/tdb2/test/run-tdb1-die-during-transaction.c b/lib/tdb2/test/run-tdb1-die-during-transaction.c new file mode 100644 index 0000000000..ae03d5f8b9 --- /dev/null +++ b/lib/tdb2/test/run-tdb1-die-during-transaction.c @@ -0,0 +1,215 @@ +#include <ccan/tdb2/private.h> +#include <unistd.h> +#include "tdb1-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_lockcheck1 +#define ftruncate ftruncate_check + +#include "tdb2-source.h" +#include <ccan/tap/tap.h> +#include <stdlib.h> +#include <stdbool.h> +#include <stdarg.h> +#include <err.h> +#include <setjmp.h> +#include "tdb1-external-agent.h" +#include "tdb1-logging.h" + +#undef write +#undef pwrite +#undef fcntl +#undef ftruncate + +static bool in_transaction; +static int target, current; +static jmp_buf jmpbuf; +#define TEST_DBNAME "run-die-during-transaction.tdb" +#define KEY_STRING "helloworld" + +static void maybe_die(int fd) +{ + if (in_transaction && current++ == target) { + longjmp(jmpbuf, 1); + } +} + +static ssize_t pwrite_check(int fd, + const void *buf, size_t count, off_t offset) +{ + ssize_t ret; + + maybe_die(fd); + + ret = pwrite(fd, buf, count, offset); + if (ret != count) + return ret; + + maybe_die(fd); + return ret; +} + +static ssize_t write_check(int fd, const void *buf, size_t count) +{ + ssize_t ret; + + maybe_die(fd); + + ret = write(fd, buf, count); + if (ret != count) + return ret; + + maybe_die(fd); + return ret; +} + +static int ftruncate_check(int fd, off_t length) +{ + int ret; + + maybe_die(fd); + + ret = ftruncate(fd, length); + + maybe_die(fd); + return ret; +} + +static bool test_death(enum operation op, struct agent *agent) +{ + struct tdb1_context *tdb = NULL; + TDB1_DATA key; + enum agent_return ret; + int needed_recovery = 0; + + current = target = 0; +reset: + unlink(TEST_DBNAME); + tdb = tdb1_open_ex(TEST_DBNAME, 1024, TDB1_NOMMAP, + O_CREAT|O_TRUNC|O_RDWR, 0600, &taplogctx, NULL); + + if (setjmp(jmpbuf) != 0) { + /* We're partway through. Simulate our death. */ + close(tdb->fd); + forget_locking1(); + in_transaction = false; + + ret = external_agent_operation1(agent, NEEDS_RECOVERY, ""); + if (ret == SUCCESS) + needed_recovery++; + else if (ret != FAILED) { + diag("Step %u agent NEEDS_RECOVERY = %s", current, + agent_return_name1(ret)); + return false; + } + + ret = external_agent_operation1(agent, op, KEY_STRING); + if (ret != SUCCESS) { + diag("Step %u op %s failed = %s", current, + operation_name1(op), + agent_return_name1(ret)); + return false; + } + + ret = external_agent_operation1(agent, NEEDS_RECOVERY, ""); + if (ret != FAILED) { + diag("Still needs recovery after step %u = %s", + current, agent_return_name1(ret)); + return false; + } + + ret = external_agent_operation1(agent, CHECK, ""); + if (ret != SUCCESS) { + diag("Step %u check failed = %s", current, + agent_return_name1(ret)); + return false; + } + + ret = external_agent_operation1(agent, CLOSE, ""); + if (ret != SUCCESS) { + diag("Step %u close failed = %s", current, + agent_return_name1(ret)); + return false; + } + + /* Suppress logging as this tries to use closed fd. */ + suppress_logging = true; + suppress_lockcheck1 = true; + tdb1_close(tdb); + suppress_logging = false; + suppress_lockcheck1 = false; + target++; + current = 0; + goto reset; + } + + /* Put key for agent to fetch. */ + key.dsize = strlen(KEY_STRING); + key.dptr = (void *)KEY_STRING; + if (tdb1_store(tdb, key, key, TDB1_INSERT) != 0) + return false; + + /* This is the key we insert in transaction. */ + key.dsize--; + + ret = external_agent_operation1(agent, OPEN, TEST_DBNAME); + if (ret != SUCCESS) + errx(1, "Agent failed to open: %s", agent_return_name1(ret)); + + ret = external_agent_operation1(agent, FETCH, KEY_STRING); + if (ret != SUCCESS) + errx(1, "Agent failed find key: %s", agent_return_name1(ret)); + + in_transaction = true; + if (tdb1_transaction_start(tdb) != 0) + return false; + + if (tdb1_store(tdb, key, key, TDB1_INSERT) != 0) + return false; + + if (tdb1_transaction_commit(tdb) != 0) + return false; + + in_transaction = false; + + /* We made it! */ + diag("Completed %u runs", current); + tdb1_close(tdb); + ret = external_agent_operation1(agent, CLOSE, ""); + if (ret != SUCCESS) { + diag("Step %u close failed = %s", current, + agent_return_name1(ret)); + return false; + } + + ok1(needed_recovery); + ok1(locking_errors1 == 0); + ok1(forget_locking1() == 0); + locking_errors1 = 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_callback1 = maybe_die; + + agent = prepare_external_agent1(); + if (!agent) + err(1, "preparing agent"); + + for (i = 0; i < sizeof(ops)/sizeof(ops[0]); i++) { + diag("Testing %s after death", operation_name1(ops[i])); + ok1(test_death(ops[i], agent)); + } + + return exit_status(); +} diff --git a/lib/tdb2/test/run-tdb1-endian.c b/lib/tdb2/test/run-tdb1-endian.c new file mode 100644 index 0000000000..1a01de17ab --- /dev/null +++ b/lib/tdb2/test/run-tdb1-endian.c @@ -0,0 +1,54 @@ +#include "tdb2-source.h" +#include <ccan/tap/tap.h> +#include <stdlib.h> +#include <err.h> +#include "tdb1-logging.h" + +int main(int argc, char *argv[]) +{ + struct tdb1_context *tdb; + TDB1_DATA key, data; + + plan_tests(13); + tdb = tdb1_open_ex("run-endian.tdb", 1024, + TDB1_CLEAR_IF_FIRST|TDB1_CONVERT, + O_CREAT|O_TRUNC|O_RDWR, 0600, &taplogctx, NULL); + + ok1(tdb); + key.dsize = strlen("hi"); + key.dptr = (void *)"hi"; + data.dsize = strlen("world"); + data.dptr = (void *)"world"; + + ok1(tdb1_store(tdb, key, data, TDB1_MODIFY) < 0); + ok1(tdb1_error(tdb) == TDB1_ERR_NOEXIST); + ok1(tdb1_store(tdb, key, data, TDB1_INSERT) == 0); + ok1(tdb1_store(tdb, key, data, TDB1_INSERT) < 0); + ok1(tdb1_error(tdb) == TDB1_ERR_EXISTS); + ok1(tdb1_store(tdb, key, data, TDB1_MODIFY) == 0); + + data = tdb1_fetch(tdb, key); + ok1(data.dsize == strlen("world")); + ok1(memcmp(data.dptr, "world", strlen("world")) == 0); + free(data.dptr); + + key.dsize++; + data = tdb1_fetch(tdb, key); + ok1(data.dptr == NULL); + tdb1_close(tdb); + + /* Reopen: should read it */ + tdb = tdb1_open_ex("run-endian.tdb", 1024, 0, O_RDWR, 0, + &taplogctx, NULL); + ok1(tdb); + + key.dsize = strlen("hi"); + key.dptr = (void *)"hi"; + data = tdb1_fetch(tdb, key); + ok1(data.dsize == strlen("world")); + ok1(memcmp(data.dptr, "world", strlen("world")) == 0); + free(data.dptr); + tdb1_close(tdb); + + return exit_status(); +} diff --git a/lib/tdb2/test/run-tdb1-incompatible.c b/lib/tdb2/test/run-tdb1-incompatible.c new file mode 100644 index 0000000000..ed3181a9b5 --- /dev/null +++ b/lib/tdb2/test/run-tdb1-incompatible.c @@ -0,0 +1,178 @@ +#include "tdb2-source.h" +#include <ccan/tap/tap.h> +#include <stdlib.h> +#include <err.h> + +static unsigned int tdb1_dumb_hash(TDB1_DATA *key) +{ + return key->dsize; +} + +static void log_fn(struct tdb1_context *tdb, enum tdb1_debug_level level, const char *fmt, ...) +{ + unsigned int *count = tdb1_get_logging_private(tdb); + if (strstr(fmt, "hash")) + (*count)++; +} + +static unsigned int hdr_rwlocks(const char *fname) +{ + struct tdb1_header hdr; + + int fd = open(fname, O_RDONLY); + if (fd == -1) + return -1; + + if (read(fd, &hdr, sizeof(hdr)) != sizeof(hdr)) + return -1; + + close(fd); + return hdr.rwlocks; +} + +int main(int argc, char *argv[]) +{ + struct tdb1_context *tdb; + unsigned int log_count, flags; + TDB1_DATA d; + struct tdb1_logging_context log_ctx = { log_fn, &log_count }; + + plan_tests(38 * 2); + + for (flags = 0; flags <= TDB1_CONVERT; flags += TDB1_CONVERT) { + unsigned int rwmagic = TDB1_HASH_RWLOCK_MAGIC; + + if (flags & TDB1_CONVERT) + tdb1_convert(&rwmagic, sizeof(rwmagic)); + + /* Create an old-style hash. */ + log_count = 0; + tdb = tdb1_open_ex("run-incompatible.tdb", 0, flags, + O_CREAT|O_RDWR|O_TRUNC, 0600, &log_ctx, + NULL); + ok1(tdb); + ok1(log_count == 0); + d.dptr = (void *)"Hello"; + d.dsize = 5; + ok1(tdb1_store(tdb, d, d, TDB1_INSERT) == 0); + tdb1_close(tdb); + + /* Should not have marked rwlocks field. */ + ok1(hdr_rwlocks("run-incompatible.tdb") == 0); + + /* We can still open any old-style with incompat flag. */ + log_count = 0; + tdb = tdb1_open_ex("run-incompatible.tdb", 0, + TDB1_INCOMPATIBLE_HASH, + O_RDWR, 0600, &log_ctx, NULL); + ok1(tdb); + ok1(log_count == 0); + d = tdb1_fetch(tdb, d); + ok1(d.dsize == 5); + free(d.dptr); + ok1(tdb1_check(tdb, NULL, NULL) == 0); + tdb1_close(tdb); + + log_count = 0; + tdb = tdb1_open_ex("test/jenkins-le-hash.tdb1", 0, 0, O_RDONLY, + 0, &log_ctx, tdb1_jenkins_hash); + ok1(tdb); + ok1(log_count == 0); + ok1(tdb1_check(tdb, NULL, NULL) == 0); + tdb1_close(tdb); + + log_count = 0; + tdb = tdb1_open_ex("test/jenkins-be-hash.tdb1", 0, 0, O_RDONLY, + 0, &log_ctx, tdb1_jenkins_hash); + ok1(tdb); + ok1(log_count == 0); + ok1(tdb1_check(tdb, NULL, NULL) == 0); + tdb1_close(tdb); + + /* OK, now create with incompatible flag, default hash. */ + log_count = 0; + tdb = tdb1_open_ex("run-incompatible.tdb", 0, + flags|TDB1_INCOMPATIBLE_HASH, + O_CREAT|O_RDWR|O_TRUNC, 0600, &log_ctx, + NULL); + ok1(tdb); + ok1(log_count == 0); + d.dptr = (void *)"Hello"; + d.dsize = 5; + ok1(tdb1_store(tdb, d, d, TDB1_INSERT) == 0); + tdb1_close(tdb); + + /* Should have marked rwlocks field. */ + ok1(hdr_rwlocks("run-incompatible.tdb") == rwmagic); + + /* Cannot open with old hash. */ + log_count = 0; + tdb = tdb1_open_ex("run-incompatible.tdb", 0, 0, + O_RDWR, 0600, &log_ctx, tdb1_old_hash); + ok1(!tdb); + ok1(log_count == 1); + + /* Can open with jenkins hash. */ + log_count = 0; + tdb = tdb1_open_ex("run-incompatible.tdb", 0, 0, + O_RDWR, 0600, &log_ctx, tdb1_jenkins_hash); + ok1(tdb); + ok1(log_count == 0); + d = tdb1_fetch(tdb, d); + ok1(d.dsize == 5); + free(d.dptr); + ok1(tdb1_check(tdb, NULL, NULL) == 0); + tdb1_close(tdb); + + /* Can open by letting it figure it out itself. */ + log_count = 0; + tdb = tdb1_open_ex("run-incompatible.tdb", 0, 0, + O_RDWR, 0600, &log_ctx, NULL); + ok1(tdb); + ok1(log_count == 0); + d.dptr = (void *)"Hello"; + d.dsize = 5; + d = tdb1_fetch(tdb, d); + ok1(d.dsize == 5); + free(d.dptr); + ok1(tdb1_check(tdb, NULL, NULL) == 0); + tdb1_close(tdb); + + /* We can also use incompatible hash with other hashes. */ + log_count = 0; + tdb = tdb1_open_ex("run-incompatible.tdb", 0, + flags|TDB1_INCOMPATIBLE_HASH, + O_CREAT|O_RDWR|O_TRUNC, 0600, &log_ctx, + tdb1_dumb_hash); + ok1(tdb); + ok1(log_count == 0); + d.dptr = (void *)"Hello"; + d.dsize = 5; + ok1(tdb1_store(tdb, d, d, TDB1_INSERT) == 0); + tdb1_close(tdb); + + /* Should have marked rwlocks field. */ + ok1(hdr_rwlocks("run-incompatible.tdb") == rwmagic); + + /* It should not open if we don't specify. */ + log_count = 0; + tdb = tdb1_open_ex("run-incompatible.tdb", 0, 0, O_RDWR, 0, + &log_ctx, NULL); + ok1(!tdb); + ok1(log_count == 1); + + /* Should reopen with correct hash. */ + log_count = 0; + tdb = tdb1_open_ex("run-incompatible.tdb", 0, 0, O_RDWR, 0, + &log_ctx, tdb1_dumb_hash); + ok1(tdb); + ok1(log_count == 0); + d = tdb1_fetch(tdb, d); + ok1(d.dsize == 5); + free(d.dptr); + ok1(tdb1_check(tdb, NULL, NULL) == 0); + tdb1_close(tdb); + } + + return exit_status(); +} diff --git a/lib/tdb2/test/run-tdb1-nested-transactions.c b/lib/tdb2/test/run-tdb1-nested-transactions.c new file mode 100644 index 0000000000..2518003c63 --- /dev/null +++ b/lib/tdb2/test/run-tdb1-nested-transactions.c @@ -0,0 +1,68 @@ +#include "tdb2-source.h" +#include <ccan/tap/tap.h> +#include <stdlib.h> +#include <stdbool.h> +#include <err.h> +#include "tdb1-logging.h" + +int main(int argc, char *argv[]) +{ + struct tdb1_context *tdb; + TDB1_DATA key, data; + + plan_tests(27); + key.dsize = strlen("hi"); + key.dptr = (void *)"hi"; + + tdb = tdb1_open_ex("run-nested-transactions.tdb", + 1024, TDB1_CLEAR_IF_FIRST|TDB1_DISALLOW_NESTING, + O_CREAT|O_TRUNC|O_RDWR, 0600, &taplogctx, NULL); + ok1(tdb); + + ok1(tdb1_transaction_start(tdb) == 0); + data.dptr = (void *)"world"; + data.dsize = strlen("world"); + ok1(tdb1_store(tdb, key, data, TDB1_INSERT) == 0); + data = tdb1_fetch(tdb, key); + ok1(data.dsize == strlen("world")); + ok1(memcmp(data.dptr, "world", strlen("world")) == 0); + free(data.dptr); + ok1(tdb1_transaction_start(tdb) != 0); + ok1(tdb1_error(tdb) == TDB1_ERR_NESTING); + + data = tdb1_fetch(tdb, key); + ok1(data.dsize == strlen("world")); + ok1(memcmp(data.dptr, "world", strlen("world")) == 0); + free(data.dptr); + ok1(tdb1_transaction_commit(tdb) == 0); + data = tdb1_fetch(tdb, key); + ok1(data.dsize == strlen("world")); + ok1(memcmp(data.dptr, "world", strlen("world")) == 0); + free(data.dptr); + tdb1_close(tdb); + + /* Allow nesting by default. */ + tdb = tdb1_open_ex("run-nested-transactions.tdb", + 1024, TDB1_DEFAULT, O_RDWR, 0, &taplogctx, NULL); + ok1(tdb); + + ok1(tdb1_transaction_start(tdb) == 0); + ok1(tdb1_transaction_start(tdb) == 0); + ok1(tdb1_delete(tdb, key) == 0); + ok1(tdb1_transaction_commit(tdb) == 0); + ok1(!tdb1_exists(tdb, key)); + ok1(tdb1_transaction_cancel(tdb) == 0); + /* Surprise! Kills inner "committed" transaction. */ + ok1(tdb1_exists(tdb, key)); + + ok1(tdb1_transaction_start(tdb) == 0); + ok1(tdb1_transaction_start(tdb) == 0); + ok1(tdb1_delete(tdb, key) == 0); + ok1(tdb1_transaction_commit(tdb) == 0); + ok1(!tdb1_exists(tdb, key)); + ok1(tdb1_transaction_commit(tdb) == 0); + ok1(!tdb1_exists(tdb, key)); + tdb1_close(tdb); + + return exit_status(); +} diff --git a/lib/tdb2/test/run-tdb1-nested-traverse.c b/lib/tdb2/test/run-tdb1-nested-traverse.c new file mode 100644 index 0000000000..b6f6ac63d9 --- /dev/null +++ b/lib/tdb2/test/run-tdb1-nested-traverse.c @@ -0,0 +1,80 @@ +#include "tdb1-lock-tracking.h" +#define fcntl fcntl_with_lockcheck1 +#include "tdb2-source.h" +#include <ccan/tap/tap.h> +#undef fcntl +#include <stdlib.h> +#include <stdbool.h> +#include <err.h> +#include "tdb1-external-agent.h" +#include "tdb1-logging.h" + +static struct agent *agent; + +static bool correct_key(TDB1_DATA key) +{ + return key.dsize == strlen("hi") + && memcmp(key.dptr, "hi", key.dsize) == 0; +} + +static bool correct_data(TDB1_DATA data) +{ + return data.dsize == strlen("world") + && memcmp(data.dptr, "world", data.dsize) == 0; +} + +static int traverse2(struct tdb1_context *tdb, TDB1_DATA key, TDB1_DATA data, + void *p) +{ + ok1(correct_key(key)); + ok1(correct_data(data)); + return 0; +} + +static int traverse1(struct tdb1_context *tdb, TDB1_DATA key, TDB1_DATA data, + void *p) +{ + ok1(correct_key(key)); + ok1(correct_data(data)); + ok1(external_agent_operation1(agent, TRANSACTION_START, tdb1_name(tdb)) + == WOULD_HAVE_BLOCKED); + tdb1_traverse(tdb, traverse2, NULL); + + /* That should *not* release the transaction lock! */ + ok1(external_agent_operation1(agent, TRANSACTION_START, tdb1_name(tdb)) + == WOULD_HAVE_BLOCKED); + return 0; +} + +int main(int argc, char *argv[]) +{ + struct tdb1_context *tdb; + TDB1_DATA key, data; + + plan_tests(17); + agent = prepare_external_agent1(); + if (!agent) + err(1, "preparing agent"); + + tdb = tdb1_open_ex("run-nested-traverse.tdb", 1024, TDB1_CLEAR_IF_FIRST, + O_CREAT|O_TRUNC|O_RDWR, 0600, &taplogctx, NULL); + ok1(tdb); + + ok1(external_agent_operation1(agent, OPEN, tdb1_name(tdb)) == SUCCESS); + ok1(external_agent_operation1(agent, TRANSACTION_START, tdb1_name(tdb)) + == SUCCESS); + ok1(external_agent_operation1(agent, TRANSACTION_COMMIT, tdb1_name(tdb)) + == SUCCESS); + + key.dsize = strlen("hi"); + key.dptr = (void *)"hi"; + data.dptr = (void *)"world"; + data.dsize = strlen("world"); + + ok1(tdb1_store(tdb, key, data, TDB1_INSERT) == 0); + tdb1_traverse(tdb, traverse1, NULL); + tdb1_traverse_read(tdb, traverse1, NULL); + tdb1_close(tdb); + + return exit_status(); +} diff --git a/lib/tdb2/test/run-tdb1-no-lock-during-traverse.c b/lib/tdb2/test/run-tdb1-no-lock-during-traverse.c new file mode 100644 index 0000000000..570562991d --- /dev/null +++ b/lib/tdb2/test/run-tdb1-no-lock-during-traverse.c @@ -0,0 +1,106 @@ +#include <ccan/tdb2/private.h> +#include <unistd.h> +#include "tdb1-lock-tracking.h" + +#define fcntl fcntl_with_lockcheck1 + +#include "tdb2-source.h" +#include <ccan/tap/tap.h> +#include <stdlib.h> +#include <err.h> +#include "tdb1-logging.h" + +#undef fcntl + +#define NUM_ENTRIES 10 + +static bool prepare_entries(struct tdb1_context *tdb) +{ + unsigned int i; + TDB1_DATA key, data; + + for (i = 0; i < NUM_ENTRIES; i++) { + key.dsize = sizeof(i); + key.dptr = (void *)&i; + data.dsize = strlen("world"); + data.dptr = (void *)"world"; + + if (tdb1_store(tdb, key, data, 0) != 0) + return false; + } + return true; +} + +static void delete_entries(struct tdb1_context *tdb) +{ + unsigned int i; + TDB1_DATA key; + + for (i = 0; i < NUM_ENTRIES; i++) { + key.dsize = sizeof(i); + key.dptr = (void *)&i; + + ok1(tdb1_delete(tdb, key) == 0); + } +} + +/* We don't know how many times this will run. */ +static int delete_other(struct tdb1_context *tdb, TDB1_DATA key, TDB1_DATA data, + void *private_data) +{ + unsigned int i; + memcpy(&i, key.dptr, 4); + i = (i + 1) % NUM_ENTRIES; + key.dptr = (void *)&i; + if (tdb1_delete(tdb, key) != 0) + (*(int *)private_data)++; + return 0; +} + +static int delete_self(struct tdb1_context *tdb, TDB1_DATA key, TDB1_DATA data, + void *private_data) +{ + ok1(tdb1_delete(tdb, key) == 0); + return 0; +} + +int main(int argc, char *argv[]) +{ + struct tdb1_context *tdb; + int errors = 0; + + plan_tests(41); + tdb = tdb1_open_ex("run-no-lock-during-traverse.tdb", + 1024, TDB1_CLEAR_IF_FIRST, O_CREAT|O_TRUNC|O_RDWR, + 0600, &taplogctx, NULL); + + ok1(tdb); + ok1(prepare_entries(tdb)); + ok1(locking_errors1 == 0); + ok1(tdb1_lockall(tdb) == 0); + ok1(locking_errors1 == 0); + tdb1_traverse(tdb, delete_other, &errors); + ok1(errors == 0); + ok1(locking_errors1 == 0); + ok1(tdb1_unlockall(tdb) == 0); + + ok1(prepare_entries(tdb)); + ok1(locking_errors1 == 0); + ok1(tdb1_lockall(tdb) == 0); + ok1(locking_errors1 == 0); + tdb1_traverse(tdb, delete_self, NULL); + ok1(locking_errors1 == 0); + ok1(tdb1_unlockall(tdb) == 0); + + ok1(prepare_entries(tdb)); + ok1(locking_errors1 == 0); + ok1(tdb1_lockall(tdb) == 0); + ok1(locking_errors1 == 0); + delete_entries(tdb); + ok1(locking_errors1 == 0); + ok1(tdb1_unlockall(tdb) == 0); + + ok1(tdb1_close(tdb) == 0); + + return exit_status(); +} diff --git a/lib/tdb2/test/run-tdb1-oldhash.c b/lib/tdb2/test/run-tdb1-oldhash.c new file mode 100644 index 0000000000..32b4200d54 --- /dev/null +++ b/lib/tdb2/test/run-tdb1-oldhash.c @@ -0,0 +1,40 @@ +#include "tdb2-source.h" +#include <ccan/tap/tap.h> +#include <stdlib.h> +#include <err.h> +#include "tdb1-logging.h" + +int main(int argc, char *argv[]) +{ + struct tdb1_context *tdb; + + plan_tests(8); + + /* Old format (with zeroes in the hash magic fields) should + * open with any hash (since we don't know what hash they used). */ + tdb = tdb1_open_ex("test/old-nohash-le.tdb1", 0, 0, O_RDWR, 0, + &taplogctx, NULL); + ok1(tdb); + ok1(tdb1_check(tdb, NULL, NULL) == 0); + tdb1_close(tdb); + + tdb = tdb1_open_ex("test/old-nohash-be.tdb1", 0, 0, O_RDWR, 0, + &taplogctx, NULL); + ok1(tdb); + ok1(tdb1_check(tdb, NULL, NULL) == 0); + tdb1_close(tdb); + + tdb = tdb1_open_ex("test/old-nohash-le.tdb1", 0, 0, O_RDWR, 0, + &taplogctx, tdb1_jenkins_hash); + ok1(tdb); + ok1(tdb1_check(tdb, NULL, NULL) == 0); + tdb1_close(tdb); + + tdb = tdb1_open_ex("test/old-nohash-be.tdb1", 0, 0, O_RDWR, 0, + &taplogctx, tdb1_jenkins_hash); + ok1(tdb); + ok1(tdb1_check(tdb, NULL, NULL) == 0); + tdb1_close(tdb); + + return exit_status(); +} diff --git a/lib/tdb2/test/run-tdb1-open-during-transaction.c b/lib/tdb2/test/run-tdb1-open-during-transaction.c new file mode 100644 index 0000000000..7b22320a73 --- /dev/null +++ b/lib/tdb2/test/run-tdb1-open-during-transaction.c @@ -0,0 +1,176 @@ +#include "config.h" +#include "tdb1-lock-tracking.h" +#include <unistd.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_lockcheck1 +#define ftruncate ftruncate_check + +#include "tdb2-source.h" +#include <ccan/tap/tap.h> +#include <stdlib.h> +#include <stdbool.h> +#include <stdarg.h> +#include <err.h> +#include "tdb1-external-agent.h" +#include "tdb1-logging.h" + +static struct agent *agent; +static bool opened; +static int errors = 0; +static bool clear_if_first; +#define TEST_DBNAME "run-open-during-transaction.tdb" + +#undef write +#undef pwrite +#undef fcntl +#undef ftruncate + +static bool is_same(const char *snapshot, const char *latest, off_t len) +{ + unsigned i; + + for (i = 0; i < len; i++) { + if (snapshot[i] != latest[i]) + return false; + } + return true; +} + +static bool compare_file(int fd, const char *snapshot, off_t snapshot_len) +{ + char *contents; + bool same; + + /* over-length read serves as length check. */ + contents = malloc(snapshot_len+1); + same = pread(fd, contents, snapshot_len+1, 0) == snapshot_len + && is_same(snapshot, contents, snapshot_len); + free(contents); + return same; +} + +static void check_file_intact(int fd) +{ + enum agent_return ret; + struct stat st; + char *contents; + + fstat(fd, &st); + contents = malloc(st.st_size); + if (pread(fd, contents, st.st_size, 0) != st.st_size) { + diag("Read fail"); + errors++; + return; + } + + /* Ask agent to open file. */ + ret = external_agent_operation1(agent, clear_if_first ? + OPEN_WITH_CLEAR_IF_FIRST : + OPEN, + TEST_DBNAME); + + /* It's OK to open it, but it must not have changed! */ + if (!compare_file(fd, contents, st.st_size)) { + diag("Agent changed file after opening %s", + agent_return_name1(ret)); + errors++; + } + + if (ret == SUCCESS) { + ret = external_agent_operation1(agent, CLOSE, NULL); + if (ret != SUCCESS) { + diag("Agent failed to close tdb: %s", + agent_return_name1(ret)); + errors++; + } + } else if (ret != WOULD_HAVE_BLOCKED) { + diag("Agent opening file gave %s", + agent_return_name1(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[] = { TDB1_DEFAULT, + TDB1_CLEAR_IF_FIRST, + TDB1_NOMMAP, + TDB1_CLEAR_IF_FIRST | TDB1_NOMMAP }; + int i; + struct tdb1_context *tdb; + TDB1_DATA key, data; + + plan_tests(20); + agent = prepare_external_agent1(); + if (!agent) + err(1, "preparing agent"); + + unlock_callback1 = after_unlock; + for (i = 0; i < sizeof(flags)/sizeof(flags[0]); i++) { + clear_if_first = (flags[i] & TDB1_CLEAR_IF_FIRST); + diag("Test with %s and %s\n", + clear_if_first ? "CLEAR" : "DEFAULT", + (flags[i] & TDB1_NOMMAP) ? "no mmap" : "mmap"); + unlink(TEST_DBNAME); + tdb = tdb1_open_ex(TEST_DBNAME, 1024, flags[i], + O_CREAT|O_TRUNC|O_RDWR, 0600, + &taplogctx, NULL); + ok1(tdb); + + opened = true; + ok1(tdb1_transaction_start(tdb) == 0); + key.dsize = strlen("hi"); + key.dptr = (void *)"hi"; + data.dptr = (void *)"world"; + data.dsize = strlen("world"); + + ok1(tdb1_store(tdb, key, data, TDB1_INSERT) == 0); + ok1(tdb1_transaction_commit(tdb) == 0); + ok(!errors, "We had %u open errors", errors); + + opened = false; + tdb1_close(tdb); + } + + return exit_status(); +} diff --git a/lib/tdb2/test/run-tdb1-readonly-check.c b/lib/tdb2/test/run-tdb1-readonly-check.c new file mode 100644 index 0000000000..2c06ca92ee --- /dev/null +++ b/lib/tdb2/test/run-tdb1-readonly-check.c @@ -0,0 +1,43 @@ +/* We should be able to tdb1_check a O_RDONLY tdb, and we were previously allowed + * to tdb1_check() inside a transaction (though that's paranoia!). */ +#include "tdb2-source.h" +#include <ccan/tap/tap.h> +#include <stdlib.h> +#include <err.h> +#include "tdb1-logging.h" + +int main(int argc, char *argv[]) +{ + struct tdb1_context *tdb; + TDB1_DATA key, data; + + plan_tests(11); + tdb = tdb1_open_ex("run-readonly-check.tdb", 1024, + TDB1_DEFAULT, + O_CREAT|O_TRUNC|O_RDWR, 0600, &taplogctx, NULL); + + ok1(tdb); + key.dsize = strlen("hi"); + key.dptr = (void *)"hi"; + data.dsize = strlen("world"); + data.dptr = (void *)"world"; + + ok1(tdb1_store(tdb, key, data, TDB1_INSERT) == 0); + ok1(tdb1_check(tdb, NULL, NULL) == 0); + + /* We are also allowed to do a check inside a transaction. */ + ok1(tdb1_transaction_start(tdb) == 0); + ok1(tdb1_check(tdb, NULL, NULL) == 0); + ok1(tdb1_close(tdb) == 0); + + tdb = tdb1_open_ex("run-readonly-check.tdb", 1024, + TDB1_DEFAULT, O_RDONLY, 0, &taplogctx, NULL); + + ok1(tdb); + ok1(tdb1_store(tdb, key, data, TDB1_MODIFY) == -1); + ok1(tdb1_error(tdb) == TDB1_ERR_RDONLY); + ok1(tdb1_check(tdb, NULL, NULL) == 0); + ok1(tdb1_close(tdb) == 0); + + return exit_status(); +} diff --git a/lib/tdb2/test/run-tdb1-rwlock-check.c b/lib/tdb2/test/run-tdb1-rwlock-check.c new file mode 100644 index 0000000000..5d438d3883 --- /dev/null +++ b/lib/tdb2/test/run-tdb1-rwlock-check.c @@ -0,0 +1,36 @@ +#include "tdb2-source.h" +#include <ccan/tap/tap.h> +#include <stdlib.h> +#include <err.h> + +static void log_fn(struct tdb1_context *tdb, enum tdb1_debug_level level, const char *fmt, ...) +{ + unsigned int *count = tdb1_get_logging_private(tdb); + if (strstr(fmt, "spinlocks")) + (*count)++; +} + +/* The code should barf on TDBs created with rwlocks. */ +int main(int argc, char *argv[]) +{ + struct tdb1_context *tdb; + unsigned int log_count; + struct tdb1_logging_context log_ctx = { log_fn, &log_count }; + + plan_tests(4); + + /* We should fail to open rwlock-using tdbs of either endian. */ + log_count = 0; + tdb = tdb1_open_ex("test/rwlock-le.tdb1", 0, 0, O_RDWR, 0, + &log_ctx, NULL); + ok1(!tdb); + ok1(log_count == 1); + + log_count = 0; + tdb = tdb1_open_ex("test/rwlock-be.tdb1", 0, 0, O_RDWR, 0, + &log_ctx, NULL); + ok1(!tdb); + ok1(log_count == 1); + + return exit_status(); +} diff --git a/lib/tdb2/test/run-tdb1-summary.c b/lib/tdb2/test/run-tdb1-summary.c new file mode 100644 index 0000000000..ccfc0958e1 --- /dev/null +++ b/lib/tdb2/test/run-tdb1-summary.c @@ -0,0 +1,54 @@ +#include "tdb2-source.h" +#include <ccan/tap/tap.h> +#include <stdlib.h> +#include <err.h> + +int main(int argc, char *argv[]) +{ + unsigned int i, j; + struct tdb1_context *tdb; + int flags[] = { TDB1_INTERNAL, TDB1_DEFAULT, TDB1_NOMMAP, + TDB1_INTERNAL|TDB1_CONVERT, TDB1_CONVERT, + TDB1_NOMMAP|TDB1_CONVERT }; + TDB1_DATA key = { (unsigned char *)&j, sizeof(j) }; + TDB1_DATA data = { (unsigned char *)&j, sizeof(j) }; + char *summary; + + plan_tests(sizeof(flags) / sizeof(flags[0]) * 14); + for (i = 0; i < sizeof(flags) / sizeof(flags[0]); i++) { + tdb = tdb1_open("run-summary.tdb", 131, flags[i], + O_RDWR|O_CREAT|O_TRUNC, 0600); + ok1(tdb); + if (!tdb) + continue; + + /* Put some stuff in there. */ + for (j = 0; j < 500; j++) { + /* Make sure padding varies to we get some graphs! */ + data.dsize = j % (sizeof(j) + 1); + if (tdb1_store(tdb, key, data, TDB1_REPLACE) != 0) + fail("Storing in tdb"); + } + + summary = tdb1_summary(tdb); + diag("%s", summary); + ok1(strstr(summary, "Size of file/data: ")); + ok1(strstr(summary, "Number of records: 500\n")); + ok1(strstr(summary, "Smallest/average/largest keys: 4/4/4\n")); + ok1(strstr(summary, "Smallest/average/largest data: 0/2/4\n")); + ok1(strstr(summary, "Smallest/average/largest padding: ")); + ok1(strstr(summary, "Number of dead records: 0\n")); + ok1(strstr(summary, "Number of free records: 1\n")); + ok1(strstr(summary, "Smallest/average/largest free records: ")); + ok1(strstr(summary, "Number of hash chains: 131\n")); + ok1(strstr(summary, "Smallest/average/largest hash chains: ")); + ok1(strstr(summary, "Number of uncoalesced records: 0\n")); + ok1(strstr(summary, "Smallest/average/largest uncoalesced runs: 0/0/0\n")); + ok1(strstr(summary, "Percentage keys/data/padding/free/dead/rechdrs&tailers/hashes: ")); + + free(summary); + tdb1_close(tdb); + } + + return exit_status(); +} diff --git a/lib/tdb2/test/run-tdb1-traverse-in-transaction.c b/lib/tdb2/test/run-tdb1-traverse-in-transaction.c new file mode 100644 index 0000000000..eb925be20c --- /dev/null +++ b/lib/tdb2/test/run-tdb1-traverse-in-transaction.c @@ -0,0 +1,80 @@ +#include "config.h" +#include "tdb1-lock-tracking.h" +#define fcntl fcntl_with_lockcheck1 +#include "tdb2-source.h" +#include <ccan/tap/tap.h> +#undef fcntl_with_lockcheck +#include <stdlib.h> +#include <stdbool.h> +#include <err.h> +#include "tdb1-external-agent.h" +#include "tdb1-logging.h" + +static struct agent *agent; + +static bool correct_key(TDB1_DATA key) +{ + return key.dsize == strlen("hi") + && memcmp(key.dptr, "hi", key.dsize) == 0; +} + +static bool correct_data(TDB1_DATA data) +{ + return data.dsize == strlen("world") + && memcmp(data.dptr, "world", data.dsize) == 0; +} + +static int traverse(struct tdb1_context *tdb, TDB1_DATA key, TDB1_DATA data, + void *p) +{ + ok1(correct_key(key)); + ok1(correct_data(data)); + return 0; +} + +int main(int argc, char *argv[]) +{ + struct tdb1_context *tdb; + TDB1_DATA key, data; + + plan_tests(13); + agent = prepare_external_agent1(); + if (!agent) + err(1, "preparing agent"); + + tdb = tdb1_open_ex("run-traverse-in-transaction.tdb", + 1024, TDB1_CLEAR_IF_FIRST, O_CREAT|O_TRUNC|O_RDWR, + 0600, &taplogctx, NULL); + ok1(tdb); + + key.dsize = strlen("hi"); + key.dptr = (void *)"hi"; + data.dptr = (void *)"world"; + data.dsize = strlen("world"); + + ok1(tdb1_store(tdb, key, data, TDB1_INSERT) == 0); + + ok1(external_agent_operation1(agent, OPEN, tdb1_name(tdb)) == SUCCESS); + + ok1(tdb1_transaction_start(tdb) == 0); + ok1(external_agent_operation1(agent, TRANSACTION_START, tdb1_name(tdb)) + == WOULD_HAVE_BLOCKED); + tdb1_traverse(tdb, traverse, NULL); + + /* That should *not* release the transaction lock! */ + ok1(external_agent_operation1(agent, TRANSACTION_START, tdb1_name(tdb)) + == WOULD_HAVE_BLOCKED); + tdb1_traverse_read(tdb, traverse, NULL); + + /* That should *not* release the transaction lock! */ + ok1(external_agent_operation1(agent, TRANSACTION_START, tdb1_name(tdb)) + == WOULD_HAVE_BLOCKED); + ok1(tdb1_transaction_commit(tdb) == 0); + /* Now we should be fine. */ + ok1(external_agent_operation1(agent, TRANSACTION_START, tdb1_name(tdb)) + == SUCCESS); + + tdb1_close(tdb); + + return exit_status(); +} diff --git a/lib/tdb2/test/run-tdb1-wronghash-fail.c b/lib/tdb2/test/run-tdb1-wronghash-fail.c new file mode 100644 index 0000000000..5a7e311a9c --- /dev/null +++ b/lib/tdb2/test/run-tdb1-wronghash-fail.c @@ -0,0 +1,111 @@ +#include "tdb2-source.h" +#include <ccan/tap/tap.h> +#include <stdlib.h> +#include <err.h> + +static void log_fn(struct tdb1_context *tdb, enum tdb1_debug_level level, const char *fmt, ...) +{ + unsigned int *count = tdb1_get_logging_private(tdb); + if (strstr(fmt, "hash")) + (*count)++; +} + +int main(int argc, char *argv[]) +{ + struct tdb1_context *tdb; + unsigned int log_count; + TDB1_DATA d; + struct tdb1_logging_context log_ctx = { log_fn, &log_count }; + + plan_tests(28); + + /* Create with default hash. */ + log_count = 0; + tdb = tdb1_open_ex("run-wronghash-fail.tdb", 0, 0, + O_CREAT|O_RDWR|O_TRUNC, 0600, &log_ctx, NULL); + ok1(tdb); + ok1(log_count == 0); + d.dptr = (void *)"Hello"; + d.dsize = 5; + ok1(tdb1_store(tdb, d, d, TDB1_INSERT) == 0); + tdb1_close(tdb); + + /* Fail to open with different hash. */ + tdb = tdb1_open_ex("run-wronghash-fail.tdb", 0, 0, O_RDWR, 0, + &log_ctx, tdb1_jenkins_hash); + ok1(!tdb); + ok1(log_count == 1); + + /* Create with different hash. */ + log_count = 0; + tdb = tdb1_open_ex("run-wronghash-fail.tdb", 0, 0, + O_CREAT|O_RDWR|O_TRUNC, + 0600, &log_ctx, tdb1_jenkins_hash); + ok1(tdb); + ok1(log_count == 0); + tdb1_close(tdb); + + /* Endian should be no problem. */ + log_count = 0; + tdb = tdb1_open_ex("test/jenkins-le-hash.tdb1", 0, 0, O_RDWR, 0, + &log_ctx, tdb1_old_hash); + ok1(!tdb); + ok1(log_count == 1); + + log_count = 0; + tdb = tdb1_open_ex("test/jenkins-be-hash.tdb1", 0, 0, O_RDWR, 0, + &log_ctx, tdb1_old_hash); + ok1(!tdb); + ok1(log_count == 1); + + log_count = 0; + /* Fail to open with old default hash. */ + tdb = tdb1_open_ex("run-wronghash-fail.tdb", 0, 0, O_RDWR, 0, + &log_ctx, tdb1_old_hash); + ok1(!tdb); + ok1(log_count == 1); + + log_count = 0; + tdb = tdb1_open_ex("test/jenkins-le-hash.tdb1", 0, 0, O_RDONLY, + 0, &log_ctx, tdb1_jenkins_hash); + ok1(tdb); + ok1(log_count == 0); + ok1(tdb1_check(tdb, NULL, NULL) == 0); + tdb1_close(tdb); + + log_count = 0; + tdb = tdb1_open_ex("test/jenkins-be-hash.tdb1", 0, 0, O_RDONLY, + 0, &log_ctx, tdb1_jenkins_hash); + ok1(tdb); + ok1(log_count == 0); + ok1(tdb1_check(tdb, NULL, NULL) == 0); + tdb1_close(tdb); + + /* It should open with jenkins hash if we don't specify. */ + log_count = 0; + tdb = tdb1_open_ex("test/jenkins-le-hash.tdb1", 0, 0, O_RDWR, 0, + &log_ctx, NULL); + ok1(tdb); + ok1(log_count == 0); + ok1(tdb1_check(tdb, NULL, NULL) == 0); + tdb1_close(tdb); + + log_count = 0; + tdb = tdb1_open_ex("test/jenkins-be-hash.tdb1", 0, 0, O_RDWR, 0, + &log_ctx, NULL); + ok1(tdb); + ok1(log_count == 0); + ok1(tdb1_check(tdb, NULL, NULL) == 0); + tdb1_close(tdb); + + log_count = 0; + tdb = tdb1_open_ex("run-wronghash-fail.tdb", 0, 0, O_RDONLY, + 0, &log_ctx, NULL); + ok1(tdb); + ok1(log_count == 0); + ok1(tdb1_check(tdb, NULL, NULL) == 0); + tdb1_close(tdb); + + + return exit_status(); +} diff --git a/lib/tdb2/test/run-tdb1-zero-append.c b/lib/tdb2/test/run-tdb1-zero-append.c new file mode 100644 index 0000000000..e79ab60a00 --- /dev/null +++ b/lib/tdb2/test/run-tdb1-zero-append.c @@ -0,0 +1,31 @@ +#include "tdb2-source.h" +#include <ccan/tap/tap.h> +#include <stdlib.h> +#include <err.h> +#include "tdb1-logging.h" + +int main(int argc, char *argv[]) +{ + struct tdb1_context *tdb; + TDB1_DATA key, data; + + plan_tests(4); + tdb = tdb1_open_ex(NULL, 1024, TDB1_INTERNAL, O_CREAT|O_TRUNC|O_RDWR, + 0600, &taplogctx, NULL); + ok1(tdb); + + /* Tickle bug on appending zero length buffer to zero length buffer. */ + key.dsize = strlen("hi"); + key.dptr = (void *)"hi"; + data.dptr = (void *)"world"; + data.dsize = 0; + + ok1(tdb1_append(tdb, key, data) == 0); + ok1(tdb1_append(tdb, key, data) == 0); + data = tdb1_fetch(tdb, key); + ok1(data.dsize == 0); + free(data.dptr); + tdb1_close(tdb); + + return exit_status(); +} diff --git a/lib/tdb2/test/run-tdb1.c b/lib/tdb2/test/run-tdb1.c new file mode 100644 index 0000000000..ebbfd77bb2 --- /dev/null +++ b/lib/tdb2/test/run-tdb1.c @@ -0,0 +1,40 @@ +#include "tdb2-source.h" +#include <ccan/tap/tap.h> +#include <stdlib.h> +#include <err.h> +#include "tdb1-logging.h" + +int main(int argc, char *argv[]) +{ + struct tdb1_context *tdb; + TDB1_DATA key, data; + + plan_tests(10); + tdb = tdb1_open_ex("run.tdb", 1024, TDB1_CLEAR_IF_FIRST, + O_CREAT|O_TRUNC|O_RDWR, 0600, &taplogctx, NULL); + + ok1(tdb); + key.dsize = strlen("hi"); + key.dptr = (void *)"hi"; + data.dsize = strlen("world"); + data.dptr = (void *)"world"; + + ok1(tdb1_store(tdb, key, data, TDB1_MODIFY) < 0); + ok1(tdb1_error(tdb) == TDB1_ERR_NOEXIST); + ok1(tdb1_store(tdb, key, data, TDB1_INSERT) == 0); + ok1(tdb1_store(tdb, key, data, TDB1_INSERT) < 0); + ok1(tdb1_error(tdb) == TDB1_ERR_EXISTS); + ok1(tdb1_store(tdb, key, data, TDB1_MODIFY) == 0); + + data = tdb1_fetch(tdb, key); + ok1(data.dsize == strlen("world")); + ok1(memcmp(data.dptr, "world", strlen("world")) == 0); + free(data.dptr); + + key.dsize++; + data = tdb1_fetch(tdb, key); + ok1(data.dptr == NULL); + tdb1_close(tdb); + + return exit_status(); +} diff --git a/lib/tdb2/test/rwlock-be.tdb1 b/lib/tdb2/test/rwlock-be.tdb1 Binary files differnew file mode 100644 index 0000000000..45b5f09a1b --- /dev/null +++ b/lib/tdb2/test/rwlock-be.tdb1 diff --git a/lib/tdb2/test/rwlock-le.tdb1 b/lib/tdb2/test/rwlock-le.tdb1 Binary files differnew file mode 100644 index 0000000000..45b5f09a1b --- /dev/null +++ b/lib/tdb2/test/rwlock-le.tdb1 diff --git a/lib/tdb2/test/tdb1-external-agent.c b/lib/tdb2/test/tdb1-external-agent.c new file mode 100644 index 0000000000..3f73ae3aaf --- /dev/null +++ b/lib/tdb2/test/tdb1-external-agent.c @@ -0,0 +1,196 @@ +#include "tdb1-external-agent.h" +#include "tdb1-lock-tracking.h" +#include "tdb1-logging.h" +#include <sys/types.h> +#include <sys/wait.h> +#include <unistd.h> +#include <err.h> +#include <fcntl.h> +#include <stdlib.h> +#include <limits.h> +#include <string.h> +#include <errno.h> +#include <ccan/tdb2/tdb1.h> +#include <ccan/tdb2/tdb1_private.h> +#include <ccan/tap/tap.h> +#include <stdio.h> +#include <stdarg.h> + +static struct tdb1_context *tdb; + +static enum agent_return do_operation(enum operation op, const char *name) +{ + TDB1_DATA k; + enum agent_return ret; + TDB1_DATA data; + + if (op != OPEN && op != OPEN_WITH_CLEAR_IF_FIRST && !tdb) { + diag("external: No tdb open!"); + return OTHER_FAILURE; + } + + k.dptr = (void *)name; + k.dsize = strlen(name); + + locking_would_block1 = 0; + switch (op) { + case OPEN: + if (tdb) { + diag("Already have tdb %s open", tdb1_name(tdb)); + return OTHER_FAILURE; + } + tdb = tdb1_open_ex(name, 0, TDB1_DEFAULT, O_RDWR, 0, + &taplogctx, NULL); + if (!tdb) { + if (!locking_would_block1) + diag("Opening tdb gave %s", strerror(errno)); + ret = OTHER_FAILURE; + } else + ret = SUCCESS; + break; + case OPEN_WITH_CLEAR_IF_FIRST: + if (tdb) + return OTHER_FAILURE; + tdb = tdb1_open_ex(name, 0, TDB1_CLEAR_IF_FIRST, O_RDWR, 0, + &taplogctx, NULL); + ret = tdb ? SUCCESS : OTHER_FAILURE; + break; + case TRANSACTION_START: + ret = tdb1_transaction_start(tdb) == 0 ? SUCCESS : OTHER_FAILURE; + break; + case FETCH: + data = tdb1_fetch(tdb, k); + if (data.dptr == NULL) { + if (tdb->ecode == TDB1_ERR_NOEXIST) + ret = FAILED; + else + ret = OTHER_FAILURE; + } else if (data.dsize != k.dsize + || memcmp(data.dptr, k.dptr, k.dsize) != 0) { + ret = OTHER_FAILURE; + } else { + ret = SUCCESS; + } + free(data.dptr); + break; + case STORE: + ret = tdb1_store(tdb, k, k, 0) == 0 ? SUCCESS : OTHER_FAILURE; + break; + case TRANSACTION_COMMIT: + ret = tdb1_transaction_commit(tdb)==0 ? SUCCESS : OTHER_FAILURE; + break; + case CHECK: + ret = tdb1_check(tdb, NULL, NULL) == 0 ? SUCCESS : OTHER_FAILURE; + break; + case NEEDS_RECOVERY: + ret = tdb1_needs_recovery(tdb) ? SUCCESS : FAILED; + break; + case CLOSE: + ret = tdb1_close(tdb) == 0 ? SUCCESS : OTHER_FAILURE; + tdb = NULL; + break; + default: + ret = OTHER_FAILURE; + } + + if (locking_would_block1) + ret = WOULD_HAVE_BLOCKED; + + return ret; +} + +struct agent { + int cmdfd, responsefd; +}; + +/* Do this before doing any tdb stuff. Return handle, or NULL. */ +struct agent *prepare_external_agent1(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_locks1 = 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"); + } + exit(0); +} + +/* Ask the external agent to try to do an operation. */ +enum agent_return external_agent_operation1(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_name1(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_name1(enum operation op) +{ + switch (op) { + case OPEN: return "OPEN"; + case OPEN_WITH_CLEAR_IF_FIRST: return "OPEN_WITH_CLEAR_IF_FIRST"; + case TRANSACTION_START: return "TRANSACTION_START"; + case FETCH: return "FETCH"; + case STORE: return "STORE"; + case TRANSACTION_COMMIT: return "TRANSACTION_COMMIT"; + case CHECK: return "CHECK"; + case NEEDS_RECOVERY: return "NEEDS_RECOVERY"; + case CLOSE: return "CLOSE"; + } + return "**INVALID**"; +} diff --git a/lib/tdb2/test/tdb1-external-agent.h b/lib/tdb2/test/tdb1-external-agent.h new file mode 100644 index 0000000000..8b0c1548a7 --- /dev/null +++ b/lib/tdb2/test/tdb1-external-agent.h @@ -0,0 +1,41 @@ +#ifndef TDB_TEST_EXTERNAL_AGENT_H +#define TDB_TEST_EXTERNAL_AGENT_H + +/* For locking tests, we need a different process to try things at + * various times. */ +enum operation { + OPEN, + OPEN_WITH_CLEAR_IF_FIRST, + TRANSACTION_START, + FETCH, + STORE, + TRANSACTION_COMMIT, + CHECK, + NEEDS_RECOVERY, + CLOSE, +}; + +/* Do this before doing any tdb stuff. Return handle, or -1. */ +struct agent *prepare_external_agent1(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 == tdb name for OPEN/OPEN_WITH_CLEAR_IF_FIRST, + * record name for FETCH/STORE (store stores name as data too) + */ +enum agent_return external_agent_operation1(struct agent *handle, + enum operation op, + const char *name); + +/* Mapping enum -> string. */ +const char *agent_return_name1(enum agent_return ret); +const char *operation_name1(enum operation op); + +#endif /* TDB_TEST_EXTERNAL_AGENT_H */ diff --git a/lib/tdb2/test/tdb1-lock-tracking.c b/lib/tdb2/test/tdb1-lock-tracking.c new file mode 100644 index 0000000000..197b1f0706 --- /dev/null +++ b/lib/tdb2/test/tdb1-lock-tracking.c @@ -0,0 +1,146 @@ +/* We save the locks so we can reaquire them. */ +#include <ccan/tdb2/tdb1_private.h> +#include <unistd.h> +#include <fcntl.h> +#include <stdarg.h> +#include <stdlib.h> +#include <ccan/tap/tap.h> +#include "tdb1-lock-tracking.h" + +struct lock { + struct lock *next; + unsigned int off; + unsigned int len; + int type; +}; +static struct lock *locks; +int locking_errors1 = 0; +bool suppress_lockcheck1 = false; +bool nonblocking_locks1; +int locking_would_block1 = 0; +void (*unlock_callback1)(int fd); + +int fcntl_with_lockcheck1(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_locks1) { + 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_block1++; + } + + 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_lockcheck1) { + diag("Unknown unlock %u@%u - %i", + (int)fl->l_len, (int)fl->l_start, ret); + locking_errors1++; + } + } 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; + + /* tdb_allrecord_lock does this, handle adjacent: */ + if (fl->l_start == i_end && fl->l_type == i->type) { + if (ret == 0) { + i->len = fl->l_len + ? i->len + fl->l_len + : 0; + } + goto done; + } + } + if (i) { + /* Special case: upgrade of allrecord lock. */ + if (i->type == F_RDLCK && fl->l_type == F_WRLCK + && i->off == TDB1_FREELIST_TOP + && fl->l_start == TDB1_FREELIST_TOP + && i->len == 0 + && fl->l_len == 0) { + if (ret == 0) + i->type = F_WRLCK; + goto done; + } + if (!suppress_lockcheck1) { + 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_errors1++; + } + } + + 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_callback1) + unlock_callback1(fd); + return ret; +} + +unsigned int forget_locking1(void) +{ + unsigned int num = 0; + while (locks) { + struct lock *next = locks->next; + free(locks); + locks = next; + num++; + } + return num; +} diff --git a/lib/tdb2/test/tdb1-lock-tracking.h b/lib/tdb2/test/tdb1-lock-tracking.h new file mode 100644 index 0000000000..cb8c2f1278 --- /dev/null +++ b/lib/tdb2/test/tdb1-lock-tracking.h @@ -0,0 +1,26 @@ +#ifndef TDB1_LOCK_TRACKING_H +#define TDB1_LOCK_TRACKING_H +#include <ccan/tdb2/private.h> +#include <stdbool.h> + +/* Set this if you want a callback after fnctl unlock. */ +extern void (*unlock_callback1)(int fd); + +/* Replacement fcntl. */ +int fcntl_with_lockcheck1(int fd, int cmd, ... /* arg */ ); + +/* Discard locking info: returns number of locks outstanding. */ +unsigned int forget_locking1(void); + +/* Number of errors in locking. */ +extern int locking_errors1; + +/* Suppress lock checking. */ +extern bool suppress_lockcheck1; + +/* Make all locks non-blocking. */ +extern bool nonblocking_locks1; + +/* Number of times we failed a lock because we made it non-blocking. */ +extern int locking_would_block1; +#endif /* LOCK_TRACKING_H */ diff --git a/lib/tdb2/test/tdb1-logging.c b/lib/tdb2/test/tdb1-logging.c new file mode 100644 index 0000000000..43ce07b27c --- /dev/null +++ b/lib/tdb2/test/tdb1-logging.c @@ -0,0 +1,30 @@ +#include "tdb1-logging.h" +#include <ccan/tap/tap.h> +#include <stdarg.h> +#include <stdlib.h> +#include <string.h> +#include <stdio.h> + +/* Turn log messages into tap diag messages. */ +static void taplog(struct tdb1_context *tdb, + enum tdb1_debug_level level, + const char *fmt, ...) +{ + va_list ap; + char line[200]; + + if (suppress_logging) + return; + + va_start(ap, fmt); + vsprintf(line, fmt, ap); + va_end(ap); + + /* Strip trailing \n: diag adds it. */ + if (line[0] && line[strlen(line)-1] == '\n') + diag("%s%.*s", log_prefix, (unsigned)strlen(line)-1, line); + else + diag("%s%s", log_prefix, line); +} + +struct tdb1_logging_context taplogctx = { taplog, NULL }; diff --git a/lib/tdb2/test/tdb1-logging.h b/lib/tdb2/test/tdb1-logging.h new file mode 100644 index 0000000000..128ec7f81d --- /dev/null +++ b/lib/tdb2/test/tdb1-logging.h @@ -0,0 +1,10 @@ +#ifndef TDB_TEST_LOGGING_H +#define TDB_TEST_LOGGING_H +#include <ccan/tdb2/tdb1.h> +#include <stdbool.h> + +extern bool suppress_logging; +extern const char *log_prefix; +extern struct tdb1_logging_context taplogctx; + +#endif /* TDB_TEST_LOGGING_H */ diff --git a/lib/tdb2/test/tdb1.corrupt b/lib/tdb2/test/tdb1.corrupt Binary files differnew file mode 100644 index 0000000000..83d6677454 --- /dev/null +++ b/lib/tdb2/test/tdb1.corrupt diff --git a/lib/tdb2/test/tdb2-source.h b/lib/tdb2/test/tdb2-source.h index 56765c8cd7..f289aeb009 100644 --- a/lib/tdb2/test/tdb2-source.h +++ b/lib/tdb2/test/tdb2-source.h @@ -1,3 +1,4 @@ +#include "config.h" #include <ccan/tdb2/check.c> #include <ccan/tdb2/free.c> #include <ccan/tdb2/hash.c> |