tdb: handle processes dying during transaction commit.
[ccan] / ccan / tdb / test / external-transaction.c
index c1442c215d6b0af3da35e4dff499c9b1c72cf6ae..23d8471386413f39a2d7a7d311ad13c3e22b0021 100644 (file)
@@ -7,7 +7,14 @@
 #include <stdlib.h>
 #include <limits.h>
 #include <string.h>
+#include <errno.h>
 #include <ccan/tdb/tdb.h>
+#include <ccan/tdb/tdb_private.h>
+#include <ccan/tap/tap.h>
+#include <stdio.h>
+#include <stdarg.h>
+
+static struct tdb_context *tdb;
 
 static volatile sig_atomic_t alarmed;
 static void do_alarm(int signum)
@@ -15,14 +22,62 @@ static void do_alarm(int signum)
        alarmed++;
 }
 
-static int do_transaction(const char *name)
+static void taplog(struct tdb_context *tdb,
+                  enum tdb_debug_level level,
+                  const char *fmt, ...)
 {
+       va_list ap;
+       char line[200];
+
+       va_start(ap, fmt);
+       vsprintf(line, fmt, ap);
+       va_end(ap);
+
+       diag("external: %s", line);
+}
+
+static int do_operation(enum operation op, const char *name)
+{
+       struct tdb_logging_context logctx = { taplog, NULL };
+
        TDB_DATA k = { .dptr = (void *)"a", .dsize = 1 };
        TDB_DATA d = { .dptr = (void *)"b", .dsize = 1 };
-       struct tdb_context *tdb = tdb_open(name, 0, 0, O_RDWR, 0);
 
-       if (!tdb)
-               return -1;
+       if (op <= KEEP_OPENED) {
+               tdb = tdb_open_ex(name, 0, op == OPEN_WITH_CLEAR_IF_FIRST ?
+                                 TDB_CLEAR_IF_FIRST : TDB_DEFAULT, O_RDWR, 0,
+                                 &logctx, NULL);
+               if (!tdb)
+                       return -1;
+       }
+
+       if (op == KEEP_OPENED) {
+               return 0;
+       } else if (op == OPEN || op == OPEN_WITH_CLEAR_IF_FIRST || op == CLOSE) {
+               tdb_close(tdb);
+               tdb = NULL;
+               return 1;
+       } else if (op == STORE_KEEP_OPENED) {
+               if (tdb_store(tdb, k, d, 0) != 0)
+                       return -2;
+               return 1;
+       } else if (op == FETCH_KEEP_OPENED) {
+               TDB_DATA ret;
+               ret = tdb_fetch(tdb, k);
+               if (ret.dptr == NULL) {
+                       if (tdb_error(tdb) == TDB_ERR_NOEXIST)
+                               return 1;
+                       return -3;
+               }
+               if (ret.dsize != 1 || *(char *)ret.dptr != 'b')
+                       return -4;
+               free(ret.dptr);
+               return 1;
+       } else if (op == CHECK_KEEP_OPENED) {
+               return tdb_check(tdb, NULL, 0) == 0;
+       } else if (op == NEEDS_RECOVERY_KEEP_OPENED) {
+               return tdb_needs_recovery(tdb);
+       }
 
        alarmed = 0;
        tdb_setalarm_sigptr(tdb, &alarmed);
@@ -31,73 +86,93 @@ static int do_transaction(const char *name)
        if (tdb_transaction_start(tdb) != 0)
                goto maybe_alarmed;
 
+       alarm(0);
        if (tdb_store(tdb, k, d, 0) != 0) {
                tdb_transaction_cancel(tdb);
                tdb_close(tdb);
+               tdb = NULL;
                return -2;
        }
 
        if (tdb_transaction_commit(tdb) == 0) {
                tdb_delete(tdb, k);
-               tdb_close(tdb);
+               if (op != TRANSACTION_KEEP_OPENED) {
+                       tdb_close(tdb);
+                       tdb = NULL;
+               }
                return 1;
        }
 
        tdb_delete(tdb, k);
 maybe_alarmed:
-       tdb_close(tdb);
+       if (op != TRANSACTION_KEEP_OPENED) {
+               tdb_close(tdb);
+               tdb = NULL;
+       }
        if (alarmed)
                return 0;
        return -3;
 }
 
