git » libjio » commit 2de82bf

This patch implements a preloader library for the UNIX API.

author Alberto Bertogli
2004-10-10 23:58:43 UTC
committer Alberto Bertogli
2007-07-15 13:25:37 UTC
parent 1bb7a49f1fe5bbb75743364fc695b59032a1bed7

This patch implements a preloader library for the UNIX API.

This patch implements a preloader library for the UNIX API.

You can use it like:

LD_PRELOAD=libjio_preload.so my_nice_app

and it will intercept all the relevant calls to the UNIX API (like open(),
read(), write() and so on) and call libjio's instead.

It has some limitations: it works only with glibc, it has a fixed (build-time
configurable) maximum number of file descriptors (8k by default), and it
hasn't been tested under 64 bit platforms.

It also has some good points: it works with apps compiled both with and
without LFS, it doesn't introduce much overhead, and it allows you to use and
test the library without having to write or even recompile any applications.

The patch also includes some modifications to the Makefile.

In order to build it, do "make preload" and then "make preload_install" to
install it.

Makefile +22 -7
bindings/preload/libjio_preload.c +456 -0

diff --git a/Makefile b/Makefile
index b4c3ab4..014b9e1 100644
--- a/Makefile
+++ b/Makefile
@@ -8,15 +8,15 @@ OBJS = checksum.o common.o trans.o check.o unix.o ansi.o
 # rules
 default: all
 
-all: shared static jiofsck
+all: libjio.so libjio.a jiofsck
 
-shared: $(OBJS)
+libjio.so: $(OBJS)
 	$(CC) -shared $(OBJS) -o libjio.so
 
-static: $(OBJS)
+libjio.a: $(OBJS)
 	$(AR) cr libjio.a $(OBJS)
 
-jiofsck: jiofsck.o static
+jiofsck: jiofsck.o libjio.a
 	$(CC) jiofsck.o libjio.a -lpthread -o jiofsck
 
 install: all
@@ -33,21 +33,36 @@ install: all
 	@echo "Please run ldconfig to update your library cache"
 	@echo
 
+.c.o:
+	$(CC) $(CFLAGS) $(INCLUDES) -c $< -o $@
+
+
 python: all
 	cd bindings/python && python setup.py build
 
 python_install: python
 	cd bindings/python && python setup.py install
 
-.c.o:
-	$(CC) $(CFLAGS) $(INCLUDES) -c $< -o $@
+
+preload: all
+	install -d bindings/preload/build/
+	$(CC) $(INCLUDES) -Wall -O6 -shared -fPIC \
+		-D_XOPEN_SOURCE=500 \
+		-ldl -lpthread -L. -ljio -I. \
+		bindings/preload/libjio_preload.c \
+		-o bindings/preload/build/libjio_preload.so
+
+preload_install: preload
+	install -d $(PREFIX)/lib
+	install -m 0755 bindings/preload/build/libjio_preload.so $(PREFIX)/lib
 
 
 clean:
 	rm -f $(OBJS) libjio.a libjio.so jiofsck.o jiofsck
 	rm -f *.bb *.bbg *.da *.gcov gmon.out
 	rm -rf bindings/python/build/
+	rm -rf bindings/preload/build/
 
 
-.PHONY: default all shared static install clean
+.PHONY: default all install python python_install preload preload_install clean
 
