In the next patch, we want to get rid of the requirement for the user
to supply LD_LIBRARY_PATH pointing to vddk libs, if we can derive it
ourselves from libdir. However, VDDK itself requires LD_LIBRARY_PATH
to be set (because it tries to load libraries that in turn depend on a
bare library name, which no manner of dlopen() hacking can work
around, and implementing la_objsearch() is no better for requiring
LD_AUDIT to be set). And since ld.so caches the value of
LD_LIBRARY_PATH at startup (for security reasons), the ONLY way to set
it for loading vddk, while clearing it again before --run spawns a
child process, is to re-exec nbdkit with slight alterations.
Since VDDK only runs on Linux, we can assume the presence of
/proc/self/{exe,cmdline}, and parse off everything we need (provided
nbdkit didn't muck with argv[], which we just fixed) to recursively
exec with a munged environment that still has enough breadcrumbs to
undo the munging.
This patch does not quite set LD_LIBRARY_PATH correctly in all cases
(since vddk expects libdir= to point to vmware-vix-disklib-distrib/,
but LD_LIBRARY_PATH to vmware-vix-disklib-distrib/lib64), but that
will be cleaned up in the next patch; in the meantime, the re-exec in
this patch fires but has no ill effects.
While making the change, clean up static variables that were explicitly
zero-initialized to instead rely on .bss initial value.
Signed-off-by: Eric Blake <eblake(a)redhat.com>
---
plugins/vddk/vddk.c | 172 +++++++++++++++++++++++++++++++++++++-------
1 file changed, 147 insertions(+), 25 deletions(-)
diff --git a/plugins/vddk/vddk.c b/plugins/vddk/vddk.c
index 0abec68e..5ae41547 100644
--- a/plugins/vddk/vddk.c
+++ b/plugins/vddk/vddk.c
@@ -1,5 +1,5 @@
/* nbdkit
- * Copyright (C) 2013-2019 Red Hat Inc.
+ * Copyright (C) 2013-2020 Red Hat Inc.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
@@ -40,6 +40,7 @@
#include <string.h>
#include <unistd.h>
#include <dlfcn.h>
+#include <fcntl.h>
#define NBDKIT_API_VERSION 2
@@ -71,25 +72,26 @@ int vddk_debug_extents;
#define VDDK_MAJOR 5
#define VDDK_MINOR 1
-static void *dl = NULL; /* dlopen handle */
-static int init_called = 0; /* was InitEx called */
-
-static char *config = NULL; /* config */
-static const char *cookie = NULL; /* cookie */
-static const char *filename = NULL; /* file */
-static char *libdir = NULL; /* libdir */
-static uint16_t nfc_host_port = 0; /* nfchostport */
-static char *password = NULL; /* password */
-static uint16_t port = 0; /* port */
-static const char *server_name = NULL; /* server */
-static bool single_link = false; /* single-link */
-static const char *snapshot_moref = NULL; /* snapshot */
-static const char *thumb_print = NULL; /* thumbprint */
-static const char *transport_modes = NULL; /* transports */
-static bool unbuffered = false; /* unbuffered */
-static const char *username = NULL; /* user */
-static const char *vmx_spec = NULL; /* vm */
-static bool is_remote = false;
+static void *dl; /* dlopen handle */
+static bool init_called; /* was InitEx called */
+static char *reexeced; /* orig LD_LIBRARY_PATH on reexec */
+
+static char *config; /* config */
+static const char *cookie; /* cookie */
+static const char *filename; /* file */
+static char *libdir; /* libdir */
+static uint16_t nfc_host_port; /* nfchostport */
+static char *password; /* password */
+static uint16_t port; /* port */
+static const char *server_name; /* server */
+static bool single_link; /* single-link */
+static const char *snapshot_moref; /* snapshot */
+static const char *thumb_print; /* thumbprint */
+static const char *transport_modes; /* transports */
+static bool unbuffered; /* unbuffered */
+static const char *username; /* user */
+static const char *vmx_spec; /* vm */
+static bool is_remote;
#define VDDK_ERROR(err, fs, ...) \
do { \
@@ -162,6 +164,8 @@ vddk_unload (void)
static int
vddk_config (const char *key, const char *value)
{
+ int r;
+
if (strcmp (key, "config") == 0) {
/* See FILENAMES AND PATHS in nbdkit-plugin(3). */
free (config);
@@ -199,12 +203,15 @@ vddk_config (const char *key, const char *value)
if (nbdkit_parse_uint16_t ("port", value, &port) == -1)
return -1;
}
+ else if (strcmp (key, "reexeced_") == 0) {
+ /* Special name because it is only for internal use. */
+ reexeced = (char *)value;
+ }
else if (strcmp (key, "server") == 0) {
server_name = value;
}
else if (strcmp (key, "single-link") == 0) {
- int r = nbdkit_parse_bool (value);
-
+ r = nbdkit_parse_bool (value);
if (r == -1)
return -1;
single_link = r;
@@ -219,8 +226,7 @@ vddk_config (const char *key, const char *value)
transport_modes = value;
}
else if (strcmp (key, "unbuffered") == 0) {
- int r = nbdkit_parse_bool (value);
-
+ r = nbdkit_parse_bool (value);
if (r == -1)
return -1;
unbuffered = r;
@@ -242,6 +248,92 @@ vddk_config (const char *key, const char *value)
return 0;
}
+/* Perform a re-exec that temporarily modifies LD_LIBRARY_PATH. Does
+ * not return on success; on failure, problems have been logged, but
+ * the caller prefers to proceed as if this had not been attempted.
+ * Thus, no return value is needed.
+ */
+static void
+perform_reexec (const char *prepend)
+{
+ CLEANUP_FREE char *library = NULL;
+ const char *env = getenv ("LD_LIBRARY_PATH");
+ int argc = 0;
+ CLEANUP_FREE char **argv = NULL;
+ int fd;
+ size_t len = 0, buflen = 512;
+ CLEANUP_FREE char *buf = NULL;
+
+ /* In order to re-exec, we need our original command line. The
+ * Linux kernel does not make it easy to know in advance how large
+ * it was, so we just slurp in the whole file, doubling our reads
+ * until we get a short read. This assumes nbdkit did not alter its
+ * original argv[].
+ */
+ fd = open ("/proc/self/cmdline", O_RDONLY);
+ if (fd == -1) {
+ nbdkit_debug ("failure to parse original argv: %m");
+ return;
+ }
+
+ do {
+ char *p = realloc (buf, buflen * 2);
+ ssize_t r;
+
+ if (!p) {
+ nbdkit_debug ("failure to parse original argv: %m");
+ return;
+ }
+ buf = p;
+ buflen *= 2;
+ r = read (fd, buf + len, buflen - len);
+ if (r == -1) {
+ nbdkit_debug ("failure to parse original argv: %m");
+ return;
+ }
+ len += r;
+ } while (len == buflen);
+ nbdkit_debug ("original command line occupies %zu bytes", len);
+
+ /* Split cmdline into argv, then append one more arg. */
+ buflen = len;
+ len = 0;
+ while (len < buflen) {
+ char **tmp = realloc (argv, sizeof *argv * (argc + 2));
+
+ if (!tmp) {
+ nbdkit_debug ("failure to parse original argv: %m");
+ return;
+ }
+ argv = tmp;
+ argv[argc++] = buf + len;
+ len += strlen (buf + len) + 1;
+ }
+ if (!env)
+ env = "";
+ nbdkit_debug ("original argc == %d, adding reexeced_=%s", argc, env);
+ if (asprintf (&reexeced, "reexeced_=%s", env) == -1) {
+ nbdkit_debug ("failure to re-exec: %m");
+ return;
+ }
+ argv[argc++] = reexeced;
+ argv[argc] = NULL;
+
+ if (env[0])
+ asprintf (&library, "%s:%s", prepend, env);
+ else
+ library = strdup (prepend);
+ if (!library || setenv ("LD_LIBRARY_PATH", library, 1) == -1) {
+ nbdkit_debug ("failure to set LD_LIBRARY_PATH: %m");
+ return;
+ }
+
+ nbdkit_debug ("re-executing with updated LD_LIBRARY_PATH=%s", library);
+ fflush (NULL);
+ execvp ("/proc/self/exe", argv);
+ nbdkit_debug ("failure to execvp: %m");
+}
+
/* Load the VDDK library. */
static void
load_library (void)
@@ -266,6 +358,8 @@ load_library (void)
}
}
if (dl == NULL) {
+ if (!reexeced && libdir)
+ perform_reexec (libdir); /* TODO: Use correct dir */
nbdkit_error ("%s\n\n"
"If '%s' is located on a non-standard path you may need
to\n"
"set $LD_LIBRARY_PATH or edit /etc/ld.so.conf.\n\n"
@@ -301,6 +395,34 @@ vddk_config_complete (void)
return -1;
}
+ /* If load_library caused a re-execution with an expanded
+ * LD_LIBRARY_PATH, restore it back to its original contents, passed
+ * as the value of "reexeced_". dlopen uses the value of
+ * LD_LIBRARY_PATH cached at program startup; our change is for the
+ * sake of child processes (such as --run) to see the same
+ * environment as the original nbdkit saw before re-exec.
+ */
+ if (reexeced) {
+ char *env = getenv ("LD_LIBRARY_PATH");
+
+ nbdkit_debug ("cleaning up after re-exec");
+ if (!env || strstr (env, reexeced) != 0 ||
+ (libdir && strncmp (env, libdir, strlen (libdir)) != 0)) {
+ nbdkit_error ("'reexeced_' set with garbled environment");
+ return -1;
+ }
+ if (reexeced[0]) {
+ if (setenv ("LD_LIBRARY_PATH", reexeced, 1) == -1) {
+ nbdkit_error ("setenv: %m");
+ return -1;
+ }
+ }
+ else if (unsetenv ("LD_LIBRARY_PATH") == -1) {
+ nbdkit_error ("unsetenv: %m");
+ return -1;
+ }
+ }
+
/* For remote connections, check all the parameters have been
* passed. Note that VDDK will segfault if parameters that it
* expects are NULL (and there's no real way to tell what parameters
@@ -347,7 +469,7 @@ vddk_config_complete (void)
VDDK_ERROR (err, "VixDiskLib_InitEx");
exit (EXIT_FAILURE);
}
- init_called = 1;
+ init_called = true;
return 0;
}
--
2.24.1