+struct agent {
+       int cmdfd, responsefd;
+};
 
-/* Do this before doing any tdb stuff.  Return handle, or -1. */
-int prepare_external_agent(void)
+/* Do this before doing any tdb stuff.  Return handle, or NULL. */
+struct agent *prepare_external_agent(void)
 {
-       int pid;
+       int pid, ret;
        int command[2], response[2];
        struct sigaction act = { .sa_handler = do_alarm };
-       char name[PATH_MAX];
+       char name[1+PATH_MAX];
 
        if (pipe(command) != 0 || pipe(response) != 0)
-               return -1;
+               return NULL;
 
        pid = fork();
        if (pid < 0)
-               return -1;
+               return NULL;
 
        if (pid != 0) {
+               struct agent *agent = malloc(sizeof(*agent));
+
                close(command[0]);
                close(response[1]);
-               /* FIXME: Make fds consective. */
-               dup2(command[1]+1, response[1]);
-               return command[1];
+               agent->cmdfd = command[1];
+               agent->responsefd = response[0];
+               return agent;
        }
 
        close(command[1]);
        close(response[0]);
        sigaction(SIGALRM, &act, NULL);
 
-       while (read(command[0], name, sizeof(name)) != 0) {
-               int result = do_transaction(name);
+       while ((ret = read(command[0], name, sizeof(name))) > 0) {
+               int result;
+
+               result = do_operation(name[0], name+1);
                if (write(response[1], &result, sizeof(result))
                    != sizeof(result))
                        err(1, "Writing response");
        }
+       diag("external: read %i: %s", ret, strerror(errno));
        exit(0);
 }
 
-/* Ask the external agent to try to do a transaction. */
-bool external_agent_transaction(int handle, const char *tdbname)
+/* Ask the external agent to try to do an operation. */
+int external_agent_operation(struct agent *agent,
+                             enum operation op, const char *tdbname)
 {
        int res;
+       char string[1 + strlen(tdbname) + 1];
+
+       string[0] = op;
+       strcpy(string+1, tdbname);
 
-       if (write(handle, tdbname, strlen(tdbname)+1)
-           != strlen(tdbname)+1)
+       if (write(agent->cmdfd, string, sizeof(string)) != sizeof(string))
                err(1, "Writing to agent");
 
-       if (read(handle+1, &res, sizeof(res)) != sizeof(res))
+       if (read(agent->responsefd, &res, sizeof(res)) != sizeof(res))
                err(1, "Reading from agent");
 
        if (res > 1)
@@ -105,3 +180,41 @@ bool external_agent_transaction(int handle, const char *tdbname)
 
        return res;
 }
+
+void external_agent_operation_start(struct agent *agent,
+                                   enum operation op, const char *tdbname)
+{
+       char string[1 + strlen(tdbname) + 1];
+
+       string[0] = op;
+       strcpy(string+1, tdbname);
+
+       if (write(agent->cmdfd, string, sizeof(string)) != sizeof(string))
+               err(1, "Writing to agent");
+}
+
+bool external_agent_operation_check(struct agent *agent, bool block, int *res)
+{
+       int flags = fcntl(agent->responsefd, F_GETFL);
+
+       if (block)
+               fcntl(agent->responsefd, F_SETFL, flags & ~O_NONBLOCK);
+       else
+               fcntl(agent->responsefd, F_SETFL, flags | O_NONBLOCK);
+
+       switch (read(agent->responsefd, res, sizeof(*res))) {
+       case sizeof(*res):
+               break;
+       case 0:
+               errx(1, "Agent died?");
+       default:
+               if (!block && (errno == EAGAIN || errno == EWOULDBLOCK))
+                       return false;
+               err(1, "%slocking reading from agent", block ? "B" : "Non-b");
+       }
+
+       if (*res > 1)
+               errx(1, "Agent returned %u\n", *res);
+
+       return true;
+}