At least the VDDK plugin needs a way to load .so files with a custom
directory prepended. Asking the user to set LD_LIBRARY_PATH is too
strong (that would leak into the child process created by --run, and
makes the user think about issues that we'd rather cover
automatically). But overriding dlopen() to make it easier to prepend
a directory name under our control requires either a separate
namespace via dlmopen() (which itself is annoying - it messes up gdb
debugging), or that the override occur prior to any other library that
also depends on -ldl. Which means that if we are going to provide
this functionality, we have to do so from nbdkit proper.
Note that properly injecting a dlopen shim for all subsequent shared
library loads requires that the override itself be in a shared
library. Thus, nbdkit has to pull it in via a shared library link,
rather than merely a .o file, and we now have to install something in
pkglibdir. What's more, now that we link against a shared library
instead of just libtool convenience libraries, libtool now creates a
wrapper app named lt-nbdkit so that it can run an in-tree build that
still locates the uninstalled shared library; this affects some
expected output in tests.
Signed-off-by: Eric Blake <eblake(a)redhat.com>
---
docs/nbdkit-plugin.pod | 14 +++++
include/nbdkit-common.h | 2 +
server/Makefile.am | 13 ++++
server/nbdkit.syms | 1 +
server/shim.c | 99 ++++++++++++++++++++++++++++++
tests/test-nbdkit-backend-debug.sh | 26 ++++----
6 files changed, 142 insertions(+), 13 deletions(-)
create mode 100644 server/shim.c
diff --git a/docs/nbdkit-plugin.pod b/docs/nbdkit-plugin.pod
index 41bffb7f..f3825067 100644
--- a/docs/nbdkit-plugin.pod
+++ b/docs/nbdkit-plugin.pod
@@ -1207,6 +1207,20 @@ and returns C<NULL>.
The returned string must be freed by the caller.
+=head2 C<nbdkit_set_dlopen_prefix>
+
+ int nbdkit_set_dlopen_prefix (const char *prefix);
+
+Some plugins load a shared library that in turn uses L<dlopen(3)> to
+load further libraries. If these libraries reside in non-standard
+locations, it may be necessary to prefix any bare library names with
+the desired directory to load them from. Rather than requiring a user
+to amend C<LD_LIBRARY_PATH> in the calling environment (which would
+have knock-on effects to nbdkit and any child process spawned by the
+B<--run> argument), the plugin can request that nbdkit rewrites all
+dlopen requests that lack a slash character to instead attempt a
+dlopen of a file residing in C<prefix>.
+
=head2 umask
All plugins will see a L<umask(2)> of C<0022>.
diff --git a/include/nbdkit-common.h b/include/nbdkit-common.h
index 50f3dd4f..44abce5e 100644
--- a/include/nbdkit-common.h
+++ b/include/nbdkit-common.h
@@ -83,6 +83,8 @@ extern void nbdkit_debug (const char *msg, ...) ATTRIBUTE_FORMAT_PRINTF
(1, 2);
extern void nbdkit_vdebug (const char *msg, va_list args)
ATTRIBUTE_FORMAT_PRINTF (1, 0);
+extern int nbdkit_set_dlopen_prefix (const char *newdir);
+
extern char *nbdkit_absolute_path (const char *path);
extern int64_t nbdkit_parse_size (const char *str);
extern int nbdkit_parse_bool (const char *str);
diff --git a/server/Makefile.am b/server/Makefile.am
index 9351fefc..b6728511 100644
--- a/server/Makefile.am
+++ b/server/Makefile.am
@@ -33,6 +33,7 @@ include $(top_srcdir)/common-rules.mk
EXTRA_DIST = nbdkit.syms
+pkglib_LTLIBRARIES = nbdkit-shim-dlopen.la
sbin_PROGRAMS = nbdkit
nbdkit_SOURCES = \
@@ -71,6 +72,17 @@ if ENABLE_LIBFUZZER
nbdkit_SOURCES += fuzzer.c
endif
+nbdkit_shim_dlopen_la_SOURCES = shim.c
+nbdkit_shim_dlopen_la_CPPFLAGS = \
+ -I$(top_srcdir)/common/utils \
+ $(NULL)
+nbdkit_shim_dlopen_la_CFLAGS = $(WARNINGS_CFLAGS)
+nbdkit_shim_dlopen_la_LIBADD = $(DL_LIBS)
+nbdkit_shim_dlopen_la_LDFLAGS = \
+ -module -no-undefined -shared -avoid-version \
+ $(DL_LDFLAGS) \
+ $(NULL)
+
nbdkit_CPPFLAGS = \
-Dbindir=\"$(bindir)\" \
-Dlibdir=\"$(libdir)\" \
@@ -92,6 +104,7 @@ nbdkit_CFLAGS = \
$(VALGRIND_CFLAGS) \
$(NULL)
nbdkit_LDADD = \
+ nbdkit-shim-dlopen.la \
$(GNUTLS_LIBS) \
$(LIBSELINUX_LIBS) \
$(DL_LIBS) \
diff --git a/server/nbdkit.syms b/server/nbdkit.syms
index 96c22c07..d20e0784 100644
--- a/server/nbdkit.syms
+++ b/server/nbdkit.syms
@@ -63,6 +63,7 @@
nbdkit_peer_name;
nbdkit_read_password;
nbdkit_realpath;
+ nbdkit_set_dlopen_prefix;
nbdkit_set_error;
nbdkit_vdebug;
nbdkit_verror;
diff --git a/server/shim.c b/server/shim.c
new file mode 100644
index 00000000..a2682820
--- /dev/null
+++ b/server/shim.c
@@ -0,0 +1,99 @@
+/* nbdkit
+ * Copyright (C) 2020 Red Hat Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of Red Hat nor the names of its contributors may be
+ * used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY RED HAT AND CONTRIBUTORS ''AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+ * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL RED HAT OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/* This file provides a shim around dlopen, to aid plugins such as
+ * vddk that want to convert relative shared library requests from
+ * subsequent code into absolute requests. This library has to be a
+ * load-time dependency of the main nbdkit executable prior to the
+ * library providing the real dlopen (libdl on Linux, libc on BSD).
+ */
+
+#include <config.h>
+
+#include <dlfcn.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "internal.h"
+
+static void *(*orig_dlopen) (const char *filename, int flags);
+
+/* NULL for normal behavior, or set when we want to override
+ * relative dlopen()s to instead be an absolute open with prefix.
+ */
+static char *dir;
+
+int
+nbdkit_set_dlopen_prefix (const char *newdir)
+{
+ free (dir);
+ if (newdir) {
+ dir = strdup (newdir);
+ if (!dir) {
+ nbdkit_error ("strdup: %m");
+ return -1;
+ }
+ }
+ else
+ dir = NULL;
+ return 0;
+}
+
+void *
+dlopen (const char *filename, int flags)
+{
+ /* Using CLEANUP_FREE would make this shared library bigger. */
+ char *tmp = NULL;
+ void *ret;
+
+ if (dir && ! strchr (filename, '/')) {
+ /* Caller expects failure to set dlerror, so we still have to call
+ * dlopen; the best we can do is log our allocation failure.
+ */
+ if (asprintf (&tmp, "%s/%s", dir, filename) >= 0)
+ filename = tmp;
+ else
+ nbdkit_debug ("dlopen: failed to allocate replacement for %s",
filename);
+ }
+
+ ret = orig_dlopen (filename, flags);
+ free (tmp);
+ return ret;
+}
+
+static void constructor (void) __attribute__ ((constructor));
+static void
+constructor (void)
+{
+ orig_dlopen = dlsym (RTLD_NEXT, "dlopen");
+}
diff --git a/tests/test-nbdkit-backend-debug.sh b/tests/test-nbdkit-backend-debug.sh
index 3a28b756..8fe78b48 100755
--- a/tests/test-nbdkit-backend-debug.sh
+++ b/tests/test-nbdkit-backend-debug.sh
@@ -1,6 +1,6 @@
#!/usr/bin/env bash
# nbdkit
-# Copyright (C) 2019 Red Hat Inc.
+# Copyright (C) 2019-2020 Red Hat Inc.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
@@ -49,10 +49,10 @@ nbdkit -U - \
--run "qemu-img convert \$nbd $out" |& tee $debug
# Should contain all debugging messages.
-grep '^nbdkit:.*debug: nofilter: open' $debug
-grep '^nbdkit:.*debug: memory: open' $debug
-grep '^nbdkit:.*debug: nofilter: pread' $debug
-grep '^nbdkit:.*debug: memory: pread' $debug
+grep 'nbdkit:.*debug: nofilter: open' $debug
+grep 'nbdkit:.*debug: memory: open' $debug
+grep 'nbdkit:.*debug: nofilter: pread' $debug
+grep 'nbdkit:.*debug: memory: pread' $debug
nbdkit -U - \
-v -D nbdkit.backend.controlpath=0 \
@@ -61,10 +61,10 @@ nbdkit -U - \
--run "qemu-img convert \$nbd $out" |& tee $debug
# Should contain only datapath messages.
-grep -v '^nbdkit:.*debug: nofilter: open' $debug
-grep -v '^nbdkit:.*debug: memory: open' $debug
-grep '^nbdkit:.*debug: nofilter: pread' $debug
-grep '^nbdkit:.*debug: memory: pread' $debug
+grep -v 'nbdkit:.*debug: nofilter: open' $debug
+grep -v 'nbdkit:.*debug: memory: open' $debug
+grep 'nbdkit:.*debug: nofilter: pread' $debug
+grep 'nbdkit:.*debug: memory: pread' $debug
nbdkit -U - \
-v -D nbdkit.backend.datapath=0 \
@@ -73,7 +73,7 @@ nbdkit -U - \
--run "qemu-img convert \$nbd $out" |& tee $debug
# Should contain only controlpath messages.
-grep '^nbdkit:.*debug: nofilter: open' $debug
-grep '^nbdkit:.*debug: memory: open' $debug
-grep -v '^nbdkit:.*debug: nofilter: pread' $debug
-grep -v '^nbdkit:.*debug: memory: pread' $debug
+grep 'nbdkit:.*debug: nofilter: open' $debug
+grep 'nbdkit:.*debug: memory: open' $debug
+grep -v 'nbdkit:.*debug: nofilter: pread' $debug
+grep -v 'nbdkit:.*debug: memory: pread' $debug
--
2.24.1