git » libfiu » commit 1f41f72

preload/posix: Replace open-coded hash table with the existing one

author Alberto Bertogli
2018-09-30 20:03:27 UTC
committer Alberto Bertogli
2018-09-30 21:50:42 UTC
parent 2b53d12fdbc3966920de72b53963b1b40496c4f3

preload/posix: Replace open-coded hash table with the existing one

In preload/posix/modules/posix.custom.c we need to keep a set of FILE *.
Current implementation open-codes a hash table of fixed size.

This patch replaces that with an existing hash table implementation.
The existing one is dynamic in size, but requires to convert the
pointers to string keys, so it has a performance penalty.

This change is mostly to make the code easier to maintain, as ~halves
the lines of code, and reuses a more tested hash implementation.

libfiu/hash.h +1 -0
preload/posix/Makefile +3 -2
preload/posix/modules/posix.custom.c +50 -104

diff --git a/libfiu/hash.h b/libfiu/hash.h
index edd5f6d..fb88c03 100644
--- a/libfiu/hash.h
+++ b/libfiu/hash.h
@@ -5,6 +5,7 @@
 /* Generic hash table. See hash.c for more information. */
 
 #include <sys/types.h>		/* for size_t */
+#include <stdbool.h>		/* for bool */
 #include <stdint.h>		/* for int64_t */
 
 typedef struct hash hash_t;
diff --git a/preload/posix/Makefile b/preload/posix/Makefile
index a45477a..6ef224b 100644
--- a/preload/posix/Makefile
+++ b/preload/posix/Makefile
@@ -98,8 +98,9 @@ build-env.h: build-env.h.in build-libcsoname
 		> build-env.h
 
 
-fiu_posix_preload.so: build-flags build-env.h build-needlibdl $(OBJS)
-	$(NICE_CC) $(ALL_CFLAGS) -shared -fPIC $(OBJS) \
+fiu_posix_preload.so: build-flags build-env.h build-needlibdl \
+		$(OBJS) ../../libfiu/hash.c
+	$(NICE_CC) $(ALL_CFLAGS) -shared -fPIC $(OBJS) ../../libfiu/hash.c \
 		-L../../libfiu/ \
 		-lfiu `cat build-needlibdl` \
 		-o fiu_posix_preload.so
diff --git a/preload/posix/modules/posix.custom.c b/preload/posix/modules/posix.custom.c
index 86c6269..9088b69 100644
--- a/preload/posix/modules/posix.custom.c
+++ b/preload/posix/modules/posix.custom.c
@@ -4,7 +4,7 @@
  */
 
 #include "codegen.h"
-
+#include "hash.h"
 
 #include <sys/types.h>
 #include <unistd.h>
@@ -185,57 +185,58 @@ mkwrap_bottom(open64, (pathname, flags, mode))
 #endif
 
 /*
- * Here we keep track of when a FILE* I/O operation fails and fix ferror
- * accordingly.
+ * To simulate ferror() properly, we need to keep track of which FILE * have
+ * we returned a failure for.
+ *
+ * To do that, we keep a set of FILE *. This set is implemented using a hash
+ * table, for convenience.
+ *
+ * An alternative would be to have a static array, but then lookups would
+ * scale linearly with the number of files we're expected to fail, and (most
+ * importantly) require a new data type just for this.
  */
+static hash_t *ferror_hash_table;
+static pthread_mutex_t ferror_hash_table_mutex = PTHREAD_MUTEX_INITIALIZER;
 
-#define MAX_FERROR_TRACKED_FILES 16384
+static void constructor_attr(200) _fiu_init_ferror_hash_table(void)
+{
+	rec_inc();
+	pthread_mutex_lock(&ferror_hash_table_mutex);
 
-static void * ferror_hash_table[MAX_FERROR_TRACKED_FILES] = {NULL};
+	// Create the hash table. No need for a value destructor, as our values
+	// are not pointers but static values.
+	ferror_hash_table = hash_create(NULL);
 
-static int ferror_hash_table_usage = 0;
+	pthread_mutex_unlock(&ferror_hash_table_mutex);
+	rec_dec();
+}
 
