 .gitignore                         |    5 +
 bindings/python/fiu_ctrl.in.py     |  200 ++++++++++++++++++++++++++++++++++++
 bindings/python/setup.py           |   28 +++++-
 libfiu/Makefile                    |    9 +-
 libfiu/fiu.c                       |   17 +++-
 preload/posix/Makefile             |   14 ++-
 preload/posix/codegen.h            |    2 +-
 preload/posix/generate             |   36 ++++++-
 preload/posix/modules/linux.io.mod |    2 +-
 preload/posix/modules/posix.io.mod |   31 +++++--
 preload/posix/modules/posix.mm.mod |    1 +
 preload/run/fiu-run.in             |   18 +++-
 tests/Makefile                     |   28 +++++-
 tests/generated/generate-test      |    8 +-
 tests/generated/tests/mmap.conf    |    2 +-
 tests/generated/tests/pread.conf   |   10 ++
 tests/generated/tests/pread64.conf |   11 ++
 tests/generated/tests/strdup.conf  |   11 ++
 tests/test-basic.py                |   30 ++++++
 tests/test-fiu_ctrl.py             |   68 ++++++++++++
 tests/test-wildcards.py            |    2 +
 tests/utils/.gitignore             |    2 +
 tests/utils/Makefile               |   51 +++++++++
 tests/utils/test-basic_ctrl.py     |   63 +++++++++++
 tests/utils/test-basic_run.sh      |   17 +++
 tests/utils/wrap                   |   26 +++++
 utils/fiu-ctrl                     |    2 -
 27 files changed, 659 insertions(+), 35 deletions(-)

