/* * Copyright (c) 2012 Sean Bartell * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * - Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * - Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * - The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /** @cond internal */ /** @addtogroup bithenge * @{ */ /** * @file * Fake system call errors for testing. */ #include #include #include #include #include #include #include #define BITHENGE_FAILURE_DECLS_ONLY 1 #include "failure.h" #include "common.h" /* * This file raises fake errors from system calls, to test that Bithenge * handles the errors correctly. It has two primary modes of operation, * depending on an environment variable: * * BITHENGE_FAILURE_INDEX not set: when a system call is made, a child process * returns a fake error from that call. If the child process handles the error * correctly (exit code is 1), the main process continues without errors. If * the child process has a problem, the main process raises the fake error * again and shows all stdout and stderr output. For speed, errors are only * raised when part of the backtrace has not been seen before. * * BITHENGE_FAILURE_INDEX set: the program runs normally until system call * number BITHENGE_FAILURE_INDEX is made; a fake error is returned from this * call. */ static int g_initialized = 0; static int g_failure_index = -1; static int g_failure_index_selected = -1; typedef struct backtrace_item { struct backtrace_item *next; void *backtrace_item; } backtrace_item_t; static backtrace_item_t *g_backtrace_items = NULL; static void atexit_handler(void) { while (g_backtrace_items) { backtrace_item_t *first = g_backtrace_items; g_backtrace_items = first->next; free(first); } } static inline void initialize(void) { g_initialized = 1; errno_t rc = atexit(atexit_handler); if (rc) exit(127); char *sel_str = getenv("BITHENGE_FAILURE_INDEX"); if (sel_str) g_failure_index_selected = strtol(sel_str, NULL, 10); } /* * Record a hit for a backtrace address and return whether this is the first * hit. */ static inline errno_t backtrace_item_hit(void *addr) { backtrace_item_t **bip; for (bip = &g_backtrace_items; *bip; bip = &(*bip)->next) { backtrace_item_t *bi = *bip; if (bi->backtrace_item == addr) { /* Keep frequently accessed items near the front. */ *bip = bi->next; bi->next = g_backtrace_items; g_backtrace_items = bi; return 0; } } /* No item found; create one. */ backtrace_item_t *i = malloc(sizeof(*i)); if (!i) exit(127); i->next = g_backtrace_items; i->backtrace_item = addr; g_backtrace_items = i; return 1; } errno_t bithenge_should_fail(void) { g_failure_index++; if (!g_initialized) initialize(); if (g_failure_index_selected != -1) { if (g_failure_index == g_failure_index_selected) return 1; /* breakpoint here */ return 0; } /* * If all backtrace items have been seen already, there's no need to * try raising an error. */ void *trace[256]; int size = backtrace(trace, 256); int raise_error = 0; for (int i = 0; i < size; i++) { if (backtrace_item_hit(trace[i])) raise_error = 1; } if (!raise_error) return 0; if (!fork()) { /* Child silently fails. */ int null = open("/dev/null", O_WRONLY); if (null == -1) exit(127); vfs_clone(null, STDOUT_FILENO, false); vfs_clone(null, STDERR_FILENO, false); vfs_put(null); return 1; } /* Parent checks whether child failed correctly. */ int status; wait(&status); if (WIFEXITED(status) && WEXITSTATUS(status) == 1) return 0; /* * The child had an error! We couldn't easily debug it because it was * in a separate process with redirected stdout and stderr. Do it again * without redirecting or forking. */ fprintf(stderr, "** Fake error raised here (BITHENGE_FAILURE_INDEX=%d)\n", g_failure_index); return 1; } void *bithenge_failure_malloc(size_t size) { if (bithenge_should_fail()) return NULL; return malloc(size); } void *bithenge_failure_realloc(void *ptr, size_t size) { if (bithenge_should_fail()) return NULL; return realloc(ptr, size); } ssize_t bithenge_failure_read(int fd, void *buf, size_t count) { if (bithenge_should_fail()) { errno = EIO; return -1; } return read(fd, buf, count); } off_t bithenge_failure_lseek(int fd, off_t offset, int whither) { if (bithenge_should_fail()) { errno = EINVAL; return (off_t) -1; } return lseek(fd, offset, whither); } errno_t bithenge_failure_ferror(FILE *stream) { if (bithenge_should_fail()) return 1; return ferror(stream); } char *bithenge_failure_str_ndup(const char *s, size_t max_len) { if (bithenge_should_fail()) return NULL; return str_ndup(s, max_len); } errno_t bithenge_failure_open(const char *pathname, int flags) { if (bithenge_should_fail()) { errno = EACCES; return -1; } return open(pathname, flags); } errno_t bithenge_failure_fstat(int fd, vfs_stat_t *buf) { if (bithenge_should_fail()) { errno = EIO; return -1; } return fstat(fd, buf); } /** @} */ /** @endcond */