diff options
author | Nicolas Williams <nico@cryptonector.com> | 2015-05-03 19:23:37 -0500 |
---|---|---|
committer | Nicolas Williams <nico@cryptonector.com> | 2015-05-11 21:36:46 -0500 |
commit | 821d6404b665809144afcad21c7e4cb277ff02a2 (patch) | |
tree | 68acd3e9896cb577a954775824cc6d104791abf3 | |
parent | e3cb1f76cd3e2e55455757217be15662d3c75236 (diff) |
Add error injection library
-rw-r--r-- | Makefile.am | 9 | ||||
-rw-r--r-- | configure.ac | 5 | ||||
-rw-r--r-- | inject_errors.c | 112 | ||||
-rwxr-xr-x | tests/run | 16 |
4 files changed, 141 insertions, 1 deletions
diff --git a/Makefile.am b/Makefile.am index a45c89fb..1030bbbd 100644 --- a/Makefile.am +++ b/Makefile.am @@ -8,7 +8,6 @@ LIBJQ_SRC = locfile.c bytecode.c compile.c execute.c builtin.c jv.c \ jv_parse.c jv_print.c jv_dtoa.c jv_unicode.c jv_aux.c jv_file.c \ jv_alloc.c jq_test.c util.c linker.c ${LIBJQ_INCS} - ### C build options AM_CFLAGS = -Wextra -Wall -Wno-missing-field-initializers \ @@ -47,6 +46,14 @@ libjq_la_LDFLAGS = -export-symbols-regex '^j[qv]_' -version-info 1:4:0 include_HEADERS = jv.h jq.h +### Error injection for testing + +if ENABLE_ERROR_INJECTION +lib_LTLIBRARIES += libinject_errors.la +libinject_errors_la_SOURCES = inject_errors.c +libinject_errors_la_LIBADD = -ldl +libinject_errors_la_LDFLAGS = -module +endif ### Building the jq binary diff --git a/configure.ac b/configure.ac index 2b7cf68b..b24b6a16 100644 --- a/configure.ac +++ b/configure.ac @@ -87,6 +87,10 @@ dnl Don't attempt to build docs if there's no Ruby lying around AC_ARG_ENABLE([docs], AC_HELP_STRING([--disable-docs], [don't build docs])) +dnl Don't attempt to build the error injection object (if there is no LD_PRELOAD support) +AC_ARG_ENABLE([error-injection], + AC_HELP_STRING([--enable-error-injection], [build and test with error injection])) + AS_IF([test "x$enable_docs" != "xno"],[ AC_CHECK_PROGS(bundle_cmd, bundle) @@ -112,6 +116,7 @@ EOF ]) AM_CONDITIONAL([ENABLE_DOCS], [test "x$enable_docs" != xno]) +AM_CONDITIONAL([ENABLE_ERROR_INJECTION], [test "x$enable_error_injection" = xyes]) AC_FIND_FUNC([isatty], [c], [#include <unistd.h>], [0]) AC_FIND_FUNC([_isatty], [c], [#include <io.h>], [0]) diff --git a/inject_errors.c b/inject_errors.c new file mode 100644 index 00000000..b61a2713 --- /dev/null +++ b/inject_errors.c @@ -0,0 +1,112 @@ + +#define _GNU_SOURCE /* for RTLD_NEXT */ +#include <assert.h> +#include <dlfcn.h> +#include <errno.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> + +static FILE *fail; +static FILE *fail_read; +static FILE *fail_write; +static FILE *fail_close; +static int error; + +static FILE * (*real_fopen)(const char *, const char *); +static int (*real_fclose)(FILE *); +static int (*real_ferror)(FILE *); +static void (*real_clearerr)(FILE *); +static char * (*real_fgets)(char *, int, FILE *); +static size_t (*real_fread)(void *, size_t, size_t, FILE *); +static size_t (*real_fwrite)(const void *, size_t, size_t, FILE *); + +#define GET_REAL(sym) \ + do { \ + if (real_ ## sym == 0) { \ + real_ ## sym = dlsym(RTLD_NEXT, #sym); \ + assert(real_ ## sym != 0); \ + } \ + } while (0) + +#define dbg_write(msg) (void)write(2, msg, sizeof(msg) - 1) + +#define dbg() \ + do { \ + dbg_write("here: "); \ + dbg_write(__func__); \ + dbg_write("!\n"); \ + } while (0) + +FILE *fopen(const char *path, const char *mode) { + GET_REAL(fopen); + fail = fail_read = fail_write = fail_close = 0; + FILE *f = real_fopen(path, mode); + error = EIO; + if (strcmp(path, "fail_read") == 0) { + fail = fail_read = f; + } else if (strncmp(path, "fail_write", sizeof("fail_write") - 1) == 0) { + // Not that jq opens files for write anyways... + fail = fail_write = f; + if (strcmp(path, "fail_write_enospc") == 0) + error = ENOSPC; + } else if (strncmp(path, "fail_close", sizeof("fail_close") - 1) == 0) { + fail = fail_close = f; + if (strcmp(path, "fail_close_enospc") == 0) + error = ENOSPC; + } + return f; +} + +int fclose(FILE *f) { + GET_REAL(fclose); + int res = real_fclose(f); + if (fail_close == f) { + fail = fail_read = fail_write = fail_close = 0; + return EOF; + } + return res; +} + +char * fgets(char *buf, int len, FILE *f) { + GET_REAL(fgets); + char *res = real_fgets(buf, len, f); + if (fail_read == f) + return 0; + return res; +} + +size_t fread(void *buf, size_t sz, size_t nemb, FILE *f) { + GET_REAL(fread); + size_t res = real_fread(buf, sz, nemb, f); + if (fail_read == f) + return 0; + return res; +} + +size_t fwrite(const void *buf, size_t sz, size_t nemb, FILE *f) { + GET_REAL(fwrite); + size_t res = real_fwrite(buf, sz, nemb, f); + if (fail_write == f) + return 0; + return res; +} + +int ferror(FILE *f) { + GET_REAL(ferror); + int res = real_ferror(f); + if (fail == f) { + errno = error; + return 1; + } + return res; +} + +void clearerr(FILE *f) { + GET_REAL(clearerr); + real_clearerr(f); + if (fail == f) { + fail = fail_read = fail_write = fail_close = 0; + error = 0; + } +} @@ -31,6 +31,22 @@ if [ -z "$d" ]; then exit 0 fi +if [ -f $PWD/.libs/libinject_errors.so ]; then + # Do some simple error injection tests to check that we're handling + # I/O errors correctly. + ( + jq=$PWD/jq + libinject=$PWD/.libs/libinject_errors.so + cd $d + LD_PRELOAD=$libinject $jq . /dev/null + touch fail_read + LD_PRELOAD=$libinject $jq . fail_read && exit 2 + touch fail_close + LD_PRELOAD=$libinject $jq . fail_close && exit 2 + true + ) +fi + printf 'a\0b\nc\0d\ne' > $d/input $VALGRIND $Q ./jq -Rse '. == "a\u0000b\nc\u0000d\ne"' $d/input $VALGRIND $Q ./jq -Rne '[inputs] == ["a\u0000b", "c\u0000d", "e"]' $d/input |