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