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;
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,
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)) {
TDB_ATTRIBUTE_LOG = 0,
TDB_ATTRIBUTE_HASH = 1,
TDB_ATTRIBUTE_SEED = 2,
- TDB_ATTRIBUTE_STATS = 3
+ TDB_ATTRIBUTE_STATS = 3,
+ TDB_ATTRIBUTE_OPENHOOK = 4
};
/**
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.
*
*
* 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;
struct tdb_attribute_hash hash;
struct tdb_attribute_seed seed;
struct tdb_attribute_stats stats;
+ struct tdb_attribute_openhook openhook;
};
#ifdef __cplusplus
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;
}
} 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) {
{
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";
* various times. */
enum operation {
OPEN,
+ OPEN_WITH_HOOK,
FETCH,
STORE,
TRANSACTION_START,
#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
--- /dev/null
+#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();
+}
+