X-Git-Url: https://git.ozlabs.org/?p=ccan;a=blobdiff_plain;f=ccan%2Ffailtest%2Ffailtest.c;h=56ace7431847c5ac9fd059b77d4b9271958a3fb7;hp=2471510614a070c6c404e04d51789bbc28a8536e;hb=acb6106ca2778d74af91f9b92bc32df179195b6b;hpb=4601063b15ad7a1896135bf91bbcb7cabfbc9000 diff --git a/ccan/failtest/failtest.c b/ccan/failtest/failtest.c index 24715106..56ace743 100644 --- a/ccan/failtest/failtest.c +++ b/ccan/failtest/failtest.c @@ -1,4 +1,5 @@ -#include "config.h" +/* Licensed under LGPL - see LICENSE file for details */ +#include #include #include #include @@ -12,16 +13,21 @@ #include #include #include +#include #include #include +#include #include #include -#include #include +#include +#include +#include -enum failtest_result (*failtest_hook)(struct failtest_call *, unsigned); +enum failtest_result (*failtest_hook)(struct tlist_calls *); static int tracefd = -1; +static int warnfd; unsigned int failtest_timeout_ms = 20000; @@ -43,13 +49,44 @@ struct lock_info { int type; }; -bool (*failtest_exit_check)(struct failtest_call *history, unsigned num); +/* We hash the call location together with its backtrace. */ +static size_t hash_call(const struct failtest_call *call) +{ + return hash(call->file, strlen(call->file), + hash(&call->line, 1, + hash(call->backtrace, call->backtrace_num, + call->type))); +} + +static bool call_eq(const struct failtest_call *call1, + const struct failtest_call *call2) +{ + unsigned int i; + + if (strcmp(call1->file, call2->file) != 0 + || call1->line != call2->line + || call1->type != call2->type + || call1->backtrace_num != call2->backtrace_num) + return false; + + for (i = 0; i < call1->backtrace_num; i++) + if (call1->backtrace[i] != call2->backtrace[i]) + return false; + + return true; +} + +/* Defines struct failtable. */ +HTABLE_DEFINE_TYPE(struct failtest_call, (struct failtest_call *), hash_call, + call_eq, failtable); + +bool (*failtest_exit_check)(struct tlist_calls *history); -static struct failtest_call *history = NULL; -static unsigned int history_num = 0; +static struct tlist_calls history = TLIST_INIT(history); static int control_fd = -1; static struct timeval start; -static unsigned int probe_count = 0; +static bool probing = false; +static struct failtable failtable; static struct write_call *child_writes = NULL; static unsigned int child_writes_num = 0; @@ -60,28 +97,60 @@ static unsigned int lock_num = 0; static pid_t orig_pid; -static const char info_to_arg[] = "mceoxprwf"; +static const char info_to_arg[] = "mceoxprwfa"; /* Dummy call used for failtest_undo wrappers. */ static struct failtest_call unrecorded_call; +#if HAVE_BACKTRACE +#include + +static void **get_backtrace(unsigned int *num) +{ + static unsigned int max_back = 100; + void **ret; + +again: + ret = malloc(max_back * sizeof(void *)); + *num = backtrace(ret, max_back); + if (*num == max_back) { + free(ret); + max_back *= 2; + goto again; + } + return ret; +} +#else +/* This will test slightly less, since will consider all of the same + * calls as identical. But, it's slightly faster! */ +static void **get_backtrace(unsigned int *num) +{ + *num = 0; + return NULL; +} +#endif /* HAVE_BACKTRACE */ + static struct failtest_call *add_history_(enum failtest_call_type type, const char *file, unsigned int line, const void *elem, size_t elem_size) { + struct failtest_call *call; + /* NULL file is how we suppress failure. */ if (!file) return &unrecorded_call; - history = realloc(history, (history_num + 1) * sizeof(*history)); - history[history_num].type = type; - history[history_num].file = file; - history[history_num].line = line; - history[history_num].cleanup = NULL; - memcpy(&history[history_num].u, elem, elem_size); - return &history[history_num++]; + call = malloc(sizeof *call); + call->type = type; + call->file = file; + call->line = line; + call->cleanup = NULL; + call->backtrace = get_backtrace(&call->backtrace_num); + memcpy(&call->u, elem, elem_size); + tlist_add_tail(&history, call, list); + return call; } #define add_history(type, file, line, elem) \ @@ -91,6 +160,24 @@ static struct failtest_call *add_history_(enum failtest_call_type type, #define set_cleanup(call, clean, type) \ (call)->cleanup = (void *)((void)sizeof(clean((type *)NULL),1), (clean)) + +/* Dup the fd to a high value (out of the way I hope!), and close the old fd. */ +static int move_fd_to_high(int fd) +{ + int i; + + for (i = FD_SETSIZE - 1; i >= 0; i--) { + if (fcntl(i, F_GETFL) == -1 && errno == EBADF) { + if (dup2(fd, i) == -1) + err(1, "Failed to dup fd %i to %i", fd, i); + close(fd); + return i; + } + } + /* Nothing? Really? Er... ok? */ + return fd; +} + static bool read_write_info(int fd) { struct write_call *w; @@ -113,18 +200,52 @@ static bool read_write_info(int fd) static char *failpath_string(void) { - unsigned int i; - char *ret = malloc(history_num + 1); - - for (i = 0; i < history_num; i++) { - ret[i] = info_to_arg[history[i].type]; - if (history[i].fail) - ret[i] = toupper(ret[i]); + struct failtest_call *i; + char *ret = strdup(""); + unsigned len = 0; + + /* Inefficient, but who cares? */ + tlist_for_each(&history, i, list) { + ret = realloc(ret, len + 2); + ret[len] = info_to_arg[i->type]; + if (i->fail) + ret[len] = toupper(ret[len]); + ret[++len] = '\0'; } - ret[i] = '\0'; return ret; } +static void warn_via_fd(int e, const char *fmt, va_list ap) +{ + char *p = failpath_string(); + + vdprintf(warnfd, fmt, ap); + if (e != -1) + dprintf(warnfd, ": %s", strerror(e)); + dprintf(warnfd, " [%s]\n", p); + free(p); +} + +static void fwarn(const char *fmt, ...) +{ + va_list ap; + int e = errno; + + va_start(ap, fmt); + warn_via_fd(e, fmt, ap); + va_end(ap); +} + + +static void fwarnx(const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + warn_via_fd(-1, fmt, ap); + va_end(ap); +} + static void tell_parent(enum info_type type) { if (control_fd != -1) @@ -264,23 +385,23 @@ static struct saved_file *save_file(struct saved_file *next, int fd) static struct saved_file *save_files(void) { struct saved_file *files = NULL; - int i; + struct failtest_call *i; /* Figure out the set of live fds. */ - for (i = history_num - 2; i >= 0; i--) { - if (history[i].type == FAILTEST_OPEN) { - int fd = history[i].u.open.ret; + tlist_for_each_rev(&history, i, list) { + if (i->type == FAILTEST_OPEN) { + int fd = i->u.open.ret; /* Only do successful, writable fds. */ if (fd < 0) continue; /* If it was closed, cleanup == NULL. */ - if (!history[i].cleanup) + if (!i->cleanup) continue; - if ((history[i].u.open.flags & O_RDWR) == O_RDWR) { + if ((i->u.open.flags & O_RDWR) == O_RDWR) { files = save_file(files, fd); - } else if ((history[i].u.open.flags & O_WRONLY) + } else if ((i->u.open.flags & O_WRONLY) == O_WRONLY) { /* FIXME: Handle O_WRONLY. Open with O_RDWR? */ abort(); @@ -309,45 +430,68 @@ static void restore_files(struct saved_file *s) } } +static void free_files(struct saved_file *s) +{ + while (s) { + struct saved_file *next = s->next; + free(s->contents); + free(s); + s = next; + } +} + +static void free_call(struct failtest_call *call) +{ + /* We don't do this in cleanup: needed even for failed opens. */ + if (call->type == FAILTEST_OPEN) + free((char *)call->u.open.pathname); + free(call->backtrace); + tlist_del_from(&history, call, list); + free(call); +} + /* Free up memory, so valgrind doesn't report leaks. */ static void free_everything(void) { - unsigned int i; + struct failtest_call *i; - /* We don't do this in cleanup: needed even for failed opens. */ - for (i = 0; i < history_num; i++) { - if (history[i].type == FAILTEST_OPEN) - free((char *)history[i].u.open.pathname); - } - free(history); + while ((i = tlist_top(&history, struct failtest_call, list)) != NULL) + free_call(i); + + failtable_clear(&failtable); } static NORETURN void failtest_cleanup(bool forced_cleanup, int status) { - int i; + struct failtest_call *i; /* For children, we don't care if they "failed" the testing. */ if (control_fd != -1) status = 0; - if (forced_cleanup) - history_num--; + if (forced_cleanup) { + /* We didn't actually do final operation: remove it. */ + i = tlist_tail(&history, struct failtest_call, list); + free_call(i); + } /* Cleanup everything, in reverse order. */ - for (i = history_num - 1; i >= 0; i--) { - if (!history[i].cleanup) + tlist_for_each_rev(&history, i, list) { + if (!i->cleanup) continue; if (!forced_cleanup) { printf("Leak at %s:%u: --failpath=%s\n", - history[i].file, history[i].line, - failpath_string()); + i->file, i->line, failpath_string()); status = 1; } - history[i].cleanup(&history[i].u); + i->cleanup(&i->u); } free_everything(); - tell_parent(SUCCESS); + if (status == 0) + tell_parent(SUCCESS); + else + tell_parent(FAILURE); exit(status); } @@ -359,10 +503,7 @@ static bool should_fail(struct failtest_call *call) char *out = NULL; size_t outlen = 0; struct saved_file *files; - - /* Are we probing? */ - if (probe_count && --probe_count == 0) - failtest_cleanup(true, 0); + struct failtest_call *dup; if (call == &unrecorded_call) return false; @@ -379,23 +520,21 @@ static bool should_fail(struct failtest_call *call) != info_to_arg[call->type]) errx(1, "Failpath expected '%c' got '%c'\n", info_to_arg[call->type], *failpath); - call->fail = isupper((unsigned char)*(failpath++)); + call->fail = cisupper(*(failpath++)); return call->fail; } } /* Attach debugger if they asked for it. */ - if (debugpath && history_num == strlen(debugpath)) { - unsigned int i; + if (debugpath) { + char *path; - for (i = 0; i < history_num; i++) { - unsigned char c = info_to_arg[history[i].type]; - if (history[i].fail) - c = toupper(c); - if (c != debugpath[i]) - break; - } - if (i == history_num) { + /* Pretend this last call matches whatever path wanted: + * keeps valgrind happy. */ + call->fail = cisupper(debugpath[strlen(debugpath)-1]); + path = failpath_string(); + + if (streq(path, debugpath)) { char str[80]; /* Don't timeout. */ @@ -404,29 +543,45 @@ static bool should_fail(struct failtest_call *call) getpid(), getpid()); if (system(str) == 0) sleep(5); + } else { + /* Ignore last character: could be upper or lower. */ + path[strlen(path)-1] = '\0'; + if (!strstarts(debugpath, path)) { + fprintf(stderr, + "--debugpath not followed: %s\n", path); + debugpath = NULL; + } } + free(path); } + /* Are we probing? If so, we never fail twice. */ + if (probing) + return call->fail = false; + + /* Don't more than once in the same place. */ + dup = failtable_get(&failtable, call); + if (dup) + return call->fail = false; + if (failtest_hook) { - switch (failtest_hook(history, history_num)) { + switch (failtest_hook(&history)) { case FAIL_OK: break; + case FAIL_PROBE: + probing = true; + break; case FAIL_DONT_FAIL: call->fail = false; return false; - case FAIL_PROBE: - /* Already down probe path? Stop now. */ - if (probe_count) - failtest_cleanup(true, 0); - /* FIXME: We should run *parent* and run probe until - * calls match up again. */ - probe_count = 3; - break; default: abort(); } } + /* Add it to our table of calls. */ + failtable_add(&failtable, call); + files = save_files(); /* We're going to fail in the child. */ @@ -442,25 +597,24 @@ static bool should_fail(struct failtest_call *call) if (child == 0) { if (tracefd != -1) { - struct timeval now; + struct timeval diff; const char *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; - p = failpath_string(); + char *failpath; + struct failtest_call *c; + + c = tlist_tail(&history, struct failtest_call, list); + diff = time_sub(time_now(), start); + failpath = failpath_string(); trace("%u->%u (%u.%02u): %s (", getppid(), getpid(), - (int)now.tv_sec, (int)now.tv_usec / 10000, p); - free((char *)p); - p = strrchr(history[history_num-1].file, '/'); + (int)diff.tv_sec, (int)diff.tv_usec / 10000, + failpath); + free(failpath); + p = strrchr(c->file, '/'); if (p) trace("%s", p+1); else - trace("%s", history[history_num-1].file); - trace(":%u)\n", history[history_num-1].line); + trace("%s", c->file); + trace(":%u)\n", c->line); } close(control[0]); close(output[0]); @@ -468,7 +622,9 @@ static bool should_fail(struct failtest_call *call) dup2(output[1], STDERR_FILENO); if (output[1] != STDOUT_FILENO && output[1] != STDERR_FILENO) close(output[1]); - control_fd = control[1]; + control_fd = move_fd_to_high(control[1]); + /* Valgrind spots the leak if we don't free these. */ + free_files(files); return true; } @@ -547,6 +703,9 @@ static bool should_fail(struct failtest_call *call) restore_files(files); + /* Only child does probe. */ + probing = false; + /* We continue onwards without failing. */ call->fail = false; return false; @@ -590,14 +749,14 @@ void *failtest_malloc(size_t size, const char *file, unsigned line) p = add_history(FAILTEST_MALLOC, file, line, &call); if (should_fail(p)) { - p->u.calloc.ret = NULL; + p->u.malloc.ret = NULL; p->error = ENOMEM; } else { - p->u.calloc.ret = malloc(size); + p->u.malloc.ret = malloc(size); set_cleanup(p, cleanup_malloc, struct malloc_call); } errno = p->error; - return p->u.calloc.ret; + return p->u.malloc.ret; } static void cleanup_realloc(struct realloc_call *call) @@ -606,28 +765,28 @@ static void cleanup_realloc(struct realloc_call *call) } /* Walk back and find out if we got this ptr from a previous routine. */ -static void fixup_ptr_history(void *ptr, unsigned int last) +static void fixup_ptr_history(void *ptr) { - int i; + struct failtest_call *i; /* Start at end of history, work back. */ - for (i = last - 1; i >= 0; i--) { - switch (history[i].type) { + tlist_for_each_rev(&history, i, list) { + switch (i->type) { case FAILTEST_REALLOC: - if (history[i].u.realloc.ret == ptr) { - history[i].cleanup = NULL; + if (i->u.realloc.ret == ptr) { + i->cleanup = NULL; return; } break; case FAILTEST_MALLOC: - if (history[i].u.malloc.ret == ptr) { - history[i].cleanup = NULL; + if (i->u.malloc.ret == ptr) { + i->cleanup = NULL; return; } break; case FAILTEST_CALLOC: - if (history[i].u.calloc.ret == ptr) { - history[i].cleanup = NULL; + if (i->u.calloc.ret == ptr) { + i->cleanup = NULL; return; } break; @@ -649,7 +808,9 @@ void *failtest_realloc(void *ptr, size_t size, const char *file, unsigned line) p->u.realloc.ret = NULL; p->error = ENOMEM; } else { - fixup_ptr_history(ptr, history_num-1); + /* Don't catch this one in the history fixup... */ + p->u.realloc.ret = NULL; + fixup_ptr_history(ptr); p->u.realloc.ret = realloc(ptr, size); set_cleanup(p, cleanup_realloc, struct realloc_call); } @@ -659,7 +820,7 @@ void *failtest_realloc(void *ptr, size_t size, const char *file, unsigned line) void failtest_free(void *ptr) { - fixup_ptr_history(ptr, history_num); + fixup_ptr_history(ptr); free(ptr); } @@ -688,7 +849,7 @@ int failtest_open(const char *pathname, free((char *)call.pathname); p->u.open.ret = open(pathname, call.flags, call.mode); - if (!failpath && p->u.open.ret == -1) { + if (p->u.open.ret == -1) { p->fail = false; p->error = errno; } else if (should_fail(p)) { @@ -703,6 +864,30 @@ int failtest_open(const char *pathname, return p->u.open.ret; } +void *failtest_mmap(void *addr, size_t length, int prot, int flags, + int fd, off_t offset, const char *file, unsigned line) +{ + struct failtest_call *p; + struct mmap_call call; + + call.addr = addr; + call.length = length; + call.prot = prot; + call.flags = flags; + call.offset = offset; + call.fd = fd; + + p = add_history(FAILTEST_MMAP, file, line, &call); + if (should_fail(p)) { + p->u.mmap.ret = MAP_FAILED; + p->error = ENOMEM; + } else { + p->u.mmap.ret = mmap(addr, length, prot, flags, fd, offset); + } + errno = p->error; + return p->u.mmap.ret; +} + static void cleanup_pipe(struct pipe_call *call) { if (!call->closed[0]) @@ -897,7 +1082,7 @@ add_lock(struct lock_info *locks, int fd, off_t start, off_t end, int type) /* We trap this so we can record it: we don't fail it. */ int failtest_close(int fd, const char *file, unsigned line) { - int i; + struct failtest_call *i; struct close_call call; struct failtest_call *p; @@ -914,30 +1099,30 @@ int failtest_close(int fd, const char *file, unsigned line) return close(fd); /* Trace history to find source of fd. */ - for (i = history_num-1; i >= 0; i--) { - switch (history[i].type) { + tlist_for_each_rev(&history, i, list) { + switch (i->type) { case FAILTEST_PIPE: /* From a pipe? */ - if (history[i].u.pipe.fds[0] == fd) { - assert(!history[i].u.pipe.closed[0]); - history[i].u.pipe.closed[0] = true; - if (history[i].u.pipe.closed[1]) - history[i].cleanup = NULL; + if (i->u.pipe.fds[0] == fd) { + assert(!i->u.pipe.closed[0]); + i->u.pipe.closed[0] = true; + if (i->u.pipe.closed[1]) + i->cleanup = NULL; goto out; } - if (history[i].u.pipe.fds[1] == fd) { - assert(!history[i].u.pipe.closed[1]); - history[i].u.pipe.closed[1] = true; - if (history[i].u.pipe.closed[0]) - history[i].cleanup = NULL; + if (i->u.pipe.fds[1] == fd) { + assert(!i->u.pipe.closed[1]); + i->u.pipe.closed[1] = true; + if (i->u.pipe.closed[0]) + i->cleanup = NULL; goto out; } break; case FAILTEST_OPEN: - if (history[i].u.open.ret == fd) { - assert((void *)history[i].cleanup + if (i->u.open.ret == fd) { + assert((void *)i->cleanup == (void *)cleanup_open); - history[i].cleanup = NULL; + i->cleanup = NULL; goto out; } break; @@ -1038,19 +1223,21 @@ void failtest_init(int argc, char *argv[]) unsigned int i; orig_pid = getpid(); - + + warnfd = move_fd_to_high(dup(STDERR_FILENO)); 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); + tracefd = warnfd; failtest_timeout_ms = -1; } else if (!strncmp(argv[i], "--debugpath=", strlen("--debugpath="))) { debugpath = argv[i] + strlen("--debugpath="); } } - gettimeofday(&start, NULL); + failtable_init(&failtable); + start = time_now(); } bool failtest_has_failed(void) @@ -1061,7 +1248,7 @@ bool failtest_has_failed(void) void failtest_exit(int status) { if (failtest_exit_check) { - if (!failtest_exit_check(history, history_num)) + if (!failtest_exit_check(&history)) child_fail(NULL, 0, "failtest_exit_check failed\n"); }