diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-05 17:46:09 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-05 17:46:09 +0000 |
commit | 48ae1a0fb55b89de931512ffe4afd2c629ae828a (patch) | |
tree | a464b4d09295e0d1cdc290e42dc8cc97c4e044fe /test | |
parent | Initial commit. (diff) | |
download | tdb-48ae1a0fb55b89de931512ffe4afd2c629ae828a.tar.xz tdb-48ae1a0fb55b89de931512ffe4afd2c629ae828a.zip |
Adding upstream version 1.4.8.upstream/1.4.8upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'test')
56 files changed, 5216 insertions, 0 deletions
diff --git a/test/circular_chain.tdb b/test/circular_chain.tdb Binary files differnew file mode 100644 index 0000000..1e143d3 --- /dev/null +++ b/test/circular_chain.tdb diff --git a/test/circular_freelist.tdb b/test/circular_freelist.tdb Binary files differnew file mode 100644 index 0000000..f1c85e5 --- /dev/null +++ b/test/circular_freelist.tdb diff --git a/test/external-agent.c b/test/external-agent.c new file mode 100644 index 0000000..3c59c06 --- /dev/null +++ b/test/external-agent.c @@ -0,0 +1,224 @@ +#include "external-agent.h" +#include "lock-tracking.h" +#include "logging.h" +#include <sys/types.h> +#include <sys/wait.h> +#include <unistd.h> +#include <fcntl.h> +#include <stdlib.h> +#include <limits.h> +#include <string.h> +#include <errno.h> +#include "../common/tdb_private.h" +#include "tap-interface.h" +#include <stdio.h> +#include <stdarg.h> + +static struct tdb_context *tdb; + +static enum agent_return do_operation(enum operation op, const char *name) +{ + TDB_DATA k; + enum agent_return ret; + TDB_DATA data; + + if (op != OPEN && op != OPEN_WITH_CLEAR_IF_FIRST && !tdb) { + diag("external: No tdb open!"); + return OTHER_FAILURE; + } + + k.dptr = discard_const_p(uint8_t, name); + k.dsize = strlen(name); + + locking_would_block = 0; + switch (op) { + case OPEN: + if (tdb) { + diag("Already have tdb %s open", tdb_name(tdb)); + return OTHER_FAILURE; + } + tdb = tdb_open_ex(name, 0, TDB_DEFAULT, O_RDWR, 0, + &taplogctx, NULL); + if (!tdb) { + if (!locking_would_block) + diag("Opening tdb gave %s", strerror(errno)); + ret = OTHER_FAILURE; + } else + ret = SUCCESS; + break; + case OPEN_WITH_CLEAR_IF_FIRST: + if (tdb) + return OTHER_FAILURE; + tdb = tdb_open_ex(name, 0, TDB_CLEAR_IF_FIRST, O_RDWR, 0, + &taplogctx, NULL); + ret = tdb ? SUCCESS : OTHER_FAILURE; + break; + case TRANSACTION_START: + ret = tdb_transaction_start(tdb) == 0 ? SUCCESS : OTHER_FAILURE; + break; + case FETCH: + data = tdb_fetch(tdb, k); + if (data.dptr == NULL) { + if (tdb_error(tdb) == TDB_ERR_NOEXIST) + ret = FAILED; + else + ret = OTHER_FAILURE; + } else if (data.dsize != k.dsize + || memcmp(data.dptr, k.dptr, k.dsize) != 0) { + ret = OTHER_FAILURE; + } else { + ret = SUCCESS; + } + free(data.dptr); + break; + case STORE: + ret = tdb_store(tdb, k, k, 0) == 0 ? SUCCESS : OTHER_FAILURE; + break; + case TRANSACTION_COMMIT: + ret = tdb_transaction_commit(tdb)==0 ? SUCCESS : OTHER_FAILURE; + break; + case CHECK: + ret = tdb_check(tdb, NULL, NULL) == 0 ? SUCCESS : OTHER_FAILURE; + break; + case NEEDS_RECOVERY: + ret = tdb_needs_recovery(tdb) ? SUCCESS : FAILED; + break; + case CLOSE: + ret = tdb_close(tdb) == 0 ? SUCCESS : OTHER_FAILURE; + tdb = NULL; + break; + case PING: + ret = SUCCESS; + break; + case UNMAP: + ret = tdb_munmap(tdb) == 0 ? SUCCESS : OTHER_FAILURE; + if (ret == SUCCESS) { + tdb->flags |= TDB_NOMMAP; + } + break; + default: + ret = OTHER_FAILURE; + } + + if (locking_would_block) + ret = WOULD_HAVE_BLOCKED; + + return ret; +} + +struct agent { + int cmdfd, responsefd; + pid_t pid; +}; + +/* Do this before doing any tdb stuff. Return handle, or NULL. */ +struct agent *prepare_external_agent(void) +{ + int ret; + int command[2], response[2]; + char name[1+PATH_MAX]; + struct agent *agent = malloc(sizeof(*agent)); + + if (pipe(command) != 0 || pipe(response) != 0) { + fprintf(stderr, "pipe failed: %s\n", strerror(errno)); + exit(1); + } + + agent->pid = fork(); + if (agent->pid < 0) { + fprintf(stderr, "fork failed: %s\n", strerror(errno)); + exit(1); + } + + if (agent->pid != 0) { + close(command[0]); + close(response[1]); + agent->cmdfd = command[1]; + agent->responsefd = response[0]; + return agent; + } + + close(command[1]); + close(response[0]); + + /* We want to fail, not block. */ + nonblocking_locks = true; + log_prefix = "external: "; + while ((ret = read(command[0], name, sizeof(name))) > 0) { + enum agent_return result; + + result = do_operation(name[0], name+1); + if (write(response[1], &result, sizeof(result)) + != sizeof(result)) + abort(); + } + exit(0); +} + +void shutdown_agent(struct agent *agent) +{ + pid_t p; + + close(agent->cmdfd); + close(agent->responsefd); + p = waitpid(agent->pid, NULL, WNOHANG); + if (p == 0) { + kill(agent->pid, SIGKILL); + } + waitpid(agent->pid, NULL, 0); + free(agent); +} + +/* Ask the external agent to try to do an operation. */ +enum agent_return external_agent_operation(struct agent *agent, + enum operation op, + const char *name) +{ + enum agent_return res; + unsigned int len; + char *string; + + if (!name) + name = ""; + len = 1 + strlen(name) + 1; + string = malloc(len); + + string[0] = op; + strncpy(string+1, name, len - 1); + string[len-1] = '\0'; + + if (write(agent->cmdfd, string, len) != len + || read(agent->responsefd, &res, sizeof(res)) != sizeof(res)) + res = AGENT_DIED; + + free(string); + return res; +} + +const char *agent_return_name(enum agent_return ret) +{ + return ret == SUCCESS ? "SUCCESS" + : ret == WOULD_HAVE_BLOCKED ? "WOULD_HAVE_BLOCKED" + : ret == AGENT_DIED ? "AGENT_DIED" + : ret == FAILED ? "FAILED" + : ret == OTHER_FAILURE ? "OTHER_FAILURE" + : "**INVALID**"; +} + +const char *operation_name(enum operation op) +{ + switch (op) { + case OPEN: return "OPEN"; + case OPEN_WITH_CLEAR_IF_FIRST: return "OPEN_WITH_CLEAR_IF_FIRST"; + case TRANSACTION_START: return "TRANSACTION_START"; + case FETCH: return "FETCH"; + case STORE: return "STORE"; + case TRANSACTION_COMMIT: return "TRANSACTION_COMMIT"; + case CHECK: return "CHECK"; + case NEEDS_RECOVERY: return "NEEDS_RECOVERY"; + case CLOSE: return "CLOSE"; + case PING: return "PING"; + case UNMAP: return "UNMAP"; + } + return "**INVALID**"; +} diff --git a/test/external-agent.h b/test/external-agent.h new file mode 100644 index 0000000..de9d0ac --- /dev/null +++ b/test/external-agent.h @@ -0,0 +1,44 @@ +#ifndef TDB_TEST_EXTERNAL_AGENT_H +#define TDB_TEST_EXTERNAL_AGENT_H + +/* For locking tests, we need a different process to try things at + * various times. */ +enum operation { + OPEN, + OPEN_WITH_CLEAR_IF_FIRST, + TRANSACTION_START, + FETCH, + STORE, + TRANSACTION_COMMIT, + CHECK, + NEEDS_RECOVERY, + CLOSE, + PING, + UNMAP, +}; + +/* Do this before doing any tdb stuff. Return handle, or -1. */ +struct agent *prepare_external_agent(void); +void shutdown_agent(struct agent *agent); + +enum agent_return { + SUCCESS, + WOULD_HAVE_BLOCKED, + AGENT_DIED, + FAILED, /* For fetch, or NEEDS_RECOVERY */ + OTHER_FAILURE, +}; + +/* Ask the external agent to try to do an operation. + * name == tdb name for OPEN/OPEN_WITH_CLEAR_IF_FIRST, + * record name for FETCH/STORE (store stores name as data too) + */ +enum agent_return external_agent_operation(struct agent *handle, + enum operation op, + const char *name); + +/* Mapping enum -> string. */ +const char *agent_return_name(enum agent_return ret); +const char *operation_name(enum operation op); + +#endif /* TDB_TEST_EXTERNAL_AGENT_H */ diff --git a/test/jenkins-be-hash.tdb b/test/jenkins-be-hash.tdb Binary files differnew file mode 100644 index 0000000..b652840 --- /dev/null +++ b/test/jenkins-be-hash.tdb diff --git a/test/jenkins-le-hash.tdb b/test/jenkins-le-hash.tdb Binary files differnew file mode 100644 index 0000000..007e0a3 --- /dev/null +++ b/test/jenkins-le-hash.tdb diff --git a/test/lock-tracking.c b/test/lock-tracking.c new file mode 100644 index 0000000..b2f092c --- /dev/null +++ b/test/lock-tracking.c @@ -0,0 +1,157 @@ +/* We save the locks so we can reaquire them. */ +#include "../common/tdb_private.h" +#include <unistd.h> +#include <fcntl.h> +#include <stdarg.h> +#include <stdlib.h> +#include "tap-interface.h" +#include "lock-tracking.h" + +struct testlock { + struct testlock *next; + unsigned int off; + unsigned int len; + int type; +}; +static struct testlock *testlocks; +int locking_errors = 0; +bool suppress_lockcheck = false; +bool nonblocking_locks; +int locking_would_block = 0; +void (*unlock_callback)(int fd); + +int fcntl_with_lockcheck(int fd, int cmd, ... /* arg */ ) +{ + va_list ap; + int ret, arg3; + struct flock *fl; + bool may_block = false; + + if (cmd != F_SETLK && cmd != F_SETLKW) { + /* This may be totally bogus, but we don't know in general. */ + va_start(ap, cmd); + arg3 = va_arg(ap, int); + va_end(ap); + + return fcntl(fd, cmd, arg3); + } + + va_start(ap, cmd); + fl = va_arg(ap, struct flock *); + va_end(ap); + + if (cmd == F_SETLKW && nonblocking_locks) { + cmd = F_SETLK; + may_block = true; + } + ret = fcntl(fd, cmd, fl); + + /* Detect when we failed, but might have been OK if we waited. */ + if (may_block && ret == -1 && (errno == EAGAIN || errno == EACCES)) { + locking_would_block++; + } + + if (fl->l_type == F_UNLCK) { + struct testlock **l; + struct testlock *old = NULL; + + for (l = &testlocks; *l; l = &(*l)->next) { + if ((*l)->off == fl->l_start + && (*l)->len == fl->l_len) { + if (ret == 0) { + old = *l; + *l = (*l)->next; + free(old); + } + break; + } + if (((*l)->off == fl->l_start) + && ((*l)->len == 0) + && (ret == 0)) { + /* + * Remove a piece from the start of the + * allrecord_lock + */ + old = *l; + (*l)->off += fl->l_len; + break; + } + } + if (!old && !suppress_lockcheck) { + diag("Unknown unlock %u@%u - %i", + (int)fl->l_len, (int)fl->l_start, ret); + locking_errors++; + } + } else { + struct testlock *new, *i; + unsigned int fl_end = fl->l_start + fl->l_len; + if (fl->l_len == 0) + fl_end = (unsigned int)-1; + + /* Check for overlaps: we shouldn't do this. */ + for (i = testlocks; i; i = i->next) { + unsigned int i_end = i->off + i->len; + if (i->len == 0) + i_end = (unsigned int)-1; + + if (fl->l_start >= i->off && fl->l_start < i_end) + break; + if (fl_end >= i->off && fl_end < i_end) + break; + + /* tdb_allrecord_lock does this, handle adjacent: */ + if (fl->l_start == i_end && fl->l_type == i->type) { + if (ret == 0) { + i->len = fl->l_len + ? i->len + fl->l_len + : 0; + } + goto done; + } + } + if (i) { + /* Special case: upgrade of allrecord lock. */ + if (i->type == F_RDLCK && fl->l_type == F_WRLCK + && i->off == FREELIST_TOP + && fl->l_start == FREELIST_TOP + && i->len == 0 + && fl->l_len == 0) { + if (ret == 0) + i->type = F_WRLCK; + goto done; + } + if (!suppress_lockcheck) { + diag("%s testlock %u@%u overlaps %u@%u", + fl->l_type == F_WRLCK ? "write" : "read", + (int)fl->l_len, (int)fl->l_start, + i->len, (int)i->off); + locking_errors++; + } + } + + if (ret == 0) { + new = malloc(sizeof *new); + new->off = fl->l_start; + new->len = fl->l_len; + new->type = fl->l_type; + new->next = testlocks; + testlocks = new; + } + } +done: + if (ret == 0 && fl->l_type == F_UNLCK && unlock_callback) + unlock_callback(fd); + return ret; +} + +unsigned int forget_locking(void) +{ + unsigned int num = 0; + while (testlocks) { + struct testlock *next = testlocks->next; + free(testlocks); + testlocks = next; + num++; + } + return num; +} diff --git a/test/lock-tracking.h b/test/lock-tracking.h new file mode 100644 index 0000000..f2c9c44 --- /dev/null +++ b/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/test/logging.c b/test/logging.c new file mode 100644 index 0000000..dfab486 --- /dev/null +++ b/test/logging.c @@ -0,0 +1,33 @@ +#include "logging.h" +#include "tap-interface.h" +#include <stdarg.h> +#include <stdlib.h> +#include <string.h> +#include <stdio.h> + +bool suppress_logging = false; +const char *log_prefix = ""; + +/* Turn log messages into tap diag messages. */ +static void taplog(struct tdb_context *tdb, + enum tdb_debug_level level, + const char *fmt, ...) +{ + va_list ap; + char line[200]; + + if (suppress_logging) + return; + + va_start(ap, fmt); + vsprintf(line, fmt, ap); + va_end(ap); + + /* Strip trailing \n: diag adds it. */ + if (line[0] && line[strlen(line)-1] == '\n') + diag("%s%.*s", log_prefix, (unsigned)strlen(line)-1, line); + else + diag("%s%s", log_prefix, line); +} + +struct tdb_logging_context taplogctx = { taplog, NULL }; diff --git a/test/logging.h b/test/logging.h new file mode 100644 index 0000000..89e77b2 --- /dev/null +++ b/test/logging.h @@ -0,0 +1,11 @@ +#ifndef TDB_TEST_LOGGING_H +#define TDB_TEST_LOGGING_H +#include "replace.h" +#include "../include/tdb.h" +#include <stdbool.h> + +extern bool suppress_logging; +extern const char *log_prefix; +extern struct tdb_logging_context taplogctx; + +#endif /* TDB_TEST_LOGGING_H */ diff --git a/test/old-nohash-be.tdb b/test/old-nohash-be.tdb Binary files differnew file mode 100644 index 0000000..1c49116 --- /dev/null +++ b/test/old-nohash-be.tdb diff --git a/test/old-nohash-le.tdb b/test/old-nohash-le.tdb Binary files differnew file mode 100644 index 0000000..0655072 --- /dev/null +++ b/test/old-nohash-le.tdb diff --git a/test/run-3G-file.c b/test/run-3G-file.c new file mode 100644 index 0000000..79e291b --- /dev/null +++ b/test/run-3G-file.c @@ -0,0 +1,145 @@ +#include "../common/tdb_private.h" +#include "../common/io.c" +#include "../common/tdb.c" +#include "../common/lock.c" +#include "../common/freelist.c" +#include "../common/traverse.c" +#include "../common/transaction.c" +#include "../common/error.c" +#include "../common/open.c" +#include "../common/check.c" +#include "../common/hash.c" +#include "../common/mutex.c" +#include "tap-interface.h" +#include <stdlib.h> +#include "logging.h" + +static int tdb_expand_file_sparse(struct tdb_context *tdb, + tdb_off_t size, + tdb_off_t addition) +{ + if (tdb->read_only || tdb->traverse_read) { + tdb->ecode = TDB_ERR_RDONLY; + return -1; + } + + if (tdb_ftruncate(tdb, size+addition) == -1) { + char b = 0; + ssize_t written = tdb_pwrite(tdb, &b, 1, (size+addition) - 1); + if (written == 0) { + /* try once more, potentially revealing errno */ + written = tdb_pwrite(tdb, &b, 1, (size+addition) - 1); + } + if (written == 0) { + /* again - give up, guessing errno */ + errno = ENOSPC; + } + if (written != 1) { + TDB_LOG((tdb, TDB_DEBUG_FATAL, "expand_file to %d failed (%s)\n", + size+addition, strerror(errno))); + return -1; + } + } + + return 0; +} + +static const struct tdb_methods large_io_methods = { + tdb_read, + tdb_write, + tdb_next_hash_chain, + tdb_notrans_oob, + tdb_expand_file_sparse +}; + +static int test_traverse(struct tdb_context *tdb, TDB_DATA key, TDB_DATA data, + void *_data) +{ + TDB_DATA *expect = _data; + ok1(key.dsize == strlen("hi")); + ok1(memcmp(key.dptr, "hi", strlen("hi")) == 0); + ok1(data.dsize == expect->dsize); + ok1(memcmp(data.dptr, expect->dptr, data.dsize) == 0); + return 0; +} + +int main(int argc, char *argv[]) +{ + struct tdb_context *tdb; + TDB_DATA key, orig_data, data; + uint32_t hashval; + tdb_off_t rec_ptr; + struct tdb_record rec; + int ret; + + plan_tests(24); + tdb = tdb_open_ex("run-36-file.tdb", 1024, TDB_CLEAR_IF_FIRST, + O_CREAT|O_TRUNC|O_RDWR, 0600, &taplogctx, NULL); + + ok1(tdb); + tdb->methods = &large_io_methods; + + key.dsize = strlen("hi"); + key.dptr = discard_const_p(uint8_t, "hi"); + orig_data.dsize = strlen("world"); + orig_data.dptr = discard_const_p(uint8_t, "world"); + + /* Enlarge the file (internally multiplies by 2). */ + ret = tdb_expand(tdb, 1500000000); +#ifdef HAVE_INCOHERENT_MMAP + /* This can fail due to mmap failure on 32 bit systems. */ + if (ret == -1) { + /* These should now fail. */ + ok1(tdb_store(tdb, key, orig_data, TDB_INSERT) == -1); + data = tdb_fetch(tdb, key); + ok1(data.dptr == NULL); + ok1(tdb_traverse(tdb, test_traverse, &orig_data) == -1); + ok1(tdb_delete(tdb, key) == -1); + ok1(tdb_traverse(tdb, test_traverse, NULL) == -1); + /* Skip the rest... */ + for (ret = 0; ret < 24 - 6; ret++) + ok1(1); + tdb_close(tdb); + return exit_status(); + } +#endif + ok1(ret == 0); + + /* Put an entry in, and check it. */ + ok1(tdb_store(tdb, key, orig_data, TDB_INSERT) == 0); + + data = tdb_fetch(tdb, key); + ok1(data.dsize == strlen("world")); + ok1(memcmp(data.dptr, "world", strlen("world")) == 0); + free(data.dptr); + + /* That currently fills at the end, make sure that's true. */ + hashval = tdb->hash_fn(&key); + rec_ptr = tdb_find_lock_hash(tdb, key, hashval, F_RDLCK, &rec); + ok1(rec_ptr); + ok1(rec_ptr > 2U*1024*1024*1024); + tdb_unlock(tdb, BUCKET(rec.full_hash), F_RDLCK); + + /* Traverse must work. */ + ok1(tdb_traverse(tdb, test_traverse, &orig_data) == 1); + + /* Delete should work. */ + ok1(tdb_delete(tdb, key) == 0); + + ok1(tdb_traverse(tdb, test_traverse, NULL) == 0); + + /* Transactions should work. */ + ok1(tdb_transaction_start(tdb) == 0); + ok1(tdb_store(tdb, key, orig_data, TDB_INSERT) == 0); + + data = tdb_fetch(tdb, key); + ok1(data.dsize == strlen("world")); + ok1(memcmp(data.dptr, "world", strlen("world")) == 0); + free(data.dptr); + ok1(tdb_transaction_commit(tdb) == 0); + + ok1(tdb_traverse(tdb, test_traverse, &orig_data) == 1); + tdb_close(tdb); + + return exit_status(); +} diff --git a/test/run-allrecord-traverse-deadlock.c b/test/run-allrecord-traverse-deadlock.c new file mode 100644 index 0000000..2c58206 --- /dev/null +++ b/test/run-allrecord-traverse-deadlock.c @@ -0,0 +1,203 @@ +#include "../common/tdb_private.h" +#include "../common/io.c" +#include "../common/tdb.c" +#include "../common/lock.c" +#include "../common/freelist.c" +#include "../common/traverse.c" +#include "../common/transaction.c" +#include "../common/error.c" +#include "../common/open.c" +#include "../common/check.c" +#include "../common/hash.c" +#include "../common/mutex.c" +#include "tap-interface.h" +#include <stdlib.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <stdarg.h> +#include "logging.h" + +static void do_allrecord_lock(const char *name, int tdb_flags, int up, + int down) +{ + struct tdb_context *tdb; + int ret; + ssize_t nread, nwritten; + char c = 0; + + tdb = tdb_open_ex(name, 3, tdb_flags, + O_RDWR|O_CREAT, 0755, &taplogctx, NULL); + ok(tdb, "tdb_open_ex should succeed"); + + ret = tdb_lockall(tdb); + ok(ret == 0, "tdb_lockall should succeed"); + + nwritten = write(up, &c, sizeof(c)); + ok(nwritten == sizeof(c), "write should succeed"); + + nread = read(down, &c, sizeof(c)); + ok(nread == sizeof(c), "read should succeed"); + + ret = tdb_traverse(tdb, NULL, NULL); + ok(ret == -1, "do_allrecord_lock: traverse should fail"); + + nwritten = write(up, &c, sizeof(c)); + ok(nwritten == sizeof(c), "write should succeed"); + + exit(0); +} + +static void do_traverse(const char *name, int tdb_flags, int up, int down) +{ + struct tdb_context *tdb; + int ret; + ssize_t nread, nwritten; + char c = 0; + + tdb = tdb_open_ex(name, 3, tdb_flags, + O_RDWR|O_CREAT, 0755, &taplogctx, NULL); + ok(tdb, "tdb_open_ex should succeed"); + + ret = tdb_traverse(tdb, NULL, NULL); + ok(ret == 1, "do_traverse: tdb_traverse should return 1 record"); + + nwritten = write(up, &c, sizeof(c)); + ok(nwritten == sizeof(c), "write should succeed"); + + nread = read(down, &c, sizeof(c)); + ok(nread == sizeof(c), "read should succeed"); + + exit(0); +} + +/* + * Process 1: get the allrecord_lock on a tdb. + * Process 2: start a traverse, this will stall waiting for the + * first chainlock: That is taken by the allrecord_lock + * Process 1: start a traverse: This will get EDEADLK in trying to + * get the TRANSACTION_LOCK. It will deadlock for mutexes, + * which don't have built-in deadlock detection. + */ + +static int do_tests(const char *name, int tdb_flags) +{ + struct tdb_context *tdb; + int ret; + pid_t traverse_child, allrecord_child; + int traverse_down[2]; + int traverse_up[2]; + int allrecord_down[2]; + int allrecord_up[2]; + char c; + ssize_t nread, nwritten; + TDB_DATA key, data; + + key.dsize = strlen("hi"); + key.dptr = discard_const_p(uint8_t, "hi"); + data.dsize = strlen("world"); + data.dptr = discard_const_p(uint8_t, "world"); + + tdb = tdb_open_ex(name, 3, tdb_flags, + O_RDWR|O_CREAT, 0755, &taplogctx, NULL); + ok(tdb, "tdb_open_ex should succeed"); + + ret = tdb_store(tdb, key, data, TDB_INSERT); + ok(ret == 0, "tdb_store should succeed"); + + ret = pipe(traverse_down); + ok(ret == 0, "pipe should succeed"); + + ret = pipe(traverse_up); + ok(ret == 0, "pipe should succeed"); + + ret = pipe(allrecord_down); + ok(ret == 0, "pipe should succeed"); + + ret = pipe(allrecord_up); + ok(ret == 0, "pipe should succeed"); + + allrecord_child = fork(); + ok(allrecord_child != -1, "fork should succeed"); + + if (allrecord_child == 0) { + tdb_close(tdb); + close(traverse_up[0]); + close(traverse_up[1]); + close(traverse_down[0]); + close(traverse_down[1]); + close(allrecord_up[0]); + close(allrecord_down[1]); + do_allrecord_lock(name, tdb_flags, + allrecord_up[1], allrecord_down[0]); + exit(0); + } + close(allrecord_up[1]); + close(allrecord_down[0]); + + nread = read(allrecord_up[0], &c, sizeof(c)); + ok(nread == sizeof(c), "read should succeed"); + + traverse_child = fork(); + ok(traverse_child != -1, "fork should succeed"); + + if (traverse_child == 0) { + tdb_close(tdb); + close(traverse_up[0]); + close(traverse_down[1]); + close(allrecord_up[0]); + close(allrecord_down[1]); + do_traverse(name, tdb_flags, + traverse_up[1], traverse_down[0]); + exit(0); + } + close(traverse_up[1]); + close(traverse_down[0]); + + poll(NULL, 0, 1000); + + nwritten = write(allrecord_down[1], &c, sizeof(c)); + ok(nwritten == sizeof(c), "write should succeed"); + + nread = read(traverse_up[0], &c, sizeof(c)); + ok(nread == sizeof(c), "read should succeed"); + + nwritten = write(traverse_down[1], &c, sizeof(c)); + ok(nwritten == sizeof(c), "write should succeed"); + + nread = read(allrecord_up[0], &c, sizeof(c)); + ok(nread == sizeof(c), "ret should succeed"); + + close(traverse_up[0]); + close(traverse_down[1]); + close(allrecord_up[0]); + close(allrecord_down[1]); + diag("%s tests done", name); + return exit_status(); +} + +int main(int argc, char *argv[]) +{ + int ret; + bool mutex_support; + + mutex_support = tdb_runtime_check_for_robust_mutexes(); + + ret = do_tests("marklock-deadlock-fcntl.tdb", + TDB_CLEAR_IF_FIRST | + TDB_INCOMPATIBLE_HASH); + ok(ret == 0, "marklock-deadlock-fcntl.tdb tests should succeed"); + + if (!mutex_support) { + skip(1, "No robust mutex support, " + "skipping marklock-deadlock-mutex.tdb tests"); + return exit_status(); + } + + ret = do_tests("marklock-deadlock-mutex.tdb", + TDB_CLEAR_IF_FIRST | + TDB_MUTEX_LOCKING | + TDB_INCOMPATIBLE_HASH); + ok(ret == 0, "marklock-deadlock-mutex.tdb tests should succeed"); + + return exit_status(); +} diff --git a/test/run-bad-tdb-header.c b/test/run-bad-tdb-header.c new file mode 100644 index 0000000..9d29fdf --- /dev/null +++ b/test/run-bad-tdb-header.c @@ -0,0 +1,59 @@ +#include "../common/tdb_private.h" +#include "../common/io.c" +#include "../common/tdb.c" +#include "../common/lock.c" +#include "../common/freelist.c" +#include "../common/traverse.c" +#include "../common/transaction.c" +#include "../common/error.c" +#include "../common/open.c" +#include "../common/check.c" +#include "../common/hash.c" +#include "../common/mutex.c" +#include "tap-interface.h" +#include <stdlib.h> +#include "logging.h" + +int main(int argc, char *argv[]) +{ + struct tdb_context *tdb; + struct tdb_header hdr; + int fd; + + plan_tests(11); + /* Can open fine if complete crap, as long as O_CREAT. */ + fd = open("run-bad-tdb-header.tdb", O_RDWR|O_CREAT|O_TRUNC, 0600); + ok1(fd >= 0); + ok1(write(fd, "hello world", 11) == 11); + close(fd); + tdb = tdb_open_ex("run-bad-tdb-header.tdb", 1024, 0, O_RDWR, 0, + &taplogctx, NULL); + ok1(!tdb); + tdb = tdb_open_ex("run-bad-tdb-header.tdb", 1024, 0, O_CREAT|O_RDWR, + 0600, &taplogctx, NULL); + ok1(tdb); + tdb_close(tdb); + + /* Now, with wrong version it should *not* overwrite. */ + fd = open("run-bad-tdb-header.tdb", O_RDWR); + ok1(fd >= 0); + ok1(read(fd, &hdr, sizeof(hdr)) == sizeof(hdr)); + ok1(hdr.version == TDB_VERSION); + hdr.version++; + lseek(fd, 0, SEEK_SET); + ok1(write(fd, &hdr, sizeof(hdr)) == sizeof(hdr)); + close(fd); + + tdb = tdb_open_ex("run-bad-tdb-header.tdb", 1024, 0, O_RDWR|O_CREAT, + 0600, &taplogctx, NULL); + ok1(errno == EIO); + ok1(!tdb); + + /* With truncate, will be fine. */ + tdb = tdb_open_ex("run-bad-tdb-header.tdb", 1024, 0, + O_RDWR|O_CREAT|O_TRUNC, 0600, &taplogctx, NULL); + ok1(tdb); + tdb_close(tdb); + + return exit_status(); +} diff --git a/test/run-check.c b/test/run-check.c new file mode 100644 index 0000000..ce389a2 --- /dev/null +++ b/test/run-check.c @@ -0,0 +1,65 @@ +#include "../common/tdb_private.h" +#include "../common/io.c" +#include "../common/tdb.c" +#include "../common/lock.c" +#include "../common/freelist.c" +#include "../common/traverse.c" +#include "../common/transaction.c" +#include "../common/error.c" +#include "../common/open.c" +#include "../common/check.c" +#include "../common/hash.c" +#include "../common/mutex.c" +#include "tap-interface.h" +#include <stdlib.h> +#include "logging.h" + +int main(int argc, char *argv[]) +{ + struct tdb_context *tdb; + TDB_DATA key, data; + + plan_tests(13); + tdb = tdb_open_ex("run-check.tdb", 1, TDB_CLEAR_IF_FIRST, + O_CREAT|O_TRUNC|O_RDWR, 0600, &taplogctx, NULL); + + ok1(tdb); + ok1(tdb_check(tdb, NULL, NULL) == 0); + + key.dsize = strlen("hi"); + key.dptr = discard_const_p(uint8_t, "hi"); + data.dsize = strlen("world"); + data.dptr = discard_const_p(uint8_t, "world"); + + ok1(tdb_store(tdb, key, data, TDB_INSERT) == 0); + ok1(tdb_check(tdb, NULL, NULL) == 0); + tdb_close(tdb); + + tdb = tdb_open_ex("run-check.tdb", 1024, 0, O_RDWR, 0, + &taplogctx, NULL); + ok1(tdb); + ok1(tdb_check(tdb, NULL, NULL) == 0); + tdb_close(tdb); + + tdb = tdb_open_ex("test/tdb.corrupt", 1024, 0, O_RDWR, 0, + &taplogctx, NULL); + ok1(tdb); + ok1(tdb_check(tdb, NULL, NULL) == -1); + ok1(tdb_error(tdb) == TDB_ERR_CORRUPT); + tdb_close(tdb); + + /* Big and little endian should work! */ + tdb = tdb_open_ex("test/old-nohash-le.tdb", 1024, 0, O_RDWR, 0, + &taplogctx, NULL); + ok1(tdb); + ok1(tdb_check(tdb, NULL, NULL) == 0); + tdb_close(tdb); + + tdb = tdb_open_ex("test/old-nohash-be.tdb", 1024, 0, O_RDWR, 0, + &taplogctx, NULL); + ok1(tdb); + ok1(tdb_check(tdb, NULL, NULL) == 0); + tdb_close(tdb); + + return exit_status(); +} diff --git a/test/run-circular-chain.c b/test/run-circular-chain.c new file mode 100644 index 0000000..4fb32a2 --- /dev/null +++ b/test/run-circular-chain.c @@ -0,0 +1,42 @@ +#include "../common/tdb_private.h" +#include "../common/io.c" +#include "../common/tdb.c" +#include "../common/lock.c" +#include "../common/freelist.c" +#include "../common/traverse.c" +#include "../common/transaction.c" +#include "../common/error.c" +#include "../common/open.c" +#include "../common/check.c" +#include "../common/hash.c" +#include "../common/mutex.c" +#include "tap-interface.h" +#include <stdlib.h> +#include "logging.h" + +int main(int argc, char *argv[]) +{ + struct tdb_context *tdb; + TDB_DATA key; + + plan_tests(3); + tdb = tdb_open_ex( + "test/circular_chain.tdb", + 0, + TDB_DEFAULT, + O_RDONLY, + 0600, + &taplogctx, + NULL); + + ok1(tdb); + key.dsize = strlen("x"); + key.dptr = discard_const_p(uint8_t, "x"); + + ok1(tdb_exists(tdb, key) == 0); + ok1(tdb_error(tdb) == TDB_ERR_CORRUPT); + + tdb_close(tdb); + + return exit_status(); +} diff --git a/test/run-circular-freelist.c b/test/run-circular-freelist.c new file mode 100644 index 0000000..f1bec87 --- /dev/null +++ b/test/run-circular-freelist.c @@ -0,0 +1,50 @@ +#include "../common/tdb_private.h" +#include "../common/io.c" +#include "../common/tdb.c" +#include "../common/lock.c" +#include "../common/freelist.c" +#include "../common/traverse.c" +#include "../common/transaction.c" +#include "../common/error.c" +#include "../common/open.c" +#include "../common/check.c" +#include "../common/hash.c" +#include "../common/mutex.c" +#include "tap-interface.h" +#include <stdlib.h> +#include "logging.h" + +int main(int argc, char *argv[]) +{ + struct tdb_context *tdb; + TDB_DATA key, data; + + plan_tests(3); + tdb = tdb_open_ex( + "test/circular_freelist.tdb", + 0, + TDB_DEFAULT, + O_RDWR, + 0600, + &taplogctx, + NULL); + + ok1(tdb); + + /* + * All freelist records are just 1 byte key and value. Insert + * something that will walk the whole freelist and hit the + * circle. + */ + key.dsize = strlen("x"); + key.dptr = discard_const_p(uint8_t, "x"); + data.dsize = strlen("too long"); + data.dptr = discard_const_p(uint8_t, "too long"); + + ok1(tdb_store(tdb, key, data, TDB_INSERT) == -1); + ok1(tdb_error(tdb) == TDB_ERR_CORRUPT); + + tdb_close(tdb); + + return exit_status(); +} diff --git a/test/run-corrupt.c b/test/run-corrupt.c new file mode 100644 index 0000000..e6fc751 --- /dev/null +++ b/test/run-corrupt.c @@ -0,0 +1,132 @@ +#include "../common/tdb_private.h" +#include "../common/io.c" +#include "../common/tdb.c" +#include "../common/lock.c" +#include "../common/freelist.c" +#include "../common/traverse.c" +#include "../common/transaction.c" +#include "../common/error.c" +#include "../common/open.c" +#include "../common/check.c" +#include "../common/hash.c" +#include "../common/mutex.c" +#include "tap-interface.h" +#include <stdlib.h> +#include "logging.h" + +static int check(TDB_DATA key, TDB_DATA data, void *private) +{ + unsigned int *sizes = private; + + if (key.dsize > strlen("hello")) + return -1; + if (memcmp(key.dptr, "hello", key.dsize) != 0) + return -1; + + if (data.dsize != strlen("world")) + return -1; + if (memcmp(data.dptr, "world", data.dsize) != 0) + return -1; + + sizes[0] += key.dsize; + sizes[1] += data.dsize; + return 0; +} + +static void tdb_flip_bit(struct tdb_context *tdb, unsigned int bit) +{ + unsigned int off = bit / CHAR_BIT; + unsigned char mask = (1 << (bit % CHAR_BIT)); + + if (tdb->map_ptr) + ((unsigned char *)tdb->map_ptr)[off] ^= mask; + else { + unsigned char c; + if (pread(tdb->fd, &c, 1, off) != 1) { + fprintf(stderr, "pread: %s\n", strerror(errno)); + exit(1); + } + c ^= mask; + if (pwrite(tdb->fd, &c, 1, off) != 1) { + fprintf(stderr, "pwrite: %s\n", strerror(errno)); + exit(1); + } + } +} + +static void check_test(struct tdb_context *tdb) +{ + TDB_DATA key, data; + unsigned int i, verifiable, corrupt, sizes[2], dsize, ksize; + + ok1(tdb_check(tdb, NULL, NULL) == 0); + + key.dptr = discard_const_p(uint8_t, "hello"); + data.dsize = strlen("world"); + data.dptr = discard_const_p(uint8_t, "world"); + + /* Key and data size respectively. */ + dsize = ksize = 0; + + /* 5 keys in hash size 2 means we'll have multichains. */ + for (key.dsize = 1; key.dsize <= 5; key.dsize++) { + ksize += key.dsize; + dsize += data.dsize; + if (tdb_store(tdb, key, data, TDB_INSERT) != 0) + abort(); + } + + /* This is how many bytes we expect to be verifiable. */ + /* From the file header. */ + verifiable = strlen(TDB_MAGIC_FOOD) + 1 + + 2 * sizeof(uint32_t) + 2 * sizeof(tdb_off_t) + + 2 * sizeof(uint32_t); + /* From the free list chain and hash chains. */ + verifiable += 3 * sizeof(tdb_off_t); + /* From the record headers & tailer */ + verifiable += 5 * (sizeof(struct tdb_record) + sizeof(uint32_t)); + /* The free block: we ignore datalen, keylen, full_hash. */ + verifiable += sizeof(struct tdb_record) - 3*sizeof(uint32_t) + + sizeof(uint32_t); + /* Our check function verifies the key and data. */ + verifiable += ksize + dsize; + + /* Flip one bit at a time, make sure it detects verifiable bytes. */ + for (i = 0, corrupt = 0; i < tdb->map_size * CHAR_BIT; i++) { + tdb_flip_bit(tdb, i); + memset(sizes, 0, sizeof(sizes)); + if (tdb_check(tdb, check, sizes) != 0) + corrupt++; + else if (sizes[0] != ksize || sizes[1] != dsize) + corrupt++; + tdb_flip_bit(tdb, i); + } + ok(corrupt == verifiable * CHAR_BIT, "corrupt %u should be %u", + corrupt, verifiable * CHAR_BIT); +} + +int main(int argc, char *argv[]) +{ + struct tdb_context *tdb; + + plan_tests(4); + /* This should use mmap. */ + tdb = tdb_open_ex("run-corrupt.tdb", 2, TDB_CLEAR_IF_FIRST, + O_CREAT|O_TRUNC|O_RDWR, 0600, &taplogctx, NULL); + + if (!tdb) + abort(); + check_test(tdb); + tdb_close(tdb); + + /* This should not. */ + tdb = tdb_open_ex("run-corrupt.tdb", 2, TDB_CLEAR_IF_FIRST|TDB_NOMMAP, + O_CREAT|O_TRUNC|O_RDWR, 0600, &taplogctx, NULL); + + if (!tdb) + abort(); + check_test(tdb); + tdb_close(tdb); + + return exit_status(); +} diff --git a/test/run-die-during-transaction.c b/test/run-die-during-transaction.c new file mode 100644 index 0000000..c636d87 --- /dev/null +++ b/test/run-die-during-transaction.c @@ -0,0 +1,232 @@ +#include "../common/tdb_private.h" +#include "lock-tracking.h" +static ssize_t pwrite_check(int fd, const void *buf, size_t count, off_t offset); +static ssize_t write_check(int fd, const void *buf, size_t count); +static int ftruncate_check(int fd, off_t length); + +#define pwrite pwrite_check +#define write write_check +#define fcntl fcntl_with_lockcheck +#define ftruncate ftruncate_check + +#include "../common/io.c" +#include "../common/tdb.c" +#include "../common/lock.c" +#include "../common/freelist.c" +#include "../common/traverse.c" +#include "../common/transaction.c" +#include "../common/error.c" +#include "../common/open.c" +#include "../common/check.c" +#include "../common/hash.c" +#include "../common/mutex.c" +#include "tap-interface.h" +#include <stdlib.h> +#include <stdbool.h> +#include <stdarg.h> +#include <setjmp.h> +#include "external-agent.h" +#include "logging.h" + +#undef write +#undef pwrite +#undef fcntl +#undef ftruncate + +static bool in_transaction; +static int target, current; +static jmp_buf jmpbuf; +#define TEST_DBNAME "run-die-during-transaction.tdb" +#define KEY_STRING "helloworld" + +static void maybe_die(int fd) +{ + if (in_transaction && current++ == target) { + longjmp(jmpbuf, 1); + } +} + +static ssize_t pwrite_check(int fd, + const void *buf, size_t count, off_t offset) +{ + ssize_t ret; + + maybe_die(fd); + + ret = pwrite(fd, buf, count, offset); + if (ret != count) + return ret; + + maybe_die(fd); + return ret; +} + +static ssize_t write_check(int fd, const void *buf, size_t count) +{ + ssize_t ret; + + maybe_die(fd); + + ret = write(fd, buf, count); + if (ret != count) + return ret; + + maybe_die(fd); + return ret; +} + +static int ftruncate_check(int fd, off_t length) +{ + int ret; + + maybe_die(fd); + + ret = ftruncate(fd, length); + + maybe_die(fd); + return ret; +} + +static bool test_death(enum operation op, struct agent *agent) +{ + struct tdb_context *tdb = NULL; + TDB_DATA key; + enum agent_return ret; + int needed_recovery = 0; + + current = target = 0; +reset: + unlink(TEST_DBNAME); + tdb = tdb_open_ex(TEST_DBNAME, 1024, TDB_NOMMAP, + O_CREAT|O_TRUNC|O_RDWR, 0600, &taplogctx, NULL); + + if (setjmp(jmpbuf) != 0) { + /* We're partway through. Simulate our death. */ + close(tdb->fd); + forget_locking(); + in_transaction = false; + + ret = external_agent_operation(agent, NEEDS_RECOVERY, ""); + if (ret == SUCCESS) + needed_recovery++; + else if (ret != FAILED) { + diag("Step %u agent NEEDS_RECOVERY = %s", current, + agent_return_name(ret)); + return false; + } + + ret = external_agent_operation(agent, op, KEY_STRING); + if (ret != SUCCESS) { + diag("Step %u op %s failed = %s", current, + operation_name(op), + agent_return_name(ret)); + return false; + } + + ret = external_agent_operation(agent, NEEDS_RECOVERY, ""); + if (ret != FAILED) { + diag("Still needs recovery after step %u = %s", + current, agent_return_name(ret)); + return false; + } + + ret = external_agent_operation(agent, CHECK, ""); + if (ret != SUCCESS) { + diag("Step %u check failed = %s", current, + agent_return_name(ret)); + return false; + } + + ret = external_agent_operation(agent, CLOSE, ""); + if (ret != SUCCESS) { + diag("Step %u close failed = %s", current, + agent_return_name(ret)); + return false; + } + + /* Suppress logging as this tries to use closed fd. */ + suppress_logging = true; + suppress_lockcheck = true; + tdb_close(tdb); + suppress_logging = false; + suppress_lockcheck = false; + target++; + current = 0; + goto reset; + } + + /* Put key for agent to fetch. */ + key.dsize = strlen(KEY_STRING); + key.dptr = discard_const_p(uint8_t, KEY_STRING); + if (tdb_store(tdb, key, key, TDB_INSERT) != 0) + return false; + + /* This is the key we insert in transaction. */ + key.dsize--; + + ret = external_agent_operation(agent, OPEN, TEST_DBNAME); + if (ret != SUCCESS) { + fprintf(stderr, "Agent failed to open: %s\n", + agent_return_name(ret)); + exit(1); + } + + ret = external_agent_operation(agent, FETCH, KEY_STRING); + if (ret != SUCCESS) { + fprintf(stderr, "Agent failed find key: %s\n", + agent_return_name(ret)); + exit(1); + } + + in_transaction = true; + if (tdb_transaction_start(tdb) != 0) + return false; + + if (tdb_store(tdb, key, key, TDB_INSERT) != 0) + return false; + + if (tdb_transaction_commit(tdb) != 0) + return false; + + in_transaction = false; + + /* We made it! */ + diag("Completed %u runs", current); + tdb_close(tdb); + ret = external_agent_operation(agent, CLOSE, ""); + if (ret != SUCCESS) { + diag("Step %u close failed = %s", current, + agent_return_name(ret)); + return false; + } + +#ifdef HAVE_INCOHERENT_MMAP + /* This means we always mmap, which makes this test a noop. */ + ok1(1); +#else + ok1(needed_recovery); +#endif + ok1(locking_errors == 0); + ok1(forget_locking() == 0); + locking_errors = 0; + return true; +} + +int main(int argc, char *argv[]) +{ + enum operation ops[] = { FETCH, STORE, TRANSACTION_START }; + struct agent *agent; + int i; + + plan_tests(12); + unlock_callback = maybe_die; + + agent = prepare_external_agent(); + + for (i = 0; i < sizeof(ops)/sizeof(ops[0]); i++) { + diag("Testing %s after death", operation_name(ops[i])); + ok1(test_death(ops[i], agent)); + } + + return exit_status(); +} diff --git a/test/run-endian.c b/test/run-endian.c new file mode 100644 index 0000000..9d4d5f5 --- /dev/null +++ b/test/run-endian.c @@ -0,0 +1,64 @@ +#include "../common/tdb_private.h" +#include "../common/io.c" +#include "../common/tdb.c" +#include "../common/lock.c" +#include "../common/freelist.c" +#include "../common/traverse.c" +#include "../common/transaction.c" +#include "../common/error.c" +#include "../common/open.c" +#include "../common/check.c" +#include "../common/hash.c" +#include "../common/mutex.c" +#include "tap-interface.h" +#include <stdlib.h> +#include "logging.h" + +int main(int argc, char *argv[]) +{ + struct tdb_context *tdb; + TDB_DATA key, data; + + plan_tests(13); + tdb = tdb_open_ex("run-endian.tdb", 1024, + TDB_CLEAR_IF_FIRST|TDB_CONVERT, + O_CREAT|O_TRUNC|O_RDWR, 0600, &taplogctx, NULL); + + ok1(tdb); + key.dsize = strlen("hi"); + key.dptr = discard_const_p(uint8_t, "hi"); + data.dsize = strlen("world"); + data.dptr = discard_const_p(uint8_t, "world"); + + ok1(tdb_store(tdb, key, data, TDB_MODIFY) < 0); + ok1(tdb_error(tdb) == TDB_ERR_NOEXIST); + ok1(tdb_store(tdb, key, data, TDB_INSERT) == 0); + ok1(tdb_store(tdb, key, data, TDB_INSERT) < 0); + ok1(tdb_error(tdb) == TDB_ERR_EXISTS); + ok1(tdb_store(tdb, key, data, TDB_MODIFY) == 0); + + data = tdb_fetch(tdb, key); + ok1(data.dsize == strlen("world")); + ok1(memcmp(data.dptr, "world", strlen("world")) == 0); + free(data.dptr); + + key.dsize++; + data = tdb_fetch(tdb, key); + ok1(data.dptr == NULL); + tdb_close(tdb); + + /* Reopen: should read it */ + tdb = tdb_open_ex("run-endian.tdb", 1024, 0, O_RDWR, 0, + &taplogctx, NULL); + ok1(tdb); + + key.dsize = strlen("hi"); + key.dptr = discard_const_p(uint8_t, "hi"); + data = tdb_fetch(tdb, key); + ok1(data.dsize == strlen("world")); + ok1(memcmp(data.dptr, "world", strlen("world")) == 0); + free(data.dptr); + tdb_close(tdb); + + return exit_status(); +} diff --git a/test/run-fcntl-deadlock.c b/test/run-fcntl-deadlock.c new file mode 100644 index 0000000..0a328af --- /dev/null +++ b/test/run-fcntl-deadlock.c @@ -0,0 +1,202 @@ +#include "../common/tdb_private.h" +#include "../common/io.c" +#include "../common/tdb.c" +#include "../common/lock.c" +#include "../common/freelist.c" +#include "../common/traverse.c" +#include "../common/transaction.c" +#include "../common/error.c" +#include "../common/open.c" +#include "../common/check.c" +#include "../common/hash.c" +#include "../common/mutex.c" +#include "replace.h" +#include "system/filesys.h" +#include "system/time.h" +#include <errno.h> +#include "tap-interface.h" + +/* + * This tests the low level locking requirement + * for the allrecord lock/prepare_commit and traverse_read interaction. + * + * The pattern with the traverse_read and prepare_commit interaction is + * the following: + * + * 1. transaction_start got the allrecord lock with F_RDLCK. + * + * 2. the traverse_read code walks the database in a sequence like this + * (per chain): + * 2.1 chainlock(chainX, F_RDLCK) + * 2.2 recordlock(chainX.record1, F_RDLCK) + * 2.3 chainunlock(chainX, F_RDLCK) + * 2.4 callback(chainX.record1) + * 2.5 chainlock(chainX, F_RDLCK) + * 2.6 recordunlock(chainX.record1, F_RDLCK) + * 2.7 recordlock(chainX.record2, F_RDLCK) + * 2.8 chainunlock(chainX, F_RDLCK) + * 2.9 callback(chainX.record2) + * 2.10 chainlock(chainX, F_RDLCK) + * 2.11 recordunlock(chainX.record2, F_RDLCK) + * 2.12 chainunlock(chainX, F_RDLCK) + * 2.13 goto next chain + * + * So it has always one record locked in F_RDLCK mode and tries to + * get the 2nd one before it releases the first one. + * + * 3. prepare_commit tries to upgrade the allrecord lock to F_RWLCK + * If that happens at the time of 2.4, the operation of + * 2.5 may deadlock with the allrecord lock upgrade. + * On Linux step 2.5 works in order to make some progress with the + * locking, but on solaris it might fail because the kernel + * wants to satisfy the 1st lock requester before the 2nd one. + * + * I think the first step is a standalone test that does this: + * + * process1: F_RDLCK for ofs=0 len=2 + * process2: F_RDLCK for ofs=0 len=1 + * process1: upgrade ofs=0 len=2 to F_RWLCK (in blocking mode) + * process2: F_RDLCK for ofs=1 len=1 + * process2: unlock ofs=0 len=2 + * process1: should continue at that point + * + * Such a test follows here... + */ + +static int raw_fcntl_lock(int fd, int rw, off_t off, off_t len, bool waitflag) +{ + struct flock fl; + int cmd; + fl.l_type = rw; + fl.l_whence = SEEK_SET; + fl.l_start = off; + fl.l_len = len; + fl.l_pid = 0; + + cmd = waitflag ? F_SETLKW : F_SETLK; + + return fcntl(fd, cmd, &fl); +} + +static int raw_fcntl_unlock(int fd, off_t off, off_t len) +{ + struct flock fl; + fl.l_type = F_UNLCK; + fl.l_whence = SEEK_SET; + fl.l_start = off; + fl.l_len = len; + fl.l_pid = 0; + + return fcntl(fd, F_SETLKW, &fl); +} + + +int pipe_r; +int pipe_w; +char buf[2]; + +static void expect_char(char c) +{ + read(pipe_r, buf, 1); + if (*buf != c) { + fail("We were expecting %c, but got %c", c, buf[0]); + } +} + +static void send_char(char c) +{ + write(pipe_w, &c, 1); +} + + +int main(int argc, char *argv[]) +{ + int process; + int fd; + const char *filename = "run-fcntl-deadlock.lck"; + int pid; + int pipes_1_2[2]; + int pipes_2_1[2]; + int ret; + + pipe(pipes_1_2); + pipe(pipes_2_1); + fd = open(filename, O_RDWR | O_CREAT, 0755); + + pid = fork(); + if (pid == 0) { + pipe_r = pipes_1_2[0]; + pipe_w = pipes_2_1[1]; + process = 2; + alarm(15); + } else { + pipe_r = pipes_2_1[0]; + pipe_w = pipes_1_2[1]; + process = 1; + alarm(15); + } + + /* a: process1: F_RDLCK for ofs=0 len=2 */ + if (process == 1) { + ret = raw_fcntl_lock(fd, F_RDLCK, 0, 2, true); + ok(ret == 0, + "process 1 lock ofs=0 len=2: %d - %s", + ret, strerror(errno)); + diag("process 1 took read lock on range 0,2"); + send_char('a'); + } + + /* process2: F_RDLCK for ofs=0 len=1 */ + if (process == 2) { + expect_char('a'); + ret = raw_fcntl_lock(fd, F_RDLCK, 0, 1, true); + ok(ret == 0, + "process 2 lock ofs=0 len=1: %d - %s", + ret, strerror(errno));; + diag("process 2 took read lock on range 0,1"); + send_char('b'); + } + + /* process1: upgrade ofs=0 len=2 to F_RWLCK (in blocking mode) */ + if (process == 1) { + expect_char('b'); + send_char('c'); + diag("process 1 starts upgrade on range 0,2"); + ret = raw_fcntl_lock(fd, F_WRLCK, 0, 2, true); + ok(ret == 0, + "process 1 RW lock ofs=0 len=2: %d - %s", + ret, strerror(errno)); + diag("process 1 got read upgrade done"); + /* at this point process 1 is blocked on 2 releasing the + read lock */ + } + + /* + * process2: F_RDLCK for ofs=1 len=1 + * process2: unlock ofs=0 len=2 + */ + if (process == 2) { + expect_char('c'); /* we know process 1 is *about* to lock */ + sleep(1); + ret = raw_fcntl_lock(fd, F_RDLCK, 1, 1, true); + ok(ret == 0, + "process 2 lock ofs=1 len=1: %d - %s", + ret, strerror(errno)); + diag("process 2 got read lock on 1,1\n"); + ret = raw_fcntl_unlock(fd, 0, 2); + ok(ret == 0, + "process 2 unlock ofs=0 len=2: %d - %s", + ret, strerror(errno)); + diag("process 2 released read lock on 0,2\n"); + sleep(1); + send_char('d'); + } + + if (process == 1) { + expect_char('d'); + } + + diag("process %d has got to the end\n", process); + + return 0; +} diff --git a/test/run-incompatible.c b/test/run-incompatible.c new file mode 100644 index 0000000..5f1b586 --- /dev/null +++ b/test/run-incompatible.c @@ -0,0 +1,188 @@ +#include "../common/tdb_private.h" +#include "../common/io.c" +#include "../common/tdb.c" +#include "../common/lock.c" +#include "../common/freelist.c" +#include "../common/traverse.c" +#include "../common/transaction.c" +#include "../common/error.c" +#include "../common/open.c" +#include "../common/check.c" +#include "../common/hash.c" +#include "../common/mutex.c" +#include "tap-interface.h" +#include <stdlib.h> + +static unsigned int tdb_dumb_hash(TDB_DATA *key) +{ + return key->dsize; +} + +static void log_fn(struct tdb_context *tdb, enum tdb_debug_level level, const char *fmt, ...) +{ + unsigned int *count = tdb_get_logging_private(tdb); + if (strstr(fmt, "hash")) + (*count)++; +} + +static unsigned int hdr_rwlocks(const char *fname) +{ + struct tdb_header hdr; + ssize_t nread; + + int fd = open(fname, O_RDONLY); + if (fd == -1) + return -1; + + nread = read(fd, &hdr, sizeof(hdr)); + close(fd); + if (nread != sizeof(hdr)) { + return -1; + } + return hdr.rwlocks; +} + +int main(int argc, char *argv[]) +{ + struct tdb_context *tdb; + unsigned int log_count, flags; + TDB_DATA d, r; + struct tdb_logging_context log_ctx = { log_fn, &log_count }; + + plan_tests(38 * 2); + + for (flags = 0; flags <= TDB_CONVERT; flags += TDB_CONVERT) { + unsigned int rwmagic = TDB_HASH_RWLOCK_MAGIC; + + if (flags & TDB_CONVERT) + tdb_convert(&rwmagic, sizeof(rwmagic)); + + /* Create an old-style hash. */ + log_count = 0; + tdb = tdb_open_ex("run-incompatible.tdb", 0, flags, + O_CREAT|O_RDWR|O_TRUNC, 0600, &log_ctx, + NULL); + ok1(tdb); + ok1(log_count == 0); + d.dptr = discard_const_p(uint8_t, "Hello"); + d.dsize = 5; + ok1(tdb_store(tdb, d, d, TDB_INSERT) == 0); + tdb_close(tdb); + + /* Should not have marked rwlocks field. */ + ok1(hdr_rwlocks("run-incompatible.tdb") == 0); + + /* We can still open any old-style with incompat flag. */ + log_count = 0; + tdb = tdb_open_ex("run-incompatible.tdb", 0, + TDB_INCOMPATIBLE_HASH, + O_RDWR, 0600, &log_ctx, NULL); + ok1(tdb); + ok1(log_count == 0); + r = tdb_fetch(tdb, d); + ok1(r.dsize == 5); + free(r.dptr); + ok1(tdb_check(tdb, NULL, NULL) == 0); + tdb_close(tdb); + + log_count = 0; + tdb = tdb_open_ex("test/jenkins-le-hash.tdb", 0, 0, O_RDONLY, + 0, &log_ctx, tdb_jenkins_hash); + ok1(tdb); + ok1(log_count == 0); + ok1(tdb_check(tdb, NULL, NULL) == 0); + tdb_close(tdb); + + log_count = 0; + tdb = tdb_open_ex("test/jenkins-be-hash.tdb", 0, 0, O_RDONLY, + 0, &log_ctx, tdb_jenkins_hash); + ok1(tdb); + ok1(log_count == 0); + ok1(tdb_check(tdb, NULL, NULL) == 0); + tdb_close(tdb); + + /* OK, now create with incompatible flag, default hash. */ + log_count = 0; + tdb = tdb_open_ex("run-incompatible.tdb", 0, + flags|TDB_INCOMPATIBLE_HASH, + O_CREAT|O_RDWR|O_TRUNC, 0600, &log_ctx, + NULL); + ok1(tdb); + ok1(log_count == 0); + d.dptr = discard_const_p(uint8_t, "Hello"); + d.dsize = 5; + ok1(tdb_store(tdb, d, d, TDB_INSERT) == 0); + tdb_close(tdb); + + /* Should have marked rwlocks field. */ + ok1(hdr_rwlocks("run-incompatible.tdb") == rwmagic); + + /* Cannot open with old hash. */ + log_count = 0; + tdb = tdb_open_ex("run-incompatible.tdb", 0, 0, + O_RDWR, 0600, &log_ctx, tdb_old_hash); + ok1(!tdb); + ok1(log_count == 1); + + /* Can open with jenkins hash. */ + log_count = 0; + tdb = tdb_open_ex("run-incompatible.tdb", 0, 0, + O_RDWR, 0600, &log_ctx, tdb_jenkins_hash); + ok1(tdb); + ok1(log_count == 0); + r = tdb_fetch(tdb, d); + ok1(r.dsize == 5); + free(r.dptr); + ok1(tdb_check(tdb, NULL, NULL) == 0); + tdb_close(tdb); + + /* Can open by letting it figure it out itself. */ + log_count = 0; + tdb = tdb_open_ex("run-incompatible.tdb", 0, 0, + O_RDWR, 0600, &log_ctx, NULL); + ok1(tdb); + ok1(log_count == 0); + r = tdb_fetch(tdb, d); + ok1(r.dsize == 5); + free(r.dptr); + ok1(tdb_check(tdb, NULL, NULL) == 0); + tdb_close(tdb); + + /* We can also use incompatible hash with other hashes. */ + log_count = 0; + tdb = tdb_open_ex("run-incompatible.tdb", 0, + flags|TDB_INCOMPATIBLE_HASH, + O_CREAT|O_RDWR|O_TRUNC, 0600, &log_ctx, + tdb_dumb_hash); + ok1(tdb); + ok1(log_count == 0); + d.dptr = discard_const_p(uint8_t, "Hello"); + d.dsize = 5; + ok1(tdb_store(tdb, d, d, TDB_INSERT) == 0); + tdb_close(tdb); + + /* Should have marked rwlocks field. */ + ok1(hdr_rwlocks("run-incompatible.tdb") == rwmagic); + + /* It should not open if we don't specify. */ + log_count = 0; + tdb = tdb_open_ex("run-incompatible.tdb", 0, 0, O_RDWR, 0, + &log_ctx, NULL); + ok1(!tdb); + ok1(log_count == 1); + + /* Should reopen with correct hash. */ + log_count = 0; + tdb = tdb_open_ex("run-incompatible.tdb", 0, 0, O_RDWR, 0, + &log_ctx, tdb_dumb_hash); + ok1(tdb); + ok1(log_count == 0); + r = tdb_fetch(tdb, d); + ok1(r.dsize == 5); + free(r.dptr); + ok1(tdb_check(tdb, NULL, NULL) == 0); + tdb_close(tdb); + } + + return exit_status(); +} diff --git a/test/run-marklock-deadlock.c b/test/run-marklock-deadlock.c new file mode 100644 index 0000000..37e959f --- /dev/null +++ b/test/run-marklock-deadlock.c @@ -0,0 +1,278 @@ +#include "../common/tdb_private.h" +#include "../common/io.c" +#include "../common/tdb.c" +#include "../common/lock.c" +#include "../common/freelist.c" +#include "../common/traverse.c" +#include "../common/transaction.c" +#include "../common/error.c" +#include "../common/open.c" +#include "../common/check.c" +#include "../common/hash.c" +#include "../common/mutex.c" +#include "tap-interface.h" +#include <stdlib.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <stdarg.h> +#include "logging.h" + +static TDB_DATA key, data; + +static void do_chainlock(const char *name, int tdb_flags, int up, int down) +{ + struct tdb_context *tdb; + int ret; + ssize_t nread, nwritten; + char c = 0; + + tdb = tdb_open_ex(name, 3, tdb_flags, + O_RDWR|O_CREAT, 0755, &taplogctx, NULL); + ok(tdb, "tdb_open_ex should succeed"); + + ret = tdb_chainlock(tdb, key); + ok(ret == 0, "tdb_chainlock should succeed"); + + nwritten = write(up, &c, sizeof(c)); + ok(nwritten == sizeof(c), "write should succeed"); + + nread = read(down, &c, sizeof(c)); + ok(nread == sizeof(c), "read should succeed"); + + exit(0); +} + +static void do_allrecord_lock(const char *name, int tdb_flags, int up, int down) +{ + struct tdb_context *tdb; + int ret; + ssize_t nread, nwritten; + char c = 0; + + tdb = tdb_open_ex(name, 3, tdb_flags, + O_RDWR|O_CREAT, 0755, &taplogctx, NULL); + ok(tdb, "tdb_open_ex should succeed"); + + ret = tdb_allrecord_lock(tdb, F_WRLCK, TDB_LOCK_WAIT, false); + ok(ret == 0, "tdb_allrecord_lock should succeed"); + + nwritten = write(up, &c, sizeof(c)); + ok(nwritten == sizeof(c), "write should succeed"); + + nread = read(down, &c, sizeof(c)); + ok(nread == sizeof(c), "read should succeed"); + + exit(0); +} + +/* The code should barf on TDBs created with rwlocks. */ +static int do_tests(const char *name, int tdb_flags) +{ + struct tdb_context *tdb; + int ret; + pid_t chainlock_child, allrecord_child; + int chainlock_down[2]; + int chainlock_up[2]; + int allrecord_down[2]; + int allrecord_up[2]; + char c; + ssize_t nread, nwritten; + + key.dsize = strlen("hi"); + key.dptr = discard_const_p(uint8_t, "hi"); + data.dsize = strlen("world"); + data.dptr = discard_const_p(uint8_t, "world"); + + ret = pipe(chainlock_down); + ok(ret == 0, "pipe should succeed"); + + ret = pipe(chainlock_up); + ok(ret == 0, "pipe should succeed"); + + ret = pipe(allrecord_down); + ok(ret == 0, "pipe should succeed"); + + ret = pipe(allrecord_up); + ok(ret == 0, "pipe should succeed"); + + chainlock_child = fork(); + ok(chainlock_child != -1, "fork should succeed"); + + if (chainlock_child == 0) { + close(chainlock_up[0]); + close(chainlock_down[1]); + close(allrecord_up[0]); + close(allrecord_up[1]); + close(allrecord_down[0]); + close(allrecord_down[1]); + do_chainlock(name, tdb_flags, + chainlock_up[1], chainlock_down[0]); + exit(0); + } + close(chainlock_up[1]); + close(chainlock_down[0]); + + nread = read(chainlock_up[0], &c, sizeof(c)); + ok(nread == sizeof(c), "read should succeed"); + + /* + * Now we have a process holding a chainlock. Start another process + * trying the allrecord lock. This will block. + */ + + allrecord_child = fork(); + ok(allrecord_child != -1, "fork should succeed"); + + if (allrecord_child == 0) { + close(chainlock_up[0]); + close(chainlock_up[1]); + close(chainlock_down[0]); + close(chainlock_down[1]); + close(allrecord_up[0]); + close(allrecord_down[1]); + do_allrecord_lock(name, tdb_flags, + allrecord_up[1], allrecord_down[0]); + exit(0); + } + close(allrecord_up[1]); + close(allrecord_down[0]); + + poll(NULL, 0, 500); + + tdb = tdb_open_ex(name, 3, tdb_flags, + O_RDWR|O_CREAT, 0755, &taplogctx, NULL); + ok(tdb, "tdb_open_ex should succeed"); + + /* + * Someone already holds a chainlock, but we're able to get the + * freelist lock. + * + * The freelist lock/mutex is independent from the allrecord lock/mutex. + */ + + ret = tdb_chainlock_nonblock(tdb, key); + ok(ret == -1, "tdb_chainlock_nonblock should not succeed"); + + ret = tdb_lock_nonblock(tdb, -1, F_WRLCK); + ok(ret == 0, "tdb_lock_nonblock should succeed"); + + ret = tdb_unlock(tdb, -1, F_WRLCK); + ok(ret == 0, "tdb_unlock should succeed"); + + /* + * We have someone else having done the lock for us. Just mark it. + */ + + ret = tdb_chainlock_mark(tdb, key); + ok(ret == 0, "tdb_chainlock_mark should succeed"); + + /* + * The tdb_store below will block the freelist. In one version of the + * mutex patches, the freelist was already blocked here by the + * allrecord child, which was waiting for the chainlock child to give + * up its chainlock. Make sure that we don't run into this + * deadlock. To exercise the deadlock, just comment out the "ok" + * line. + * + * The freelist lock/mutex is independent from the allrecord lock/mutex. + */ + + ret = tdb_lock_nonblock(tdb, -1, F_WRLCK); + ok(ret == 0, "tdb_lock_nonblock should succeed"); + + ret = tdb_unlock(tdb, -1, F_WRLCK); + ok(ret == 0, "tdb_unlock should succeed"); + + ret = tdb_store(tdb, key, data, TDB_INSERT); + ok(ret == 0, "tdb_store should succeed"); + + ret = tdb_chainlock_unmark(tdb, key); + ok(ret == 0, "tdb_chainlock_unmark should succeed"); + + nwritten = write(chainlock_down[1], &c, sizeof(c)); + ok(nwritten == sizeof(c), "write should succeed"); + + nread = read(chainlock_up[0], &c, sizeof(c)); + ok(nread == 0, "read should succeed"); + + nread = read(allrecord_up[0], &c, sizeof(c)); + ok(nread == sizeof(c), "read should succeed"); + + /* + * Someone already holds the allrecord lock, but we're able to get the + * freelist lock. + * + * The freelist lock/mutex is independent from the allrecord lock/mutex. + */ + + ret = tdb_chainlock_nonblock(tdb, key); + ok(ret == -1, "tdb_chainlock_nonblock should not succeed"); + + ret = tdb_lockall_nonblock(tdb); + ok(ret == -1, "tdb_lockall_nonblock should not succeed"); + + ret = tdb_lock_nonblock(tdb, -1, F_WRLCK); + ok(ret == 0, "tdb_lock_nonblock should succeed"); + + ret = tdb_unlock(tdb, -1, F_WRLCK); + ok(ret == 0, "tdb_unlock should succeed"); + + /* + * We have someone else having done the lock for us. Just mark it. + */ + + ret = tdb_lockall_mark(tdb); + ok(ret == 0, "tdb_lockall_mark should succeed"); + + ret = tdb_lock_nonblock(tdb, -1, F_WRLCK); + ok(ret == 0, "tdb_lock_nonblock should succeed"); + + ret = tdb_unlock(tdb, -1, F_WRLCK); + ok(ret == 0, "tdb_unlock should succeed"); + + ret = tdb_store(tdb, key, data, TDB_REPLACE); + ok(ret == 0, "tdb_store should succeed"); + + ret = tdb_lockall_unmark(tdb); + ok(ret == 0, "tdb_lockall_unmark should succeed"); + + nwritten = write(allrecord_down[1], &c, sizeof(c)); + ok(nwritten == sizeof(c), "write should succeed"); + + nread = read(allrecord_up[0], &c, sizeof(c)); + ok(nread == 0, "read should succeed"); + + close(chainlock_up[0]); + close(chainlock_down[1]); + close(allrecord_up[0]); + close(allrecord_down[1]); + diag("%s tests done", name); + return exit_status(); +} + +int main(int argc, char *argv[]) +{ + int ret; + bool mutex_support; + + mutex_support = tdb_runtime_check_for_robust_mutexes(); + + ret = do_tests("marklock-deadlock-fcntl.tdb", + TDB_CLEAR_IF_FIRST | + TDB_INCOMPATIBLE_HASH); + ok(ret == 0, "marklock-deadlock-fcntl.tdb tests should succeed"); + + if (!mutex_support) { + skip(1, "No robust mutex support, " + "skipping marklock-deadlock-mutex.tdb tests"); + return exit_status(); + } + + ret = do_tests("marklock-deadlock-mutex.tdb", + TDB_CLEAR_IF_FIRST | + TDB_MUTEX_LOCKING | + TDB_INCOMPATIBLE_HASH); + ok(ret == 0, "marklock-deadlock-mutex.tdb tests should succeed"); + + return exit_status(); +} diff --git a/test/run-mutex-allrecord-bench.c b/test/run-mutex-allrecord-bench.c new file mode 100644 index 0000000..b81e597 --- /dev/null +++ b/test/run-mutex-allrecord-bench.c @@ -0,0 +1,82 @@ +#include "../common/tdb_private.h" +#include "../common/io.c" +#include "../common/tdb.c" +#include "../common/lock.c" +#include "../common/freelist.c" +#include "../common/traverse.c" +#include "../common/transaction.c" +#include "../common/error.c" +#include "../common/open.c" +#include "../common/check.c" +#include "../common/hash.c" +#include "../common/mutex.c" +#include "tap-interface.h" +#include <stdlib.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <stdarg.h> + +static TDB_DATA key, data; + +static void log_fn(struct tdb_context *tdb, enum tdb_debug_level level, + const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); +} + +static double timeval_elapsed2(const struct timeval *tv1, const struct timeval *tv2) +{ + return (tv2->tv_sec - tv1->tv_sec) + + (tv2->tv_usec - tv1->tv_usec)*1.0e-6; +} + +static double timeval_elapsed(const struct timeval *tv) +{ + struct timeval tv2; + gettimeofday(&tv2, NULL); + return timeval_elapsed2(tv, &tv2); +} + +/* The code should barf on TDBs created with rwlocks. */ +int main(int argc, char *argv[]) +{ + struct tdb_context *tdb; + unsigned int log_count; + struct tdb_logging_context log_ctx = { log_fn, &log_count }; + int ret; + struct timeval start; + double elapsed; + bool runtime_support; + + runtime_support = tdb_runtime_check_for_robust_mutexes(); + + if (!runtime_support) { + skip(1, "No robust mutex support"); + return exit_status(); + } + + key.dsize = strlen("hi"); + key.dptr = discard_const_p(uint8_t, "hi"); + data.dsize = strlen("world"); + data.dptr = discard_const_p(uint8_t, "world"); + + tdb = tdb_open_ex("mutex-allrecord-bench.tdb", 1000000, + TDB_INCOMPATIBLE_HASH| + TDB_MUTEX_LOCKING| + TDB_CLEAR_IF_FIRST, + O_RDWR|O_CREAT, 0755, &log_ctx, NULL); + ok(tdb, "tdb_open_ex should succeed"); + + gettimeofday(&start, NULL); + ret = tdb_allrecord_lock(tdb, F_WRLCK, TDB_LOCK_WAIT, false); + elapsed = timeval_elapsed(&start); + + ok(ret == 0, "tdb_allrecord_lock should succeed"); + + diag("allrecord_lock took %f seconds", elapsed); + + return exit_status(); +} diff --git a/test/run-mutex-allrecord-block.c b/test/run-mutex-allrecord-block.c new file mode 100644 index 0000000..fcd3b4f --- /dev/null +++ b/test/run-mutex-allrecord-block.c @@ -0,0 +1,120 @@ +#include "../common/tdb_private.h" +#include "../common/io.c" +#include "../common/tdb.c" +#include "../common/lock.c" +#include "../common/freelist.c" +#include "../common/traverse.c" +#include "../common/transaction.c" +#include "../common/error.c" +#include "../common/open.c" +#include "../common/check.c" +#include "../common/hash.c" +#include "../common/mutex.c" +#include "tap-interface.h" +#include <stdlib.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <stdarg.h> + +static TDB_DATA key, data; + +static void log_fn(struct tdb_context *tdb, enum tdb_debug_level level, + const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); +} + +static int do_child(int tdb_flags, int to, int from) +{ + struct tdb_context *tdb; + unsigned int log_count; + struct tdb_logging_context log_ctx = { log_fn, &log_count }; + int ret; + char c = 0; + + tdb = tdb_open_ex("mutex-allrecord-block.tdb", 3, tdb_flags, + O_RDWR|O_CREAT, 0755, &log_ctx, NULL); + ok(tdb, "tdb_open_ex should succeed"); + + ret = tdb_allrecord_lock(tdb, F_WRLCK, TDB_LOCK_WAIT, false); + ok(ret == 0, "tdb_allrecord_lock should succeed"); + + write(to, &c, sizeof(c)); + + read(from, &c, sizeof(c)); + + ret = tdb_allrecord_unlock(tdb, F_WRLCK, false); + ok(ret == 0, "tdb_allrecord_unlock should succeed"); + + return 0; +} + +/* The code should barf on TDBs created with rwlocks. */ +int main(int argc, char *argv[]) +{ + struct tdb_context *tdb; + unsigned int log_count; + struct tdb_logging_context log_ctx = { log_fn, &log_count }; + int ret, status; + pid_t child, wait_ret; + int fromchild[2]; + int tochild[2]; + char c; + int tdb_flags; + bool runtime_support; + + runtime_support = tdb_runtime_check_for_robust_mutexes(); + + if (!runtime_support) { + skip(1, "No robust mutex support"); + return exit_status(); + } + + key.dsize = strlen("hi"); + key.dptr = discard_const_p(uint8_t, "hi"); + data.dsize = strlen("world"); + data.dptr = discard_const_p(uint8_t, "world"); + + pipe(fromchild); + pipe(tochild); + + tdb_flags = TDB_INCOMPATIBLE_HASH| + TDB_MUTEX_LOCKING| + TDB_CLEAR_IF_FIRST; + + child = fork(); + if (child == 0) { + close(fromchild[0]); + close(tochild[1]); + return do_child(tdb_flags, fromchild[1], tochild[0]); + } + close(fromchild[1]); + close(tochild[0]); + + read(fromchild[0], &c, sizeof(c)); + + tdb = tdb_open_ex("mutex-allrecord-block.tdb", 0, + tdb_flags, O_RDWR|O_CREAT, 0755, + &log_ctx, NULL); + ok(tdb, "tdb_open_ex should succeed"); + + ret = tdb_chainlock_nonblock(tdb, key); + ok(ret == -1, "tdb_chainlock_nonblock should not succeed"); + + write(tochild[1], &c, sizeof(c)); + + ret = tdb_chainlock(tdb, key); + ok(ret == 0, "tdb_chainlock should not succeed"); + + ret = tdb_chainunlock(tdb, key); + ok(ret == 0, "tdb_chainunlock should succeed"); + + wait_ret = wait(&status); + ok(wait_ret == child, "child should have exited correctly"); + + diag("done"); + return exit_status(); +} diff --git a/test/run-mutex-allrecord-trylock.c b/test/run-mutex-allrecord-trylock.c new file mode 100644 index 0000000..4b683db --- /dev/null +++ b/test/run-mutex-allrecord-trylock.c @@ -0,0 +1,113 @@ +#include "../common/tdb_private.h" +#include "../common/io.c" +#include "../common/tdb.c" +#include "../common/lock.c" +#include "../common/freelist.c" +#include "../common/traverse.c" +#include "../common/transaction.c" +#include "../common/error.c" +#include "../common/open.c" +#include "../common/check.c" +#include "../common/hash.c" +#include "../common/mutex.c" +#include "tap-interface.h" +#include <stdlib.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <stdarg.h> + +static TDB_DATA key, data; + +static void log_fn(struct tdb_context *tdb, enum tdb_debug_level level, + const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); +} + +static int do_child(int tdb_flags, int to, int from) +{ + struct tdb_context *tdb; + unsigned int log_count; + struct tdb_logging_context log_ctx = { log_fn, &log_count }; + int ret; + char c = 0; + + tdb = tdb_open_ex("mutex-allrecord-trylock.tdb", 3, tdb_flags, + O_RDWR|O_CREAT, 0755, &log_ctx, NULL); + ok(tdb, "tdb_open_ex should succeed"); + + ret = tdb_chainlock(tdb, key); + ok(ret == 0, "tdb_chainlock should succeed"); + + write(to, &c, sizeof(c)); + + read(from, &c, sizeof(c)); + + ret = tdb_chainunlock(tdb, key); + ok(ret == 0, "tdb_chainunlock should succeed"); + + return 0; +} + +/* The code should barf on TDBs created with rwlocks. */ +int main(int argc, char *argv[]) +{ + struct tdb_context *tdb; + unsigned int log_count; + struct tdb_logging_context log_ctx = { log_fn, &log_count }; + int ret, status; + pid_t child, wait_ret; + int fromchild[2]; + int tochild[2]; + char c; + int tdb_flags; + bool runtime_support; + + runtime_support = tdb_runtime_check_for_robust_mutexes(); + + if (!runtime_support) { + skip(1, "No robust mutex support"); + return exit_status(); + } + + key.dsize = strlen("hi"); + key.dptr = discard_const_p(uint8_t, "hi"); + data.dsize = strlen("world"); + data.dptr = discard_const_p(uint8_t, "world"); + + pipe(fromchild); + pipe(tochild); + + tdb_flags = TDB_INCOMPATIBLE_HASH| + TDB_MUTEX_LOCKING| + TDB_CLEAR_IF_FIRST; + + child = fork(); + if (child == 0) { + close(fromchild[0]); + close(tochild[1]); + return do_child(tdb_flags, fromchild[1], tochild[0]); + } + close(fromchild[1]); + close(tochild[0]); + + read(fromchild[0], &c, sizeof(c)); + + tdb = tdb_open_ex("mutex-allrecord-trylock.tdb", 0, tdb_flags, + O_RDWR|O_CREAT, 0755, &log_ctx, NULL); + ok(tdb, "tdb_open_ex should succeed"); + + ret = tdb_allrecord_lock(tdb, F_WRLCK, TDB_LOCK_NOWAIT, false); + ok(ret == -1, "tdb_allrecord_lock (nowait) should not succeed"); + + write(tochild[1], &c, sizeof(c)); + + wait_ret = wait(&status); + ok(wait_ret == child, "child should have exited correctly"); + + diag("done"); + return exit_status(); +} diff --git a/test/run-mutex-die.c b/test/run-mutex-die.c new file mode 100644 index 0000000..4b8eac1 --- /dev/null +++ b/test/run-mutex-die.c @@ -0,0 +1,269 @@ +#include "../common/tdb_private.h" +#include "lock-tracking.h" +static ssize_t pwrite_check(int fd, const void *buf, size_t count, off_t offset); +static ssize_t write_check(int fd, const void *buf, size_t count); +static int ftruncate_check(int fd, off_t length); + +#define pwrite pwrite_check +#define write write_check +#define fcntl fcntl_with_lockcheck +#define ftruncate ftruncate_check + +#include "../common/io.c" +#include "../common/tdb.c" +#include "../common/lock.c" +#include "../common/freelist.c" +#include "../common/traverse.c" +#include "../common/transaction.c" +#include "../common/error.c" +#include "../common/open.c" +#include "../common/check.c" +#include "../common/hash.c" +#include "../common/mutex.c" +#include "tap-interface.h" +#include <stdlib.h> +#include <stdbool.h> +#include <stdarg.h> +#include "external-agent.h" +#include "logging.h" + +#undef write +#undef pwrite +#undef fcntl +#undef ftruncate + +static int target, current; +#define TEST_DBNAME "run-mutex-die.tdb" +#define KEY_STRING "helloworld" + +static void maybe_die(int fd) +{ + if (target == 0) { + return; + } + current += 1; + if (current == target) { + _exit(1); + } +} + +static ssize_t pwrite_check(int fd, + const void *buf, size_t count, off_t offset) +{ + ssize_t ret; + + maybe_die(fd); + + ret = pwrite(fd, buf, count, offset); + if (ret != count) + return ret; + + maybe_die(fd); + return ret; +} + +static ssize_t write_check(int fd, const void *buf, size_t count) +{ + ssize_t ret; + + maybe_die(fd); + + ret = write(fd, buf, count); + if (ret != count) + return ret; + + maybe_die(fd); + return ret; +} + +static int ftruncate_check(int fd, off_t length) +{ + int ret; + + maybe_die(fd); + + ret = ftruncate(fd, length); + + maybe_die(fd); + return ret; +} + +static enum agent_return flakey_ops(struct agent *a) +{ + enum agent_return ret; + + /* + * Run in the external agent child + */ + + ret = external_agent_operation(a, OPEN_WITH_CLEAR_IF_FIRST, TEST_DBNAME); + if (ret != SUCCESS) { + fprintf(stderr, "Agent failed to open: %s\n", + agent_return_name(ret)); + return ret; + } + ret = external_agent_operation(a, UNMAP, ""); + if (ret != SUCCESS) { + fprintf(stderr, "Agent failed to unmap: %s\n", + agent_return_name(ret)); + return ret; + } + ret = external_agent_operation(a, STORE, "xyz"); + if (ret != SUCCESS) { + fprintf(stderr, "Agent failed to store: %s\n", + agent_return_name(ret)); + return ret; + } + ret = external_agent_operation(a, STORE, KEY_STRING); + if (ret != SUCCESS) { + fprintf(stderr, "Agent failed store: %s\n", + agent_return_name(ret)); + return ret; + } + ret = external_agent_operation(a, FETCH, KEY_STRING); + if (ret != SUCCESS) { + fprintf(stderr, "Agent failed find key: %s\n", + agent_return_name(ret)); + return ret; + } + ret = external_agent_operation(a, PING, ""); + if (ret != SUCCESS) { + fprintf(stderr, "Agent failed ping: %s\n", + agent_return_name(ret)); + return ret; + } + return ret; +} + +static bool prep_db(void) { + struct tdb_context *tdb; + TDB_DATA key; + TDB_DATA data; + + key.dptr = discard_const_p(uint8_t, KEY_STRING); + key.dsize = strlen((char *)key.dptr); + data.dptr = discard_const_p(uint8_t, "foo"); + data.dsize = strlen((char *)data.dptr); + + unlink(TEST_DBNAME); + + tdb = tdb_open_ex( + TEST_DBNAME, 2, + TDB_INCOMPATIBLE_HASH|TDB_MUTEX_LOCKING|TDB_CLEAR_IF_FIRST, + O_CREAT|O_TRUNC|O_RDWR, 0600, &taplogctx, NULL); + if (tdb == NULL) { + return false; + } + + if (tdb_store(tdb, key, data, TDB_INSERT) != 0) { + return false; + } + + tdb_close(tdb); + tdb = NULL; + + forget_locking(); + + return true; +} + +static bool test_db(void) { + struct tdb_context *tdb; + int ret; + + tdb = tdb_open_ex( + TEST_DBNAME, 1024, TDB_INCOMPATIBLE_HASH, + O_RDWR, 0600, &taplogctx, NULL); + + if (tdb == NULL) { + perror("tdb_open_ex failed"); + return false; + } + + ret = tdb_traverse(tdb, NULL, NULL); + if (ret == -1) { + perror("traverse failed"); + goto fail; + } + + tdb_close(tdb); + + forget_locking(); + + return true; + +fail: + tdb_close(tdb); + return false; +} + +static bool test_one(void) +{ + enum agent_return ret; + + ret = AGENT_DIED; + target = 19; + + while (ret != SUCCESS) { + struct agent *agent; + + { + int child_target = target; + bool pret; + target = 0; + pret = prep_db(); + ok1(pret); + target = child_target; + } + + agent = prepare_external_agent(); + + ret = flakey_ops(agent); + + diag("Agent (target=%d) returns %s", + target, agent_return_name(ret)); + + if (ret == SUCCESS) { + ok((target > 19), "At least one AGENT_DIED expected"); + } else { + ok(ret == AGENT_DIED, "AGENT_DIED expected"); + } + + shutdown_agent(agent); + + { + int child_target = target; + bool tret; + target = 0; + tret = test_db(); + ok1(tret); + target = child_target; + } + + target += 1; + } + + return true; +} + +int main(int argc, char *argv[]) +{ + bool ret; + bool runtime_support; + + runtime_support = tdb_runtime_check_for_robust_mutexes(); + + if (!runtime_support) { + skip(1, "No robust mutex support"); + return exit_status(); + } + + plan_tests(12); + unlock_callback = maybe_die; + + ret = test_one(); + ok1(ret); + + diag("done"); + return exit_status(); +} diff --git a/test/run-mutex-openflags2.c b/test/run-mutex-openflags2.c new file mode 100644 index 0000000..89603e6 --- /dev/null +++ b/test/run-mutex-openflags2.c @@ -0,0 +1,146 @@ +#include "../common/tdb_private.h" +#include "../common/io.c" +#include "../common/tdb.c" +#include "../common/lock.c" +#include "../common/freelist.c" +#include "../common/traverse.c" +#include "../common/transaction.c" +#include "../common/error.c" +#include "../common/open.c" +#include "../common/check.c" +#include "../common/hash.c" +#include "../common/mutex.c" +#include "tap-interface.h" +#include <stdlib.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <poll.h> +#include <stdarg.h> + +static TDB_DATA key, data; + +static void log_void(struct tdb_context *tdb, enum tdb_debug_level level, + const char *fmt, ...) +{ +} + +static void log_fn(struct tdb_context *tdb, enum tdb_debug_level level, + const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); +} + +static int do_child(int fd) +{ + struct tdb_context *tdb; + unsigned int log_count; + struct tdb_logging_context log_ctx = { log_fn, &log_count }; + struct tdb_logging_context nolog_ctx = { log_void, NULL }; + char c; + + read(fd, &c, 1); + + tdb = tdb_open_ex("mutex-openflags2.tdb", 0, + TDB_DEFAULT, + O_RDWR|O_CREAT, 0755, &nolog_ctx, NULL); + ok((tdb == NULL) && (errno == EINVAL), "TDB_DEFAULT without " + "TDB_MUTEX_LOCKING should fail with EINVAL - %d", errno); + + tdb = tdb_open_ex("mutex-openflags2.tdb", 0, + TDB_CLEAR_IF_FIRST, + O_RDWR|O_CREAT, 0755, &nolog_ctx, NULL); + ok((tdb == NULL) && (errno == EINVAL), "TDB_CLEAR_IF_FIRST without " + "TDB_MUTEX_LOCKING should fail with EINVAL - %d", errno); + + tdb = tdb_open_ex("mutex-openflags2.tdb", 0, + TDB_CLEAR_IF_FIRST | + TDB_MUTEX_LOCKING | + TDB_INTERNAL, + O_RDWR|O_CREAT, 0755, &nolog_ctx, NULL); + ok((tdb == NULL) && (errno == EINVAL), "TDB_MUTEX_LOCKING with " + "TDB_INTERNAL should fail with EINVAL - %d", errno); + + tdb = tdb_open_ex("mutex-openflags2.tdb", 0, + TDB_CLEAR_IF_FIRST | + TDB_MUTEX_LOCKING | + TDB_NOMMAP, + O_RDWR|O_CREAT, 0755, &nolog_ctx, NULL); + ok((tdb == NULL) && (errno == EINVAL), "TDB_MUTEX_LOCKING with " + "TDB_NOMMAP should fail with EINVAL - %d", errno); + + tdb = tdb_open_ex("mutex-openflags2.tdb", 0, + TDB_CLEAR_IF_FIRST | + TDB_MUTEX_LOCKING, + O_RDONLY, 0755, &nolog_ctx, NULL); + ok((tdb != NULL), "TDB_MUTEX_LOCKING with " + "O_RDONLY should work - %d", errno); + tdb_close(tdb); + + tdb = tdb_open_ex("mutex-openflags2.tdb", 0, + TDB_CLEAR_IF_FIRST | + TDB_MUTEX_LOCKING, + O_RDWR|O_CREAT, 0755, &log_ctx, NULL); + ok((tdb != NULL), "TDB_MUTEX_LOCKING with TDB_CLEAR_IF_FIRST" + "TDB_NOMMAP should work - %d", errno); + + return 0; +} + +/* The code should barf on TDBs created with rwlocks. */ +int main(int argc, char *argv[]) +{ + struct tdb_context *tdb; + unsigned int log_count; + struct tdb_logging_context log_ctx = { log_fn, &log_count }; + struct tdb_logging_context nolog_ctx = { log_void, NULL }; + int ret, status; + pid_t child, wait_ret; + int pipefd[2]; + char c = 0; + bool runtime_support; + + runtime_support = tdb_runtime_check_for_robust_mutexes(); + + ret = pipe(pipefd); + ok1(ret == 0); + + key.dsize = strlen("hi"); + key.dptr = discard_const_p(uint8_t, "hi"); + data.dsize = strlen("world"); + data.dptr = discard_const_p(uint8_t, "world"); + + if (!runtime_support) { + tdb = tdb_open_ex("mutex-openflags2.tdb", 0, + TDB_CLEAR_IF_FIRST| + TDB_MUTEX_LOCKING, + O_RDWR|O_CREAT, 0755, &nolog_ctx, NULL); + ok((tdb == NULL) && (errno == ENOSYS), "TDB_MUTEX_LOCKING without " + "runtime support should fail with ENOSYS - %d", errno); + + skip(1, "No robust mutex support"); + return exit_status(); + } + + child = fork(); + if (child == 0) { + return do_child(pipefd[0]); + } + + tdb = tdb_open_ex("mutex-openflags2.tdb", 0, + TDB_CLEAR_IF_FIRST| + TDB_MUTEX_LOCKING, + O_RDWR|O_CREAT, 0755, &log_ctx, NULL); + ok((tdb != NULL), "tdb_open_ex with mutexes should succeed"); + + write(pipefd[1], &c, 1); + + wait_ret = wait(&status); + ok((wait_ret == child) && (status == 0), + "child should have exited correctly"); + + diag("done"); + return exit_status(); +} diff --git a/test/run-mutex-transaction1.c b/test/run-mutex-transaction1.c new file mode 100644 index 0000000..7b9f7b1 --- /dev/null +++ b/test/run-mutex-transaction1.c @@ -0,0 +1,236 @@ +#include "../common/tdb_private.h" +#include "../common/io.c" +#include "../common/tdb.c" +#include "../common/lock.c" +#include "../common/freelist.c" +#include "../common/traverse.c" +#include "../common/transaction.c" +#include "../common/error.c" +#include "../common/open.c" +#include "../common/check.c" +#include "../common/hash.c" +#include "../common/mutex.c" +#include "tap-interface.h" +#include <stdlib.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <stdarg.h> + +static TDB_DATA key, data; + +static void log_fn(struct tdb_context *tdb, enum tdb_debug_level level, + const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); +} + +static int do_child(int tdb_flags, int to, int from) +{ + struct tdb_context *tdb; + unsigned int log_count; + struct tdb_logging_context log_ctx = { log_fn, &log_count }; + int ret; + char c = 0; + + tdb = tdb_open_ex("mutex-transaction1.tdb", 3, tdb_flags, + O_RDWR|O_CREAT, 0755, &log_ctx, NULL); + ok(tdb, "tdb_open_ex should succeed"); + + ret = tdb_transaction_start(tdb); + ok(ret == 0, "tdb_transaction_start should succeed"); + + ret = tdb_store(tdb, key, data, TDB_INSERT); + ok(ret == 0, "tdb_store(tdb, key, data, TDB_INSERT) should succeed"); + + write(to, &c, sizeof(c)); + read(from, &c, sizeof(c)); + + ret = tdb_transaction_cancel(tdb); + ok(ret == 0, "tdb_transaction_cancel should succeed"); + + write(to, &c, sizeof(c)); + read(from, &c, sizeof(c)); + + ret = tdb_transaction_start(tdb); + ok(ret == 0, "tdb_transaction_start should succeed"); + + ret = tdb_store(tdb, key, data, TDB_INSERT); + ok(ret == 0, "tdb_store(tdb, key, data, TDB_INSERT) should succeed"); + + write(to, &c, sizeof(c)); + read(from, &c, sizeof(c)); + + ret = tdb_transaction_commit(tdb); + ok(ret == 0, "tdb_transaction_commit should succeed"); + + write(to, &c, sizeof(c)); + read(from, &c, sizeof(c)); + + ret = tdb_transaction_start(tdb); + ok(ret == 0, "tdb_transaction_start should succeed"); + + ret = tdb_store(tdb, key, key, TDB_REPLACE); + ok(ret == 0, "tdb_store(tdb, key, data, TDB_REPLACE) should succeed"); + + write(to, &c, sizeof(c)); + read(from, &c, sizeof(c)); + + ret = tdb_transaction_commit(tdb); + ok(ret == 0, "tdb_transaction_commit should succeed"); + + write(to, &c, sizeof(c)); + read(from, &c, sizeof(c)); + + return 0; +} + +/* The code should barf on TDBs created with rwlocks. */ +int main(int argc, char *argv[]) +{ + struct tdb_context *tdb; + unsigned int log_count; + struct tdb_logging_context log_ctx = { log_fn, &log_count }; + int ret, status; + pid_t child, wait_ret; + int fromchild[2]; + int tochild[2]; + TDB_DATA val; + char c; + int tdb_flags; + bool runtime_support; + + runtime_support = tdb_runtime_check_for_robust_mutexes(); + + if (!runtime_support) { + skip(1, "No robust mutex support"); + return exit_status(); + } + + key.dsize = strlen("hi"); + key.dptr = discard_const_p(uint8_t, "hi"); + data.dsize = strlen("world"); + data.dptr = discard_const_p(uint8_t, "world"); + + pipe(fromchild); + pipe(tochild); + + tdb_flags = TDB_INCOMPATIBLE_HASH| + TDB_MUTEX_LOCKING| + TDB_CLEAR_IF_FIRST; + + child = fork(); + if (child == 0) { + close(fromchild[0]); + close(tochild[1]); + return do_child(tdb_flags, fromchild[1], tochild[0]); + } + close(fromchild[1]); + close(tochild[0]); + + read(fromchild[0], &c, sizeof(c)); + + tdb = tdb_open_ex("mutex-transaction1.tdb", 0, + tdb_flags, O_RDWR|O_CREAT, 0755, + &log_ctx, NULL); + ok(tdb, "tdb_open_ex should succeed"); + + /* + * The child has the transaction running + */ + ret = tdb_transaction_start_nonblock(tdb); + ok(ret == -1, "tdb_transaction_start_nonblock not succeed"); + + ret = tdb_chainlock_nonblock(tdb, key); + ok(ret == -1, "tdb_chainlock_nonblock should not succeed"); + + /* + * We can still read + */ + ret = tdb_exists(tdb, key); + ok(ret == 0, "tdb_exists(tdb, key) should return 0"); + + val = tdb_fetch(tdb, key); + ok(val.dsize == 0, "tdb_fetch(tdb, key) should return an empty value"); + + write(tochild[1], &c, sizeof(c)); + + /* + * When the child canceled we can start... + */ + ret = tdb_transaction_start(tdb); + ok(ret == 0, "tdb_transaction_start should succeed"); + + read(fromchild[0], &c, sizeof(c)); + write(tochild[1], &c, sizeof(c)); + + ret = tdb_transaction_cancel(tdb); + ok(ret == 0, "tdb_transaction_cancel should succeed"); + + /* + * When we canceled the child can start and store... + */ + read(fromchild[0], &c, sizeof(c)); + + /* + * We still see the old values before the child commits... + */ + ret = tdb_exists(tdb, key); + ok(ret == 0, "tdb_exists(tdb, key) should return 0"); + + val = tdb_fetch(tdb, key); + ok(val.dsize == 0, "tdb_fetch(tdb, key) should return an empty value"); + + write(tochild[1], &c, sizeof(c)); + read(fromchild[0], &c, sizeof(c)); + + /* + * We see the new values after the commit... + */ + ret = tdb_exists(tdb, key); + ok(ret == 1, "tdb_exists(tdb, key) should return 1"); + + val = tdb_fetch(tdb, key); + ok(val.dsize != 0, "tdb_fetch(tdb, key) should return a value"); + ok(val.dsize == data.dsize, "tdb_fetch(tdb, key) should return a value"); + ok(memcmp(val.dptr, data.dptr, data.dsize) == 0, "tdb_fetch(tdb, key) should return a value"); + + write(tochild[1], &c, sizeof(c)); + read(fromchild[0], &c, sizeof(c)); + + /* + * The child started a new transaction and replaces the value, + * but we still see the old values before the child commits... + */ + ret = tdb_exists(tdb, key); + ok(ret == 1, "tdb_exists(tdb, key) should return 1"); + + val = tdb_fetch(tdb, key); + ok(val.dsize != 0, "tdb_fetch(tdb, key) should return a value"); + ok(val.dsize == data.dsize, "tdb_fetch(tdb, key) should return a value"); + ok(memcmp(val.dptr, data.dptr, data.dsize) == 0, "tdb_fetch(tdb, key) should return a value"); + + write(tochild[1], &c, sizeof(c)); + read(fromchild[0], &c, sizeof(c)); + + /* + * We see the new values after the commit... + */ + ret = tdb_exists(tdb, key); + ok(ret == 1, "tdb_exists(tdb, key) should return 1"); + + val = tdb_fetch(tdb, key); + ok(val.dsize != 0, "tdb_fetch(tdb, key) should return a value"); + ok(val.dsize == key.dsize, "tdb_fetch(tdb, key) should return a value"); + ok(memcmp(val.dptr, key.dptr, key.dsize) == 0, "tdb_fetch(tdb, key) should return a value"); + + write(tochild[1], &c, sizeof(c)); + + wait_ret = wait(&status); + ok(wait_ret == child, "child should have exited correctly"); + + diag("done"); + return exit_status(); +} diff --git a/test/run-mutex-trylock.c b/test/run-mutex-trylock.c new file mode 100644 index 0000000..c96b635 --- /dev/null +++ b/test/run-mutex-trylock.c @@ -0,0 +1,122 @@ +#include "../common/tdb_private.h" +#include "../common/io.c" +#include "../common/tdb.c" +#include "../common/lock.c" +#include "../common/freelist.c" +#include "../common/traverse.c" +#include "../common/transaction.c" +#include "../common/error.c" +#include "../common/open.c" +#include "../common/check.c" +#include "../common/hash.c" +#include "../common/mutex.c" +#include "tap-interface.h" +#include <stdlib.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <stdarg.h> + +static TDB_DATA key, data; + +static void log_fn(struct tdb_context *tdb, enum tdb_debug_level level, + const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); +} + +static int do_child(int tdb_flags, int to, int from) +{ + struct tdb_context *tdb; + unsigned int log_count; + struct tdb_logging_context log_ctx = { log_fn, &log_count }; + int ret; + char c = 0; + + tdb = tdb_open_ex("mutex-trylock.tdb", 0, tdb_flags, + O_RDWR|O_CREAT, 0755, &log_ctx, NULL); + ok(tdb, "tdb_open_ex should succeed"); + + ret = tdb_chainlock(tdb, key); + ok(ret == 0, "tdb_chainlock should succeed"); + + write(to, &c, sizeof(c)); + + read(from, &c, sizeof(c)); + + ret = tdb_chainunlock(tdb, key); + ok(ret == 0, "tdb_chainunlock should succeed"); + + write(to, &c, sizeof(c)); + + return 0; +} + +/* The code should barf on TDBs created with rwlocks. */ +int main(int argc, char *argv[]) +{ + struct tdb_context *tdb; + unsigned int log_count; + struct tdb_logging_context log_ctx = { log_fn, &log_count }; + int ret, status; + pid_t child, wait_ret; + int fromchild[2]; + int tochild[2]; + char c; + int tdb_flags; + bool runtime_support; + + runtime_support = tdb_runtime_check_for_robust_mutexes(); + + if (!runtime_support) { + skip(1, "No robust mutex support"); + return exit_status(); + } + + key.dsize = strlen("hi"); + key.dptr = discard_const_p(uint8_t, "hi"); + data.dsize = strlen("world"); + data.dptr = discard_const_p(uint8_t, "world"); + + pipe(fromchild); + pipe(tochild); + + tdb_flags = TDB_INCOMPATIBLE_HASH| + TDB_MUTEX_LOCKING| + TDB_CLEAR_IF_FIRST; + + child = fork(); + if (child == 0) { + close(fromchild[0]); + close(tochild[1]); + return do_child(tdb_flags, fromchild[1], tochild[0]); + } + close(fromchild[1]); + close(tochild[0]); + + read(fromchild[0], &c, sizeof(c)); + + tdb = tdb_open_ex("mutex-trylock.tdb", 0, tdb_flags, + O_RDWR|O_CREAT, 0755, &log_ctx, NULL); + ok(tdb, "tdb_open_ex should succeed"); + + ret = tdb_chainlock_nonblock(tdb, key); + ok(ret == -1, "tdb_chainlock_nonblock should not succeed"); + + write(tochild[1], &c, sizeof(c)); + + read(fromchild[0], &c, sizeof(c)); + + ret = tdb_chainlock_nonblock(tdb, key); + ok(ret == 0, "tdb_chainlock_nonblock should succeed"); + ret = tdb_chainunlock(tdb, key); + ok(ret == 0, "tdb_chainunlock should succeed"); + + wait_ret = wait(&status); + ok(wait_ret == child, "child should have exited correctly"); + + diag("done"); + return exit_status(); +} diff --git a/test/run-mutex1.c b/test/run-mutex1.c new file mode 100644 index 0000000..eb75946 --- /dev/null +++ b/test/run-mutex1.c @@ -0,0 +1,138 @@ +#include "../common/tdb_private.h" +#include "../common/io.c" +#include "../common/tdb.c" +#include "../common/lock.c" +#include "../common/freelist.c" +#include "../common/traverse.c" +#include "../common/transaction.c" +#include "../common/error.c" +#include "../common/open.c" +#include "../common/check.c" +#include "../common/hash.c" +#include "../common/mutex.c" +#include "tap-interface.h" +#include <stdlib.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <stdarg.h> + +static TDB_DATA key, data; + +static void log_fn(struct tdb_context *tdb, enum tdb_debug_level level, + const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); +} + +static int do_child(int tdb_flags, int to, int from) +{ + struct tdb_context *tdb; + unsigned int log_count; + struct tdb_logging_context log_ctx = { log_fn, &log_count }; + int ret; + char c = 0; + + tdb = tdb_open_ex("mutex1.tdb", 0, tdb_flags, + O_RDWR|O_CREAT, 0755, &log_ctx, NULL); + ok(tdb, "tdb_open_ex should succeed"); + + ret = tdb_chainlock(tdb, key); + ok(ret == 0, "tdb_chainlock should succeed"); + + write(to, &c, sizeof(c)); + read(from, &c, sizeof(c)); + + ret = tdb_chainunlock(tdb, key); + ok(ret == 0, "tdb_chainunlock should succeed"); + + write(to, &c, sizeof(c)); + read(from, &c, sizeof(c)); + + ret = tdb_allrecord_lock(tdb, F_WRLCK, TDB_LOCK_WAIT, false); + ok(ret == 0, "tdb_allrecord_lock should succeed"); + + write(to, &c, sizeof(c)); + read(from, &c, sizeof(c)); + + ret = tdb_allrecord_unlock(tdb, F_WRLCK, false); + ok(ret == 0, "tdb_allrecord_lock should succeed"); + + return 0; +} + +/* The code should barf on TDBs created with rwlocks. */ +int main(int argc, char *argv[]) +{ + struct tdb_context *tdb; + unsigned int log_count; + struct tdb_logging_context log_ctx = { log_fn, &log_count }; + int ret, status; + pid_t child, wait_ret; + int fromchild[2]; + int tochild[2]; + char c; + int tdb_flags; + bool runtime_support; + + runtime_support = tdb_runtime_check_for_robust_mutexes(); + + if (!runtime_support) { + skip(1, "No robust mutex support"); + return exit_status(); + } + + key.dsize = strlen("hi"); + key.dptr = discard_const_p(uint8_t, "hi"); + data.dsize = strlen("world"); + data.dptr = discard_const_p(uint8_t, "world"); + + pipe(fromchild); + pipe(tochild); + + tdb_flags = TDB_INCOMPATIBLE_HASH| + TDB_MUTEX_LOCKING| + TDB_CLEAR_IF_FIRST; + + child = fork(); + if (child == 0) { + close(fromchild[0]); + close(tochild[1]); + return do_child(tdb_flags, fromchild[1], tochild[0]); + } + close(fromchild[1]); + close(tochild[0]); + + read(fromchild[0], &c, sizeof(c)); + + tdb = tdb_open_ex("mutex1.tdb", 0, tdb_flags, + O_RDWR|O_CREAT, 0755, &log_ctx, NULL); + ok(tdb, "tdb_open_ex should succeed"); + + write(tochild[1], &c, sizeof(c)); + read(fromchild[0], &c, sizeof(c)); + + ret = tdb_allrecord_lock(tdb, F_WRLCK, TDB_LOCK_WAIT, false); + ok(ret == 0, "tdb_allrecord_lock should succeed"); + + ret = tdb_store(tdb, key, data, 0); + ok(ret == 0, "tdb_store should succeed"); + + ret = tdb_allrecord_unlock(tdb, F_WRLCK, false); + ok(ret == 0, "tdb_allrecord_unlock should succeed"); + + write(tochild[1], &c, sizeof(c)); + read(fromchild[0], &c, sizeof(c)); + write(tochild[1], &c, sizeof(c)); + + ret = tdb_delete(tdb, key); + ok(ret == 0, "tdb_delete should succeed"); + + wait_ret = wait(&status); + ok(wait_ret == child, "child should have exited correctly"); + + diag("done"); + return exit_status(); +} diff --git a/test/run-nested-transactions.c b/test/run-nested-transactions.c new file mode 100644 index 0000000..864adf2 --- /dev/null +++ b/test/run-nested-transactions.c @@ -0,0 +1,79 @@ +#include "../common/tdb_private.h" +#include "../common/io.c" +#include "../common/tdb.c" +#include "../common/lock.c" +#include "../common/freelist.c" +#include "../common/traverse.c" +#include "../common/transaction.c" +#include "../common/error.c" +#include "../common/open.c" +#include "../common/check.c" +#include "../common/hash.c" +#include "../common/mutex.c" +#include "tap-interface.h" +#include <stdlib.h> +#include <stdbool.h> +#include "logging.h" + +int main(int argc, char *argv[]) +{ + struct tdb_context *tdb; + TDB_DATA key, data; + + plan_tests(27); + key.dsize = strlen("hi"); + key.dptr = discard_const_p(uint8_t, "hi"); + + tdb = tdb_open_ex("run-nested-transactions.tdb", + 1024, TDB_CLEAR_IF_FIRST|TDB_DISALLOW_NESTING, + O_CREAT|O_TRUNC|O_RDWR, 0600, &taplogctx, NULL); + ok1(tdb); + + /* Nesting disallowed. */ + ok1(tdb_transaction_start(tdb) == 0); + data.dptr = discard_const_p(uint8_t, "world"); + data.dsize = strlen("world"); + ok1(tdb_store(tdb, key, data, TDB_INSERT) == 0); + data = tdb_fetch(tdb, key); + ok1(data.dsize == strlen("world")); + ok1(memcmp(data.dptr, "world", strlen("world")) == 0); + free(data.dptr); + ok1(tdb_transaction_start(tdb) != 0); + ok1(tdb_error(tdb) == TDB_ERR_NESTING); + + data = tdb_fetch(tdb, key); + ok1(data.dsize == strlen("world")); + ok1(memcmp(data.dptr, "world", strlen("world")) == 0); + free(data.dptr); + ok1(tdb_transaction_commit(tdb) == 0); + data = tdb_fetch(tdb, key); + ok1(data.dsize == strlen("world")); + ok1(memcmp(data.dptr, "world", strlen("world")) == 0); + free(data.dptr); + tdb_close(tdb); + + /* Nesting allowed by default */ + tdb = tdb_open_ex("run-nested-transactions.tdb", + 1024, TDB_DEFAULT, O_RDWR, 0, &taplogctx, NULL); + ok1(tdb); + + ok1(tdb_transaction_start(tdb) == 0); + ok1(tdb_transaction_start(tdb) == 0); + ok1(tdb_delete(tdb, key) == 0); + ok1(tdb_transaction_commit(tdb) == 0); + ok1(!tdb_exists(tdb, key)); + ok1(tdb_transaction_cancel(tdb) == 0); + /* Surprise! Kills inner "committed" transaction. */ + ok1(tdb_exists(tdb, key)); + + ok1(tdb_transaction_start(tdb) == 0); + ok1(tdb_transaction_start(tdb) == 0); + ok1(tdb_delete(tdb, key) == 0); + ok1(tdb_transaction_commit(tdb) == 0); + ok1(!tdb_exists(tdb, key)); + ok1(tdb_transaction_commit(tdb) == 0); + ok1(!tdb_exists(tdb, key)); + tdb_close(tdb); + + return exit_status(); +} diff --git a/test/run-nested-traverse.c b/test/run-nested-traverse.c new file mode 100644 index 0000000..aeaa085 --- /dev/null +++ b/test/run-nested-traverse.c @@ -0,0 +1,111 @@ +#include "../common/tdb_private.h" +#include "lock-tracking.h" +#define fcntl fcntl_with_lockcheck +#include "../common/io.c" +#include "../common/tdb.c" +#include "../common/lock.c" +#include "../common/freelist.c" +#include "../common/traverse.c" +#include "../common/transaction.c" +#include "../common/error.c" +#include "../common/open.c" +#include "../common/check.c" +#include "../common/hash.c" +#include "../common/mutex.c" +#include "tap-interface.h" +#undef fcntl +#include <stdlib.h> +#include <stdbool.h> +#include "external-agent.h" +#include "logging.h" + +static struct agent *agent; + +static bool correct_key(TDB_DATA key) +{ + return key.dsize == strlen("hi") + && memcmp(key.dptr, "hi", key.dsize) == 0; +} + +static bool correct_data(TDB_DATA data) +{ + return data.dsize == strlen("world") + && memcmp(data.dptr, "world", data.dsize) == 0; +} + +static int traverse2(struct tdb_context *tdb, TDB_DATA key, TDB_DATA data, + void *p) +{ + ok1(correct_key(key)); + ok1(correct_data(data)); + return 0; +} + +static int traverse1r(struct tdb_context *tdb, TDB_DATA key, TDB_DATA data, + void *p) +{ + ok1(correct_key(key)); + ok1(correct_data(data)); + ok1(external_agent_operation(agent, TRANSACTION_START, tdb_name(tdb)) + == SUCCESS); + ok1(external_agent_operation(agent, STORE, tdb_name(tdb)) + == SUCCESS); + ok1(external_agent_operation(agent, TRANSACTION_COMMIT, tdb_name(tdb)) + == WOULD_HAVE_BLOCKED); + tdb_traverse(tdb, traverse2, NULL); + + /* That should *not* release the all-records lock! */ + ok1(external_agent_operation(agent, TRANSACTION_START, tdb_name(tdb)) + == SUCCESS); + ok1(external_agent_operation(agent, STORE, tdb_name(tdb)) + == SUCCESS); + ok1(external_agent_operation(agent, TRANSACTION_COMMIT, tdb_name(tdb)) + == WOULD_HAVE_BLOCKED); + return 0; +} + +static int traverse1w(struct tdb_context *tdb, TDB_DATA key, TDB_DATA data, + void *p) +{ + ok1(correct_key(key)); + ok1(correct_data(data)); + ok1(external_agent_operation(agent, TRANSACTION_START, tdb_name(tdb)) + == WOULD_HAVE_BLOCKED); + tdb_traverse(tdb, traverse2, NULL); + + /* That should *not* release the all-records lock! */ + ok1(external_agent_operation(agent, TRANSACTION_START, tdb_name(tdb)) + == WOULD_HAVE_BLOCKED); + return 0; +} + +int main(int argc, char *argv[]) +{ + struct tdb_context *tdb; + TDB_DATA key, data; + + plan_tests(17); + agent = prepare_external_agent(); + + tdb = tdb_open_ex("run-nested-traverse.tdb", 1024, TDB_CLEAR_IF_FIRST, + O_CREAT|O_TRUNC|O_RDWR, 0600, &taplogctx, NULL); + ok1(tdb); + + ok1(external_agent_operation(agent, OPEN, tdb_name(tdb)) == SUCCESS); + ok1(external_agent_operation(agent, TRANSACTION_START, tdb_name(tdb)) + == SUCCESS); + ok1(external_agent_operation(agent, TRANSACTION_COMMIT, tdb_name(tdb)) + == SUCCESS); + + key.dsize = strlen("hi"); + key.dptr = discard_const_p(uint8_t, "hi"); + data.dptr = discard_const_p(uint8_t, "world"); + data.dsize = strlen("world"); + + ok1(tdb_store(tdb, key, data, TDB_INSERT) == 0); + tdb_traverse(tdb, traverse1w, NULL); + tdb_traverse_read(tdb, traverse1r, NULL); + tdb_close(tdb); + + return exit_status(); +} diff --git a/test/run-no-lock-during-traverse.c b/test/run-no-lock-during-traverse.c new file mode 100644 index 0000000..737a32f --- /dev/null +++ b/test/run-no-lock-during-traverse.c @@ -0,0 +1,114 @@ +#include "../common/tdb_private.h" +#include "lock-tracking.h" + +#define fcntl fcntl_with_lockcheck + +#include "../common/io.c" +#include "../common/tdb.c" +#include "../common/lock.c" +#include "../common/freelist.c" +#include "../common/traverse.c" +#include "../common/transaction.c" +#include "../common/error.c" +#include "../common/open.c" +#include "../common/check.c" +#include "../common/hash.c" +#include "../common/mutex.c" +#include "tap-interface.h" +#include <stdlib.h> +#include "logging.h" + +#undef fcntl + +#define NUM_ENTRIES 10 + +static bool prepare_entries(struct tdb_context *tdb) +{ + unsigned int i; + TDB_DATA key, data; + + for (i = 0; i < NUM_ENTRIES; i++) { + key.dsize = sizeof(i); + key.dptr = (void *)&i; + data.dsize = strlen("world"); + data.dptr = discard_const_p(uint8_t, "world"); + + if (tdb_store(tdb, key, data, 0) != 0) + return false; + } + return true; +} + +static void delete_entries(struct tdb_context *tdb) +{ + unsigned int i; + TDB_DATA key; + + for (i = 0; i < NUM_ENTRIES; i++) { + key.dsize = sizeof(i); + key.dptr = (void *)&i; + + ok1(tdb_delete(tdb, key) == 0); + } +} + +/* We don't know how many times this will run. */ +static int delete_other(struct tdb_context *tdb, TDB_DATA key, TDB_DATA data, + void *private_data) +{ + unsigned int i; + memcpy(&i, key.dptr, 4); + i = (i + 1) % NUM_ENTRIES; + key.dptr = (void *)&i; + if (tdb_delete(tdb, key) != 0) + (*(int *)private_data)++; + return 0; +} + +static int delete_self(struct tdb_context *tdb, TDB_DATA key, TDB_DATA data, + void *private_data) +{ + ok1(tdb_delete(tdb, key) == 0); + return 0; +} + +int main(int argc, char *argv[]) +{ + struct tdb_context *tdb; + int errors = 0; + + plan_tests(41); + tdb = tdb_open_ex("run-no-lock-during-traverse.tdb", + 1024, TDB_CLEAR_IF_FIRST, O_CREAT|O_TRUNC|O_RDWR, + 0600, &taplogctx, NULL); + + ok1(tdb); + ok1(prepare_entries(tdb)); + ok1(locking_errors == 0); + ok1(tdb_lockall(tdb) == 0); + ok1(locking_errors == 0); + tdb_traverse(tdb, delete_other, &errors); + ok1(errors == 0); + ok1(locking_errors == 0); + ok1(tdb_unlockall(tdb) == 0); + + ok1(prepare_entries(tdb)); + ok1(locking_errors == 0); + ok1(tdb_lockall(tdb) == 0); + ok1(locking_errors == 0); + tdb_traverse(tdb, delete_self, NULL); + ok1(locking_errors == 0); + ok1(tdb_unlockall(tdb) == 0); + + ok1(prepare_entries(tdb)); + ok1(locking_errors == 0); + ok1(tdb_lockall(tdb) == 0); + ok1(locking_errors == 0); + delete_entries(tdb); + ok1(locking_errors == 0); + ok1(tdb_unlockall(tdb) == 0); + + ok1(tdb_close(tdb) == 0); + + return exit_status(); +} diff --git a/test/run-oldhash.c b/test/run-oldhash.c new file mode 100644 index 0000000..aaee6f6 --- /dev/null +++ b/test/run-oldhash.c @@ -0,0 +1,50 @@ +#include "../common/tdb_private.h" +#include "../common/io.c" +#include "../common/tdb.c" +#include "../common/lock.c" +#include "../common/freelist.c" +#include "../common/traverse.c" +#include "../common/transaction.c" +#include "../common/error.c" +#include "../common/open.c" +#include "../common/check.c" +#include "../common/hash.c" +#include "../common/mutex.c" +#include "tap-interface.h" +#include <stdlib.h> +#include "logging.h" + +int main(int argc, char *argv[]) +{ + struct tdb_context *tdb; + + plan_tests(8); + + /* Old format (with zeroes in the hash magic fields) should + * open with any hash (since we don't know what hash they used). */ + tdb = tdb_open_ex("test/old-nohash-le.tdb", 0, 0, O_RDWR, 0, + &taplogctx, NULL); + ok1(tdb); + ok1(tdb_check(tdb, NULL, NULL) == 0); + tdb_close(tdb); + + tdb = tdb_open_ex("test/old-nohash-be.tdb", 0, 0, O_RDWR, 0, + &taplogctx, NULL); + ok1(tdb); + ok1(tdb_check(tdb, NULL, NULL) == 0); + tdb_close(tdb); + + tdb = tdb_open_ex("test/old-nohash-le.tdb", 0, 0, O_RDWR, 0, + &taplogctx, tdb_jenkins_hash); + ok1(tdb); + ok1(tdb_check(tdb, NULL, NULL) == 0); + tdb_close(tdb); + + tdb = tdb_open_ex("test/old-nohash-be.tdb", 0, 0, O_RDWR, 0, + &taplogctx, tdb_jenkins_hash); + ok1(tdb); + ok1(tdb_check(tdb, NULL, NULL) == 0); + tdb_close(tdb); + + return exit_status(); +} diff --git a/test/run-open-during-transaction.c b/test/run-open-during-transaction.c new file mode 100644 index 0000000..9a6c6c1 --- /dev/null +++ b/test/run-open-during-transaction.c @@ -0,0 +1,183 @@ +#include "../common/tdb_private.h" +#include "lock-tracking.h" + +static ssize_t pwrite_check(int fd, const void *buf, size_t count, off_t offset); +static ssize_t write_check(int fd, const void *buf, size_t count); +static int ftruncate_check(int fd, off_t length); + +#define pwrite pwrite_check +#define write write_check +#define fcntl fcntl_with_lockcheck +#define ftruncate ftruncate_check + +#include "../common/io.c" +#include "../common/tdb.c" +#include "../common/lock.c" +#include "../common/freelist.c" +#include "../common/traverse.c" +#include "../common/transaction.c" +#include "../common/error.c" +#include "../common/open.c" +#include "../common/check.c" +#include "../common/hash.c" +#include "../common/mutex.c" +#include "tap-interface.h" +#include <stdlib.h> +#include <stdbool.h> +#include <stdarg.h> +#include "external-agent.h" +#include "logging.h" + +static struct agent *agent; +static bool opened; +static int errors = 0; +static bool clear_if_first; +#define TEST_DBNAME "run-open-during-transaction.tdb" + +#undef write +#undef pwrite +#undef fcntl +#undef ftruncate + +static bool is_same(const char *snapshot, const char *latest, off_t len) +{ + unsigned i; + + for (i = 0; i < len; i++) { + if (snapshot[i] != latest[i]) + return false; + } + return true; +} + +static bool compare_file(int fd, const char *snapshot, off_t snapshot_len) +{ + char *contents; + bool same; + + /* over-length read serves as length check. */ + contents = malloc(snapshot_len+1); + same = pread(fd, contents, snapshot_len+1, 0) == snapshot_len + && is_same(snapshot, contents, snapshot_len); + free(contents); + return same; +} + +static void check_file_intact(int fd) +{ + enum agent_return ret; + struct stat st; + char *contents; + + fstat(fd, &st); + contents = malloc(st.st_size); + if (pread(fd, contents, st.st_size, 0) != st.st_size) { + diag("Read fail"); + errors++; + free(contents); + return; + } + + /* Ask agent to open file. */ + ret = external_agent_operation(agent, clear_if_first ? + OPEN_WITH_CLEAR_IF_FIRST : + OPEN, + TEST_DBNAME); + + /* It's OK to open it, but it must not have changed! */ + if (!compare_file(fd, contents, st.st_size)) { + diag("Agent changed file after opening %s", + agent_return_name(ret)); + errors++; + } + + if (ret == SUCCESS) { + ret = external_agent_operation(agent, CLOSE, NULL); + if (ret != SUCCESS) { + diag("Agent failed to close tdb: %s", + agent_return_name(ret)); + errors++; + } + } else if (ret != WOULD_HAVE_BLOCKED) { + diag("Agent opening file gave %s", + agent_return_name(ret)); + errors++; + } + + free(contents); +} + +static void after_unlock(int fd) +{ + if (opened) + check_file_intact(fd); +} + +static ssize_t pwrite_check(int fd, + const void *buf, size_t count, off_t offset) +{ + if (opened) + check_file_intact(fd); + + return pwrite(fd, buf, count, offset); +} + +static ssize_t write_check(int fd, const void *buf, size_t count) +{ + if (opened) + check_file_intact(fd); + + return write(fd, buf, count); +} + +static int ftruncate_check(int fd, off_t length) +{ + if (opened) + check_file_intact(fd); + + return ftruncate(fd, length); + +} + +int main(int argc, char *argv[]) +{ + const int flags[] = { TDB_DEFAULT, + TDB_CLEAR_IF_FIRST, + TDB_NOMMAP, + TDB_CLEAR_IF_FIRST | TDB_NOMMAP }; + int i; + struct tdb_context *tdb; + TDB_DATA key, data; + + plan_tests(20); + agent = prepare_external_agent(); + + unlock_callback = after_unlock; + for (i = 0; i < sizeof(flags)/sizeof(flags[0]); i++) { + clear_if_first = (flags[i] & TDB_CLEAR_IF_FIRST); + diag("Test with %s and %s", + clear_if_first ? "CLEAR" : "DEFAULT", + (flags[i] & TDB_NOMMAP) ? "no mmap" : "mmap"); + unlink(TEST_DBNAME); + tdb = tdb_open_ex(TEST_DBNAME, 1024, flags[i], + O_CREAT|O_TRUNC|O_RDWR, 0600, + &taplogctx, NULL); + ok1(tdb); + + opened = true; + ok1(tdb_transaction_start(tdb) == 0); + key.dsize = strlen("hi"); + key.dptr = discard_const_p(uint8_t, "hi"); + data.dptr = discard_const_p(uint8_t, "world"); + data.dsize = strlen("world"); + + ok1(tdb_store(tdb, key, data, TDB_INSERT) == 0); + ok1(tdb_transaction_commit(tdb) == 0); + ok(!errors, "We had %u open errors", errors); + + opened = false; + tdb_close(tdb); + } + + return exit_status(); +} diff --git a/test/run-rdlock-upgrade.c b/test/run-rdlock-upgrade.c new file mode 100644 index 0000000..042001b --- /dev/null +++ b/test/run-rdlock-upgrade.c @@ -0,0 +1,166 @@ +#include "../common/io.c" +#include "../common/tdb.c" +#include "../common/lock.c" +#include "../common/freelist.c" +#include "../common/traverse.c" +#include "../common/transaction.c" +#include "../common/error.c" +#include "../common/open.c" +#include "../common/check.c" +#include "../common/hash.c" +#include "../common/mutex.c" +#include "tap-interface.h" +#include <stdlib.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <stdarg.h> +#include "logging.h" + +static TDB_DATA key, data; + +static void do_chainlock(const char *name, int tdb_flags, int up, int down) +{ + struct tdb_context *tdb; + int ret; + ssize_t nread, nwritten; + char c = 0; + + tdb = tdb_open_ex(name, 3, tdb_flags, + O_RDWR|O_CREAT, 0755, &taplogctx, NULL); + ok(tdb, "tdb_open_ex should succeed"); + + ret = tdb_chainlock_read(tdb, key); + ok(ret == 0, "tdb_chainlock_read should succeed"); + + nwritten = write(up, &c, sizeof(c)); + ok(nwritten == sizeof(c), "write should succeed"); + + nread = read(down, &c, sizeof(c)); + ok(nread == 0, "read should succeed"); + + exit(0); +} + +static void do_trylock(const char *name, int tdb_flags, int up, int down) +{ + struct tdb_context *tdb; + int ret; + ssize_t nread, nwritten; + char c = 0; + + tdb = tdb_open_ex(name, 3, tdb_flags, + O_RDWR|O_CREAT, 0755, &taplogctx, NULL); + ok(tdb, "tdb_open_ex should succeed"); + + /* + * tdb used to have a bug where with fcntl locks an upgrade + * from a readlock to writelock did not check for the + * underlying fcntl lock. Mutexes don't distinguish between + * readlocks and writelocks, so that bug does not apply here. + */ + + ret = tdb_chainlock_read(tdb, key); + ok(ret == 0, "tdb_chainlock_read should succeed"); + + ret = tdb_chainlock_nonblock(tdb, key); + ok(ret == -1, "tdb_chainlock_nonblock should fail"); + + nwritten = write(up, &c, sizeof(c)); + ok(nwritten == sizeof(c), "write should succeed"); + + nread = read(down, &c, sizeof(c)); + ok(nread == 0, "read should succeed"); + + exit(0); +} + +static int do_tests(const char *name, int tdb_flags) +{ + int ret; + pid_t chainlock_child, store_child; + int chainlock_down[2]; + int chainlock_up[2]; + int store_down[2]; + int store_up[2]; + char c; + ssize_t nread; + + key.dsize = strlen("hi"); + key.dptr = discard_const_p(uint8_t, "hi"); + data.dsize = strlen("world"); + data.dptr = discard_const_p(uint8_t, "world"); + + ret = pipe(chainlock_down); + ok(ret == 0, "pipe should succeed"); + + ret = pipe(chainlock_up); + ok(ret == 0, "pipe should succeed"); + + ret = pipe(store_down); + ok(ret == 0, "pipe should succeed"); + + ret = pipe(store_up); + ok(ret == 0, "pipe should succeed"); + + chainlock_child = fork(); + ok(chainlock_child != -1, "fork should succeed"); + + if (chainlock_child == 0) { + close(chainlock_up[0]); + close(chainlock_down[1]); + close(store_up[0]); + close(store_up[1]); + close(store_down[0]); + close(store_down[1]); + do_chainlock(name, tdb_flags, + chainlock_up[1], chainlock_down[0]); + exit(0); + } + close(chainlock_up[1]); + close(chainlock_down[0]); + + nread = read(chainlock_up[0], &c, sizeof(c)); + ok(nread == sizeof(c), "read should succeed"); + + /* + * Now we have a process holding a chain read lock. Start + * another process trying to write lock. This should fail. + */ + + store_child = fork(); + ok(store_child != -1, "fork should succeed"); + + if (store_child == 0) { + close(chainlock_up[0]); + close(chainlock_down[1]); + close(store_up[0]); + close(store_down[1]); + do_trylock(name, tdb_flags, + store_up[1], store_down[0]); + exit(0); + } + close(store_up[1]); + close(store_down[0]); + + nread = read(store_up[0], &c, sizeof(c)); + ok(nread == sizeof(c), "read should succeed"); + + close(chainlock_up[0]); + close(chainlock_down[1]); + close(store_up[0]); + close(store_down[1]); + diag("%s tests done", name); + return exit_status(); +} + +int main(int argc, char *argv[]) +{ + int ret; + + ret = do_tests("rdlock-upgrade.tdb", + TDB_CLEAR_IF_FIRST | + TDB_INCOMPATIBLE_HASH); + ok(ret == 0, "rdlock-upgrade.tdb tests should succeed"); + + return exit_status(); +} diff --git a/test/run-readonly-check.c b/test/run-readonly-check.c new file mode 100644 index 0000000..c5e0f7d --- /dev/null +++ b/test/run-readonly-check.c @@ -0,0 +1,53 @@ +/* We should be able to tdb_check a O_RDONLY tdb, and we were previously allowed + * to tdb_check() inside a transaction (though that's paranoia!). */ +#include "../common/tdb_private.h" +#include "../common/io.c" +#include "../common/tdb.c" +#include "../common/lock.c" +#include "../common/freelist.c" +#include "../common/traverse.c" +#include "../common/transaction.c" +#include "../common/error.c" +#include "../common/open.c" +#include "../common/check.c" +#include "../common/hash.c" +#include "../common/mutex.c" +#include "tap-interface.h" +#include <stdlib.h> +#include "logging.h" + +int main(int argc, char *argv[]) +{ + struct tdb_context *tdb; + TDB_DATA key, data; + + plan_tests(11); + tdb = tdb_open_ex("run-readonly-check.tdb", 1024, + TDB_DEFAULT, + O_CREAT|O_TRUNC|O_RDWR, 0600, &taplogctx, NULL); + + ok1(tdb); + key.dsize = strlen("hi"); + key.dptr = discard_const_p(uint8_t, "hi"); + data.dsize = strlen("world"); + data.dptr = discard_const_p(uint8_t, "world"); + + ok1(tdb_store(tdb, key, data, TDB_INSERT) == 0); + ok1(tdb_check(tdb, NULL, NULL) == 0); + + /* We are also allowed to do a check inside a transaction. */ + ok1(tdb_transaction_start(tdb) == 0); + ok1(tdb_check(tdb, NULL, NULL) == 0); + ok1(tdb_close(tdb) == 0); + + tdb = tdb_open_ex("run-readonly-check.tdb", 1024, + TDB_DEFAULT, O_RDONLY, 0, &taplogctx, NULL); + + ok1(tdb); + ok1(tdb_store(tdb, key, data, TDB_MODIFY) == -1); + ok1(tdb_error(tdb) == TDB_ERR_RDONLY); + ok1(tdb_check(tdb, NULL, NULL) == 0); + ok1(tdb_close(tdb) == 0); + + return exit_status(); +} diff --git a/test/run-rescue-find_entry.c b/test/run-rescue-find_entry.c new file mode 100644 index 0000000..5d6f8f7 --- /dev/null +++ b/test/run-rescue-find_entry.c @@ -0,0 +1,51 @@ +#include "../common/tdb_private.h" +#include "../common/io.c" +#include "../common/tdb.c" +#include "../common/lock.c" +#include "../common/freelist.c" +#include "../common/traverse.c" +#include "../common/transaction.c" +#include "../common/error.c" +#include "../common/open.c" +#include "../common/check.c" +#include "../common/hash.c" +#include "../common/rescue.c" +#include "../common/mutex.c" +#include "tap-interface.h" +#include <stdlib.h> +#include "logging.h" + +#define NUM 20 + +/* Binary searches are deceptively simple: easy to screw up! */ +int main(int argc, char *argv[]) +{ + unsigned int i, j, n; + struct found f[NUM+1]; + struct found_table table; + + /* Set up array for searching. */ + for (i = 0; i < NUM+1; i++) { + f[i].head = i * 3; + } + table.arr = f; + + for (i = 0; i < NUM; i++) { + table.num = i; + for (j = 0; j < (i + 2) * 3; j++) { + n = find_entry(&table, j); + ok1(n <= i); + + /* If we were searching for something too large... */ + if (j > i*3) + ok1(n == i); + else { + /* It must give us something after j */ + ok1(f[n].head >= j); + ok1(n == 0 || f[n-1].head < j); + } + } + } + + return exit_status(); +} diff --git a/test/run-rescue.c b/test/run-rescue.c new file mode 100644 index 0000000..e43f53b --- /dev/null +++ b/test/run-rescue.c @@ -0,0 +1,127 @@ +#include "../common/tdb_private.h" +#include "../common/io.c" +#include "../common/tdb.c" +#include "../common/lock.c" +#include "../common/freelist.c" +#include "../common/traverse.c" +#include "../common/transaction.c" +#include "../common/error.c" +#include "../common/open.c" +#include "../common/check.c" +#include "../common/hash.c" +#include "../common/rescue.c" +#include "../common/mutex.c" +#include "tap-interface.h" +#include <stdlib.h> +#include "logging.h" + +struct walk_data { + TDB_DATA key; + TDB_DATA data; + bool fail; + unsigned count; +}; + +static inline bool tdb_deq(TDB_DATA a, TDB_DATA b) +{ + return a.dsize == b.dsize && memcmp(a.dptr, b.dptr, a.dsize) == 0; +} + +static inline TDB_DATA tdb_mkdata(const void *p, size_t len) +{ + TDB_DATA d; + d.dptr = discard_const_p(uint8_t, p); + d.dsize = len; + return d; +} + +static void walk(TDB_DATA key, TDB_DATA data, void *_wd) +{ + struct walk_data *wd = _wd; + + if (!tdb_deq(key, wd->key)) { + wd->fail = true; + } + + if (!tdb_deq(data, wd->data)) { + wd->fail = true; + } + wd->count++; +} + +static void count_records(TDB_DATA key, TDB_DATA data, void *_wd) +{ + struct walk_data *wd = _wd; + + if (!tdb_deq(key, wd->key) || !tdb_deq(data, wd->data)) + diag("%.*s::%.*s", + (int)key.dsize, key.dptr, (int)data.dsize, data.dptr); + wd->count++; +} + +static void log_fn(struct tdb_context *tdb, enum tdb_debug_level level, const char *fmt, ...) +{ + unsigned int *count = tdb_get_logging_private(tdb); + (*count)++; +} + +int main(int argc, char *argv[]) +{ + struct tdb_context *tdb; + struct walk_data wd; + unsigned int i, size, log_count = 0; + struct tdb_logging_context log_ctx = { log_fn, &log_count }; + + plan_tests(8); + tdb = tdb_open_ex("run-rescue.tdb", 1, TDB_CLEAR_IF_FIRST, + O_CREAT|O_TRUNC|O_RDWR, 0600, &log_ctx, NULL); + + wd.key.dsize = strlen("hi"); + wd.key.dptr = discard_const_p(uint8_t, "hi"); + wd.data.dsize = strlen("world"); + wd.data.dptr = discard_const_p(uint8_t, "world"); + wd.count = 0; + wd.fail = false; + + ok1(tdb_store(tdb, wd.key, wd.data, TDB_INSERT) == 0); + + ok1(tdb_rescue(tdb, walk, &wd) == 0); + ok1(!wd.fail); + ok1(wd.count == 1); + + /* Corrupt the database, walk should either get it or not. */ + size = tdb->map_size; + for (i = sizeof(struct tdb_header); i < size; i++) { + char c; + if (tdb->methods->tdb_read(tdb, i, &c, 1, false) != 0) + fail("Reading offset %i", i); + if (tdb->methods->tdb_write(tdb, i, "X", 1) != 0) + fail("Writing X at offset %i", i); + + wd.count = 0; + if (tdb_rescue(tdb, count_records, &wd) != 0) { + wd.fail = true; + break; + } + /* Could be 0 or 1. */ + if (wd.count > 1) { + wd.fail = true; + break; + } + if (tdb->methods->tdb_write(tdb, i, &c, 1) != 0) + fail("Restoring offset %i", i); + } + ok1(log_count == 0); + ok1(!wd.fail); + tdb_close(tdb); + + /* Now try our known-corrupt db. */ + tdb = tdb_open_ex("test/tdb.corrupt", 1024, 0, O_RDWR, 0, + &taplogctx, NULL); + wd.count = 0; + ok1(tdb_rescue(tdb, count_records, &wd) == 0); + ok1(wd.count == 1627); + tdb_close(tdb); + + return exit_status(); +} diff --git a/test/run-rwlock-check.c b/test/run-rwlock-check.c new file mode 100644 index 0000000..2ac9dc3 --- /dev/null +++ b/test/run-rwlock-check.c @@ -0,0 +1,46 @@ +#include "../common/tdb_private.h" +#include "../common/io.c" +#include "../common/tdb.c" +#include "../common/lock.c" +#include "../common/freelist.c" +#include "../common/traverse.c" +#include "../common/transaction.c" +#include "../common/error.c" +#include "../common/open.c" +#include "../common/check.c" +#include "../common/hash.c" +#include "../common/mutex.c" +#include "tap-interface.h" +#include <stdlib.h> + +static void log_fn(struct tdb_context *tdb, enum tdb_debug_level level, const char *fmt, ...) +{ + unsigned int *count = tdb_get_logging_private(tdb); + if (strstr(fmt, "spinlocks")) + (*count)++; +} + +/* The code should barf on TDBs created with rwlocks. */ +int main(int argc, char *argv[]) +{ + struct tdb_context *tdb; + unsigned int log_count; + struct tdb_logging_context log_ctx = { log_fn, &log_count }; + + plan_tests(4); + + /* We should fail to open rwlock-using tdbs of either endian. */ + log_count = 0; + tdb = tdb_open_ex("test/rwlock-le.tdb", 0, 0, O_RDWR, 0, + &log_ctx, NULL); + ok1(!tdb); + ok1(log_count == 1); + + log_count = 0; + tdb = tdb_open_ex("test/rwlock-be.tdb", 0, 0, O_RDWR, 0, + &log_ctx, NULL); + ok1(!tdb); + ok1(log_count == 1); + + return exit_status(); +} diff --git a/test/run-summary.c b/test/run-summary.c new file mode 100644 index 0000000..8b9a1a0 --- /dev/null +++ b/test/run-summary.c @@ -0,0 +1,65 @@ +#include "../common/tdb_private.h" +#include "../common/io.c" +#include "../common/tdb.c" +#include "../common/lock.c" +#include "../common/freelist.c" +#include "../common/traverse.c" +#include "../common/transaction.c" +#include "../common/error.c" +#include "../common/open.c" +#include "../common/check.c" +#include "../common/hash.c" +#include "../common/summary.c" +#include "../common/mutex.c" +#include "tap-interface.h" +#include <stdlib.h> + +int main(int argc, char *argv[]) +{ + unsigned int i, j; + struct tdb_context *tdb; + int flags[] = { TDB_INTERNAL, TDB_DEFAULT, TDB_NOMMAP, + TDB_INTERNAL|TDB_CONVERT, TDB_CONVERT, + TDB_NOMMAP|TDB_CONVERT }; + TDB_DATA key = { (unsigned char *)&j, sizeof(j) }; + TDB_DATA data = { (unsigned char *)&j, sizeof(j) }; + char *summary; + + plan_tests(sizeof(flags) / sizeof(flags[0]) * 14); + for (i = 0; i < sizeof(flags) / sizeof(flags[0]); i++) { + tdb = tdb_open("run-summary.tdb", 131, flags[i], + O_RDWR|O_CREAT|O_TRUNC, 0600); + ok1(tdb); + if (!tdb) + continue; + + /* Put some stuff in there. */ + for (j = 0; j < 500; j++) { + /* Make sure padding varies to we get some graphs! */ + data.dsize = j % (sizeof(j) + 1); + if (tdb_store(tdb, key, data, TDB_REPLACE) != 0) + fail("Storing in tdb"); + } + + summary = tdb_summary(tdb); + diag("%s", summary); + ok1(strstr(summary, "Size of file/data: ")); + ok1(strstr(summary, "Number of records: 500\n")); + ok1(strstr(summary, "Smallest/average/largest keys: 4/4/4\n")); + ok1(strstr(summary, "Smallest/average/largest data: 0/2/4\n")); + ok1(strstr(summary, "Smallest/average/largest padding: ")); + ok1(strstr(summary, "Number of dead records: 0\n")); + ok1(strstr(summary, "Number of free records: 1\n")); + ok1(strstr(summary, "Smallest/average/largest free records: ")); + ok1(strstr(summary, "Number of hash chains: 131\n")); + ok1(strstr(summary, "Smallest/average/largest hash chains: ")); + ok1(strstr(summary, "Number of uncoalesced records: 0\n")); + ok1(strstr(summary, "Smallest/average/largest uncoalesced runs: 0/0/0\n")); + ok1(strstr(summary, "Percentage keys/data/padding/free/dead/rechdrs&tailers/hashes: ")); + + free(summary); + tdb_close(tdb); + } + + return exit_status(); +} diff --git a/test/run-transaction-expand.c b/test/run-transaction-expand.c new file mode 100644 index 0000000..d36b894 --- /dev/null +++ b/test/run-transaction-expand.c @@ -0,0 +1,125 @@ +#include "../common/tdb_private.h" + +/* Speed up the tests, but do the actual sync tests. */ +static unsigned int sync_counts = 0; +static inline int fake_fsync(int fd) +{ + sync_counts++; + return 0; +} +#define fsync fake_fsync + +#ifdef MS_SYNC +static inline int fake_msync(void *addr, size_t length, int flags) +{ + sync_counts++; + return 0; +} +#define msync fake_msync +#endif + +#ifdef HAVE_FDATASYNC +static inline int fake_fdatasync(int fd) +{ + sync_counts++; + return 0; +} +#define fdatasync fake_fdatasync +#endif + +#include "../common/io.c" +#include "../common/tdb.c" +#include "../common/lock.c" +#include "../common/freelist.c" +#include "../common/traverse.c" +#include "../common/transaction.c" +#include "../common/error.c" +#include "../common/open.c" +#include "../common/check.c" +#include "../common/hash.c" +#include "../common/mutex.c" +#include "tap-interface.h" +#include <stdlib.h> +#include "logging.h" + +static void write_record(struct tdb_context *tdb, size_t extra_len, + TDB_DATA *data) +{ + TDB_DATA key; + key.dsize = strlen("hi"); + key.dptr = discard_const_p(uint8_t, "hi"); + + data->dsize += extra_len; + tdb_transaction_start(tdb); + tdb_store(tdb, key, *data, TDB_REPLACE); + tdb_transaction_commit(tdb); +} + +int main(int argc, char *argv[]) +{ + struct tdb_context *tdb; + size_t i; + TDB_DATA data; + struct tdb_record rec; + tdb_off_t off; + + /* Do *not* suppress sync for this test; we do it ourselves. */ + unsetenv("TDB_NO_FSYNC"); + + plan_tests(5); + tdb = tdb_open_ex("run-transaction-expand.tdb", + 1024, TDB_CLEAR_IF_FIRST, + O_CREAT|O_TRUNC|O_RDWR, 0600, &taplogctx, NULL); + ok1(tdb); + + data.dsize = 0; + data.dptr = calloc(1000, getpagesize()); + if (data.dptr == NULL) { + diag("Unable to allocate memory for data.dptr"); + tdb_close(tdb); + exit(1); + } + + /* Simulate a slowly growing record. */ + for (i = 0; i < 1000; i++) + write_record(tdb, getpagesize(), &data); + + tdb_ofs_read(tdb, TDB_RECOVERY_HEAD, &off); + tdb_read(tdb, off, &rec, sizeof(rec), DOCONV()); + diag("TDB size = %zu, recovery = %llu-%llu", + (size_t)tdb->map_size, (unsigned long long)off, (unsigned long long)(off + sizeof(rec) + rec.rec_len)); + + /* We should only be about 5 times larger than largest record. */ + ok1(tdb->map_size < 6 * i * getpagesize()); + tdb_close(tdb); + + tdb = tdb_open_ex("run-transaction-expand.tdb", + 1024, TDB_CLEAR_IF_FIRST, + O_CREAT|O_TRUNC|O_RDWR, 0600, &taplogctx, NULL); + ok1(tdb); + + data.dsize = 0; + + /* Simulate a slowly growing record, repacking to keep + * recovery area at end. */ + for (i = 0; i < 1000; i++) { + write_record(tdb, getpagesize(), &data); + if (i % 10 == 0) + tdb_repack(tdb); + } + + tdb_ofs_read(tdb, TDB_RECOVERY_HEAD, &off); + tdb_read(tdb, off, &rec, sizeof(rec), DOCONV()); + diag("TDB size = %zu, recovery = %llu-%llu", + (size_t)tdb->map_size, (unsigned long long)off, (unsigned long long)(off + sizeof(rec) + rec.rec_len)); + + /* We should only be about 4 times larger than largest record. */ + ok1(tdb->map_size < 5 * i * getpagesize()); + + /* We should have synchronized multiple times. */ + ok1(sync_counts); + tdb_close(tdb); + free(data.dptr); + + return exit_status(); +} diff --git a/test/run-traverse-chain.c b/test/run-traverse-chain.c new file mode 100644 index 0000000..2a25bec --- /dev/null +++ b/test/run-traverse-chain.c @@ -0,0 +1,94 @@ +#include "../common/tdb_private.h" +#include "../common/io.c" +#include "../common/tdb.c" +#include "../common/lock.c" +#include "../common/freelist.c" +#include "../common/traverse.c" +#include "../common/transaction.c" +#include "../common/error.c" +#include "../common/open.c" +#include "../common/check.c" +#include "../common/hash.c" +#include "../common/mutex.c" +#include "tap-interface.h" +#include <stdlib.h> +#include "logging.h" + +static char keystr0[] = "x"; +static TDB_DATA key0 = { .dptr = (uint8_t *)keystr0, + .dsize = sizeof(keystr0) }; +static char valuestr0[] = "y"; +static TDB_DATA value0 = { .dptr = (uint8_t *)valuestr0, + .dsize = sizeof(valuestr0) }; + +static char keystr1[] = "aaa"; +static TDB_DATA key1 = { .dptr = (uint8_t *)keystr1, + .dsize = sizeof(keystr1) }; +static char valuestr1[] = "bbbbb"; +static TDB_DATA value1 = { .dptr = (uint8_t *)valuestr1, + .dsize = sizeof(valuestr1) }; + +static TDB_DATA *keys[] = { &key0, &key1 }; +static TDB_DATA *values[] = { &value0, &value1 }; + +static bool tdb_data_same(TDB_DATA d1, TDB_DATA d2) +{ + if (d1.dsize != d2.dsize) { + return false; + } + return (memcmp(d1.dptr, d2.dptr, d1.dsize) == 0); +} + +struct traverse_chain_state { + size_t idx; + bool ok; +}; + +static int traverse_chain_fn(struct tdb_context *tdb, + TDB_DATA key, + TDB_DATA data, + void *private_data) +{ + struct traverse_chain_state *state = private_data; + + state->ok &= tdb_data_same(key, *keys[state->idx]); + state->ok &= tdb_data_same(data, *values[state->idx]); + state->idx += 1; + + return 0; +} + +int main(int argc, char *argv[]) +{ + struct tdb_context *tdb; + struct traverse_chain_state state = { .ok = true }; + int ret; + + plan_tests(4); + + tdb = tdb_open_ex( + "traverse_chain.tdb", + 1, + TDB_CLEAR_IF_FIRST, + O_RDWR|O_CREAT, + 0600, + &taplogctx, + NULL); + ok1(tdb); + + /* add in reverse order, tdb_store adds to the front of the list */ + ret = tdb_store(tdb, key1, value1, TDB_INSERT); + ok1(ret == 0); + ret = tdb_store(tdb, key0, value0, TDB_INSERT); + ok1(ret == 0); + + ret = tdb_traverse_key_chain(tdb, key0, traverse_chain_fn, &state); + ok1(ret == 2); + ok1(state.ok); + + unlink(tdb_name(tdb)); + + tdb_close(tdb); + + return exit_status(); +} diff --git a/test/run-traverse-in-transaction.c b/test/run-traverse-in-transaction.c new file mode 100644 index 0000000..d187b9b --- /dev/null +++ b/test/run-traverse-in-transaction.c @@ -0,0 +1,90 @@ +#include "lock-tracking.h" +#include "../common/tdb_private.h" +#define fcntl fcntl_with_lockcheck +#include "../common/io.c" +#include "../common/tdb.c" +#include "../common/lock.c" +#include "../common/freelist.c" +#include "../common/traverse.c" +#include "../common/transaction.c" +#include "../common/error.c" +#include "../common/open.c" +#include "../common/check.c" +#include "../common/hash.c" +#include "../common/mutex.c" +#include "tap-interface.h" +#undef fcntl_with_lockcheck +#include <stdlib.h> +#include <stdbool.h> +#include "external-agent.h" +#include "logging.h" + +static struct agent *agent; + +static bool correct_key(TDB_DATA key) +{ + return key.dsize == strlen("hi") + && memcmp(key.dptr, "hi", key.dsize) == 0; +} + +static bool correct_data(TDB_DATA data) +{ + return data.dsize == strlen("world") + && memcmp(data.dptr, "world", data.dsize) == 0; +} + +static int traverse(struct tdb_context *tdb, TDB_DATA key, TDB_DATA data, + void *p) +{ + ok1(correct_key(key)); + ok1(correct_data(data)); + return 0; +} + +int main(int argc, char *argv[]) +{ + struct tdb_context *tdb; + TDB_DATA key, data; + + plan_tests(13); + agent = prepare_external_agent(); + + tdb = tdb_open_ex("run-traverse-in-transaction.tdb", + 1024, TDB_CLEAR_IF_FIRST, O_CREAT|O_TRUNC|O_RDWR, + 0600, &taplogctx, NULL); + ok1(tdb); + + key.dsize = strlen("hi"); + key.dptr = discard_const_p(uint8_t, "hi"); + data.dptr = discard_const_p(uint8_t, "world"); + data.dsize = strlen("world"); + + ok1(tdb_store(tdb, key, data, TDB_INSERT) == 0); + + ok1(external_agent_operation(agent, OPEN, tdb_name(tdb)) == SUCCESS); + + ok1(tdb_transaction_active(tdb) == 0); + ok1(tdb_transaction_start(tdb) == 0); + ok1(tdb_transaction_active(tdb) == 1); + ok1(external_agent_operation(agent, TRANSACTION_START, tdb_name(tdb)) + == WOULD_HAVE_BLOCKED); + tdb_traverse(tdb, traverse, NULL); + + /* That should *not* release the transaction lock! */ + ok1(external_agent_operation(agent, TRANSACTION_START, tdb_name(tdb)) + == WOULD_HAVE_BLOCKED); + tdb_traverse_read(tdb, traverse, NULL); + + /* That should *not* release the transaction lock! */ + ok1(external_agent_operation(agent, TRANSACTION_START, tdb_name(tdb)) + == WOULD_HAVE_BLOCKED); + ok1(tdb_transaction_commit(tdb) == 0); + ok1(tdb_transaction_active(tdb) == 0); + /* Now we should be fine. */ + ok1(external_agent_operation(agent, TRANSACTION_START, tdb_name(tdb)) + == SUCCESS); + + tdb_close(tdb); + + return exit_status(); +} diff --git a/test/run-wronghash-fail.c b/test/run-wronghash-fail.c new file mode 100644 index 0000000..c44b0f5 --- /dev/null +++ b/test/run-wronghash-fail.c @@ -0,0 +1,121 @@ +#include "../common/tdb_private.h" +#include "../common/io.c" +#include "../common/tdb.c" +#include "../common/lock.c" +#include "../common/freelist.c" +#include "../common/traverse.c" +#include "../common/transaction.c" +#include "../common/error.c" +#include "../common/open.c" +#include "../common/check.c" +#include "../common/hash.c" +#include "../common/mutex.c" +#include "tap-interface.h" +#include <stdlib.h> + +static void log_fn(struct tdb_context *tdb, enum tdb_debug_level level, const char *fmt, ...) +{ + unsigned int *count = tdb_get_logging_private(tdb); + if (strstr(fmt, "hash")) + (*count)++; +} + +int main(int argc, char *argv[]) +{ + struct tdb_context *tdb; + unsigned int log_count; + TDB_DATA d; + struct tdb_logging_context log_ctx = { log_fn, &log_count }; + + plan_tests(28); + + /* Create with default hash. */ + log_count = 0; + tdb = tdb_open_ex("run-wronghash-fail.tdb", 0, 0, + O_CREAT|O_RDWR|O_TRUNC, 0600, &log_ctx, NULL); + ok1(tdb); + ok1(log_count == 0); + d.dptr = discard_const_p(uint8_t, "Hello"); + d.dsize = 5; + ok1(tdb_store(tdb, d, d, TDB_INSERT) == 0); + tdb_close(tdb); + + /* Fail to open with different hash. */ + tdb = tdb_open_ex("run-wronghash-fail.tdb", 0, 0, O_RDWR, 0, + &log_ctx, tdb_jenkins_hash); + ok1(!tdb); + ok1(log_count == 1); + + /* Create with different hash. */ + log_count = 0; + tdb = tdb_open_ex("run-wronghash-fail.tdb", 0, 0, + O_CREAT|O_RDWR|O_TRUNC, + 0600, &log_ctx, tdb_jenkins_hash); + ok1(tdb); + ok1(log_count == 0); + tdb_close(tdb); + + /* Endian should be no problem. */ + log_count = 0; + tdb = tdb_open_ex("test/jenkins-le-hash.tdb", 0, 0, O_RDWR, 0, + &log_ctx, tdb_old_hash); + ok1(!tdb); + ok1(log_count == 1); + + log_count = 0; + tdb = tdb_open_ex("test/jenkins-be-hash.tdb", 0, 0, O_RDWR, 0, + &log_ctx, tdb_old_hash); + ok1(!tdb); + ok1(log_count == 1); + + log_count = 0; + /* Fail to open with old default hash. */ + tdb = tdb_open_ex("run-wronghash-fail.tdb", 0, 0, O_RDWR, 0, + &log_ctx, tdb_old_hash); + ok1(!tdb); + ok1(log_count == 1); + + log_count = 0; + tdb = tdb_open_ex("test/jenkins-le-hash.tdb", 0, 0, O_RDONLY, + 0, &log_ctx, tdb_jenkins_hash); + ok1(tdb); + ok1(log_count == 0); + ok1(tdb_check(tdb, NULL, NULL) == 0); + tdb_close(tdb); + + log_count = 0; + tdb = tdb_open_ex("test/jenkins-be-hash.tdb", 0, 0, O_RDONLY, + 0, &log_ctx, tdb_jenkins_hash); + ok1(tdb); + ok1(log_count == 0); + ok1(tdb_check(tdb, NULL, NULL) == 0); + tdb_close(tdb); + + /* It should open with jenkins hash if we don't specify. */ + log_count = 0; + tdb = tdb_open_ex("test/jenkins-le-hash.tdb", 0, 0, O_RDWR, 0, + &log_ctx, NULL); + ok1(tdb); + ok1(log_count == 0); + ok1(tdb_check(tdb, NULL, NULL) == 0); + tdb_close(tdb); + + log_count = 0; + tdb = tdb_open_ex("test/jenkins-be-hash.tdb", 0, 0, O_RDWR, 0, + &log_ctx, NULL); + ok1(tdb); + ok1(log_count == 0); + ok1(tdb_check(tdb, NULL, NULL) == 0); + tdb_close(tdb); + + log_count = 0; + tdb = tdb_open_ex("run-wronghash-fail.tdb", 0, 0, O_RDONLY, + 0, &log_ctx, NULL); + ok1(tdb); + ok1(log_count == 0); + ok1(tdb_check(tdb, NULL, NULL) == 0); + tdb_close(tdb); + + + return exit_status(); +} diff --git a/test/run-zero-append.c b/test/run-zero-append.c new file mode 100644 index 0000000..f9eba1b --- /dev/null +++ b/test/run-zero-append.c @@ -0,0 +1,41 @@ +#include "../common/tdb_private.h" +#include "../common/io.c" +#include "../common/tdb.c" +#include "../common/lock.c" +#include "../common/freelist.c" +#include "../common/traverse.c" +#include "../common/transaction.c" +#include "../common/error.c" +#include "../common/open.c" +#include "../common/check.c" +#include "../common/hash.c" +#include "../common/mutex.c" +#include "tap-interface.h" +#include <stdlib.h> +#include "logging.h" + +int main(int argc, char *argv[]) +{ + struct tdb_context *tdb; + TDB_DATA key, data; + + plan_tests(4); + tdb = tdb_open_ex(NULL, 1024, TDB_INTERNAL, O_CREAT|O_TRUNC|O_RDWR, + 0600, &taplogctx, NULL); + ok1(tdb); + + /* Tickle bug on appending zero length buffer to zero length buffer. */ + key.dsize = strlen("hi"); + key.dptr = discard_const_p(uint8_t, "hi"); + data.dptr = discard_const_p(uint8_t, "world"); + data.dsize = 0; + + ok1(tdb_append(tdb, key, data) == 0); + ok1(tdb_append(tdb, key, data) == 0); + data = tdb_fetch(tdb, key); + ok1(data.dsize == 0); + tdb_close(tdb); + free(data.dptr); + + return exit_status(); +} diff --git a/test/run.c b/test/run.c new file mode 100644 index 0000000..c744c4d --- /dev/null +++ b/test/run.c @@ -0,0 +1,50 @@ +#include "../common/tdb_private.h" +#include "../common/io.c" +#include "../common/tdb.c" +#include "../common/lock.c" +#include "../common/freelist.c" +#include "../common/traverse.c" +#include "../common/transaction.c" +#include "../common/error.c" +#include "../common/open.c" +#include "../common/check.c" +#include "../common/hash.c" +#include "../common/mutex.c" +#include "tap-interface.h" +#include <stdlib.h> +#include "logging.h" + +int main(int argc, char *argv[]) +{ + struct tdb_context *tdb; + TDB_DATA key, data; + + plan_tests(10); + tdb = tdb_open_ex("run.tdb", 1024, TDB_CLEAR_IF_FIRST, + O_CREAT|O_TRUNC|O_RDWR, 0600, &taplogctx, NULL); + + ok1(tdb); + key.dsize = strlen("hi"); + key.dptr = discard_const_p(uint8_t, "hi"); + data.dsize = strlen("world"); + data.dptr = discard_const_p(uint8_t, "world"); + + ok1(tdb_store(tdb, key, data, TDB_MODIFY) < 0); + ok1(tdb_error(tdb) == TDB_ERR_NOEXIST); + ok1(tdb_store(tdb, key, data, TDB_INSERT) == 0); + ok1(tdb_store(tdb, key, data, TDB_INSERT) < 0); + ok1(tdb_error(tdb) == TDB_ERR_EXISTS); + ok1(tdb_store(tdb, key, data, TDB_MODIFY) == 0); + + data = tdb_fetch(tdb, key); + ok1(data.dsize == strlen("world")); + ok1(memcmp(data.dptr, "world", strlen("world")) == 0); + free(data.dptr); + + key.dsize++; + data = tdb_fetch(tdb, key); + ok1(data.dptr == NULL); + tdb_close(tdb); + + return exit_status(); +} diff --git a/test/rwlock-be.tdb b/test/rwlock-be.tdb Binary files differnew file mode 100644 index 0000000..45b5f09 --- /dev/null +++ b/test/rwlock-be.tdb diff --git a/test/rwlock-le.tdb b/test/rwlock-le.tdb Binary files differnew file mode 100644 index 0000000..45b5f09 --- /dev/null +++ b/test/rwlock-le.tdb diff --git a/test/sample_tdb.tdb b/test/sample_tdb.tdb Binary files differnew file mode 100644 index 0000000..a40e50c --- /dev/null +++ b/test/sample_tdb.tdb diff --git a/test/tap-interface.h b/test/tap-interface.h new file mode 100644 index 0000000..8f742d8 --- /dev/null +++ b/test/tap-interface.h @@ -0,0 +1,58 @@ +/* + Unix SMB/CIFS implementation. + Simplistic implementation of tap interface. + + Copyright (C) Rusty Russell 2012 + + ** NOTE! The following LGPL license applies to the talloc + ** library. This does NOT imply that all of Samba is released + ** under the LGPL + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 3 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, see <http://www.gnu.org/licenses/>. +*/ +#include <stdio.h> + +#ifndef __location__ +#define __TAP_STRING_LINE1__(s) #s +#define __TAP_STRING_LINE2__(s) __TAP_STRING_LINE1__(s) +#define __TAP_STRING_LINE3__ __TAP_STRING_LINE2__(__LINE__) +#define __location__ __FILE__ ":" __TAP_STRING_LINE3__ +#endif + +#define plan_tests(num) +#define fail(...) do { \ + fprintf(stderr, __VA_ARGS__); \ + fprintf(stderr, "\n"); \ + fflush(stderr); \ + exit(1); \ +} while(0) +#define diag(...) do { \ + fprintf(stdout, __VA_ARGS__); \ + fprintf(stdout, "\n"); \ + fflush(stdout); \ +} while(0) +#define pass(...) do { \ + fprintf(stdout, "."); \ + fflush(stdout); \ +} while(0) +#define ok(e, ...) do { \ + if (e) { \ + pass(); \ + } else { \ + fail(__VA_ARGS__); \ + } \ +} while(0) +#define ok1(e) ok((e), "%s:%s", __location__, #e) +#define skip(n, ...) diag(__VA_ARGS__) +#define exit_status() 0 diff --git a/test/tap-to-subunit.h b/test/tap-to-subunit.h new file mode 100644 index 0000000..a5cf74f --- /dev/null +++ b/test/tap-to-subunit.h @@ -0,0 +1,155 @@ +#ifndef TAP_TO_SUBUNIT_H +#define TAP_TO_SUBUNIT_H +/* + * tap-style wrapper for subunit. + * + * Copyright (c) 2011 Rusty Russell + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ +#include "replace.h" + +/** + * plan_tests - announce the number of tests you plan to run + * @tests: the number of tests + * + * This should be the first call in your test program: it allows tracing + * of failures which mean that not all tests are run. + * + * If you don't know how many tests will actually be run, assume all of them + * and use skip() if you don't actually run some tests. + * + * Example: + * plan_tests(13); + */ +void plan_tests(unsigned int tests); + +/** + * ok1 - Simple conditional test + * @e: the expression which we expect to be true. + * + * This is the simplest kind of test: if the expression is true, the + * test passes. The name of the test which is printed will simply be + * file name, line number, and the expression itself. + * + * Example: + * ok1(somefunc() == 1); + */ +# define ok1(e) ((e) ? \ + _gen_result(1, __func__, __FILE__, __LINE__, "%s", #e) : \ + _gen_result(0, __func__, __FILE__, __LINE__, "%s", #e)) + +/** + * ok - Conditional test with a name + * @e: the expression which we expect to be true. + * @...: the printf-style name of the test. + * + * If the expression is true, the test passes. The name of the test will be + * the filename, line number, and the printf-style string. This can be clearer + * than simply the expression itself. + * + * Example: + * ok1(somefunc() == 1); + * ok(somefunc() == 0, "Second somefunc() should fail"); + */ +# define ok(e, ...) ((e) ? \ + _gen_result(1, __func__, __FILE__, __LINE__, \ + __VA_ARGS__) : \ + _gen_result(0, __func__, __FILE__, __LINE__, \ + __VA_ARGS__)) + +/** + * pass - Note that a test passed + * @...: the printf-style name of the test. + * + * For complicated code paths, it can be easiest to simply call pass() in one + * branch and fail() in another. + * + * Example: + * int x = somefunc(); + * if (x > 0) + * pass("somefunc() returned a valid value"); + * else + * fail("somefunc() returned an invalid value"); + */ +# define pass(...) ok(1, __VA_ARGS__) + +/** + * fail - Note that a test failed + * @...: the printf-style name of the test. + * + * For complicated code paths, it can be easiest to simply call pass() in one + * branch and fail() in another. + */ +# define fail(...) ok(0, __VA_ARGS__) + +unsigned int _gen_result(int, const char *, const char *, unsigned int, + const char *, ...) PRINTF_ATTRIBUTE(5, 6); + +/** + * diag - print a diagnostic message (use instead of printf/fprintf) + * @fmt: the format of the printf-style message + * + * diag ensures that the output will not be considered to be a test + * result by the TAP test harness. It will append '\n' for you. + * + * Example: + * diag("Now running complex tests"); + */ +void diag(const char *fmt, ...) PRINTF_ATTRIBUTE(1, 2); + +/** + * skip - print a diagnostic message (use instead of printf/fprintf) + * @n: number of tests you're skipping. + * @fmt: the format of the reason you're skipping the tests. + * + * Sometimes tests cannot be run because the test system lacks some feature: + * you should explicitly document that you're skipping tests using skip(). + * + * From the Test::More documentation: + * If it's something the user might not be able to do, use SKIP. This + * includes optional modules that aren't installed, running under an OS that + * doesn't have some feature (like fork() or symlinks), or maybe you need an + * Internet connection and one isn't available. + * + * Example: + * #ifdef HAVE_SOME_FEATURE + * ok1(somefunc()); + * #else + * skip(1, "Don't have SOME_FEATURE"); + * #endif + */ +void skip(unsigned int n, const char *fmt, ...) PRINTF_ATTRIBUTE(2, 3); + +/** + * exit_status - the value that main should return. + * + * For maximum compatibility your test program should return a particular exit + * code (ie. 0 if all tests were run, and every test which was expected to + * succeed succeeded). + * + * Example: + * exit(exit_status()); + */ +int exit_status(void); +#endif /* CCAN_TAP_H */ diff --git a/test/tdb.corrupt b/test/tdb.corrupt Binary files differnew file mode 100644 index 0000000..83d6677 --- /dev/null +++ b/test/tdb.corrupt diff --git a/test/test_tdbbackup.sh b/test/test_tdbbackup.sh new file mode 100755 index 0000000..8552ea1 --- /dev/null +++ b/test/test_tdbbackup.sh @@ -0,0 +1,57 @@ +#!/bin/sh +# Blackbox test for tdbbackup of given ldb or tdb database +# Copyright (C) 2018 Andrew Bartlett <abartlet@samba.org> + +if [ $# -lt 1 ]; then + echo "Usage: $0 LDBFILE" + exit 1 +fi + +LDBFILE=$1 + +timestamp() +{ + date -u +'time: %Y-%m-%d %H:%M:%S.%6NZ' | sed 's/\..*NZ$/.000000Z/' +} + +subunit_fail_test() +{ + timestamp + printf 'failure: %s [\n' "$1" + cat - + echo "]" +} + +testit() +{ + name="$1" + shift + cmdline="$@" + timestamp + printf 'test: %s\n' "$1" + output=$($cmdline 2>&1) + status=$? + if [ x$status = x0 ]; then + timestamp + printf 'success: %s\n' "$name" + else + echo "$output" | subunit_fail_test "$name" + fi + return $status +} + +$BINDIR/tdbdump $LDBFILE | sort >orig_dump + +testit "normal tdbbackup on tdb file" $BINDIR/tdbbackup $LDBFILE -s .bak +$BINDIR/tdbdump $LDBFILE.bak | sort >bak_dump +testit "cmp between tdbdumps of original and backup" cmp orig_dump bak_dump +rm $LDBFILE.bak +rm bak_dump + +testit "readonly tdbbackup on tdb file" $BINDIR/tdbbackup $LDBFILE -s .bak -r +$BINDIR/tdbdump $LDBFILE.bak | sort >bak_dump +testit "cmp between tdbdumps of original and back dbs" cmp orig_dump bak_dump +rm $LDBFILE.bak +rm bak_dump + +rm orig_dump |