New test: reveals race (found by Volker) when open occurs during transaction commit.
[ccan] / ccan / tdb / test / run-open-during-transaction.c
1 #define _XOPEN_SOURCE 500
2 #include <unistd.h>
3 static ssize_t pwrite_check(int fd, const void *buf, size_t count, off_t offset);
4 static ssize_t write_check(int fd, const void *buf, size_t count);
5 static int fcntl_check(int fd, int cmd, ... /* arg */ );
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_check
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/tap/tap.h>
23 #include <stdlib.h>
24 #include <stdbool.h>
25 #include <stdarg.h>
26 #include <err.h>
27 #include "external-transaction.h"
28
29 static struct agent *agent;
30 static bool agent_pending;
31 static bool in_transaction;
32 static int errors = 0;
33 static bool snapshot_uptodate;
34 static char *snapshot;
35 static off_t snapshot_len;
36 static bool clear_if_first;
37 #define TEST_DBNAME "/tmp/test7.tdb"
38
39 #undef write
40 #undef pwrite
41 #undef fcntl
42 #undef ftruncate
43
44 static void save_file_contents(int fd)
45 {
46         struct stat st;
47         int res;
48
49         /* Save copy of file. */
50         stat(TEST_DBNAME, &st);
51         if (snapshot_len != st.st_size) {
52                 snapshot = realloc(snapshot, st.st_size * 2);
53                 snapshot_len = st.st_size;
54         }
55         res = pread(fd, snapshot, snapshot_len, 0);
56         if (res != snapshot_len)
57                 err(1, "Reading %zu bytes = %u", (size_t)snapshot_len, res);
58         snapshot_uptodate = true;
59 }
60
61 static void check_for_agent(int fd, bool block)
62 {
63         struct stat st;
64         int res;
65
66         if (!external_agent_operation_check(agent, block, &res))
67                 return;
68
69         if (res != 0)
70                 err(1, "Agent failed open");
71         agent_pending = false;
72
73         if (!snapshot_uptodate)
74                 return;
75
76         stat(TEST_DBNAME, &st);
77         if (st.st_size != snapshot_len) {
78                 diag("Other open changed size from %zu -> %zu",
79                      (size_t)snapshot_len, (size_t)st.st_size);
80                 errors++;
81                 return;
82         }
83
84         if (pread(fd, snapshot+snapshot_len, snapshot_len, 0) != snapshot_len)
85                 err(1, "Reading %zu bytes", (size_t)snapshot_len);
86         if (memcmp(snapshot, snapshot+snapshot_len, snapshot_len) != 0) {
87                 diag("File changed");
88                 errors++;
89                 return;
90         }
91 }
92
93 static void check_file_contents(int fd)
94 {
95         if (agent_pending)
96                 check_for_agent(fd, false);
97
98         if (!agent_pending) {
99                 save_file_contents(fd);
100
101                 /* Ask agent to open file. */
102                 external_agent_operation_start(agent,
103                                                clear_if_first ?
104                                                OPEN_WITH_CLEAR_IF_FIRST :
105                                                OPEN,
106                                                TEST_DBNAME);
107                 agent_pending = true;
108                 /* Hack: give it a chance to run. */
109                 sleep(0);
110         }
111
112         check_for_agent(fd, false);
113 }
114
115 static ssize_t pwrite_check(int fd,
116                             const void *buf, size_t count, off_t offset)
117 {
118         ssize_t ret;
119
120         if (in_transaction)
121                 check_file_contents(fd);
122
123         snapshot_uptodate = false;
124         ret = pwrite(fd, buf, count, offset);
125         if (ret != count)
126                 return ret;
127
128         if (in_transaction)
129                 check_file_contents(fd);
130         return ret;
131 }
132
133 static ssize_t write_check(int fd, const void *buf, size_t count)
134 {
135         ssize_t ret;
136
137         if (in_transaction)
138                 check_file_contents(fd);
139
140         snapshot_uptodate = false;
141
142         ret = write(fd, buf, count);
143         if (ret != count)
144                 return ret;
145
146         if (in_transaction)
147                 check_file_contents(fd);
148         return ret;
149 }
150
151 /* This seems to be a macro for glibc... */
152 extern int fcntl(int fd, int cmd, ... /* arg */ );
153
154 static int fcntl_check(int fd, int cmd, ... /* arg */ )
155 {
156         va_list ap;
157         int ret, arg3;
158         struct flock *fl;
159
160         if (cmd != F_SETLK && cmd != F_SETLKW) {
161                 /* This may be totally bogus, but we don't know in general. */
162                 va_start(ap, cmd);
163                 arg3 = va_arg(ap, int);
164                 va_end(ap);
165
166                 return fcntl(fd, cmd, arg3);
167         }
168
169         va_start(ap, cmd);
170         fl = va_arg(ap, struct flock *);
171         va_end(ap);
172
173         ret = fcntl(fd, cmd, fl);
174
175         if (in_transaction && fl->l_type == F_UNLCK)
176                 check_file_contents(fd);
177         return ret;
178 }
179
180 static int ftruncate_check(int fd, off_t length)
181 {
182         int ret;
183
184         if (in_transaction)
185                 check_file_contents(fd);
186
187         snapshot_uptodate = false;
188
189         ret = ftruncate(fd, length);
190
191         if (in_transaction)
192                 check_file_contents(fd);
193         return ret;
194 }
195
196 int main(int argc, char *argv[])
197 {
198         const int flags[] = { TDB_DEFAULT,
199                               TDB_CLEAR_IF_FIRST,
200                               TDB_NOMMAP, 
201                               TDB_CLEAR_IF_FIRST | TDB_NOMMAP };
202         int i;
203         struct tdb_context *tdb;
204         TDB_DATA key, data;
205
206         plan_tests(20);
207         agent = prepare_external_agent();
208         if (!agent)
209                 err(1, "preparing agent");
210
211         /* Nice ourselves down: we can't tell the difference between agent
212          * blocking on lock, and agent not scheduled. */
213         nice(15);
214
215         for (i = 0; i < sizeof(flags)/sizeof(flags[0]); i++) {
216                 unlink(TEST_DBNAME);
217                 tdb = tdb_open(TEST_DBNAME, 1024, flags[i],
218                                O_CREAT|O_TRUNC|O_RDWR, 0600);
219                 ok1(tdb);
220                 clear_if_first = (flags[i] & TDB_CLEAR_IF_FIRST);
221
222                 ok1(tdb_transaction_start(tdb) == 0);
223                 in_transaction = true;
224                 key.dsize = strlen("hi");
225                 key.dptr = (void *)"hi";
226                 data.dptr = (void *)"world";
227                 data.dsize = strlen("world");
228
229                 ok1(tdb_store(tdb, key, data, TDB_INSERT) == 0);
230                 ok1(tdb_transaction_commit(tdb) == 0);
231                 if (agent_pending)
232                         check_for_agent(tdb->fd, true);
233                 ok(errors == 0, "We had %u errors", errors);
234
235                 tdb_close(tdb);
236         }
237
238         return exit_status();
239 }