diff --git a/bindings/preload/libjio_preload.c b/bindings/preload/libjio_preload.c
new file mode 100644
index 0000000..85075d7
--- /dev/null
+++ b/bindings/preload/libjio_preload.c
@@ -0,0 +1,456 @@
+
+/*
+ * libjio C preloader
+ * Alberto Bertogli (albertogli@telpin.com.ar)
+ *
+ * This generates a shared object that, when prelinked, can be used to make an
+ * existing application to use libjio for UNIX I/O.
+ * It's not nice or pretty, and does some nasty tricks to work both with and
+ * without LFS. I don't think it builds or works without glibc.
+ */
+
+
+#include <unistd.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <sys/uio.h>
+#include <dlfcn.h>
+
+/* we don't build this with LFS, however, it's essential that the proper
+ * environment is set for libjio's loading; otherwise we would mess the ABI
+ * up */
+typedef long long off64_t;
+#define _FILE_OFFSET_BITS 64
+#define off_t off64_t
+#include <libjio.h>
+#undef off_t
+#undef _FILE_OFFSET_BITS
+
+
+/* maximum number of simultaneous open file descriptors we support */
+#define MAXFD (4096 * 2)
+
+/* recursion counter, per-thread */
+static int __thread called = 0;
+
+
+/* C library functions, filled via the dynamic loader */
+static void *libc;
+
+static int (*c_open)(const char *pathname, int flags, mode_t mode);
+static int (*c_open64)(const char *pathname, int flags, mode_t mode);
+static int (*c_close)(int fd);
+static ssize_t (*c_read)(int fd, void *buf, size_t count);
+static ssize_t (*c_pread)(int fd, void *buf, size_t count, off_t offset);
+static ssize_t (*c_pread64)(int fd, void *buf, size_t count, off64_t offset);
+static ssize_t (*c_readv)(int fd, const struct iovec *vector, int count);
+static ssize_t (*c_write)(int fd, const void *buf, size_t count);
+static ssize_t (*c_pwrite)(int fd, const void *buf, size_t count, off_t offset);
+static ssize_t (*c_pwrite64)(int fd, const void *buf, size_t count, off64_t offset);
+static ssize_t (*c_writev)(int fd, const struct iovec *vector, int count);
+static int (*c_ftruncate)(int fd, off_t length);
+static int (*c_ftruncate64)(int fd, off64_t length);
+static off_t (*c_lseek)(int fd, off_t offset, int whence);
+static off64_t (*c_lseek64)(int fd, off64_t offset, int whence);
+static int (*c_fsync)(int fd);
+
+
+/* file descriptor table, to translate fds to jfs */
+struct fd_entry {
+	int fd;
+	struct jfs *fs;
+	pthread_mutex_t lock;
+};
+static struct fd_entry fd_table[MAXFD];
+
+/* useful macros, mostly for debugging purposes */
+#if 1
+	#define rec_inc() do { called++; } while(0)
+	#define rec_dec() do { called--; } while(0)
+	#define printd(...) do { } while(0)
+
+#else
+	/* debug variants */
+
+	#define rec_inc()				\
+		do {					\
+			called++;			\
+			fprintf(stderr, "I: %d\n", called); \
+			fflush(stderr);			\
+		} while (0)
+
+	#define rec_dec()				\
+		do {					\
+			called--;			\
+			fprintf(stderr, "D: %d\n", called); \
+			fflush(stderr);			\
+		} while (0)
+
+	#define printd(...)				\
+		do {					\
+			if (called)			\
+				fprintf(stderr, "\t");	\
+			called++;			\
+			fprintf(stderr, "%s(): ", __FUNCTION__ ); \
+			fprintf(stderr, __VA_ARGS__);	\
+			fflush(stderr);			\
+			called--;			\
+		} while(0)
+#endif
+
+
+/* functions used to lock fds from the table; they do boundary checks so we
+ * catch out of bounds accesses */
+static inline int fd_lock(int fd)
+{
+	if (fd < 0 || fd >= MAXFD) {
+		printd("locking out of bounds fd %d\n", fd);
+		return 0;
+	}
+	//printd("L %d\n", fd);
+	pthread_mutex_lock(&(fd_table[fd].lock));
+	//printd("OK %d\n", fd);
+	return 1;
+}
+
+static inline int fd_unlock(int fd)
+{
+	if (fd < 0 || fd >= MAXFD) {
+		printd("unlocking out of bounds fd %d\n", fd);
+		return 0;
+	}
+	//printd("U %d\n", fd);
+	pthread_mutex_unlock(&(fd_table[fd].lock));
+	//printd("OK %d\n", fd);
+	return 1;
+}
+
+
+/*
+ * library intialization
+ */
+
+static int __attribute__((constructor)) init(void)
+{
+	int i;
+	pthread_mutexattr_t attr;
+
+	printd("starting\n");
+
+	/* initialize fd_table */
+	pthread_mutexattr_init(&attr);
+	pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_NORMAL);
+	for (i = 0; i < MAXFD; i++) {
+		fd_table[i].fd = -1;
+		fd_table[i].fs = NULL;
+		pthread_mutex_init(&(fd_table[i].lock), &attr);
+	}
+	pthread_mutexattr_destroy(&attr);
+
+	/* dynamically load the C library */
+	libc = dlopen("libc.so.6", RTLD_NOW);
+	if (libc == NULL) {
+		printd("Error loading libc: %s\n", dlerror());
+		return 0;
+	}
+
+	/* load symbols from the C library */
+	#define libc_load(F) c_##F = dlsym(libc, #F)
+	libc_load(open);
+	libc_load(open64);
+	libc_load(close);
+	libc_load(read);
+	libc_load(pread);
+	libc_load(pread64);
+	libc_load(readv);
+	libc_load(write);
+	libc_load(pwrite);
+	libc_load(pwrite64);
+	libc_load(writev);
+	libc_load(ftruncate);
+	libc_load(ftruncate64);
+	libc_load(lseek);
+	libc_load(lseek64);
+	libc_load(fsync);
+
+	printd("done\n");
+	return 1;
+}
+
+/*
+ * wrappers
+ */
+
+int open(const char *pathname, int flags, ...)
+{
+	int r, fd;
+	struct jfs *fs;
+	mode_t mode;
+	struct stat st;
+	va_list l;
+
+	if (flags & O_CREAT) {
+		va_start(l, flags);
+		mode = va_arg(l, mode_t);
+		va_end(l);
+	} else {
+		/* set it to 0, it's ignored anyway */
+		mode = 0;
+	}
+
+	if (called) {
+		printd("orig (r)\n");
+		return (*c_open)(pathname, flags, mode);
+	}
+	printd("libjio\n");
+
+	/* skip special files */
+	r = stat(pathname, &st);
+	if (r == 0 && ( S_ISDIR(st.st_mode) \
+			|| S_ISCHR(st.st_mode) \
+			|| S_ISFIFO(st.st_mode) ) ) {
+		printd("orig (s)\n");
+		return (*c_open)(pathname, flags, mode);
+	}
+
+	/* skip /proc and /sys (not /dev, the problematic files are taken care
+	 * of with the stat test above */
+	/* FIXME: this breaks with relative paths */
+	if ( (strncmp("/proc", pathname, 5) == 0) ||
+			(strncmp("/sys", pathname, 4) == 0) ) {
+		printd("orig (f)\n");
+		return (*c_open)(pathname, flags, mode);
+	}
+
+	rec_inc();
+	fs = malloc(sizeof(struct jfs));
+	if (fs == NULL) {
+		rec_dec();
+		return -1;
+	}
+	fd = jopen(fs, pathname, flags, mode, 0);
+	if (fd >= MAXFD) {
+		printd("too many open fds: %d\n", fd);
+		jclose(fs);
+		free(fs);
+		rec_dec();
+		return -1;
+	}
+	rec_dec();
+
+	if (fd < 0) {
+		printd("return %d\n", fd);
+		return fd;
+	}
+
+	fd_lock(fd);
+	fd_table[fd].fd = fd;
+	fd_table[fd].fs = fs;
+	fd_unlock(fd);
+
+	printd("return %d\n", fd);
+	return fd;
+}
+
+/* exact copy of open(), but call c_open64 instead of c_open */
+int open64(const char *pathname, int flags, ...)
+{
+	int r, fd;
+	struct jfs *fs;
+	mode_t mode;
+	struct stat st;
+	va_list l;
+
+	if (flags & O_CREAT) {
+		va_start(l, flags);
+		mode = va_arg(l, mode_t);
+		va_end(l);
+	} else {
+		/* set it to 0, it's ignored anyway */
+		mode = 0;
+	}
+
+	if (called) {
+		printd("orig (r)\n");
+		return (*c_open64)(pathname, flags, mode);
+	}
+	printd("libjio\n");
+
+	/* skip special files */
+	r = stat(pathname, &st);
+	if (r == 0 && ( S_ISDIR(st.st_mode) \
+			|| S_ISCHR(st.st_mode) \
+			|| S_ISFIFO(st.st_mode) ) ) {
+		printd("orig (s)\n");
+		return (*c_open64)(pathname, flags, mode);
+	}
+
+	/* skip /proc and /sys (not /dev, the problematic files are taken care
+	 * of with the stat test above */
+	/* FIXME: this breaks with relative paths */
+	if ( (strncmp("/proc", pathname, 5) == 0) ||
+			(strncmp("/sys", pathname, 4) == 0) ) {
+		printd("orig (f)\n");
+		return (*c_open64)(pathname, flags, mode);
+	}
+
+	rec_inc();
+	fs = malloc(sizeof(struct jfs));
+	if (fs == NULL) {
+		rec_dec();
+		return -1;
+	}
+	fd = jopen(fs, pathname, flags, mode, 0);
+	if (fd >= MAXFD) {
+		printd("too many open fds: %d\n", fd);
+		jclose(fs);
+		free(fs);
+		rec_dec();
+		return -1;
+	}
+	rec_dec();
+
+	if (fd < 0) {
+		printd("return %d\n", fd);
+		return fd;
+	}
+
+	fd_lock(fd);
+	fd_table[fd].fd = fd;
+	fd_table[fd].fs = fs;
+	fd_unlock(fd);
+
+	printd("return %d\n", fd);
+	return fd;
+}
+
+
+int close(int fd)
+{
+	int r;
+	struct jfs *fs;
+
+	if (called) {
+		printd("orig\n");
+		return (*c_close)(fd);
+	}
+
+	if (!fd_lock(fd)) {
+		printd("out of bounds fd: %d\n", fd);
+		return -1;
+	}
+	fs = fd_table[fd].fs;
+	if (fs == NULL) {
+		printd("NULL fs, fd %d\n", fd);
+		fd_unlock(fd);
+		return (*c_close)(fd);
+	}
+	printd("libjio\n");
+
+	rec_inc();
+	r = jclose(fs);
+	if (fd_table[fd].fs != NULL) {
+		free(fd_table[fd].fs);
+		fd_table[fd].fd = -1;
+		fd_table[fd].fs = NULL;
+	}
+	rec_dec();
+	fd_unlock(fd);
+
+	printd("return %d\n", r);
+	return r;
+}
+
+
+/* the rest of the functions are automagically generated from the following
+ * macro. The ugliest. I'm so proud. */
+
+#define mkwrapper(rtype, name, DEF, INVR, INVM)			\
+	rtype name DEF						\
+	{ 							\
+		rtype r;					\
+		struct jfs *fs;					\
+								\
+		if (called) {					\
+			printd("orig \n");			\
+			return (*c_##name) INVR;		\
+		}						\
+								\
+		if (!fd_lock(fd)) {				\
+			printd("out of bounds fd: %d\n", fd);	\
+			return -1;				\
+		}						\
+		fs = fd_table[fd].fs;				\
+		if (fs == NULL) {				\
+			printd("(): NULL fs, fd %d\n", fd); 	\
+			fd_unlock(fd);				\
+			return (*c_##name) INVR;		\
+		}						\
+		printd("libjio\n");				\
+								\
+		rec_inc();					\
+		r = j##name INVM;				\
+		rec_dec();					\
+		fd_unlock(fd);					\
+								\
+		printd("return %lld\n", (long long) r); 	\
+		return r;					\
+	}
+
+
+/* 32-bit versions */
+mkwrapper(ssize_t, read, (int fd, void *buf, size_t count),
+		(fd, buf, count), (fs, buf, count) );
+
+mkwrapper(ssize_t, pread, (int fd, void *buf, size_t count, off_t offset),
+		(fd, buf, count, offset), (fs, buf, count, offset) );
+
+mkwrapper(ssize_t, readv, (int fd, const struct iovec *vector, int count),
+		(fd, vector, count), (fs, vector, count) );
+
+mkwrapper(ssize_t, write, (int fd, const void *buf, size_t count),
+		(fd, buf, count), (fs, buf, count) );
+
+mkwrapper(ssize_t, pwrite,
+		(int fd, const void *buf, size_t count, off_t offset),
+		(fd, buf, count, offset), (fs, buf, count, offset) );
+
+mkwrapper(ssize_t, writev, (int fd, const struct iovec *vector, int count),
+		(fd, vector, count), (fs, vector, count) );
+
+mkwrapper(off_t, lseek, (int fd, off_t offset, int whence),
+		(fd, offset, whence), (fs, offset, whence) );
+
+
+/* libjio defines jtruncate and jsync, not jftruncate and jfsync, which breaks
+ * the macro; so we add a nice #define to unbreak it */
+#define jftruncate jtruncate
+mkwrapper(int, ftruncate, (int fd, off_t length),
+		(fd, length), (fs, length) );
+
+#define jfsync jsync
+mkwrapper(int, fsync, (int fd), (fd), (fs) );
+
+
+/* 64-bit versions */
+#define jpread64 jpread
+mkwrapper(ssize_t, pread64, (int fd, void *buf, size_t count, off64_t offset),
+		(fd, buf, count, offset), (fs, buf, count, offset) );
+
+#define jpwrite64 jpwrite
+mkwrapper(ssize_t, pwrite64,
+		(int fd, const void *buf, size_t count, off64_t offset),
+		(fd, buf, count, offset), (fs, buf, count, offset) );
+
+#define jlseek64 jlseek
+mkwrapper(off64_t, lseek64, (int fd, off64_t offset, int whence),
+		(fd, offset, whence), (fs, offset, whence) );
+
+#define jftruncate64 jtruncate
+mkwrapper(int, ftruncate64, (int fd, off64_t length),
+		(fd, length), (fs, length) );
+