From 9a983db35c09028386bf968e3863d17f497a5a79 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Vivek=20Das=C2=A0Mohapatra?= <vivek@collabora.co.uk>
Date: Mon, 30 Oct 2017 16:30:03 +0000
Subject: [PATCH] capsule: rework the handle infrastructure to allow multiple
 capsules

Define a capsule mete-data struct which can be declared globally
per-proxy to allow libcapsule to manage a set of capsules and
merge them into one namespace if they share a /host prefix.

This allows us to have one capsule per proxied library, each
with the correct soname instead of one monolithic blob capsule
which can only have one soname.

Conflicts:
	capsule/capsule-dlmopen.c
	capsule/capsule-init.c
	capsule/capsule-wrappers.c
---
 capsule/capsule-dlmopen.c  |  66 ++++++-
 capsule/capsule-init.c     | 390 +++++++++++++++++++++++++++++++++++--
 capsule/capsule-private.h  |   4 +-
 capsule/capsule-wrappers.c |  79 +++++---
 capsule/capsule.h          | 118 ++++++++---
 5 files changed, 593 insertions(+), 64 deletions(-)

diff --git a/capsule/capsule-dlmopen.c b/capsule/capsule-dlmopen.c
index 7e3546a9b..95ac4175c 100644
--- a/capsule/capsule-dlmopen.c
+++ b/capsule/capsule-dlmopen.c
@@ -241,8 +241,62 @@ capsule_load (const capsule cap,
 {
     void *ret = NULL;
     ld_libs_t ldlibs = {};
+    capsule_metadata *cm = NULL;
+
+    // if we were passed a default new-namespace flag see
+    // if we've already established a namespace for this
+    // filesystem prefix in the global cache and use that:
+    DEBUG( DEBUG_CAPSULE, "requested namespace %ld for %s (newlm: %d)",
+           *namespace, dso, LM_ID_NEWLM );
+    if( *namespace == LM_ID_NEWLM )
+    {
+        // first fetch the subcapsule metadata for this specific
+        // target DSO if it exists:
+        for( size_t n = 0; n < capsule_manifest->next; n++ )
+        {
+            capsule_metadata *m = ptr_list_nth_ptr( capsule_manifest, n );
+            DEBUG( DEBUG_CAPSULE, "checking metadata %d for %p, %s",
+                   n, m, m ? m->soname : "---" );
+
+            if( !m || strcmp( dso, m->soname ) )
+                continue;
+
+            cm = m;
+            break;
+        }
+
+        DEBUG( DEBUG_CAPSULE, "metadata %p found for %s (LM: %ld)",
+               cm, cm->soname, cm->namespace );
+
+        // next, if we have subcapsule metadata, see if there's
+        // a Lmid_t value alrady allocated for it:
+        if( cm && cm->namespace == LM_ID_NEWLM )
+            for( size_t n = 0; n < capsule_manifest->next; n++ )
+            {
+                capsule_metadata *m = ptr_list_nth_ptr( capsule_manifest, n );
+
+                // this subcapsule is for a different prefix. skip it:
+                //DEBUG( DEBUG_CAPSULE, "checking prefix match: %p vs %p",
+                //       cap->prefix, m->active_prefix );
+                if( strcmp( cap->prefix, m->active_prefix ) )
+                    continue;
+
+                // we found a pre-allocated Lmid_t. Use it:
+                if( m->namespace != LM_ID_NEWLM )
+                {
+                    DEBUG( DEBUG_CAPSULE, "re-using LM: %ld from %s",
+                           m->namespace, m->soname );
+                    *namespace = cm->namespace = m->namespace;
+                    break;
+                }
+            }
+    }
+
+    ld_libs_init( &ldlibs,
+                  (const char **) cap->meta->combined_exclude,
+                  cap->meta->active_prefix, debug_flags, errcode, error );
 
-    if( !ld_libs_init( &ldlibs, cap->exclude, cap->prefix, debug_flags, errcode, error ) )
+    if( errcode && *errcode )
         return NULL;
 
     // ==================================================================
@@ -273,7 +327,11 @@ capsule_load (const capsule cap,
     // ==================================================================
     // load the stack of DSOs we need:
     ret = ld_libs_load( &ldlibs, namespace, 0, errcode, error );
-    cap->namespace = *namespace;
+
+    cap->meta->namespace = *namespace;
+
+    if( cm )
+        cm->namespace = *namespace;
 
     if( debug_flags & DEBUG_CAPSULE )
     {
@@ -287,7 +345,9 @@ capsule_load (const capsule cap,
     // TODO: failure in the dlopen fixup phase should probably be fatal:
     if( ret      != NULL && // no errors so far
         wrappers != NULL )  // have a dlopen fixup function
-        install_wrappers( ret, wrappers, cap->exclude, errcode, error );
+        install_wrappers( ret, wrappers,
+                          (const char **)cap->meta->combined_exclude,
+                          errcode, error );
 
     cap->dl_handle = ret;
 
diff --git a/capsule/capsule-init.c b/capsule/capsule-init.c
index 85f1f9d16..767d0dcb4 100644
--- a/capsule/capsule-init.c
+++ b/capsule/capsule-init.c
@@ -2,40 +2,408 @@
 #include "capsule/capsule-private.h"
 #include "utils/utils.h"
 #include <stdlib.h>
+#include <stdio.h>
 #include <dlfcn.h>
+#include <string.h>
+#include <ctype.h>
+#include <sys/param.h>
+
+#define CAP_ENV_PREFIX "CAPSULE_"
+
+#define DUMP_STRV(what, css) \
+    ({ int _i = 0; for( char **_c = css; _c && *_c; _c++, _i++ ) \
+            DEBUG( DEBUG_CAPSULE, "  ->%s[ %02d ]: %s", #what, _i, *_c ); })
+
+#define DUMP_METADATA(x,m) ({  \
+        DEBUG( DEBUG_CAPSULE, "\nMETADATA #%d\n", x );     \
+        DEBUG( DEBUG_CAPSULE, "                        \n" \
+              "struct _capsule_metadata %p"          "\n"  \
+              "{"                                    "\n"  \
+              "  Lmid_t namespace;              %ld" "\n"  \
+              "  const char  *soname;           %s"  "\n"  \
+              "  const char  *default_prefix;   %s"  "\n"  \
+              "  char        *active_prefix;    %s"  "\n"  \
+              "  const char **exclude;          %p"  "\n"  \
+              "  const char **export;           %p"  "\n"  \
+              "  const char **nowrap;           %p"  "\n"  \
+              "  char **combined_exclude;       %p"  "\n"  \
+              "  char **combined_export;        %p"  "\n"  \
+              "  char **combined_nowrap;        %p"  "\n"  \
+              "};"                                   "\n", \
+              m,                                           \
+              m->namespace        ,                        \
+              m->soname           ,                        \
+              m->default_prefix   ,                        \
+              m->active_prefix    ,                        \
+              m->exclude          ,                        \
+              m->export           ,                        \
+              m->nowrap           ,                        \
+              m->combined_exclude ,                        \
+              m->combined_export  ,                        \
+              m->combined_nowrap  ); })
+
+typedef struct _tree_data
+{
+    const char *prefix;
+    ptr_list *list;
+} tree_data;
+
+static ptr_list *tree_exclusions = NULL;
+static ptr_list *tree_exports    = NULL;
+static ptr_list *tree_nowrap     = NULL;
+
+ptr_list *capsule_manifest  = NULL;
 dlsymfunc capsule_dl_symbol = NULL;
 dlopnfunc capsule_dl_open   = NULL;
 
+static int ptr_equal (const void *a, const void *b)
+{
+    return (a == b) ? 1 : 0;
+}
+
+static int str_equal (const void *a, const void *b)
+{
+    if( a == b )
+        return 1;
+
+    if( !a || !b )
+        return 0;
+
+    return strcmp( (char *)a, (char *)b ) == 0;
+}
+
+static tree_data *
+get_prefix_data (const char *prefix, ptr_list **cache)
+{
+    tree_data *td;
+
+    if( *cache == NULL )
+        *cache = ptr_list_alloc( 4 );
+
+    for( size_t x = 0; x < (*cache)->next; x++ )
+    {
+        td = ptr_list_nth_ptr( *cache, x );
+
+        if( !td || strcmp( prefix, td->prefix ) )
+            continue;
+
+        return td;
+    }
+
+    td = calloc( 1, sizeof(tree_data) );
+
+    td->prefix      = prefix;
+    td->list        = ptr_list_alloc( 4 );
+
+    ptr_list_push_ptr( *cache, td );
+
+    return td;
+}
+
+static tree_data *
+get_excludes (const char *prefix)
+{
+    return get_prefix_data( prefix, &tree_exclusions );
+}
+
+static tree_data *
+get_exports (const char *prefix)
+{
+    return get_prefix_data( prefix, &tree_exports );
+}
+
+static tree_data *
+get_nowrap (const char *prefix)
+{
+    return get_prefix_data( prefix, &tree_nowrap );
+}
+
+static void
+get_capsule_metadata (struct link_map *map, ptr_list *info, const char *only)
+{
+    ElfW(Addr) base = map->l_addr;
+    ElfW(Dyn) *dyn  = map->l_ld;
+    ElfW(Dyn) *entry;
+    ElfW(Sym) *symbol;
+
+    const void *strtab = NULL;
+    const void *symtab = NULL;
+
+    if( map->l_name == NULL || *map->l_name == '\0' )
+        return;
+
+    for( entry = dyn; entry->d_tag != DT_NULL; entry++ )
+    {
+        switch( entry->d_tag )
+        {
+          case DT_SYMTAB:
+            symtab = (const void *) fix_addr( (void *)base, entry->d_un.d_ptr );
+            break;
+
+          case DT_STRTAB:
+            strtab = (const void *) fix_addr( (void *)base, entry->d_un.d_ptr );
+            break;
+
+          default:
+            break;
+        }
+    }
+
+    if( !strtab || !symtab )
+        return;
+
+    for( symbol = (ElfW(Sym) *)symtab;
+         ( (ELFW_ST_TYPE(symbol->st_info) < STT_NUM) &&
+           (ELFW_ST_BIND(symbol->st_info) < STB_NUM) );
+         symbol++ )
+    {
+        capsule_metadata *meta = NULL;
+        char *name = (char *)strtab + symbol->st_name;
+
+        if( !name || strcmp( "capsule_meta", name ) )
+            continue;
+
+        meta = (capsule_metadata *)fix_addr( (void *)base, symbol->st_value );
+
+        // if we were asked for a specific soname's meta data then ignore
+        // everything else:
+        if( only && strcmp( only, meta->soname ) )
+            continue;
+
+        meta->namespace = LM_ID_NEWLM;
+
+        if( meta->active_prefix )
+            free( meta->active_prefix );
+
+        meta->active_prefix =
+          capsule_get_prefix( meta->default_prefix, meta->soname );
+
+        ptr_list_add_ptr( info, meta, ptr_equal );
+        DEBUG( DEBUG_CAPSULE, "found metatdata for %s … %s at %p",
+               meta->active_prefix, meta->soname, meta );
+        break;
+    }
+}
+
+static char **
+cook_list (tree_data *tx)
+{
+    char **cooked = calloc( tx->list->next + 1, sizeof(char *) );
+
+    for( size_t j = 0; j < tx->list->next; j++ )
+        *(cooked + j) = (char *)ptr_list_nth_ptr( tx->list, j );
+
+    *(cooked + tx->list->next) = NULL;
+
+    return cooked;
+}
+
+static void
+add_new_strings_to_ptrlist (ptr_list *list, const char **strings)
+{
+    for( char **c = (char **)strings; c && *c; c++ )
+        ptr_list_add_ptr( list, *c, str_equal );
+}
+
+static void
+free_strv (char **strv)
+{
+    if( !strv )
+        return;
+
+    free( strv );
+}
+
+static void
+update_metadata (const char *match)
+{
+    struct link_map *map;
+    void *handle;
+
+    handle = dlmopen( LM_ID_BASE, NULL, RTLD_LAZY|RTLD_NOLOAD );
+    dlinfo( handle, RTLD_DI_LINKMAP, &map );
+
+    // we're not guaranteed to be at the start of the link map chain:
+    while( map->l_prev )
+        map = map->l_prev;
+
+    // pick up any capsule metadata we can see:
+    // if the soname (match) is NULL, this means _all_ metadata,
+    // otherwise just the metadata that is string-equal:
+    if (map->l_next)
+        for( struct link_map *m = map; m; m = m->l_next )
+            get_capsule_metadata( m, capsule_manifest, match );
+
+    // merge the string lists for each active prefix:
+    // ie all excludes for /host should be in one exclude list,
+    // all nowrap entries for /host should bein another list, all
+    // excludes for /badgerbadger should be in another, etc etc:
+    for( size_t i = 0; i < capsule_manifest->next; i++ )
+    {
+        capsule_metadata *cm = ptr_list_nth_ptr( capsule_manifest, i );
+        tree_data *tx = get_excludes( cm->active_prefix );
+        tree_data *te = get_exports ( cm->active_prefix );
+        tree_data *tn = get_nowrap  ( cm->active_prefix );
+
+        add_new_strings_to_ptrlist( tx->list, cm->exclude );
+        add_new_strings_to_ptrlist( te->list, cm->export  );
+        add_new_strings_to_ptrlist( tn->list, cm->nowrap  );
+    }
+
+    // now squash the meta data ptr_lists into char** that
+    // the underlying infrastructure actually uses:
+    for( size_t i = 0; i < capsule_manifest->next; i++ )
+    {
+        capsule_metadata *cm = ptr_list_nth_ptr( capsule_manifest, i );
+        tree_data *tx = get_excludes( cm->active_prefix );
+        tree_data *te = get_exports ( cm->active_prefix );
+        tree_data *tn = get_nowrap  ( cm->active_prefix );
+
+        free_strv( cm->combined_exclude );
+        free_strv( cm->combined_export  );
+        free_strv( cm->combined_nowrap  );
+
+        cm->combined_exclude = cook_list( tx );
+        cm->combined_export  = cook_list( te );
+        cm->combined_nowrap  = cook_list( tn );
+    }
+}
+
 static void __attribute__ ((constructor)) _init_capsule (void)
 {
+    capsule_manifest = ptr_list_alloc( 16 );
+
+    set_debug_flags( secure_getenv("CAPSULE_DEBUG") );
+
+    update_metadata( NULL );
+
     capsule_dl_symbol = dlsym( RTLD_DEFAULT, "dlsym"  );
     capsule_dl_open   = dlsym( RTLD_DEFAULT, "dlopen" );
 
+    if( !(debug_flags & DEBUG_CAPSULE) )
+        return;
+
+    for( unsigned int x = 0; x < capsule_manifest->next; x++ )
+    {
+        capsule_metadata *cm = capsule_manifest->loc[ x ].ptr;
+        const char **soname = cm->exclude;
+
+        DEBUG( DEBUG_CAPSULE, "[%02d] %s metadata:\n", x, cm->soname );
+
+        while( soname && *soname )
+            fprintf( stderr, "    %s\n", *(soname++) );
+    }
+}
+
+char *
+capsule_get_prefix (const char *dflt, const char *soname)
+{
+    char env_var[PATH_MAX] = CAP_ENV_PREFIX;
+    const size_t offs = strlen( CAP_ENV_PREFIX );
+    size_t x = 0;
+    char *prefix = NULL;
+
+    for( ; (x < strlen(soname)) && (x + offs < PATH_MAX); x++ )
+    {
+        char c = *(soname + x);
+
+        env_var[ x + offs ] = isalnum( c ) ? toupper( c ) : '_';
+    }
+
+    safe_strncpy( &env_var[ x + offs ], "_PREFIX", PATH_MAX - (x + offs + 1) );
+    env_var[ PATH_MAX - 1 ] = '\0';
+
+    fprintf(stderr, "checking %s\n", &env_var[0] );
+    if( (prefix = secure_getenv( &env_var[0] )) )
+    {
+        DEBUG( DEBUG_SEARCH, "Capsule prefix is %s: %s", &env_var[0], prefix );
+        return strdup( prefix );
+    }
+
+    fprintf(stderr, "checking %s\n", CAP_ENV_PREFIX "PREFIX" );
+    if( (prefix = secure_getenv( CAP_ENV_PREFIX "PREFIX" )) )
+    {
+        DEBUG( DEBUG_SEARCH, "Capsule prefix is "CAP_ENV_PREFIX"PREFIX: %s",
+               prefix );
+        return strdup( prefix );
+    }
+
+    if( dflt )
+    {
+        DEBUG( DEBUG_SEARCH, "Capsule prefix is built-in: %s", dflt );
+        return strdup( dflt );
+    }
+
+    DEBUG( DEBUG_SEARCH, "Capsule prefix is missing" );
+    return NULL;
 }
 
+static capsule_metadata *
+get_cached_metadata (const char *soname)
+{
+    size_t i = 0;
+    capsule_metadata *meta = NULL;
+
+    for( size_t n = 0; n < capsule_manifest->next; n++ )
+    {
+        capsule_metadata *cm = ptr_list_nth_ptr( capsule_manifest, n );
+
+        if( !cm || strcmp( cm->soname, soname ) )
+            continue;
+
+        i = n;
+        meta = cm;
+        break;
+    }
+
+    if(meta)
+        DUMP_METADATA(i, meta);
+
+    return meta;
+}
 
 capsule
-capsule_init (Lmid_t namespace,
-              const char *prefix,
-              const char **exclude,
-              const char **exported)
+capsule_init (const char *soname)
 {
     capsule handle = xcalloc( 1, sizeof(struct _capsule) );
+    capsule_metadata *meta = NULL;
 
-    set_debug_flags( secure_getenv("CAPSULE_DEBUG") );
+    meta = get_cached_metadata( soname );
 
-    handle->namespace  = namespace;
-    handle->prefix     = prefix;
-    handle->exclude    = exclude;
-    handle->exported   = exported;
+    if( !meta )
+    {
+        DEBUG( DEBUG_CAPSULE, "no metadata for %s registered: "
+               "may be a dlopened capsule", soname );
+        DEBUG( DEBUG_CAPSULE, "updating capsule metadata" );
+        update_metadata( soname );
+        meta = get_cached_metadata( soname );
+    }
 
+    if( meta )
+    {
+        handle->prefix  = meta->active_prefix;
+
+        for( size_t i = 0; i < capsule_manifest->next; i++ )
+        {
+            capsule_metadata *cm = ptr_list_nth_ptr( capsule_manifest, i );
+            DEBUG( DEBUG_CAPSULE, " ");
+            DUMP_METADATA( i, cm );
+            DUMP_STRV( excluded, cm->combined_exclude );
+            DUMP_STRV( exported, cm->combined_export  );
+            DUMP_STRV( nowrap,   cm->combined_nowrap  );
+        }
+    }
     // in principle we should be able to make both reloc calls
     // efficient in the same do-not-redo-your-work way, but for
     // some reason the unrestricted relocation breaks if we turn
     // this one on. setting the tracker to NULL disables for now.
-    handle->seen.all  = NULL;
+    handle->seen.all  = ptr_list_alloc( 32 );
     handle->seen.some = ptr_list_alloc( 32 );
 
+    // poke the initialised metadata back into the handle
+    // and vice versa:
+    handle->meta = meta;
+    meta->handle = handle;
+
     return handle;
 }
-
diff --git a/capsule/capsule-private.h b/capsule/capsule-private.h
index 6b85e3ef8..9602ccc32 100644
--- a/capsule/capsule-private.h
+++ b/capsule/capsule-private.h
@@ -8,13 +8,11 @@ typedef void * (*dlopnfunc) (const char *file, int flags);
 
 struct _capsule
 {
-    Lmid_t namespace;
     void  *dl_handle;
     const char *prefix;
-    const char **exclude;  // list of DSOs _not_ to install in the capsule
-    const char **exported; // list of DSOs from which to export
     capsule_item *relocations;
     struct { ptr_list *all; ptr_list *some; } seen;
+    capsule_metadata *meta;
 };
 
 extern ptr_list *capsule_manifest;
diff --git a/capsule/capsule-wrappers.c b/capsule/capsule-wrappers.c
index d0384fa97..b163c8fbf 100644
--- a/capsule/capsule-wrappers.c
+++ b/capsule/capsule-wrappers.c
@@ -10,9 +10,9 @@
 #include <errno.h>
 
 static int
-dso_is_exported (const char *dsopath, const char **exported)
+dso_is_exported (const char *dsopath, char **exported)
 {
-    for( const char **ex = exported; ex && *ex; ex++ )
+    for( char **ex = exported; ex && *ex; ex++ )
         if( soname_matches_path( *ex, dsopath ) )
             return 1;
 
@@ -20,20 +20,35 @@ dso_is_exported (const char *dsopath, const char **exported)
 }
 
 void *
-capsule_external_dlsym (capsule cap, void *handle, const char *symbol)
+capsule_external_dlsym (void *handle, const char *symbol)
 {
     DEBUG( DEBUG_DLFUNC|DEBUG_WRAPPERS, "dlsym(%s)", symbol );
     void *addr = NULL;
 
-    if( addr )
+    for( size_t n = 0; n < capsule_manifest->next; n++ )
     {
-        Dl_info dso = { 0 };
+        capsule_metadata *m = ptr_list_nth_ptr( capsule_manifest, n );
+        addr = capsule_dl_symbol ( m->handle->dl_handle, symbol );
 
-        // only keep addr from the capsule if it's from an exported DSO:
-        // or if we are unable to determine where it came from (what?)
-        if( dladdr( addr, &dso ) )
-            if( !dso_is_exported( dso.dli_fname, cap->exported ) )
-                addr = NULL;
+        if( addr )
+        {
+            Dl_info dso = { 0 };
+
+            // only keep addr from the capsule if it's from an exported DSO:
+            // or if we are unable to determine where it came from (what?)
+            if( dladdr( addr, &dso ) )
+            {
+                if( !dso_is_exported( dso.dli_fname, m->combined_export ) )
+                    addr = NULL;
+
+                DEBUG( DEBUG_DLFUNC|DEBUG_WRAPPERS,
+                       "symbol %s is from soname %s - %s",
+                       symbol, dso.dli_fname, addr ? "OK" : "Ignored" );
+
+                if( addr )
+                    break;
+            }
+        }
     }
 
     if( addr == NULL )
@@ -47,7 +62,7 @@ capsule_external_dlsym (capsule cap, void *handle, const char *symbol)
 }
 
 void *
-capsule_external_dlopen(const capsule cap, const char *file, int flag)
+capsule_external_dlopen(const char *file, int flag)
 {
     void *handle = NULL;
     char *error  = NULL;
@@ -70,11 +85,30 @@ capsule_external_dlopen(const capsule cap, const char *file, int flag)
             debug_flags = DEBUG_RELOCS|DEBUG_SEARCH;
         // This may not even be necessary, so it should not be fatal.
         // We do want to log it though as it might be an important clue:
-        if( capsule_relocate( cap, NULL, &error ) != 0 )
+        for( size_t n = 0; n < capsule_manifest->next; n++ )
         {
-            fprintf( stderr, "relocation after dlopen(%s, …) failed: %s\n",
-                     file, error );
-            free( error );
+            capsule_metadata *m = ptr_list_nth_ptr( capsule_manifest, n );
+            const capsule c = m->handle;
+
+            if( capsule_relocate( c, NULL, &error ) != 0 )
+            {
+                fprintf( stderr,
+                         "relocation from %s after dlopen(%s, …) failed: %s\n",
+                         m->soname, file, error );
+                free( error );
+            }
+
+            if( capsule_relocate_except( c,
+                                         m->dl_wrappers,
+                                         (const char **)m->combined_nowrap,
+                                         &error ) != 0 )
+            {
+                fprintf( stderr,
+                         "dl-wrapper relocation from %s after "
+                         "dlopen(%s, …) failed: %s\n",
+                         m->soname, file, error );
+                free( error );
+            }
         }
 
         debug_flags = df;
@@ -93,17 +127,12 @@ capsule_shim_dlopen(const capsule cap, const char *file, int flag)
 
     DEBUG( DEBUG_WRAPPERS|DEBUG_DLFUNC,
            "dlopen(%s, %x) wrapper: LMID: %ld; prefix: %s;",
-           file, flag, cap->namespace, cap->prefix );
+           file, flag, cap->meta->namespace, cap->meta->active_prefix );
 
     if( cap->prefix && strcmp(cap->prefix, "/") )
     {
-        if( !ld_libs_init( &ldlibs, cap->exclude, cap->prefix, debug_flags,
-                           &code, &errors ) )
-        {
-            DEBUG( DEBUG_LDCACHE|DEBUG_WRAPPERS|DEBUG_DLFUNC,
-                   "ld_libs_init: error %d: %s", code, errors );
-            goto cleanup;
-        }
+        ld_libs_init( &ldlibs, (const char **)cap->meta->combined_exclude,
+                      cap->prefix, debug_flags, &code, &errors );
 
         if( !ld_libs_load_cache( &ldlibs, "/etc/ld.so.cache", &code, &errors ) )
         {
@@ -131,7 +160,7 @@ capsule_shim_dlopen(const capsule cap, const char *file, int flag)
         }
 
         // load them up in reverse dependency order:
-        res = ld_libs_load( &ldlibs, &cap->namespace, flag, &code, &errors );
+        res = ld_libs_load( &ldlibs, &cap->meta->namespace, flag, &code, &errors );
 
         if( !res )
             DEBUG( DEBUG_WRAPPERS|DEBUG_DLFUNC,
@@ -141,7 +170,7 @@ capsule_shim_dlopen(const capsule cap, const char *file, int flag)
     }
     else // no prefix: straightforward dlmopen into our capsule namespace:
     {
-        res = dlmopen( cap->namespace, file, flag );
+        res = dlmopen( cap->meta->namespace, file, flag );
 
         if( !res )
             DEBUG( DEBUG_WRAPPERS|DEBUG_DLFUNC,
diff --git a/capsule/capsule.h b/capsule/capsule.h
index f8cb4157b..3baa512c2 100644
--- a/capsule/capsule.h
+++ b/capsule/capsule.h
@@ -68,29 +68,57 @@ struct _capsule_item
 };
 
 /**
- * capsule_init:
- * @namespace: An #Lmid_t value. (usually %LM_ID_NEWLM)
- * @prefix: The mount point of the foreign tree in wich to find DSOs
+ * capsule_metadata:
+ * @namespace: #Lmid_t value for the namespace used for this capsule
+ * @soname: The soname of the encapsulated library
+ * @default_prefix: The default root location of the filesystem from which the
+ *                  encapsulated library should be loaded
  * @exclude: (array zero-terminated=1): an array of char *, each
  *           specifying a DSO not to load, terminated by a %NULL entry
- * @exported: An array of DSO names considered to ba valid symbol sources
+ * @export: (array zero-terminated=1): an array of char *, each
+ *          specifying a DSO whose symbols should be exported from this
+ *          capsule
+ * @nowrap: (array zero-terminated=1): an array of char *, each specifying
+ *          a DSO whose dlopen() implementation should _not_ be wrapped
+ *          should typically contain the libc cluster and the capsule proxy
+ *          library itself
+ *
+ * This struct allows libcapsule proxy libraries to statically declare
+ * metadata about themselves that libcapsule needs at link time in order
+ * to function properly.
+ */
+typedef struct _capsule_metadata capsule_metadata;
+
+struct _capsule_metadata
+{
+    /*< public >*/
+    Lmid_t namespace;
+    const char   *soname;
+    const char   *default_prefix;
+    const char  **exclude;
+    const char  **export;
+    const char  **nowrap;
+    capsule_item *dl_wrappers;
+    /*< private >*/
+    char   *active_prefix;
+    char  **combined_exclude;
+    char  **combined_export;
+    char  **combined_nowrap;
+    capsule handle;
+};
+
+/**
+ * capsule_init:
+ * @soname: the soname of the target library
  *
  * Returns: a #capsule handle.
  *
  * Does any initialisation necessary to use libcapsule's functions.
  * Currently just initialises the debug flags from the CAPSULE_DEBUG
  * environment variable.
- *
- * The #Lmid_t value @namespace should normally be %LM_ID_NEWLM
- * to create a new namespace.
- *
- * An empty ("") or void (%NULL) @prefix is equivalent to "/".
  */
 _CAPSULE_PUBLIC
-capsule capsule_init (Lmid_t namespace,
-                      const char *prefix,
-                      const char **exclude,
-                      const char **exported);
+capsule capsule_init (const char *soname);
 
 /**
  * capsule_relocate:
@@ -230,7 +258,6 @@ void *capsule_shim_dlopen(const capsule capsule, const char *file, int flag);
 
 /**
  * capsule_external_dlsym:
- * @cap: A dl handle as returned by capsule_load()
  * @handle: A dl handle, as passed to dlsym()
  * @symbol: A symbol name, as passed to dlsym()
  *
@@ -246,17 +273,64 @@ void *capsule_shim_dlopen(const capsule capsule, const char *file, int flag);
  *
  * Instead we must intercept dlsym() calls made outside the capsule
  * and attempt to look for the required symbol in the namespace defined
- * by @capsule first - If the required symbol is found there AND is
- * from one of the DSO names present in @exported then that symbol is
- * returned. If either of those conditions is not met then a normal
- * dlsym call with the passed handle is made.
+ * by the active capsules first - If the required symbol is found there
+ * AND is from one of the DSO names present in the exported list then that
+ * symbol is returned. If either of those conditions is not met then
+ * a normal dlsym call with the passed handle is made.
  *
  * This function provides the functionality described above, and is
- * intended for use in a suitable wrapper implemented in the the shim
- * library.
+ * normally used automaticaly by libcapsule. It is exposed as API in
+ * case a libcapsule proxy library needs to do its own specialised
+ * symbol lookups.
+ */
+_CAPSULE_PUBLIC
+void *capsule_external_dlsym (void *handle, const char *symbol);
+
+/**
+ * capsule_external_dlopen:
+ * @file: A soname, filename or path as passed to dlopen()
+ * @flag: The dl flags, as per dlopen()
+ *
+ * Returns: a void * handle, as per dlopen()
+ *
+ * This wrapper is meant to be replace normal calls to dlopen() made by the
+ * main program or a non-capsule library - it is necessary because en ELF
+ * object loaded by dlopen() may need us to trigger the capsule_relocate()
+ * operation in order to make sure its GOT entries are correctly updated.
+ *
+ * This wrapper carries out a normal dlopen() and then re-triggers the
+ * initial capsule_relocate() call immediately, before returning
+ * the same value that dlopen() would have, given the same @file and @flag
+ * arguments.
  */
 _CAPSULE_PUBLIC
-void *capsule_external_dlsym (capsule cap, void *handle, const char *symbol);
+void *capsule_external_dlopen(const char *file, int flag);
+
+/**
+ * capsule_get_prefix:
+ * @dflt: A default capsule prefix path
+ * @soname: The soname of the library we are encapsulating
+ *
+ * Returns: A newly allocated char * pointing to the prefix path
+ *
+ * libcapsule provides a proxy to a library, potentially from a foreign
+ * filesystem tree (found at, for example, ‘/host’).
+ *
+ * Since it is useful for this location to be overrideable at startup
+ * on a per-target-library basis this function standardises the prefix
+ * selection algorithm as follows:
+ *
+ * - An environment variable based on @soname:
+ *   libGL.so.1 would map to CAPSULE_LIBGL_SO_1_PREFIX
+ * - If that is unset, the CAPSULE_PREFIX environment variable
+ * - Next: The default to the value passed in @dflt
+ * - And if all that failed, NULL (which is internally equivalent to "/")
+ *
+ * Although the value is newly allocated it will typically be cached
+ * in a structure that needs to survive the entire lifespan of the
+ * running program, so freeing it is unlikely to be a concern.
+ **/
+_CAPSULE_PUBLIC
+char *capsule_get_prefix(const char *dflt, const char *soname);
 
 _CAPSULE_PUBLIC
-void *capsule_external_dlopen(const capsule cap, const char *file, int flag);
-- 
GitLab