tdb: add test for tdb_summary
[ccan] / ccan / tdb / test / run-die-during-transaction.c
1 #define _XOPEN_SOURCE 500
2 #include <unistd.h>
3 #include "lock-tracking.h"
4 static ssize_t pwrite_check(int fd, const void *buf, size_t count, off_t offset);
5 static ssize_t write_check(int fd, const void *buf, size_t count);
6 static int ftruncate_check(int fd, off_t length);
7
8 #define pwrite pwrite_check
9 #define write write_check
10 #define fcntl fcntl_with_lockcheck
11 #define ftruncate ftruncate_check
12
13 #include <ccan/tdb/tdb.h>
14 #include <ccan/tdb/io.c>
15 #include <ccan/tdb/tdb.c>
16 #include <ccan/tdb/lock.c>
17 #include <ccan/tdb/freelist.c>
18 #include <ccan/tdb/traverse.c>
19 #include <ccan/tdb/transaction.c>
20 #include <ccan/tdb/error.c>
21 #include <ccan/tdb/open.c>
22 #include <ccan/tdb/check.c>
23 #include <ccan/tdb/hash.c>
24 #include <ccan/tap/tap.h>
25 #include <stdlib.h>
26 #include <stdbool.h>
27 #include <stdarg.h>
28 #include <err.h>
29 #include <setjmp.h>
30 #include "external-agent.h"
31 #include "logging.h"
32
33 #undef write
34 #undef pwrite
35 #undef fcntl
36 #undef ftruncate
37
38 static bool in_transaction;
39 static int target, current;
40 static jmp_buf jmpbuf;
41 #define TEST_DBNAME "run-die-during-transaction.tdb"
42 #define KEY_STRING "helloworld"
43
44 static void maybe_die(int fd)
45 {
46         if (in_transaction && current++ == target) {
47                 longjmp(jmpbuf, 1);
48         }
49 }
50
51 static ssize_t pwrite_check(int fd,
52                             const void *buf, size_t count, off_t offset)
53 {
54         ssize_t ret;
55
56         maybe_die(fd);
57
58         ret = pwrite(fd, buf, count, offset);
59         if (ret != count)
60                 return ret;
61
62         maybe_die(fd);
63         return ret;
64 }
65
66 static ssize_t write_check(int fd, const void *buf, size_t count)
67 {
68         ssize_t ret;
69
70         maybe_die(fd);
71
72         ret = write(fd, buf, count);
73         if (ret != count)
74                 return ret;
75
76         maybe_die(fd);
77         return ret;
78 }
79
80 static int ftruncate_check(int fd, off_t length)
81 {
82         int ret;
83
84         maybe_die(fd);
85
86         ret = ftruncate(fd, length);
87
88         maybe_die(fd);
89         return ret;
90 }
91
92 static bool test_death(enum operation op, struct agent *agent)
93 {
94         struct tdb_context *tdb = NULL;
95         TDB_DATA key;
96         enum agent_return ret;
97         int needed_recovery = 0;
98
99         current = target = 0;
100 reset:
101         unlink(TEST_DBNAME);
102         tdb = tdb_open_ex(TEST_DBNAME, 1024, TDB_NOMMAP,
103                           O_CREAT|O_TRUNC|O_RDWR, 0600, &taplogctx, NULL);
104
105         if (setjmp(jmpbuf) != 0) {
106                 /* We're partway through.  Simulate our death. */
107                 close(tdb->fd);
108                 forget_locking();
109                 in_transaction = false;
110
111                 ret = external_agent_operation(agent, NEEDS_RECOVERY, "");
112                 if (ret == SUCCESS)
113                         needed_recovery++;
114                 else if (ret != FAILED) {
115                         diag("Step %u agent NEEDS_RECOVERY = %s", current,
116                              agent_return_name(ret));
117                         return false;
118                 }
119
120                 ret = external_agent_operation(agent, op, KEY_STRING);
121                 if (ret != SUCCESS) {
122                         diag("Step %u op %s failed = %s", current,
123                              operation_name(op),
124                              agent_return_name(ret));
125                         return false;
126                 }
127
128                 ret = external_agent_operation(agent, NEEDS_RECOVERY, "");
129                 if (ret != FAILED) {
130                         diag("Still needs recovery after step %u = %s",
131                              current, agent_return_name(ret));
132                         return false;
133                 }
134
135                 ret = external_agent_operation(agent, CHECK, "");
136                 if (ret != SUCCESS) {
137                         diag("Step %u check failed = %s", current,
138                              agent_return_name(ret));
139                         return false;
140                 }
141
142                 ret = external_agent_operation(agent, CLOSE, "");
143                 if (ret != SUCCESS) {
144                         diag("Step %u close failed = %s", current,
145                              agent_return_name(ret));
146                         return false;
147                 }
148
149                 /* Suppress logging as this tries to use closed fd. */
150                 suppress_logging = true;
151                 suppress_lockcheck = true;
152                 tdb_close(tdb);
153                 suppress_logging = false;
154                 suppress_lockcheck = false;
155                 target++;
156                 current = 0;
157                 goto reset;
158         }
159
160         /* Put key for agent to fetch. */
161         key.dsize = strlen(KEY_STRING);
162         key.dptr = (void *)KEY_STRING;
163         if (tdb_store(tdb, key, key, TDB_INSERT) != 0)
164                 return false;
165
166         /* This is the key we insert in transaction. */
167         key.dsize--;
168
169         ret = external_agent_operation(agent, OPEN, TEST_DBNAME);
170         if (ret != SUCCESS)
171                 errx(1, "Agent failed to open: %s", agent_return_name(ret));
172
173         ret = external_agent_operation(agent, FETCH, KEY_STRING);
174         if (ret != SUCCESS)
175                 errx(1, "Agent failed find key: %s", agent_return_name(ret));
176
177         in_transaction = true;
178         if (tdb_transaction_start(tdb) != 0)
179                 return false;
180
181         if (tdb_store(tdb, key, key, TDB_INSERT) != 0)
182                 return false;
183
184         if (tdb_transaction_commit(tdb) != 0)
185                 return false;
186
187         in_transaction = false;
188
189         /* We made it! */
190         diag("Completed %u runs", current);
191         tdb_close(tdb);
192         ret = external_agent_operation(agent, CLOSE, "");
193         if (ret != SUCCESS) {
194                 diag("Step %u close failed = %s", current,
195                      agent_return_name(ret));
196                 return false;
197         }
198
199         ok1(needed_recovery);
200         ok1(locking_errors == 0);
201         ok1(forget_locking() == 0);
202         locking_errors = 0;
203         return true;
204 }
205
206 int main(int argc, char *argv[])
207 {
208         enum operation ops[] = { FETCH, STORE, TRANSACTION_START };
209         struct agent *agent;
210         int i;
211
212         plan_tests(12);
213         unlock_callback = maybe_die;
214
215         agent = prepare_external_agent();
216         if (!agent)
217                 err(1, "preparing agent");
218
219         for (i = 0; i < sizeof(ops)/sizeof(ops[0]); i++) {
220                 diag("Testing %s after death", operation_name(ops[i]));
221                 ok1(test_death(ops[i], agent));
222         }
223
224         return exit_status();
225 }