X-Git-Url: https://git.ozlabs.org/?p=ccan;a=blobdiff_plain;f=ccan%2Ffailtest%2Ffailtest.c;h=f59ed03acf4abc70f48c8779fabf6684ab724444;hp=b25b049edac1d162c663ae58218c75dc3815e0dd;hb=9571a41e8494f3135557e3ec50c2de856392173e;hpb=3ad57bc902f31ba347df59de04168f00c626d924 diff --git a/ccan/failtest/failtest.c b/ccan/failtest/failtest.c index b25b049e..f59ed03a 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,13 +13,15 @@ #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; @@ -42,10 +45,9 @@ struct lock_info { int type; }; -bool (*failtest_exit_check)(struct failtest_call *history, unsigned num); +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; @@ -57,6 +59,8 @@ static pid_t lock_owner; static struct lock_info *locks = NULL; static unsigned int lock_num = 0; +static pid_t orig_pid; + static const char info_to_arg[] = "mceoxprwf"; /* Dummy call used for failtest_undo wrappers. */ @@ -68,17 +72,20 @@ static struct failtest_call *add_history_(enum failtest_call_type type, 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; + memcpy(&call->u, elem, elem_size); + tlist_add_tail(&history, call, list); + return call; } #define add_history(type, file, line, elem) \ @@ -110,15 +117,18 @@ 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; } @@ -248,7 +258,8 @@ static struct saved_file *save_file(struct saved_file *next, int fd) s->len = lseek(fd, 0, SEEK_END); lseek(fd, 0, SEEK_SET); s->contents = malloc(s->len); - read(fd, s->contents, s->len); + if (read(fd, s->contents, s->len) != s->len) + err(1, "Failed to save %zu bytes", (size_t)s->len); lseek(fd, s->off, SEEK_SET); return s; } @@ -260,23 +271,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(); @@ -293,8 +304,11 @@ static void restore_files(struct saved_file *s) struct saved_file *next = s->next; lseek(s->fd, 0, SEEK_SET); - write(s->fd, s->contents, s->len); - ftruncate(s->fd, s->len); + if (write(s->fd, s->contents, s->len) != s->len) + err(1, "Failed to restore %zu bytes", (size_t)s->len); + if (ftruncate(s->fd, s->len) != 0) + err(1, "Failed to trim file to length %zu", + (size_t)s->len); free(s->contents); lseek(s->fd, s->off, SEEK_SET); free(s); @@ -302,41 +316,58 @@ 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); + 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); } 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(); @@ -354,7 +385,7 @@ static bool should_fail(struct failtest_call *call) struct saved_file *files; /* Are we probing? */ - if (probe_count && --probe_count == 0) + if (probe_count && --probe_count == 0 && control_fd != -1) failtest_cleanup(true, 0); if (call == &unrecorded_call) @@ -372,49 +403,51 @@ 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 = failpath_string(); - 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) { + if (streq(path, debugpath)) { char str[80]; /* Don't timeout. */ signal(SIGUSR1, SIG_IGN); sprintf(str, "xterm -e gdb /proc/%d/exe %d &", getpid(), getpid()); - system(str); - sleep(5); + if (system(str) == 0) + sleep(5); + } else if (!strstarts(path, debugpath)) { + fprintf(stderr, "--debugpath not followed: %s\n", path); + debugpath = NULL; } + free(path); } if (failtest_hook) { - switch (failtest_hook(history, history_num)) { + switch (failtest_hook(&history)) { case FAIL_OK: break; + case FAIL_PROBE: + /* Already down probe path? Stop now. */ + if (!probe_count) { + /* FIXME: We should run *parent* and + * run probe until calls match up again. */ + probe_count = 3; + break; + } else { + /* Child should give up now. */ + if (control_fd != -1) + failtest_cleanup(true, 0); + /* Parent, don't fail again. */ + } 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(); } @@ -435,25 +468,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]); @@ -462,6 +494,8 @@ static bool should_fail(struct failtest_call *call) if (output[1] != STDOUT_FILENO && output[1] != STDERR_FILENO) close(output[1]); control_fd = control[1]; + /* Valgrind spots the leak if we don't free these. */ + free_files(files); return true; } @@ -583,14 +617,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) @@ -599,28 +633,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; @@ -642,7 +676,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); } @@ -652,7 +688,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); } @@ -672,7 +708,7 @@ int failtest_open(const char *pathname, va_start(ap, line); call.flags = va_arg(ap, int); if (call.flags & O_CREAT) { - call.mode = va_arg(ap, mode_t); + call.mode = va_arg(ap, int); va_end(ap); } p = add_history(FAILTEST_OPEN, file, line, &call); @@ -681,7 +717,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)) { @@ -890,7 +926,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; @@ -907,30 +943,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; @@ -1019,10 +1055,19 @@ int failtest_fcntl(int fd, const char *file, unsigned line, int cmd, ...) return p->u.fcntl.ret; } +pid_t failtest_getpid(const char *file, unsigned line) +{ + /* You must call failtest_init first! */ + assert(orig_pid); + return orig_pid; +} + void failtest_init(int argc, char *argv[]) { unsigned int i; + orig_pid = getpid(); + for (i = 1; i < argc; i++) { if (!strncmp(argv[i], "--failpath=", strlen("--failpath="))) { failpath = argv[i] + strlen("--failpath="); @@ -1034,13 +1079,18 @@ void failtest_init(int argc, char *argv[]) debugpath = argv[i] + strlen("--debugpath="); } } - gettimeofday(&start, NULL); + start = time_now(); +} + +bool failtest_has_failed(void) +{ + return control_fd != -1; } 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"); }