tdb2: transaction support
[ccan] / ccan / tdb2 / test / run-57-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/tdb2/tdb.c>
14 #include <ccan/tdb2/free.c>
15 #include <ccan/tdb2/lock.c>
16 #include <ccan/tdb2/io.c>
17 #include <ccan/tdb2/hash.c>
18 #include <ccan/tdb2/check.c>
19 #include <ccan/tdb2/transaction.c>
20 #include <ccan/tap/tap.h>
21 #include <stdlib.h>
22 #include <stdbool.h>
23 #include <stdarg.h>
24 #include <err.h>
25 #include <setjmp.h>
26 #include "external-agent.h"
27 #include "logging.h"
28
29 #undef write
30 #undef pwrite
31 #undef fcntl
32 #undef ftruncate
33
34 static bool in_transaction;
35 static int target, current;
36 static jmp_buf jmpbuf;
37 #define TEST_DBNAME "run-57-die-during-transaction.tdb"
38 #define KEY_STRING "helloworld"
39
40 static void maybe_die(int fd)
41 {
42         if (in_transaction && current++ == target) {
43                 longjmp(jmpbuf, 1);
44         }
45 }
46
47 static ssize_t pwrite_check(int fd,
48                             const void *buf, size_t count, off_t offset)
49 {
50         ssize_t ret;
51
52         maybe_die(fd);
53
54         ret = pwrite(fd, buf, count, offset);
55         if (ret != count)
56                 return ret;
57
58         maybe_die(fd);
59         return ret;
60 }
61
62 static ssize_t write_check(int fd, const void *buf, size_t count)
63 {
64         ssize_t ret;
65
66         maybe_die(fd);
67
68         ret = write(fd, buf, count);
69         if (ret != count)
70                 return ret;
71
72         maybe_die(fd);
73         return ret;
74 }
75
76 static int ftruncate_check(int fd, off_t length)
77 {
78         int ret;
79
80         maybe_die(fd);
81
82         ret = ftruncate(fd, length);
83
84         maybe_die(fd);
85         return ret;
86 }
87
88 static bool test_death(enum operation op, struct agent *agent)
89 {
90         struct tdb_context *tdb = NULL;
91         TDB_DATA key;
92         enum agent_return ret;
93         int needed_recovery = 0;
94
95         current = target = 0;
96 reset:
97         unlink(TEST_DBNAME);
98         tdb = tdb_open(TEST_DBNAME, TDB_NOMMAP,
99                        O_CREAT|O_TRUNC|O_RDWR, 0600, &tap_log_attr);
100
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         /* Put key for agent to fetch. */
157         key.dsize = strlen(KEY_STRING);
158         key.dptr = (void *)KEY_STRING;
159         if (tdb_store(tdb, key, key, TDB_INSERT) != 0)
160                 return false;
161
162         /* This is the key we insert in transaction. */
163         key.dsize--;
164
165         ret = external_agent_operation(agent, OPEN, TEST_DBNAME);
166         if (ret != SUCCESS)
167                 errx(1, "Agent failed to open: %s", agent_return_name(ret));
168
169         ret = external_agent_operation(agent, FETCH, KEY_STRING);
170         if (ret != SUCCESS)
171                 errx(1, "Agent failed find key: %s", agent_return_name(ret));
172
173         in_transaction = true;
174         if (tdb_transaction_start(tdb) != 0)
175                 return false;
176
177         if (tdb_store(tdb, key, key, TDB_INSERT) != 0)
178                 return false;
179
180         if (tdb_transaction_commit(tdb) != 0)
181                 return false;
182
183         in_transaction = false;
184
185         /* We made it! */
186         diag("Completed %u runs", current);
187         tdb_close(tdb);
188         ret = external_agent_operation(agent, CLOSE, "");
189         if (ret != SUCCESS) {
190                 diag("Step %u close failed = %s", current,
191                      agent_return_name(ret));
192                 return false;
193         }
194
195         ok1(needed_recovery);
196         ok1(locking_errors == 0);
197         ok1(forget_locking() == 0);
198         locking_errors = 0;
199         return true;
200 }
201
202 int main(int argc, char *argv[])
203 {
204         enum operation ops[] = { FETCH, STORE, TRANSACTION_START };
205         struct agent *agent;
206         int i;
207
208         plan_tests(12);
209         unlock_callback = maybe_die;
210
211         agent = prepare_external_agent();
212         if (!agent)
213                 err(1, "preparing agent");
214
215         for (i = 0; i < sizeof(ops)/sizeof(ops[0]); i++) {
216                 diag("Testing %s after death", operation_name(ops[i]));
217                 ok1(test_death(ops[i], agent));
218         }
219
220         return exit_status();
221 }