New test: reveals race (found by Volker) when open occurs during transaction commit.
[ccan] / ccan / tdb / test / external-transaction.c
1 #include "external-transaction.h"
2 #include <sys/types.h>
3 #include <sys/wait.h>
4 #include <unistd.h>
5 #include <err.h>
6 #include <fcntl.h>
7 #include <stdlib.h>
8 #include <limits.h>
9 #include <string.h>
10 #include <errno.h>
11 #include <ccan/tdb/tdb.h>
12 #include <ccan/tap/tap.h>
13
14 static volatile sig_atomic_t alarmed;
15 static void do_alarm(int signum)
16 {
17         alarmed++;
18 }
19
20 static int do_operation(enum operation op, const char *name)
21 {
22         TDB_DATA k = { .dptr = (void *)"a", .dsize = 1 };
23         TDB_DATA d = { .dptr = (void *)"b", .dsize = 1 };
24         struct tdb_context *tdb;
25
26         tdb = tdb_open(name, 0, op == OPEN_WITH_CLEAR_IF_FIRST ?
27                        TDB_CLEAR_IF_FIRST : TDB_DEFAULT, O_RDWR, 0);
28         if (!tdb)
29                 return -1;
30
31         if (op == OPEN || op == OPEN_WITH_CLEAR_IF_FIRST) {
32                 tdb_close(tdb);
33                 return 0;
34         }
35
36         alarmed = 0;
37         tdb_setalarm_sigptr(tdb, &alarmed);
38
39         alarm(1);
40         if (tdb_transaction_start(tdb) != 0)
41                 goto maybe_alarmed;
42
43         if (tdb_store(tdb, k, d, 0) != 0) {
44                 tdb_transaction_cancel(tdb);
45                 tdb_close(tdb);
46                 return -2;
47         }
48
49         if (tdb_transaction_commit(tdb) == 0) {
50                 tdb_delete(tdb, k);
51                 tdb_close(tdb);
52                 return 1;
53         }
54
55         tdb_delete(tdb, k);
56 maybe_alarmed:
57         tdb_close(tdb);
58         if (alarmed)
59                 return 0;
60         return -3;
61 }
62
63 struct agent {
64         int cmdfd, responsefd;
65 };
66
67 /* Do this before doing any tdb stuff.  Return handle, or NULL. */
68 struct agent *prepare_external_agent(void)
69 {
70         int pid;
71         int command[2], response[2];
72         struct sigaction act = { .sa_handler = do_alarm };
73         char name[1+PATH_MAX];
74
75         if (pipe(command) != 0 || pipe(response) != 0)
76                 return NULL;
77
78         pid = fork();
79         if (pid < 0)
80                 return NULL;
81
82         if (pid != 0) {
83                 struct agent *agent = malloc(sizeof(*agent));
84
85                 close(command[0]);
86                 close(response[1]);
87                 agent->cmdfd = command[1];
88                 agent->responsefd = response[0];
89                 return agent;
90         }
91
92         close(command[1]);
93         close(response[0]);
94         sigaction(SIGALRM, &act, NULL);
95
96         while (read(command[0], name, sizeof(name)) != 0) {
97                 int result;
98
99                 result = do_operation(name[0], name+1);
100                 if (write(response[1], &result, sizeof(result))
101                     != sizeof(result))
102                         err(1, "Writing response");
103         }
104         exit(0);
105 }
106
107 /* Ask the external agent to try to do an operation. */
108 bool external_agent_operation(struct agent *agent,
109                               enum operation op, const char *tdbname)
110 {
111         int res;
112         char string[1 + strlen(tdbname) + 1];
113
114         string[0] = op;
115         strcpy(string+1, tdbname);
116
117         if (write(agent->cmdfd, string, sizeof(string)) != sizeof(string))
118                 err(1, "Writing to agent");
119
120         if (read(agent->responsefd, &res, sizeof(res)) != sizeof(res))
121                 err(1, "Reading from agent");
122
123         if (res > 1)
124                 errx(1, "Agent returned %u\n", res);
125
126         return res;
127 }
128
129 void external_agent_operation_start(struct agent *agent,
130                                     enum operation op, const char *tdbname)
131 {
132         char string[1 + strlen(tdbname) + 1];
133
134         string[0] = op;
135         strcpy(string+1, tdbname);
136
137         if (write(agent->cmdfd, string, sizeof(string)) != sizeof(string))
138                 err(1, "Writing to agent");
139 }
140
141 bool external_agent_operation_check(struct agent *agent, bool block, int *res)
142 {
143         int flags = fcntl(agent->responsefd, F_GETFL);
144
145         if (block)
146                 fcntl(agent->responsefd, F_SETFL, flags & ~O_NONBLOCK);
147         else
148                 fcntl(agent->responsefd, F_SETFL, flags | O_NONBLOCK);
149
150         switch (read(agent->responsefd, res, sizeof(*res))) {
151         case sizeof(*res):
152                 break;
153         case 0:
154                 errx(1, "Agent died?");
155         default:
156                 if (!block && (errno == EAGAIN || errno == EWOULDBLOCK))
157                         return false;
158                 err(1, "%slocking reading from agent", block ? "B" : "Non-b");
159         }
160
161         if (*res > 1)
162                 errx(1, "Agent returned %u\n", *res);
163
164         return true;
165 }