// Copyright © 2017 Collabora Ltd // // This file is part of libcapsule. // // libcapsule is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation; either version 2.1 of the // License, or (at your option) any later version. // // libcapsule 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 Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with libcapsule. If not, see . #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "debug.h" #include "ld-libs.h" #include "utils.h" // We only really care about x86 here because that's the only thing // libcapsule supports, but we might as well be a bit more complete. // See https://sourceware.org/glibc/wiki/ABIList #if defined(__x86_64__) && defined(__ILP32__) # define LD_SO "/libx32/ld-linux-x32.so.2" #elif defined(__x86_64__) # define LD_SO "/lib64/ld-linux-x86-64.so.2" #elif defined(__sparc__) && defined(__arch64__) # define LD_SO "/lib64/ld-linux.so.2" #elif defined(__i386__) || defined(__alpha__) || defined(__sh__) || \ defined(__sparc__) # define LD_SO "/lib/ld-linux.so.2" #elif defined(__aarch64__) # define LD_SO "/lib/ld-linux-aarch64.so.1" #elif defined(__arm__) && defined(__ARM_EABI__) && defined(_ARM_PCS_VFP) # define LD_SO "/lib/ld-linux-armhf.so.3" #elif defined(__arm__) && defined(__ARM_EABI__) # define LD_SO "/lib/ld-linux.so.3" #elif defined(__hppa__) || defined(__m68k__) || defined(__powerpc__) || \ defined(__s390__) # define LD_SO "/lib64/ld.so.1" #elif defined(__powerpc64__) && __BYTE_ORDER == __LITTLE_ENDIAN # define LD_SO "/lib/ld64.so.2" #elif defined(__s390x__) || defined(__powerpc64__) # define LD_SO "/lib/ld64.so.1" #else # error Unsupported architecture: we do not know where ld.so is #endif static const char * const libc_patterns[] = { "soname:libBrokenLocale.so.1", "soname:libanl.so.1", "soname:libc.so.6", "soname:libcidn.so.1", "soname:libcrypt.so.1", "soname:libdl.so.2", "soname:libm.so.6", "soname:libmemusage.so", "soname:libmvec.so.1", "soname:libnsl.so.1", "soname:libpcprofile.so", "soname:libpthread.so.0", "soname:libresolv.so.2", "soname:librt.so.1", "soname:libthread_db.so.1", "soname:libutil.so.1", NULL }; enum { OPTION_CONTAINER, OPTION_DEST, OPTION_LINK_TARGET, OPTION_NO_GLIBC, OPTION_PRINT_LD_SO, OPTION_PROVIDER, OPTION_RESOLVE_LD_SO, }; static const char * const *arg_patterns = NULL; static const char *option_container = "/"; static const char *option_dest = "."; static const char *option_provider = "/"; static const char *option_link_target = NULL; static bool option_glibc = true; static struct option long_options[] = { { "container", required_argument, NULL, OPTION_CONTAINER }, { "dest", required_argument, NULL, OPTION_DEST }, { "help", no_argument, NULL, 'h' }, { "link-target", required_argument, NULL, OPTION_LINK_TARGET }, { "no-glibc", no_argument, NULL, OPTION_NO_GLIBC }, { "print-ld.so", no_argument, NULL, OPTION_PRINT_LD_SO }, { "provider", required_argument, NULL, OPTION_PROVIDER }, { "resolve-ld.so", required_argument, NULL, OPTION_RESOLVE_LD_SO }, { NULL } }; static int dest_fd = -1; #define strstarts(str, start) \ (strncmp( str, start, strlen( start ) ) == 0) /* Equivalent to GNU basename(3) from string.h, but not POSIX * basename(3) from libgen.h. */ static const char *my_basename (const char *path) { const char *ret = strrchr( path, '/' ); if( ret == NULL ) return path; assert( ret[0] == '/' ); return ret + 1; } static bool resolve_ld_so ( const char *prefix, char path[PATH_MAX], const char **within_prefix, int *code, char **message ) { size_t prefix_len = strlen( prefix ); if( build_filename( path, PATH_MAX, prefix, LD_SO, NULL ) >= PATH_MAX ) { _capsule_set_error( code, message, E2BIG, "prefix \"%s\" is too long", prefix ); return false; } DEBUG( DEBUG_TOOL, "Starting with %s", path ); while( resolve_link( prefix, path ) ) { DEBUG( DEBUG_TOOL, "-> %s", path ); } if( strcmp( prefix, "/" ) == 0 ) { prefix_len = 0; } if( ( prefix_len > 0 && strncmp( path, prefix, prefix_len ) != 0 ) || path[prefix_len] != '/' ) { _capsule_set_error( code, message, EXDEV, "\"%s\" is not within prefix \"%s\"", path, prefix ); return false; } if( within_prefix != NULL) *within_prefix = path + prefix_len; return true; } static void usage (int code) __attribute__((noreturn)); static void usage (int code) { FILE *fh; if( code == 0 ) { fh = stdout; } else { fh = stderr; // Assume we already printed a warning; make it stand out more fprintf( fh, "\n" ); } fprintf( fh, "Usage:\n" "%s [OPTIONS] PATTERN...\n", program_invocation_short_name ); fprintf( fh, "\tCreate symbolic links in LIBDIR that will make the\n" "\tPATTERNs from PROVIDER available, assuming LIBDIR\n" "\twill be added to the container's LD_LIBRARY_PATH.\n" ); fprintf( fh, "\n" ); fprintf( fh, "%s --print-ld.so\n", program_invocation_short_name ); fprintf( fh, "\tPrint the ld.so filename for this architecture and exit.\n" ); fprintf( fh, "%s --resolve-ld.so=TREE\n", program_invocation_short_name ); fprintf( fh, "\tPrint the absolute path of the file that implements ld.so\n" "\tin TREE.\n" ); fprintf( fh, "\n" ); fprintf( fh, "%s --help\n", program_invocation_short_name ); fprintf( fh, "\tShow this help.\n" ); fprintf( fh, "\n" ); fprintf( fh, "Options:\n" ); fprintf( fh, "--container=CONTAINER\n" "\tAssume the container will look like CONTAINER when\n" "\tdeciding which libraries are needed [default: /]\n" ); fprintf( fh, "--dest=LIBDIR\n" "\tCreate symlinks in LIBDIR [default: .]\n" ); fprintf( fh, "--link-target=PATH\n" "\tAssume PROVIDER will be mounted at PATH when the\n" "\tcontainer is used [default: PROVIDER]\n" ); fprintf( fh, "--provider=PROVIDER\n" "\tFind libraries in PROVIDER [default: /]\n" ); fprintf( fh, "--no-glibc\n" "\tDon't capture libraries that are part of glibc\n" ); fprintf( fh, "\n" ); fprintf( fh, "Each PATTERN is one of:\n" ); fprintf( fh, "\n" ); fprintf( fh, "soname:SONAME\n" "\tCapture the library in ld.so.cache whose name is\n" "\texactly SONAME\n" ); fprintf( fh, "soname-match:GLOB\n" "\tCapture every library in ld.so.cache that matches\n" "\ta shell-style glob (which will usually need to be\n" "\tquoted when using a shell)\n" ); fprintf( fh, "only-dependencies:PATTERN\n" "\tCapture the dependencies of each library matched by\n" "\tPATTERN, but not the library matched by PATTERN itself\n" "\t(unless a match for PATTERN depends on another match)\n" ); fprintf( fh, "no-dependencies:PATTERN\n" "\tCapture each library matched by PATTERN, but not\n" "\ttheir dependencies\n" ); fprintf( fh, "if-exists:PATTERN\n" "\tCapture PATTERN, but don't fail if nothing matches\n" ); fprintf( fh, "even-if-older:PATTERN\n" "\tCapture PATTERN, even if the version in CONTAINER\n" "\tappears newer\n" ); fprintf( fh, "gl:\n" "\tShortcut for even-if-older:if-exists:soname:libGL.so.1,\n" "\teven-if-older:if-exists:soname-match:libGLX_*.so.0, and\n" "\tvarious other GL-related libraries\n" ); fprintf( fh, "path:ABS-PATH\n" "\tResolve ABS-PATH as though chrooted into PROVIDER\n" "\tand capture the result\n" ); fprintf( fh, "path-match:GLOB\n" "\tResolve GLOB as though chrooted into PROVIDER\n" "\tand capture the result\n" ); fprintf( fh, "an absolute path with no '?', '*', '['\n" "\tSame as path:PATTERN\n" ); fprintf( fh, "a glob pattern starting with '/'\n" "\tSame as path-match:PATTERN\n" ); fprintf( fh, "a glob pattern with no '/'\n" "\tSame as soname-match:PATTERN\n" ); fprintf( fh, "a bare SONAME with no '/', '?', '*', '['\n" "\tSame as soname:PATTERN\n" ); exit( code ); } typedef enum { CAPTURE_FLAG_NONE = 0, CAPTURE_FLAG_EVEN_IF_OLDER = (1 << 0), CAPTURE_FLAG_IF_EXISTS = (1 << 1), CAPTURE_FLAG_LIBRARY_ITSELF = ( 1 << 2 ), CAPTURE_FLAG_DEPENDENCIES = ( 1 << 3 ), } capture_flags; static bool init_with_target( ld_libs *ldlibs, const char *tree, const char *target, int *code, char **message ) { if( !ld_libs_init( ldlibs, NULL, tree, debug_flags, code, message ) ) { goto fail; } if( !ld_libs_load_cache( ldlibs, "/etc/ld.so.cache", code, message ) ) { goto fail; } if( !ld_libs_set_target( ldlibs, target, code, message ) ) { goto fail; } return true; fail: ld_libs_finish( ldlibs ); return false; } static bool capture_patterns( const char * const *patterns, capture_flags flags, int *code, char **message ); static bool capture_one( const char *soname, capture_flags flags, int *code, char **message ) { unsigned int i; _capsule_cleanup(ld_libs_finish) ld_libs provider = {}; int local_code = 0; _capsule_autofree char *local_message = NULL; if( !init_with_target( &provider, option_provider, soname, &local_code, &local_message ) ) { if( ( flags & CAPTURE_FLAG_IF_EXISTS ) && local_code == ENOENT ) { DEBUG( DEBUG_TOOL, "%s not found, ignoring", soname ); _capsule_clear( &local_message ); return true; } else { _capsule_propagate_error( code, message, local_code, _capsule_steal_pointer( &local_message ) ); return false; } } if( !ld_libs_find_dependencies( &provider, code, message ) ) { return false; } for( i = 0; i < N_ELEMENTS( provider.needed ); i++ ) { _capsule_autofree char *target = NULL; struct stat statbuf; const char *its_basename; if( !provider.needed[i].name ) { continue; } if( i == 0 && !( flags & CAPTURE_FLAG_LIBRARY_ITSELF ) ) { DEBUG( DEBUG_TOOL, "Not capturing \"%s\" itself as requested", provider.needed[i].name ); continue; } if( i > 0 && !( flags & CAPTURE_FLAG_DEPENDENCIES ) ) { DEBUG( DEBUG_TOOL, "Not capturing dependencies of \"%s\" as requested", provider.needed[0].name ); break; } its_basename = my_basename( provider.needed[i].name ); if( !option_glibc ) { unsigned int j; bool capture = true; for( j = 0; j < N_ELEMENTS( libc_patterns ); j++ ) { if( libc_patterns[j] == NULL ) break; assert( strstarts( libc_patterns[j], "soname:" ) ); if( strcmp( libc_patterns[j] + strlen( "soname:" ), its_basename ) == 0 ) { DEBUG( DEBUG_TOOL, "Not capturing \"%s\" because it is part of glibc", provider.needed[i].name ); capture = false; break; } } if( !capture ) continue; } if( fstatat( dest_fd, its_basename, &statbuf, AT_SYMLINK_NOFOLLOW ) == 0 ) { /* We already created a symlink for this library. No further * action required (but keep going through its dependencies * in case we need to symlink those into place) */ DEBUG( DEBUG_TOOL, "We already have a symlink for %s", provider.needed[i].name ); continue; } /* For the library we were originally looking for, we don't * compare with the container if we have EVEN_IF_OLDER flag. * For its dependencies, we ignore that flag. */ if( option_container == NULL ) { DEBUG( DEBUG_TOOL, "Container unknown, cannot compare version with " "\"%s\": assuming provider version is newer", provider.needed[i].path ); } else if( i == 0 && ( flags & CAPTURE_FLAG_EVEN_IF_OLDER ) ) { DEBUG( DEBUG_TOOL, "Explicitly requested %s from %s even if older: \"%s\"", provider.needed[i].name, option_provider, provider.needed[i].path ); } else { _capsule_cleanup(ld_libs_finish) ld_libs container = {}; if( init_with_target( &container, option_container, provider.needed[i].name, &local_code, &local_message ) ) { _capsule_autofree char *p_realpath = NULL; _capsule_autofree char *c_realpath = NULL; const char *p_basename; const char *c_basename; // This might look redundant, but resolve_symlink_prefixed() // doesn't chase symlinks if the prefix is '/' or empty. p_realpath = realpath( provider.needed[i].path, NULL ); c_realpath = realpath( container.needed[0].path, NULL ); p_basename = my_basename( p_realpath ); c_basename = my_basename( c_realpath ); DEBUG( DEBUG_TOOL, "Comparing %s \"%s\" from \"%s\" with " "\"%s\" from \"%s\"", provider.needed[i].name, p_basename, option_provider, c_basename, option_container ); /* If equal, we prefer the provider over the container */ if( strverscmp( c_basename, p_basename ) > 0 ) { /* Version in container is strictly newer: don't * symlink in the one from the provider */ DEBUG( DEBUG_TOOL, "%s is strictly newer in the container", provider.needed[i].name ); continue; } else { DEBUG( DEBUG_TOOL, "%s is newer or equal in the provider", provider.needed[i].name ); } } else if( local_code == ENOENT ) { /* else assume it's absent from the container, which is * just like it being newer in the provider */ DEBUG( DEBUG_TOOL, "%s is not in the container", provider.needed[i].name ); _capsule_clear( &local_message ); } else { _capsule_propagate_error( code, message, local_code, _capsule_steal_pointer( &local_message ) ); return false; } } /* By this point we've decided we want the version from the * provider, not the version from the container */ if( option_link_target == NULL ) { target = xstrdup( provider.needed[i].path ); } else { char path[PATH_MAX]; size_t prefix_len = strlen( option_provider ); // We need to take the realpath() inside the container, // because if we're using LD_LIBRARY_PATH rather than // libcapsule, we have to follow the chain of // $libdir/libGL.so.1 -> /etc/alternatives/whatever -> ... // within that prefix. safe_strncpy( path, provider.needed[i].path, sizeof(path) ); DEBUG( DEBUG_TOOL, "Link target initially: \"%s\"", path ); while( resolve_link( option_provider, path ) ) { DEBUG( DEBUG_TOOL, "Link target pursued to: \"%s\"", path ); } if( strcmp( option_provider, "/" ) == 0 ) { prefix_len = 0; } if( strncmp( path, option_provider, prefix_len ) != 0 || path[prefix_len] != '/' ) { warnx( "warning: \"%s\" is not within prefix \"%s\"", path, option_provider ); continue; } target = build_filename_alloc( option_link_target, path + prefix_len, NULL ); } assert( target != NULL ); DEBUG( DEBUG_TOOL, "Creating symlink %s/%s -> %s", option_dest, its_basename, target ); if( symlinkat( target, dest_fd, its_basename ) < 0 ) { warn( "warning: cannot create symlink %s/%s", option_dest, its_basename ); } if( strcmp( its_basename, "libc.so.6" ) == 0 ) { /* Having captured libc, we need to capture the rest of * the related libraries from the same place */ DEBUG( DEBUG_TOOL, "Capturing the rest of glibc to go with %s", provider.needed[i].name ); if( !capture_patterns( libc_patterns, ( flags | CAPTURE_FLAG_IF_EXISTS | CAPTURE_FLAG_EVEN_IF_OLDER ), code, message ) ) { return false; } } } return true; } typedef struct { const char *pattern; capture_flags flags; bool found; ld_cache cache; int *code; char **message; } cache_foreach_context; static intptr_t cache_foreach_cb (const char *name, int flag, unsigned int osv, uint64_t hwcap, const char *path, void *data) { cache_foreach_context *ctx = data; if( !name || !*name ) { warnx( "warning: empty name found in ld.so.cache" ); return 0; } // We don't really care about whether the library matches our class, // machine, hwcaps etc. - if we can't dlopen a library of this name, // we'll just skip it. if( fnmatch( ctx->pattern, name, 0 ) == 0 ) { DEBUG( DEBUG_TOOL, "%s matches %s", name, ctx->pattern ); ctx->found = true; if( !capture_one( name, ctx->flags | CAPTURE_FLAG_IF_EXISTS, ctx->code, ctx->message ) ) return 1; } return 0; // continue iteration } static bool capture_soname_match( const char *pattern, capture_flags flags, int *code, char **message ) { _capsule_autofree char *cache_path = NULL; cache_foreach_context ctx = { .pattern = pattern, .flags = flags, .cache = { .is_open = 0 }, .found = false, .code = code, .message = message, }; bool ret = false; DEBUG( DEBUG_TOOL, "%s", pattern ); cache_path = build_filename_alloc( option_provider, "/etc/ld.so.cache", NULL ); if( !ld_cache_open( &ctx.cache, cache_path, code, message ) ) goto out; if( ld_cache_foreach( &ctx.cache, cache_foreach_cb, &ctx ) != 0 ) goto out; if( !ctx.found && !( flags & CAPTURE_FLAG_IF_EXISTS ) ) { _capsule_set_error( code, message, ENOENT, "no matches found for glob pattern \"%s\" " "in ld.so.cache", pattern ); goto out; } ret = true; out: if( ctx.cache.is_open ) ld_cache_close( &ctx.cache ); return ret; } static bool capture_path_match( const char *pattern, capture_flags flags, int *code, char **message ) { char *abs_path = NULL; int res; bool ret = false; glob_t buffer; size_t i; DEBUG( DEBUG_TOOL, "%s", pattern ); abs_path = build_filename_alloc( option_provider, pattern, NULL ); res = glob( abs_path, 0, NULL, &buffer ); switch( res ) { case 0: ret = true; for( i = 0; i < buffer.gl_pathc; i++) { const char *path = buffer.gl_pathv[i]; if( option_provider != NULL && strcmp( option_provider, "/" ) != 0 && ( !strstarts( path, option_provider ) || path[strlen( option_provider )] != '/' ) ) { _capsule_set_error( code, message, EXDEV, "path pattern \"%s\" matches \"%s\"" "which is not in \"%s\"", pattern, path, option_provider ); globfree( &buffer ); return false; } if( !capture_one( path + strlen( option_provider ), flags, code, message ) ) { ret = false; break; } } globfree( &buffer ); break; case GLOB_NOMATCH: if( flags & CAPTURE_FLAG_IF_EXISTS ) { ret = true; } else { _capsule_set_error( code, message, ENOENT, "no matches found for glob pattern \"%s\" " "in \"%s\"", pattern, option_provider ); } break; case GLOB_NOSPACE: _capsule_set_error( code, message, ENOMEM, "unable to match glob pattern \"%s\" " "in \"%s\"", pattern, option_provider ); break; default: case GLOB_ABORTED: _capsule_set_error( code, message, EIO, "unable to match glob pattern \"%s\" " "in \"%s\"", pattern, option_provider ); break; } free( abs_path ); return ret; } static bool capture_pattern( const char *pattern, capture_flags flags, int *code, char **message ) { DEBUG( DEBUG_TOOL, "%s", pattern ); if ( ( flags & ( CAPTURE_FLAG_LIBRARY_ITSELF | CAPTURE_FLAG_DEPENDENCIES ) ) == 0 ) { _capsule_set_error( code, message, EINVAL, "combining no-dependencies: with " "only-dependencies: is meaningless, " "so \"%s\" is invalid", pattern ); return false; } if( strstarts( pattern, "path:" ) ) { if( !strstarts( pattern, "path:/" ) ) { _capsule_set_error( code, message, EINVAL, "path: requires an absolute path as " "argument, not \"%s\"", pattern ); return false; } return capture_one( pattern + strlen( "path:" ), flags, code, message ); } if( strstarts( pattern, "soname:" ) ) { return capture_one( pattern + strlen( "soname:" ), flags, code, message ); } if( strstarts( pattern, "soname-match:" ) ) { return capture_soname_match( pattern + strlen( "soname-match:" ), flags, code, message ); } if( strstarts( pattern, "path-match:" ) ) { return capture_path_match( pattern + strlen( "path-match:" ), flags, code, message ); } if( strstarts( pattern, "if-exists:" ) ) { return capture_pattern( pattern + strlen( "if-exists:" ), flags | CAPTURE_FLAG_IF_EXISTS, code, message ); } if( strstarts( pattern, "even-if-older:" ) ) { return capture_pattern( pattern + strlen( "even-if-older:" ), flags | CAPTURE_FLAG_EVEN_IF_OLDER, code, message ); } if( strstarts( pattern, "only-dependencies:" ) ) { return capture_pattern( pattern + strlen( "only-dependencies:" ), flags & ~CAPTURE_FLAG_LIBRARY_ITSELF, code, message ); } if( strstarts( pattern, "no-dependencies:" ) ) { return capture_pattern( pattern + strlen( "no-dependencies:" ), flags & ~CAPTURE_FLAG_DEPENDENCIES, code, message ); } if( strcmp( pattern, "gl:" ) == 0 ) { // Useful information: // https://devtalk.nvidia.com/default/topic/915640/multiple-glx-client-libraries-in-the-nvidia-linux-driver-installer-package/ static const char * const gl_patterns[] = { "soname:libEGL.so.1", // Vendor ICDs for libEGL.so.1 // (Registered via JSON in /usr/share/glvnd/egl_vendor.d) "soname-match:libEGL_*.so.*", "soname:libGL.so.1", "soname:libGLESv1_CM.so.1", // Vendor ICDs for libGLESv1_CM.so.1 "soname-match:libGLESv1_CM_*.so.*", "soname:libGLESv2.so.2", // Vendor ICDs for libGLESv2.so.2 "soname:libGLESv2_*.so.*", "soname:libGLX.so.0", // Vendor ICDs for libGL.so.1 and/or libGLX.so.0 "soname-match:libGLX_*.so.*", // This one looks redundant, but because it's usually a // symlink to someone else's implementation, we can't find // it in the ld.so cache under its own name: its SONAME is // libGLX_mesa.so.0 or libGLX_nvidia.so.0. So we can't find // it by wildcard-matching and have to look it up explicitly // instead. "soname:libGLX_indirect.so.0", // This is an implementation detail of GLVND, but it had // better match the GLVND dispatchers or bad things will // happen "soname-match:libGLdispatch.so.*", "soname:libOpenGL.so.0", // Mostly used by Mesa, but apps/games are also allowed to // use it directly "soname:libgbm.so.1", // Mesa libraries should have DT_NEEDED for this, but some // historical versions didn't, so it wouldn't be picked up // by recursive dependency resolution "soname:libglapi.so.0", // Some libraries are not explicitly mentioned here: // For NVIDIA, we also need libnvidia-glcore.so.$VERSION, // but it will be pulled in by dependencies, so we don't // need to list it explicitly. // For NVIDIA, we also need libnvidia-tls.so.$VERSION, // either the TLS or non-TLS version as appropriate; but // again it will be pulled in via dependencies. NULL }; /* We usually want to capture the host GL stack even if it * appears older than what's in the container. */ return capture_patterns( gl_patterns, ( flags | CAPTURE_FLAG_IF_EXISTS | CAPTURE_FLAG_EVEN_IF_OLDER ), code, message ); } if( strcmp( pattern, "nvidia:" ) == 0 ) { static const char * const gl_patterns[] = { "soname:libEGL.so.1", "soname-match:libEGL_nvidia.so.*", "soname:libGL.so.1", "soname:libGLESv1_CM.so.1", "soname-match:libGLESv1_CM_nvidia.so.*", "soname:libGLESv2.so.2", "soname-match:libGLESv2_nvidia.so.*", "soname:libGLX.so.0", "soname-match:libGLX_nvidia.so.*", "soname:libGLX_indirect.so.0", "soname-match:libGLdispatch.so.*", "soname:libOpenGL.so.0", "soname-match:libcuda.so.*", "soname-match:libglx.so.*", "soname-match:libnvcuvid.so.*", "soname-match:libnvidia-*.so.*", "soname-match:libOpenCL.so.*", "soname-match:libvdpau_nvidia.so.*", NULL }; /* We certainly want to capture the host GL stack even if it * appears older than what's in the container: the NVIDIA * proprietary drivers have to be in lockstep with the kernel. */ return capture_patterns( gl_patterns, ( flags | CAPTURE_FLAG_IF_EXISTS | CAPTURE_FLAG_EVEN_IF_OLDER ), code, message ); } if( strchr( pattern, ':' ) != NULL ) { _capsule_set_error( code, message, EINVAL, "patterns containing ':' must match a known " "mode, not \"%s\" (use soname: or path: to " "take patterns containing ':' literally, if " "necessary)", pattern ); return false; } if( pattern[0] == '/' ) { if( strchr( pattern, '*' ) != NULL || strchr( pattern, '?' ) != NULL || strchr( pattern, '[' ) != NULL ) { // Interpret as if path-match: return capture_path_match( pattern, flags, code, message ); } else { return capture_one( pattern, flags, code, message ); } } if( strchr( pattern, '/' ) != NULL ) { _capsule_set_error( code, message, EINVAL, "path arguments must be absolute, not \"%s\"", pattern ); return false; } if( strchr( pattern, '*' ) != NULL || strchr( pattern, '?' ) != NULL || strchr( pattern, '[' ) != NULL ) { // Interpret as if soname-match: return capture_soname_match( pattern, flags, code, message ); } // Default: interpret as if soname: return capture_one( pattern, flags, code, message ); } static bool capture_patterns( const char * const *patterns, capture_flags flags, int *code, char **message ) { unsigned int i; for( i = 0; patterns[i] != NULL; i++ ) { if( !capture_pattern( patterns[i], flags, code, message ) ) return false; } return true; } int main (int argc, char **argv) { capture_flags flags = (CAPTURE_FLAG_LIBRARY_ITSELF | CAPTURE_FLAG_DEPENDENCIES ); int code = 0; char *message = NULL; set_debug_flags( getenv("CAPSULE_DEBUG") ); while( 1 ) { int opt = getopt_long( argc, argv, "h", long_options, NULL ); if( opt == -1 ) { break; } switch( opt ) { case '?': default: usage( 2 ); break; // not reached case 'h': usage( 0 ); break; // not reached case OPTION_CONTAINER: option_container = optarg; break; case OPTION_DEST: option_dest = optarg; break; case OPTION_LINK_TARGET: option_link_target = optarg; break; case OPTION_PRINT_LD_SO: puts( LD_SO ); return 0; case OPTION_PROVIDER: option_provider = optarg; break; case OPTION_NO_GLIBC: option_glibc = false; break; case OPTION_RESOLVE_LD_SO: { char path[PATH_MAX] = { 0 }; const char *within_prefix = NULL; if( !resolve_ld_so( optarg, path, &within_prefix, &code, &message ) ) { errx( 1, "code %d: %s", code, message ); } puts( within_prefix ); return 0; } break; } } if( optind >= argc ) { warnx( "One or more patterns must be provided" ); usage( 2 ); } arg_patterns = (const char * const *) argv + optind; assert( arg_patterns[argc - optind] == NULL ); if( strcmp( option_dest, "." ) != 0 && mkdir( option_dest, 0755 ) < 0 && errno != EEXIST ) { err( 1, "creating \"%s\"", option_dest ); } dest_fd = open( option_dest, O_RDWR|O_DIRECTORY|O_CLOEXEC|O_PATH ); if( dest_fd < 0 ) { err( 1, "opening \"%s\"", option_dest ); } if( !capture_patterns( arg_patterns, flags, &code, &message ) ) { errx( 1, "code %d: %s", code, message ); } close( dest_fd ); return 0; }