 .gitignore                            |   1 +
 Makefile                              |   2 +-
 bindings/python/fiu.py                |   5 +
 bindings/python/fiu_ll.c              |  12 +
 bindings/python/setup.py              |  11 +-
 libfiu/Makefile                       |   2 +-
 libfiu/fiu-rc.c                       |  19 +-
 libfiu/fiu.c                          |  20 ++
 libfiu/fiu.h                          |  16 ++
 libfiu/hash.c                         | 116 +++++---
 libfiu/hash.h                         |   1 +
 libfiu/symbols.map                    |   1 +
 preload/posix/Makefile                |  17 +-
 preload/posix/build-libccheck.c       |   8 +
 preload/posix/codegen.h               |  29 +-
 preload/posix/generate                |  21 +-
 preload/posix/modules/posix.custom.c  | 484 +++++++++++++++++++++++++++++++++-
 preload/posix/modules/posix.stdio.mod | 233 ++++++++++++++++
 preload/run/Makefile                  |   1 +
 tests/Makefile                        |  11 +-
 tests/generated/tests/fopen.conf      |   9 +
 tests/generated/tests/fprintf.conf    |  10 +
 tests/generated/tests/fread.conf      |  10 +
 tests/test-ferror.c                   |  67 +++++
 tests/test-set_prng_seed-env.py       |  17 ++
 tests/test-set_prng_seed.py           |  23 ++
 tests/utils/Makefile                  |   1 -
 27 files changed, 1081 insertions(+), 66 deletions(-)

diff --git a/.gitignore b/.gitignore
index b365210..b80b6b7 100644
--- a/.gitignore
+++ b/.gitignore
@@ -36,3 +36,4 @@ tests/generated/tests/*.[oc]
 .*.swp
 *.gcno
 *.gcda
+.*
diff --git a/Makefile b/Makefile
index 6749813..cd4e81c 100644
--- a/Makefile
+++ b/Makefile
@@ -80,7 +80,7 @@ python3_install: python3
 	cd bindings/python && python3 setup.py install
 
 python_clean:
-	cd bindings/python && rm -rf build/
+	cd bindings/python && rm -rf build/ fiu_ctrl.py
 
 
 clean: python_clean preload_clean libfiu_clean utils_clean test_clean
diff --git a/bindings/python/fiu.py b/bindings/python/fiu.py
index 0b0a308..20f4a13 100644
--- a/bindings/python/fiu.py
+++ b/bindings/python/fiu.py
@@ -95,6 +95,11 @@ def disable(name):
 	if r != 0:
 		raise RuntimeError(r)
 
+def set_prng_seed(seed):
+	"""Sets the PRNG seed. Don't use this unless you know what you're
+	doing."""
+	return _ll.set_prng_seed(seed)
+
 def rc_fifo(basename):
 	"""Enables remote control over a named pipe that begins with the given
 	basename. The final path will be "basename-$PID"."""
diff --git a/bindings/python/fiu_ll.c b/bindings/python/fiu_ll.c
index e49874b..85ca93b 100644
--- a/bindings/python/fiu_ll.c
+++ b/bindings/python/fiu_ll.c
@@ -187,6 +187,17 @@ static PyObject *disable(PyObject *self, PyObject *args)
 	return PyLong_FromLong(fiu_disable(name));
 }
 
+static PyObject *set_prng_seed(PyObject *self, PyObject *args)
+{
+	int seed;
+
+	if (!PyArg_ParseTuple(args, "i:set_prng_seed", &seed))
+		return NULL;
+
+	fiu_set_prng_seed(seed);
+	Py_RETURN_NONE;
+}
+
 static PyObject *rc_fifo(PyObject *self, PyObject *args)
 {
 	char *basename;
@@ -207,6 +218,7 @@ static PyMethodDef fiu_methods[] = {
 	{ "enable_stack_by_name", (PyCFunction) enable_stack_by_name,
 		METH_VARARGS, NULL },
 	{ "disable", (PyCFunction) disable, METH_VARARGS, NULL },
+	{ "set_prng_seed", (PyCFunction) set_prng_seed, METH_VARARGS, NULL },
 	{ "rc_fifo", (PyCFunction) rc_fifo, METH_VARARGS, NULL },
 	{ NULL }
 };
diff --git a/bindings/python/setup.py b/bindings/python/setup.py
index 6bb9c0b..963f613 100644
--- a/bindings/python/setup.py
+++ b/bindings/python/setup.py
@@ -1,6 +1,7 @@
 
 import os
 import sys
+import tempfile
 from distutils.core import setup, Extension
 from distutils.command.build_py import build_py
 
@@ -22,12 +23,18 @@ class generate_and_build_py (build_py):
         prefix = os.environ.get('PREFIX', '/usr/local/')
         plibpath = os.environ.get('PLIBPATH', prefix + '/lib/')
 
-        contents = open('fiu_ctrl.in.py', 'r').read()
+        contents = open('fiu_ctrl.in.py', 'rt').read()
         contents = contents.replace('@@PLIBPATH@@', plibpath)
 
-        out = open('fiu_ctrl.py', 'w')
+        # Create/update the file atomically, as this could be invoked in
+        # parallel for python 2 and 3, and we don't want to accidentally use
+        # partially written files.
+        out = tempfile.NamedTemporaryFile(
+                mode = 'wt', delete = False,
+                dir = '.', prefix = 'tmp-fiu_ctrl.py')
         out.write(contents)
         out.close()
+        os.rename(out.name, 'fiu_ctrl.py')
 
 
 fiu_ll = Extension("fiu_ll",
diff --git a/libfiu/Makefile b/libfiu/Makefile
index 050f20b..9fc38a4 100644
--- a/libfiu/Makefile
+++ b/libfiu/Makefile
@@ -38,7 +38,7 @@ else
 	NICE_CC = $(CC)
 endif
 
-LIB_VER=0.96
+LIB_VER=0.97
 LIB_SO_VER=0
 
 
diff --git a/libfiu/fiu-rc.c b/libfiu/fiu-rc.c
index cd45086..913cf6b 100644
--- a/libfiu/fiu-rc.c
+++ b/libfiu/fiu-rc.c
@@ -81,11 +81,12 @@ static int read_line(int fd, char *buf)
  */
 int fiu_rc_string(const char *cmd, char ** const error)
 {
-	char m_cmd[MAX_LINE];
-	char command[MAX_LINE], parameters[MAX_LINE];
+	char m_cmd[MAX_LINE] = {0};
+	char command[MAX_LINE] = {0};
+	char parameters[MAX_LINE] = {0};
 
 	/* We need a version of cmd we can write to for parsing */
-	strncpy(m_cmd, cmd, MAX_LINE);
+	strncpy(m_cmd, cmd, MAX_LINE - 1);
 
 	/* Separate command and parameters */
 	{
@@ -96,14 +97,14 @@ int fiu_rc_string(const char *cmd, char ** const error)
 			*error = "Cannot get command";
 			return -1;
 		}
-		strncpy(command, tok, MAX_LINE);
+		strncpy(command, tok, MAX_LINE - 1);
 
 		tok = strtok_r(NULL, " \t", &state);
 		if (tok == NULL) {
 			*error = "Cannot get parameters";
 			return -1;
 		}
-		strncpy(parameters, tok, MAX_LINE);
+		strncpy(parameters, tok, MAX_LINE - 1);
 	}
 
 	/* Parsing of parameters.
@@ -224,9 +225,9 @@ static int rc_do_command(int fdr, int fdw)
  * removed. If the process forks, a new pipe will be created.
  */
 
