5119004b97fb6d2af561f83ced86fc9c4afa3af8
[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 taplog(struct tdb_context *tdb,
45                    enum tdb_debug_level level,
46                    const char *fmt, ...)
47 {
48         va_list ap;
49         char line[200];
50
51         va_start(ap, fmt);
52         vsprintf(line, fmt, ap);
53         va_end(ap);
54
55         diag("%s", line);
56 }
57
58 static void save_file_contents(int fd)
59 {
60         struct stat st;
61         int res;
62
63         /* Save copy of file. */
64         stat(TEST_DBNAME, &st);
65         if (snapshot_len != st.st_size) {
66                 snapshot = realloc(snapshot, st.st_size * 2);
67                 snapshot_len = st.st_size;
68         }
69         res = pread(fd, snapshot, snapshot_len, 0);
70         if (res != snapshot_len)
71                 err(1, "Reading %zu bytes = %u", (size_t)snapshot_len, res);
72         snapshot_uptodate = true;
73 }
74
75 static void check_for_agent(int fd, bool block)
76 {
77         struct stat st;
78         int res;
79
80         if (!external_agent_operation_check(agent, block, &res))
81                 return;
82
83         if (res != 0)
84                 err(1, "Agent failed open");
85         agent_pending = false;
86
87         if (!snapshot_uptodate)
88                 return;
89
90         stat(TEST_DBNAME, &st);
91         if (st.st_size != snapshot_len) {
92                 diag("Other open changed size from %zu -> %zu",
93                      (size_t)snapshot_len, (size_t)st.st_size);
94                 errors++;
95                 return;
96         }
97
98         if (pread(fd, snapshot+snapshot_len, snapshot_len, 0) != snapshot_len)
99                 err(1, "Reading %zu bytes", (size_t)snapshot_len);
100         if (memcmp(snapshot, snapshot+snapshot_len, snapshot_len) != 0) {
101                 diag("File changed");
102                 errors++;
103                 return;
104         }
105 }
106
107 static void check_file_contents(int fd)
108 {
109         if (agent_pending)
110                 check_for_agent(fd, false);
111
112         if (!agent_pending) {
113                 save_file_contents(fd);
114
115                 /* Ask agent to open file. */
116                 external_agent_operation_start(agent,
117                                                clear_if_first ?
118                                                OPEN_WITH_CLEAR_IF_FIRST :
119                                                OPEN,
120                                                TEST_DBNAME);
121                 agent_pending = true;
122                 /* Hack: give it a chance to run. */
123                 sleep(0);
124         }
125
126         check_for_agent(fd, false);
127 }
128
129 static ssize_t pwrite_check(int fd,
130                             const void *buf, size_t count, off_t offset)
131 {
132         ssize_t ret;
133
134         if (in_transaction)
135                 check_file_contents(fd);
136
137         snapshot_uptodate = false;
138         ret = pwrite(fd, buf, count, offset);
139         if (ret != count)
140                 return ret;
141
142         if (in_transaction)
143                 check_file_contents(fd);
144         return ret;
145 }
146
147 static ssize_t write_check(int fd, const void *buf, size_t count)
148 {
149         ssize_t ret;
150
151         if (in_transaction)
152                 check_file_contents(fd);
153
154         snapshot_uptodate = false;
155
156         ret = write(fd, buf, count);
157         if (ret != count)
158                 return ret;
159
160         if (in_transaction)
161                 check_file_contents(fd);
162         return ret;
163 }
164
165 /* This seems to be a macro for glibc... */
166 extern int fcntl(int fd, int cmd, ... /* arg */ );
167
168 static int fcntl_check(int fd, int cmd, ... /* arg */ )
169 {
170         va_list ap;
171         int ret, arg3;
172         struct flock *fl;
173
174         if (cmd != F_SETLK && cmd != F_SETLKW) {
175                 /* This may be totally bogus, but we don't know in general. */
176                 va_start(ap, cmd);
177                 arg3 = va_arg(ap, int);
178                 va_end(ap);
179
180                 return fcntl(fd, cmd, arg3);
181         }
182
183         va_start(ap, cmd);
184         fl = va_arg(ap, struct flock *);
185         va_end(ap);
186
187         ret = fcntl(fd, cmd, fl);
188
189         if (in_transaction && fl->l_type == F_UNLCK)
190                 check_file_contents(fd);
191         return ret;
192 }
193
194 static int ftruncate_check(int fd, off_t length)
195 {
196         int ret;
197
198         if (in_transaction)
199                 check_file_contents(fd);
200
201         snapshot_uptodate = false;
202
203         ret = ftruncate(fd, length);
204
205         if (in_transaction)
206                 check_file_contents(fd);
207         return ret;
208 }
209
210 int main(int argc, char *argv[])
211 {
212         struct tdb_logging_context logctx = { taplog, NULL };
213         const int flags[] = { TDB_DEFAULT,
214                               TDB_CLEAR_IF_FIRST,
215                               TDB_NOMMAP, 
216                               TDB_CLEAR_IF_FIRST | TDB_NOMMAP };
217         int i;
218         struct tdb_context *tdb;
219         TDB_DATA key, data;
220
221         plan_tests(20);
222         agent = prepare_external_agent();
223         if (!agent)
224                 err(1, "preparing agent");
225
226         /* Nice ourselves down: we can't tell the difference between agent
227          * blocking on lock, and agent not scheduled. */
228         nice(15);
229
230         for (i = 0; i < sizeof(flags)/sizeof(flags[0]); i++) {
231                 clear_if_first = (flags[i] & TDB_CLEAR_IF_FIRST);
232                 diag("Test with %s and %s\n",
233                      clear_if_first ? "CLEAR" : "DEFAULT",
234                      (flags[i] & TDB_NOMMAP) ? "no mmap" : "mmap");
235                 unlink(TEST_DBNAME);
236                 tdb = tdb_open_ex(TEST_DBNAME, 1024, flags[i],
237                                   O_CREAT|O_TRUNC|O_RDWR, 0600,
238                                   &logctx, NULL);
239                 ok1(tdb);
240
241                 ok1(tdb_transaction_start(tdb) == 0);
242                 in_transaction = true;
243                 key.dsize = strlen("hi");
244                 key.dptr = (void *)"hi";
245                 data.dptr = (void *)"world";
246                 data.dsize = strlen("world");
247
248                 ok1(tdb_store(tdb, key, data, TDB_INSERT) == 0);
249                 ok1(tdb_transaction_commit(tdb) == 0);
250                 if (agent_pending)
251                         check_for_agent(tdb->fd, true);
252                 ok(errors == 0, "We had %u unexpected changes", errors);
253
254                 tdb_close(tdb);
255         }
256
257         return exit_status();
258 }