]> git.ozlabs.org Git - ccan/blob - ccan/tdb2/test/external-agent.c
tdb2: simplify failtest helper.
[ccan] / ccan / tdb2 / test / external-agent.c
1 #include "external-agent.h"
2 #include "logging.h"
3 #include "lock-tracking.h"
4 #include <sys/types.h>
5 #include <sys/wait.h>
6 #include <unistd.h>
7 #include <err.h>
8 #include <fcntl.h>
9 #include <stdlib.h>
10 #include <limits.h>
11 #include <string.h>
12 #include <errno.h>
13 #include <ccan/tdb2/tdb1_private.h>
14 #include <ccan/tap/tap.h>
15 #include <stdio.h>
16 #include <stdarg.h>
17
18 static struct tdb_context *tdb;
19
20 void (*external_agent_free)(void *) = free;
21
22 static enum TDB_ERROR clear_if_first(int fd, void *arg)
23 {
24 /* We hold a lock offset 4 always, so we can tell if anyone is holding it.
25  * (This is compatible with tdb1's TDB_CLEAR_IF_FIRST flag).  */
26         struct flock fl;
27
28         fl.l_type = F_WRLCK;
29         fl.l_whence = SEEK_SET;
30         fl.l_start = 4;
31         fl.l_len = 1;
32
33         if (fcntl(fd, F_SETLK, &fl) == 0) {
34                 /* We must be first ones to open it! */
35                 diag("agent truncating file!");
36                 if (ftruncate(fd, 0) != 0) {
37                         return TDB_ERR_IO;
38                 }
39         }
40         fl.l_type = F_RDLCK;
41         if (fcntl(fd, F_SETLKW, &fl) != 0) {
42                 return TDB_ERR_IO;
43         }
44         return TDB_SUCCESS;
45 }
46
47 static enum agent_return do_operation(enum operation op, const char *name)
48 {
49         TDB_DATA k;
50         enum agent_return ret;
51         TDB_DATA data;
52         enum TDB_ERROR ecode;
53         union tdb_attribute cif;
54
55         if (op != OPEN && op != OPEN_WITH_HOOK && !tdb) {
56                 diag("external: No tdb open!");
57                 return OTHER_FAILURE;
58         }
59
60         diag("external: %s", operation_name(op));
61
62         k = tdb_mkdata(name, strlen(name));
63
64         locking_would_block = 0;
65         switch (op) {
66         case OPEN:
67                 if (tdb) {
68                         diag("Already have tdb %s open", tdb->name);
69                         return OTHER_FAILURE;
70                 }
71                 tdb = tdb_open(name, TDB_DEFAULT, O_RDWR, 0, &tap_log_attr);
72                 if (!tdb) {
73                         if (!locking_would_block)
74                                 diag("Opening tdb gave %s", strerror(errno));
75                         forget_locking();
76                         ret = OTHER_FAILURE;
77                 } else
78                         ret = SUCCESS;
79                 break;
80         case OPEN_WITH_HOOK:
81                 if (tdb) {
82                         diag("Already have tdb %s open", tdb->name);
83                         return OTHER_FAILURE;
84                 }
85                 cif.openhook.base.attr = TDB_ATTRIBUTE_OPENHOOK;
86                 cif.openhook.base.next = &tap_log_attr;
87                 cif.openhook.fn = clear_if_first;
88                 tdb = tdb_open(name, TDB_DEFAULT, O_RDWR, 0, &cif);
89                 if (!tdb) {
90                         if (!locking_would_block)
91                                 diag("Opening tdb gave %s", strerror(errno));
92                         forget_locking();
93                         ret = OTHER_FAILURE;
94                 } else
95                         ret = SUCCESS;
96                 break;
97         case FETCH:
98                 ecode = tdb_fetch(tdb, k, &data);
99                 if (ecode == TDB_ERR_NOEXIST) {
100                         ret = FAILED;
101                 } else if (ecode < 0) {
102                         ret = OTHER_FAILURE;
103                 } else if (!tdb_deq(data, k)) {
104                         ret = OTHER_FAILURE;
105                         external_agent_free(data.dptr);
106                 } else {
107                         ret = SUCCESS;
108                         external_agent_free(data.dptr);
109                 }
110                 break;
111         case STORE:
112                 ret = tdb_store(tdb, k, k, 0) == 0 ? SUCCESS : OTHER_FAILURE;
113                 break;
114         case TRANSACTION_START:
115                 ret = tdb_transaction_start(tdb) == 0 ? SUCCESS : OTHER_FAILURE;
116                 break;
117         case TRANSACTION_COMMIT:
118                 ret = tdb_transaction_commit(tdb)==0 ? SUCCESS : OTHER_FAILURE;
119                 break;
120         case NEEDS_RECOVERY:
121                 if (tdb->flags & TDB_VERSION1)
122                         ret = tdb1_needs_recovery(tdb) ? SUCCESS : FAILED;
123                 else
124                         ret = tdb_needs_recovery(tdb) ? SUCCESS : FAILED;
125                 break;
126         case CHECK:
127                 ret = tdb_check(tdb, NULL, NULL) == 0 ? SUCCESS : OTHER_FAILURE;
128                 break;
129         case CLOSE:
130                 ret = tdb_close(tdb) == 0 ? SUCCESS : OTHER_FAILURE;
131                 tdb = NULL;
132                 break;
133         case SEND_SIGNAL:
134                 /* We do this async */
135                 ret = SUCCESS;
136                 break;
137         default:
138                 ret = OTHER_FAILURE;
139         }
140
141         if (locking_would_block)
142                 ret = WOULD_HAVE_BLOCKED;
143
144         return ret;
145 }
146
147 struct agent {
148         int cmdfd, responsefd;
149 };
150
151 /* Do this before doing any tdb stuff.  Return handle, or NULL. */
152 struct agent *prepare_external_agent(void)
153 {
154         int pid, ret;
155         int command[2], response[2];
156         char name[1+PATH_MAX];
157
158         if (pipe(command) != 0 || pipe(response) != 0)
159                 return NULL;
160
161         pid = fork();
162         if (pid < 0)
163                 return NULL;
164
165         if (pid != 0) {
166                 struct agent *agent = malloc(sizeof(*agent));
167
168                 close(command[0]);
169                 close(response[1]);
170                 agent->cmdfd = command[1];
171                 agent->responsefd = response[0];
172                 return agent;
173         }
174
175         close(command[1]);
176         close(response[0]);
177
178         /* We want to fail, not block. */
179         nonblocking_locks = true;
180         log_prefix = "external: ";
181         while ((ret = read(command[0], name, sizeof(name))) > 0) {
182                 enum agent_return result;
183
184                 result = do_operation(name[0], name+1);
185                 if (write(response[1], &result, sizeof(result))
186                     != sizeof(result))
187                         err(1, "Writing response");
188                 if (name[0] == SEND_SIGNAL) {
189                         struct timeval ten_ms;
190                         ten_ms.tv_sec = 0;
191                         ten_ms.tv_usec = 10000;
192                         select(0, NULL, NULL, NULL, &ten_ms);
193                         kill(getppid(), SIGUSR1);
194                 }
195         }
196         exit(0);
197 }
198
199 /* Ask the external agent to try to do an operation. */
200 enum agent_return external_agent_operation(struct agent *agent,
201                                            enum operation op,
202                                            const char *name)
203 {
204         enum agent_return res;
205         unsigned int len;
206         char *string;
207
208         if (!name)
209                 name = "";
210         len = 1 + strlen(name) + 1;
211         string = malloc(len);
212
213         string[0] = op;
214         strcpy(string+1, name);
215
216         if (write(agent->cmdfd, string, len) != len
217             || read(agent->responsefd, &res, sizeof(res)) != sizeof(res))
218                 res = AGENT_DIED;
219
220         free(string);
221         return res;
222 }
223
224 const char *agent_return_name(enum agent_return ret)
225 {
226         return ret == SUCCESS ? "SUCCESS"
227                 : ret == WOULD_HAVE_BLOCKED ? "WOULD_HAVE_BLOCKED"
228                 : ret == AGENT_DIED ? "AGENT_DIED"
229                 : ret == FAILED ? "FAILED"
230                 : ret == OTHER_FAILURE ? "OTHER_FAILURE"
231                 : "**INVALID**";
232 }
233
234 const char *operation_name(enum operation op)
235 {
236         switch (op) {
237         case OPEN: return "OPEN";
238         case OPEN_WITH_HOOK: return "OPEN_WITH_HOOK";
239         case FETCH: return "FETCH";
240         case STORE: return "STORE";
241         case CHECK: return "CHECK";
242         case TRANSACTION_START: return "TRANSACTION_START";
243         case TRANSACTION_COMMIT: return "TRANSACTION_COMMIT";
244         case NEEDS_RECOVERY: return "NEEDS_RECOVERY";
245         case SEND_SIGNAL: return "SEND_SIGNAL";
246         case CLOSE: return "CLOSE";
247         }
248         return "**INVALID**";
249 }
250
251 void free_external_agent(struct agent *agent)
252 {
253         close(agent->cmdfd);
254         close(agent->responsefd);
255         free(agent);
256 }