-static char npipe_basename[PATH_MAX];
-static char npipe_path_in[PATH_MAX];
-static char npipe_path_out[PATH_MAX];
+static char npipe_basename[PATH_MAX] = {0};
+static char npipe_path_in[PATH_MAX] = {0};
+static char npipe_path_out[PATH_MAX] = {0};
 
 static void *rc_fifo_thread(void *unused)
 {
@@ -328,7 +329,7 @@ int fiu_rc_fifo(const char *basename)
 	if (r < 0)
 		return r;
 
-	strncpy(npipe_basename, basename, PATH_MAX);
+	strncpy(npipe_basename, basename, PATH_MAX - 1);
 	pthread_atfork(NULL, NULL, fifo_atfork_child);
 
 	return r;
diff --git a/libfiu/fiu.c b/libfiu/fiu.c
index 4f9de64..df81669 100644
--- a/libfiu/fiu.c
+++ b/libfiu/fiu.c
@@ -191,11 +191,17 @@ static int should_stack_fail(struct pf_info *pf)
  * To seed it, we use the current microseconds. To prevent seed reuse, we
  * re-seed after each fork (see atfork_child()). */
 static unsigned int randd_xn = 0xA673F42D;
+static bool randd_xn_manual = false;
 
 static void prng_seed(void)
 {
 	struct timeval tv;
 
+	/* If the seed is being handled manually, don't interfere. */
+	if (randd_xn_manual) {
+		return;
+	}
+
 	gettimeofday(&tv, NULL);
 
 	randd_xn = tv.tv_usec;
@@ -225,6 +231,8 @@ static void atfork_child(void)
  * time without clashes. */
 int fiu_init(unsigned int flags)
 {
+	char *static_seed_from_env;
+
 	/* Used to avoid re-initialization, protected by enabled_fails_lock */
 	static int initialized = 0;
 
@@ -246,6 +254,11 @@ int fiu_init(unsigned int flags)
 		return -1;
 	}
 
+	static_seed_from_env = getenv("FIU_PRNG_SEED");
+	if (static_seed_from_env != NULL) {
+		fiu_set_prng_seed(atoi(static_seed_from_env));
+	}
+
 	prng_seed();
 
 	initialized = 1;
@@ -255,6 +268,13 @@ int fiu_init(unsigned int flags)
 	return 0;
 }
 
+/* Sets the PRNG seed. */
+void fiu_set_prng_seed(unsigned int seed)
+{
+	randd_xn = seed;
+	randd_xn_manual = true;
+}
+
 /* Returns the failure status of the given name. Must work well even before
  * fiu_init() is called assuming no points of failure are enabled; although it
  * can (and does) assume fiu_init() will be called before enabling any. */
diff --git a/libfiu/fiu.h b/libfiu/fiu.h
index 7659b7f..8dd6bb7 100644
--- a/libfiu/fiu.h
+++ b/libfiu/fiu.h
@@ -30,6 +30,21 @@ extern "C" {
  */
 int fiu_init(unsigned int flags);
 
+/** Sets the PRNG seed.
+ *
+ * This function is called if you want to manually set the seed used to
+ * generate random numbers.  This allows more control over randomized tests.
+ *
+ * Must be called before fiu_init(), or at fork. Otherwise, races might occur
+ * as this function is not threadsafe wrt. other fiu functions.
+ *
+ * There's no need to call this function for normal operations. Don't use it
+ * unless you know what you're doing.
+ *
+ * @param seed  PRNG seed to use.
+ */
+void fiu_set_prng_seed(unsigned int seed);
+
 /** Returns the failure status of the given point of failure.
  *
  * @param name  Point of failure name.
@@ -73,6 +88,7 @@ void *fiu_failinfo(void);
  * don't include it to avoid a circular dependency. */
 
 #define fiu_init(flags) 0
+#define fiu_set_prng_seed(seed)
 #define fiu_fail(name) 0
 #define fiu_failinfo() NULL
 #define fiu_do_on(name, action)
diff --git a/libfiu/hash.c b/libfiu/hash.c
index e8b037d..45a9b9b 100644
--- a/libfiu/hash.c
+++ b/libfiu/hash.c
@@ -80,6 +80,7 @@ struct hash {
 	struct entry *entries;
 	size_t table_size;
 	size_t nentries;
+	size_t nremoved;
 	void (*destructor)(void *);
 };
 
@@ -109,6 +110,7 @@ struct hash *hash_create(void (*destructor)(void *))
 
 	h->table_size = MIN_SIZE;
 	h->nentries = 0;
+	h->nremoved = 0;
 
 	if (destructor == NULL)
 		destructor = dumb_destructor;
@@ -142,7 +144,7 @@ void *hash_get(struct hash *h, const char *key)
 
 	pos = murmurhash2(key, strlen(key)) % h->table_size;
 
-	for (;;) {
+	for (int i = 0; i < h->table_size; i++) {
 		entry = h->entries + pos;
 		if (entry->in_use == NEVER) {
 			/* We got to a entry never used, no match. */
@@ -151,12 +153,16 @@ void *hash_get(struct hash *h, const char *key)
 				strcmp(key, entry->key) == 0) {
 			/* The key matches. */
 			return entry->value;
-		} else {
-			/* We use linear probing for now */
-			pos = (pos + 1) % h->table_size;
 		}
+
+		/* The key doesn't match this entry, continue with linear probing. */
+		pos = (pos + 1) % h->table_size;
 	}
 
+	/* We went through the entire table and did not find the key.
+	 * Note this is a pathological case that we don't expect would happen
+	 * under normal operation, since we resize the table so there are
+	 * always some never-used spaces. */
 	return NULL;
 }
 
@@ -170,25 +176,33 @@ static bool _hash_set(struct hash *h, char *key, void *value)
 
 	pos = murmurhash2(key, strlen(key)) % h->table_size;
 
-	for (;;) {
+	for (int i = 0; i < h->table_size; i++) {
 		entry = h->entries + pos;
-		if (entry->in_use != IN_USE) {
+		if (entry->in_use == NEVER) {
+			/* Use only fresh entries, reusing removed ones
+			 * introduces significant complications when combined
+			 * with the linear probing. */
 			entry->in_use = IN_USE;
 			entry->key = key;
 			entry->value = value;
 			h->nentries++;
 			return true;
-		} else if (strcmp(key, entry->key) == 0) {
+		} else if (entry->in_use == IN_USE &&
+				strcmp(key, entry->key) == 0) {
 			/* The key matches, override the value. */
 			h->destructor(entry->value);
 			entry->value = value;
 			return true;
-		} else {
-			/* The key doesn't match, linear probing. */
-			pos = (pos + 1) % h->table_size;
 		}
+
+		/* The key doesn't match this entry, continue with linear probing. */
+		pos = (pos + 1) % h->table_size;
 	}
 
+	/* We went through the entire table and did not find the key.
+	 * Note this is a pathological case that we don't expect would happen
+	 * under normal operation, since we resize the table so there are
+	 * always some never-used spaces. */
 	return false;
 }
 
@@ -198,9 +212,10 @@ static bool resize_table(struct hash *h, size_t new_size)
 	struct entry *old_entries, *e;
 	size_t old_size;
 
+	/* Do not resize below minimum size; however, continue anyway to clear
+	 * up removed entries. */
 	if (new_size < MIN_SIZE) {
-		/* Do not resize below minimum size */
-		return true;
+		new_size = MIN_SIZE;
 	}
 
 	old_entries = h->entries;
@@ -213,6 +228,7 @@ static bool resize_table(struct hash *h, size_t new_size)
 	memset(h->entries, 0, sizeof(struct entry) * new_size);
 	h->table_size = new_size;
 	h->nentries = 0;
+	h->nremoved = 0;
 
 	/* Insert the old entries into the new table. We use the internal
 	 * version _hash_set() to avoid copying the keys again. */
@@ -227,26 +243,53 @@ static bool resize_table(struct hash *h, size_t new_size)
 	return true;
 }
 
+static bool auto_resize_table(hash_t *h) {
+	/* Slots in the table is taken by valid and removed entries.
+	 * Since we don't reuse removed slots, we need to also take them into
+	 * consideration and shrink if we are accumulating too many.
+	 *
+	 * This function always resizes to <number of entries> * 2, which
+	 * preserves the "at least 30% usable" property both when growing and
+	 * shrinking, and makes calculations simpler.
+	 */
+
+	/* Maintain at least 30% usable entries. */
+	float usable_ratio = (float) (h->table_size - h->nentries - h->nremoved)
+		/ h->table_size;
+	if (usable_ratio < 0.3) {
+		return resize_table(h, h->nentries * 2);
+	}
+
+	/* If we have less than 30% used (by valid entries), compact. */
+	float entries_ratio = (float) h->nentries / h->table_size;
+	if (h->table_size > MIN_SIZE && entries_ratio < 0.3) {
+		return resize_table(h, h->nentries * 2);
+	}
+
+	return true;
+}
+
+
 bool hash_set(struct hash *h, const char *key, void *value)
 {
-	if ((float) h->nentries / h->table_size > 0.7) {
-		/* If we're over 70% full, grow the table by 30% */
-		if (!resize_table(h, h->table_size * 1.3))
-			return false;
+	bool r = _hash_set(h, strdup(key), value);
+
+	if (!auto_resize_table(h)) {
+		return false;
 	}
 
-	return _hash_set(h, strdup(key), value);
+	return r;
 }
 
-
 bool hash_del(struct hash *h, const char *key)
 {
 	size_t pos;
 	struct entry *entry;
+	bool found = false;
 
 	pos = murmurhash2(key, strlen(key)) % h->table_size;
 
-	for (;;) {
+	for (int i = 0; i < h->table_size; i++) {
 		entry = h->entries + pos;
 		if (entry->in_use == NEVER) {
 			/* We got to a never used key, not found. */
@@ -254,29 +297,34 @@ bool hash_del(struct hash *h, const char *key)
 		} else if (entry->in_use == IN_USE &&
 				strcmp(key, entry->key) == 0) {
 			/* The key matches, remove it. */
-			free(entry->key);
-			h->destructor(entry->value);
-			entry->key = NULL;
-			entry->value = NULL;
-			entry->in_use = REMOVED;
+			found = true;
 			break;
-		} else {
-			/* The key doesn't match, linear probing. */
-			pos = (pos + 1) % h->table_size;
 		}
+
+		/* The key doesn't match this entry, continue with linear probing. */
+		pos = (pos + 1) % h->table_size;
 	}
 
-	if (h->table_size > MIN_SIZE &&
-			(float) h->nentries / h->table_size < 0.5) {
-		/* If we're over 50% free, shrink. */
-		if (!resize_table(h, h->table_size * 0.8))
-			return false;
+	if (!found) {
+		return false;
 	}
 
-	return true;
+	/* Remove the entry */
+	free(entry->key);
+	h->destructor(entry->value);
+	entry->key = NULL;
+	entry->value = NULL;
+	entry->in_use = REMOVED;
+	h->nentries--;
+	h->nremoved++;
 
+	if (!auto_resize_table(h))
+		return false;
+
+	return true;
 }
 
+
 /* Generic, simple cache.
  *
  * It is implemented using a hash table and manipulating it directly when
@@ -436,7 +484,7 @@ bool cache_del(struct cache *c, const char *key)
 		free(e->key);
 		e->key = NULL;
 		e->value = NULL;
-		e->in_use = REMOVED;
+		e->in_use = NEVER;
 		pthread_rwlock_unlock(&c->lock);
 		return true;
 	}
diff --git a/libfiu/hash.h b/libfiu/hash.h
index edd5f6d..fb88c03 100644
--- a/libfiu/hash.h
+++ b/libfiu/hash.h
@@ -5,6 +5,7 @@
 /* Generic hash table. See hash.c for more information. */
 
 #include <sys/types.h>		/* for size_t */
+#include <stdbool.h>		/* for bool */
 #include <stdint.h>		/* for int64_t */
 
 typedef struct hash hash_t;
diff --git a/libfiu/symbols.map b/libfiu/symbols.map
index 204acc1..bae73d5 100644
--- a/libfiu/symbols.map
+++ b/libfiu/symbols.map
@@ -10,6 +10,7 @@
 		fiu_fail;
 		fiu_failinfo;
 		fiu_init;
+		fiu_set_prng_seed;
 		fiu_rc_fifo;
 		fiu_rc_string;
 
diff --git a/preload/posix/Makefile b/preload/posix/Makefile
index d63ef5d..6ef224b 100644
--- a/preload/posix/Makefile
+++ b/preload/posix/Makefile
@@ -81,8 +81,12 @@ build-needlibdl:
 # ia64 and alpha use libc.so.6.1), so find which one to use at build-time.
 # Please note that the argument to ldd *must* start with "./", otherwise some
 # "ldd"s won't work (for example, the one in FreeBSD 8.1).
+# To find out the soname, we build a dummy shared library against the libc,
+# and use ldd to extract the version. This shared library must actually use
+# something from libc, otherwise a smart compiler/linker (such as one using
+# --as-needed) might not link it against libc.
 build-libcsoname:
-	@$(CC) -x c /dev/null -lc -shared -o build-libccheck.so
+	@$(CC) build-libccheck.c -lc -shared -fPIC -o build-libccheck.so
 	@ldd ./build-libccheck.so | grep libc.so | awk '{ print $$1 }' > $@
 	@rm build-libccheck.so
 	@test "`cat $@`" != "" || \
@@ -94,17 +98,16 @@ build-env.h: build-env.h.in build-libcsoname
 		> build-env.h
 
 
-fiu_posix_preload.so: build-flags build-env.h build-needlibdl $(OBJS)
-	$(NICE_CC) $(ALL_CFLAGS) -shared -fPIC $(OBJS) \
+fiu_posix_preload.so: build-flags build-env.h build-needlibdl \
+		$(OBJS) ../../libfiu/hash.c
+	$(NICE_CC) $(ALL_CFLAGS) -shared -fPIC $(OBJS) ../../libfiu/hash.c \
 		-L../../libfiu/ \
 		-lfiu `cat build-needlibdl` \
 		-o fiu_posix_preload.so
 
 
-# this should only be needed when building the function list and not the
-# preload library
-%.mod.fl: %.mod
-	$(NICE_GEN) $< $<.c $@
+# A module's function list gets generated with the .c file.
+%.mod.fl: %.mod.c ;
 
 function_list: $(GEN_FL) function_list.in
 	@echo "  function_list"
diff --git a/preload/posix/build-libccheck.c b/preload/posix/build-libccheck.c
new file mode 100644
index 0000000..41fad24
--- /dev/null
+++ b/preload/posix/build-libccheck.c
@@ -0,0 +1,8 @@
+/* Dummy file used at build time to find out libc's soname.
+ * It must use something from libc. See the Makefile for details. */
+
+#include <stdio.h>
+
+int use_printf(void) {
+	return printf("I'm using libc\n");
+}
diff --git a/preload/posix/codegen.h b/preload/posix/codegen.h
index 9e8cb3a..5ca27a8 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) \
 	{							\
@@ -92,7 +95,7 @@ void *libc_symbol(const char *symbol);
 	}
 
 /* Generates the definition part of the wrapped function. */
-#define mkwrap_def(RTYPE, NAME, PARAMS, PARAMST) \
+#define mkwrap_def(RTYPE, NAME, PARAMS) \
 	RTYPE NAME PARAMS					\
 	{ 							\
 		RTYPE r;					\
@@ -122,7 +125,7 @@ void *libc_symbol(const char *symbol);
 /* Generates the common top for most functions (init + def + body called) */
 #define mkwrap_top(RTYPE, NAME, PARAMS, PARAMSN, PARAMST, ON_ERR) \
 	mkwrap_init(RTYPE, NAME, PARAMS, PARAMST) \
-	mkwrap_def(RTYPE, NAME, PARAMS, PARAMST) \
+	mkwrap_def(RTYPE, NAME, PARAMS) \
 	mkwrap_body_called(NAME, PARAMSN, ON_ERR)
 
 /* Generates the body of the function for normal, non-errno usage. The return
@@ -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..e6eea8f 100644
--- a/preload/posix/modules/posix.custom.c
+++ b/preload/posix/modules/posix.custom.c
@@ -4,14 +4,18 @@
  */
 
 #include "codegen.h"
+#include "hash.h"
 
-
+#include <inttypes.h>
 #include <sys/types.h>
 #include <unistd.h>
 #include <sys/stat.h>
 #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 +40,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 +184,479 @@ mkwrap_body_errno("posix/io/oc/open", -1)
 mkwrap_bottom(open64, (pathname, flags, mode))
 
 #endif
+
+/*
+ * To simulate ferror() properly, we need to keep track of which FILE * have
+ * we returned a failure for.
+ *
+ * To do that, we keep a set of FILE *. This set is implemented using a hash
+ * table, for convenience.
+ *
+ * An alternative would be to have a static array, but then lookups would
+ * scale linearly with the number of files we're expected to fail, and (most
+ * importantly) require a new data type just for this.
+ */
+static hash_t *ferror_hash_table;
+static pthread_mutex_t ferror_hash_table_mutex = PTHREAD_MUTEX_INITIALIZER;
+
+static void constructor_attr(200) _fiu_init_ferror_hash_table(void)
+{
+	rec_inc();
+	pthread_mutex_lock(&ferror_hash_table_mutex);
+
+	// Create the hash table. No need for a value destructor, as our values
+	// are not pointers but static values.
+	ferror_hash_table = hash_create(NULL);
+
+	pthread_mutex_unlock(&ferror_hash_table_mutex);
+	rec_dec();
+}
+
+/* Our hash table uses character keys, to convert stream to keys we just get
+ * the hexadecimal representation via PRIxPTR (%p is implementation
+ * dependent). */
+#define STREAM_KEY_SIZE (sizeof(uintptr_t) * 2 + 1)
+
+static void stream_to_key(void *stream, char key[STREAM_KEY_SIZE])
+{
+	snprintf(key, STREAM_KEY_SIZE, "%" PRIxPTR, (uintptr_t) stream);
+}
+
+void set_ferror(void * stream)
+{
+	char key[STREAM_KEY_SIZE];
+	stream_to_key(stream, key);
+
+	pthread_mutex_lock(&ferror_hash_table_mutex);
+
+	// Use a dummy the value; we don't care about it, we only need to be able to
+	// distinguish it from NULL.
+	hash_set(ferror_hash_table, key, (void *) 0xDEAD);
+
+	pthread_mutex_unlock(&ferror_hash_table_mutex);
+}
+
+static int get_ferror(void * stream)
+{
+	char key[STREAM_KEY_SIZE];
+	stream_to_key(stream, key);
+
+	pthread_mutex_lock(&ferror_hash_table_mutex);
+
+	void *value = hash_get(ferror_hash_table, key);
+
+	pthread_mutex_unlock(&ferror_hash_table_mutex);
+
+	return value != NULL;
+}
+
+static void clear_ferror(void * stream)
+{
+	char key[STREAM_KEY_SIZE];
+	stream_to_key(stream, key);
+
+	pthread_mutex_lock(&ferror_hash_table_mutex);
+
+	hash_del(ferror_hash_table, key);
+
+	pthread_mutex_unlock(&ferror_hash_table_mutex);
+}
+
+
+/* Wrapper for ferror() */
+mkwrap_top(int, ferror, (FILE *stream), (stream), (FILE *), 1)
+mkwrap_body_hardcoded("posix/stdio/error/ferror", 1)
+
+	/* The following is like mkwrap_bottom(), but has an additional check to
+	 * return 1 if we previously returned an error for this file, so the
+	 * semantics are consistent. */
+	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");
+		r = 1;
+	}
+
+exit:
+	rec_dec();
+	return r;
+}
+
+
+/* Custom wrapper for clearerr().
+ * This function does not fail as such, but we override it so we can provide a
+ * consistent view. */
+mkwrap_init(void, clearerr, (FILE *stream), (FILE *))
+
+void clearerr (FILE *stream)
+{
+	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();
+}
+
+
+/* Custom wrapper for fclose() that clears ferror_hash_table. */
+mkwrap_top(int , fclose, (FILE *stream), (stream), (FILE *), (EOF))
+	static const int valid_errnos[] = {
+	  #ifdef EAGAIN
+		EAGAIN,
+	  #endif
+	  #ifdef EBADF
+		EBADF,
+	  #endif
+	  #ifdef EFBIG
+		EFBIG,
+	  #endif
+	  #ifdef EFBIG
+		EFBIG,
+	  #endif
+	  #ifdef EINTR
+		EINTR,
+	  #endif
+	  #ifdef EIO
+		EIO,
+	  #endif
+	  #ifdef ENOMEM
+		ENOMEM,
+	  #endif
+	  #ifdef ENOSPC
+		ENOSPC,
+	  #endif
+	  #ifdef EPIPE
+		EPIPE,
+	  #endif
+	  #ifdef ENXIO
+		ENXIO,
+	  #endif
+	};
+mkwrap_body_errno("posix/stdio/oc/fclose", EOF)
+
+/* We need a custom injection of clear_ferror here to prevent stale entries in
+ * ferror_hash_table. */
+	clear_ferror(stream);
+
+mkwrap_bottom(fclose, (stream))
+
+
+/*
+ * Variadic functions
+ *
+ * For some variadic functions, like printf(), we use a non-variadic
+ * counterpart (vprintf()) to implement the wrapping.
+ *
+ */
+
+/* Generate the body for a variadic function, which relies on a non-variadic
+ * function for expansion.
+ *
+ * We expect mkwrap_init to be called on the non-variadic one, see the calls
+ * below for examples.
+ */
+#define mkwrap_variadic_def_and_body_called( \
+		RTYPE, /* return type */ \
+		NAME, /* variadic name */ \
+		NVNAME, /* non-variadic name */ \
+		PARAMS, /* parameter definition, with types */ \
+		PARAMSN, /* parameter names only, with "arguments" for va part */ \
+		LASTPN, /* last parameter name */ \
+		ON_ERR /* what to return on error */ \
+		) \
+								\
+	RTYPE NAME PARAMS					\
+	{ 							\
+		RTYPE r;					\
+		int fstatus;					\
+		va_list arguments;				\
+								\
+		if (_fiu_called) {				\
+			if (_fiu_orig_##NVNAME == NULL) {	\
+				if (_fiu_in_init_##NVNAME) {	\
+					printd("fail on init\n"); \
+					return ON_ERR;		\
+				} else {			\
+					printd("get orig\n");	\
+					_fiu_init_##NVNAME();	\
+				}				\
+			}					\
+			printd("orig\n");			\
+			va_start(arguments, LASTPN);		\
+			r = (*_fiu_orig_##NVNAME) PARAMSN;	\
+			va_end(arguments);			\
+								\
+			return r;				\
+		}						\
+								\
+		printd("fiu\n");				\
+								\
+		/* fiu_fail() may call anything */		\
+		rec_inc();
+
+
+#define mkwrap_variadic_bottom( \
+		NAME /* variadic name */, \
+		NVNAME /* non-variadic name */, \
+		PARAMSN /* parameter names only, with "arguments" for va part */, \
+		LASTPN /* last parameter name */ \
+		) \
+								\
+		if (_fiu_orig_##NVNAME == NULL)			\
+			_fiu_init_##NVNAME();			\
+								\
+		printd("calling orig\n");			\
+		va_start(arguments, LASTPN);			\
+		r = (*_fiu_orig_##NVNAME) PARAMSN;		\
+		va_end(arguments);				\
+								\
+	exit:							\
+		rec_dec();					\
+		return r;					\
+	}
+
+
+/* fprintf() -> vfprintf() */
+mkwrap_init(int, vfprintf,
+	(FILE *restrict stream, const char *restrict format, va_list ap),
+	(FILE *restrict, const char *restrict, va_list));
+
+mkwrap_variadic_def_and_body_called(int, fprintf, vfprintf,
+	(FILE *restrict stream, const char *restrict format, ...),
+	(stream, format, arguments),
+	format, -1)
+
+	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
+	};
+
+mkwrap_body_errno_ferror("posix/stdio/sp/fprintf", -1, stream)
+mkwrap_variadic_bottom(fprintf, vfprintf, (stream, format, arguments), format)
+
+
+/* printf() -> vprintf() */
+mkwrap_init(int, vprintf,
+	(const char *restrict format, va_list ap),
+	(const char *restrict, va_list));
+
+mkwrap_variadic_def_and_body_called(int, printf, vprintf,
+	(const char *restrict format, ...),
+	(format, arguments),
+	format, -1)
+
+	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
+	};
+
+mkwrap_body_errno_ferror("posix/stdio/sp/printf", -1, stdout)
+mkwrap_variadic_bottom(printf, vprintf, (format, arguments), format)
+
+
+/* dprintf() -> vdprintf() */
+mkwrap_init(int, vdprintf,
+	(int fildes, const char *restrict format, va_list ap),
+	(int, const char *restrict, va_list));
+
+mkwrap_variadic_def_and_body_called(int, dprintf, vdprintf,
+	(int fildes, const char *restrict format, ...),
+	(fildes, format, arguments),
+	format, -1)
+
+	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
+	};
+
+mkwrap_body_errno("posix/stdio/sp/dprintf", -1)
+mkwrap_variadic_bottom(dprintf, vdprintf, (fildes, format, arguments), format)
+
+
+/* fscanf() -> vfscanf() */
+mkwrap_init(int, vfscanf,
+	(FILE *restrict stream, const char *restrict format, va_list ap),
+	(FILE *restrict, const char *restrict, va_list));
+
+mkwrap_variadic_def_and_body_called(int, fscanf, vfscanf,
+	(FILE *restrict stream, const char *restrict format, ...),
+	(stream, format, arguments),
+	format, EOF)
+
+	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
+	};
+
+mkwrap_body_errno_ferror("posix/stdio/sp/fscanf", EOF, stream)
+mkwrap_variadic_bottom(fscanf, vfscanf, (stream, format, arguments), format)
+
+
+/* scanf() -> vscanf() */
+mkwrap_init(int, vscanf,
+	(const char *restrict format, va_list ap),
+	(const char *restrict, va_list));
+
+mkwrap_variadic_def_and_body_called(int, scanf, vscanf,
+	(const char *restrict format, ...),
+	(format, arguments),
+	format, EOF)
+
+	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
+	};
+
+mkwrap_body_errno_ferror("posix/stdio/sp/scanf", EOF, stdin)
+mkwrap_variadic_bottom(scanf, vscanf, (format, arguments), format)
diff --git a/preload/posix/modules/posix.stdio.mod b/preload/posix/modules/posix.stdio.mod
new file mode 100644
index 0000000..3055536
--- /dev/null
+++ b/preload/posix/modules/posix.stdio.mod
@@ -0,0 +1,233 @@
+
+# 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
+
+# This one needs to be further instrumented.
+# 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
+
+
diff --git a/preload/run/Makefile b/preload/run/Makefile
index c0d864c..448413d 100644
--- a/preload/run/Makefile
+++ b/preload/run/Makefile
@@ -84,6 +84,7 @@ uninstall:
 clean:
 	rm -f $(OBJS) fiu_run_preload.so fiu-run build-flags
 	rm -f *.bb *.bbg *.da *.gcov *.gcda *.gcno gmon.out
