X-Git-Url: http://git.ozlabs.org/?p=ccan;a=blobdiff_plain;f=ccan%2Ffailtest%2Ffailtest.c;h=598a9e7930e933873ffbd9d02a3578d12d752472;hp=1127e4fdd7c385973e4d91b3050507c4086968fa;hb=f75f0ca743b7a41fe3b31dbde7d2595ba2e75bbe;hpb=97f93f50918abc3578bf868f0e1207eeb6ab1c83 diff --git a/ccan/failtest/failtest.c b/ccan/failtest/failtest.c index 1127e4fd..598a9e79 100644 --- a/ccan/failtest/failtest.c +++ b/ccan/failtest/failtest.c @@ -10,20 +10,25 @@ #include #include #include -#include +#include +#include #include #include #include +#include bool (*failtest_hook)(struct failtest_call *history, unsigned num) = failtest_default_hook; +static int tracefd = -1; + unsigned int failtest_timeout_ms = 20000; const char *failpath; enum info_type { WRITE, + RELEASE_LOCKS, FAILURE, SUCCESS, UNEXPECTED @@ -49,11 +54,19 @@ 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; static unsigned int history_num = 0; static int control_fd = -1; +static struct timeval start; static struct write_info *writes = NULL; static unsigned int writes_num = 0; @@ -64,7 +77,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; @@ -130,18 +147,18 @@ static bool read_write_info(int fd) return true; } -static void print_reproduce(void) +static char *failpath_string(void) { unsigned int i; + char *ret = malloc(history_num + 1); - printf("To reproduce: --failpath="); for (i = 0; i < history_num; i++) { + ret[i] = info_to_arg[history[i].type]; if (history[i].fail) - printf("%c", toupper(info_to_arg[history[i].type])); - else - printf("%c", info_to_arg[history[i].type]); + ret[i] = toupper(ret[i]); } - printf("\n"); + ret[i] = '\0'; + return ret; } static void tell_parent(enum info_type type) @@ -153,13 +170,15 @@ static void tell_parent(enum info_type type) static void child_fail(const char *out, size_t outlen, const char *fmt, ...) { va_list ap; + char *path = failpath_string(); va_start(ap, fmt); vfprintf(stderr, fmt, ap); va_end(ap); fprintf(stderr, "%.*s", (int)outlen, out); - print_reproduce(); + printf("To reproduce: --failpath=%s\n", path); + free(path); tell_parent(FAILURE); exit(1); } @@ -171,6 +190,84 @@ 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 void trace_str(const char *str) +{ + ssize_t ret; + + while ((ret = write(tracefd, str, strlen(str))) <= 0) { + str += ret; + if (!*str) + return; + } + err(1, "Writing trace."); +} + static bool should_fail(struct failtest_call *call) { int status; @@ -186,7 +283,8 @@ static bool should_fail(struct failtest_call *call) if (tolower(*failpath) != info_to_arg[call->type]) errx(1, "Failpath expected '%c' got '%c'\n", info_to_arg[call->type], *failpath); - return isupper(*(failpath++)); + call->fail = isupper(*(failpath++)); + return call->fail; } if (!failtest_hook(history, history_num)) { @@ -206,6 +304,31 @@ static bool should_fail(struct failtest_call *call) err(1, "forking failed"); if (child == 0) { + if (tracefd != -1) { + struct timeval now; + char str[50], *p; + gettimeofday(&now, NULL); + if (now.tv_usec < start.tv_usec) { + now.tv_sec--; + now.tv_usec += 1000000; + } + now.tv_usec -= start.tv_usec; + now.tv_sec -= start.tv_sec; + sprintf(str, "%u (%u.%02u): ", getpid(), + (int)now.tv_sec, (int)now.tv_usec / 10000); + trace_str(str); + p = failpath_string(); + trace_str(p); + free(p); + trace_str("("); + p = strchr(history[history_num-1].file, '/'); + if (p) + trace_str(p+1); + else + trace_str(history[history_num-1].file); + sprintf(str, ":%u)\n", history[history_num-1].line); + trace_str(str); + } close(control[0]); close(output[0]); dup2(output[1], STDOUT_FILENO); @@ -251,6 +374,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) { @@ -336,17 +462,17 @@ void *failtest_realloc(void *ptr, size_t size, const char *file, unsigned line) return p->u.realloc.ret; } -int failtest_open(const char *pathname, int flags, +int failtest_open(const char *pathname, const char *file, unsigned line, ...) { struct failtest_call *p; struct open_call call; + va_list ap; call.pathname = strdup(pathname); - call.flags = flags; - if (flags & O_CREAT) { - va_list ap; - va_start(ap, line); + va_start(ap, line); + call.flags = va_arg(ap, int); + if (call.flags & O_CREAT) { call.mode = va_arg(ap, mode_t); va_end(ap); } @@ -359,7 +485,7 @@ int failtest_open(const char *pathname, int flags, /* FIXME: Play with error codes? */ p->error = EACCES; } else { - p->u.open.ret = open(pathname, flags, call.mode); + p->u.open.ret = open(pathname, call.flags, call.mode); } errno = p->error; return p->u.open.ret; @@ -384,14 +510,15 @@ int failtest_pipe(int pipefd[2], const char *file, unsigned line) return p->u.pipe.ret; } -ssize_t failtest_read(int fd, void *buf, size_t count, - const char *file, unsigned line) +ssize_t failtest_pread(int fd, void *buf, size_t count, off_t off, + const char *file, unsigned line) { struct failtest_call *p; struct read_call call; call.fd = fd; call.buf = buf; call.count = count; + call.off = off; p = add_history(FAILTEST_READ, file, line, &call); /* This is going to change seek offset, so save it. */ @@ -403,7 +530,7 @@ ssize_t failtest_read(int fd, void *buf, size_t count, p->u.read.ret = -1; p->error = EIO; } else { - p->u.read.ret = read(fd, buf, count); + p->u.read.ret = pread(fd, buf, count, off); } errno = p->error; return p->u.read.ret; @@ -415,8 +542,8 @@ static struct write_info *new_write(void) return &writes[writes_num++]; } -ssize_t failtest_write(int fd, const void *buf, size_t count, - const char *file, unsigned line) +ssize_t failtest_pwrite(int fd, const void *buf, size_t count, off_t off, + const char *file, unsigned line) { struct failtest_call *p; struct write_call call; @@ -425,6 +552,7 @@ ssize_t failtest_write(int fd, const void *buf, size_t count, call.fd = fd; call.buf = buf; call.count = count; + call.off = off; p = add_history(FAILTEST_WRITE, file, line, &call); offset = lseek(fd, 0, SEEK_CUR); @@ -488,12 +616,89 @@ ssize_t failtest_write(int fd, const void *buf, size_t count, return p->u.write.ret; } } - p->u.write.ret = write(fd, buf, count); + p->u.write.ret = pwrite(fd, buf, count, off); } errno = p->error; return p->u.write.ret; } +ssize_t failtest_read(int fd, void *buf, size_t count, + const char *file, unsigned line) +{ + return failtest_pread(fd, buf, count, lseek(fd, 0, SEEK_CUR), + file, line); +} + +ssize_t failtest_write(int fd, const void *buf, size_t count, + const char *file, unsigned line) +{ + return failtest_pwrite(fd, buf, count, lseek(fd, 0, SEEK_CUR), + file, line); +} + +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,23 +716,128 @@ 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 - && strncmp(argv[1], "--failpath=", strlen("--failpath=")) == 0) { - failpath = argv[1] + strlen("--failpath="); + unsigned int i; + + for (i = 1; i < argc; i++) { + if (!strncmp(argv[i], "--failpath=", strlen("--failpath="))) { + failpath = argv[i] + strlen("--failpath="); + } else if (strcmp(argv[i], "--tracepath") == 0) { + tracefd = dup(STDERR_FILENO); + failtest_timeout_ms = -1; + } + } + gettimeofday(&start, NULL); +} + +/* Free up memory, so valgrind doesn't report leaks. */ +static void free_everything(void) +{ + unsigned int i; + + for (i = 0; i < writes_num; i++) { + free(writes[i].data); + if (writes[i].hdr.offset != (off_t)-1) + free(writes[i].olddata); } + free(writes); + free(fd_orig); + for (i = 0; i < history_num; i++) { + if (history[i].type == FAILTEST_OPEN) + free((char *)history[i].u.open.pathname); + } + free(history); } void failtest_exit(int status) { unsigned int i; - if (control_fd == -1) + if (control_fd == -1) { + free_everything(); exit(status); + } if (failtest_exit_check) { if (!failtest_exit_check(history, history_num)) @@ -555,6 +865,7 @@ void failtest_exit(int status) close(fd_orig[i].fd); } + free_everything(); tell_parent(SUCCESS); exit(0); }