From 5e1616698e06e2ce661122c6c9b04aae50271be8 Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Tue, 15 Feb 2011 23:03:06 +1030 Subject: [PATCH] failtest: fcntl handling Catch fcntl, particularly fcntl locks. --- ccan/failtest/_info | 1 + ccan/failtest/failtest.c | 226 +++++++++++++++++++++++++++++- ccan/failtest/failtest.h | 14 ++ ccan/failtest/failtest_override.h | 3 + ccan/failtest/failtest_proto.h | 2 +- ccan/failtest/failtest_undo.h | 4 + ccan/failtest/test/run-locking.c | 133 ++++++++++++++++++ 7 files changed, 380 insertions(+), 3 deletions(-) create mode 100644 ccan/failtest/test/run-locking.c diff --git a/ccan/failtest/_info b/ccan/failtest/_info index 4b43246f..830fce49 100644 --- a/ccan/failtest/_info +++ b/ccan/failtest/_info @@ -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; } diff --git a/ccan/failtest/failtest.c b/ccan/failtest/failtest.c index 5d7a54f1..2b78b8e3 100644 --- a/ccan/failtest/failtest.c +++ b/ccan/failtest/failtest.c @@ -10,10 +10,11 @@ #include #include #include -#include +#include #include #include #include +#include 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 diff --git a/ccan/failtest/failtest.h b/ccan/failtest/failtest.h index 8aae82b3..5c57efcf 100644 --- a/ccan/failtest/failtest.h +++ b/ccan/failtest/failtest.h @@ -2,6 +2,7 @@ #define CCAN_FAILTEST_H #include #include +#include #include /** @@ -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; }; diff --git a/ccan/failtest/failtest_override.h b/ccan/failtest/failtest_override.h index dd328b50..90d1bfdc 100644 --- a/ccan/failtest/failtest_override.h +++ b/ccan/failtest/failtest_override.h @@ -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 #endif /* CCAN_FAILTEST_OVERRIDE_H */ diff --git a/ccan/failtest/failtest_proto.h b/ccan/failtest/failtest_proto.h index cdb43eda..66f28c9b 100644 --- a/ccan/failtest/failtest_proto.h +++ b/ccan/failtest/failtest_proto.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 */ diff --git a/ccan/failtest/failtest_undo.h b/ccan/failtest/failtest_undo.h index 17bf3566..7f29acd5 100644 --- a/ccan/failtest/failtest_undo.h +++ b/ccan/failtest/failtest_undo.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 */ diff --git a/ccan/failtest/test/run-locking.c b/ccan/failtest/test/run-locking.c new file mode 100644 index 00000000..b25cfab9 --- /dev/null +++ b/ccan/failtest/test/run-locking.c @@ -0,0 +1,133 @@ +#include +#include +#include +#include +#include +#include +/* Include the C files directly. */ +#include + +#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(); +} -- 2.39.2