+	rm -f build-needlibdl
 
 .PHONY: default install uninstall clean .force-build-flags
 
diff --git a/tests/Makefile b/tests/Makefile
index c4a63ed..f0d3c85 100644
--- a/tests/Makefile
+++ b/tests/Makefile
@@ -13,12 +13,15 @@ endif
 
 ifneq ($(V), 1)
 	NICE_CC = @echo "  CC  $@"; $(CC)
-	NICE_RUN = @echo "  RUN $<"; LD_LIBRARY_PATH=../libfiu/
+	NICE_RUN = @echo "  RUN $<"; \
+		   LD_LIBRARY_PATH=../libfiu/ \
+		   LD_PRELOAD=./libs/fiu_run_preload.so:./libs/fiu_posix_preload.so
 	NICE_PY = @echo "  PY  $<"; ./wrap-python 2
 	NICE_LN = @echo "  LN $@"; ln -f
 else
 	NICE_CC = $(CC)
-	NICE_RUN = LD_LIBRARY_PATH=../libfiu/
+	NICE_RUN = LD_LIBRARY_PATH=../libfiu/ \
+		   LD_PRELOAD=./libs/fiu_run_preload.so:./libs/fiu_posix_preload.so
 	NICE_PY = ./wrap-python 2
 	NICE_LN = ln -f
 endif