diff --git a/.gitignore b/.gitignore
index fa4df1a..55e90a6 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,7 @@
 bindings/python/build
 bindings/python/*.pyc
 bindings/python/*.pyo
+bindings/python/fiu_ctrl.py
 libfiu/*.o
 libfiu/libfiu.a
 libfiu/libfiu.pc
@@ -28,5 +29,9 @@ preload/run/build-needlibdl
 tests/*.o
 tests/build-flags
 tests/test-?
+tests/libs/
 tests/generated/build-flags
 tests/generated/tests/*.[oc]
+.*.swp
+*.gcno
+*.gcda
diff --git a/bindings/python/fiu_ctrl.in.py b/bindings/python/fiu_ctrl.in.py
new file mode 100644
index 0000000..8228e08
--- /dev/null
+++ b/bindings/python/fiu_ctrl.in.py
@@ -0,0 +1,200 @@
+"""
+libfiu python module for remote control
+
+This module provides an easy way to run a command with libfiu enabled, and
+controlling the failure points dynamically.
+
+It provides similar functionality to the fiu-ctrl and fiu-run shell tools, but
+is useful for programmed tests.
+
+Note it assumes the preloading libraries are installed in @@PLIBPATH@@.
+"""
+
+import os
+import tempfile
+import subprocess
+import shutil
+import time
+
+
+# Default path to look for preloader libraries.
+PLIBPATH = "@@PLIBPATH@@"
+
+
+class CommandError (RuntimeError):
+    """There was an error running the command."""
+    pass
+
+
+class Flags:
+	"""Contains the valid flag constants.
+
+	ONETIME: This point of failure is disabled immediately after failing once.
+	"""
+	ONETIME = "onetime"
+
+
+class _ControlBase (object):
+    """Base class for remote control objects."""
+
+    def run_raw_cmd(self, cmd, args):
+        """Runs a new raw command. To be implemented by subclasses"""
+        raise NotImplementedError
+
+    def _basic_args(self, name, failnum, failinfo, flags):
+        """Converts the common arguments to an args list for run_raw_cmd()."""
+        args = ["name=%s" % name]
+        if failnum:
+            args.append("failnum=%s" % failnum)
+        if failinfo:
+            args.append("failinfo=%s" % failinfo)
+        if flags:
+            args.extend(flags)
+
+        return args
+
+    def enable(self, name, failnum = 1, failinfo = None, flags = ()):
+        """Enables the given point of failure."""
+        args = self._basic_args(name, failnum, failinfo, flags)
+        self.run_raw_cmd("enable", args)
+
+    def enable_random(self, name, probability, failnum = 1,
+            failinfo = None, flags = ()):
+        "Enables the given point of failure, with the given probability."
+        args = self._basic_args(name, failnum, failinfo, flags)
+        args.append("probability=%f" % probability)
+        self.run_raw_cmd("enable_random", args)
+
+    def enable_stack_by_name(self, name, func_name,
+            failnum = 1, failinfo = None, flags = (),
+            pos_in_stack = -1):
+        """Enables the given point of failure, but only if 'func_name' is in
+        the stack.
+
+        'func_name' is be the name of the C function to look for.
+        """
+        args = self._basic_args(name, failnum, failinfo, flags)
+        args.append("func_name=%s" % func_name)
+        if pos_in_stack >= 0:
+            args.append("pos_in_stack=%d" % pos_in_stack)
+        self.run_raw_cmd("enable_stack_by_name", args)
+
+    def disable(self, name):
+        """Disables the given point of failure."""
+        self.run_raw_cmd("disable", ["name=%s" % name])
+
+
+def _open_with_timeout(path, mode, timeout = 3):
+    """Open a file, waiting if it doesn't exist yet."""
+    deadline = time.time() + timeout
+    while not os.path.exists(path):
+        time.sleep(0.01)
+        if time.time() >= deadline:
+            raise RuntimeError("Timeout waiting for file %r" % path)
+
+    return open(path, mode)
+
+
+class PipeControl (_ControlBase):
+    """Control pipe used to control a libfiu-instrumented process."""
+    def __init__(self, path_prefix):
+        """Constructor.
+
+        Args:
+            path: Path to the control pipe.
+        """
+        self.path_in = path_prefix + ".in"
+        self.path_out = path_prefix + ".out"
+
+    def _open_pipes(self):
+        # Open the files, but wait if they are not there, as the child process
+        # may not have created them yet.
+        fd_in = _open_with_timeout(self.path_in, "a")
+        fd_out = _open_with_timeout(self.path_out, "r")
+        return fd_in, fd_out
+
+    def run_raw_cmd(self, cmd, args):
+        """Send a raw command over the pipe."""
+        # Note we open the pipe each time for simplicity, and also to simplify
+        # external intervention that can be used for debugging.
+        fd_in, fd_out = self._open_pipes()
+
+        s = "%s %s\n" % (cmd, ','.join(args))
+        fd_in.write(s)
+        fd_in.flush()
+
+        r = int(fd_out.readline())
+        if r != 0:
+            raise CommandError
+
+
+class EnvironmentControl (_ControlBase):
+    """Pre-execution environment control."""
+    def __init__(self):
+        self.env = ""
+
+    def run_raw_cmd(self, cmd, args):
+        """Add a raw command to the environment."""
+        self.env += "%s %s\n" % (cmd, ','.join(args))
+
+
+class Subprocess (_ControlBase):
+    """Wrapper for subprocess.Popen, but without immediate execution.
+
+    This class provides a wrapper for subprocess.Popen, which can be used to
+    run other processes under libfiu.
+
+    However, the processes don't start straight away, allowing the user to
+    pre-configure some failure points.
+
+    The process can then be started with the start() method.
+
+    After the process has been started, the failure points can be controlled
+    remotely via the same functions.
+
+    Processes can be started only once.
+    Note that using shell=True is not recommended, as it makes the pid of the
+    controlled process to be unknown.
+    """
+    def __init__(self, *args, **kwargs):
+        self.args = args
+        self.kwargs = kwargs
+
+        # Note this removes fiu_enable_posix from kwargs if it's there, that
+        # way kwargs remains "clean" for passing to Popen.
+        self.fiu_enable_posix = kwargs.pop('fiu_enable_posix', False)
+
+        self._proc = None
+        self.tmpdir = None
+
+        # Initially, this is an EnvironmentControl so we can do preparation;
+        # once we start the command, we will change this to be PipeControl.
+        self.ctrl = EnvironmentControl()
+
+    def run_raw_cmd(self, cmd, args):
+        self.ctrl.run_raw_cmd(cmd, args)
+
+    def start(self):
+        self.tmpdir = tempfile.mkdtemp(prefix = 'fiu_ctrl-')
+
+        env = os.environ
+        env['LD_PRELOAD'] = env.get('LD_PRELOAD', '')
+        if self.fiu_enable_posix:
+            env['LD_PRELOAD'] += ' ' + PLIBPATH + '/fiu_posix_preload.so'
+        env['LD_PRELOAD'] += ' ' + PLIBPATH + '/fiu_run_preload.so '
+        env['FIU_CTRL_FIFO'] = self.tmpdir + '/ctrl-fifo'
+        env['FIU_ENABLE'] = self.ctrl.env
+
+        self._proc = subprocess.Popen(*self.args, **self.kwargs)
+
+        fifo_path = "%s-%d" % (env['FIU_CTRL_FIFO'], self._proc.pid)
+        self.ctrl = PipeControl(fifo_path)
+
+        return self._proc
+
+    def __del__(self):
+        # Remove the temporary directory.
+        # The "'fiu_ctrl-' in self.tmpdir" check is just a safeguard.
+        if self.tmpdir and 'fiu_ctrl-' in self.tmpdir:
+            shutil.rmtree(self.tmpdir)
+
diff --git a/bindings/python/setup.py b/bindings/python/setup.py
index fed2a78..6bb9c0b 100644
--- a/bindings/python/setup.py
+++ b/bindings/python/setup.py
@@ -1,12 +1,35 @@
 
+import os
 import sys
 from distutils.core import setup, Extension
+from distutils.command.build_py import build_py
 
 if sys.version_info[0] == 2:
 	ver_define = ('PYTHON2', '1')
 elif sys.version_info[0] == 3:
 	ver_define = ('PYTHON3', '1')
 
+
+# We need to generate the fiu_ctrl.py file from fiu_ctrl.in.py, replacing some
+# environment variables in its contents.
+class generate_and_build_py (build_py):
+    def run(self):
+        if not self.dry_run:
+            self.generate_fiu_ctrl()
+        build_py.run(self)
+
+    def generate_fiu_ctrl(self):
+        prefix = os.environ.get('PREFIX', '/usr/local/')
+        plibpath = os.environ.get('PLIBPATH', prefix + '/lib/')
+
+        contents = open('fiu_ctrl.in.py', 'r').read()
+        contents = contents.replace('@@PLIBPATH@@', plibpath)
+
+        out = open('fiu_ctrl.py', 'w')
+        out.write(contents)
+        out.close()
+
+
 fiu_ll = Extension("fiu_ll",
 		sources = ['fiu_ll.c'],
 		define_macros = [ver_define],
@@ -23,7 +46,8 @@ setup(
 	author = "Alberto Bertogli",
 	author_email = "albertito@blitiri.com.ar",
 	url = "http://blitiri.com.ar/p/libfiu",
-	py_modules = ['fiu'],
-	ext_modules = [fiu_ll]
+	py_modules = ['fiu', 'fiu_ctrl'],
+	ext_modules = [fiu_ll],
+    cmdclass = {'build_py': generate_and_build_py},
 )
 
diff --git a/libfiu/Makefile b/libfiu/Makefile
index 086a0b7..7d5d7d1 100644
--- a/libfiu/Makefile
+++ b/libfiu/Makefile
@@ -15,6 +15,9 @@ else
    endif
 endif
 
+ifdef TRACE
+    ALL_CFLAGS += -DFIU_TRACE=1
+endif
 
 # prefix for installing the binaries
 PREFIX=/usr/local
@@ -35,7 +38,7 @@ else
 	NICE_CC = $(CC)
 endif
 
-LIB_VER=0.92
+LIB_VER=0.93
 LIB_SO_VER=0
 
 
@@ -43,7 +46,7 @@ LIB_SO_VER=0
 # attempting to compile it normally, and fall back to the dummy version if
 # that fails. Not very sophisticated but should be safe.
 USE_DUMMY_BACKTRACE := $(shell $(CC) $(ALL_CFLAGS) -c backtrace.c \
-	-o /dev/null 2>/dev/null || echo -DDUMMY_BACKTRACE)
+	-o backtrace-check.o || echo -DDUMMY_BACKTRACE)
 ifndef USE_DUMMY_BACKTRACE
 # The real backtrace needs linking against libdl.
 USE_LIBDL = -ldl
@@ -135,7 +138,7 @@ doxygen:
 
 
 clean:
-	rm -f libfiu.pc $(OBJS)
+	rm -f libfiu.pc $(OBJS) backtrace-check.o
 	rm -f libfiu.so libfiu.so.$(LIB_VER) libfiu.so.$(LIB_SO_VER) libfiu.a
 	rm -f *.bb *.bbg *.da *.gcov *.gcda *.gcno gmon.out build-flags
 	$(MAKE) -C doxygen $@
diff --git a/libfiu/fiu.c b/libfiu/fiu.c
index ce89112..fd71815 100644
--- a/libfiu/fiu.c
+++ b/libfiu/fiu.c
@@ -14,6 +14,15 @@
 #include "internal.h"
 #include "wtable.h"
 
+/* Tracing mode for debugging libfiu itself. */
+#ifdef FIU_TRACE
+  #include <stdio.h>
+  #define trace(...) \
+	do { fprintf(stderr, __VA_ARGS__); fflush(stderr); } while (0)
+#else
+  #define trace(...) do { } while(0)
+#endif
+
 
 /* Different methods to decide when a point of failure fails */
 enum pf_method {
@@ -275,9 +284,7 @@ int fiu_fail(const char *name)
 
 	/* None found. */
 	if (pf == NULL) {
-		ef_runlock();
-		rec_count--;
-		return 0;
+		goto exit;
 	}
 
 	if (pf->flags & FIU_ONETIME) {
@@ -318,11 +325,15 @@ int fiu_fail(const char *name)
 	}
 
 exit:
+	trace("FIU  Not failing %s\n", name);
+
 	ef_runlock();
 	rec_count--;
 	return 0;
 
 exit_fail:
+	trace("FIU  Failing %s on %s\n", name, pf->name);
+
 	pthread_setspecific(last_failinfo_key,
 			pf->failinfo);
 	failnum = pf->failnum;
diff --git a/preload/posix/Makefile b/preload/posix/Makefile
index b269781..674882b 100644
--- a/preload/posix/Makefile
+++ b/preload/posix/Makefile
@@ -1,7 +1,8 @@
 
 CFLAGS += -std=c99 -Wall -O3
 ALL_CFLAGS = -D_XOPEN_SOURCE=600 -fPIC -DFIU_ENABLE=1 \
-		-I. -I../../libfiu/ $(CFLAGS)
+		-D_LARGEFILE64_SOURCE=1 -I. -I../../libfiu/ \
+		$(CFLAGS)
 
 ifdef DEBUG
 ALL_CFLAGS += -g
@@ -11,6 +12,11 @@ ifdef PROFILE
 ALL_CFLAGS += -g -pg -fprofile-arcs -ftest-coverage
 endif
 
+ifdef POSIX_TRACE
+    ALL_CFLAGS += -DFIU_POSIX_TRACE=1
+endif
+
+
 # prefix for installing the binaries
 PREFIX=/usr/local
 
@@ -52,7 +58,7 @@ build-flags: .force-build-flags
 
 $(GEN_OBJS): $(GEN_C)
 
-$(OBJS): build-flags
+$(OBJS): build-flags codegen.h
 
 %.mod.c: %.mod
 	$(NICE_GEN) $< $@ $<.fl
@@ -62,7 +68,7 @@ $(OBJS): build-flags
 
 # We define _GNU_SOURCE to get RTLD_NEXT if available; on non-GNU
 # platforms it should be harmless.
-codegen.o: codegen.c
+codegen.o: codegen.c build-flags
 	$(NICE_CC) $(ALL_CFLAGS) -D_GNU_SOURCE -c $< -o $@
 
 # some platforms do not have libdl, we only use it if available
@@ -117,6 +123,8 @@ clean:
 	rm -f build-flags build-env.h build-libcsoname build-needlibdl
 	rm -f function_list fiu_posix_preload.so
 	rm -f *.bb *.bbg *.da *.gcov *.gcda *.gcno gmon.out
+	rm -f modules/*.bb modules/*.bbg modules/*.da
+	rm -f modules/*.gcov modules/*.gcda modules/*.gcno modules/gmon.out
 
 .PHONY: default install uninstall clean .force-build-flags
 
diff --git a/preload/posix/codegen.h b/preload/posix/codegen.h
index efee7bc..2885da1 100644
--- a/preload/posix/codegen.h
+++ b/preload/posix/codegen.h
@@ -29,7 +29,7 @@ void *libc_symbol(const char *symbol);
 #endif
 
 /* Useful macros for recursion and debugging */
-#if 1
+#ifndef FIU_POSIX_TRACE
 	#define rec_inc() do { _fiu_called++; } while(0)
 	#define rec_dec() do { _fiu_called--; } while(0)
 	#define printd(...) do { } while(0)
diff --git a/preload/posix/generate b/preload/posix/generate
index 9c93ef6..6e93247 100755
--- a/preload/posix/generate
+++ b/preload/posix/generate
@@ -8,6 +8,7 @@ The code is NOT nice. It just does the trick.
 
 import sys
 import re
+import copy
 
 
 # Function definition regular expression
@@ -53,6 +54,10 @@ class Function:
 		# if the given parameter should be reduced by a random amount
 		self.reduce = None
 
+		# describes possible variations of function, for example
+		# pread() and pread64().
+		self.variants = []
+
 	def load_from_definition(self, definition):
 		m = func_def_re.match(definition)
 		self.name = m.group("name")
@@ -75,6 +80,8 @@ class Function:
 				self.valid_errnos = v.split()
 			elif k == 'reduce':
 				self.reduce = v
+			elif k == 'variants':
+				self.variants = v.split();
 			else:
 				raise SyntaxError, \
 					"Unknown information: " + k
@@ -145,6 +152,31 @@ class Function:
 			n.append(self.fiu_name + '/reduce')
 		return n
 
+        def apply_variant(self, v):
+		if v != 'off64_t':
+			raise SyntaxError, "Unknown function variant: " + v
+
+		f = copy.copy(self)
+
+		# NOTE: We don't modify fiu_name here to be able to enable
+		# both <func> and <func>64 versions of the function by
+		# enabling just <func>.
+		f.name = f.name + "64"
+		f.params = f.params.replace("off_t", "off64_t")
+		f.params_info = [
+			(x, y) if x != "off_t " else ("off64_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")]
+
+	def get_all_variants(self):
+		"""Returns all variants of the given function provided via
+		'variants:' function directive"""
+		variants = [self]
+		for v in self.variants:
+			variants.extend(self.apply_variant(v))
+		return variants
 
 class Include:
 	"Represents an include directive"
@@ -222,7 +254,7 @@ def parse_module(path):
 			# either a new function or a directive, but in either
 			# case the current function is done
 			if current_func:
-				directives.append(current_func)
+				directives.extend(current_func.get_all_variants())
 			current_func = None
 
 			l = l.strip()
@@ -248,7 +280,7 @@ def parse_module(path):
 			current_func.load_info(l.strip())
 
 	if current_func:
-		directives.append(current_func)
+		directives.extend(current_func.get_all_variants())
 
 	return directives
 
diff --git a/preload/posix/modules/linux.io.mod b/preload/posix/modules/linux.io.mod
index e05df19..63f2246 100644
--- a/preload/posix/modules/linux.io.mod
+++ b/preload/posix/modules/linux.io.mod
@@ -7,7 +7,7 @@ fiu name base: linux/io/
 # sync_file_range() is linux-only
 v: #ifdef __linux__
 
-int sync_file_range(int fd, off_t offset, off_t nbytes, \
+int sync_file_range(int fd, off64_t offset, off64_t nbytes, \
 		unsigned int flags);
 	on error: -1
 	valid errnos: EBADF EINVAL EIO ENOMEM ENOSPC
diff --git a/preload/posix/modules/posix.io.mod b/preload/posix/modules/posix.io.mod
index 0f4d4a5..98bedd1 100644
--- a/preload/posix/modules/posix.io.mod
+++ b/preload/posix/modules/posix.io.mod
@@ -36,46 +36,63 @@ fiu name base: posix/io/rw/
 
 ssize_t read(int fd, void *buf, size_t count);
 	on error: -1
-	valid errnos: EBADFD EFAULT EINTR EINVAL EIO EISDIR EOVERFLOW
+	valid errnos: EBADFD EFAULT EINTR EINVAL EIO EISDIR
 	reduce: count
 
 ssize_t pread(int fd, void *buf, size_t count, off_t offset);
 	on error: -1
-	valid errnos: EBADFD EFAULT EINTR EINVAL EIO EISDIR EOVERFLOW
+	valid errnos: EBADFD EFAULT EINTR EINVAL EIO EISDIR EOVERFLOW ENXIO
 	reduce: count
+	variants: off64_t
 
 ssize_t readv(int fd, const struct iovec *iov, int iovcnt);
 	on error: -1
-	valid errnos: EBADFD EFAULT EINTR EINVAL EIO EISDIR EOVERFLOW
+	valid errnos: EBADFD EFAULT EINTR EINVAL EIO EISDIR
 	reduce: iovcnt
 
+ssize_t preadv(int fd, const struct iovec *iov, int iovcnt, off_t offset);
+	on error: -1
+	valid errnos: EBADFD EFAULT EINTR EINVAL EIO EISDIR EOVERFLOW ENXIO
+	reduce: iovcnt
+	variants: off64_t
+
 
 ssize_t write(int fd, const void *buf, size_t count);
 	on error: -1
-	valid errnos: EBADFD EFAULT EFBIG EINTR EINVAL EIO ENOSPC
+	valid errnos: EBADFD EDQUOT EFAULT EFBIG EINTR EINVAL EIO ENOSPC
 	reduce: count
 
 ssize_t pwrite(int fd, const void *buf, size_t count, off_t offset);
 	on error: -1
-	valid errnos: EBADFD EFAULT EFBIG EINTR EINVAL EIO ENOSPC \
-		EOVERFLOW
+	valid errnos: EBADFD EDQUOT EFAULT EFBIG EINTR EINVAL EIO ENOSPC \
+		EOVERFLOW ENXIO
 	reduce: count
+	variants: off64_t
 
 ssize_t writev(int fd, const struct iovec *iov, int iovcnt);
 	on error: -1
-	valid errnos: EBADFD EFAULT EFBIG EINTR EINVAL EIO ENOSPC
+	valid errnos: EBADFD EDQUOT EFAULT EFBIG EINTR EINVAL EIO ENOSPC
+	reduce: iovcnt
+
+ssize_t pwritev(int fd, const struct iovec *iov, int iovcnt, off_t offset);
+	on error: -1
+	valid errnos: EBADFD EDQUOT EFAULT EFBIG EINTR EINVAL EIO ENOSPC \
+		EOVERFLOW ENXIO
 	reduce: iovcnt
+	variants: off64_t
 
 
 int truncate(const char *path, off_t length);
 	on error: -1
 	valid errnos: EACCES EFAULT EFBIG EINTR EINVAL EIO EISDIR ELOOP \
 		ENAMETOOLONG ENOENT ENOTDIR EPERM EROFS ETXTBSY
+	variants: off64_t
 
 int ftruncate(int fd, off_t length);
 	on error: -1
 	valid errnos: EACCES EBADF EFAULT EFBIG EINTR EINVAL EIO EISDIR ELOOP \
 		ENAMETOOLONG ENOENT ENOTDIR EPERM EROFS ETXTBSY
+	variants: off64_t
 
 
 fiu name base: posix/io/dir/
diff --git a/preload/posix/modules/posix.mm.mod b/preload/posix/modules/posix.mm.mod
index 6d96a93..128b34a 100644
--- a/preload/posix/modules/posix.mm.mod
+++ b/preload/posix/modules/posix.mm.mod
@@ -13,6 +13,7 @@ void *mmap(void *addr, size_t length, int prot, int flags, int fd, \
 	on error: MAP_FAILED
 	valid errnos: EACCES EAGAIN EBADF EINVAL ENFILE ENODEV ENOMEM EPERM \
 		ETXTBSY
+	variants: off64_t
 
 int munmap(void *addr, size_t length);
 	on error: 0
diff --git a/preload/run/fiu-run.in b/preload/run/fiu-run.in
index 0fe385f..8926094 100644
--- a/preload/run/fiu-run.in
+++ b/preload/run/fiu-run.in
@@ -20,6 +20,9 @@ PRELOAD_LIBS=""
 # use the POSIX preload library?
 USE_POSIX_PRELOAD=0
 
+# don't run, but show the command line instead
+DRY_RUN=0
+
 HELP_MSG="
 Usage: fiu-run [options] program [arguments]
 
@@ -96,7 +99,7 @@ function add_deprecated_enable() {
 }
 
 opts_reset;
-while getopts "+c:f:l:xe:p:u:i:h" opt; do
+while getopts "+c:f:l:xne:p:u:i:h" opt; do
 	case $opt in
 	c)
 		# Note we use the newline as a command separator.
@@ -112,6 +115,9 @@ while getopts "+c:f:l:xe:p:u:i:h" opt; do
 	x)
 		USE_POSIX_PRELOAD=1
 		;;
+	n)
+		DRY_RUN=1
+		;;
 
 	# Deprecated options
 	e)
@@ -155,5 +161,13 @@ fi
 export FIU_ENABLE="$ENABLE"
 export FIU_CTRL_FIFO="$FIFO_PREFIX"
 export LD_PRELOAD="$PLIBPATH/fiu_run_preload.so $PRELOAD_LIBS"
-exec "$@"
+
+if [ $DRY_RUN -eq 1 ] ; then
+	echo "FIU_ENABLE=\"$ENABLE\"" \\
+	echo "FIU_CTRL_FIFO=\"$FIFO_PREFIX\"" \\
+	echo "LD_PRELOAD=\"$PLIBPATH/fiu_run_preload.so $PRELOAD_LIBS\"" \\
+	echo "$@"
+else
+	exec "$@"
+fi
 
diff --git a/tests/Makefile b/tests/Makefile
index cdc478d..7d57070 100644
--- a/tests/Makefile
+++ b/tests/Makefile
@@ -15,17 +15,33 @@ ifneq ($(V), 1)
 	NICE_CC = @echo "  CC  $@"; $(CC)
 	NICE_RUN = @echo "  RUN $<"; LD_LIBRARY_PATH=../libfiu/
 	NICE_PY = @echo "  PY  $<"; ./wrap-python 2
+	NICE_LN = @echo "  LN $@"; ln -f
 else
 	NICE_CC = $(CC)
 	NICE_RUN = LD_LIBRARY_PATH=../libfiu/
 	NICE_PY = ./wrap-python 2
+	NICE_LN = ln -f
 endif
 
 default: tests
 
 all: tests
 
-tests: c-tests py-tests gen-tests
+tests: c-tests py-tests gen-tests utils-tests
+
+
+# Link the libraries to a single place, some of the tests need this.
+libs:
+	mkdir -p libs/
+
+libs/fiu_posix_preload.so: ../preload/posix/fiu_posix_preload.so libs
+	$(NICE_LN) $< libs/
+
+libs/fiu_run_preload.so: ../preload/run/fiu_run_preload.so libs
+	$(NICE_LN) $< libs/
+
+lnlibs: libs/fiu_posix_preload.so libs/fiu_run_preload.so
+
 
 #
 # C tests
@@ -65,16 +81,19 @@ PY_TESTS := $(wildcard test-*.py)
 
 py-tests: $(patsubst %.py,py-run-%,$(PY_TESTS))
 
-py-run-%: %.py
+py-run-%: %.py lnlibs
 	$(NICE_PY) ./$<
 
+
 #
-# Generated tests
+# Sub-directory tests
 #
 
 gen-tests:
 	$(MAKE) -C generated
 
+utils-tests:
+	$(MAKE) -C utils
 
 #
 # Cleanup
@@ -85,13 +104,14 @@ gen-tests:
 # also remove them when cleaning just in case.
 clean:
 	rm -f $(C_OBJS) $(C_BINS)
+	rm -rf libs/
 	rm -f *.bb *.bbg *.da *.gcov *.gcda *.gcno gmon.out build-flags
 	$(MAKE) -C generated clean
 
 FORCE:
 
 .PHONY: default all clean \
-	tests c-tests py-tests \
+	tests c-tests py-tests gen-tests utils-tests \
 	.force-build-flags
 
 
diff --git a/tests/generated/generate-test b/tests/generated/generate-test
index 8d5dae8..fec4585 100755
--- a/tests/generated/generate-test
+++ b/tests/generated/generate-test
@@ -110,8 +110,8 @@ def generate(options, outfile):
 	else:
 		outfile.write("\n\n")
 
-	if options['ifdef']:
-		outfile.write("#ifdef %s\n" % options['ifdef'])
+	if options['if']:
+		outfile.write("#if %s\n" % options['if'])
 
 	if 'errno_on_fail' in options:
 		options['errno_cond'] = \
@@ -126,7 +126,7 @@ def generate(options, outfile):
 	outfile.write(Template(TEST_FAILURE_TMPL).substitute(options))
 	outfile.write(Template(TEST_MAIN_TMPL).substitute(options))
 
-	if options['ifdef']:
+	if options['if']:
 		outfile.write("#else\n")
 		outfile.write(Template(TEST_SKIPPED_TMPL).substitute(options))
 		outfile.write("#endif\n")
@@ -148,7 +148,7 @@ def main():
 		'include': (),
 		'prep': '',
 
-		'ifdef': '',
+		'if': '',
 
 		# These are C conditions that are always true.
 		'success_cond': '1',
diff --git a/tests/generated/tests/mmap.conf b/tests/generated/tests/mmap.conf
index 68af0ca..6f58885 100644
--- a/tests/generated/tests/mmap.conf
+++ b/tests/generated/tests/mmap.conf
@@ -2,7 +2,7 @@
 [mmap]
 fp: posix/mm/mmap
 include: sys/mman.h unistd.h
-ifdef: _POSIX_MAPPED_FILES
+if: defined _POSIX_MAPPED_FILES
 prep: void *p;
 call: p = mmap(NULL, 4092, PROT_READ | PROT_WRITE,
 		MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
diff --git a/tests/generated/tests/pread.conf b/tests/generated/tests/pread.conf
new file mode 100644
index 0000000..179cc4c
--- /dev/null
+++ b/tests/generated/tests/pread.conf
@@ -0,0 +1,10 @@
+
+[pread]
+fp: posix/io/rw/pread
+include: unistd.h sys/types.h sys/stat.h fcntl.h
+prep: unsigned char buf[1024]; ssize_t r; int fd = open("/dev/zero", O_RDONLY);
+call: r = pread(fd, buf, 1024, 0);
+success_cond: r != -1
+failure_cond: r == -1
+errno_on_fail: EIO
+
diff --git a/tests/generated/tests/pread64.conf b/tests/generated/tests/pread64.conf
new file mode 100644
index 0000000..4716eb8
--- /dev/null
+++ b/tests/generated/tests/pread64.conf
@@ -0,0 +1,11 @@
+
+[pread64]
+if: defined __GLIBC__
+fp: posix/io/rw/pread
+include: unistd.h sys/types.h sys/stat.h fcntl.h
+prep: unsigned char buf[1024]; ssize_t r; int fd = open("/dev/zero", O_RDONLY);
+call: r = pread64(fd, buf, 1024, 0);
+success_cond: r != -1
+failure_cond: r == -1
+errno_on_fail: EIO
+
diff --git a/tests/generated/tests/strdup.conf b/tests/generated/tests/strdup.conf
new file mode 100644
index 0000000..f1b7796
--- /dev/null
+++ b/tests/generated/tests/strdup.conf
@@ -0,0 +1,11 @@
+
+[strdup]
+fp: libc/str/strdup
+include: string.h
+if: !(defined strdup)
+prep: char *dup, *s = "test string\n";
+call: dup = strdup(s);
+success_cond: strcmp(s, dup) == 0
+failure_cond: dup == NULL
+errno_on_fail: ENOMEM
+
diff --git a/tests/test-basic.py b/tests/test-basic.py
new file mode 100644
index 0000000..8a9f0d7
--- /dev/null
+++ b/tests/test-basic.py
@@ -0,0 +1,30 @@
+"""
+Basic tests for general functionality.
+"""
+
+import fiu
+
+# Test unknown failure point.
+assert not fiu.fail('unknown')
+
+# Test enable/disable.
+fiu.enable('p1')
+assert fiu.fail('p1')
+fiu.disable('p1')
+assert not fiu.fail('p1')
+
+# Test enable_random.
+fiu.enable_random('p1', probability = 0.5)
+result = { True: 0, False: 0 }
+for i in range(1000):
+    result[fiu.fail('p1')] += 1
+
+assert 400 < result[True] < 600, result
+assert 400 < result[False] < 600, result
+
+# Test repeated enabling/disabling.
+fiu.enable('p1')
+fiu.enable('p1')
+assert fiu.fail('p1')
+fiu.disable('p1')
+assert not fiu.fail('p1')
diff --git a/tests/test-fiu_ctrl.py b/tests/test-fiu_ctrl.py
new file mode 100644
index 0000000..104ca9d
--- /dev/null
+++ b/tests/test-fiu_ctrl.py
@@ -0,0 +1,68 @@
+"""
+Tests for the fiu_ctrl.py module.
+
+Note the command line utility is covered by the utils/ tests, not from here,
+this is just for the Python module.
+"""
+
+import subprocess
+import fiu_ctrl
+import errno
+import time
+
+fiu_ctrl.PLIBPATH = "./libs/"
+
+def run_cat(**kwargs):
+    return fiu_ctrl.Subprocess(["/bin/cat"],
+        stdin = subprocess.PIPE, stdout = subprocess.PIPE,
+        stderr = subprocess.PIPE, **kwargs)
+
+# Run without any failure point being enabled.
+cmd = run_cat()
+p = cmd.start()
+out, err = p.communicate('test\n')
+assert out == 'test\n', out
+assert err == '', err
+
+# Enable before starting.
+cmd = run_cat(fiu_enable_posix = True)
+cmd.enable('posix/io/rw/*', failinfo = errno.ENOSPC)
+p = cmd.start()
+out, err = p.communicate('test\n')
+assert out == '', out
+assert 'space' in err, err
+
+# Enable after starting.
+cmd = run_cat(fiu_enable_posix = True)
+p = cmd.start()
+cmd.enable('posix/io/rw/*', failinfo = errno.ENOSPC)
+out, err = p.communicate('test\n')
+assert out == '', out
+assert 'space' in err, err
+
+# Enable-disable.
+cmd = run_cat(fiu_enable_posix = True)
+p = cmd.start()
+cmd.enable('posix/io/rw/*', failinfo = errno.ENOSPC)
+cmd.disable('posix/io/rw/*')
+out, err = p.communicate('test\n')
+assert out == 'test\n', (out, err)
+
+# Enable random.
+result = { True: 0, False: 0 }
+for i in range(50):
+    cmd = run_cat(fiu_enable_posix = True)
+    p = cmd.start()
+    cmd.enable_random('posix/io/rw/*', failinfo = errno.ENOSPC,
+            probability = 0.5)
+    out, err = p.communicate('test\n')
+    if 'space' in err:
+        result[False] += 1
+    elif out == 'test\n':
+        result[True] += 1
+    else:
+        assert False, (out, err)
+
+assert 10 < result[True] < 40, result
+assert 10 < result[False] < 40, result
+
diff --git a/tests/test-wildcards.py b/tests/test-wildcards.py
index 6603299..abfcd7d 100644
--- a/tests/test-wildcards.py
+++ b/tests/test-wildcards.py
@@ -12,6 +12,8 @@ assert fiu.fail("a:b:c")
 assert fiu.fail("a:b:x")
 assert fiu.fail("a:b:c:d")
 
+fiu.enable("a:b:*")  # Test repeated enabling of a wildcard.
+
 fiu.enable("a:b:c:d")
 assert fiu.fail("a:b:c:d")
 
diff --git a/tests/utils/.gitignore b/tests/utils/.gitignore
new file mode 100644
index 0000000..e4c47bc
--- /dev/null
+++ b/tests/utils/.gitignore
@@ -0,0 +1,2 @@
+output-*.txt
+libs/
diff --git a/tests/utils/Makefile b/tests/utils/Makefile
new file mode 100644
index 0000000..fc78d2e
--- /dev/null
+++ b/tests/utils/Makefile
@@ -0,0 +1,51 @@
+
+ifneq ($(V), 1)
+	NICE_RUN = @echo "  RUN $<"; LD_LIBRARY_PATH=../../libfiu/
+	NICE_LN = @echo "  LN $@"; ln -f
+else
+	NICE_RUN = LD_LIBRARY_PATH=../../libfiu/
+	NICE_LN = ln -f
+endif
+
+
+default: all
+
+all: lnlibs tests
+
+
+# Link the libraries to a single place, as the scripts expect.
+libs:
+	mkdir -p libs/
+
+libs/fiu_posix_preload.so: ../../preload/posix/fiu_posix_preload.so libs
+	$(NICE_LN) $< libs/
+
+libs/fiu_run_preload.so: ../../preload/run/fiu_run_preload.so libs
+	$(NICE_LN) $< libs/
+
+lnlibs: libs/fiu_posix_preload.so libs/fiu_run_preload.so
+
+
+#
+# Tests
+#
+
+SRCS := $(wildcard test-*)
+
+tests: $(patsubst %,run-%,$(SRCS))
+
+run-%: %
+	$(NICE_RUN) ./$< > output-$<.txt 2>&1
+
+
+#
+# Cleanup
+#
+
+clean:
+	rm -f libs/*.so output-*.txt
+	rmdir libs/
+
+.PHONY: default all clean \
+	tests lnlibs
+
diff --git a/tests/utils/test-basic_ctrl.py b/tests/utils/test-basic_ctrl.py
new file mode 100755
index 0000000..e21cb35
--- /dev/null
+++ b/tests/utils/test-basic_ctrl.py
@@ -0,0 +1,63 @@
+#!/usr/bin/env python
+
+import subprocess
+import time
+
+def fiu_ctrl(p, args):
+    subprocess.check_call("./wrap fiu-ctrl".split() + args + [str(p.pid)])
+
+def launch_sh():
+    # We use cat as a subprocess as it is reasonably ubiquitous, simple and
+    # straightforward (which helps debugging and troubleshooting), but at the
+    # same time it is interactive and we can make it do the operations we
+    # want.
+    p = subprocess.Popen("./wrap fiu-run -x cat".split(),
+            stdin=subprocess.PIPE, stdout=subprocess.PIPE,
+            stderr=subprocess.PIPE)
+
+    # Give it a moment to initialize and create the control files.
+    time.sleep(0.2)
+    return p
+
+def send_cmd(p, cmd):
+    p.stdin.write(cmd)
+    p.stdin.close()
+
+    # Give the control thread a moment to process the command.
+    time.sleep(0.2)
+    return p.stdout.read(), p.stderr.read()
+
+
+# Launch a subprocess and check that it shows up in fiu-ls.
+p = launch_sh()
+out = subprocess.check_output("./wrap fiu-ls".split())
+assert ("%s: cat" % p.pid) in out, out
+
+# Send it a command and check that it works.
+# Nothing interesting here from libfiu's perspective, but it helps make sure
+# the test environment is sane.
+out, err = send_cmd(p, "test\n")
+assert out == 'test\n', out
+assert err == '', err
+
+# Launch and then make I/O fail at runtime.
+p = launch_sh()
+fiu_ctrl(p, ["-c", "enable name=posix/io/*"])
+out, err = send_cmd(p, "test\n")
+assert out == '', out
+assert 'error' in err
+
+# Same, but with failinfo.
+p = launch_sh()
+fiu_ctrl(p, ["-c", "enable name=posix/io/*,failinfo=3"])
+out, err = send_cmd(p, "test\n")
+assert out == '', out
+assert 'error' in err
+
+# Same, but with probability.
+p = launch_sh()
+fiu_ctrl(p, ["-c", "enable_random name=posix/io/*,probability=0.999"])
+out, err = send_cmd(p, "test\n")
+assert out == '', out
+assert 'error' in err
+
diff --git a/tests/utils/test-basic_run.sh b/tests/utils/test-basic_run.sh
new file mode 100755
index 0000000..4d2ba60
--- /dev/null
+++ b/tests/utils/test-basic_run.sh
@@ -0,0 +1,17 @@
+#!/bin/bash
+
+set -ex
+
+# Very basic test, nothing enabled.
+./wrap fiu-run true
+
+# Same as above, but including the posix preloader.
+./wrap fiu-run -x true
+
+# Now with an unused failure point.
+./wrap fiu-run -c "enable name=p1" true
+
+# open() failure.
+! ./wrap fiu-run -x -c "enable name=posix/io/oc/open" cat /dev/null
+
+
diff --git a/tests/utils/wrap b/tests/utils/wrap
new file mode 100755
index 0000000..ad32b9d
--- /dev/null
+++ b/tests/utils/wrap
@@ -0,0 +1,26 @@
+#!/bin/bash
+
+# Wrap the utilities (fiu-ctrl, fiu-ls, fiu-run) so that we can run them
+# easily from within the development tree, agains the locally-built libraries.
+#
+# The first argument is the utility to run.
+
+case "$1" in
+"fiu-ctrl")
+	BIN=../../utils/fiu-ctrl
+	ARGS=
+	;;
+"fiu-ls")
+	BIN=../../utils/fiu-ls
+	ARGS=
+	;;
+"fiu-run")
+	BIN=../../preload/run/fiu-run
+	ARGS="-l ./libs"
+	;;
+esac
+
+shift
+
+exec $BIN $ARGS "$@"
+
diff --git a/utils/fiu-ctrl b/utils/fiu-ctrl
index 1b400d8..4b3a649 100755
--- a/utils/fiu-ctrl
+++ b/utils/fiu-ctrl
@@ -23,8 +23,6 @@ The following options are supported:
   -f ctrlpath	Enable remote control over named pipes with the given path as
 		base name, the process id will be appended (defaults to
 		\"$FIFO_PREFIX\", set to \"\" to disable).
-  -l path	Path where to find the libfiu preload libraries, defaults to
-		$PLIBPATH (which is usually correct).
 
 Remote control commands are of the form 'command param1=value1,param2=value2'.
 Valid commands are:
