git » libfiu » commit e0fe797

Support for the full set of I/O operations in stdio.h, including proper support for stream error indicators reported by ferror() after a simulated fault.

author Ian Blanes
2018-09-12 10:12:13 UTC
committer Alberto Bertogli
2018-09-26 18:32:03 UTC
parent 0b0ab9a723d86ee17c1922709267d6f86c18df6f

Support for the full set of I/O operations in stdio.h, including proper support for stream error indicators reported by ferror() after a simulated fault.

preload/posix/codegen.h +23 -2
preload/posix/generate +19 -2
preload/posix/modules/posix.custom.c +727 -1
preload/posix/modules/posix.stdio.mod +232 -0

diff --git a/preload/posix/codegen.h b/preload/posix/codegen.h
index 9e8cb3a..8cbabaa 100644
--- a/preload/posix/codegen.h
+++ b/preload/posix/codegen.h
@@ -11,6 +11,9 @@ extern int __thread _fiu_called;
 /* Get a symbol from libc */
 void *libc_symbol(const char *symbol);
 
+/* Record internally an error for a stream */
+void set_ferror(void * stream);
+
 /* Some compilers support constructor priorities. Since we don't rely on them,
  * but use them for clarity purposes, use a macro so libfiu builds on systems
  * where they're not supported.
@@ -75,9 +78,9 @@ void *libc_symbol(const char *symbol);
 
 /* Generates the init part of the wrapped function */
 #define mkwrap_init(RTYPE, NAME, PARAMS, PARAMST) \
