 .gitignore                                       |   1 +
 libfiu/Makefile                                  |   5 +-
 libfiu/symbols.map                               |  17 ++++
 libfiu/wtable.c                                  |   5 +
 preload/posix/Makefile                           |   2 +-
 preload/posix/codegen.h                          |  29 ++++--
 preload/posix/modules/posix.custom.c             | 124 ++++++++++++++++-------
 tests/Makefile                                   |  11 +-
 tests/collisions/.gitignore                      |   2 +
 tests/collisions/Makefile                        |  67 ++++++++++++
 tests/collisions/binary.c                        |  32 ++++++
 tests/collisions/libcoltest.c                    |   6 ++
 tests/collisions/libcoltest.h                    |   4 +
 tests/generated/tests/{open.conf => open64.conf} |   5 +-
 tests/small-cat.c                                |  36 +++++++
 tests/test-cache_invalidation.py                 |  22 ++++
 tests/test-fiu_ctrl.py                           |   4 +-
 17 files changed, 319 insertions(+), 53 deletions(-)

diff --git a/.gitignore b/.gitignore
index 55e90a6..b365210 100644
--- a/.gitignore
+++ b/.gitignore
@@ -29,6 +29,7 @@ preload/run/build-needlibdl
 tests/*.o
 tests/build-flags
 tests/test-?
+tests/small-cat
 tests/libs/
 tests/generated/build-flags
 tests/generated/tests/*.[oc]
diff --git a/libfiu/Makefile b/libfiu/Makefile
index 7d5d7d1..0eb7ad8 100644
--- a/libfiu/Makefile
+++ b/libfiu/Makefile
@@ -38,7 +38,7 @@ else
 	NICE_CC = $(CC)
 endif
 
-LIB_VER=0.93
+LIB_VER=0.94
 LIB_SO_VER=0
 
 
@@ -66,9 +66,10 @@ libfiu.pc: build-flags libfiu.pc.in
 
 libs: libfiu.so libfiu.a
 
-libfiu.so: build-flags fiu.h $(OBJS)
+libfiu.so: build-flags symbols.map fiu.h $(OBJS)
 	$(NICE_CC) $(ALL_CFLAGS) -shared -fPIC \
 		-Wl,-soname,libfiu.so.$(LIB_SO_VER) \
+		-Wl,--version-script=symbols.map \
 		$(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)
diff --git a/libfiu/symbols.map b/libfiu/symbols.map
new file mode 100644
index 0000000..204acc1
--- /dev/null
+++ b/libfiu/symbols.map
@@ -0,0 +1,17 @@
+# Map of publicly exported symbols.
+{
+	global:
+		fiu_disable;
+		fiu_enable;
+		fiu_enable_external;
+		fiu_enable_random;
+		fiu_enable_stack;
+		fiu_enable_stack_by_name;
+		fiu_fail;
+		fiu_failinfo;
+		fiu_init;
+		fiu_rc_fifo;
+		fiu_rc_string;
+
+	local: *;
+};
diff --git a/libfiu/wtable.c b/libfiu/wtable.c
index bf77878..7e271fa 100644
--- a/libfiu/wtable.c
+++ b/libfiu/wtable.c
@@ -309,6 +309,11 @@ bool wtable_set(struct wtable *t, const char *key, void *value)
 				return false;
 		}
 
+		/* Invalidate the cache. We could be smart and walk it,
+		 * removing only the negative hits, but it's also more
+		 * expensive */
+		cache_invalidate(t->wcache);
+
 		return wildcards_set(t, strdup(key), value);
 	} else {
 		return hash_set(t->finals, key, value);
diff --git a/preload/posix/Makefile b/preload/posix/Makefile
index 674882b..4355829 100644
--- a/preload/posix/Makefile
+++ b/preload/posix/Makefile
@@ -68,7 +68,7 @@ $(OBJS): build-flags codegen.h
 
 # We define _GNU_SOURCE to get RTLD_NEXT if available; on non-GNU
 # platforms it should be harmless.
-codegen.o: codegen.c build-flags
+codegen.o: codegen.c build-flags build-env.h
 	$(NICE_CC) $(ALL_CFLAGS) -D_GNU_SOURCE -c $< -o $@
 
 # some platforms do not have libdl, we only use it if available
diff --git a/preload/posix/codegen.h b/preload/posix/codegen.h
index 2885da1..9e8cb3a 100644
--- a/preload/posix/codegen.h
+++ b/preload/posix/codegen.h
@@ -18,9 +18,9 @@ void *libc_symbol(const char *symbol);
  *  - GCC >= 4.3 on Linux
  *  - clang as of 2010-03-14
  */
+#define _GCC_VERSION (__GNUC__ * 1000 + __GNUC_MINOR__ * 10)
 #if \
-	( (defined __GNUC__) \
-		&& __GNUC__ >= 4 && __GNUC_MINOR__ >= 3 ) \
+	( (defined __GNUC__) && _GCC_VERSION >= 4030 ) \
 	|| (defined __clang__)
   #define constructor_attr(prio) __attribute__((constructor(prio)))
 #else
@@ -73,8 +73,8 @@ void *libc_symbol(const char *symbol);
  * Wrapper generator macros
  */
 
-/* Generates the common top of the wrapped function */
-#define mkwrap_top(RTYPE, NAME, PARAMS, PARAMSN, PARAMST, ON_ERR) \
+/* Generates the init part of the wrapped function */
+#define mkwrap_init(RTYPE, NAME, PARAMS, PARAMST) \
 	static RTYPE (*_fiu_orig_##NAME) PARAMS = NULL;		\
 								\
 	static int _fiu_in_init_##NAME = 0;			\
@@ -89,13 +89,17 @@ void *libc_symbol(const char *symbol);
 								\
 		_fiu_in_init_##NAME--;				\
 		rec_dec();					\
-	}							\
-								\
+	}
+
+/* Generates the definition part of the wrapped function. */
+#define mkwrap_def(RTYPE, NAME, PARAMS, PARAMST) \
 	RTYPE NAME PARAMS					\
 	{ 							\
 		RTYPE r;					\
-		int fstatus;					\
-								\
+		int fstatus;
+
+/* Generate the first part of the body, which checks the recursion status */
+#define mkwrap_body_called(NAME, PARAMSN, ON_ERR) \
 		if (_fiu_called) {				\
 			if (_fiu_orig_##NAME == NULL) {		\
 				if (_fiu_in_init_##NAME) {	\
@@ -115,6 +119,11 @@ void *libc_symbol(const char *symbol);
 		/* fiu_fail() may call anything */		\
 		rec_inc();
 
+/* Generates the common top for most functions (init + def + body called) */
+#define mkwrap_top(RTYPE, NAME, PARAMS, PARAMSN, PARAMST, ON_ERR) \
+	mkwrap_init(RTYPE, NAME, PARAMS, PARAMST) \
+	mkwrap_def(RTYPE, NAME, PARAMS, PARAMST) \
+	mkwrap_body_called(NAME, PARAMSN, ON_ERR)
 
 /* Generates the body of the function for normal, non-errno usage. The return
  * value is taken from failinfo. */
@@ -123,6 +132,7 @@ void *libc_symbol(const char *symbol);
 		fstatus = fiu_fail(FIU_NAME);			\
 		if (fstatus != 0) {				\
 			r = (RTYPE) fiu_failinfo();		\
+			printd("failing\n");			\
 			goto exit;				\
 		}
 
@@ -133,6 +143,7 @@ void *libc_symbol(const char *symbol);
 		fstatus = fiu_fail(FIU_NAME);			\
 		if (fstatus != 0) {				\
 			r = FAIL_RET;				\
+			printd("failing\n");			\
 			goto exit;				\
 		}
 
@@ -151,6 +162,7 @@ void *libc_symbol(const char *symbol);
 				errno = (long) finfo;		\
 			}					\
 			r = FAIL_RET;				\
+			printd("failing\n");			\
 			goto exit;				\
 		}
 
@@ -161,6 +173,7 @@ void *libc_symbol(const char *symbol);
 								\
 		fstatus = fiu_fail(FIU_NAME);			\
 		if (fstatus != 0) {				\
+			printd("reducing\n");			\
 			CNT -= random() % CNT;			\
 		}
 
diff --git a/preload/posix/modules/posix.custom.c b/preload/posix/modules/posix.custom.c
index 959c58b..2672728 100644
--- a/preload/posix/modules/posix.custom.c
+++ b/preload/posix/modules/posix.custom.c
@@ -16,21 +16,8 @@
 
 /* Wrapper for open(), we can't generate it because it has a variable number
  * of arguments */
-static int (*_fiu_orig_open) (const char *pathname, int flags, ...) = NULL;
-
-static int _fiu_in_init_open = 0;
-
-static void constructor_attr(201) _fiu_init_open(void)
-{
-	rec_inc();
-	_fiu_in_init_open++;
-
-	_fiu_orig_open = (int (*) (const char *, int, ...))
-		libc_symbol("open");
-
-	_fiu_in_init_open--;
-	rec_dec();
-}
+mkwrap_init(int, open, (const char *pathname, int flags, ...),
+		(const char *, int, ...))
 
 int open(const char *pathname, int flags, ...)
 {
@@ -60,28 +47,93 @@ int open(const char *pathname, int flags, ...)
 
 	/* Differences from the generated code end here */
 
-	if (_fiu_called) {
-		if (_fiu_orig_open == NULL) {
-			if (_fiu_in_init_open) {
-				printd("fail on init\n");
-				return -1;
-			} else {
-				printd("get orig\n");
-				_fiu_init_open();
-			}
-		}
-
-		printd("orig\n");
-		return (*_fiu_orig_open) (pathname, flags, mode);
-	}
+/* Use the normal macros to complete the function, now that we have
+ * set mode to something */
+mkwrap_body_called(open, (pathname, flags, mode), -1)
+
+	static const int valid_errnos[] = {
+	  #ifdef EACCESS
+		EACCES,
+	  #endif
+	  #ifdef EFAULT
+		EFAULT,
+	  #endif
+	  #ifdef EFBIG
+		EFBIG,
+	  #endif
+	  #ifdef EOVERFLOW
+		EOVERFLOW,
+	  #endif
+	  #ifdef ELOOP
+		ELOOP,
+	  #endif
+	  #ifdef EMFILE
+		EMFILE,
+	  #endif
+	  #ifdef ENAMETOOLONG
+		ENAMETOOLONG,
+	  #endif
+	  #ifdef ENFILE
+		ENFILE,
+	  #endif
+	  #ifdef ENOENT
+		ENOENT,
+	  #endif
+	  #ifdef ENOMEM
+		ENOMEM,
+	  #endif
+	  #ifdef ENOSPC
+		ENOSPC,
+	  #endif
+	  #ifdef ENOTDIR
+		ENOTDIR,
+	  #endif
+	  #ifdef EROFS
+		EROFS
+	  #endif
+	};
+mkwrap_body_errno("posix/io/oc/open", -1)
+mkwrap_bottom(open, (pathname, flags, mode))
+
+
+/* The 64-bit variant for glibc.
+ * The code is identical to open(), just using open64() where appropriate. */
+#ifdef __GLIBC__
+
+mkwrap_init(int, open64, (const char *pathname, int flags, ...),
+		(const char *, int, ...))
+
+int open64(const char *pathname, int flags, ...)
+{
+	int r;
+	int fstatus;
+
+	/* Differences from the generated code begin here */
+
+	mode_t mode;
+	va_list l;
+
+	if (flags & O_CREAT) {
+		va_start(l, flags);
+
+		/* va_arg() can only take fully promoted types, and mode_t
+		 * sometimes is smaller than an int, so we should always pass
+		 * int to it, and not mode_t. Not doing so would may result in
+		 * a compile-time warning and run-time error. We asume that it
+		 * is never bigger than an int, which holds in practise. */
+		mode = va_arg(l, int);
 
-	printd("fiu\n");
+		va_end(l);
+	} else {
+		/* set it to 0, it's ignored anyway */
+		mode = 0;
+	}
 
-	/* fiu_fail() may call anything */
-	rec_inc();
+	/* Differences from the generated code end here */
 
-	/* Use the normal macros to complete the function, now that we have a
-	 * set mode to something */
+/* Use the normal macros to complete the function, now that we have
+ * set mode to something */
+mkwrap_body_called(open, (pathname, flags, mode), -1)
 
 	static const int valid_errnos[] = {
 	  #ifdef EACCESS
@@ -125,6 +177,6 @@ int open(const char *pathname, int flags, ...)
 	  #endif
 	};
 mkwrap_body_errno("posix/io/oc/open", -1)
-mkwrap_bottom(open, (pathname, flags, mode))
-
+mkwrap_bottom(open64, (pathname, flags, mode))
 
+#endif
diff --git a/tests/Makefile b/tests/Makefile
index 7d57070..5d8ec6e 100644
--- a/tests/Makefile
+++ b/tests/Makefile
@@ -27,7 +27,7 @@ default: tests
 
 all: tests
 
-tests: c-tests py-tests gen-tests utils-tests
+tests: c-tests py-tests gen-tests utils-tests collisions-tests
 
 
 # Link the libraries to a single place, some of the tests need this.
@@ -81,9 +81,11 @@ PY_TESTS := $(wildcard test-*.py)
 
 py-tests: $(patsubst %.py,py-run-%,$(PY_TESTS))
 
-py-run-%: %.py lnlibs
+py-run-%: %.py lnlibs small-cat
 	$(NICE_PY) ./$<
 
+small-cat: small-cat.c
+	$(NICE_CC) $(ALL_CFLAGS) $< -o $@
 
 #
 # Sub-directory tests
@@ -95,6 +97,9 @@ gen-tests:
 utils-tests:
 	$(MAKE) -C utils
 
+collisions-tests:
+	$(MAKE) -C collisions
+
 #
 # Cleanup
 #
@@ -104,7 +109,7 @@ utils-tests:
 # also remove them when cleaning just in case.
 clean:
 	rm -f $(C_OBJS) $(C_BINS)
-	rm -rf libs/
+	rm -rf libs/ small-cat
 	rm -f *.bb *.bbg *.da *.gcov *.gcda *.gcno gmon.out build-flags
 	$(MAKE) -C generated clean
 
diff --git a/tests/collisions/.gitignore b/tests/collisions/.gitignore
new file mode 100644
index 0000000..7e2df7b
--- /dev/null
+++ b/tests/collisions/.gitignore
@@ -0,0 +1,2 @@
+binary
+libcoltest.so
diff --git a/tests/collisions/Makefile b/tests/collisions/Makefile
new file mode 100644
index 0000000..0292d84
--- /dev/null
+++ b/tests/collisions/Makefile
@@ -0,0 +1,67 @@
+
+CFLAGS += -std=c99 -pedantic -Wall -rdynamic
+ALL_CFLAGS = -I../../libfiu/ -L../../libfiu/ -L./ \
+	-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_LN = @echo "  LN $@"; ln -f
+else
+	NICE_CC = $(CC)
+	NICE_RUN = LD_LIBRARY_PATH="./:../../libfiu/"
+	NICE_LN = ln -f
+endif
+
+default: tests
+
+all: tests
+
+
+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
+
+#
+# Test library and binaries.
+#
+
+libcoltest.so: libcoltest.c libcoltest.h
+	$(NICE_CC) $(ALL_CFLAGS) -shared -fPIC $< -o $@
+
+binary: binary.c libcoltest.so
+	$(NICE_CC) $(ALL_CFLAGS) -L. $< -lfiu -lcoltest -o $@
+
+
+tests: binary
+	$(NICE_RUN) ./binary
+
+#
+# Cleanup
+#
+
+clean:
+	rm -f *.bb *.bbg *.da *.gcov *.gcda *.gcno gmon.out build-flags
+	rm -f libcoltest.so binary
+
+FORCE:
+
+.PHONY: default all clean \
+	tests c-tests py-tests \
+	.force-build-flags
+
+
diff --git a/tests/collisions/binary.c b/tests/collisions/binary.c
new file mode 100644
index 0000000..7fb825d
--- /dev/null
+++ b/tests/collisions/binary.c
@@ -0,0 +1,32 @@
+// A binary that uses some of the same function names as libfiu.
+// We use this to test function name collissions.
+
+#include <stdio.h> // printf()
+#include "libcoltest.h"
+
+#define ASSERT_CALLED(NAME, N)                                                 \
+	if (called_##NAME != N) {                                              \
+		printf("Error: " #NAME "called %d != " #N "\n",                \
+		       called_##NAME);                                         \
+		return 1;                                                      \
+	}
+
+#define CHECK(NAME)                                                            \
+	ASSERT_CALLED(NAME, 0)                                                 \
+	NAME();                                                                \
+	ASSERT_CALLED(NAME, 1)
+
+int called_wtable_set = 0;
+
+void wtable_set(void) { called_wtable_set++; }
+
+int main(void)
+{
+	// Defined in libcoltest.
+	CHECK(wtable_get)
+
+	// Defined here.
+	CHECK(wtable_set)
+
+	return 0;
+}
diff --git a/tests/collisions/libcoltest.c b/tests/collisions/libcoltest.c
new file mode 100644
index 0000000..765d6d0
--- /dev/null
+++ b/tests/collisions/libcoltest.c
@@ -0,0 +1,6 @@
+// A library that uses some of the same function names as libfiu.
+// We use this to test function name collissions.
+
+int called_wtable_get = 0;
+
+void wtable_get(void) { called_wtable_get++; }
diff --git a/tests/collisions/libcoltest.h b/tests/collisions/libcoltest.h
new file mode 100644
index 0000000..3d704ab
--- /dev/null
+++ b/tests/collisions/libcoltest.h
@@ -0,0 +1,4 @@
+// Headers for libcoltest.
+
+extern int called_wtable_get;
+void wtable_get(void);
diff --git a/tests/generated/tests/open.conf b/tests/generated/tests/open64.conf
similarity index 67%
copy from tests/generated/tests/open.conf
copy to tests/generated/tests/open64.conf
index 0bf367a..78c1839 100644
--- a/tests/generated/tests/open.conf
+++ b/tests/generated/tests/open64.conf
@@ -1,9 +1,10 @@
 
-[open]
+[open64]
+if: defined __GLIBC__
 fp: posix/io/oc/open
 include: sys/types.h sys/stat.h fcntl.h
 prep: int fd = -1;
-call: fd = open("/dev/null", O_RDONLY);
+call: fd = open64("/dev/null", O_RDONLY);
 success_cond: fd != -1
 failure_cond: fd == -1
 errno_on_fail: ELOOP
diff --git a/tests/small-cat.c b/tests/small-cat.c
new file mode 100644
index 0000000..4fa42f1
--- /dev/null
+++ b/tests/small-cat.c
@@ -0,0 +1,36 @@
+// A small "cat" utility that copies stdin to stdout.
+// It does only one 4K read from stdin, and writes it to stdout in as few
+// write()s as possible.
+// This gives a controlled number of operations, which makes testing the
+// random operations more robust.
+
+#include <stdio.h>  // printf(), perror()
+#include <unistd.h> // read(), write()
+
+const size_t BUFSIZE = 4092;
+
+int main(void)
+{
+	char buf[BUFSIZE];
+	ssize_t r, w, pos;
+
+	r = read(0, buf, BUFSIZE);
+	if (r < 0) {
+		perror("Read error in small-cat");
+		return 1;
+	}
+
+	pos = 0;
+	while (r > 0) {
+		w = write(1, buf + pos, r);
+		if (w <= 0) {
+			perror("Write error in small-cat");
+			return 2;
+		}
+
+		pos += w;
+		r -= w;
+	}
+
+	return 0;
+}
diff --git a/tests/test-cache_invalidation.py b/tests/test-cache_invalidation.py
new file mode 100644
index 0000000..ad2954a
--- /dev/null
+++ b/tests/test-cache_invalidation.py
@@ -0,0 +1,22 @@
+"""
+Tests to make sure cache invalidation works.
+"""
+
+import fiu
+
+# Unknown - add - fail - remove - not fail.
+# The initial unknown is relevant because it places a negative match in the
+# cache.
+assert not fiu.fail('p1')
+fiu.enable('p1')
+assert fiu.fail('p1')
+fiu.disable('p1')
+assert not fiu.fail('p1')
+
+# Same as above, but with wildcards.
+assert not fiu.fail('p2/x')
+fiu.enable('p2/*')
+assert fiu.fail('p2/x')
+fiu.disable('p2/*')
+assert not fiu.fail('p2/x')
+
diff --git a/tests/test-fiu_ctrl.py b/tests/test-fiu_ctrl.py
index 104ca9d..04e2fbc 100644
--- a/tests/test-fiu_ctrl.py
+++ b/tests/test-fiu_ctrl.py
@@ -13,7 +13,7 @@ import time
 fiu_ctrl.PLIBPATH = "./libs/"
 
 def run_cat(**kwargs):
-    return fiu_ctrl.Subprocess(["/bin/cat"],
+    return fiu_ctrl.Subprocess(["./small-cat"],
         stdin = subprocess.PIPE, stdout = subprocess.PIPE,
         stderr = subprocess.PIPE, **kwargs)
 
@@ -49,6 +49,8 @@ out, err = p.communicate('test\n')
 assert out == 'test\n', (out, err)
 
 # Enable random.
+# This relies on cat doing a reasonably small number of read and writes, which
+# our small-cat does.
 result = { True: 0, False: 0 }
 for i in range(50):
     cmd = run_cat(fiu_enable_posix = True)
