1
0
mirror of https://github.com/samba-team/samba.git synced 2025-01-07 17:18:11 +03:00
samba-mirror/lib/tdb/test/lock-tracking.c
Volker Lendecke 68559b787e tdb: Enhance lock tracking a bit
lock-tracking.c mirrors the in-kernel fcntl structures to detect
unexpected use of fcntl calls. During my mutex work I changed our fcntl
use so that we unlock the allrecord_lock in two pieces: The range covering
the hash locks and the range covering the data area for the individual
traverse record locks. Splitting locks is not covered by lock-tracking.c.

This patch extends lock-tracking.c with this little piece. It's still
far from complete to track the full range of fcntl semantics. It is not
strictly needed right now, but it does not hurt either.

Signed-off-by: Volker Lendecke <vl@samba.org>
Reviewed-by: Stefan Metzmacher <metze@samba.org>
2013-02-19 15:46:45 +01:00

158 lines
3.4 KiB
C

/* We save the locks so we can reaquire them. */
#include "../common/tdb_private.h"
#include <unistd.h>
#include <fcntl.h>
#include <stdarg.h>
#include <stdlib.h>
#include "tap-interface.h"
#include "lock-tracking.h"
struct testlock {
struct testlock *next;
unsigned int off;
unsigned int len;
int type;
};
static struct testlock *testlocks;
int locking_errors = 0;
bool suppress_lockcheck = false;
bool nonblocking_locks;
int locking_would_block = 0;
void (*unlock_callback)(int fd);
int fcntl_with_lockcheck(int fd, int cmd, ... /* arg */ )
{
va_list ap;
int ret, arg3;
struct flock *fl;
bool may_block = false;
if (cmd != F_SETLK && cmd != F_SETLKW) {
/* This may be totally bogus, but we don't know in general. */
va_start(ap, cmd);
arg3 = va_arg(ap, int);
va_end(ap);
return fcntl(fd, cmd, arg3);
}
va_start(ap, cmd);
fl = va_arg(ap, struct flock *);
va_end(ap);
if (cmd == F_SETLKW && nonblocking_locks) {
cmd = F_SETLK;
may_block = true;
}
ret = fcntl(fd, cmd, fl);
/* Detect when we failed, but might have been OK if we waited. */
if (may_block && ret == -1 && (errno == EAGAIN || errno == EACCES)) {
locking_would_block++;
}
if (fl->l_type == F_UNLCK) {
struct testlock **l;
struct testlock *old = NULL;
for (l = &testlocks; *l; l = &(*l)->next) {
if ((*l)->off == fl->l_start
&& (*l)->len == fl->l_len) {
if (ret == 0) {
old = *l;
*l = (*l)->next;
free(old);
}
break;
}
if (((*l)->off == fl->l_start)
&& ((*l)->len == 0)
&& (ret == 0)) {
/*
* Remove a piece from the start of the
* allrecord_lock
*/
old = *l;
(*l)->off += fl->l_len;
break;
}
}
if (!old && !suppress_lockcheck) {
diag("Unknown unlock %u@%u - %i",
(int)fl->l_len, (int)fl->l_start, ret);
locking_errors++;
}
} else {
struct testlock *new, *i;
unsigned int fl_end = fl->l_start + fl->l_len;
if (fl->l_len == 0)
fl_end = (unsigned int)-1;
/* Check for overlaps: we shouldn't do this. */
for (i = testlocks; i; i = i->next) {
unsigned int i_end = i->off + i->len;
if (i->len == 0)
i_end = (unsigned int)-1;
if (fl->l_start >= i->off && fl->l_start < i_end)
break;
if (fl_end >= i->off && fl_end < i_end)
break;
/* tdb_allrecord_lock does this, handle adjacent: */
if (fl->l_start == i_end && fl->l_type == i->type) {
if (ret == 0) {
i->len = fl->l_len
? i->len + fl->l_len
: 0;
}
goto done;
}
}
if (i) {
/* Special case: upgrade of allrecord lock. */
if (i->type == F_RDLCK && fl->l_type == F_WRLCK
&& i->off == FREELIST_TOP
&& fl->l_start == FREELIST_TOP
&& i->len == 0
&& fl->l_len == 0) {
if (ret == 0)
i->type = F_WRLCK;
goto done;
}
if (!suppress_lockcheck) {
diag("%s testlock %u@%u overlaps %u@%u",
fl->l_type == F_WRLCK ? "write" : "read",
(int)fl->l_len, (int)fl->l_start,
i->len, (int)i->off);
locking_errors++;
}
}
if (ret == 0) {
new = malloc(sizeof *new);
new->off = fl->l_start;
new->len = fl->l_len;
new->type = fl->l_type;
new->next = testlocks;
testlocks = new;
}
}
done:
if (ret == 0 && fl->l_type == F_UNLCK && unlock_callback)
unlock_callback(fd);
return ret;
}
unsigned int forget_locking(void)
{
unsigned int num = 0;
while (testlocks) {
struct testlock *next = testlocks->next;
free(testlocks);
testlocks = next;
num++;
}
return num;
}