tdb: new test, cleanup old tests by centralizing lock tracking.
[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 #if 0
80                 return tdb_maybe_needs_recovery(tdb);
81 #else
82                 return 0;
83 #endif
84         }
85
86         alarmed = 0;
87         tdb_setalarm_sigptr(tdb, &alarmed);
88
89         alarm(1);
90         if (tdb_transaction_start(tdb) != 0)
91                 goto maybe_alarmed;
92
93         alarm(0);
94         if (tdb_store(tdb, k, d, 0) != 0) {
95                 tdb_transaction_cancel(tdb);
96                 tdb_close(tdb);
97                 tdb = NULL;
98                 return -2;
99         }
100
101         if (tdb_transaction_commit(tdb) == 0) {
102                 tdb_delete(tdb, k);
103                 if (op != TRANSACTION_KEEP_OPENED) {
104                         tdb_close(tdb);
105                         tdb = NULL;
106                 }
107                 return 1;
108         }
109
110         tdb_delete(tdb, k);
111 maybe_alarmed:
112         if (op != TRANSACTION_KEEP_OPENED) {
113                 tdb_close(tdb);
114                 tdb = NULL;
115         }
116         if (alarmed)
117                 return 0;
118         return -3;
119 }
120
121 struct agent {
122         int cmdfd, responsefd;
123 };
124
125 /* Do this before doing any tdb stuff.  Return handle, or NULL. */
126 struct agent *prepare_external_agent(void)
127 {
128         int pid, ret;
129         int command[2], response[2];
130         struct sigaction act = { .sa_handler = do_alarm };
131         char name[1+PATH_MAX];
132
133         if (pipe(command) != 0 || pipe(response) != 0)
134                 return NULL;
135
136         pid = fork();
137         if (pid < 0)
138                 return NULL;
139
140         if (pid != 0) {
141                 struct agent *agent = malloc(sizeof(*agent));
142
143                 close(command[0]);
144                 close(response[1]);
145                 agent->cmdfd = command[1];
146                 agent->responsefd = response[0];
147                 return agent;
148         }
149
150         close(command[1]);
151         close(response[0]);
152         sigaction(SIGALRM, &act, NULL);
153
154         while ((ret = read(command[0], name, sizeof(name))) > 0) {
155                 int result;
156
157                 result = do_operation(name[0], name+1);
158                 if (write(response[1], &result, sizeof(result))
159                     != sizeof(result))
160                         err(1, "Writing response");
161         }
162         diag("external: read %i: %s", ret, strerror(errno));
163         exit(0);
164 }
165
166 /* Ask the external agent to try to do an operation. */
167 int external_agent_operation(struct agent *agent,
168                               enum operation op, const char *tdbname)
169 {
170         int res;
171         char string[1 + strlen(tdbname) + 1];
172
173         string[0] = op;
174         strcpy(string+1, tdbname);
175
176         if (write(agent->cmdfd, string, sizeof(string)) != sizeof(string))
177                 err(1, "Writing to agent");
178
179         if (read(agent->responsefd, &res, sizeof(res)) != sizeof(res))
180                 err(1, "Reading from agent");
181
182         if (res > 1)
183                 errx(1, "Agent returned %u\n", res);
184
185         return res;
186 }
187
188 void external_agent_operation_start(struct agent *agent,
189                                     enum operation op, const char *tdbname)
190 {
191         char string[1 + strlen(tdbname) + 1];
192
193         string[0] = op;
194         strcpy(string+1, tdbname);
195
196         if (write(agent->cmdfd, string, sizeof(string)) != sizeof(string))
197                 err(1, "Writing to agent");
198 }
199
200 bool external_agent_operation_check(struct agent *agent, bool block, int *res)
201 {
202         int flags = fcntl(agent->responsefd, F_GETFL);
203
204         if (block)
205                 fcntl(agent->responsefd, F_SETFL, flags & ~O_NONBLOCK);
206         else
207                 fcntl(agent->responsefd, F_SETFL, flags | O_NONBLOCK);
208
209         switch (read(agent->responsefd, res, sizeof(*res))) {
210         case sizeof(*res):
211                 break;
212         case 0:
213                 errx(1, "Agent died?");
214         default:
215                 if (!block && (errno == EAGAIN || errno == EWOULDBLOCK))
216                         return false;
217                 err(1, "%slocking reading from agent", block ? "B" : "Non-b");
218         }
219
220         if (*res > 1)
221                 errx(1, "Agent returned %u\n", *res);
222
223         return true;
224 }