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