-	static RTYPE (*_fiu_orig_##NAME) PARAMS = NULL;		\
+	static __thread RTYPE (*_fiu_orig_##NAME) PARAMS = NULL;		\
 								\
-	static int _fiu_in_init_##NAME = 0;			\
+	static __thread int _fiu_in_init_##NAME = 0;			\
 								\
 	static void constructor_attr(201) _fiu_init_##NAME(void) \
 	{							\
@@ -166,6 +169,24 @@ void *libc_symbol(const char *symbol);
 			goto exit;				\
 		}
 
+/* As mkwrap_body_errno, but calls set_ferror for the given stream. */
+#define mkwrap_body_errno_ferror(FIU_NAME, FAIL_RET, STREAM) \
+								\
+		fstatus = fiu_fail(FIU_NAME);			\
+		if (fstatus != 0) {				\
+			void *finfo = fiu_failinfo();		\
+			if (finfo == NULL) {			\
+				errno = valid_errnos[random() % \
+					sizeof(valid_errnos) / sizeof(int)]; \
+			} else {				\
+				errno = (long) finfo;		\
+			}					\
+			r = FAIL_RET;				\
+			printd("failing\n");			\
+			set_ferror(STREAM); \
+			goto exit;				\
+		}
+
 /* Generates a body part that will reduce the CNT parameter in a random
  * amount when the given point of failure is enabled. Can be combined with the
  * other body generators. */
diff --git a/preload/posix/generate b/preload/posix/generate
index 3c862f3..0b073e4 100755
--- a/preload/posix/generate
+++ b/preload/posix/generate
@@ -51,6 +51,9 @@ class Function:
 		self.use_errno = False
 		self.valid_errnos = []
 
+		# the FILE * for which a future ferror() shall fail too.
+		self.ferror = None
+
 		# if the given parameter should be reduced by a random amount
 		self.reduce = None
 
@@ -78,6 +81,8 @@ class Function:
 			elif k == 'valid errnos':
 				self.use_errno = True
 				self.valid_errnos = v.split()
+			elif k == 'ferror':
+				self.ferror = v
 			elif k == 'reduce':
 				self.reduce = v
 			elif k == 'variants':
@@ -125,8 +130,12 @@ class Function:
 			# be explicit 
 			self.write_valid_errnos(f)
 
-			f.write('mkwrap_body_errno("%s", %s)\n' % \
-					(self.fiu_name, self.on_error) )
+			if self.ferror is not None:
+				f.write('mkwrap_body_errno_ferror("%s", %s, %s)\n' % \
+						(self.fiu_name, self.on_error, self.ferror) )
+			else:
+				f.write('mkwrap_body_errno("%s", %s)\n' % \
+						(self.fiu_name, self.on_error) )
 		elif self.on_error is not None:
 			f.write('mkwrap_body_hardcoded("%s", %s)\n' % \
 					(self.fiu_name, self.on_error) )
@@ -163,9 +172,17 @@ class Function:
 		# enabling just <func>.
 		f.name = f.name + "64"
 		f.params = f.params.replace("off_t", "off64_t")
+		f.params = f.params.replace("fpos_t *", "fpos64_t *")
+		f.params = f.params.replace("const fpos_t *", "const fpos64_t *")
 		f.params_info = [
 			(x, y) if x != "off_t " else ("off64_t ", y)
 			for (x, y) in f.params_info]
+		f.params_info = [
+			(x, y) if x != "fpos_t *" else ("fpos64_t *", y)
+			for (x, y) in f.params_info]
+		f.params_info = [
+			(x, y) if x != "const fpos_t *" else ("const fpos64_t *", y)
+			for (x, y) in f.params_info]
 
 		# This is glibc-specific, so surround it with #ifdefs.
 		return [Verbatim("#ifdef __GLIBC__"), f, Verbatim("#endif")]
diff --git a/preload/posix/modules/posix.custom.c b/preload/posix/modules/posix.custom.c
index 2672728..e099be7 100644
--- a/preload/posix/modules/posix.custom.c
+++ b/preload/posix/modules/posix.custom.c
@@ -12,6 +12,9 @@
 #include <fcntl.h>
 #include <errno.h>
 #include <stdarg.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <pthread.h>
 
 
 /* Wrapper for open(), we can't generate it because it has a variable number
@@ -36,7 +39,7 @@ int open(const char *pathname, int flags, ...)
 		 * sometimes is smaller than an int, so we should always pass
 		 * int to it, and not mode_t. Not doing so would may result in
 		 * a compile-time warning and run-time error. We asume that it
-		 * is never bigger than an int, which holds in practise. */
+		 * is never bigger than an int, which holds in practice. */
 		mode = va_arg(l, int);
 
 		va_end(l);
@@ -180,3 +183,726 @@ mkwrap_body_errno("posix/io/oc/open", -1)
 mkwrap_bottom(open64, (pathname, flags, mode))
 
 #endif
+
+/*
+ * Here we keep track of when a FILE* I/O operation fails and fix ferror
+ * accordingly.
+ */
+
+#define MAX_FERROR_TRACKED_FILES 16384
+
+static void * ferror_hash_table[MAX_FERROR_TRACKED_FILES] = {NULL};
+
+static int ferror_hash_table_usage = 0;
+
+static pthread_mutex_t ferror_hash_table_usage_mutex
+	= PTHREAD_MUTEX_INITIALIZER;
+
+void set_ferror(void * stream)
+{
+	if (stream == NULL)
+		return;
+
+	/* Hash table has to have at least one empty position. */
+	if (ferror_hash_table_usage + 1 == MAX_FERROR_TRACKED_FILES) {
+		/* Original call is already failing, so we cannot report this
+		 * otherwise. */
+		fprintf(stderr, "libfiu: ferror() hash table is full, ferror() will"
+			" not be faked for this file (too many open files)\n");
+		return;
+	}
+
+	pthread_mutex_lock(&ferror_hash_table_usage_mutex);
+
+	/* Our hash function is taking the least significant bits of a FILE *. */
+	uintptr_t ptr = (uintptr_t) stream;
+
+	int index = (int) (ptr % MAX_FERROR_TRACKED_FILES);
+
+	for (;;) {
+		if (ferror_hash_table[index] == stream) {
+			// found => do nothing
+			break;
+		}
+
+		if (ferror_hash_table[index] == NULL) {
+			// not found => insert
+			ferror_hash_table[index] = stream;
+			ferror_hash_table_usage++;
+			break;
+		}
+
+		index = (index + 1) % MAX_FERROR_TRACKED_FILES;
+	}
+
+	pthread_mutex_unlock(&ferror_hash_table_usage_mutex);
+}
+
+static int get_ferror(void * stream)
+{
+	if (stream == NULL)
+		return 1;
+
+	pthread_mutex_lock(&ferror_hash_table_usage_mutex);
+
+	uintptr_t ptr = (uintptr_t) stream;
+
+	int index = (int) (ptr % MAX_FERROR_TRACKED_FILES);
+
+	int r;
+
+	for (;;) {
+		if (ferror_hash_table[index] == stream) {
+			// found
+			r = 1;
+			break;
+		}
+
+		if (ferror_hash_table[index] == NULL) {
+			// not found
+			r = 0;
+			break;
+		}
+
+		index = (index + 1) % MAX_FERROR_TRACKED_FILES;
+	}
+
+	pthread_mutex_unlock(&ferror_hash_table_usage_mutex);
+
+	return r;
+}
+
+static void clear_ferror(void * stream)
+{
+	if (stream == NULL)
+		return;
+
+	pthread_mutex_lock(&ferror_hash_table_usage_mutex);
+
+	uintptr_t ptr = (uintptr_t) stream;
+
+	int index = (int) (ptr % MAX_FERROR_TRACKED_FILES);
+
+	for (;;) {
+		if (ferror_hash_table[index] == stream) {
+			/* found => clear and move back colliding entries. */
+
+			for (;;) {
+				int next_index = (index + 1) % MAX_FERROR_TRACKED_FILES;
+				ferror_hash_table[index] = ferror_hash_table[next_index];
+
+				if (ferror_hash_table[index] == NULL)
+					break;
+
+				index = next_index;
+			}
+
+			ferror_hash_table_usage--;
+
+			break;
+		}
+
+		if (ferror_hash_table[index] == NULL) {
+			/* not found => do nothing */
+			break;
+		}
+
+		index = (index + 1) % MAX_FERROR_TRACKED_FILES;
+	}
+
+	pthread_mutex_unlock(&ferror_hash_table_usage_mutex);
+}
+
+
+/* Wrapper for ferror() */
+static __thread int (*_fiu_orig_ferror) (FILE *stream) = NULL;
+
+static __thread int _fiu_in_init_ferror = 0;
+
+static void __attribute__((constructor(201))) _fiu_init_ferror(void) {
+	rec_inc();
+	_fiu_in_init_ferror++;
+	_fiu_orig_ferror = (int (*) (FILE *)) libc_symbol("ferror");
+	_fiu_in_init_ferror--;
+	rec_dec();
+}
+
+int ferror (FILE *stream)
+{
+	int r;
+	int fstatus;
+	if (_fiu_called) {
+		if (_fiu_orig_ferror == NULL) {
+			if (_fiu_in_init_ferror) {
+				printd("fail on init\n");
+				return 1;
+			} else {
+				printd("get orig\n");
+				_fiu_init_ferror();
+			}
+		}
+
+		printd("orig\n");
+		return (*_fiu_orig_ferror) (stream);
+	}
+	printd("fiu\n");
+
+	/* fiu_fail() may call anything */
+	rec_inc();
+	fstatus = fiu_fail("posix/stdio/error/ferror");
+	if (fstatus != 0) {
+		r = 1;
+		printd("failing\n");
+		goto exit;
+	}
+
+	if (_fiu_orig_ferror == NULL)
+		_fiu_init_ferror();
+
+	printd("calling orig\n");
+	r = (*_fiu_orig_ferror) (stream);
+
+	if (r == 0 && get_ferror(stream)) {
+		printd("ferror fixed\n");
+		return 1;
+	}
+
+exit:
+	rec_dec();
+	return r;
+}
+
+
+
+/* Wrapper for clearerr() */
+static __thread void (*_fiu_orig_clearerr) (FILE *stream) = NULL;
+
+static __thread int _fiu_in_init_clearerr = 0;
+
+static void __attribute__((constructor(201))) _fiu_init_clearerr(void)
+{
+	rec_inc();
+	_fiu_in_init_clearerr++;
+	_fiu_orig_clearerr = (void (*) (FILE *)) libc_symbol("clearerr");
+	_fiu_in_init_clearerr--;
+	rec_dec();
+}
+
+void clearerr (FILE *stream)
+{
+	if (_fiu_called) {
+		if (_fiu_orig_clearerr == NULL) {
+			if (_fiu_in_init_clearerr) {
+				printd("fail on init\n");
+				return;
+			} else {
+				printd("get orig\n");
+				_fiu_init_clearerr();
+			}
+		}
+		printd("orig\n");
+		(*_fiu_orig_clearerr) (stream);
+		return;
+	}
+
+	printd("fiu\n");
+
+	rec_inc();
+
+	if (_fiu_orig_clearerr == NULL)
+			_fiu_init_clearerr();
+
+	printd("calling orig\n");
+	(*_fiu_orig_clearerr) (stream);
+
+	printd("fixing internal state\n");
+	clear_ferror(stream);
+
+	rec_dec();
+}
+
+
+
+/* Wrapper for fprintf(), we can't generate it because it has a variable
+ * number of arguments */
+
+mkwrap_init(int, vfprintf,
+	(FILE *restrict stream, const char *restrict format, va_list ap),
+	(FILE *restrict, const char *restrict, va_list));
+
+int fprintf(FILE *restrict stream, const char *restrict format, ...)
+{
+	int r;
+	va_list arguments;
+
+	if (_fiu_called) {
+		if (_fiu_orig_vfprintf == NULL) {
+			if (_fiu_in_init_vfprintf) {
+				printd("fail on init\n");
+				return -1;
+			} else {
+				printd("get orig\n");
+				_fiu_init_vfprintf();
+			}
+		}
+		printd("orig\n");
+		va_start(arguments, format);
+		r = (*_fiu_orig_vfprintf) (stream, format, arguments);
+		va_end(arguments);
+
+		return r;
+	}
+
+	printd("fiu\n");
+
+	/* fiu_fail() may call anything */
+	rec_inc();
+
+	static const int valid_errnos[] = {
+	  #ifdef EAGAIN
+		EAGAIN,
+	  #endif
+	  #ifdef EBADF
+		EBADF,
+	  #endif
+	  #ifdef EFBIG
+		EFBIG,
+	  #endif
+	  #ifdef EINTR
+		EINTR,
+	  #endif
+	  #ifdef EIO
+		EIO,
+	  #endif
+	  #ifdef ENOMEM
+		ENOMEM,
+	  #endif
+	  #ifdef ENOSPC
+		ENOSPC,
+	  #endif
+	  #ifdef ENXIO
+		ENXIO,
+	  #endif
+	  #ifdef EPIPE
+		EPIPE,
+	  #endif
+	  #ifdef EILSEQ
+		EILSEQ,
+	  #endif
+	  #ifdef EOVERFLOW
+		EOVERFLOW,
+	  #endif
+	};
+
+	const int fstatus = fiu_fail("posix/stdio/sp/fprintf");
+
+	if (fstatus != 0) {
+		void *finfo = fiu_failinfo();
+		if (finfo == NULL) {
+			errno = valid_errnos[random() %
+				sizeof(valid_errnos) / sizeof(int)];
+		} else {
+			errno = (long) finfo;
+		}
+		r = -1;
+		printd("failing\n");
+		set_ferror(stream);
+		goto exit;
+	}
+
+	if (_fiu_orig_vfprintf == NULL)
+		_fiu_init_vfprintf();
+
+	printd("calling orig\n");
+	va_start(arguments, format);
+	r = (*_fiu_orig_vfprintf) (stream, format, arguments);
+	va_end(arguments);
+
+exit:
+	rec_dec();
+	return r;
+}
+
+
+/* Wrapper for printf(), we can't generate it because it has a variable
+ * number of arguments */
+
+mkwrap_init(int, vprintf,
+	(const char *restrict format, va_list ap),
+	(const char *restrict, va_list));
+
+int printf(const char *restrict format, ...)
+{
+	int r;
+	va_list arguments;
+
+	if (_fiu_called) {
+		if (_fiu_orig_vprintf == NULL) {
+			if (_fiu_in_init_vprintf) {
+				printd("fail on init\n");
+				return -1;
+			} else {
+				printd("get orig\n");
+				_fiu_init_vprintf();
+			}
+		}
+		printd("orig\n");
+		va_start(arguments, format);
+		r = (*_fiu_orig_vprintf) (format, arguments);
+		va_end(arguments);
+
+		return r;
+	}
+
+	printd("fiu\n");
+
+	/* fiu_fail() may call anything */
+	rec_inc();
+
+	static const int valid_errnos[] = {
+	  #ifdef EAGAIN
+		EAGAIN,
+	  #endif
+	  #ifdef EBADF
+		EBADF,
+	  #endif
+	  #ifdef EFBIG
+		EFBIG,
+	  #endif
+	  #ifdef EINTR
+		EINTR,
+	  #endif
+	  #ifdef EIO
+		EIO,
+	  #endif
+	  #ifdef ENOMEM
+		ENOMEM,
+	  #endif
+	  #ifdef ENOSPC
+		ENOSPC,
+	  #endif
+	  #ifdef ENXIO
+		ENXIO,
+	  #endif
+	  #ifdef EPIPE
+		EPIPE,
+	  #endif
+	  #ifdef EILSEQ
+		EILSEQ,
+	  #endif
+	  #ifdef EOVERFLOW
+		EOVERFLOW,
+	  #endif
+	};
+
+	const int fstatus = fiu_fail("posix/stdio/sp/printf");
+	if (fstatus != 0) {
+		void *finfo = fiu_failinfo();
+		if (finfo == NULL) {
+			errno = valid_errnos[random() %
+				sizeof(valid_errnos) / sizeof(int)];
+		} else {
+			errno = (long) finfo;
+		}
+		r = -1;
+		printd("failing\n");
+		set_ferror(stdout);
+		goto exit;
+	}
+
+	if (_fiu_orig_vprintf == NULL)
+		_fiu_init_vprintf();
+
+	printd("calling orig\n");
+	va_start(arguments, format);
+	r = (*_fiu_orig_vprintf) (format, arguments);
+	va_end(arguments);
+
+exit:
+	rec_dec();
+	return r;
+}
+
+
+/* Wrapper for dprintf(), we can't generate it because it has a variable
+ * number of arguments */
+
+mkwrap_init(int, vdprintf,
+	(int fildes, const char *restrict format, va_list ap),
+	(int, const char *restrict, va_list));
+
+int dprintf(int fildes, const char *restrict format, ...)
+{
+	int r;
+	va_list arguments;
+
+	if (_fiu_called) {
+		if (_fiu_orig_vdprintf == NULL) {
+			if (_fiu_in_init_vdprintf) {
+				printd("fail on init\n");
+				return -1;
+			} else {
+				printd("get orig\n");
+				_fiu_init_vdprintf();
+			}
+		}
+		printd("orig\n");
+		va_start(arguments, format);
+		r = (*_fiu_orig_vdprintf) (fildes, format, arguments);
+		va_end(arguments);
+
+		return r;
+	}
+
+	printd("fiu\n");
+
+	/* fiu_fail() may call anything */
+	rec_inc();
+
+	static const int valid_errnos[] = {
+	  #ifdef EAGAIN
+		EAGAIN,
+	  #endif
+	  #ifdef EBADF
+		EBADF,
+	  #endif
+	  #ifdef EFBIG
+		EFBIG,
+	  #endif
+	  #ifdef EINTR
+		EINTR,
+	  #endif
+	  #ifdef EIO
+		EIO,
+	  #endif
+	  #ifdef ENOMEM
+		ENOMEM,
+	  #endif
+	  #ifdef ENOSPC
+		ENOSPC,
+	  #endif
+	  #ifdef ENXIO
+		ENXIO,
+	  #endif
+	  #ifdef EPIPE
+		EPIPE,
+	  #endif
+	  #ifdef EILSEQ
+		EILSEQ,
+	  #endif
+	  #ifdef EOVERFLOW
+		EOVERFLOW,
+	  #endif
+	};
+
+	const int fstatus = fiu_fail("posix/stdio/sp/dprintf");
+	if (fstatus != 0) {
+		void *finfo = fiu_failinfo();
+		if (finfo == NULL) {
+			errno = valid_errnos[random() %
+				sizeof(valid_errnos) / sizeof(int)];
+		} else {
+			errno = (long) finfo;
+		}
+		r = -1;
+		printd("failing\n");
+		goto exit;
+	}
+
+	if (_fiu_orig_vdprintf == NULL)
+		_fiu_init_vdprintf();
+
+	printd("calling orig\n");
+	va_start(arguments, format);
+	r = (*_fiu_orig_vdprintf) (fildes, format, arguments);
+	va_end(arguments);
+
+exit:
+	rec_dec();
+	return r;
+}
+
+
+/* Wrapper for fscanf(), we can't generate it because it has a variable
+ * number of arguments */
+
+mkwrap_init(int, vfscanf,
+	(FILE *restrict stream, const char *restrict format, va_list ap),
+	(FILE *restrict, const char *restrict, va_list));
+
+int fscanf(FILE *restrict stream, const char *restrict format, ...)
+{
+	int r;
+	va_list arguments;
+
+	if (_fiu_called) {
+		if (_fiu_orig_vfscanf == NULL) {
+			if (_fiu_in_init_vfscanf) {
+				printd("fail on init\n");
+				return EOF;
+			} else {
+				printd("get orig\n");
+				_fiu_init_vfscanf();
+			}
+		}
+		printd("orig\n");
+		va_start(arguments, format);
+		r = (*_fiu_orig_vfscanf) (stream, format, arguments);
+		va_end(arguments);
+
+		return r;
+	}
+
+	printd("fiu\n");
+
+	/* fiu_fail() may call anything */
+	rec_inc();
+
+	static const int valid_errnos[] = {
+	  #ifdef EAGAIN
+		EAGAIN,
+	  #endif
+	  #ifdef EBADF
+		EBADF,
+	  #endif
+	  #ifdef EINTR
+		EINTR,
+	  #endif
+	  #ifdef EIO
+		EIO,
+	  #endif
+	  #ifdef ENOMEM
+		ENOMEM,
+	  #endif
+	  #ifdef ENXIO
+		ENXIO,
+	  #endif
+	  #ifdef EOVERFLOW
+		EOVERFLOW,
+	  #endif
+	  #ifdef EILSEQ
+		EILSEQ,
+	  #endif
+	  #ifdef EINVAL
+		EINVAL,
+	  #endif
+	};
+
+	const int fstatus = fiu_fail("posix/stdio/sp/fscanf");
+	if (fstatus != 0) {
+		void *finfo = fiu_failinfo();
+		if (finfo == NULL) {
+			errno = valid_errnos[random() %
+				sizeof(valid_errnos) / sizeof(int)];
+		} else {
+			errno = (long) finfo;
+		}
+		r = EOF;
+		printd("failing\n");
+		set_ferror(stream);
+		goto exit;
+	}
+
+	if (_fiu_orig_vfscanf == NULL)
+		_fiu_init_vfscanf();
+
+	printd("calling orig\n");
+	va_start(arguments, format);
+	r = (*_fiu_orig_vfscanf) (stream, format, arguments);
+	va_end(arguments);
+
+exit:
+	rec_dec();
+	return r;
+}
+
+
+/* Wrapper for scanf(), we can't generate it because it has a variable
+ * number of arguments */
+
+mkwrap_init(int, vscanf,
+	(const char *restrict format, va_list ap),
+	(const char *restrict, va_list));
+
+int scanf(const char *restrict format, ...)
+{
+	int r;
+	va_list arguments;
+
+	if (_fiu_called) {
+		if (_fiu_orig_vscanf == NULL) {
+			if (_fiu_in_init_vscanf) {
+				printd("fail on init\n");
+				return EOF;
+			} else {
+				printd("get orig\n");
+				_fiu_init_vscanf();
+			}
+		}
+		printd("orig\n");
+		va_start(arguments, format);
+		r = (*_fiu_orig_vscanf) (format, arguments);
+		va_end(arguments);
+
+		return r;
+	}
+
+	printd("fiu\n");
+
+	/* fiu_fail() may call anything */
+	rec_inc();
+
+	static const int valid_errnos[] = {
+	  #ifdef EAGAIN
+		EAGAIN,
+	  #endif
+	  #ifdef EBADF
+		EBADF,
+	  #endif
+	  #ifdef EINTR
+		EINTR,
+	  #endif
+	  #ifdef EIO
+		EIO,
+	  #endif
+	  #ifdef ENOMEM
+		ENOMEM,
+	  #endif
+	  #ifdef ENXIO
+		ENXIO,
+	  #endif
+	  #ifdef EOVERFLOW
+		EOVERFLOW,
+	  #endif
+	  #ifdef EILSEQ
+		EILSEQ,
+	  #endif
+	};
+
+	const int fstatus = fiu_fail("posix/stdio/sp/scanf");
+	if (fstatus != 0) {
+		void *finfo = fiu_failinfo();
+		if (finfo == NULL) {
+			errno = valid_errnos[random() %
+				sizeof(valid_errnos) / sizeof(int)];
+		} else {
+			errno = (long) finfo;
+		}
+		r = EOF;
+		printd("failing\n");
+		set_ferror(stdin);
+		goto exit;
+	}
+
+	if (_fiu_orig_vscanf == NULL)
+		_fiu_init_vscanf();
+
+	printd("calling orig\n");
+	va_start(arguments, format);
+	r = (*_fiu_orig_vscanf) (format, arguments);
+	va_end(arguments);
+
+exit:
+	rec_dec();
+	return r;
+}
diff --git a/preload/posix/modules/posix.stdio.mod b/preload/posix/modules/posix.stdio.mod
new file mode 100644
index 0000000..61e9567
--- /dev/null
+++ b/preload/posix/modules/posix.stdio.mod
@@ -0,0 +1,232 @@
+
+# Posix stdio.h I/O
+
+include: <stdio.h>
+include: <errno.h>
+include: <stdarg.h>
+
+fiu name base: posix/stdio/oc/
+
+FILE *fopen(const char *pathname, const char *mode);
+	on error: NULL
+	valid errnos: EACCES EINTR EISDIR ELOOP EMFILE ENAMETOOLONG ENFILE ENOENT ENOTDIR ENOSPC ENXIO EOVERFLOW EROFS EINVAL ENOMEM ETXTBSY
+	variants: off64_t
+
+FILE *freopen(const char *pathname, const char *mode, FILE *stream);
+	on error: NULL
+	valid errnos: EACCES EBADF EINTR EISDIR ELOOP EMFILE ENAMETOOLONG ENFILE ENOENT ENOTDIR ENOSPC ENXIO EOVERFLOW EROFS EBADF EINVAL ENOMEM ENXIO ETXTBSY
+	variants: off64_t
+
+int fclose(FILE *stream);
+	on error: EOF
+	valid errnos: EAGAIN EBADF EFBIG EFBIG EINTR EIO ENOMEM ENOSPC EPIPE ENXIO
+
+FILE *fdopen(int fd, const char *mode);
+	on error: NULL
+	valid errnos: EMFILE EBADF EINVAL EMFILE ENOMEM
+
+FILE *fmemopen(void *restrict buf, size_t size, const char *restrict mode);
+	on error: NULL
+	valid errnos: EMFILE EINVAL ENOMEM
+
+FILE *open_memstream(char **bufp, size_t *sizep);
+	on error: NULL
+	valid errnos: EMFILE EINVAL ENOMEM
+
+FILE *popen(const char *command, const char *mode);
+	on error: NULL
+	valid errnos: EMFILE EINVAL ENOMEM EAGAIN ENFILE
+
+int pclose(FILE *stream);
+	on error: -1
+	valid errnos: ECHILD
+
+
+fiu name base: posix/stdio/tmp/
+
+FILE *tmpfile(void);
+	on error: NULL
+	valid errnos: EINTR EMFILE ENFILE ENOSPC EOVERFLOW ENOMEM
+	variants: off64_t
+
+char *tmpnam(char *s);
+	on error: NULL
+
+char *tempnam(const char *dir, const char *pfx);
+	on error: NULL
+	valid errnos: ENOMEM
+
+fiu name base: posix/stdio/rw/
+
+size_t fread(void *restrict ptr, size_t size, size_t nitems, FILE *restrict stream);
+	on error: 0
+	valid errnos: EAGAIN EBADF EINTR EIO EOVERFLOW ENOMEM ENXIO
+	ferror: stream
+
+size_t fwrite(const void *restrict ptr, size_t size, size_t nitems, FILE *restrict stream);
+	on error: 0
+	valid errnos: EAGAIN EBADF EFBIG EINTR EIO ENOSPC EPIPE ENOMEM ENXIO
+	ferror: stream
+
+fiu name base: posix/stdio/seek/
+
+int fgetpos(FILE *restrict stream, fpos_t *restrict pos);
+	on error: -1
+	valid errnos: EBADF EOVERFLOW ESPIPE
+
+long ftell(FILE *stream);
+	on error: -1
+	valid errnos: EBADF EOVERFLOW ESPIPE
+
+off_t ftello(FILE *stream);
+	on error: -1
+	valid errnos: EBADF EOVERFLOW ESPIPE
+	variants: off64_t
+
+int fseek(FILE *stream, long int offset, int whence);
+	on error: -1
+	valid errnos: EAGAIN EBADF EFBIG EINTR EINVAL EIO ENOSPC EOVERFLOW EPIPE ENXIO
+	ferror: stream
+
+int fseeko(FILE *stream, off_t offset, int whence);
+	on error: -1
+	valid errnos: EAGAIN EBADF EFBIG EINTR EINVAL EIO ENOSPC EOVERFLOW EPIPE ENXIO
+	ferror: stream
+	variants: off64_t
+
+int fsetpos(FILE *stream, const fpos_t *pos);
+	on error: -1
+	valid errnos: EAGAIN EBADF EFBIG EINTR EIO ENOSPC EOVERFLOW EPIPE ENXIO
+	ferror: stream
+	variants: off64_t
+
+# void rewind(FILE *stream);
+#	valid errnos: EAGAIN EBADF EFBIG EINTR EINVAL EIO ENOSPC EOVERFLOW EPIPE ENXIO
+#	ferror: stream
+
+
+fiu name base: posix/stdio/gp/
+
+int fgetc(FILE *stream);
+	on error: EOF
+	valid errnos: EAGAIN EBADF EINTR EIO ENOMEM ENXIO EOVERFLOW
+	ferror: stream
+
+char *fgets(char *restrict s, int n, FILE *restrict stream);
+	on error: NULL
+	valid errnos: EAGAIN EBADF EINTR EIO ENOMEM ENXIO EOVERFLOW
+	ferror: stream
+
+int getc(FILE *stream);
+	on error: EOF
+	valid errnos: EAGAIN EBADF EINTR EIO ENOMEM ENXIO EOVERFLOW
+	ferror: stream
+
+int getchar(void);
+	on error: EOF
+	valid errnos: EAGAIN EBADF EINTR EIO ENOMEM ENXIO EOVERFLOW
+	ferror: stdin
+
+char *gets(char *s);
+	on error: NULL
+	valid errnos: EAGAIN EBADF EINTR EIO ENOMEM ENXIO EOVERFLOW
+	ferror: stdin
+
+int fputc(int c, FILE *stream);
+	on error: EOF
+	valid errnos: EAGAIN EBADF EFBIG EINTR EIO ENOMEM ENOSPC ENXIO EPIPE
+	ferror: stream
+
+int fputs(const char *restrict s, FILE *restrict stream);
+	on error: EOF
+	valid errnos: EAGAIN EBADF EFBIG EINTR EIO ENOMEM ENOSPC ENXIO EPIPE
+	ferror: stream
+
+#ifndef putc
+int putc(int c, FILE *stream);
+	on error: EOF
+	valid errnos: EAGAIN EBADF EFBIG EINTR EIO ENOMEM ENOSPC ENXIO EPIPE
+	ferror: stream
+#endif
+
+int putchar(int c);
+	on error: EOF
+	valid errnos: EAGAIN EBADF EFBIG EINTR EIO ENOMEM ENOSPC ENXIO EPIPE
+	ferror: stdout
+
+int puts(const char *s);
+	on error: EOF
+	valid errnos: EAGAIN EBADF EFBIG EINTR EIO ENOMEM ENOSPC ENXIO EPIPE
+	ferror: stdout
+
+int ungetc(int c, FILE *stream);
+	on error: EOF
+	ferror: stream
+
+ssize_t getdelim(char **restrict lineptr, size_t *restrict n, int delimiter, FILE *restrict stream);
+	on error: -1
+	valid errnos: EAGAIN EBADF EINTR EIO ENOMEM ENXIO EOVERFLOW INVAL
+	ferror: stream
+
+ssize_t getline(char **restrict lineptr, size_t *restrict n, FILE *restrict stream);
+	on error: -1
+	valid errnos: EAGAIN EBADF EINTR EIO ENOMEM ENXIO EOVERFLOW INVAL
+	ferror: stream
+
+
+fiu name base: posix/stdio/sp/
+
+# Variants with a variable number of arguments need a custom definition.
+
+# int fprintf(FILE *restrict stream, const char *restrict format, ...);
+# int printf(const char *restrict format, ...);
+# int dprintf(int fildes, const char *restrict format, ...);
+
+int vfprintf(FILE *restrict stream, const char *restrict format, va_list ap);
+	on error: -1
+	valid errnos: EAGAIN EBADF EFBIG EINTR EIO ENOMEM ENOSPC ENXIO EPIPE EILSEQ EOVERFLOW
+	ferror: stream
+
+int vprintf(const char *restrict format, va_list ap);
+	on error: -1
+	valid errnos: EAGAIN EBADF EFBIG EINTR EIO ENOMEM ENOSPC ENXIO EPIPE EILSEQ EOVERFLOW
+	ferror: stdout
+
+int vdprintf(int fildes, const char *restrict format, va_list ap);
+	on error: -1
+	valid errnos: EAGAIN EBADF EFBIG EINTR EIO ENOMEM ENOSPC ENXIO EPIPE EILSEQ EOVERFLOW
+
+# int fscanf(FILE *restrict stream, const char *restrict format, ...);
+# int scanf(const char *restrict format, ...);
+
+int vfscanf(FILE *restrict stream, const char *restrict format, va_list arg);
+	on error: EOF
+	valid errnos: EAGAIN EBADF EINTR EIO ENOMEM ENXIO EOVERFLOW EILSEQ EINVAL
+	ferror: stream
+
+int vscanf(const char *restrict format, va_list arg);
+	on error: EOF
+	valid errnos: EAGAIN EBADF EINTR EIO ENOMEM ENXIO EOVERFLOW EILSEQ
+	ferror: stdin
+
+fiu name base: posix/stdio/
+
+# Other functions not worth categorizing
+
+int remove(const char *filename);
+	on error: -1
+	valid errnos: EACCES EBUSY EEXIST ENOTEMPTY EINVAL EIO ELOOP ENAMETOOLONG ENOENT ENOTDIR EPERM EROFS ETXTBSY
+
+int setvbuf(FILE *restrict stream, char *restrict buf, int type, size_t size);
+	on error: EOF
+	valid errnos: EBADF
+
+int ftrylockfile(FILE *file);
+	on error: 1
+
+int fflush(FILE *stream);
+	on error: EOF
+	valid errnos: EAGAIN EBADF EFBIG EINTR EIO ENOMEM ENOSPC EPIPE ENXIO
+	ferror: stream
+
+