Line data Source code
1 : #include "../common/tdb_private.h"
2 : #include "../common/io.c"
3 : #include "../common/tdb.c"
4 : #include "../common/lock.c"
5 : #include "../common/freelist.c"
6 : #include "../common/traverse.c"
7 : #include "../common/transaction.c"
8 : #include "../common/error.c"
9 : #include "../common/open.c"
10 : #include "../common/check.c"
11 : #include "../common/hash.c"
12 : #include "../common/mutex.c"
13 : #include "tap-interface.h"
14 : #include <stdlib.h>
15 : #include <sys/types.h>
16 : #include <sys/wait.h>
17 : #include <stdarg.h>
18 : #include "logging.h"
19 :
20 : static TDB_DATA key, data;
21 :
22 2 : static void do_chainlock(const char *name, int tdb_flags, int up, int down)
23 : {
24 : struct tdb_context *tdb;
25 : int ret;
26 : ssize_t nread, nwritten;
27 2 : char c = 0;
28 :
29 2 : tdb = tdb_open_ex(name, 3, tdb_flags,
30 : O_RDWR|O_CREAT, 0755, &taplogctx, NULL);
31 2 : ok(tdb, "tdb_open_ex should succeed");
32 :
33 2 : ret = tdb_chainlock(tdb, key);
34 2 : ok(ret == 0, "tdb_chainlock should succeed");
35 :
36 2 : nwritten = write(up, &c, sizeof(c));
37 2 : ok(nwritten == sizeof(c), "write should succeed");
38 :
39 2 : nread = read(down, &c, sizeof(c));
40 2 : ok(nread == sizeof(c), "read should succeed");
41 :
42 2 : exit(0);
43 : }
44 :
45 2 : static void do_allrecord_lock(const char *name, int tdb_flags, int up, int down)
46 : {
47 : struct tdb_context *tdb;
48 : int ret;
49 : ssize_t nread, nwritten;
50 2 : char c = 0;
51 :
52 2 : tdb = tdb_open_ex(name, 3, tdb_flags,
53 : O_RDWR|O_CREAT, 0755, &taplogctx, NULL);
54 2 : ok(tdb, "tdb_open_ex should succeed");
55 :
56 2 : ret = tdb_allrecord_lock(tdb, F_WRLCK, TDB_LOCK_WAIT, false);
57 2 : ok(ret == 0, "tdb_allrecord_lock should succeed");
58 :
59 2 : nwritten = write(up, &c, sizeof(c));
60 2 : ok(nwritten == sizeof(c), "write should succeed");
61 :
62 2 : nread = read(down, &c, sizeof(c));
63 2 : ok(nread == sizeof(c), "read should succeed");
64 :
65 2 : exit(0);
66 : }
67 :
68 : /* The code should barf on TDBs created with rwlocks. */
69 2 : static int do_tests(const char *name, int tdb_flags)
70 : {
71 : struct tdb_context *tdb;
72 : int ret;
73 : pid_t chainlock_child, allrecord_child;
74 : int chainlock_down[2];
75 : int chainlock_up[2];
76 : int allrecord_down[2];
77 : int allrecord_up[2];
78 : char c;
79 : ssize_t nread, nwritten;
80 :
81 2 : key.dsize = strlen("hi");
82 2 : key.dptr = discard_const_p(uint8_t, "hi");
83 2 : data.dsize = strlen("world");
84 2 : data.dptr = discard_const_p(uint8_t, "world");
85 :
86 2 : ret = pipe(chainlock_down);
87 2 : ok(ret == 0, "pipe should succeed");
88 :
89 2 : ret = pipe(chainlock_up);
90 2 : ok(ret == 0, "pipe should succeed");
91 :
92 2 : ret = pipe(allrecord_down);
93 2 : ok(ret == 0, "pipe should succeed");
94 :
95 2 : ret = pipe(allrecord_up);
96 2 : ok(ret == 0, "pipe should succeed");
97 :
98 2 : chainlock_child = fork();
99 4 : ok(chainlock_child != -1, "fork should succeed");
100 :
101 4 : if (chainlock_child == 0) {
102 2 : close(chainlock_up[0]);
103 2 : close(chainlock_down[1]);
104 2 : close(allrecord_up[0]);
105 2 : close(allrecord_up[1]);
106 2 : close(allrecord_down[0]);
107 2 : close(allrecord_down[1]);
108 2 : do_chainlock(name, tdb_flags,
109 : chainlock_up[1], chainlock_down[0]);
110 0 : exit(0);
111 : }
112 2 : close(chainlock_up[1]);
113 2 : close(chainlock_down[0]);
114 :
115 2 : nread = read(chainlock_up[0], &c, sizeof(c));
116 2 : ok(nread == sizeof(c), "read should succeed");
117 :
118 : /*
119 : * Now we have a process holding a chainlock. Start another process
120 : * trying the allrecord lock. This will block.
121 : */
122 :
123 2 : allrecord_child = fork();
124 4 : ok(allrecord_child != -1, "fork should succeed");
125 :
126 4 : if (allrecord_child == 0) {
127 2 : close(chainlock_up[0]);
128 2 : close(chainlock_up[1]);
129 2 : close(chainlock_down[0]);
130 2 : close(chainlock_down[1]);
131 2 : close(allrecord_up[0]);
132 2 : close(allrecord_down[1]);
133 2 : do_allrecord_lock(name, tdb_flags,
134 : allrecord_up[1], allrecord_down[0]);
135 0 : exit(0);
136 : }
137 2 : close(allrecord_up[1]);
138 2 : close(allrecord_down[0]);
139 :
140 2 : poll(NULL, 0, 500);
141 :
142 2 : tdb = tdb_open_ex(name, 3, tdb_flags,
143 : O_RDWR|O_CREAT, 0755, &taplogctx, NULL);
144 2 : ok(tdb, "tdb_open_ex should succeed");
145 :
146 : /*
147 : * Someone already holds a chainlock, but we're able to get the
148 : * freelist lock.
149 : *
150 : * The freelist lock/mutex is independent from the allrecord lock/mutex.
151 : */
152 :
153 2 : ret = tdb_chainlock_nonblock(tdb, key);
154 2 : ok(ret == -1, "tdb_chainlock_nonblock should not succeed");
155 :
156 2 : ret = tdb_lock_nonblock(tdb, -1, F_WRLCK);
157 2 : ok(ret == 0, "tdb_lock_nonblock should succeed");
158 :
159 2 : ret = tdb_unlock(tdb, -1, F_WRLCK);
160 2 : ok(ret == 0, "tdb_unlock should succeed");
161 :
162 : /*
163 : * We have someone else having done the lock for us. Just mark it.
164 : */
165 :
166 2 : ret = tdb_chainlock_mark(tdb, key);
167 2 : ok(ret == 0, "tdb_chainlock_mark should succeed");
168 :
169 : /*
170 : * The tdb_store below will block the freelist. In one version of the
171 : * mutex patches, the freelist was already blocked here by the
172 : * allrecord child, which was waiting for the chainlock child to give
173 : * up its chainlock. Make sure that we don't run into this
174 : * deadlock. To exercise the deadlock, just comment out the "ok"
175 : * line.
176 : *
177 : * The freelist lock/mutex is independent from the allrecord lock/mutex.
178 : */
179 :
180 2 : ret = tdb_lock_nonblock(tdb, -1, F_WRLCK);
181 2 : ok(ret == 0, "tdb_lock_nonblock should succeed");
182 :
183 2 : ret = tdb_unlock(tdb, -1, F_WRLCK);
184 2 : ok(ret == 0, "tdb_unlock should succeed");
185 :
186 2 : ret = tdb_store(tdb, key, data, TDB_INSERT);
187 2 : ok(ret == 0, "tdb_store should succeed");
188 :
189 2 : ret = tdb_chainlock_unmark(tdb, key);
190 2 : ok(ret == 0, "tdb_chainlock_unmark should succeed");
191 :
192 2 : nwritten = write(chainlock_down[1], &c, sizeof(c));
193 2 : ok(nwritten == sizeof(c), "write should succeed");
194 :
195 2 : nread = read(chainlock_up[0], &c, sizeof(c));
196 2 : ok(nread == 0, "read should succeed");
197 :
198 2 : nread = read(allrecord_up[0], &c, sizeof(c));
199 2 : ok(nread == sizeof(c), "read should succeed");
200 :
201 : /*
202 : * Someone already holds the allrecord lock, but we're able to get the
203 : * freelist lock.
204 : *
205 : * The freelist lock/mutex is independent from the allrecord lock/mutex.
206 : */
207 :
208 2 : ret = tdb_chainlock_nonblock(tdb, key);
209 2 : ok(ret == -1, "tdb_chainlock_nonblock should not succeed");
210 :
211 2 : ret = tdb_lockall_nonblock(tdb);
212 2 : ok(ret == -1, "tdb_lockall_nonblock should not succeed");
213 :
214 2 : ret = tdb_lock_nonblock(tdb, -1, F_WRLCK);
215 2 : ok(ret == 0, "tdb_lock_nonblock should succeed");
216 :
217 2 : ret = tdb_unlock(tdb, -1, F_WRLCK);
218 2 : ok(ret == 0, "tdb_unlock should succeed");
219 :
220 : /*
221 : * We have someone else having done the lock for us. Just mark it.
222 : */
223 :
224 2 : ret = tdb_lockall_mark(tdb);
225 2 : ok(ret == 0, "tdb_lockall_mark should succeed");
226 :
227 2 : ret = tdb_lock_nonblock(tdb, -1, F_WRLCK);
228 2 : ok(ret == 0, "tdb_lock_nonblock should succeed");
229 :
230 2 : ret = tdb_unlock(tdb, -1, F_WRLCK);
231 2 : ok(ret == 0, "tdb_unlock should succeed");
232 :
233 2 : ret = tdb_store(tdb, key, data, TDB_REPLACE);
234 2 : ok(ret == 0, "tdb_store should succeed");
235 :
236 2 : ret = tdb_lockall_unmark(tdb);
237 2 : ok(ret == 0, "tdb_lockall_unmark should succeed");
238 :
239 2 : nwritten = write(allrecord_down[1], &c, sizeof(c));
240 2 : ok(nwritten == sizeof(c), "write should succeed");
241 :
242 2 : nread = read(allrecord_up[0], &c, sizeof(c));
243 2 : ok(nread == 0, "read should succeed");
244 :
245 2 : close(chainlock_up[0]);
246 2 : close(chainlock_down[1]);
247 2 : close(allrecord_up[0]);
248 2 : close(allrecord_down[1]);
249 2 : diag("%s tests done", name);
250 2 : return exit_status();
251 : }
252 :
253 1 : int main(int argc, char *argv[])
254 : {
255 : int ret;
256 : bool mutex_support;
257 :
258 1 : mutex_support = tdb_runtime_check_for_robust_mutexes();
259 :
260 1 : ret = do_tests("marklock-deadlock-fcntl.tdb",
261 : TDB_CLEAR_IF_FIRST |
262 : TDB_INCOMPATIBLE_HASH);
263 1 : ok(ret == 0, "marklock-deadlock-fcntl.tdb tests should succeed");
264 :
265 1 : if (!mutex_support) {
266 0 : skip(1, "No robust mutex support, "
267 : "skipping marklock-deadlock-mutex.tdb tests");
268 0 : return exit_status();
269 : }
270 :
271 1 : ret = do_tests("marklock-deadlock-mutex.tdb",
272 : TDB_CLEAR_IF_FIRST |
273 : TDB_MUTEX_LOCKING |
274 : TDB_INCOMPATIBLE_HASH);
275 1 : ok(ret == 0, "marklock-deadlock-mutex.tdb tests should succeed");
276 :
277 1 : return exit_status();
278 : }
|