-static pthread_mutex_t ferror_hash_table_usage_mutex
-	= PTHREAD_MUTEX_INITIALIZER;
+/* Our hash table uses character keys, to convert stream to keys we just get
+ * the hexadecimal representation via %p.
+ * 64 bit pointers fit in 18 characters, use 20 so we have room for \0 and
+ * more practical alignment. */
+#define STREAM_KEY_SIZE 20
+
+static void stream_to_key(void *stream, char key[STREAM_KEY_SIZE])
+{
+	snprintf(key, STREAM_KEY_SIZE, "%p", stream);
+}
 
 void set_ferror(void * stream)
 {
 	if (stream == NULL)
 		return;
 
-	/* Hash table has to have at least one empty position. */
-	if (ferror_hash_table_usage + 1 == MAX_FERROR_TRACKED_FILES) {
-		/* Original call is already failing, so we cannot report this
-		 * otherwise. */
-		fprintf(stderr, "libfiu: ferror() hash table is full, ferror() will"
-			" not be faked for this file (too many open files)\n");
-		return;
-	}
-
-	pthread_mutex_lock(&ferror_hash_table_usage_mutex);
-
-	/* Our hash function is taking the least significant bits of a FILE *. */
-	uintptr_t ptr = (uintptr_t) stream;
+	char key[STREAM_KEY_SIZE];
+	stream_to_key(stream, key);
 
-	int index = (int) (ptr % MAX_FERROR_TRACKED_FILES);
+	pthread_mutex_lock(&ferror_hash_table_mutex);
 
-	for (;;) {
-		if (ferror_hash_table[index] == stream) {
-			// found => do nothing
-			break;
-		}
-
-		if (ferror_hash_table[index] == NULL) {
-			// not found => insert
-			ferror_hash_table[index] = stream;
-			ferror_hash_table_usage++;
-			break;
-		}
+	// Use a dummy the value; we don't care about it, we only need to be able to
+	// distinguish it from NULL.
+	hash_set(ferror_hash_table, key, (void *) 0xDEAD);
 
-		index = (index + 1) % MAX_FERROR_TRACKED_FILES;
-	}
-
-	pthread_mutex_unlock(&ferror_hash_table_usage_mutex);
+	pthread_mutex_unlock(&ferror_hash_table_mutex);
 }
 
 static int get_ferror(void * stream)
@@ -243,33 +244,16 @@ static int get_ferror(void * stream)
 	if (stream == NULL)
 		return 1;
 
-	pthread_mutex_lock(&ferror_hash_table_usage_mutex);
-
-	uintptr_t ptr = (uintptr_t) stream;
-
-	int index = (int) (ptr % MAX_FERROR_TRACKED_FILES);
-
-	int r;
-
-	for (;;) {
-		if (ferror_hash_table[index] == stream) {
-			// found
-			r = 1;
-			break;
-		}
+	char key[STREAM_KEY_SIZE];
+	stream_to_key(stream, key);
 
-		if (ferror_hash_table[index] == NULL) {
-			// not found
-			r = 0;
-			break;
-		}
+	pthread_mutex_lock(&ferror_hash_table_mutex);
 
-		index = (index + 1) % MAX_FERROR_TRACKED_FILES;
-	}
+	void *value = hash_get(ferror_hash_table, key);
 
-	pthread_mutex_unlock(&ferror_hash_table_usage_mutex);
+	pthread_mutex_unlock(&ferror_hash_table_mutex);
 
-	return r;
+	return value != NULL;
 }
 
 static void clear_ferror(void * stream)
@@ -277,52 +261,14 @@ static void clear_ferror(void * stream)
 	if (stream == NULL)
 		return;
 
-	pthread_mutex_lock(&ferror_hash_table_usage_mutex);
-
-	uintptr_t ptr = (uintptr_t) stream;
-
-	int index = (int) (ptr % MAX_FERROR_TRACKED_FILES);
-
-	for (;;) {
-		if (ferror_hash_table[index] == stream) {
-			/* found => clear and move back colliding entries. */
-
-			for (;;) {
-				int next_index = (index + 1) % MAX_FERROR_TRACKED_FILES;
-
-				/* Break if next entry is not a collision or if there is
-				 * nothing to copy back. */
+	char key[STREAM_KEY_SIZE];
+	stream_to_key(stream, key);
 
-				int next_hash_value = (int) (
-					(uintptr_t) ferror_hash_table[next_index]
-					% MAX_FERROR_TRACKED_FILES);
+	pthread_mutex_lock(&ferror_hash_table_mutex);
 
-				if (next_index == next_hash_value
-						|| ferror_hash_table[next_index] == NULL) {
-
-				    ferror_hash_table[index] = NULL;
-				    break;
-				}
-
-				ferror_hash_table[index] = ferror_hash_table[next_index];
-
-				index = next_index;
-			}
-
-			ferror_hash_table_usage--;
-
-			break;
-		}
-
-		if (ferror_hash_table[index] == NULL) {
-			/* not found => do nothing */
-			break;
-		}
-
-		index = (index + 1) % MAX_FERROR_TRACKED_FILES;
-	}
+	hash_del(ferror_hash_table, key);
 
-	pthread_mutex_unlock(&ferror_hash_table_usage_mutex);
+	pthread_mutex_unlock(&ferror_hash_table_mutex);
 }