From 0468e6992770e5082f2fb11cd93c2d8209e3f351 Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Thu, 7 Apr 2011 13:51:54 +0930 Subject: [PATCH] tdb2: open hook for implementing TDB_CLEAR_IF_FIRST This allows the caller to implement clear-if-first semantics as per TDB1. The flag was removed for good reasons: performance and unreliability, but SAMBA3 still uses it widely, so this allows them to reimplement it themselves. (There is no way to do it without help like this from tdb2, since it has to be done under the open lock). --- ccan/tdb2/open.c | 15 +++++ ccan/tdb2/tdb2.h | 23 +++++++- ccan/tdb2/test/external-agent.c | 45 ++++++++++++++- ccan/tdb2/test/external-agent.h | 1 + ccan/tdb2/test/failtest_helper.h | 2 +- ccan/tdb2/test/run-83-openhook.c | 99 ++++++++++++++++++++++++++++++++ 6 files changed, 181 insertions(+), 4 deletions(-) create mode 100644 ccan/tdb2/test/run-83-openhook.c diff --git a/ccan/tdb2/open.c b/ccan/tdb2/open.c index 56514ae5..f358d032 100644 --- a/ccan/tdb2/open.c +++ b/ccan/tdb2/open.c @@ -193,6 +193,7 @@ struct tdb_context *tdb_open(const char *name, int tdb_flags, ssize_t rlen; struct tdb_header hdr; struct tdb_attribute_seed *seed = NULL; + struct tdb_attribute_openhook *openhook = NULL; tdb_bool_err berr; enum TDB_ERROR ecode; @@ -233,6 +234,9 @@ struct tdb_context *tdb_open(const char *name, int tdb_flags, if (tdb->stats->size > sizeof(attr->stats)) tdb->stats->size = sizeof(attr->stats); break; + case TDB_ATTRIBUTE_OPENHOOK: + openhook = &attr->openhook; + break; default: ecode = tdb_logerr(tdb, TDB_ERR_EINVAL, TDB_LOG_USE_ERROR, @@ -339,6 +343,17 @@ struct tdb_context *tdb_open(const char *name, int tdb_flags, goto fail; } + /* call their open hook if they gave us one. */ + if (openhook) { + ecode = openhook->fn(tdb->file->fd, openhook->data); + if (ecode != TDB_SUCCESS) { + tdb_logerr(tdb, ecode, TDB_LOG_ERROR, + "tdb_open: open hook failed"); + goto fail; + } + open_flags |= O_CREAT; + } + /* If they used O_TRUNC, read will return 0. */ rlen = pread(tdb->file->fd, &hdr, sizeof(hdr), 0); if (rlen == 0 && (open_flags & O_CREAT)) { diff --git a/ccan/tdb2/tdb2.h b/ccan/tdb2/tdb2.h index 0731503b..24137b88 100644 --- a/ccan/tdb2/tdb2.h +++ b/ccan/tdb2/tdb2.h @@ -606,7 +606,8 @@ enum tdb_attribute_type { TDB_ATTRIBUTE_LOG = 0, TDB_ATTRIBUTE_HASH = 1, TDB_ATTRIBUTE_SEED = 2, - TDB_ATTRIBUTE_STATS = 3 + TDB_ATTRIBUTE_STATS = 3, + TDB_ATTRIBUTE_OPENHOOK = 4 }; /** @@ -716,6 +717,22 @@ struct tdb_attribute_stats { uint64_t lock_nonblock; }; +/** + * struct tdb_attribute_openhook - tdb special effects hook for open + * + * This attribute contains a function to call once we have the OPEN_LOCK + * for the tdb, but before we've examined its contents. If this succeeds, + * the tdb will be populated if it's then zero-length. + * + * This is a hack to allow support for TDB1-style TDB_CLEAR_IF_FIRST + * behaviour. + */ +struct tdb_attribute_openhook { + struct tdb_attribute_base base; /* .attr = TDB_ATTRIBUTE_OPENHOOK */ + enum TDB_ERROR (*fn)(int fd, void *data); + void *data; +}; + /** * union tdb_attribute - tdb attributes. * @@ -723,7 +740,8 @@ struct tdb_attribute_stats { * * See also: * struct tdb_attribute_log, struct tdb_attribute_hash, - * struct tdb_attribute_seed, struct tdb_attribute_stats. + * struct tdb_attribute_seed, struct tdb_attribute_stats, + * struct tdb_attribute_openhook. */ union tdb_attribute { struct tdb_attribute_base base; @@ -731,6 +749,7 @@ union tdb_attribute { struct tdb_attribute_hash hash; struct tdb_attribute_seed seed; struct tdb_attribute_stats stats; + struct tdb_attribute_openhook openhook; }; #ifdef __cplusplus diff --git a/ccan/tdb2/test/external-agent.c b/ccan/tdb2/test/external-agent.c index df51a0c5..081b8500 100644 --- a/ccan/tdb2/test/external-agent.c +++ b/ccan/tdb2/test/external-agent.c @@ -17,14 +17,39 @@ static struct tdb_context *tdb; +static enum TDB_ERROR clear_if_first(int fd, void *arg) +{ +/* We hold a lock offset 63 always, so we can tell if anyone is holding it. */ + struct flock fl; + + fl.l_type = F_WRLCK; + fl.l_whence = SEEK_SET; + fl.l_start = 63; + fl.l_len = 1; + + if (fcntl(fd, F_SETLK, &fl) == 0) { + /* We must be first ones to open it! */ + diag("agent truncating file!"); + if (ftruncate(fd, 0) != 0) { + return TDB_ERR_IO; + } + } + fl.l_type = F_RDLCK; + if (fcntl(fd, F_SETLKW, &fl) != 0) { + return TDB_ERR_IO; + } + return TDB_SUCCESS; +} + static enum agent_return do_operation(enum operation op, const char *name) { TDB_DATA k; enum agent_return ret; TDB_DATA data; enum TDB_ERROR ecode; + union tdb_attribute cif; - if (op != OPEN && !tdb) { + if (op != OPEN && op != OPEN_WITH_HOOK && !tdb) { diag("external: No tdb open!"); return OTHER_FAILURE; } @@ -49,6 +74,23 @@ static enum agent_return do_operation(enum operation op, const char *name) } else ret = SUCCESS; break; + case OPEN_WITH_HOOK: + if (tdb) { + diag("Already have tdb %s open", tdb->name); + return OTHER_FAILURE; + } + cif.openhook.base.attr = TDB_ATTRIBUTE_OPENHOOK; + cif.openhook.base.next = &tap_log_attr; + cif.openhook.fn = clear_if_first; + tdb = tdb_open(name, TDB_DEFAULT, O_RDWR, 0, &cif); + if (!tdb) { + if (!locking_would_block) + diag("Opening tdb gave %s", strerror(errno)); + forget_locking(); + ret = OTHER_FAILURE; + } else + ret = SUCCESS; + break; case FETCH: ecode = tdb_fetch(tdb, k, &data); if (ecode == TDB_ERR_NOEXIST) { @@ -176,6 +218,7 @@ const char *operation_name(enum operation op) { switch (op) { case OPEN: return "OPEN"; + case OPEN_WITH_HOOK: return "OPEN_WITH_HOOK"; case FETCH: return "FETCH"; case STORE: return "STORE"; case CHECK: return "CHECK"; diff --git a/ccan/tdb2/test/external-agent.h b/ccan/tdb2/test/external-agent.h index b5d6c31e..a2f630ad 100644 --- a/ccan/tdb2/test/external-agent.h +++ b/ccan/tdb2/test/external-agent.h @@ -5,6 +5,7 @@ * various times. */ enum operation { OPEN, + OPEN_WITH_HOOK, FETCH, STORE, TRANSACTION_START, diff --git a/ccan/tdb2/test/failtest_helper.h b/ccan/tdb2/test/failtest_helper.h index 6a02cdb0..42b38149 100644 --- a/ccan/tdb2/test/failtest_helper.h +++ b/ccan/tdb2/test/failtest_helper.h @@ -4,7 +4,7 @@ #include /* FIXME: Check these! */ -#define INITIAL_TDB_MALLOC "open.c", 199, FAILTEST_MALLOC +#define INITIAL_TDB_MALLOC "open.c", 200, FAILTEST_MALLOC #define URANDOM_OPEN "open.c", 44, FAILTEST_OPEN #define URANDOM_READ "open.c", 24, FAILTEST_READ diff --git a/ccan/tdb2/test/run-83-openhook.c b/ccan/tdb2/test/run-83-openhook.c new file mode 100644 index 00000000..c8527935 --- /dev/null +++ b/ccan/tdb2/test/run-83-openhook.c @@ -0,0 +1,99 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "external-agent.h" +#include "logging.h" + +static enum TDB_ERROR clear_if_first(int fd, void *arg) +{ +/* We hold a lock offset 63 always, so we can tell if anyone is holding it. */ + struct flock fl; + + if (arg != clear_if_first) + return TDB_ERR_CORRUPT; + + fl.l_type = F_WRLCK; + fl.l_whence = SEEK_SET; + fl.l_start = 63; + fl.l_len = 1; + + if (fcntl(fd, F_SETLK, &fl) == 0) { + /* We must be first ones to open it! */ + diag("truncating file!"); + if (ftruncate(fd, 0) != 0) { + return TDB_ERR_IO; + } + } + fl.l_type = F_RDLCK; + if (fcntl(fd, F_SETLKW, &fl) != 0) { + return TDB_ERR_IO; + } + return TDB_SUCCESS; +} + +int main(int argc, char *argv[]) +{ + unsigned int i; + struct tdb_context *tdb; + struct agent *agent; + union tdb_attribute cif; + struct tdb_data key = tdb_mkdata("key", 3); + int flags[] = { TDB_DEFAULT, TDB_NOMMAP, + TDB_CONVERT, TDB_NOMMAP|TDB_CONVERT }; + + cif.openhook.base.attr = TDB_ATTRIBUTE_OPENHOOK; + cif.openhook.base.next = &tap_log_attr; + cif.openhook.fn = clear_if_first; + cif.openhook.data = clear_if_first; + + agent = prepare_external_agent(); + plan_tests(sizeof(flags) / sizeof(flags[0]) * 13); + for (i = 0; i < sizeof(flags) / sizeof(flags[0]); i++) { + /* Create it */ + tdb = tdb_open("run-83-openhook.tdb", flags[i], + O_RDWR|O_CREAT|O_TRUNC, 0600, NULL); + ok1(tdb); + ok1(tdb_store(tdb, key, key, TDB_REPLACE) == 0); + tdb_close(tdb); + + /* Now, open with CIF, should clear it. */ + tdb = tdb_open("run-83-openhook.tdb", flags[i], + O_RDWR, 0, &cif); + ok1(tdb); + ok1(!tdb_exists(tdb, key)); + ok1(tdb_store(tdb, key, key, TDB_REPLACE) == 0); + + /* Agent should not clear it, since it's still open. */ + ok1(external_agent_operation(agent, OPEN_WITH_HOOK, + "run-83-openhook.tdb") == SUCCESS); + ok1(external_agent_operation(agent, FETCH, "key") == SUCCESS); + ok1(external_agent_operation(agent, CLOSE, "") == SUCCESS); + + /* Still exists for us too. */ + ok1(tdb_exists(tdb, key)); + + /* Close it, now agent should clear it. */ + tdb_close(tdb); + + ok1(external_agent_operation(agent, OPEN_WITH_HOOK, + "run-83-openhook.tdb") == SUCCESS); + ok1(external_agent_operation(agent, FETCH, "key") == FAILED); + ok1(external_agent_operation(agent, CLOSE, "") == SUCCESS); + + ok1(tap_log_messages == 0); + } + + free_external_agent(agent); + return exit_status(); +} + -- 2.39.2