summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorNicolas Williams <nico@cryptonector.com>2015-05-03 19:23:37 -0500
committerNicolas Williams <nico@cryptonector.com>2015-05-11 21:36:46 -0500
commit821d6404b665809144afcad21c7e4cb277ff02a2 (patch)
tree68acd3e9896cb577a954775824cc6d104791abf3
parente3cb1f76cd3e2e55455757217be15662d3c75236 (diff)
Add error injection library
-rw-r--r--Makefile.am9
-rw-r--r--configure.ac5
-rw-r--r--inject_errors.c112
-rwxr-xr-xtests/run16
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;
+ }
+}
diff --git a/tests/run b/tests/run
index 67229969..b213b565 100755
--- a/tests/run
+++ b/tests/run
@@ -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