At least the VDDK plugin needs a way to load .so files with a custom
directory prepended. We have previously documented that a user can do
this by overriding LD_LIBRARY_PATH in the environment prior to
starting nbdkit, but that is rather strong (that variable leaks into
the child process created by --run, and makes the user think about
issues that we'd rather cover automatically; and while vddk's .load
could call setenv() prior to when the --run child gets forked to scrub
LD_LIBRARY_PATH, it is hard to discern if scrubbing it would go
against the user's intentions to have it pass unmodified to the
child). So a better option is to make it possible to perform a
dynamic override of dlopen() where we can limit the scope of the
override to JUST the nbdkit process, without the user having to mess
with environment variables.
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
dlopen() (either by a shared library linked in before -ldl, or by
using -rdynamic - thankfully, we are already using -rdynamic, as shown
by nbdkit.syms). But it does mean that if we are going to provide
dlopen() enhancement functionality, we have to do so from nbdkit
proper.
Signed-off-by: Eric Blake <eblake(a)redhat.com>
---
docs/nbdkit-plugin.pod | 14 ++++++
include/nbdkit-common.h | 2 +
server/Makefile.am | 3 +-
server/nbdkit.syms | 5 ++-
server/shim.c | 97 +++++++++++++++++++++++++++++++++++++++++
5 files changed, 119 insertions(+), 2 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..e184c66e 100644
--- a/server/Makefile.am
+++ b/server/Makefile.am
@@ -59,6 +59,7 @@ nbdkit_SOURCES = \
protocol-handshake-newstyle.c \
public.c \
quit.c \
+ shim.c \
signals.c \
socket-activation.c \
sockets.c \
@@ -116,7 +117,7 @@ endif
BUILT_SOURCES = synopsis.c
EXTRA_DIST += synopsis.c
-EXTRA_nbdkit_DEPENDENCIES = synopsis.c
+EXTRA_nbdkit_DEPENDENCIES = synopsis.c nbdkit.syms
CLEANFILES += synopsis.c
main.c: synopsis.c
synopsis.c: $(top_srcdir)/docs/synopsis.txt
diff --git a/server/nbdkit.syms b/server/nbdkit.syms
index 96c22c07..0255168e 100644
--- a/server/nbdkit.syms
+++ b/server/nbdkit.syms
@@ -36,8 +36,10 @@
# to export.
{
- # The functions we want plugins and filters to call.
+ # The functions we want plugins and filters to call. The export of
+ # dlopen is needed for nbdkit_set_dlopen_prefix (see shim.c).
global:
+ dlopen;
nbdkit_absolute_path;
nbdkit_add_extent;
nbdkit_debug;
@@ -63,6 +65,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..dc8bdbc7
--- /dev/null
+++ b/server/shim.c
@@ -0,0 +1,97 @@
+/* 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. For subsequent shared
+ * libraries to see our shim rather than the original dlopen (from
+ * libdl on Linux or libc on BSD), we have to export it from
+ * nbdkit.syms.
+ */
+
+#include <config.h>
+
+#include <dlfcn.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "internal.h"
+#include "cleanup.h"
+
+static void *(*orig_dlopen) (const char *filename, int flags);
+
+/* NULL for normal behavior, or set when we want to override a
+ * relative dlopen() to instead use dir to form an absolute name.
+ */
+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)
+{
+ CLEANUP_FREE char *tmp = NULL;
+
+ 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);
+ }
+
+ return orig_dlopen (filename, flags);
+}
+
+static void constructor (void) __attribute__ ((constructor));
+static void
+constructor (void)
+{
+ orig_dlopen = dlsym (RTLD_NEXT, "dlopen");
+}
--
2.24.1