@@ -67,7 +70,7 @@ test-enable_stac%: test-enable_stac%.c build-flags
 	$(NICE_CC) $(ALL_CFLAGS) \
 		-rdynamic -fno-optimize-sibling-calls $< -lfiu -lpthread -o $@
 
-c-run-%: %
+c-run-%: % lnlibs
 	$(NICE_RUN) ./$<
 
 
@@ -120,6 +123,8 @@ clean:
 	rm -rf libs/ small-cat
 	rm -f *.bb *.bbg *.da *.gcov *.gcda *.gcno gmon.out build-flags
 	$(MAKE) -C generated clean
+	$(MAKE) -C collisions clean
+	$(MAKE) -C utils clean
 
 FORCE:
 
diff --git a/tests/generated/tests/fopen.conf b/tests/generated/tests/fopen.conf
new file mode 100644
index 0000000..25e057b
--- /dev/null
+++ b/tests/generated/tests/fopen.conf
@@ -0,0 +1,9 @@
+
+[fopen]
+fp: posix/stdio/oc/fopen
+include: stdio.h
+prep: FILE *fp;
+call: fp = fopen("/dev/null", "r");
+success_cond: fp != NULL
+failure_cond: fp == NULL
+errno_on_fail: ELOOP
diff --git a/tests/generated/tests/fprintf.conf b/tests/generated/tests/fprintf.conf
new file mode 100644
index 0000000..f8ed16e
--- /dev/null
+++ b/tests/generated/tests/fprintf.conf
@@ -0,0 +1,10 @@
+
+[fprintf]
+fp: posix/stdio/sp/fprintf
+include: stdio.h
+prep: int r; FILE *fp = fopen("/dev/null", "w");
+call: r = fprintf(fp, "%%d\n", 1234);
+success_cond: r == 5 && ferror(fp) == 0
+failure_cond: r == -1 && ferror(fp) != 0
+errno_on_fail: EIO
+
diff --git a/tests/generated/tests/fread.conf b/tests/generated/tests/fread.conf
new file mode 100644
index 0000000..5eee474
--- /dev/null
+++ b/tests/generated/tests/fread.conf
@@ -0,0 +1,10 @@
+
+[fread]
+fp: posix/stdio/rw/fread
+include: stdio.h
+prep: unsigned char buf[1024]; ssize_t r; FILE *fp = fopen("/dev/zero", "r");
+call: r = fread(buf, 1, 1024, fp);
+success_cond: r == 1024 && ferror(fp) == 0
+failure_cond: r == 0 && ferror(fp) != 0
+errno_on_fail: EIO
+
diff --git a/tests/test-ferror.c b/tests/test-ferror.c
new file mode 100644
index 0000000..ab1e079
--- /dev/null
+++ b/tests/test-ferror.c
@@ -0,0 +1,67 @@
+/* Test the handling of FILE * errors. */
+
+#include <fiu.h>
+#include <fiu-control.h>
+#include <stdio.h>
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+
+
+int test(const char *prefix) {
+	FILE *fp = fopen("/dev/zero", "r");
+
+	unsigned char buf[1024];
+	ssize_t r;
+
+	fiu_enable("posix/stdio/rw/fread", 1, (void *) EIO, 0);
+
+	r = fread(buf, 1, 1024, fp);
+
+	fiu_disable("posix/stdio/rw/fread");
+
+	if (r != 0) {
+		printf("%s: fread() succeeded, should have failed\n", prefix);
+		return -1;
+	}
+
+	if (errno != EIO) {
+		printf("%s: errno not set appropriately: ", prefix);
+		printf("errno = %d / %s, expected EIO\n", errno, strerror(errno));
+		return -1;
+	}
+
+	if (ferror(fp) == 0) {
+		printf("%s: ferror() said there was no failure, but there was\n",
+				prefix);
+		return -1;
+	}
+
+	clearerr(fp);
+
+	if (ferror(fp) != 0) {
+		printf("%s: clearerr(), ferror() said there were failures\n",
+				prefix);
+		return -1;
+	}
+
+	fclose(fp);
+	// Unfortunately we can't easily test after fclose() has been called,
+	// because it's impossible to distinguish between a libfiu failure
+	// from a libc error (which would appear due to an operation on a
+	// closed file). To make things worse, some versions of glibc make
+	// ferror() return an error on closed files, but others work.
+
+	return 0;
+}
+
+int main(void) {
+	// Run the test many times, to stress structure reuse a bit. This is
+	// not as thorough but does exercise some bugs we've had, such as
+	// forgetting to decrement the recursion counter.
+	char prefix[8];
+	for (int i = 0; i < 200; i++) {
+		snprintf(prefix, 8, "%2d", i);
+		test(prefix);
+	}
+}
diff --git a/tests/test-set_prng_seed-env.py b/tests/test-set_prng_seed-env.py
new file mode 100644
index 0000000..78d4412
--- /dev/null
+++ b/tests/test-set_prng_seed-env.py
@@ -0,0 +1,17 @@
+"""
+Test that we get reproducible results when setting PRNG seed via the
+environment.
+"""
+
+# Set the environment variable _before_ initializing the library.
+import os
+os.environ["FIU_PRNG_SEED"] = "1234"
+
+import fiu
+
+fiu.enable_random('p1', probability = 0.5)
+result = { True: 0, False: 0 }
+for i in range(1000):
+    result[fiu.fail('p1')] += 1
+
+assert result == {False: 516, True: 484}, result
diff --git a/tests/test-set_prng_seed.py b/tests/test-set_prng_seed.py
new file mode 100644
index 0000000..f1f3f5c
--- /dev/null
+++ b/tests/test-set_prng_seed.py
@@ -0,0 +1,23 @@
+"""
+Test that we get reproducible results with manually set PRNG seeds.
+"""
+
+import fiu
+
+
+fiu.set_prng_seed(1234)
+fiu.enable_random('p1', probability = 0.5)
+result = { True: 0, False: 0 }
+for i in range(1000):
+    result[fiu.fail('p1')] += 1
+
+assert result == {False: 516, True: 484}, result
+
+
+fiu.set_prng_seed(4321)
+fiu.enable_random('p1', probability = 0.5)
+result = { True: 0, False: 0 }
+for i in range(1000):
+    result[fiu.fail('p1')] += 1
+
+assert result == {False: 495, True: 505}, result
diff --git a/tests/utils/Makefile b/tests/utils/Makefile
index 0c79bad..905b7c3 100644
--- a/tests/utils/Makefile
+++ b/tests/utils/Makefile
@@ -44,7 +44,6 @@ run-%: % lnlibs
 
 clean:
 	rm -f libs/*.so output-*.txt
-	rmdir libs/
 
 .PHONY: default all clean \
 	tests lnlibs
