b8943602655f79c5c6a0bc9851321cc4467ee268
[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         if (setjmp(jmpbuf) != 0) {
102                 /* We're partway through.  Simulate our death. */
103                 close(tdb->fd);
104                 forget_locking();
105                 in_transaction = false;
106
107                 ret = external_agent_operation(agent, NEEDS_RECOVERY, "");
108                 if (ret == SUCCESS)
109                         needed_recovery++;
110                 else if (ret != FAILED) {
111                         diag("Step %u agent NEEDS_RECOVERY = %s", current,
112                              agent_return_name(ret));
113                         return false;
114                 }
115
116                 ret = external_agent_operation(agent, op, KEY_STRING);
117                 if (ret != SUCCESS) {
118                         diag("Step %u op %s failed = %s", current,
119                              operation_name(op),
120                              agent_return_name(ret));
121                         return false;
122                 }
123
124                 ret = external_agent_operation(agent, NEEDS_RECOVERY, "");
125                 if (ret != FAILED) {
126                         diag("Still needs recovery after step %u = %s",
127                              current, agent_return_name(ret));
128                         return false;
129                 }
130
131                 ret = external_agent_operation(agent, CHECK, "");
132                 if (ret != SUCCESS) {
133                         diag("Step %u check failed = %s", current,
134                              agent_return_name(ret));
135                         return false;
136                 }
137
138                 ret = external_agent_operation(agent, CLOSE, "");
139                 if (ret != SUCCESS) {
140                         diag("Step %u close failed = %s", current,
141                              agent_return_name(ret));
142                         return false;
143                 }
144
145                 /* Suppress logging as this tries to use closed fd. */
146                 suppress_logging = true;
147                 suppress_lockcheck = true;
148                 tdb_close(tdb);
149                 suppress_logging = false;
150                 suppress_lockcheck = false;
151                 target++;
152                 current = 0;
153                 goto reset;
154         }
155
156         unlink(TEST_DBNAME);
157         tdb = tdb_open_ex(TEST_DBNAME, 1024, TDB_NOMMAP,
158                           O_CREAT|O_TRUNC|O_RDWR, 0600, &taplogctx, NULL);
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 }