37dd50841eb14b66d00d8b01dd435a5eaaa9a681
[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/tdb/tdb_private.h>
13 #include <ccan/tap/tap.h>
14 #include <stdio.h>
15 #include <stdarg.h>
16
17 static struct tdb_context *tdb;
18
19 static volatile sig_atomic_t alarmed;
20 static void do_alarm(int signum)
21 {
22         alarmed++;
23 }
24
25 static void taplog(struct tdb_context *tdb,
26                    enum tdb_debug_level level,
27                    const char *fmt, ...)
28 {
29         va_list ap;
30         char line[200];
31
32         va_start(ap, fmt);
33         vsprintf(line, fmt, ap);
34         va_end(ap);
35
36         diag("external: %s", line);
37 }
38
39 static int do_operation(enum operation op, const char *name)
40 {
41         struct tdb_logging_context logctx = { taplog, NULL };
42
43         TDB_DATA k = { .dptr = (void *)"a", .dsize = 1 };
44         TDB_DATA d = { .dptr = (void *)"b", .dsize = 1 };
45
46         if (op <= KEEP_OPENED) {
47                 tdb = tdb_open_ex(name, 0, op == OPEN_WITH_CLEAR_IF_FIRST ?
48                                   TDB_CLEAR_IF_FIRST : TDB_DEFAULT, O_RDWR, 0,
49                                   &logctx, NULL);
50                 if (!tdb)
51                         return -1;
52         }
53
54         if (op == KEEP_OPENED) {
55                 return 0;
56         } else if (op == OPEN || op == OPEN_WITH_CLEAR_IF_FIRST || op == CLOSE) {
57                 tdb_close(tdb);
58                 tdb = NULL;
59                 return 1;
60         } else if (op == STORE_KEEP_OPENED) {
61                 if (tdb_store(tdb, k, d, 0) != 0)
62                         return -2;
63                 return 1;
64         } else if (op == FETCH_KEEP_OPENED) {
65                 TDB_DATA ret;
66                 ret = tdb_fetch(tdb, k);
67                 if (ret.dptr == NULL) {
68                         if (tdb_error(tdb) == TDB_ERR_NOEXIST)
69                                 return 1;
70                         return -3;
71                 }
72                 if (ret.dsize != 1 || *(char *)ret.dptr != 'b')
73                         return -4;
74                 free(ret.dptr);
75                 return 1;
76         } else if (op == CHECK_KEEP_OPENED) {
77                 return tdb_check(tdb, NULL, 0) == 0;
78         } else if (op == NEEDS_RECOVERY_KEEP_OPENED) {
79                 return tdb_maybe_needs_recovery(tdb);
80         }
81
82         alarmed = 0;
83         tdb_setalarm_sigptr(tdb, &alarmed);
84
85         alarm(1);
86         if (tdb_transaction_start(tdb) != 0)
87                 goto maybe_alarmed;
88
89         alarm(0);
90         if (tdb_store(tdb, k, d, 0) != 0) {
91                 tdb_transaction_cancel(tdb);
92                 tdb_close(tdb);
93                 tdb = NULL;
94                 return -2;
95         }
96
97         if (tdb_transaction_commit(tdb) == 0) {
98                 tdb_delete(tdb, k);
99                 if (op != TRANSACTION_KEEP_OPENED) {
100                         tdb_close(tdb);
101                         tdb = NULL;
102                 }
103                 return 1;
104         }
105
106         tdb_delete(tdb, k);
107 maybe_alarmed:
108         if (op != TRANSACTION_KEEP_OPENED) {
109                 tdb_close(tdb);
110                 tdb = NULL;
111         }
112         if (alarmed)
113                 return 0;
114         return -3;
115 }
116
117 struct agent {
118         int cmdfd, responsefd;
119 };
120
121 /* Do this before doing any tdb stuff.  Return handle, or NULL. */
122 struct agent *prepare_external_agent(void)
123 {
124         int pid, ret;
125         int command[2], response[2];
126         struct sigaction act = { .sa_handler = do_alarm };
127         char name[1+PATH_MAX];
128
129         if (pipe(command) != 0 || pipe(response) != 0)
130                 return NULL;
131
132         pid = fork();
133         if (pid < 0)
134                 return NULL;
135
136         if (pid != 0) {
137                 struct agent *agent = malloc(sizeof(*agent));
138
139                 close(command[0]);
140                 close(response[1]);
141                 agent->cmdfd = command[1];
142                 agent->responsefd = response[0];
143                 return agent;
144         }
145
146         close(command[1]);
147         close(response[0]);
148         sigaction(SIGALRM, &act, NULL);
149
150         while ((ret = read(command[0], name, sizeof(name))) > 0) {
151                 int result;
152
153                 result = do_operation(name[0], name+1);
154                 if (write(response[1], &result, sizeof(result))
155                     != sizeof(result))
156                         err(1, "Writing response");
157         }
158         diag("external: read %i: %s", ret, strerror(errno));
159         exit(0);
160 }
161
162 /* Ask the external agent to try to do an operation. */
163 int external_agent_operation(struct agent *agent,
164                               enum operation op, const char *tdbname)
165 {
166         int res;
167         char string[1 + strlen(tdbname) + 1];
168
169         string[0] = op;
170         strcpy(string+1, tdbname);
171
172         if (write(agent->cmdfd, string, sizeof(string)) != sizeof(string))
173                 err(1, "Writing to agent");
174
175         if (read(agent->responsefd, &res, sizeof(res)) != sizeof(res))
176                 err(1, "Reading from agent");
177
178         if (res > 1)
179                 errx(1, "Agent returned %u\n", res);
180
181         return res;
182 }
183
184 void external_agent_operation_start(struct agent *agent,
185                                     enum operation op, const char *tdbname)
186 {
187         char string[1 + strlen(tdbname) + 1];
188
189         string[0] = op;
190         strcpy(string+1, tdbname);
191
192         if (write(agent->cmdfd, string, sizeof(string)) != sizeof(string))
193                 err(1, "Writing to agent");
194 }
195
196 bool external_agent_operation_check(struct agent *agent, bool block, int *res)
197 {
198         int flags = fcntl(agent->responsefd, F_GETFL);
199
200         if (block)
201                 fcntl(agent->responsefd, F_SETFL, flags & ~O_NONBLOCK);
202         else
203                 fcntl(agent->responsefd, F_SETFL, flags | O_NONBLOCK);
204
205         switch (read(agent->responsefd, res, sizeof(*res))) {
206         case sizeof(*res):
207                 break;
208         case 0:
209                 errx(1, "Agent died?");
210         default:
211                 if (!block && (errno == EAGAIN || errno == EWOULDBLOCK))
212                         return false;
213                 err(1, "%slocking reading from agent", block ? "B" : "Non-b");
214         }
215
216         if (*res > 1)
217                 errx(1, "Agent returned %u\n", *res);
218
219         return true;
220 }