tdb2: open hook for implementing TDB_CLEAR_IF_FIRST
authorRusty Russell <rusty@rustcorp.com.au>
Thu, 7 Apr 2011 04:21:54 +0000 (13:51 +0930)
committerRusty Russell <rusty@rustcorp.com.au>
Thu, 7 Apr 2011 04:21:54 +0000 (13:51 +0930)
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
ccan/tdb2/tdb2.h
ccan/tdb2/test/external-agent.c
ccan/tdb2/test/external-agent.h
ccan/tdb2/test/failtest_helper.h
ccan/tdb2/test/run-83-openhook.c [new file with mode: 0644]

index 56514ae533a4c925635e5df350ad690bff34710d..f358d0322d1d494ab05d826568b6f32f90b981b2 100644 (file)
@@ -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)) {
index 0731503b874ce130649f1bb64964460b1c7ad9f1..24137b88722dc3acd30432ad6b4425030c96b1c5 100644 (file)
@@ -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
index df51a0c5197baf2a311950c757a85f6a09bec556..081b8500a0fb206ab2ec239e94d9bb333f399516 100644 (file)
 
 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";
index b5d6c31e1cfa5a875cc1187d28014d4dd8c7be5e..a2f630adf5dc80bbd79ce7097ac70b87eb20c2e4 100644 (file)
@@ -5,6 +5,7 @@
  * various times. */
 enum operation {
        OPEN,
+       OPEN_WITH_HOOK,
        FETCH,
        STORE,
        TRANSACTION_START,
index 6a02cdb0ec626fce20c9741ec1590b8c0120036d..42b38149beecce6f4306f6a72ecf8d0f18628a1a 100644 (file)
@@ -4,7 +4,7 @@
 #include <stdbool.h>
 
 /* 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 (file)
index 0000000..c852793
--- /dev/null
@@ -0,0 +1,99 @@
+#include <ccan/tdb2/tdb.c>
+#include <ccan/tdb2/open.c>
+#include <ccan/tdb2/free.c>
+#include <ccan/tdb2/lock.c>
+#include <ccan/tdb2/io.c>
+#include <ccan/tdb2/hash.c>
+#include <ccan/tdb2/check.c>
+#include <ccan/tdb2/transaction.c>
+#include <ccan/tap/tap.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <stdarg.h>
+#include <err.h>
+#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();
+}
+