creation
authorRalph Ronnquist <ralph.ronnquist@gmail.com>
Mon, 29 Mar 2021 01:53:36 +0000 (12:53 +1100)
committerRalph Ronnquist <ralph.ronnquist@gmail.com>
Mon, 29 Mar 2021 01:53:36 +0000 (12:53 +1100)
12 files changed:
Makefile [new file with mode: 0644]
README.md [new file with mode: 0644]
debian/changelog [new file with mode: 0644]
debian/compat [new file with mode: 0644]
debian/copyright [new file with mode: 0644]
debian/rules [new file with mode: 0644]
debian/source/format [new file with mode: 0644]
libpathmap.8.adoc [new file with mode: 0644]
libpathmap.c [new file with mode: 0644]
libtarmap.8.adoc [new file with mode: 0644]
libtarmap.c [new file with mode: 0644]
tarmap.c [new file with mode: 0644]

diff --git a/Makefile b/Makefile
new file mode 100644 (file)
index 0000000..a1f3982
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,47 @@
+LIBDIR= $(DESTDIR)/usr/lib
+SBINDIR = $(DESTDIR)/usr/local/sbin
+MAN8DIR = $(DESTDIR)/usr/local/share/man/man8
+
+SBINFILES = tarmap
+LIBFILES = libtarmap.so libpathmap.so
+MAN8FILES = $(addsuffix .8,$(LIBFILES) $(SBINFILES))
+HTMLDOC = $(addsuffix .html,$(MAN8FILES))
+GENFILES = $(LIBFILES) $(SBINFILES)
+
+default: $(GENFILES)
+
+# Generic rule to compile an html file from an adoc file
+%.html: %.adoc
+       asciidoc -bhtml $^
+
+# Generic rule to compile a man page from an adoc file
+%: %.adoc
+       a2x -d manpage -f manpage $^
+
+# Generic rule for making a dynamic library form a same named .c file
+%.so: %.c
+       gcc -Wall -fPIC  -Wl,-init,so_init -shared -o $$@ $$^ -ldl
+
+# Generic rule for making a binary from a same named .c file
+%: %.c
+       gcc -Wall -fPIC -o $@ $^ -ldl
+
+clean:
+       rm -f $(GENFILES)
+
+# Installation targets
+
+INSTALLTARGETS = $(addprefix $(SBINDIR)/,$(SBINFILES))
+INSTALLTARGETS = $(addprefix $(LIBDIR)/,$(LIBFILES))
+INSTALLTARGETS += $(addprefix $(MAN8DIR)/,$(MAN8FILES))
+
+# Generic rule to install bmo the install command without renaming
+$(LIBDIR)/% $(SBINDIR)/% $(MAN8DIR)/%: %
+       install -D -T $< $@
+
+install: $(INSTALLTARGETS)
+
+# Target for development building of the deb package
+deb:
+       PREFIX= INCLUDE_PREFIX=/usr dpkg-buildpackage -us -uc --build=full
+
diff --git a/README.md b/README.md
new file mode 100644 (file)
index 0000000..fc2410b
--- /dev/null
+++ b/README.md
@@ -0,0 +1,12 @@
+The fstrap interpreter
+======================
+
+The fstrap interpreter is intended to be a prefix to a tar file, for
+executing scripts in that tar file. More specifically, it sets up a
+file access overlay for using path names via the tar file where
+available, and referring to the file system otherwise.
+
+The actual work is done through the libfstrap.so dynamic library which
+is set as LD_PRELOAD library whilst invoking the first tar file entry
+for execution.
+
diff --git a/debian/changelog b/debian/changelog
new file mode 100644 (file)
index 0000000..5a99af3
--- /dev/null
@@ -0,0 +1,4 @@
+pathmap (0.1) experimental; urgency=medium
+
+       * creation
+ -- Ralph Ronnquist <ralph.ronnquist@gmail.com>  Mon, 29 Mar 2021 12:51:07 +1100
diff --git a/debian/compat b/debian/compat
new file mode 100644 (file)
index 0000000..ec63514
--- /dev/null
@@ -0,0 +1 @@
+9
diff --git a/debian/copyright b/debian/copyright
new file mode 100644 (file)
index 0000000..e41cdbe
--- /dev/null
@@ -0,0 +1,22 @@
+Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
+Upstream-Name: pathmap
+Source: <url://example.com>
+
+Files: debian/*
+Copyright: 2021 Ralph Ronnquist <ralph@ascii>
+License: GPL-2+
+ This package is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+ .
+ This package is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ GNU General Public License for more details.
+ .
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>
+ .
+ On Debian systems, the complete text of the GNU General
+ Public License version 2 can be found in "/usr/share/common-licenses/GPL-2".
diff --git a/debian/rules b/debian/rules
new file mode 100644 (file)
index 0000000..9354bd2
--- /dev/null
@@ -0,0 +1,8 @@
+#!/usr/bin/make -f
+
+%:
+       dh $@
+
+#
+#override_dh_usrlocal:
+#      true
diff --git a/debian/source/format b/debian/source/format
new file mode 100644 (file)
index 0000000..89ae9db
--- /dev/null
@@ -0,0 +1 @@
+3.0 (native)
diff --git a/libpathmap.8.adoc b/libpathmap.8.adoc
new file mode 100644 (file)
index 0000000..0430292
--- /dev/null
@@ -0,0 +1,62 @@
+libpathmap.so(8)
+================
+:doctype: manpage
+:revdate: {sys:date "+%Y-%m-%d %H:%M:%S"}
+:COLON: :
+:EQUALS: =
+
+NAME
+----
+libpathmap.so - preload utility to redirect pathnames
+
+SYNOPSYS
+--------
++LD_PRELOAD=libpathmap.so+ _command_
+
+DESCRIPTION
+-----------
+
+This dynamic library, libpathmap.so, is intended to be used as a
+"preload" library for programs for which pathnames are to be mapped to
+a different common prefix. The effect is somewhat similar to +chroot+
+in that absolute pathnames, which normally are from the root of the
+file system, get mapped to be relative to the choosen prefix point.
+
++libpathmap.so+ is "configured" by setting environment variable
++PATHMAPNOT+ to be the path prefixes, given as a colon separated list,
+that +libpathmap.so+ should be concerned with. The first of them is
+then used as the prefix to add to the program's pathnames (when
+opening files) except to those pathnames that start with any of the
+prefixes in the list, including the first.
+
+For example, if the prefix +/elsewhere+ should be added to all
+pathnames for a program +prgrm+ then the command line might be:
+
+----
+PATHMAPNOT=/elsewhere LD_PRELOAD=libpathmap.so prgrm
+----
+
+However, if +prgrm+ is a dynamically linked executable, then all
+libraries
+
+ENVIRONMENT
+-----------
+
+PATHMAPNOT::
+
+This environment variable tells which path prefixes, given as a colon
+separated list, +libpathmap.so+ should be concerned with. The first
+prefix is the one to add to all pathnames except to those of any of
+the prefixes in the list.
+
+EXAMPLES
+--------
+
+Assume there is a directory +/home/test/extra+
+
+> env PATHMAPNOT=/home/test:/proc:/dev:/lib:/usr:/bin \
+      LD_PRELOAD=libpathmap.so \
+      ls /extra
+      
+SEE ALSO
+--------
diff --git a/libpathmap.c b/libpathmap.c
new file mode 100644 (file)
index 0000000..5628030
--- /dev/null
@@ -0,0 +1,421 @@
+/**
+ * PRELOAD utility that maps all rooted paths except some to have an
+ * additional root path prefix. This is configured with an environment
+ * variable PATHMAPNOT that is a colon separated list of pathname
+ * prefixes to *not* map with the first one being the additional root
+ * path prefix. The environment variable is loaded on the first call.
+ */
+
+#define _GNU_SOURCE
+
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <dlfcn.h>
+#include <fcntl.h>
+#include <stdarg.h>
+#include <dirent.h>
+
+/************************************************************
+ * Data structure for pathname mapping
+ *
+ * Pathnames are passed in to the library via the environment variable
+ * PATHMAPNOT as a colon separated list of pathnames that are not to
+ * be mapped. The first value of PATHMAPNOT is used as prefix to add
+ * to all paths except the ones in the list (the first inlcuded).
+ *
+ * The given pathnames are loaded into a sorted table, offering
+ * O[log(n)] lookup complexity, and the map prefix is also held
+ * separately as the 'root'.
+ */
+
+#define PATHMAPNOT "PATHMAPNOT"
+
+static struct {
+    char **data; // Array of char*
+    int count;
+    int root_length;
+    char *root;
+} prefix;
+
+// Alphabetical ordering of char* records.
+static int alphorder(const void *a, const void *b) {
+    int x = strcmp( *(const char**)a, *(const char**)b );
+    return x;
+}
+
+// Utility function to load the prefixes.
+static void load_prefixes() {
+    char *data = getenv( PATHMAPNOT );
+    char *p = data;
+    prefix.count = 0;
+    if ( data == 0 || *data == 0 ) {
+       return;
+    }
+    prefix.count++;
+    while ( *p ) {
+       if ( *(p++) == ':' ) {
+           prefix.count++;
+       }
+    }
+    prefix.data = (char**) calloc( prefix.count, sizeof( char* ) );
+    if ( data == 0 ) {
+       perror( PATHMAPNOT );
+       exit( 1 );
+    }
+    char *path = data;
+    int n = 0;
+    for ( p = data; *p; p++ ) {
+       if ( *p == ':' ) {
+           prefix.data[ n++ ] = strndup( path, ( p - path ) );
+           path = p+1;
+       }
+    }
+    prefix.data[ n++ ] = strndup( path, ( p - path ) );
+    prefix.root = prefix.data[ 0 ];
+    prefix.root_length = strlen( prefix.root );
+    qsort( prefix.data, n, sizeof( char* ), alphorder );
+}
+
+// Binary search for a given path prefix. Returns the index of longest
+// prefix match or (-i-1)<0 for mismatch (indicating insertion point i)
+static int binsearch(const char *path) {
+    int lo = 0;
+    int hi = prefix.count;
+    while ( lo < hi ) {
+       int m = ( lo + hi ) / 2;
+       int x = strcmp( path, prefix.data[ m ] );
+       if ( x == 0 ) {
+           return m;
+       }
+       if ( x < 0 ) {
+           hi = m;
+       } else {
+           lo = m + 1;
+       }
+    }
+    // lo = insertion point for mismatch; check if lo-1 is prefix
+    if ( lo > 0 ) {
+       int n = strlen( prefix.data[ lo - 1 ] );
+       if ( ( strncmp( prefix.data[ lo - 1 ], path, n ) == 0 ) &&
+            ( *(path+n) == '/' ) ) {
+           return lo - 1;
+       }
+    }
+    return - lo - 1;
+}
+
+// Utility function to lookup a matching prefix for the given path. If
+// one is found, then 0 is returned. Otherwise memory is allocated for
+// a new string that consists of the root path followed by the given
+// path.
+static char *maybe_add_prefix(const char *path) {
+    fprintf( stderr, "libpathmap: check %s\n", path );
+    if ( prefix.count > 0 && *path == '/' ) {
+       int x = binsearch( path );
+       //fprintf( stderr, "libpathmap: %d %s\n", x, path );
+       if ( x < 0 ) {
+           char *p = (char*)malloc( strlen( path ) + prefix.root_length + 1 );
+           memcpy( p, prefix.root, prefix.root_length );
+           strcpy( p + prefix.root_length, path );
+           fprintf( stderr, "libpathmap: => %s\n", p );
+           return p;
+       }
+    }
+    return 0;
+}
+
+int open(const char *pathname, int flags, ...) {
+    static int (*real_open) (const char *pathname, int flags, ...);
+    if ( real_open == 0 ) { 
+       real_open = dlsym( RTLD_NEXT, "open" );
+    }
+    const char *new_path = maybe_add_prefix( pathname );
+    if ( new_path ) {
+       pathname = new_path;
+    }
+    int x;
+
+    // If O_CREAT is used to create a file, the file access mode must be given.
+    if ( flags & O_CREAT ) {
+        va_list args;
+        va_start( args, flags );
+        int mode = va_arg( args, int );
+        va_end( args );
+        x = real_open( pathname, flags, mode );
+    } else {
+       x = real_open( pathname, flags);
+    }
+    if ( new_path ) {
+       free( (void*) new_path );
+    }
+    return x;
+}
+
+int __open(const char *pathname, int flags, ...) {
+    static int (*real_open) (const char *pathname, int flags, ...);
+    if ( real_open == 0 ) { 
+       real_open = dlsym( RTLD_NEXT, "__open" );
+    }
+    const char *new_path = maybe_add_prefix( pathname );
+    if ( new_path ) {
+       pathname = new_path;
+    }
+    int x;
+
+    // If O_CREAT is used to create a file, the file access mode must be given.
+    if ( flags & O_CREAT ) {
+        va_list args;
+        va_start( args, flags );
+        int mode = va_arg( args, int );
+        va_end( args );
+        x = real_open( pathname, flags, mode );
+    } else {
+       x = real_open( pathname, flags);
+    }
+    if ( new_path ) {
+       free( (void*) new_path );
+    }
+    return x;
+}
+
+int open64(const char *pathname, int flags, ...) {
+    static int (*real_open64)(const char *pathname, int flags, ...);
+    if ( real_open64 == 0 ) {
+       real_open64 = dlsym(RTLD_NEXT, "open64");
+    }
+    const char *new_path = maybe_add_prefix( pathname );
+    if ( new_path ) {
+       pathname = new_path;
+    }
+    int x;
+
+    // If O_CREAT is used to create a file, the file access mode must be given.
+    if (flags & O_CREAT) {
+        va_list args;
+        va_start(args, flags);
+        int mode = va_arg(args, int);
+        va_end(args);
+       x = real_open64( pathname, flags, mode );
+    } else {
+       x =real_open64( pathname, flags);
+    }
+    if ( new_path ) {
+       free( (void*) new_path );
+    }
+    return x;
+}
+
+int openat(int dirfd,const char *pathname, int flags, ...) {
+    static int (*real_openat) (int dirfd,const char *pathname, int flags, ...);
+    if ( real_openat == 0 ) { 
+       real_openat = dlsym( RTLD_NEXT, "openat" );
+    }
+    const char *new_path = maybe_add_prefix( pathname );
+    if ( new_path ) {
+       pathname = new_path;
+    }
+    int x;
+    if ( flags & O_CREAT ) {
+        va_list args;
+        va_start( args, flags );
+        int mode = va_arg( args, int );
+        va_end( args );
+        x = real_openat( dirfd, pathname, flags, mode );
+    } else {
+       x = real_openat( dirfd, pathname, flags);
+    }
+    if ( new_path ) {
+       free( (void*) new_path );
+    }
+    return x;
+}
+
+int __openat_2(int dirfd,const char *pathname, int flags, ...) {
+    static int (*real___openat_2) (
+       int dirfd,const char *pathname, int flags, ...);
+    if ( real___openat_2 == 0 ) { 
+       real___openat_2 = dlsym( RTLD_NEXT, "__openat_2" );
+    }
+    const char *new_path = maybe_add_prefix( pathname );
+    if ( new_path ) {
+       pathname = new_path;
+    }
+    int x;
+    if ( flags & O_CREAT ) {
+        va_list args;
+        va_start( args, flags );
+        int mode = va_arg( args, int );
+        va_end( args );
+        x = real___openat_2( dirfd, pathname, flags, mode );
+    } else {
+       x = real___openat_2( dirfd, pathname, flags);
+    }
+    if ( new_path ) {
+       free( (void*) new_path );
+    }
+    return x;
+}
+
+int openat64(int dirfd,const char *pathname, int flags, ...) {
+    static int (*real_openat64)(
+       int dirfd,const char *pathname, int flags, ...);
+    if ( real_openat64 == 0 ) {
+       real_openat64 = dlsym(RTLD_NEXT, "openat64");
+    }
+    const char *new_path = maybe_add_prefix( pathname );
+    if ( new_path ) {
+       pathname = new_path;
+    }
+    int x;
+    if (flags & O_CREAT) {
+        va_list args;
+        va_start(args, flags);
+        int mode = va_arg(args, int);
+        va_end(args);
+       x = real_openat64( dirfd, pathname, flags, mode );
+    } else {
+       x =real_openat64( dirfd, pathname, flags);
+    }
+    if ( new_path ) {
+       free( (void*) new_path );
+    }
+ return x;
+}
+
+FILE *fopen(const char *pathname,const char * mode) {
+    static FILE *(*real_fopen)(const char *pathname,const char *mode);
+    if ( real_fopen == 0 ) {
+       real_fopen = dlsym(RTLD_NEXT, "fopen");
+    }
+    const char *new_path = maybe_add_prefix( pathname );
+    if ( new_path ) {
+       pathname = new_path;
+    }
+    FILE *x = real_fopen( pathname, mode );
+    if ( new_path ) {
+       free( (void*) new_path );
+    }
+    return x;
+}
+
+FILE *fopen64(const char *pathname,const char * mode) {
+    static FILE *(*real_fopen64)(const char *pathname,const char *mode);
+    if ( real_fopen64 == 0 ) {
+       real_fopen64 = dlsym(RTLD_NEXT, "fopen64");
+    }
+    const char *new_path = maybe_add_prefix( pathname );
+    if ( new_path ) {
+       pathname = new_path;
+    }
+    FILE *x = real_fopen64( pathname, mode );
+    if ( new_path ) {
+       free( (void*) new_path );
+    }
+    return x;
+}
+
+FILE *freopen(const char *pathname,const char * mode,FILE *stream) {
+    static FILE *(*real_freopen)(
+       const char *pathname,const char *mode,FILE *stream);
+    if ( real_freopen == 0 ) {
+       real_freopen = dlsym(RTLD_NEXT, "freopen");
+    }
+    const char *new_path = maybe_add_prefix( pathname );
+    if ( new_path ) {
+       pathname = new_path;
+    }
+    FILE *x = real_freopen( pathname, mode, stream );
+    if ( new_path ) {
+       free( (void*) new_path );
+    }
+    return x;
+}
+
+DIR *opendir(const char *pathname) {
+    static DIR *(*real_opendir)(const char *pathname);
+    if ( real_opendir == 0 ) {
+       real_opendir = dlsym(RTLD_NEXT, "opendir");
+    }
+    const char *new_path = maybe_add_prefix( pathname );
+    if ( new_path ) {
+       pathname = new_path;
+    }
+    DIR *x = real_opendir( pathname );
+    if ( new_path ) {
+       free( (void*) new_path );
+    }
+    return x;
+}
+
+int __xstat(int v,const char *pathname, struct stat *statbuf) {
+    static int (*real_xstat)(int v,const char *pathname,struct stat *statbuf);
+    if ( real_xstat == 0 ) {
+       real_xstat = dlsym(RTLD_NEXT, "__xstat");
+    }
+    const char *new_path = maybe_add_prefix( pathname );
+    if ( new_path ) {
+       pathname = new_path;
+    }
+    int x = real_xstat( v, pathname, statbuf );
+    if ( new_path ) {
+       free( (void*) new_path );
+    }
+    return x;
+}
+
+int __lxstat(int v,const char *pathname, struct stat *statbuf) {
+    static int (*real_lxstat)(int v,const char *pathname,struct stat *statbuf);
+    if ( real_lxstat == 0 ) {
+       real_lxstat = dlsym(RTLD_NEXT, "__lxstat");
+    }
+    const char *new_path = maybe_add_prefix( pathname );
+    if ( new_path ) {
+       pathname = new_path;
+    }
+    int x = real_lxstat( v, pathname, statbuf );
+    if ( new_path ) {
+       free( (void*) new_path );
+    }
+    return x;
+}
+
+int stat(const char *pathname, struct stat *statbuf) {
+    static int (*real_stat)(const char *pathname,struct stat *statbuf);
+    if ( real_stat == 0 ) {
+       real_stat = dlsym(RTLD_NEXT, "stat");
+    }
+    const char *new_path = maybe_add_prefix( pathname );
+    if ( new_path ) {
+       pathname = new_path;
+    }
+    int x = real_stat( pathname, statbuf );
+    if ( new_path ) {
+       free( (void*) new_path );
+    }
+    return x;
+}
+
+int lstat(const char *pathname, struct stat *statbuf) {
+    static int (*real_lstat)(const char *pathname,struct stat *statbuf);
+    if ( real_lstat == 0 ) {
+       real_lstat = dlsym(RTLD_NEXT, "lstat");
+    }
+    const char *new_path = maybe_add_prefix( pathname );
+    if ( new_path ) {
+       pathname = new_path;
+    }
+    int x = real_lstat( pathname, statbuf );
+    if ( new_path ) {
+       free( (void*) new_path );
+    }
+    return x;
+}
+
+/**
+ * Initialize the dynamic library.
+ */
+void so_init() {
+    load_prefixes();
+}
+
diff --git a/libtarmap.8.adoc b/libtarmap.8.adoc
new file mode 100644 (file)
index 0000000..31f92bb
--- /dev/null
@@ -0,0 +1,62 @@
+libtarmap.so(8)
+===============
+:doctype: manpage
+:revdate: {sys:date "+%Y-%m-%d %H:%M:%S"}
+:COLON: :
+:EQUALS: =
+
+NAME
+----
+libtarmap.so - preload utility to map pathnames into a tar file
+
+SYNOPSYS
+--------
++LD_PRELOAD=libtarmap.so+ +TARMAP=+_tarfile_ _command_
+
+DESCRIPTION
+-----------
+
+This dynamic library, libtarmap.so, is intended to be used as a
+"preload" library for programs for which pathnames are to be mapped to
+a different common prefix. The effect is somewhat similar to +chroot+
+in that absolute pathnames, which normally are from the root of the
+file system, get mapped to be relative to the choosen prefix point.
+
++libtarmap.so+ is "configured" by setting environment variable
++PATHMAPNOT+ to be the path prefixes, given as a colon separated list,
+that +libtarmap.so+ should be concerned with. The first of them is
+then used as the prefix to add to the program's pathnames (when
+opening files) except to those pathnames that start with any of the
+prefixes in the list, including the first.
+
+For example, if the prefix +/elsewhere+ should be added to all
+pathnames for a program +prgrm+ then the command line might be:
+
+----
+PATHMAPNOT=/elsewhere LD_PRELOAD=libtarmap.so prgrm
+----
+
+However, if +prgrm+ is a dynamically linked executable, then all
+libraries
+
+ENVIRONMENT
+-----------
+
+PATHMAPNOT::
+
+This environment variable tells which path prefixes, given as a colon
+separated list, +libtarmap.so+ should be concerned with. The first
+prefix is the one to add to all pathnames except to those of any of
+the prefixes in the list.
+
+EXAMPLES
+--------
+
+Assume there is a directory +/home/test/extra+
+
+> env PATHMAPNOT=/home/test:/proc:/dev:/lib:/usr:/bin \
+      LD_PRELOAD=libtarmap.so \
+      ls /extra
+      
+SEE ALSO
+--------
diff --git a/libtarmap.c b/libtarmap.c
new file mode 100644 (file)
index 0000000..f10c9cc
--- /dev/null
@@ -0,0 +1,174 @@
+/**
+ * libtarmap implements a preload library for faked file access that
+ * opens and reads files from a tar file first where possible.
+ */
+
+#include <dlfcn.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <errno.h>
+
+static int (*real_open)(const char *pathname, int flags);
+static FILE *(*real_openat)(int dirfd, const char *pathname, int flags);
+static FILE *(*real_fopen)(const char *pathname, const char *mode);
+
+// The environment variable name
+#define TARMAP "TARMAP"
+
+// Maximal allowed size of filenames ("tar tf" output)
+#define DATASZ 10000000
+
+static struct {
+    char *tarmap; // TARMAP value
+    char *head; // Command preamble
+    char *buffer; // All file names of the tar file
+    int size; // Byte size of buffer
+    char **table; // File name index in alphabetical order
+    int count; // Number of file names
+} data;
+
+// Ordering condition for two char**
+static int alphaorder(const void *a,const void *b) {
+    int x = strcmp( *(const char**)a, *(const char**)b );
+    return x;
+}
+
+// Combine two strings with a space between as a new malloc string
+static char *join(const char *head,const char *tail) {
+    int n = strlen( head );
+    char *p = (char*) malloc( n + strlen( tail ) + 2 );
+    strcpy( p, head );
+    strcpy( p + n, " " );
+    strcpy( p + n + 1, tail );
+    return p;
+}
+
+// Run popen with TARMAP unset
+static inline FILE *popen_notarmap(const char *cmd) {
+    unsetenv( TARMAP ); // Inhibit tarmap for the command subprocess
+    FILE *file = popen( cmd, "r" );
+    setenv( TARMAP, data.tarmap, 1 ); // Re-enable for this process
+    return file;
+}
+
+// Try opening the pathname in the tar
+static FILE *tryopentar(const char *pathname) {
+    fprintf( stderr, "libtarmap: %s\n", pathname );
+    if ( bsearch( &pathname, data.table, data.count, sizeof( char * ),
+                 alphaorder ) ) {
+       char *p = join( data.head, pathname );
+       fprintf( stderr, "libtarmap: => %s\n", p );
+       FILE *file = popen_notarmap( p );
+       free( p );
+       return file;
+    }
+    return 0;
+}
+
+// Override "openat"
+FILE *openat(int dirfd, const char *pathname, int flags) {
+    if ( data.tarmap ) {
+       if ( strncmp( pathname, "/", 1 ) == 0 ) {
+           FILE *file = tryopentar( pathname + 1 );
+           if ( file ) {
+               return file;
+           }
+       }
+    }
+    return ( real_openat )? real_openat( dirfd, pathname, flags ) : 0;
+}
+
+// Override "open"
+int open(const char *pathname, int flags) {
+    if ( data.tarmap ) {
+       if ( strncmp( pathname, "/", 1 ) == 0 ) {
+           FILE *file = tryopentar( pathname + 1 );
+           if ( file ) {
+               return fileno( file );
+           }
+       }
+    }
+    return ( real_open )? real_open( pathname, flags ) : -1;
+}
+
+// Override "fopen"
+FILE *fopen(const char *pathname, const char *mode) {
+    if ( data.tarmap ) {
+       if ( strncmp( pathname, "/", 1 ) == 0 ) {
+           FILE *file = tryopentar( pathname + 1 );
+           if ( file ) {
+               return file;
+           }
+       }
+    }
+    return ( real_fopen )? real_fopen( pathname, mode ) : 0;
+}
+
+/**
+ * Initialize the dynamic library.
+ */
+void so_init() {
+    void *lib = dlopen( "libc.so.6", RTLD_LAZY );
+    real_open = dlsym( lib, "open" );
+    real_openat = dlsym( lib, "openat" );
+    real_fopen = dlsym( lib, "fopen" );
+    char *tarfile = getenv( TARMAP );
+    if ( tarfile == 0 || *tarfile == 0 ) {
+       fprintf( stderr, "(libtarmap: no tar)\n" );
+       data.tarmap = 0;
+       return; // Stop here for unset or cleared environment
+    }
+    fprintf( stderr, "libtarmap: tarfile = %s\n", tarfile );
+    if ( ( data.tarmap = realpath( tarfile, 0 ) ) == 0 ) {
+       // Cannot find the tar file .. that's total badness!
+       perror( tarfile );
+       exit( 1 );
+    }
+    data.head = join( "/bin/tar xOf", data.tarmap );
+    char *cmd = join( "/bin/tar tf", data.tarmap );
+    FILE *file = popen_notarmap( cmd );
+    if ( file == 0 ) {
+       // cannot open tar file .. that's total badness! 
+       perror( data.tarmap );
+       exit( 1 );
+    }
+    data.buffer = malloc( DATASZ );
+    data.size = read( fileno( file ), data.buffer, DATASZ );
+    if ( data.size == 0 ) {
+       perror( cmd );
+       // Not a tar or empty tar .. that's total badness!
+       fprintf( stderr, "*** libtarmap: exiting\n" );
+       exit( 1 );
+    }
+    if ( data.size >= DATASZ ) {
+       // Too many filenmames .. that's total badness! ENAMETOOLONG
+       fprintf( stderr, "*** libtarmap: too large pathname table\n");
+       exit( 1 );
+    }
+    data.buffer = realloc( data.buffer, data.size );
+    pclose( file );
+    free( cmd );
+    cmd = 0;
+    char *end = data.buffer + data.size;
+    char *p;
+    int n = 0;
+    for ( p = data.buffer; p < end; p++ ) {
+       if ( *p == '\n' ) {
+           n++;
+           *p = '\000';
+       }
+    }
+    data.table = (char**) calloc( sizeof( char* ), data.count );
+    char *name = data.buffer;
+    for ( p = data.buffer; p < end; p++ ) {
+       if ( *p == 0 ) {
+           data.table[ data.count++ ] = name;
+           name = p+1;
+       }
+    }
+    qsort( data.table, data.count, sizeof( char* ), alphaorder );
+}
diff --git a/tarmap.c b/tarmap.c
new file mode 100644 (file)
index 0000000..215fe5e
--- /dev/null
+++ b/tarmap.c
@@ -0,0 +1,61 @@
+#include <dlfcn.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#define MAXSIZE 1000000
+// Add/create string to end of env var with colon separation
+static char *makeEnv(char *name,char *value) {
+    static char buffer[ MAXSIZE ]; // new variable value
+    char *old = getenv( name );
+    int length = strlen( name ) + 1 + strlen( value );
+    if ( old ) {
+       length += 1 + strlen( old );
+    }
+    if ( length >= MAXSIZE ) {
+       fprintf( stderr, "%s\n", "*** tarmap: LD_PRELOAD .. exiting" );
+       exit( 1 );
+    }
+    if ( old ) {
+       sprintf( buffer, "%s=%s:%s", name, value, old );
+    } else {
+       sprintf( buffer, "%s=%s", name, value );
+    }
+    old = strndup( buffer, length );
+    if ( old == 0 ) {
+       fprintf( stderr, "%s\n", "*** tarmap: OOM .. exiting" );
+       exit( 1 );
+    }
+    return old;
+}
+    
+int main(int argc,char *argv[],char *envp[]) {
+    if ( argc < 3 ) {
+       fprintf( stderr, "tarmap tar-file command [ argument ]\n" );
+       exit( 1 );
+    }
+    int n = 0;
+    int i_preload = -1;
+    int i_tarmap = -1;
+    for ( n = 0; envp[n]; n++) {
+       if ( strcmp( envp[n], "LD_PRELOAD=" ) == 0 ) {
+           i_preload = n;
+       } else if ( strcmp( envp[n], "TARMAP=" ) ) {
+           i_tarmap = n;
+       }
+    }
+    if ( i_preload < 0 ) {
+       i_preload = n++;
+    }
+    if ( i_tarmap < 0 ) {
+       i_tarmap = n++;
+    }
+    char **nenvp = (char**) calloc( sizeof( char* ), n+1 );
+    memcpy( nenvp, envp, n * sizeof( char*) );
+    nenvp[ i_preload ] = makeEnv( "LD_PRELOAD", "libtarmap.so" );
+    nenvp[ i_tarmap ] = makeEnv( "TARMAP", argv[1] );
+    execve( argv[2], argv+2, nenvp );
+    perror( "tarmap exec" );
+    return 1;
+}