Commit 5e161669 authored by Rusty Russell's avatar Rusty Russell

failtest: fcntl handling

Catch fcntl, particularly fcntl locks.
parent 37e247db
......@@ -63,6 +63,7 @@ int main(int argc, char *argv[])
if (strcmp(argv[1], "depends") == 0) {
printf("ccan/compiler\n");
printf("ccan/read_write_all\n");
printf("ccan/build_assert\n");
return 0;
}
......
......@@ -10,10 +10,11 @@
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <assert.h>
#include <ccan/read_write_all/read_write_all.h>
#include <ccan/failtest/failtest_proto.h>
#include <ccan/failtest/failtest.h>
#include <ccan/build_assert/build_assert.h>
bool (*failtest_hook)(struct failtest_call *history, unsigned num)
= failtest_default_hook;
......@@ -24,6 +25,7 @@ const char *failpath;
enum info_type {
WRITE,
RELEASE_LOCKS,
FAILURE,
SUCCESS,
UNEXPECTED
......@@ -49,6 +51,13 @@ struct write_info {
char *olddata;
};
struct lock_info {
int fd;
/* end is inclusive: you can't have a 0-byte lock. */
off_t start, end;
int type;
};
bool (*failtest_exit_check)(struct failtest_call *history, unsigned num);
static struct failtest_call *history = NULL;
......@@ -64,7 +73,11 @@ static unsigned int child_writes_num = 0;
static struct fd_orig *fd_orig = NULL;
static unsigned int fd_orig_num = 0;
static const char info_to_arg[] = "mceoprw";
static pid_t lock_owner;
static struct lock_info *locks = NULL;
static unsigned int lock_num = 0;
static const char info_to_arg[] = "mceoprwf";
/* Dummy call used for failtest_undo wrappers. */
static struct failtest_call unrecorded_call;
......@@ -171,6 +184,72 @@ static void hand_down(int signal)
kill(child, signal);
}
static void release_locks(void)
{
/* Locks were never acquired/reacquired? */
if (lock_owner == 0)
return;
/* We own them? Release them all. */
if (lock_owner == getpid()) {
unsigned int i;
struct flock fl;
fl.l_type = F_UNLCK;
fl.l_whence = SEEK_SET;
fl.l_start = 0;
fl.l_len = 0;
for (i = 0; i < lock_num; i++)
fcntl(locks[i].fd, F_SETLK, &fl);
} else {
/* Our parent must have them; pass request up. */
enum info_type type = RELEASE_LOCKS;
assert(control_fd != -1);
write_all(control_fd, &type, sizeof(type));
}
lock_owner = 0;
}
/* off_t is a signed type. Getting its max is non-trivial. */
static off_t off_max(void)
{
BUILD_ASSERT(sizeof(off_t) == 4 || sizeof(off_t) == 8);
if (sizeof(off_t) == 4)
return (off_t)0x7FFFFFF;
else
return (off_t)0x7FFFFFFFFFFFFFFULL;
}
static void get_locks(void)
{
unsigned int i;
struct flock fl;
if (lock_owner == getpid())
return;
if (lock_owner != 0) {
enum info_type type = RELEASE_LOCKS;
assert(control_fd != -1);
write_all(control_fd, &type, sizeof(type));
}
fl.l_whence = SEEK_SET;
for (i = 0; i < lock_num; i++) {
fl.l_type = locks[i].type;
fl.l_start = locks[i].start;
if (locks[i].end == off_max())
fl.l_len = 0;
else
fl.l_len = locks[i].end - locks[i].start + 1;
if (fcntl(locks[i].fd, F_SETLKW, &fl) != 0)
abort();
}
lock_owner = getpid();
}
static bool should_fail(struct failtest_call *call)
{
int status;
......@@ -251,6 +330,9 @@ static bool should_fail(struct failtest_call *call)
if (type == WRITE) {
if (!read_write_info(control[0]))
break;
} else if (type == RELEASE_LOCKS) {
release_locks();
/* FIXME: Tell them we're done... */
}
}
} else if (pfd[0].revents & POLLHUP) {
......@@ -494,6 +576,69 @@ ssize_t failtest_write(int fd, const void *buf, size_t count,
return p->u.write.ret;
}
static struct lock_info *WARN_UNUSED_RESULT
add_lock(struct lock_info *locks, int fd, off_t start, off_t end, int type)
{
unsigned int i;
struct lock_info *l;
for (i = 0; i < lock_num; i++) {
l = &locks[i];
if (l->fd != fd)
continue;
/* Four cases we care about:
* Start overlap:
* l = | |
* new = | |
* Mid overlap:
* l = | |
* new = | |
* End overlap:
* l = | |
* new = | |
* Total overlap:
* l = | |
* new = | |
*/
if (start > l->start && end < l->end) {
/* Mid overlap: trim entry, add new one. */
off_t new_start, new_end;
new_start = end + 1;
new_end = l->end;
l->end = start - 1;
locks = add_lock(locks,
fd, new_start, new_end, l->type);
l = &locks[i];
} else if (start <= l->start && end >= l->end) {
/* Total overlap: eliminate entry. */
l->end = 0;
l->start = 1;
} else if (end >= l->start && end < l->end) {
/* Start overlap: trim entry. */
l->start = end + 1;
} else if (start > l->start && start <= l->end) {
/* End overlap: trim entry. */
l->end = start-1;
}
/* Nothing left? Remove it. */
if (l->end < l->start) {
memmove(l, l + 1, (--lock_num - i) * sizeof(l[0]));
i--;
}
}
if (type != F_UNLCK) {
locks = realloc(locks, (lock_num + 1) * sizeof(*locks));
l = &locks[lock_num++];
l->fd = fd;
l->start = start;
l->end = end;
l->type = type;
}
return locks;
}
/* We only trap this so we can dup fds in case we need to restore. */
int failtest_close(int fd)
{
......@@ -511,9 +656,86 @@ int failtest_close(int fd)
if (writes[i].hdr.fd == fd)
writes[i].hdr.fd = newfd;
}
locks = add_lock(locks, fd, 0, off_max(), F_UNLCK);
return close(fd);
}
/* Zero length means "to end of file" */
static off_t end_of(off_t start, off_t len)
{
if (len == 0)
return off_max();
return start + len - 1;
}
/* FIXME: This only handles locks, really. */
int failtest_fcntl(int fd, const char *file, unsigned line, int cmd, ...)
{
struct failtest_call *p;
struct fcntl_call call;
va_list ap;
call.fd = fd;
call.cmd = cmd;
/* Argument extraction. */
switch (cmd) {
case F_SETFL:
case F_SETFD:
va_start(ap, cmd);
call.arg.l = va_arg(ap, long);
va_end(ap);
return fcntl(fd, cmd, call.arg.l);
case F_GETFD:
case F_GETFL:
return fcntl(fd, cmd);
case F_GETLK:
get_locks();
va_start(ap, cmd);
call.arg.fl = *va_arg(ap, struct flock *);
va_end(ap);
return fcntl(fd, cmd, &call.arg.fl);
case F_SETLK:
case F_SETLKW:
va_start(ap, cmd);
call.arg.fl = *va_arg(ap, struct flock *);
va_end(ap);
break;
default:
/* This means you need to implement it here. */
err(1, "failtest: unknown fcntl %u", cmd);
}
p = add_history(FAILTEST_FCNTL, file, line, &call);
get_locks();
if (should_fail(p)) {
p->u.fcntl.ret = -1;
if (p->u.fcntl.cmd == F_SETLK)
p->error = EAGAIN;
else
p->error = EDEADLK;
} else {
p->u.fcntl.ret = fcntl(p->u.fcntl.fd, p->u.fcntl.cmd,
&p->u.fcntl.arg.fl);
if (p->u.fcntl.ret == -1)
p->error = errno;
else {
/* We don't handle anything else yet. */
assert(p->u.fcntl.arg.fl.l_whence == SEEK_SET);
locks = add_lock(locks,
p->u.fcntl.fd,
p->u.fcntl.arg.fl.l_start,
end_of(p->u.fcntl.arg.fl.l_start,
p->u.fcntl.arg.fl.l_len),
p->u.fcntl.arg.fl.l_type);
}
}
errno = p->error;
return p->u.fcntl.ret;
}
void failtest_init(int argc, char *argv[])
{
if (argc == 2
......
......@@ -2,6 +2,7 @@
#define CCAN_FAILTEST_H
#include <sys/types.h>
#include <stdbool.h>
#include <fcntl.h>
#include <ccan/compiler/compiler.h>
/**
......@@ -38,6 +39,7 @@ enum failtest_call_type {
FAILTEST_PIPE,
FAILTEST_READ,
FAILTEST_WRITE,
FAILTEST_FCNTL,
};
struct calloc_call {
......@@ -83,6 +85,17 @@ struct write_call {
size_t count;
};
struct fcntl_call {
int ret;
int fd;
int cmd;
union {
struct flock fl;
long l;
int i;
} arg;
};
/**
* struct failtest_call - description of a call redirected to failtest module
* @type: the call type
......@@ -115,6 +128,7 @@ struct failtest_call {
struct pipe_call pipe;
struct read_call read;
struct write_call write;
struct fcntl_call fcntl;
} u;
};
......
......@@ -42,6 +42,9 @@
#undef close
#define close(fd) failtest_close(fd)
#undef fcntl
#define fcntl(fd, ...) failtest_fcntl((fd), __FILE__, __LINE__, __VA_ARGS__)
#include <ccan/failtest/failtest_proto.h>
#endif /* CCAN_FAILTEST_OVERRIDE_H */
......@@ -16,5 +16,5 @@ ssize_t failtest_read(int fd, void *buf, size_t count,
ssize_t failtest_write(int fd, const void *buf, size_t count,
const char *file, unsigned line);
int failtest_close(int fd);
int failtest_fcntl(int fd, const char *file, unsigned line, int cmd, ...);
#endif /* CCAN_FAILTEST_PROTO_H */
......@@ -33,4 +33,8 @@
#undef close
#define close(fd) failtest_close(fd)
#undef fcntl
#define fcntl(fd, ...) \
failtest_fcntl((fd), NULL, 0, __VA_ARGS__)
#endif /* CCAN_FAILTEST_RESTORE_H */
#include <stdlib.h>
#include <setjmp.h>
#include <stdio.h>
#include <stdarg.h>
#include <assert.h>
#include <ccan/tap/tap.h>
/* Include the C files directly. */
#include <ccan/failtest/failtest.c>
#define SIZE 8
/* We don't want to fork and fail; we're just testing lock recording. */
static bool dont_fail(struct failtest_call *history, unsigned num)
{
return false;
}
static bool place_lock(int fd, char lockarr[], unsigned pos, unsigned size,
int type)
{
struct flock fl;
/* Update record keeping. */
if (type == F_RDLCK)
memset(lockarr+pos, 1, size);
else if (type == F_WRLCK)
memset(lockarr+pos, 2, size);
else
memset(lockarr+pos, 0, size);
fl.l_whence = SEEK_SET;
fl.l_type = type;
fl.l_start = pos;
fl.l_len = size;
return failtest_fcntl(fd, "run-locking.c", 1, F_SETLK, &fl) == 0;
}
static char lock_lookup(int fd, unsigned pos)
{
char ret = 0;
unsigned int i;
struct lock_info *l;
for (i = 0; i < lock_num; i++) {
l = &locks[i];
if (l->fd != fd)
continue;
if (pos >= l->start && pos <= l->end) {
if (ret)
ret = 3;
else if (l->type == F_RDLCK)
ret = 1;
else
ret = 2;
}
}
return ret;
}
static bool test(int fd,
unsigned p1, unsigned s1,
unsigned p2, unsigned s2,
unsigned p3, unsigned s3)
{
unsigned int i;
char lockarr[SIZE];
memset(lockarr, 0, sizeof(lockarr));
if (!place_lock(fd, lockarr, p1, s1, F_WRLCK))
return false;
if (!place_lock(fd, lockarr, p2, s2, F_RDLCK))
return false;
if (!place_lock(fd, lockarr, p3, s3, F_UNLCK))
return false;
for (i = 0; i < SIZE; i++) {
if (lock_lookup(fd, i) != lockarr[i])
return false;
}
/* Reset lock info. */
lock_num = 0;
return true;
}
int main(void)
{
int fd;
long flags;
unsigned int isize;
plan_tests(5835);
failtest_hook = dont_fail;
fd = open("run-locking-scratch", O_RDWR|O_CREAT, 0600);
/* GETFL and SETFL wrappers should pass through. */
flags = fcntl(fd, F_GETFL);
ok1(failtest_fcntl(fd, "run-locking.c", 1, F_GETFL) == flags);
flags |= O_NONBLOCK;
ok1(failtest_fcntl(fd, "run-locking.c", 1, F_SETFL, flags) == 0);
ok1(failtest_fcntl(fd, "run-locking.c", 1, F_GETFL) == flags);
for (isize = 1; isize < 4; isize++) {
unsigned int ipos;
for (ipos = 0; ipos + isize < SIZE; ipos++) {
unsigned int jsize;
for (jsize = 1; jsize < 4; jsize++) {
unsigned int jpos;
for (jpos = 0; jpos + jsize < SIZE; jpos++) {
unsigned int ksize;
for (ksize = 1; ksize < 4; ksize++) {
unsigned int kpos;
for (kpos = 0;
kpos + ksize < SIZE;
kpos++) {
ok1(test(fd,
ipos, isize,
jpos, jsize,
kpos, ksize));
}
}
}
}
}
}
return exit_status();
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment