 .gitignore                             |    7 +-
 Makefile                               |    5 +-
 bindings/python/fiu.py                 |   20 +++-
 bindings/python/fiu_ll.c               |   24 ++++-
 doc/guide.rst                          |   38 +++---
 doc/posix.rst                          |   10 +-
 libfiu/Makefile                        |   32 ++++-
 libfiu/backtrace.c                     |   82 +++++++++++++
 libfiu/doxygen/Doxyfile.base.in        |  204 ++++++++++++++++++++++++++++++++
 libfiu/doxygen/Doxyfile.internal       |    7 +
 libfiu/doxygen/Doxyfile.public         |    8 ++
 libfiu/doxygen/Makefile                |   33 +++++
 libfiu/fiu-control.h                   |  140 ++++++++++++++++------
 libfiu/fiu-rc.c                        |  165 +++++++++++++++++---------
 libfiu/fiu.c                           |  178 +++++++++++++++++++++-------
 libfiu/fiu.h                           |   45 ++++---
 libfiu/internal.h                      |   16 +++
 libfiu/libfiu.3                        |   49 +++++++-
 preload/posix/codegen.h                |    1 +
 preload/posix/modules/libc.mm.custom.c |    2 +
 preload/run/fiu-run.1                  |   42 ++++----
 preload/run/fiu-run.in                 |  101 ++++++++++------
 preload/run/run.c                      |  125 ++++----------------
 tests/Makefile                         |   88 ++++++++++++++
 tests/test-enable_stack.c              |   46 +++++++
 tests/test-enable_stack_by_name.c      |   46 +++++++
 tests/test-failinfo_refcount.py        |   21 ++++
 utils/fiu-ctrl                         |  101 ++++++++++------
 utils/fiu-ctrl.1                       |   44 ++++---
 29 files changed, 1274 insertions(+), 406 deletions(-)

diff --git a/.gitignore b/.gitignore
index 24e7bb9..8b1881d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -7,6 +7,9 @@ libfiu/libfiu.pc
 libfiu/libfiu.so
 libfiu/libfiu.so.*
 libfiu/build-flags
+libfiu/doxygen/doc.internal
+libfiu/doxygen/doc.public
+libfiu/doxygen/Doxyfile.base
 preload/posix/*.o
 preload/posix/*.so
 preload/posix/build-flags
@@ -22,4 +25,6 @@ preload/run/*.so
 preload/run/fiu-run
 preload/run/build-flags
 preload/run/build-needlibdl
-
+tests/*.o
+tests/build-flags
+tests/test-?
diff --git a/Makefile b/Makefile
index 9d418e1..bae9e6b 100644
--- a/Makefile
+++ b/Makefile
@@ -51,6 +51,8 @@ utils_install: utils
 utils_uninstall:
 	$(MAKE) -C utils uninstall
 
+test: libfiu bindings
+	$(MAKE) -C tests
 
 bindings: python2 python3
 
@@ -82,6 +84,7 @@ clean: python_clean preload_clean libfiu_clean utils_clean
 	python2 python2_install python3 python3_install python_clean \
 	bindings bindings_install bindings_clean \
 	preload preload_clean preload_install preload_uninstall \
-	utils utils_clean utils_install utils_uninstall
+	utils utils_clean utils_install utils_uninstall \
+	test
 
 
diff --git a/bindings/python/fiu.py b/bindings/python/fiu.py
index c61eef9..fda5639 100644
--- a/bindings/python/fiu.py
+++ b/bindings/python/fiu.py
@@ -32,7 +32,7 @@ _fi_table = {}
 
 def enable(name, failnum = 1, failinfo = None, flags = 0):
 	"Enables the given point of failure."
-	_fi_table[name] = failnum
+	_fi_table[name] = failinfo
 	r = _ll.enable(name, failnum, failinfo, flags)
 	if r != 0:
 		del _fi_table[name]
@@ -40,7 +40,7 @@ def enable(name, failnum = 1, failinfo = None, flags = 0):
 
 def enable_random(name, probability, failnum = 1, failinfo = None, flags = 0):
 	"Enables the given point of failure, with the given probability."
-	_fi_table[name] = failnum
+	_fi_table[name] = failinfo
 	r = _ll.enable_random(name, failnum, failinfo, flags, probability)
 	if r != 0:
 		del _fi_table[name]
@@ -61,6 +61,22 @@ def enable_external(name, cb, failnum = 1, flags = 0):
 	_fi_table[name] = cb
 	r = _ll.enable_external(name, failnum, flags, cb)
 	if r != 0:
+		del _fi_table[name]
+		raise RuntimeError(r)
+
+def enable_stack_by_name(name, func_name,
+		failnum = 1, failinfo = None, flags = 0,
+		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.
+	"""
+	_fi_table[name] = failinfo
+	r = _ll.enable_stack_by_name(name, failnum, failinfo, flags,
+			func_name, pos_in_stack)
+	if r != 0:
+		del _fi_table[name]
 		raise RuntimeError(r)
 
 def disable(name):
diff --git a/bindings/python/fiu_ll.c b/bindings/python/fiu_ll.c
index a558f28..e49874b 100644
--- a/bindings/python/fiu_ll.c
+++ b/bindings/python/fiu_ll.c
@@ -158,6 +158,25 @@ static PyObject *enable_external(PyObject *self, PyObject *args)
 				py_external_cb, flags, external_callback));
 }
 
+static PyObject *enable_stack_by_name(PyObject *self, PyObject *args)
+{
+	char *name;
+	int failnum;
+	PyObject *failinfo;
+	unsigned int flags;
+	char *func_name;
+	int pos_in_stack = -1;
+
+	if (!PyArg_ParseTuple(args, "siOIs|i:enable_stack_by_name",
+				&name, &failnum, &failinfo, &flags,
+				&func_name, &pos_in_stack))
+		return NULL;
+
+	return PyLong_FromLong(fiu_enable_stack_by_name(name, failnum,
+				failinfo, flags,
+				func_name, pos_in_stack));
+}
+
 static PyObject *disable(PyObject *self, PyObject *args)
 {
 	char *name;
@@ -183,7 +202,10 @@ static PyMethodDef fiu_methods[] = {
 	{ "failinfo", (PyCFunction) failinfo, METH_VARARGS, NULL },
 	{ "enable", (PyCFunction) enable, METH_VARARGS, NULL },
 	{ "enable_random", (PyCFunction) enable_random, METH_VARARGS, NULL },
-	{ "enable_external", (PyCFunction) enable_external, METH_VARARGS, NULL },
+	{ "enable_external", (PyCFunction) enable_external,
+		METH_VARARGS, NULL },
+	{ "enable_stack_by_name", (PyCFunction) enable_stack_by_name,
+		METH_VARARGS, NULL },
 	{ "disable", (PyCFunction) disable, METH_VARARGS, NULL },
 	{ "rc_fifo", (PyCFunction) rc_fifo, METH_VARARGS, NULL },
 	{ NULL }
diff --git a/doc/guide.rst b/doc/guide.rst
index c401b46..7b3d2cb 100644
--- a/doc/guide.rst
+++ b/doc/guide.rst
@@ -20,9 +20,8 @@ libfiu is a library that you can use to add fault injection to your code. It
 aims to be easy to use by means of a simple API, with minimal code impact and
 little runtime overhead when enabled.
 
-By minimal code impact I mean that the modifications you have to do to your
-code (and build system) in order to support libfiu should be as little
-intrusive as possible.
+That means that the modifications you have to do to your code (and build
+system) in order to support libfiu should be as little intrusive as possible.
 
 
 Code overview
@@ -31,8 +30,8 @@ Code overview
 Let's take a look to a small (fictitious) code sample to see what's the
 general idea behind libfiu.
 
-Let's say you have this code that checks if there's enough free space to store
-a given file::
+Assume that you have this code that checks if there's enough free space to
+store a given file::
 
         size_t free_space() {
                 [code to find out how much free space there is]
@@ -46,9 +45,9 @@ a given file::
                 return true;
         }
 
-As you can see, with current disk sizes, it's very, very strange that a disk
-runs out of free space. So the scenario where *free_space()* returns 0 is hard
-to test. With libfiu, you can modify that code into::
+With current disk sizes, it's very unusual to ran out of free space, which
+makes the scenario where *free_space()* returns 0 hard to test. With libfiu,
+you can do the following small addition::
 
         size_t free_space() {
                 fiu_return_on("no_free_space", 0);
@@ -64,9 +63,10 @@ to test. With libfiu, you can modify that code into::
                 return true;
         }
 
-There, you've just created a *point of failure* identified by the name
-"no_free_space", and said that when that point of failure is enabled, the
-function will return 0.
+The *fiu_return_on()* annotation is the only change you need to make to your
+code to create a *point of failure*, which is identified by the name
+**no_free_space**. When that point of failure is enabled, the function will
+return 0.
 
 In your testing code, you can now do this::
 
@@ -75,13 +75,13 @@ In your testing code, you can now do this::
         assert(file_fits("tmpfile") == false);
 
 The first line initializes the library, and the second *enables* the point of
-failure. As the point of failure is enabled, *free_space()* will return 0, so
-you can test how your code behaves under that condition, which was otherwise
-hard to trigger.
+failure. When the point of failure is enabled, *free_space()* will return 0,
+so you can test how your code behaves under that condition, which was
+otherwise hard to trigger.
 
-As you can see, libfiu's API has two "sides": a core API and a control API.
-The core API is used inside the code to be fault injected. The control API is
-used inside the testing code, in order to control the injection of failures.
+libfiu's API has two "sides": a core API and a control API.  The core API is
+used inside the code to be fault injected. The control API is used inside the
+testing code, in order to control the injection of failures.
 
 In the example above, *fiu_return_on()* is a part of the core API, and
 *fiu_enable()* is a part of the control API.
@@ -99,7 +99,7 @@ The build system
 
 The first thing to do is to enable your build system to use libfiu. Usually,
 you do not want to make libfiu a runtime or build-time dependency, because
-it's a testing library.
+it's often only used for testing.
 
 To that end, you should copy *fiu-local.h* into your source tree, and then
 create an option to do a *fault injection build* that #defines the constant
@@ -121,7 +121,7 @@ First, you should ``#include "fiu-local.h"`` in the files you want to add
 points of failure to. That header allows you to avoid libfiu as a build-time
 dependency, as mentioned in the last section.
 
-Then, to insert points of failure, you sprinkle your code with calls like
+Then, to insert points of failure, sprinkle your code with calls like
 ``fiu_return_on("name", -1)``, ``fiu_exit_on("name")``, or more complex code
 using ``fiu_fail("name")``. See the libfiu's manpage for the details on
 the API.
diff --git a/doc/posix.rst b/doc/posix.rst
index d10ff91..b83b56d 100644
--- a/doc/posix.rst
+++ b/doc/posix.rst
@@ -30,9 +30,9 @@ Suppose you want to run the classic program "fortune" (which some would
 definitely consider mission critical) and see how it behaves on the presence
 of *read()* errors. With *fiu-run*, you can do it like this::
 
-  $ fiu-run -x -e posix/io/rw/read -p 1 fortune
+  $ fiu-run -x -c "enable_random name=posix/io/rw/read,probability=0.05" fortune
 
-That enables the failure point with the name *posix/io/rw/read* with 1%
+That enables the failure point with the name *posix/io/rw/read* with 5%
 probability to fail *on each call*, and then runs fortune. The *-x*
 parameter tells *fiu-run* to enable fault injection in the POSIX API.
 
@@ -40,7 +40,7 @@ Run it several times and you can see that sometimes it works, but sometimes it
 doesn't, reporting an error reading, which means a *read()* failed as
 expected.
 
-When fortune is run, every *read()* has a 1% chance to fail, selecting an
+When fortune is run, every *read()* has a 5% chance to fail, selecting an
 *errno* at random from the list of the ones that read() is allowed to return.
 If you want to select an specific *errno*, you can do it by passing its
 numerical value using the *-i* parameter.
@@ -72,13 +72,13 @@ files. First, we run it with *fiu-run*::
 Everything should look normal. Then, in another terminal, we make *open()*
 fail unconditionally::
 
-  $ fiu-ctrl -e posix/io/oc/open `pidof top`
+  $ fiu-ctrl -c "enable name=posix/io/oc/open" `pidof top`
 
 After that moment, the top display will probably be empty, because it can't
 read process information. Now let's disable that failure point, so *open()*
 works again::
 
-  $ fiu-ctrl -d posix/io/oc/open `pidof top`
+  $ fiu-ctrl -c "disable name=posix/io/oc/open" `pidof top`
 
 And everything should have gone back to normal.
 
diff --git a/libfiu/Makefile b/libfiu/Makefile
index ed98034..ea765bc 100644
--- a/libfiu/Makefile
+++ b/libfiu/Makefile
@@ -21,7 +21,7 @@ DESTDIR=$(PREFIX)
 INSTALL=install
 
 
-OBJS = fiu.o fiu-rc.o
+OBJS = fiu.o fiu-rc.o backtrace.o
 
 
 ifneq ($(V), 1)
@@ -30,10 +30,21 @@ else
 	NICE_CC = $(CC)
 endif
 
-LIB_VER=0.14
+LIB_VER=0.90
 LIB_SO_VER=0
 
 
+# We attempt to detect if we have the extensions we use in backtrace.c by just
+# 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) -c backtrace.c -o /dev/null 2>/dev/null || \
+	echo -DDUMMY_BACKTRACE)
+ifndef USE_DUMMY_BACKTRACE
+# The real backtrace needs linking against libdl.
+USE_LIBDL = -ldl
+endif
+
+
 default: all
 
 all: libs libfiu.pc
@@ -50,8 +61,9 @@ libs: libfiu.so libfiu.a
 libfiu.so: build-flags fiu.h $(OBJS)
 	$(NICE_CC) $(ALL_CFLAGS) -shared -fPIC \
 		-Wl,-soname,libfiu.so.$(LIB_SO_VER) \
-		$(OBJS) -lpthread -o libfiu.so.$(LIB_VER)
+		$(OBJS) -lpthread $(USE_LIBDL) -o libfiu.so.$(LIB_VER)
 	ln -fs libfiu.so.$(LIB_VER) libfiu.so
+	ln -fs libfiu.so.$(LIB_VER) libfiu.so.$(LIB_SO_VER)
 
 libfiu.a: build-flags fiu.h $(OBJS)
 	$(AR) cr libfiu.a $(OBJS)
@@ -104,19 +116,29 @@ build-flags: .force-build-flags
 		echo "$(BF)" > build-flags; \
 	fi
 
+
 $(OBJS): build-flags
 
 .c.o:
 	$(NICE_CC) $(ALL_CFLAGS) -c $< -o $@
 
+backtrace.o: ALL_CFLAGS += $(USE_DUMMY_BACKTRACE)
+
+
+doxygen:
+	        $(MAKE) LIB_VER=$(LIB_VER) -C doxygen
+
+
 clean:
-	rm -f libfiu.pc $(OBJS) libfiu.so libfiu.so.$(LIB_VER) libfiu.a
+	rm -f libfiu.pc $(OBJS)
+	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 $@
 
 .PHONY: default all libs \
 	install-lib install-man install \
 	uninstall-lib uninstall-man uninstall \
-	clean \
+	doxygen clean \
 	.force-build-flags
 
 
diff --git a/libfiu/backtrace.c b/libfiu/backtrace.c
new file mode 100644
index 0000000..fc44d9a
--- /dev/null
+++ b/libfiu/backtrace.c
@@ -0,0 +1,82 @@
+
+/* Since our implementation relies on nonstandard functions and headers, we
+ * provide a dummy one below for compatibility purposes. The dummy version can
+ * be selected at build time using -DDUMMY_BACKTRACE. */
+#ifndef DUMMY_BACKTRACE
+
+/* This is needed for some of the functions below. */
+#define _GNU_SOURCE
+
+#include <execinfo.h>
+#include <dlfcn.h>
+#include <sys/procfs.h>
+#include <link.h>
+
+
+int get_backtrace(void *buffer, int size)
+{
+	return backtrace(buffer, size);
+}
+
+void *get_func_start(void *pc)
+{
+	int r;
+	Dl_info info;
+
+	r = dladdr(pc, &info);
+	if (r == 0)
+		return NULL;
+
+	return info.dli_saddr;
+}
+
+void *get_func_end(void *func)
+{
+	int r;
+	Dl_info dl_info;
+	ElfW(Sym) *elf_info;
+
+	r = dladdr1(func, &dl_info, (void **) &elf_info, RTLD_DL_SYMENT);
+	if (r == 0)
+		return NULL;
+	if (elf_info == NULL)
+		return NULL;
+	if (dl_info.dli_saddr == NULL)
+		return NULL;
+
+	return ((unsigned char *) func) + elf_info->st_size;
+}
+
+void *get_func_addr(const char *func_name)
+{
+	return dlsym(RTLD_DEFAULT, func_name);
+}
+
+#else
+/* Dummy versions */
+
+#warning Using dummy versions of backtrace
+
+#include <stddef.h>	/* for NULL */
+
+int get_backtrace(void *buffer, int size)
+{
+	return 0;
+}
+
+void *get_func_end(void *pc)
+{
+	return NULL;
+}
+
+void *get_func_start(void *pc)
+{
+	return NULL;
+}
+
+void *get_func_addr(const char *func_name)
+{
+	return NULL;
+}
+
+#endif // DUMMY_BACKTRACE
diff --git a/libfiu/doxygen/Doxyfile.base.in b/libfiu/doxygen/Doxyfile.base.in
new file mode 100644
index 0000000..1c8f6e8
--- /dev/null
+++ b/libfiu/doxygen/Doxyfile.base.in
@@ -0,0 +1,204 @@
+DOXYFILE_ENCODING      = UTF-8
+PROJECT_NAME           = libfiu
+PROJECT_NUMBER         = ++VERSION++
+OUTPUT_DIRECTORY       = 
+CREATE_SUBDIRS         = NO
+OUTPUT_LANGUAGE        = English
+BRIEF_MEMBER_DESC      = YES
+REPEAT_BRIEF           = YES
+ABBREVIATE_BRIEF       = 
+ALWAYS_DETAILED_SEC    = NO
+INLINE_INHERITED_MEMB  = NO
+FULL_PATH_NAMES        = NO
+STRIP_FROM_PATH        = 
+STRIP_FROM_INC_PATH    = 
+SHORT_NAMES            = NO
+JAVADOC_AUTOBRIEF      = YES
+QT_AUTOBRIEF           = YES
+MULTILINE_CPP_IS_BRIEF = NO
+INHERIT_DOCS           = YES
+SEPARATE_MEMBER_PAGES  = NO
+TAB_SIZE               = 8
+ALIASES                = 
+OPTIMIZE_OUTPUT_FOR_C  = YES
+OPTIMIZE_OUTPUT_JAVA   = NO
+OPTIMIZE_FOR_FORTRAN   = NO
+OPTIMIZE_OUTPUT_VHDL   = NO
+EXTENSION_MAPPING      = 
+BUILTIN_STL_SUPPORT    = NO
+CPP_CLI_SUPPORT        = NO
+SIP_SUPPORT            = NO
+IDL_PROPERTY_SUPPORT   = YES
+DISTRIBUTE_GROUP_DOC   = NO
+SUBGROUPING            = YES
+TYPEDEF_HIDES_STRUCT   = NO
+SYMBOL_CACHE_SIZE      = 0
+EXTRACT_ALL            = NO
+EXTRACT_PRIVATE        = NO
+EXTRACT_STATIC         = NO
+EXTRACT_LOCAL_CLASSES  = YES
+EXTRACT_LOCAL_METHODS  = NO
+EXTRACT_ANON_NSPACES   = NO
+HIDE_UNDOC_MEMBERS     = NO
+HIDE_UNDOC_CLASSES     = NO
+HIDE_FRIEND_COMPOUNDS  = NO
+HIDE_IN_BODY_DOCS      = NO
+INTERNAL_DOCS          = NO
+CASE_SENSE_NAMES       = YES
+HIDE_SCOPE_NAMES       = NO
+SHOW_INCLUDE_FILES     = YES
+INLINE_INFO            = YES
+SORT_MEMBER_DOCS       = NO
+SORT_BRIEF_DOCS        = NO
+SORT_GROUP_NAMES       = NO
+SORT_BY_SCOPE_NAME     = NO
+GENERATE_TODOLIST      = YES
+GENERATE_TESTLIST      = YES
+GENERATE_BUGLIST       = YES
+GENERATE_DEPRECATEDLIST= YES
+ENABLED_SECTIONS       = 
+MAX_INITIALIZER_LINES  = 30
+SHOW_USED_FILES        = YES
+SHOW_DIRECTORIES       = YES
+SHOW_FILES             = YES
+SHOW_NAMESPACES        = YES
+FILE_VERSION_FILTER    = 
+LAYOUT_FILE            = 
+QUIET                  = YES
+WARNINGS               = YES
+WARN_IF_UNDOCUMENTED   = YES
+WARN_IF_DOC_ERROR      = YES
+WARN_NO_PARAMDOC       = YES
+WARN_FORMAT            = "$file:$line: $text"
+WARN_LOGFILE           = 
+INPUT                  = ../
+INPUT_ENCODING         = UTF-8
+FILE_PATTERNS          = *.c *.h *.doxy
+RECURSIVE              = YES
+EXCLUDE                = 
+EXCLUDE_SYMLINKS       = NO
+EXCLUDE_PATTERNS       = 
+EXCLUDE_SYMBOLS        = 
+EXAMPLE_PATH           = 
+EXAMPLE_PATTERNS       = 
+EXAMPLE_RECURSIVE      = NO
+IMAGE_PATH             = 
+INPUT_FILTER           = 
+FILTER_PATTERNS        = 
+FILTER_SOURCE_FILES    = NO
+SOURCE_BROWSER         = YES
+INLINE_SOURCES         = NO
+STRIP_CODE_COMMENTS    = YES
+REFERENCED_BY_RELATION = YES
+REFERENCES_RELATION    = NO
+REFERENCES_LINK_SOURCE = NO
+USE_HTAGS              = NO
+VERBATIM_HEADERS       = YES
+ALPHABETICAL_INDEX     = NO
+COLS_IN_ALPHA_INDEX    = 5
+IGNORE_PREFIX          = 
+GENERATE_HTML          = YES
+HTML_OUTPUT            = html
+HTML_FILE_EXTENSION    = .html
+HTML_HEADER            = 
+HTML_FOOTER            = 
+HTML_STYLESHEET        = 
+HTML_ALIGN_MEMBERS     = YES
+HTML_DYNAMIC_SECTIONS  = YES
+GENERATE_DOCSET        = NO
+DOCSET_FEEDNAME        = "Doxygen generated docs"
+DOCSET_BUNDLE_ID       = org.doxygen.Project
+GENERATE_HTMLHELP      = NO
+CHM_FILE               = 
+HHC_LOCATION           = 
+GENERATE_CHI           = NO
+CHM_INDEX_ENCODING     = 
+BINARY_TOC             = NO
+TOC_EXPAND             = YES
+GENERATE_QHP           = NO
+QCH_FILE               = 
+QHP_NAMESPACE          = 
+QHP_VIRTUAL_FOLDER     = doc
+QHP_CUST_FILTER_NAME   = 
+QHP_CUST_FILTER_ATTRS  = 
+QHP_SECT_FILTER_ATTRS  = 
+QHG_LOCATION           = 
+DISABLE_INDEX          = NO
+ENUM_VALUES_PER_LINE   = 4
+GENERATE_TREEVIEW      = NONE
+TREEVIEW_WIDTH         = 250
+FORMULA_FONTSIZE       = 10
+GENERATE_LATEX         = NO
+LATEX_OUTPUT           = latex
+LATEX_CMD_NAME         = latex
+MAKEINDEX_CMD_NAME     = makeindex
+COMPACT_LATEX          = NO
+PAPER_TYPE             = a4wide
+EXTRA_PACKAGES         = 
+LATEX_HEADER           = 
+PDF_HYPERLINKS         = YES
+USE_PDFLATEX           = YES
+LATEX_BATCHMODE        = NO
+LATEX_HIDE_INDICES     = NO
+GENERATE_RTF           = NO
+RTF_OUTPUT             = rtf
+COMPACT_RTF            = NO
+RTF_HYPERLINKS         = NO
+RTF_STYLESHEET_FILE    = 
+RTF_EXTENSIONS_FILE    = 
+GENERATE_MAN           = NO
+MAN_OUTPUT             = man
+MAN_EXTENSION          = .3
+MAN_LINKS              = NO
+GENERATE_XML           = NO
+XML_OUTPUT             = xml
+XML_SCHEMA             = 
+XML_DTD                = 
+XML_PROGRAMLISTING     = YES
+GENERATE_AUTOGEN_DEF   = NO
+GENERATE_PERLMOD       = NO
+PERLMOD_LATEX          = NO
+PERLMOD_PRETTY         = YES
+PERLMOD_MAKEVAR_PREFIX = 
+ENABLE_PREPROCESSING   = YES
+MACRO_EXPANSION        = NO
+EXPAND_ONLY_PREDEF     = NO
+SEARCH_INCLUDES        = YES
+INCLUDE_PATH           = 
+INCLUDE_FILE_PATTERNS  = 
+PREDEFINED             = FIU_ENABLE
+EXPAND_AS_DEFINED      = 
+SKIP_FUNCTION_MACROS   = YES
+TAGFILES               = 
+GENERATE_TAGFILE       = 
+ALLEXTERNALS           = NO
+EXTERNAL_GROUPS        = YES
+PERL_PATH              = /usr/bin/perl
+CLASS_DIAGRAMS         = YES
+MSCGEN_PATH            = 
+HIDE_UNDOC_RELATIONS   = YES
+HAVE_DOT               = YES
+DOT_FONTNAME           = FreeSans
+DOT_FONTSIZE           = 10
+DOT_FONTPATH           = 
+CLASS_GRAPH            = YES
+COLLABORATION_GRAPH    = YES
+GROUP_GRAPHS           = YES
+UML_LOOK               = NO
+TEMPLATE_RELATIONS     = NO
+INCLUDE_GRAPH          = YES
+INCLUDED_BY_GRAPH      = YES
+CALL_GRAPH             = YES
+CALLER_GRAPH           = YES
+GRAPHICAL_HIERARCHY    = YES
+DIRECTORY_GRAPH        = YES
+DOT_IMAGE_FORMAT       = png
+DOT_PATH               = 
+DOTFILE_DIRS           = 
+DOT_GRAPH_MAX_NODES    = 50
+MAX_DOT_GRAPH_DEPTH    = 0
+DOT_TRANSPARENT        = YES
+DOT_MULTI_TARGETS      = NO
+GENERATE_LEGEND        = YES
+DOT_CLEANUP            = YES
+SEARCHENGINE           = NO
diff --git a/libfiu/doxygen/Doxyfile.internal b/libfiu/doxygen/Doxyfile.internal
new file mode 100644
index 0000000..6066429
--- /dev/null
+++ b/libfiu/doxygen/Doxyfile.internal
@@ -0,0 +1,7 @@
+@INCLUDE = Doxyfile.base
+PROJECT_NAME = "libfiu (internal)"
+OUTPUT_DIRECTORY = doc.internal
+EXTRACT_ALL = YES
+EXTRACT_STATIC = YES
+INTERNAL_DOCS = YES
+
diff --git a/libfiu/doxygen/Doxyfile.public b/libfiu/doxygen/Doxyfile.public
new file mode 100644
index 0000000..168bb3c
--- /dev/null
+++ b/libfiu/doxygen/Doxyfile.public
@@ -0,0 +1,8 @@
+@INCLUDE = Doxyfile.base
+PROJECT_NAME = "libfiu (public)"
+OUTPUT_DIRECTORY = doc.public
+EXTRACT_ALL = YES
+EXTRACT_STATIC = NO
+INTERNAL_DOCS = NO
+INPUT = ../fiu.h ../fiu-control.h
+
diff --git a/libfiu/doxygen/Makefile b/libfiu/doxygen/Makefile
new file mode 100644
index 0000000..daa51cd
--- /dev/null
+++ b/libfiu/doxygen/Makefile
@@ -0,0 +1,33 @@
+
+ifneq ($(V), 1)
+        NICE_DOXYGEN = @echo "  DOXYGEN  $@"; doxygen
+else
+        NICE_DOXYGEN = doxygen
+endif
+
+
+default: all
+
+all: public internal
+
+# $(LIB_VER) must be defined externally if we want the generated docs to
+# specify a version number. Usually, this Makefile will be invoked by
+# libfiu's, which has that variable properly defined.
+Doxyfile.base: Doxyfile.base.in
+	@echo "generating Doxyfile.base"
+	@cat Doxyfile.base.in | \
+		sed 's@++VERSION++@$(LIB_VER)@g' \
+		> Doxyfile.base
+
+public: Doxyfile.base
+	$(NICE_DOXYGEN) Doxyfile.public
+
+internal: Doxyfile.base
+	$(NICE_DOXYGEN) Doxyfile.internal
+
+clean:
+	rm -rf doc.internal doc.public Doxyfile.base
+
+
+.PHONY: all clean default doxygen internal public
+
diff --git a/libfiu/fiu-control.h b/libfiu/fiu-control.h
index 30ed4f1..7e54ce3 100644
--- a/libfiu/fiu-control.h
+++ b/libfiu/fiu-control.h
@@ -1,4 +1,11 @@
 
+/** @file
+ *
+ * This header contains the control API.
+ * It should be used for controlling the injection of failures, usually when
+ * writing tests.
+ */
+
 #ifndef _FIU_CONTROL_H
 #define _FIU_CONTROL_H
 
@@ -6,73 +13,136 @@
 extern "C" {
 #endif
 
-/*
- * Control API for libfiu
- */
 
 /* Flags for fiu_enable*() */
-#define FIU_ONETIME 1		/* Only fail once */
+
+/** Only fail once; the point of failure will be automatically disabled
+ * afterwards. */
+#define FIU_ONETIME 1
 
 
-/* Enables the given point of failure. That makes it always fail.
+/** Enables the given point of failure unconditionally.
  *
- * - name: point of failure name.
- * - failnum: what will fiu_fail() return, must be != 0.
- * - failinfo: what will fiu_failinfo() return.
- * - flags: flags.
- * - returns: 0 if success, < 0 otherwise.
+ * @param name  Name of the point of failure to enable.
+ * @param failnum  What will fiu_fail() return, must be != 0.
+ * @param failinfo  What will fiu_failinfo() return.
+ * @param flags  Flags.
+ * @returns 0 if success, < 0 otherwise.
  */
 int fiu_enable(const char *name, int failnum, void *failinfo,
 		unsigned int flags);
 
 
-/* Enables the given point of failure, with the given probability. That makes
- * it fail with the given probablity.
+/** Enables the given point of failure, with the given probability. That makes
+ * it fail with the given probability.
  *
- * - name: point of failure name.
- * - failnum: what will fiu_fail() return, must be != 0.
- * - failinfo: what will fiu_failinfo() return.
- * - flags: flags.
- * - probability: probability a fiu_fail() call will return failnum,
+ * @param name  Name of the point of failure to enable.
+ * @param failnum  What will fiu_fail() return, must be != 0.
+ * @param failinfo  What will fiu_failinfo() return.
+ * @param flags  Flags.
+ * @param probability Probability a fiu_fail() call will return failnum,
  * 	between 0 (never fail) and 1 (always fail). As a special fast case, -1
  * 	can also be used to always fail.
- * - returns: 0 if success, < 0 otherwise. */
+ * @returns  0 if success, < 0 otherwise.
+ */
 int fiu_enable_random(const char *name, int failnum, void *failinfo,
 		unsigned int flags, float probability);
 
 
-/** Type of external callback functions. They must return 0 to indicate not to
- * fail, != 0 to indicate otherwise. Can modify failnum, failinfo and flags,
- * in order to alter the values of the point of failure. */
+/** Type of external callback functions.
+ * They must return 0 to indicate not to fail, != 0 to indicate otherwise. Can
+ * modify failnum, failinfo and flags, in order to alter the values of the
+ * point of failure. */
 typedef int external_cb_t(const char *name, int *failnum, void **failinfo,
 		unsigned int *flags);
 
-/* Enables the given point of failure, leaving the decision whether to fail or
- * not to the given external function.
+/** Enables the given point of failure, leaving the decision whether to fail
+ * or not to the given external function.
+ *
+ * @param name  Name of the point of failure to enable.
+ * @param failnum  What will fiu_fail() return, must be != 0.
+ * @param failinfo  What will fiu_failinfo() return.
+ * @param flags  Flags.
+ * @param external_cb  Function to call to determine whether to fail or not.
+ * @returns  0 if success, < 0 otherwise.
+ */
+int fiu_enable_external(const char *name, int failnum, void *failinfo,
+		unsigned int flags, external_cb_t *external_cb);
+
+/* Enables the given point of failure, but only if the given function is in
+ * the stack at the given position.
+ *
+ * This function relies on GNU extensions such as backtrace() and dladdr(), so
+ * it may not be available on your platform.
  *
  * - name: point of failure name.
  * - failnum: what will fiu_fail() return, must be != 0.
  * - failinfo: what will fiu_failinfo() return.
  * - flags: flags.
- * - external_cb: function to call to determine whether fail or not. */
-int fiu_enable_external(const char *name, int failnum, void *failinfo,
-		unsigned int flags, external_cb_t *external_cb);
+ * - func: pointer to the function.
+ * - func_pos: position where we expect the function to be; use -1 for "any".
+ * 	Values other than -1 are not supported at the moment, but will be in
+ * 	the future.
+ */
+int fiu_enable_stack(const char *name, int failnum, void *failinfo,
+		unsigned int flags, void *func, int func_pos_in_stack);
 
-/* Disables the given point of failure. That makes it NOT fail.
+/** Enables the given point of failure, but only if 'func_name' is in
+ * the stack at 'func_pos_in_stack'.
  *
- * - name: point of failure name.
- * - returns: 0 if success, < 0 otherwise. */
+ * This function relies on GNU extensions such as backtrace() and dladdr(), so
+ * if your platform does not support them, it will always return failure.
+ *
+ * It is exactly like fiu_enable_stack() but takes a function name, it will
+ * ask the dynamic linker to find the corresponding function pointer.
+ *
+ * @param name  Name of the point of failure to enable.
+ * @param failnum  What will fiu_fail() return, must be != 0.
+ * @param failinfo  What will fiu_failinfo() return.
+ * @param flags  Flags.
+ * @param func_name  Name of the function.
+ * @param func_pos_in_stack  Position where we expect the function to be; use
+ * 		-1 for "any". Values other than -1 are not supported at the
+ * 		moment, but will be in the future.
+ * @returns  0 if success, < 0 otherwise.
+ */
+int fiu_enable_stack_by_name(const char *name, int failnum, void *failinfo,
+		unsigned int flags, const char *func_name,
+		int func_pos_in_stack);
+
+/** Disables the given point of failure. That makes it NOT fail.
+ *
+ * @param name  Name of the point of failure to disable.
+ * @returns  0 if success, < 0 otherwise.
+ */
 int fiu_disable(const char *name);
 
-/* Enables remote control over a named pipe that begins with the given
- * basename. "-$PID" will be appended to it to form the final path. After the
- * process dies, the pipe will be removed. If the process forks, a new pipe
- * will be created.
+/** Enables remote control over a named pipe.
+ *
+ * The name pipe path will begin with the given basename. "-$PID" will be
+ * appended to it to form the final path. After the process dies, the pipe
+ * will be removed. If the process forks, a new pipe will be created.
+ *
+ * Once this function has been called, the fiu-ctrl utility can be used to
+ * control the points of failure externally.
  *
- * - basename: base path to use in the creation of the named pipes.
- * - returns: 0 on success, -1 on errors. */
+ * @param basename  Base path to use in the creation of the named pipes.
+ * @returns  0 on success, -1 on errors. */
 int fiu_rc_fifo(const char *basename);
 
+/** Applies a remote control command given via a string.
+ *
+ * The format of the string is not stable and is still subject to change.
+ * At the moment, this function is exported for consumption by the libfiu
+ * utilities.
+ *
+ * @param command:  A zero-terminated string with the command to apply.
+ * @param error:  In case of an error, it will point to a human-readable error
+ *			message.
+ * @returns  0 if success, < 0 otherwise.
+ */
+int fiu_rc_string(const char *cmd, char ** const error);
+
 
 #ifdef __cplusplus
 }
diff --git a/libfiu/fiu-rc.c b/libfiu/fiu-rc.c
index 3a9151b..cd45086 100644
--- a/libfiu/fiu-rc.c
+++ b/libfiu/fiu-rc.c
@@ -24,6 +24,7 @@
 /* Max length of a line containing a control directive */
 #define MAX_LINE 512
 
+
 /*
  * Generic remote control
  */
@@ -64,77 +65,127 @@ static int read_line(int fd, char *buf)
 }
 
 /* Remote control command processing.
+ *
  * Supported commands:
- *  - disable <name>
- *  - enable <name> <failnum> <failinfo> [flags]
- *  - enable_random <name> <failnum> <failinfo> <probability> [flags]
+ *  - disable name=N
+ *  - enable name=N,failnum=F,failinfo=I
+ *  - enable_random <same as enable>,probability=P
+ *  - enable_stack_by_name <same as enable>,func_name=F,pos_in_stack=P
+ *
+ * All enable* commands can also take an additional "onetime" parameter,
+ * indicating that this should only fail once (analogous to the FIU_ONETIME
+ * flag).
  *
- * flags can be one of:
- *  - "one": same as FIU_ONETIME
+ * This function is ugly, but we aim for simplicity and ease to extend for
+ * future commands.
  */
-static int rc_process_cmd(char *cmd)
+int fiu_rc_string(const char *cmd, char ** const error)
 {
-	char *tok, *state;
-	char fp_name[MAX_LINE];
-	int failnum;
-	void *failinfo;
-	float probability;
-	unsigned int flags;
+	char m_cmd[MAX_LINE];
+	char command[MAX_LINE], parameters[MAX_LINE];
 
-	state = NULL;
-
-	tok = strtok_r(cmd, " ", &state);
-	if (tok == NULL)
-		return -1;
+	/* We need a version of cmd we can write to for parsing */
+	strncpy(m_cmd, cmd, MAX_LINE);
 
-	if (strcmp(tok, "disable") == 0) {
-		tok = strtok_r(NULL, " ", &state);
-		if (tok == NULL)
-			return -1;
-		return fiu_disable(tok);
-
-	} else if (strcmp(tok, "enable") == 0
-			|| strcmp(tok, "enable_random") == 0) {
-
-		tok = strtok_r(NULL, " ", &state);
-		if (tok == NULL)
-			return -1;
-		strncpy(fp_name, tok, MAX_LINE);
+	/* Separate command and parameters */
+	{
+		char *tok = NULL, *state = NULL;
 
-		tok = strtok_r(NULL, " ", &state);
-		if (tok == NULL)
+		tok = strtok_r(m_cmd, " \t", &state);
+		if (tok == NULL) {
+			*error = "Cannot get command";
 			return -1;
-		failnum = atoi(tok);
+		}
+		strncpy(command, tok, MAX_LINE);
 
-		tok = strtok_r(NULL, " ", &state);
-		if (tok == NULL)
+		tok = strtok_r(NULL, " \t", &state);
+		if (tok == NULL) {
+			*error = "Cannot get parameters";
 			return -1;
-		failinfo = (void *) strtoul(tok, NULL, 10);
-
-		probability = -1;
-		if (strcmp(tok, "enable_random") == 0) {
-			tok = strtok_r(NULL, " ", &state);
-			if (tok == NULL)
-				return -1;
-			probability = strtod(tok, NULL);
-			if (probability < 0 || probability > 1)
-				return -1;
 		}
+		strncpy(parameters, tok, MAX_LINE);
+	}
 
-		flags = 0;
-		tok = strtok_r(NULL, " ", &state);
-		if (tok != NULL) {
-			if (strcmp(tok, "one") == 0)
+	/* Parsing of parameters.
+	 *
+	 * To simplify the code, we parse the command parameters here. Not all
+	 * commands use all the parameters, but since they're not ambiguous it
+	 * makes it easier to do it this way. */
+	char *fp_name = NULL;
+	int failnum = 1;
+	void *failinfo = NULL;
+	unsigned int flags = 0;
+	double probability = -1;
+	char *func_name = NULL;
+	int func_pos_in_stack = -1;
+
+	{
+		/* Different tokens that we accept as parameters */
+		enum {
+			OPT_NAME = 0,
+			OPT_FAILNUM,
+			OPT_FAILINFO,
+			OPT_PROBABILITY,
+			OPT_FUNC_NAME,
+			OPT_POS_IN_STACK,
+			FLAG_ONETIME,
+		};
+		char * const token[] = {
+			[OPT_NAME] = "name",
+			[OPT_FAILNUM] = "failnum",
+			[OPT_FAILINFO] = "failinfo",
+			[OPT_PROBABILITY] = "probability",
+			[OPT_FUNC_NAME] = "func_name",
+			[OPT_POS_IN_STACK] = "pos_in_stack",
+			[FLAG_ONETIME] = "onetime",
+			NULL
+		};
+
+		char *value;
+		char *opts = parameters;
+		while (*opts != '\0') {
+			switch (getsubopt(&opts, token, &value)) {
+			case OPT_NAME:
+				fp_name = value;
+				break;
+			case OPT_FAILNUM:
+				failnum = atoi(value);
+				break;
+			case OPT_FAILINFO:
+				failinfo = (void *) strtoul(value, NULL, 10);
+				break;
+			case OPT_PROBABILITY:
+				probability = strtod(value, NULL);
+				break;
+			case OPT_FUNC_NAME:
+				func_name = value;
+				break;
+			case OPT_POS_IN_STACK:
+				func_pos_in_stack = atoi(value);
+				break;
+			case FLAG_ONETIME:
 				flags |= FIU_ONETIME;
+				break;
+			default:
+				*error = "Unknown parameter";
+				return -1;
+			}
 		}
+	}
 
-		if (probability >= 0) {
-			return fiu_enable_random(fp_name, failnum, failinfo,
-					probability, flags);
-		} else {
-			return fiu_enable(fp_name, failnum, failinfo, flags);
-		}
+	/* Excecute the command */
+	if (strcmp(command, "disable") == 0) {
+		return fiu_disable(fp_name);
+	} else if (strcmp(command, "enable") == 0) {
+		return fiu_enable(fp_name, failnum, failinfo, flags);
+	} else if (strcmp(command, "enable_random") == 0) {
+		return fiu_enable_random(fp_name, failnum, failinfo,
+				flags, probability);
+	} else if (strcmp(command, "enable_stack_by_name") == 0) {
+		return fiu_enable_stack_by_name(fp_name, failnum, failinfo,
+				flags, func_name, func_pos_in_stack);
 	} else {
+		*error = "Unknown command";
 		return -1;
 	}
 }
@@ -146,12 +197,13 @@ static int rc_do_command(int fdr, int fdw)
 {
 	int len, r, reply_len;
 	char buf[MAX_LINE], reply[MAX_LINE];
+	char *error;
 
 	len = read_line(fdr, buf);
 	if (len <= 0)
 		return len;
 
-	r = rc_process_cmd(buf);
+	r = fiu_rc_string(buf, &error);
 
 	reply_len = snprintf(reply, MAX_LINE, "%d\n", r);
 	r = write(fdw, reply, reply_len);
@@ -282,4 +334,3 @@ int fiu_rc_fifo(const char *basename)
 	return r;
 }
 
-
diff --git a/libfiu/fiu.c b/libfiu/fiu.c
index 8a849dd..cff61bc 100644
--- a/libfiu/fiu.c
+++ b/libfiu/fiu.c
@@ -19,6 +19,7 @@ enum pf_method {
 	PF_ALWAYS = 1,
 	PF_PROB,
 	PF_EXTERNAL,
+	PF_STACK,
 };
 
 /* Point of failure information */
@@ -39,6 +40,12 @@ struct pf_info {
 		/* To use when method == PF_EXTERNAL */
 		external_cb_t *external_cb;
 
+		/* To use when method == PF_STACK */
+		struct stack {
+			void *func_start;
+			void *func_end;
+			int func_pos_in_stack;
+		} stack;
 	} minfo;
 };
 
@@ -167,6 +174,41 @@ static int shrink_enabled_fails(void)
 	return 0;
 }
 
+/* Determines if the given address is within the function code. */
+static int pc_in_func(struct pf_info *pf, void *pc)
+{
+	/* We don't know if the platform allows us to know func_end,
+	 * so we use different methods depending on its availability. */
+	if (pf->minfo.stack.func_end) {
+		return (pc > pf->minfo.stack.func_start &&
+				pc < pf->minfo.stack.func_end);
+	} else {
+		return pf->minfo.stack.func_start == get_func_start(pc);
+	}
+}
+
+/* Determines wether to fail or not the given failure point, which is of type
+ * PF_STACK. Returns 1 if it should fail, or 0 if it should not. */
+static int should_stack_fail(struct pf_info *pf)
+{
+	// TODO: Find the right offset for pos_in_stack: we should look for
+	// fiu_fail(), and start counting from there.
+	int nptrs, i;
+	void *buffer[100];
+
+	nptrs = get_backtrace(buffer, 100);
+
+	for (i = 0; i < nptrs; i++) {
+		if (pc_in_func(pf, buffer[i]) &&
+				(pf->minfo.stack.func_pos_in_stack == -1 ||
+				 i == pf->minfo.stack.func_pos_in_stack)) {
+			return 1;
+		}
+	}
+
+	return 0;
+}
+
 /* Pseudorandom number generator.
  *
  * The performance of the PRNG is very sensitive to us, so we implement our
@@ -259,8 +301,8 @@ int fiu_fail(const char *name)
 
 	rec_count++;
 
-	/* we must do this before acquiring the lock and calling any
-	 * (potentially wrapped) functions */
+	/* We must do this before acquiring the lock and calling any
+	 * (potentially wrapped) functions. */
 	if (rec_count > 1) {
 		rec_count--;
 		return 0;
@@ -291,6 +333,10 @@ int fiu_fail(const char *name)
 						&(pf->flags)))
 					goto exit_fail;
 				break;
+			case PF_STACK:
+				if (should_stack_fail(pf))
+					goto exit_fail;
+				break;
 			default:
 				break;
 			}
@@ -329,42 +375,32 @@ void *fiu_failinfo(void)
  * Control API
  */
 
-/* Sets up the given pf. For internal use only. */
+/* Sets up the given pf.
+ * Only the common fields are filled, the caller should take care of the
+ * method-specific ones. For internal use only. */
 static int setup_fail(struct pf_info *pf, const char *name, int failnum,
-		void *failinfo, unsigned int flags, enum pf_method method,
-		float probability, external_cb_t *external_cb)
+		void *failinfo, unsigned int flags, enum pf_method method)
 {
 	pf->name = strdup(name);
+	if (pf->name == NULL)
+		return -1;
+
 	pf->namelen = strlen(name);
 	pf->failnum = failnum;
 	pf->failinfo = failinfo;
 	pf->flags = flags;
 	pf->method = method;
-	switch (method) {
-		case PF_ALWAYS:
-			break;
-		case PF_PROB:
-			pf->minfo.probability = probability;
-			break;
-		case PF_EXTERNAL:
-			pf->minfo.external_cb = external_cb;
-			break;
-		default:
-			return -1;
-	}
 
-	if (pf->name == NULL)
-		return -1;
 	return 0;
-
 }
 
-/* Creates a new pf in the enabled_fails table. For internal use only. */
-static int insert_new_fail(const char *name, int failnum, void *failinfo,
-		unsigned int flags, enum pf_method method, float probability,
-		external_cb_t *external_cb)
+/* Creates a new pf in the enabled_fails table.
+ * Only the common fields are filled, the caller should take care of the
+ * method-specific ones. For internal use only. */
+static struct pf_info *insert_new_fail(const char *name, int failnum,
+		void *failinfo, unsigned int flags, enum pf_method method)
 {
-	struct pf_info *pf;
+	struct pf_info *pf = NULL;
 	int rv = -1;
 	size_t prev_len;
 
@@ -377,18 +413,20 @@ static int insert_new_fail(const char *name, int failnum, void *failinfo,
 		for (pf = enabled_fails; pf <= enabled_fails_last; pf++) {
 			if (pf->name == NULL || strcmp(pf->name, name) == 0) {
 				rv = setup_fail(pf, name, failnum, failinfo,
-						flags, method, probability,
-						external_cb);
-				if (rv == 0)
-					enabled_fails_nfree--;
+						flags, method);
+				if (rv != 0) {
+					pf = NULL;
+					goto exit;
+				}
 
+				enabled_fails_nfree--;
 				goto exit;
 			}
 		}
 
 		/* There should be a free slot, but couldn't find one! This
 		 * shouldn't happen */
-		rv = -1;
+		pf = NULL;
 		goto exit;
 	}
 
@@ -399,7 +437,7 @@ static int insert_new_fail(const char *name, int failnum, void *failinfo,
 		enabled_fails_last = NULL;
 		enabled_fails_len = 0;
 		enabled_fails_nfree = 0;
-		rv = -1;
+		pf = NULL;
 		goto exit;
 	}
 
@@ -413,39 +451,95 @@ static int insert_new_fail(const char *name, int failnum, void *failinfo,
 	enabled_fails_last = enabled_fails + enabled_fails_len - 1;
 
 	pf = enabled_fails + prev_len;
-	rv = setup_fail(pf, name, failnum, failinfo, flags, method,
-			probability, external_cb);
-	if (rv == 0)
-		enabled_fails_nfree--;
+	rv = setup_fail(pf, name, failnum, failinfo, flags, method);
+	if (rv != 0) {
+		pf = NULL;
+		goto exit;
+	}
+
+	enabled_fails_nfree--;
 
 exit:
 	ef_wunlock();
 	rec_count--;
-	return rv;
+	return pf;
 }
 
 /* Makes the given name fail. */
 int fiu_enable(const char *name, int failnum, void *failinfo,
 		unsigned int flags)
 {
-	return insert_new_fail(name, failnum, failinfo, flags, PF_ALWAYS, 0,
-			NULL);
+	struct pf_info *pf;
+
+	pf = insert_new_fail(name, failnum, failinfo, flags, PF_ALWAYS);
+	if (pf == NULL)
+		return -1;
+
+	return 0;
 }
 
 /* Makes the given name fail with the given probability. */
 int fiu_enable_random(const char *name, int failnum, void *failinfo,
 		unsigned int flags, float probability)
 {
-	return insert_new_fail(name, failnum, failinfo, flags, PF_PROB,
-			probability, NULL);
+	struct pf_info *pf;
+
+	pf = insert_new_fail(name, failnum, failinfo, flags, PF_PROB);
+	if (pf == NULL)
+		return -1;
+
+	pf->minfo.probability = probability;
+	return 0;
 }
 
 /* Makes the given name fail when the external function returns != 0. */
 int fiu_enable_external(const char *name, int failnum, void *failinfo,
 		unsigned int flags, external_cb_t *external_cb)
 {
-	return insert_new_fail(name, failnum, failinfo, flags, PF_EXTERNAL,
-			0, external_cb);
+	struct pf_info *pf;
+
+	pf = insert_new_fail(name, failnum, failinfo, flags, PF_EXTERNAL);
+	if (pf == NULL)
+		return -1;
+
+	pf->minfo.external_cb = external_cb;
+	return 0;
+}
+
+/* Makes the given name fail when func is in the stack at func_pos.
+ * If func_pos is -1, then any position will match. */
+int fiu_enable_stack(const char *name, int failnum, void *failinfo,
+		unsigned int flags, void *func, int func_pos_in_stack)
+{
+	struct pf_info *pf;
+
+	/* Specifying the stack position is unsupported for now */
+	if (func_pos_in_stack != -1)
+		return -1;
+
+	pf = insert_new_fail(name, failnum, failinfo, flags, PF_STACK);
+	if (pf == NULL)
+		return -1;
+
+	pf->minfo.stack.func_start = func;
+	pf->minfo.stack.func_end = get_func_end(func);
+	pf->minfo.stack.func_pos_in_stack = func_pos_in_stack;
+	return 0;
+}
+
+/* Same as fiu_enable_stack(), but takes a function name. */
+int fiu_enable_stack_by_name(const char *name, int failnum, void *failinfo,
+		unsigned int flags, const char *func_name,
+		int func_pos_in_stack)
+{
+	void *fp;
+
+	fp = get_func_addr(func_name);
+	if (fp == NULL)
+		return -1;
+
+	return fiu_enable_stack(name, failnum, failinfo, flags, fp,
+			func_pos_in_stack);
 }
 
 /* Makes the given name NOT fail. */
diff --git a/libfiu/fiu.h b/libfiu/fiu.h
index ea35cd6..7659b7f 100644
--- a/libfiu/fiu.h
+++ b/libfiu/fiu.h
@@ -1,11 +1,11 @@
 
-/* libfiu - Fault Injection in Userspace
+/** @file
  *
- * This header, part of libfiu, contains the API that your project should use.
+ * This header contains the API that your project should use when defining
+ * points of failure in your real (non-testing) code.
  *
  * If you want to avoid having libfiu as a mandatory build-time dependency,
- * you should include fiu-stubs.h in your project, and #include it instead of
- * this.
+ * you should add fiu-local.h to your project, and \#include that instead.
  */
 
 #ifndef _FIU_H
@@ -20,25 +20,34 @@ extern "C" {
 #endif
 
 
-/* Initializes the library.
+/** Initializes the library.
  *
- * - flags: unused.
- * - returns: 0 if success, < 0 if error. */
+ * Must be called before any other library function. It is safe to invoke it
+ * more than once.
+ *
+ * @param flags  Unused.
+ * @returns  0 if success, < 0 if error.
+ */
 int fiu_init(unsigned int flags);
 
-/* Returns the failure status of the given point of failure.
+/** Returns the failure status of the given point of failure.
  *
- * - name: point of failure name.
- * - returns: the failure status (0 means it should not fail). */
+ * @param name  Point of failure name.
+ * @returns  The failure status (0 means it should not fail).
+ */
 int fiu_fail(const char *name);
 
-/* Returns the information associated with the last failure.
+/** Returns the information associated with the last failure.
  *
- * - returns: the information associated with the last fail, or NULL if there
- *      isn't one. */
+ * Please note that this function is thread-safe and thread-local, so the
+ * information returned will be for the last failure in the same thread.
+ *
+ * @returns  The information associated with the last failure, or NULL if
+ * 		there isn't one.
+ */
 void *fiu_failinfo(void);
 
-/* Performs the given action when the given point of failure fails. Mostly
+/** Performs the given action when the given point of failure fails. Mostly
  * used in the following macros. */
 #define fiu_do_on(name, action) \
         do { \
@@ -47,10 +56,10 @@ void *fiu_failinfo(void);
                 } \
         } while (0)
 
-/* Exits the program when the given point of failure fails. */
+/** Exits the program when the given point of failure fails. */
 #define fiu_exit_on(name) fiu_do_on(name, exit(EXIT_FAILURE))
 
-/* Makes the function return the given retval when the given point of failure
+/** Makes the function return the given retval when the given point of failure
  * fails. */
 #define fiu_return_on(name, retval) fiu_do_on(name, return retval)
 
@@ -60,8 +69,8 @@ void *fiu_failinfo(void);
 #endif
 
 #else
-/* fiu not enabled, this should match fiu-local.h but we don't include it
- * because it includes us assuming we're installed system-wide */
+/* These are used when fiu not enabled. They should match fiu-local.h but we
+ * don't include it to avoid a circular dependency. */
 
 #define fiu_init(flags) 0
 #define fiu_fail(name) 0
diff --git a/libfiu/internal.h b/libfiu/internal.h
index 2c3bfba..1cb95ff 100644
--- a/libfiu/internal.h
+++ b/libfiu/internal.h
@@ -7,5 +7,21 @@
 /* Recursion count, used both in fiu.c and fiu-rc.c */
 extern __thread int rec_count;
 
+
+/* Gets a stack trace. The pointers are stored in the given buffer, which must
+ * be of the given size. The number of entries is returned.
+ * It's a wrapper around glibc's backtrace(). */
+int get_backtrace(void *buffer, int size);
+
+/* Returns a pointer to the start of the function containing the given code
+ * address, or NULL if it can't find any. */
+void *get_func_end(void *pc);
+
+/* Returns a pointer to the end of the given function. */
+void *get_func_start(void *func);
+
+/* Returns a pointer to the function given by name. */
+void *get_func_addr(const char *func_name);
+
 #endif
 
diff --git a/libfiu/libfiu.3 b/libfiu/libfiu.3
index b7e3616..ac9b406 100644
--- a/libfiu/libfiu.3
+++ b/libfiu/libfiu.3
@@ -25,6 +25,12 @@ libfiu - Fault injection in userspace
 .BI "int fiu_enable_external(const char *" name ", int " failnum ","
 .BI "		void *" failinfo ", unsigned int " flags ","
 .BI "		external_cb_t *" external_cb ");"
+.BI "int fiu_enable_stack(const char *" name ", int " failnum ","
+.BI "		void *" failinfo ", unsigned int " flags ","
+.BI "		void *" func ", int " func_pos_in_stack ");"
+.BI "int fiu_enable_stack_by_name(const char *" name ", int " failnum ","
+.BI "		void *" failinfo ", unsigned int " flags ","
+.BI "		const char *" func_name ", int " func_pos_in_stack ");"
 .BI "int fiu_disable(const char *" name ");"
 .BI "int fiu_rc_fifo(const char *" basename ");"
 .sp
@@ -46,7 +52,10 @@ in the distribution.
 .SS CORE API
 
 To use the core API, you should
-.IR "#include <fiu.h>" .
+.I "#include <fiu.h>"
+(or
+.IR "<fiu-local.h>" ,
+see below).
 
 Because fault injection is usually a debugging/testing facility, unwanted at
 runtime, some special considerations were taken to minimize the impact of the
@@ -55,7 +64,7 @@ core API. First of all, if
 is not defined, then fiu.h will define empty stubs for all the core API,
 effectively disabling fault injection completely.
 
-Also, a special header
+Also, the special header
 .I fiu-local.h
 is shipped with libfiu. It is meant to be included in your project to avoid
 having libfiu as a mandatory build-time dependency. You can add it to your
@@ -84,7 +93,8 @@ runtime using the control API.
 .TP
 .BI "fiu_failinfo()"
 Returns the information associated with the last failure, or NULL if there
-isn't one.
+isn't one. This function is thread-aware and will only return information
+about failures in the calling thread.
 
 .TP
 .BI "fiu_do_on(" name ", " action ") [macro]"
@@ -158,6 +168,39 @@ same as the ones in
 .BR fiu_enable() .
 
 .TP
+.BI "int fiu_enable_stack(" name ", " failnum ", " failinfo ", " flags ", " func ", " func_pos_in_stack ")"
+Enables the given point of failure, but only if
+.I func
+is in the stack at
+.IR func_pos_in_stack .
+
+.I func
+must be a function pointer, and
+.I func_pos_in_stack
+is the position where we expect the function to be, or -1 for "any" (values
+other than -1 are not yet supported). The rest of the parameters, as well as
+the return value, are the same as the ones in
+.BR fiu_enable() .
+
+This function relies on some GNU extensions, so it may be not available in all
+platforms.
+
+.TP
+.BI "int fiu_enable_stack_by_name(" name ", " failnum ", " failinfo ", " flags ", " func_name ", " func_pos_in_stack ")"
+Enables the given point of failure, but only if
+.I func_name
+is in the stack at
+.IR func_pos_in_stack .
+
+.I func
+must be the name of a function (resolved at runtime using dlsym()); the rest
+of the parameters, as well as the return value, are the same as the ones in
+.BR fiu_enable_stack .
+
+This function relies on some GNU extensions, so it may be not available in all
+platforms.
+
+.TP
 .BI "fiu_disable(" name ")"
 Disables the given point of failure, undoing the actions of the
 .B fiu_enable*()
diff --git a/preload/posix/codegen.h b/preload/posix/codegen.h
index 0999159..80b3344 100644
--- a/preload/posix/codegen.h
+++ b/preload/posix/codegen.h
@@ -160,6 +160,7 @@ extern int __thread _fiu_called;
 		if (_fiu_orig_##NAME == NULL)			\
 			_fiu_init_##NAME();			\
 								\
+		printd("calling orig\n");			\
 		r = (*_fiu_orig_##NAME) PARAMSN;		\
 								\
 	exit:							\
diff --git a/preload/posix/modules/libc.mm.custom.c b/preload/posix/modules/libc.mm.custom.c
index 4ed23e7..e94dec3 100644
--- a/preload/posix/modules/libc.mm.custom.c
+++ b/preload/posix/modules/libc.mm.custom.c
@@ -68,6 +68,7 @@ static void *fiu_malloc_hook(size_t size, const void *caller)
 		goto exit;
 	}
 
+	printd("calling orig\n");
 	r = malloc(size);
 
 exit:
@@ -96,6 +97,7 @@ static void *fiu_realloc_hook(void *ptr, size_t size, const void *caller)
 		goto exit;
 	}
 
+	printd("calling orig\n");
 	r = realloc(ptr, size);
 
 exit:
diff --git a/preload/run/fiu-run.1 b/preload/run/fiu-run.1
index 888cce4..7e2488b 100644
--- a/preload/run/fiu-run.1
+++ b/preload/run/fiu-run.1
@@ -21,21 +21,9 @@ For additional documentation, go to the project's website at
 
 .SH OPTIONS
 .TP
-.B "-e fpname"
-Enable the given failure point name.
-.TP
-.B "-p prob"
-Use the given probability for the previous failure point. In percent, defaults
-to 100, which means "always enabled". Must come \fIafter\fR the \fB-e\fR it
-affects.
-.TP
-.B "-u failnum"
-Use the given number as the failnum for the previous failure point. Must be !=
-0, defaults to 1. Must come \fIafter\fR the \fB-e\fR it affects.
-.TP
-.B "-i failinfo"
-Use the given number as the failinfo for the previous failure point. Defaults
-to 0. Must come \fIafter\fR the \fB-e\fR it affects.
+.B "-c command"
+Run the given libfiu remote control command before executing the program (see
+below for reference).
 .TP
 .B -x
 Use the POSIX libfiu preload library, allows simulate failures in the POSIX
@@ -50,19 +38,31 @@ over named pipes.
 .B "-l path"
 Path where to find the libfiu preload libraries. Defaults to the path where
 they were installed, so it is usually correct.
+.P
 
+Remote control commands are of the form
+"\fIcommand param1=value1,param2=value2\fR".
+Valid commands are:
+.TP
+.B 'enable name=NAME'
+Enables the NAME failure point unconditionally.
+.TP
+.B 'enable_random name=NAME,probability=P'
+Enables the NAME failure point with a probability of P.
 .P
-You can enable any number of failure points by using \fB-e\fR repeatedly.
+
+All of the \fIenable*\fR commands can also optionally take \fIfailnum\fR and
+\fIfailinfo\fR parameters, analogous to the ones taken by the C functions.
+
 
 .SH EXAMPLES
-The following will run the \fBfortune\fR(1) program simulating faults in all
-the POSIX I/O functions with a 10% probability, and in malloc() with 5%
-probability (note that the \fB-x\fR parameter is required in this case to
-enable failure points in the POSIX and libc functions):
+The following will run the \fBfortune\fR(1) program simulating faults in
+read() with 5% probability (note that the \fB-x\fR parameter is required in
+this case to enable failure points in the POSIX and libc functions):
 
 .RS
 .nf
-fiu\-run \-x \-e posix/io/* \-p 10 \-e libc/mm/malloc \-p 5 fortune
+fiu\-run \-x \-c "enable_random name=posix/io/rw/read,probability=0.05" fortune
 .fi
 .RE
 
diff --git a/preload/run/fiu-run.in b/preload/run/fiu-run.in
index db33777..0fe385f 100644
--- a/preload/run/fiu-run.in
+++ b/preload/run/fiu-run.in
@@ -25,27 +25,39 @@ Usage: fiu-run [options] program [arguments]
 
 The following options are supported:
 
-  -e fpname	Enable the given failure point name.
-  -p prob	... with the given probability (defaults to 100%).
-  -u failnum	... and this failnum (must be != 0) (defaults to 1).
-  -i failinfo	... and this failinfo (defaults to 0).
   -x		Use POSIX libfiu preload library, allows simulate failures in
 		the POSIX and C standard library functions.
+  -c command	Run the given libfiu remote control command before executing
+		the program (see below for reference).
   -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).
 
-The -p, -u and -i options must come after the -e they affect.
+Remote control commands are of the form 'command param1=value1,param2=value2'.
+Valid commands are:
 
-For example:
+ - 'enable name=NAME'
+     Enables the NAME failure point unconditionally.
+ - 'enable_random name=NAME,probability=P'
+     Enables the NAME failure point with a probability of P.
 
-  fiu-run -x -e 'posix/io/*' -p 25 -e libc/mm/malloc -p 5 ls -l
+All of them can also optionally take 'failnum' and 'failinfo' parameters,
+analogous to the ones taken by the C functions.
 
-will run \"ls -l\" enabling all failure points that begin with 'posix/io/'
-with a 25% of probability to fail, and the failure point libc/mm/malloc with a
-5% of probability to fail.
+The following options existed in the past but are deprecated and WILL BE
+REMOVED in future releases: -e, -p, -u and -i.
+
+
+Example:
+
+  fiu-run -x -c 'enable_random name=posix/io/*,probability=0.25' \\
+             -c 'enable_random name=libc/mm/*,probability=0.05' ls -l
+
+Run \"ls -l\" enabling all failure points that begin with 'posix/io/' with a
+25% of probability to fail, and the failure point libc/mm/malloc with a 5% of
+probability to fail.
 "
 
 
@@ -61,31 +73,35 @@ fi
 function opts_reset() {
 	# variables to store what we know so far; after a new name is found
 	# the old one is added to $ENABLE
-	NAME=""
-	PROB=-1
-	FAILNUM=1
-	FAILINFO=0
+	DEP_NAME=""
+	DEP_PROB=-1
+	DEP_FAILNUM=1
+	DEP_FAILINFO=0
+}
+
+function add_deprecated_enable() {
+	if [ "$NAME" == "" ]; then
+		return
+	fi;
+
+	PARAMS="name=$DEP_NAME,failnum=$DEP_FAILNUM,failinfo=$DEP_FAILINFO"
+	if [ $PROB -ge 0 ]; then
+		C="enable_random $PARAMS,probability=$PROB"
+	else
+		C="enable $PARAMS"
+	fi
+
+	ENABLE="$ENABLE
+		$C"
 }
 
 opts_reset;
-while getopts "+e:p:u:i:f:l:xh" opt; do
+while getopts "+c:f:l:xe:p:u:i:h" opt; do
 	case $opt in
-	e)
-		# add the current one, if any
-		if [ "$NAME" != "" ]; then
-			ENABLE="$ENABLE:$NAME,$PROB,$FAILNUM,$FAILINFO"
-			opts_reset;
-		fi
-		NAME="$OPTARG"
-		;;
-	p)
-		PROB="$OPTARG"
-		;;
-	u)
-		FAILNUM="$OPTARG"
-		;;
-	i)
-		FAILINFO="$OPTARG"
+	c)
+		# Note we use the newline as a command separator.
+		ENABLE="$ENABLE
+			$OPTARG"
 		;;
 	f)
 		FIFO_PREFIX="$OPTARG"
@@ -96,6 +112,23 @@ while getopts "+e:p:u:i:f:l:xh" opt; do
 	x)
 		USE_POSIX_PRELOAD=1
 		;;
+
+	# Deprecated options
+	e)
+		add_deprecated_enable
+		opts_reset
+		DEP_NAME="$OPTARG"
+		;;
+	p)
+		DEP_PROB="$OPTARG"
+		;;
+	u)
+		DEP_FAILNUM="$OPTARG"
+		;;
+	i)
+		DEP_FAILINFO="$OPTARG"
+		;;
+
 	h|*)
 		echo "$HELP_MSG"
 		exit 1
@@ -104,10 +137,8 @@ while getopts "+e:p:u:i:f:l:xh" opt; do
 done
 
 # add leftovers
-if [ "$NAME" != "" ]; then
-	ENABLE="$ENABLE:$NAME,$PROB,$FAILNUM,$FAILINFO"
-	opts_reset;
-fi
+add_deprecated_enable
+opts_reset;
 
 # eat the parameters we already processed
 shift $(( $OPTIND - 1 ))
diff --git a/preload/run/run.c b/preload/run/run.c
index 8687b32..74ca75b 100644
--- a/preload/run/run.c
+++ b/preload/run/run.c
@@ -8,123 +8,46 @@
 #include <fiu.h>
 #include <fiu-control.h>
 
-/* Maximum size of parameters to the enable options */
-#define MAX_ENOPT 128
-
-enum fp_type {
-	FP_ALWAYS = 1,
-	FP_PROB = 2,
-};
-
-struct enable_option {
-	char buf[MAX_ENOPT];
-	enum fp_type type;
-	char *name;
-	int failnum;
-	unsigned long failinfo;
-	float probability;
-};
-
-/* Fills the enopt structure taking the values from the given string, which is
- * assumed to be in of the form:
- *
- *   name,probability,failnum,failinfo
- *
- * All fields are optional, except for the name. On error, enopt->name will be
- * set to NULL. Probability is in percent, -1 indicates it should always be
- * enabled. */
-static void parse_enable(const char *s, struct enable_option *enopt)
-{
-	char *tok;
-	char *state;
-
-	enopt->name = NULL;
-	enopt->type = FP_ALWAYS;
-	enopt->failnum = 1;
-	enopt->failinfo = 0;
-	enopt->probability = 1;
-
-	memset(enopt->buf, 0, MAX_ENOPT);
-	strncpy(enopt->buf, s, MAX_ENOPT);
-
-	tok = strtok_r(enopt->buf, ",", &state);
-	if (tok == NULL)
-		return;
-	enopt->name = tok;
-
-	tok = strtok_r(NULL, ",", &state);
-	if (tok == NULL)
-		return;
-	if (atoi(tok) >= 0) {
-		enopt->type = FP_PROB;
-		enopt->probability = atof(tok) / 100.0;
-	}
-
-	tok = strtok_r(NULL, ",", &state);
-	if (tok == NULL)
-		return;
-	enopt->failnum = atoi(tok);
-
-	tok = strtok_r(NULL, ",", &state);
-	if (tok == NULL)
-		return;
-	enopt->failinfo = atol(tok);
-}
 
 static void __attribute__((constructor)) fiu_run_init(void)
 {
-	int r;
-	struct enable_option enopt;
-	char *fiu_enable_env, *fiu_enable_s, *fiu_fifo_env;
-	char *tok, *state;
+	char *fiu_fifo_env, *fiu_enable_env;
 
 	fiu_init(0);
 
 	fiu_fifo_env = getenv("FIU_CTRL_FIFO");
-	if (fiu_fifo_env != NULL && *fiu_fifo_env != '\0') {
+	if (fiu_fifo_env && *fiu_fifo_env != '\0') {
 		if (fiu_rc_fifo(fiu_fifo_env) < 0) {
 			perror("fiu_run_preload: Error opening RC fifo");
 		}
 	}
 
 	fiu_enable_env = getenv("FIU_ENABLE");
-	if (fiu_enable_env == NULL)
-		return;
-
-	/* copy fiu_enable_env to fiu_enable_s so we can strtok() it */
-	fiu_enable_s = malloc(strlen(fiu_enable_env) + 1);
-	strcpy(fiu_enable_s, fiu_enable_env);
-
-	state = NULL;
-	tok = strtok_r(fiu_enable_s, ":", &state);
-	while (tok != NULL) {
-		parse_enable(tok, &enopt);
-		if (enopt.name == NULL) {
-			fprintf(stderr, "fiu-run.so: "
-					"ignoring enable without name");
-			tok = strtok_r(NULL, ":", &state);
-			continue;
+	if (fiu_enable_env && fiu_enable_env != '\0') {
+		/* FIU_ENABLE can contain more than one command, separated by
+		 * a newline, so we split them and call fiu_rc_string()
+		 * accordingly. */
+		char *tok, *state;
+		char *env_copy;
+		char *rc_error;
+
+		env_copy = strdup(fiu_enable_env);
+		if (env_copy == NULL) {
+			perror("fiu_run_preload: Error in strdup()");
+			return;
 		}
 
-		if (enopt.type == FP_ALWAYS) {
-			r = fiu_enable(enopt.name, enopt.failnum,
-					(void *) enopt.failinfo, 0);
-		} else {
-			r = fiu_enable_random(enopt.name, enopt.failnum,
-					(void *) enopt.failinfo, 0,
-					enopt.probability);
+		tok = strtok_r(env_copy, "\n", &state);
+		while (tok) {
+			if (fiu_rc_string(tok, &rc_error) != 0) {
+				fprintf(stderr,
+					"fiu_run_preload: Error applying "
+						"FIU_ENABLE commands: %s\n",
+						rc_error);
+				return;
+			}
+			tok = strtok_r(NULL, "\n", &state);
 		}
-
-		if (r < 0) {
-			fprintf(stderr, "fiu-run.so: "
-					"couldn't enable failure %s\n",
-					enopt.name);
-			continue;
-		}
-
-		tok = strtok_r(NULL, ":", &state);
 	}
-
-	free(fiu_enable_s);
 }
 
diff --git a/tests/Makefile b/tests/Makefile
new file mode 100644
index 0000000..9fb7875
--- /dev/null
+++ b/tests/Makefile
@@ -0,0 +1,88 @@
+
+CFLAGS += -std=c99 -pedantic -Wall -rdynamic
+ALL_CFLAGS = -I../libfiu/ -L../libfiu/ \
+	-D_XOPEN_SOURCE=600 -D_GNU_SOURCE -fPIC -DFIU_ENABLE=1 $(CFLAGS)
+
+ifdef DEBUG
+ALL_CFLAGS += -g
+endif
+
+ifdef PROFILE
+ALL_CFLAGS += -g -pg -fprofile-arcs -ftest-coverage
+endif
+
+ifneq ($(V), 1)
+	NICE_CC = @echo "  CC  $@"; $(CC)
+	NICE_RUN = @echo "  RUN $<"; LD_LIBRARY_PATH=../libfiu/
+	NICE_PY_RUN = @echo "  PY  $<"; \
+		      LD_LIBRARY_PATH=../libfiu/ \
+		      PYTHONPATH=../bindings/python/
+else
+	NICE_CC = $(CC)
+	NICE_RUN = LD_LIBRARY_PATH=../libfiu/
+	NICE_PY_RUN = LD_LIBRARY_PATH=../libfiu/ \
+		      PYTHONPATH=../bindings/python/
+endif
+
+default: tests
+
+all: tests
+
+tests: c-tests py-tests
+
+#
+# C tests
+#
+
+C_SRCS := $(wildcard test-*.c)
+C_OBJS := $(patsubst %.c,%.o,$(C_SRCS))
+C_BINS := $(patsubst %.c,%,$(C_SRCS))
+
+c-tests: $(patsubst %.c,c-run-%,$(C_SRCS))
+
+test-%: test-%.o build-flags
+	$(NICE_CC) $(ALL_CFLAGS) $< -lfiu -o $@
+
+c-run-%: %
+	$(NICE_RUN) ./$<
+
+
+BF = $(ALL_CFLAGS) ~ $(PREFIX)
+build-flags: .force-build-flags
+	@if [ x"$(BF)" != x"`cat build-flags 2>/dev/null`" ]; then \
+		if [ -f build-flags ]; then \
+			echo "build flags changed, rebuilding"; \
+		fi; \
+		echo "$(BF)" > build-flags; \
+	fi
+
+.c.o:
+	$(NICE_CC) $(ALL_CFLAGS) -c $< -o $@
+
+
+#
+# Python tests
+#
+
+PY_TESTS := $(wildcard test-*.py)
+
+py-tests: $(patsubst %.py,py-run-%,$(PY_TESTS))
+
+py-run-%: %.py
+	$(NICE_PY_RUN) python ./$<
+
+
+clean:
+	# Normally, $C_OBJS and $C_BINS are removed by make after building,
+	# since here they're considered "intermediate files"; however we
+	# also remove them when cleaning just in case.
+	rm -f $(C_OBJS) $(C_BINS)
+	rm -f *.bb *.bbg *.da *.gcov *.gcda *.gcno gmon.out build-flags
+
+FORCE:
+
+.PHONY: default all clean \
+	tests c-tests py-tests \
+	.force-build-flags
+
+
diff --git a/tests/test-enable_stack.c b/tests/test-enable_stack.c
new file mode 100644
index 0000000..588390c
--- /dev/null
+++ b/tests/test-enable_stack.c
@@ -0,0 +1,46 @@
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <assert.h>
+
+#include <fiu.h>
+#include <fiu-control.h>
+
+void func1(int should_fail)
+{
+	int failed;
+
+	/*
+	int nptrs;
+	void *buffer[100];
+	nptrs = backtrace(buffer, 100);
+	backtrace_symbols_fd(buffer, nptrs, 1);
+	*/
+
+	failed = fiu_fail("fp-1") != 0;
+	assert(failed == should_fail);
+}
+
+void func2(int should_fail)
+{
+	func1(should_fail);
+}
+
+
+int main(void)
+{
+	fiu_init(0);
+	fiu_enable_stack("fp-1", 1, NULL, 0, (void *) &func2, -1);
+
+	func1(0);
+	func2(1);
+
+
+	fiu_disable("fp-1");
+
+	func1(0);
+	func2(0);
+
+	return 0;
+}
+
diff --git a/tests/test-enable_stack_by_name.c b/tests/test-enable_stack_by_name.c
new file mode 100644
index 0000000..78c56ce
--- /dev/null
+++ b/tests/test-enable_stack_by_name.c
@@ -0,0 +1,46 @@
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <assert.h>
+
+#include <fiu.h>
+#include <fiu-control.h>
+
+void func1(int should_fail)
+{
+	int failed;
+
+	/*
+	int nptrs;
+	void *buffer[100];
+	nptrs = backtrace(buffer, 100);
+	backtrace_symbols_fd(buffer, nptrs, 1);
+	*/
+
+	failed = fiu_fail("fp-1") != 0;
+	assert(failed == should_fail);
+}
+
+void func2(int should_fail)
+{
+	func1(should_fail);
+}
+
+
+int main(void)
+{
+	fiu_init(0);
+	fiu_enable_stack_by_name("fp-1", 1, NULL, 0, "func2", -1);
+
+	func1(0);
+	func2(1);
+
+
+	fiu_disable("fp-1");
+
+	func1(0);
+	func2(0);
+
+	return 0;
+}
+
diff --git a/tests/test-failinfo_refcount.py b/tests/test-failinfo_refcount.py
new file mode 100644
index 0000000..79062be
--- /dev/null
+++ b/tests/test-failinfo_refcount.py
@@ -0,0 +1,21 @@
+
+"""
+Test that we keep references to failinfo as needed.
+"""
+
+import fiu
+
+# Object we'll use for failinfo
+finfo = [1, 2, 3]
+
+fiu.enable('p1', failinfo = finfo)
+
+assert fiu.fail('p1')
+assert fiu.failinfo('p1') is finfo
+
+finfo_id = id(finfo)
+del finfo
+
+assert fiu.failinfo('p1') == [1, 2, 3]
+assert id(fiu.failinfo('p1')) == finfo_id
+
diff --git a/utils/fiu-ctrl b/utils/fiu-ctrl
index a08e06d..140bb37 100755
--- a/utils/fiu-ctrl
+++ b/utils/fiu-ctrl
@@ -18,28 +18,43 @@ Usage: fiu-ctrl [options] PID [PID ...]
 
 The following options are supported:
 
-  -e fpname	Enable the given failure point name.
-  -p prob	... with the given probability (defaults to 100%).
-  -u failnum	... and this failnum (must be != 0) (defaults to 1).
-  -i failinfo	... and this failinfo (defaults to 0).
-  -d fpname	Disable the given failure point name.
-  -f ctrlpath	Set the default prefix for remote control over named pipes.
-		(defaults to \"$FIFO_PREFIX\", which is usually correct if
-		the program was run using fiu-run(1)).
+  -c command	Run the given libfiu remote control command before executing
+		the program (see below for reference).
+  -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).
 
-The -p, -u and -i options must come after the -e they affect.
+Remote control commands are of the form 'command param1=value1,param2=value2'.
+Valid commands are:
 
-For example:
+ - 'enable name=NAME'
+     Enables the NAME failure point unconditionally.
+ - 'enable_random name=NAME,probability=P'
+     Enables the NAME failure point with a probability of P.
+ - 'disable name=NAME'
+     Disables the NAME failure point.
 
-  fiu-ctrl -e posix/io/read -p 25 -e libc/mm/malloc -p 5 12345
+All of the enable\* can also optionally take 'failnum' and 'failinfo'
+parameters, analogous to the ones taken by the C functions.
 
-will tell the process with pid 12345 to enable the failure point
-'posix/io/read' with a 25% of probability to fail, and the failure point
-'libc/mm/malloc' with a 5% of probability to fail. And:
+The following options existed in the past but are deprecated and WILL BE
+REMOVED in future releases: -e, -p, -u and -i.
 
-  fiu-ctrl -d posix/io/read 12345
 
-will tell the same process to disable the previously enabled failure point.
+Examples:
+
+  fiu-ctrl -c 'enable_random name=posix/io/*,probability=0.25' \\
+           -c 'enable_random name=libc/mm/*,probability=0.05' 12345
+
+Tell the process with pid 12345 to enable the failure point 'posix/io/read'
+with a 25% of probability to fail, and the failure point 'libc/mm/malloc' with
+a 5% of probability to fail.
+
+  fiu-ctrl -c 'disable name=posix/io/read' 12345
+
+Tell the same process to disable the previously enabled failure point.
 
 You can control multiple processes at once by specifiying more than one
 process ID.
@@ -57,48 +72,54 @@ fi
 
 function opts_reset() {
 	# variables to store what we know so far; after a new name is found
-	# the old one is added to $ENABLE
-	NAME=""
-	PROB=-1
-	FAILNUM=1
-	FAILINFO=0
+	# the old one is added to $CMD
+	DEP_NAME=""
+	DEP_PROB=-1
+	DEP_FAILNUM=1
+	DEP_FAILINFO=0
 }
 
-function add_cmd() {
-	if [ "$NAME" != "" ]; then
-		if [ $PROB -ge 0 ]; then
-			C="enable_random $NAME $PROB $FAILNUM $FAILINFO"
-			CMDS[${#CMDS[*]}]="$C"
-		else
-			CMDS[${#CMDS[*]}]="enable $NAME $FAILNUM $FAILINFO"
-		fi
-		opts_reset;
+function add_deprecated_enable() {
+	if [ "$NAME" == "" ]; then
+		return
+	fi;
+
+	PARAMS="name=$DEP_NAME,failnum=$DEP_FAILNUM,failinfo=$DEP_FAILINFO"
+	if [ $PROB -ge 0 ]; then
+		C="enable_random $PARAMS,probability=$PROB"
+	else
+		C="enable $PARAMS"
 	fi
+
+	CMDS[${#CMDS[*]}]="$C"
 }
 
 opts_reset;
-while getopts "+e:p:u:i:d:f:h" opt; do
+while getopts "+c:e:p:u:i:d:f:h" opt; do
 	case $opt in
+	c)
+		CMDS[${#CMDS[*]}]="$OPTARG"
+		;;
 	e)
 		# add the current one, if any
-		add_cmd;
+		add_deprecated_enable;
 		opts_reset;
-		NAME="$OPTARG"
+		DEP_NAME="$OPTARG"
 		;;
 	p)
-		PROB="$OPTARG"
+		DEP_PROB="$OPTARG"
 		;;
 	u)
-		FAILNUM="$OPTARG"
+		DEP_FAILNUM="$OPTARG"
 		;;
 	i)
-		FAILINFO="$OPTARG"
+		DEP_FAILINFO="$OPTARG"
 		;;
 	f)
 		FIFO_PREFIX="$OPTARG"
 		;;
 	d)
-		CMDS[${#CMDS[*]}]="disable $OPTARG"
+		CMDS[${#CMDS[*]}]="disable name=$OPTARG"
 		opts_reset;
 		;;
 	h|*)
@@ -142,9 +163,9 @@ function send_cmd_fifo() {
 	P=$1
 	shift 1
 	echo "$@" > $P.in
-	R="`cat $P.out`"
-	if [ "$R" -eq -1 ]; then
-		echo "$P: Command returned error"
+	REPLY=$(cat "$P.out")
+	if [ "$REPLY" == "-1" ]; then
+		echo "$P: Command '$@' returned error ($REPLY)"
 	fi
 }
 
diff --git a/utils/fiu-ctrl.1 b/utils/fiu-ctrl.1
index d739f9a..b49e90c 100644
--- a/utils/fiu-ctrl.1
+++ b/utils/fiu-ctrl.1
@@ -17,29 +17,32 @@ For additional documentation, go to the project's website at
 
 .SH OPTIONS
 .TP
-.B "-e fpname"
-Enable the given failure point name.
-.TP
-.B "-p prob"
-Use the given probability for the previous failure point. In percent, defaults
-to 100, which means "always enabled". Must come \fIafter\fR the \fB-e\fR it
-affects.
-.TP
-.B "-u failnum"
-Use the given number as the failnum for the previous failure point. Must be !=
-0, defaults to 1. Must come \fIafter\fR the \fB-e\fR it affects.
-.TP
-.B "-i failinfo"
-Use the given number as the failinfo for the previous failure point. Defaults
-to 0. Must come \fIafter\fR the \fB-e\fR it affects.
-.TP
-.B -d
-Disable the given failure point name.
+.B "-c command"
+Run the given libfiu remote control command before executing the program (see
+below for reference).
 .TP
 .B "-f ctrlpath"
 Set the default prefix for remote control over named pipes. Defaults to
 "$TMPDIR/fiu-ctrl", or "/tmp/fiu-ctrl" if "$TMPDIR" is not set, which is the
 usually correct for programs launched using \fBfiu-run\fR(1).
+.P
+
+Remote control commands are of the form
+"\fIcommand param1=value1,param2=value2\fR".
+Valid commands are:
+.TP
+.B 'enable name=NAME'
+Enables the NAME failure point unconditionally.
+.TP
+.B 'enable_random name=NAME,probability=P'
+Enables the NAME failure point with a probability of P.
+.TP
+.B 'disable name=NAME'
+Disables the NAME failure point.
+.P
+
+All of the \fIenable*\fR commands can also optionally take \fIfailnum\fR and
+\fIfailinfo\fR parameters, analogous to the ones taken by the C functions.
 
 
 .SH EXAMPLES
@@ -49,7 +52,8 @@ failure point \fIlibc/mm/malloc\fR with a 5% of probability to fail:
 
 .RS
 .nf
-fiu\-ctrl \-e posix/io/read \-p 25 \-e libc/mm/malloc \-p 5 12345
+fiu\-ctrl \-c 'enable_random name=posix/io/*,probability=0.25' \\
+         \-c 'enable_random name=libc/mm/*,probability=0.05' 12345
 .fi
 .RE
 
@@ -58,7 +62,7 @@ failure point \fIposix/io/read\fR:
 
 .RS
 .nf
-fiu\-ctrl \-d posix/io/read 12345
+fiu\-ctrl \-c 'disable name=posix/io/read' 12345
 .fi
 .RE
 
