[Libguestfs] [PATCH nbdkit 5/9] Create new library libnbdkit.so, so plugins can be compiled with -no-undefined.

Richard W.M. Jones rjones at redhat.com
Thu Mar 26 18:25:30 UTC 2020


Traditionally nbdkit plugins have been built as shared libraries with
undefined symbols:

  $ ldd -r /usr/lib64/nbdkit/plugins/nbdkit-file-plugin.so
	linux-vdso.so.1 (0x00007ffc361a5000)
	libgcc_s.so.1 => /lib64/libgcc_s.so.1 (0x00007fad9e07d000)
	libc.so.6 => /lib64/libc.so.6 (0x00007fad9deb3000)
	/lib64/ld-linux-x86-64.so.2 (0x00007fad9e0c8000)
  undefined symbol: nbdkit_extents_free	(/usr/lib64/nbdkit/plugins/nbdkit-file-plugin.so)
  undefined symbol: nbdkit_add_extent	(/usr/lib64/nbdkit/plugins/nbdkit-file-plugin.so)
  undefined symbol: nbdkit_realpath	(/usr/lib64/nbdkit/plugins/nbdkit-file-plugin.so)
  undefined symbol: nbdkit_error	(/usr/lib64/nbdkit/plugins/nbdkit-file-plugin.so)
  undefined symbol: nbdkit_debug	(/usr/lib64/nbdkit/plugins/nbdkit-file-plugin.so)

These symbols are resolved when the plugin is loaded by the server
because the symbols are provided by the server binary itself.

Unfortunately on Windows you cannot create a DLL that has undefined
symbols (or at least not using MinGW).

This patch changes linking so that you can now do this instead:

  gcc -shared plugin.c -o plugin.so -lnbdkit

Now the nbdkit_* symbols are provided by a new library called
libnbdkit.so.  nbdkit itself also links to it.  The result is that the
plugin does not have any undefined symbols.

Note this change is *opt-in*.  On POSIX platforms it is possible to
compile the plugins in the old way or the new way.  (For Windows the
new way must be used, but Windows is a new platform so this is not an
API break.)

nbdkit and libnbdkit.so must be shipped together at all times.  There
is a "secret" private ABI between them which is not stable.  (This
does not affect plugin ABI stability or guarantees.)

Thanks: Eric Blake, Frank Gu

About the implementation:

When nbdkit starts up it must now initialize the library by calling
libnbdkit_private_init.  It checks that the server and library have
identical versions, and passes pointers to certain internal server
functions as described below.

Some of the functions are self-contained, eg the parsing functions.
These can be moved straight to the library and do not need to call
into the server to do their work.

Some of the functions are slow path and not self-contained, eg
nbdkit_error.  These continue to be implemented by the server, but I
have renamed the server function from ‘nbdkit_*’ to ‘do_nbdkit_*’, and
the library contains ‘nbdkit_*’ wrapper which simply bounces to the
‘do_nbdkit_*’ function.

Some of the functions are fast path and not self-contained, eg
nbdkit_debug.  These are handled most awkwardly by exposing selected
server functions that they need (eg. threadlocal_get_name) to the
library.
---
 configure.ac                  |   1 +
 Makefile.am                   |   1 +
 lib/Makefile.am               | 102 ++++++++
 server/Makefile.am            |  26 +-
 server/internal.h             |  39 +--
 server/log.c                  |  20 +-
 server/main.c                 |  12 +
 server/nbdkit.syms            |  37 +--
 server/plugins.c              |   2 +-
 server/public.c               | 480 +---------------------------------
 server/quit.c                 |   2 +-
 lib/lib.h                     | 102 ++++++++
 {server => lib}/debug.c       |   6 +-
 {server => lib}/extents.c     |   4 +-
 lib/init.c                    |  83 ++++++
 lib/log.c                     |  59 +++++
 lib/parse.c                   | 341 ++++++++++++++++++++++++
 lib/password.c                | 152 +++++++++++
 lib/path.c                    |  97 +++++++
 lib/slow.c                    |  78 ++++++
 {server => lib}/test-public.c |   3 +-
 lib/vfprintf.c                |  65 +++++
 wrapper.c                     |  18 +-
 .gitignore                    |   2 +-
 lib/libnbdkit.syms            |  76 ++++++
 25 files changed, 1224 insertions(+), 584 deletions(-)

diff --git a/configure.ac b/configure.ac
index 92e0d4e3..bf720cec 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1057,6 +1057,7 @@ AC_CONFIG_FILES([Makefile
                  filters/truncate/Makefile
                  filters/xz/Makefile
                  fuzzing/Makefile
+                 lib/Makefile
                  server/Makefile
                  server/nbdkit.pc
                  tests/functions.sh
diff --git a/Makefile.am b/Makefile.am
index ec8ae05d..3e6091f7 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -75,6 +75,7 @@ SUBDIRS = \
 	common/include \
 	common/protocol \
 	common/utils \
+	lib \
 	server \
 	$(NULL)
 
diff --git a/lib/Makefile.am b/lib/Makefile.am
new file mode 100644
index 00000000..4fca7c9d
--- /dev/null
+++ b/lib/Makefile.am
@@ -0,0 +1,102 @@
+# nbdkit
+# 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
+# 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.
+
+include $(top_srcdir)/common-rules.mk
+
+EXTRA_DIST = libnbdkit.syms
+
+# Note this library always has soname “libnbdkit.so.0” because plugins
+# which may link to this library must forever have a stable soname.
+# However the library only works with the corresponding nbdkit server
+# binary compiled at the same time.  The two must be shipped together.
+
+lib_LTLIBRARIES = libnbdkit.la
+libnbdkit_la_SOURCES = \
+	debug.c \
+	extents.c \
+	init.c \
+	lib.h \
+	log.c \
+	parse.c \
+	password.c \
+	path.c \
+	slow.c \
+	vfprintf.c \
+	$(NULL)
+
+libnbdkit_la_CPPFLAGS = \
+	-I$(top_srcdir)/include \
+	-I$(top_srcdir)/common/include \
+	-I$(top_srcdir)/common/utils \
+	-DIN_NBDKIT_LIB=1 \
+	$(NULL)
+libnbdkit_la_CFLAGS = \
+	$(PTHREAD_CFLAGS) \
+	$(WARNINGS_CFLAGS) \
+	$(NULL)
+libnbdkit_la_LDFLAGS = \
+	$(PTHREAD_LIBS) \
+	$(NULL)
+libnbdkit_la_LIBADD = \
+	$(top_builddir)/common/utils/libutils.la \
+	$(NULL)
+
+if USE_LINKER_SCRIPT_FOR_SERVER
+# We have to disable the linker script for libFuzzer because Clang
+# adds loads of fuzzer and ASAN-related symbols that are required by
+# the plugins but which our linker script tries to hide.
+if !ENABLE_LIBFUZZER
+libnbdkit_la_LDFLAGS += -Wl,--version-script=$(srcdir)/libnbdkit.syms
+endif
+endif
+
+# Unit testing
+
+TESTS = test-public
+
+check_PROGRAMS = test-public
+
+test_public_SOURCES = \
+	test-public.c \
+	extents.c \
+	parse.c \
+	password.c \
+	$(NULL)
+test_public_CPPFLAGS = \
+	-I$(top_srcdir)/include \
+	-I$(top_srcdir)/common/include \
+	-I$(top_srcdir)/common/protocol \
+	-I$(top_srcdir)/common/utils \
+	$(NULL)
+test_public_CFLAGS = $(WARNINGS_CFLAGS) $(VALGRIND_CFLAGS)
+test_public_LDADD = \
+	$(top_builddir)/common/utils/libutils.la \
+	$(NULL)
diff --git a/server/Makefile.am b/server/Makefile.am
index 4c789934..8d9ba0f0 100644
--- a/server/Makefile.am
+++ b/server/Makefile.am
@@ -41,9 +41,7 @@ nbdkit_SOURCES = \
 	captive.c \
 	connections.c \
 	crypto.c \
-	debug.c \
 	debug-flags.c \
-	extents.c \
 	filters.c \
 	internal.h \
 	locks.c \
@@ -80,6 +78,7 @@ nbdkit_CPPFLAGS = \
 	-Dfilterdir=\"$(filterdir)\" \
 	-Dsbindir=\"$(sbindir)\" \
 	-Dsysconfdir=\"$(sysconfdir)\" \
+	-I$(top_srcdir)/lib \
 	-I$(top_srcdir)/include \
 	-I$(top_srcdir)/common/include \
 	-I$(top_srcdir)/common/protocol \
@@ -93,6 +92,7 @@ nbdkit_CFLAGS = \
 	$(VALGRIND_CFLAGS) \
 	$(NULL)
 nbdkit_LDADD = \
+	../lib/libnbdkit.la \
 	$(GNUTLS_LIBS) \
 	$(LIBSELINUX_LIBS) \
 	$(DL_LIBS) \
@@ -129,25 +129,3 @@ synopsis.c: $(top_srcdir)/docs/synopsis.txt
 
 pkgconfigdir = $(libdir)/pkgconfig
 pkgconfig_DATA = nbdkit.pc
-
-# Unit testing
-
-TESTS = test-public
-
-check_PROGRAMS = test-public
-
-test_public_SOURCES = \
-	test-public.c \
-	public.c \
-	extents.c \
-	$(NULL)
-test_public_CPPFLAGS = \
-	-I$(top_srcdir)/include \
-	-I$(top_srcdir)/common/include \
-	-I$(top_srcdir)/common/protocol \
-	-I$(top_srcdir)/common/utils \
-	$(NULL)
-test_public_CFLAGS = $(WARNINGS_CFLAGS) $(VALGRIND_CFLAGS)
-test_public_LDADD = \
-	$(top_builddir)/common/utils/libutils.la \
-	$(NULL)
diff --git a/server/internal.h b/server/internal.h
index b43798ff..9ef0b066 100644
--- a/server/internal.h
+++ b/server/internal.h
@@ -43,6 +43,7 @@
 #define NBDKIT_INTERNAL
 #include "nbdkit-plugin.h"
 #include "nbdkit-filter.h"
+#include "lib.h"
 #include "cleanup.h"
 #include "nbd-protocol.h"
 
@@ -79,11 +80,25 @@
 # define DO_DLCLOSE 1
 #endif
 
+/* Declare program_name. */
+#if HAVE_DECL_PROGRAM_INVOCATION_SHORT_NAME == 1
+#include <errno.h>
+#define program_name program_invocation_short_name
+#else
+#define program_name "nbdkit"
+#endif
+
 #define container_of(ptr, type, member) ({                       \
       const typeof (((type *) 0)->member) *__mptr = (ptr);       \
       (type *) ((char *) __mptr - offsetof(type, member));       \
     })
 
+#define debug(fs, ...)                                   \
+  do {                                                   \
+    if_verbose                                           \
+      nbdkit_debug ((fs), ##__VA_ARGS__);                \
+  } while (0)
+
 /* Maximum read or write request that we will handle. */
 #define MAX_REQUEST_SIZE (64 * 1024 * 1024)
 
@@ -138,6 +153,7 @@ extern int quit_fd;
 extern void set_up_quit_pipe (void);
 extern void close_quit_pipe (void);
 extern void handle_quit (int sig);
+extern void do_nbdkit_shutdown (void);
 
 /* signals.c */
 extern void set_up_signals (void);
@@ -285,20 +301,13 @@ extern void crypto_init (bool tls_set_on_cli);
 extern void crypto_free (void);
 extern int crypto_negotiate_tls (int sockin, int sockout);
 
-/* debug.c */
-#define debug(fs, ...)                                   \
-  do {                                                   \
-    if_verbose                                           \
-      nbdkit_debug ((fs), ##__VA_ARGS__);                \
-  } while (0)
-
 /* debug-flags.c */
 extern void add_debug_flag (const char *arg);
 extern void apply_debug_flags (void *dl, const char *name);
 extern void free_debug_flags (void);
 
 /* log.c */
-extern void log_verror (const char *fs, va_list args);
+extern void do_nbdkit_verror (const char *fs, va_list args);
 
 /* log-*.c */
 extern void log_stderr_verror (const char *fs, va_list args)
@@ -465,6 +474,7 @@ extern int backend_cache (struct backend *b,
 extern struct backend *plugin_register (size_t index, const char *filename,
                                         void *dl, struct nbdkit_plugin *(*plugin_init) (void))
   __attribute__((__nonnull__ (2, 3, 4)));
+extern void do_nbdkit_set_error (int err);
 
 /* filters.c */
 extern struct backend *filter_register (struct backend *next, size_t index,
@@ -507,6 +517,11 @@ extern void *threadlocal_buffer (size_t size);
 extern void threadlocal_set_conn (struct connection *conn);
 extern struct connection *threadlocal_get_conn (void);
 
+/* public.c */
+extern int do_nbdkit_nanosleep (unsigned sec, unsigned nsec);
+extern const char *do_nbdkit_export_name (void);
+extern int do_nbdkit_peer_name (struct sockaddr *addr, socklen_t *addrlen);
+
 /* Macro which sets local variable struct connection *conn from
  * thread-local storage, asserting that it is non-NULL.  If you want
  * to check if conn could be NULL (eg. outside a connection context)
@@ -516,12 +531,4 @@ extern struct connection *threadlocal_get_conn (void);
   struct connection *conn = threadlocal_get_conn ();    \
   assert (conn != NULL)
 
-/* Declare program_name. */
-#if HAVE_DECL_PROGRAM_INVOCATION_SHORT_NAME == 1
-#include <errno.h>
-#define program_name program_invocation_short_name
-#else
-#define program_name "nbdkit"
-#endif
-
 #endif /* NBDKIT_INTERNAL_H */
diff --git a/server/log.c b/server/log.c
index 37de3dd2..3588c7cc 100644
--- a/server/log.c
+++ b/server/log.c
@@ -44,7 +44,7 @@
  * Note: preserves the previous value of errno.
  */
 void
-log_verror (const char *fs, va_list args)
+do_nbdkit_verror (const char *fs, va_list args)
 {
   switch (log_to) {
   case LOG_TO_DEFAULT:
@@ -64,21 +64,3 @@ log_verror (const char *fs, va_list args)
     break;
   }
 }
-
-/* Note: preserves the previous value of errno. */
-void
-nbdkit_verror (const char *fs, va_list args)
-{
-  log_verror (fs, args);
-}
-
-/* Note: preserves the previous value of errno. */
-void
-nbdkit_error (const char *fs, ...)
-{
-  va_list args;
-
-  va_start (args, fs);
-  nbdkit_verror (fs, args);
-  va_end (args);
-}
diff --git a/server/main.c b/server/main.c
index b303146c..156d85bd 100644
--- a/server/main.c
+++ b/server/main.c
@@ -168,6 +168,18 @@ main (int argc, char *argv[])
   size_t i;
   const char *magic_config_key;
 
+  /* Initialize libnbdkit.so.  This must be done very early. */
+  libnbdkit_private_init (PACKAGE_VERSION,
+                          &verbose,
+                          do_nbdkit_export_name,
+                          do_nbdkit_nanosleep,
+                          do_nbdkit_peer_name,
+                          do_nbdkit_set_error,
+                          do_nbdkit_shutdown,
+                          do_nbdkit_verror,
+                          threadlocal_get_name,
+                          threadlocal_get_instance_num);
+
   /* Refuse to run if stdin/out/err are closed, whether or not -s is used. */
   if (fcntl (STDERR_FILENO, F_GETFL) == -1) {
     /* Nowhere we can report the error. Oh well. */
diff --git a/server/nbdkit.syms b/server/nbdkit.syms
index 111223f2..5b665f85 100644
--- a/server/nbdkit.syms
+++ b/server/nbdkit.syms
@@ -30,44 +30,11 @@
 # SUCH DAMAGE.
 
 # This linker script controls the visibility of symbols in the final
-# nbdkit binary.  We want to export some symbols to plugins, but at
-# the same time we don't want plugins to be able to call arbitrary
-# functions from nbdkit, so this script lists only the symbols we want
-# to export.
+# nbdkit binary.
 
 {
-  # The functions we want plugins and filters to call.
   global:
-    nbdkit_absolute_path;
-    nbdkit_add_extent;
-    nbdkit_debug;
-    nbdkit_error;
-    nbdkit_export_name;
-    nbdkit_extents_count;
-    nbdkit_extents_free;
-    nbdkit_extents_new;
-    nbdkit_get_extent;
-    nbdkit_nanosleep;
-    nbdkit_parse_bool;
-    nbdkit_parse_int8_t;
-    nbdkit_parse_int16_t;
-    nbdkit_parse_int32_t;
-    nbdkit_parse_int64_t;
-    nbdkit_parse_int;
-    nbdkit_parse_size;
-    nbdkit_parse_uint8_t;
-    nbdkit_parse_uint16_t;
-    nbdkit_parse_uint32_t;
-    nbdkit_parse_uint64_t;
-    nbdkit_parse_unsigned;
-    nbdkit_peer_name;
-    nbdkit_read_password;
-    nbdkit_realpath;
-    nbdkit_set_error;
-    nbdkit_shutdown;
-    nbdkit_vdebug;
-    nbdkit_verror;
-
+    # -D server.* flags must be visible to nbdkit itself.
     nbdkit_debug_*;
 
   # Everything else is hidden.
diff --git a/server/plugins.c b/server/plugins.c
index fa572a6a..444ba63e 100644
--- a/server/plugins.c
+++ b/server/plugins.c
@@ -460,7 +460,7 @@ plugin_can_cache (struct backend *b, void *handle)
  * where !errno_is_preserved.
  */
 void
-nbdkit_set_error (int err)
+do_nbdkit_set_error (int err)
 {
   threadlocal_set_error (err);
 }
diff --git a/server/public.c b/server/public.c
index 3fd11253..56302bb4 100644
--- a/server/public.c
+++ b/server/public.c
@@ -30,491 +30,21 @@
  * SUCH DAMAGE.
  */
 
-/* This file contains the public utility APIs to be exported by nbdkit
- * for use by filters and plugins, declared in nbdkit-common.h.
- */
-
 #include <config.h>
 
-#include <fcntl.h>
 #include <stdio.h>
 #include <stdlib.h>
-#include <stdbool.h>
-#include <stdint.h>
-#include <inttypes.h>
-#include <string.h>
 #include <unistd.h>
+#include <fcntl.h>
 #include <limits.h>
-#include <ctype.h>
-#include <termios.h>
-#include <errno.h>
-#include <poll.h>
+#include <time.h>
 #include <signal.h>
 #include <sys/socket.h>
 
-#include "get-current-dir-name.h"
-
 #include "internal.h"
 
-char *
-nbdkit_absolute_path (const char *path)
-{
-  CLEANUP_FREE char *pwd = NULL;
-  char *ret;
-
-  if (path == NULL || *path == '\0') {
-    nbdkit_error ("cannot convert null or empty path to an absolute path");
-    return NULL;
-  }
-
-  if (*path == '/') {
-    ret = strdup (path);
-    if (!ret) {
-      nbdkit_error ("strdup: %m");
-      return NULL;
-    }
-    return ret;
-  }
-
-  pwd = get_current_dir_name ();
-  if (pwd == NULL) {
-    nbdkit_error ("get_current_dir_name: %m");
-    return NULL;
-  }
-
-  if (asprintf (&ret, "%s/%s", pwd, path) == -1) {
-    nbdkit_error ("asprintf: %m");
-    return NULL;
-  }
-
-  return ret;
-}
-
-char *
-nbdkit_realpath (const char *path)
-{
-  char *ret;
-
-  if (path == NULL || *path == '\0') {
-    nbdkit_error ("cannot resolve a null or empty path");
-    return NULL;
-  }
-
-  ret = realpath (path, NULL);
-  if (ret == NULL) {
-    nbdkit_error ("realpath: %s: %m", path);
-    return NULL;
-  }
-
-  return ret;
-}
-
-/* Common code for parsing integers. */
-#define PARSE_COMMON_TAIL                                               \
-  if (errno != 0) {                                                     \
-    nbdkit_error ("%s: could not parse number: \"%s\": %m",             \
-                  what, str);                                           \
-    return -1;                                                          \
-  }                                                                     \
-  if (end == str) {                                                     \
-    nbdkit_error ("%s: empty string where we expected a number",        \
-                  what);                                                \
-    return -1;                                                          \
-  }                                                                     \
-  if (*end) {                                                           \
-    nbdkit_error ("%s: could not parse number: \"%s\": trailing garbage", \
-                  what, str);                                           \
-    return -1;                                                          \
-  }                                                                     \
-                                                                        \
-  if (rp)                                                               \
-    *rp = r;                                                            \
-  return 0
-
-/* Functions for parsing signed integers. */
 int
-nbdkit_parse_int (const char *what, const char *str, int *rp)
-{
-  long r;
-  char *end;
-
-  errno = 0;
-  r = strtol (str, &end, 0);
-#if INT_MAX != LONG_MAX
-  if (r < INT_MIN || r > INT_MAX)
-    errno = ERANGE;
-#endif
-  PARSE_COMMON_TAIL;
-}
-
-int
-nbdkit_parse_int8_t (const char *what, const char *str, int8_t *rp)
-{
-  long r;
-  char *end;
-
-  errno = 0;
-  r = strtol (str, &end, 0);
-  if (r < INT8_MIN || r > INT8_MAX)
-    errno = ERANGE;
-  PARSE_COMMON_TAIL;
-}
-
-int
-nbdkit_parse_int16_t (const char *what, const char *str, int16_t *rp)
-{
-  long r;
-  char *end;
-
-  errno = 0;
-  r = strtol (str, &end, 0);
-  if (r < INT16_MIN || r > INT16_MAX)
-    errno = ERANGE;
-  PARSE_COMMON_TAIL;
-}
-
-int
-nbdkit_parse_int32_t (const char *what, const char *str, int32_t *rp)
-{
-  long r;
-  char *end;
-
-  errno = 0;
-  r = strtol (str, &end, 0);
-#if INT32_MAX != LONG_MAX
-  if (r < INT32_MIN || r > INT32_MAX)
-    errno = ERANGE;
-#endif
-  PARSE_COMMON_TAIL;
-}
-
-int
-nbdkit_parse_int64_t (const char *what, const char *str, int64_t *rp)
-{
-  long long r;
-  char *end;
-
-  errno = 0;
-  r = strtoll (str, &end, 0);
-#if INT64_MAX != LONGLONG_MAX
-  if (r < INT64_MIN || r > INT64_MAX)
-    errno = ERANGE;
-#endif
-  PARSE_COMMON_TAIL;
-}
-
-/* Functions for parsing unsigned integers. */
-
-/* strtou* functions have surprising behaviour if the first character
- * (after whitespace) is '-', so reject this early.
- */
-#define PARSE_ERROR_IF_NEGATIVE                                         \
-  do {                                                                  \
-    while (isspace (*str))                                              \
-      str++;                                                            \
-    if (*str == '-') {                                                  \
-      nbdkit_error ("%s: negative numbers are not allowed", what);      \
-      return -1;                                                        \
-    }                                                                   \
-  } while (0)
-
-int
-nbdkit_parse_unsigned (const char *what, const char *str, unsigned *rp)
-{
-  unsigned long r;
-  char *end;
-
-  PARSE_ERROR_IF_NEGATIVE;
-  errno = 0;
-  r = strtoul (str, &end, 0);
-#if UINT_MAX != ULONG_MAX
-  if (r > UINT_MAX)
-    errno = ERANGE;
-#endif
-  PARSE_COMMON_TAIL;
-}
-
-int
-nbdkit_parse_uint8_t (const char *what, const char *str, uint8_t *rp)
-{
-  unsigned long r;
-  char *end;
-
-  PARSE_ERROR_IF_NEGATIVE;
-  errno = 0;
-  r = strtoul (str, &end, 0);
-  if (r > UINT8_MAX)
-    errno = ERANGE;
-  PARSE_COMMON_TAIL;
-}
-
-int
-nbdkit_parse_uint16_t (const char *what, const char *str, uint16_t *rp)
-{
-  unsigned long r;
-  char *end;
-
-  PARSE_ERROR_IF_NEGATIVE;
-  errno = 0;
-  r = strtoul (str, &end, 0);
-  if (r > UINT16_MAX)
-    errno = ERANGE;
-  PARSE_COMMON_TAIL;
-}
-
-int
-nbdkit_parse_uint32_t (const char *what, const char *str, uint32_t *rp)
-{
-  unsigned long r;
-  char *end;
-
-  PARSE_ERROR_IF_NEGATIVE;
-  errno = 0;
-  r = strtoul (str, &end, 0);
-#if UINT32_MAX != ULONG_MAX
-  if (r > UINT32_MAX)
-    errno = ERANGE;
-#endif
-  PARSE_COMMON_TAIL;
-}
-
-int
-nbdkit_parse_uint64_t (const char *what, const char *str, uint64_t *rp)
-{
-  unsigned long long r;
-  char *end;
-
-  PARSE_ERROR_IF_NEGATIVE;
-  errno = 0;
-  r = strtoull (str, &end, 0);
-#if UINT64_MAX != ULONGLONG_MAX
-  if (r > UINT64_MAX)
-    errno = ERANGE;
-#endif
-  PARSE_COMMON_TAIL;
-}
-
-/* Parse a string as a size with possible scaling suffix, or return -1
- * after reporting the error.
- */
-int64_t
-nbdkit_parse_size (const char *str)
-{
-  int64_t size;
-  char *end;
-  uint64_t scale = 1;
-
-  /* Disk sizes cannot usefully exceed off_t (which is signed) and
-   * cannot be negative.  */
-  /* XXX Should we also parse things like '1.5M'? */
-  /* XXX Should we allow hex? If so, hex cannot use scaling suffixes,
-   * because some of them are valid hex digits */
-  errno = 0;
-  size = strtoimax (str, &end, 10);
-  if (str == end) {
-    nbdkit_error ("could not parse size string (%s)", str);
-    return -1;
-  }
-  if (size < 0) {
-    nbdkit_error ("size cannot be negative (%s)", str);
-    return -1;
-  }
-  if (errno) {
-    nbdkit_error ("size (%s) exceeds maximum value", str);
-    return -1;
-  }
-
-  switch (*end) {
-    /* No suffix */
-  case '\0':
-    end--; /* Safe, since we already filtered out empty string */
-    break;
-
-    /* Powers of 1024 */
-  case 'e': case 'E':
-    scale *= 1024;
-    /* fallthru */
-  case 'p': case 'P':
-    scale *= 1024;
-    /* fallthru */
-  case 't': case 'T':
-    scale *= 1024;
-    /* fallthru */
-  case 'g': case 'G':
-    scale *= 1024;
-    /* fallthru */
-  case 'm': case 'M':
-    scale *= 1024;
-    /* fallthru */
-  case 'k': case 'K':
-    scale *= 1024;
-    /* fallthru */
-  case 'b': case 'B':
-    break;
-
-    /* "sectors", ie. units of 512 bytes, even if that's not the real
-     * sector size */
-  case 's': case 'S':
-    scale = 512;
-    break;
-
-  default:
-    nbdkit_error ("could not parse size: unknown suffix '%s'", end);
-    return -1;
-  }
-
-  /* XXX Maybe we should support 'MiB' as a synonym for 'M'; and 'MB'
-   * for powers of 1000, for similarity to GNU tools. But for now,
-   * anything beyond 'M' is dropped.  */
-  if (end[1]) {
-    nbdkit_error ("could not parse size: unknown suffix '%s'", end);
-    return -1;
-  }
-
-  if (INT64_MAX / scale < size) {
-    nbdkit_error ("overflow computing size (%s)", str);
-    return -1;
-  }
-
-  return size * scale;
-}
-
-/* Parse a string as a boolean, or return -1 after reporting the error.
- */
-int
-nbdkit_parse_bool (const char *str)
-{
-  if (!strcmp (str, "1") ||
-      !strcasecmp (str, "true") ||
-      !strcasecmp (str, "t") ||
-      !strcasecmp (str, "yes") ||
-      !strcasecmp (str, "y") ||
-      !strcasecmp (str, "on"))
-    return 1;
-
-  if (!strcmp (str, "0") ||
-      !strcasecmp (str, "false") ||
-      !strcasecmp (str, "f") ||
-      !strcasecmp (str, "no") ||
-      !strcasecmp (str, "n") ||
-      !strcasecmp (str, "off"))
-    return 0;
-
-  nbdkit_error ("could not decipher boolean (%s)", str);
-  return -1;
-}
-
-/* Read a password from configuration value. */
-static int read_password_from_fd (const char *what, int fd, char **password);
-
-int
-nbdkit_read_password (const char *value, char **password)
-{
-  int tty, err;
-  struct termios orig, temp;
-  ssize_t r;
-  size_t n;
-
-  *password = NULL;
-
-  /* Read from stdin. */
-  if (strcmp (value, "-") == 0) {
-    printf ("password: ");
-
-    /* Set no echo. */
-    tty = isatty (0);
-    if (tty) {
-      tcgetattr (0, &orig);
-      temp = orig;
-      temp.c_lflag &= ~ECHO;
-      tcsetattr (0, TCSAFLUSH, &temp);
-    }
-
-    r = getline (password, &n, stdin);
-    err = errno;
-
-    /* Restore echo. */
-    if (tty)
-      tcsetattr (0, TCSAFLUSH, &orig);
-
-    /* Complete the printf above. */
-    printf ("\n");
-
-    if (r == -1) {
-      errno = err;
-      nbdkit_error ("could not read password from stdin: %m");
-      return -1;
-    }
-    if (*password && r > 0 && (*password)[r-1] == '\n')
-      (*password)[r-1] = '\0';
-  }
-
-  /* Read from numbered file descriptor. */
-  else if (value[0] == '-') {
-    int fd;
-
-    if (nbdkit_parse_int ("password file descriptor", &value[1], &fd) == -1)
-      return -1;
-    if (read_password_from_fd (&value[1], fd, password) == -1)
-      return -1;
-  }
-
-  /* Read password from a file. */
-  else if (value[0] == '+') {
-    int fd;
-
-    fd = open (&value[1], O_CLOEXEC | O_RDONLY);
-    if (fd == -1) {
-      nbdkit_error ("open %s: %m", &value[1]);
-      return -1;
-    }
-    if (read_password_from_fd (&value[1], fd, password) == -1)
-      return -1;
-  }
-
-  /* Parameter is the password. */
-  else {
-    *password = strdup (value);
-    if (*password == NULL) {
-      nbdkit_error ("strdup: %m");
-      return -1;
-    }
-  }
-
-  return 0;
-}
-
-static int
-read_password_from_fd (const char *what, int fd, char **password)
-{
-  FILE *fp;
-  size_t n;
-  ssize_t r;
-  int err;
-
-  fp = fdopen (fd, "r");
-  if (fp == NULL) {
-    nbdkit_error ("fdopen %s: %m", what);
-    close (fd);
-    return -1;
-  }
-  r = getline (password, &n, fp);
-  err = errno;
-  fclose (fp);
-  if (r == -1) {
-    errno = err;
-    nbdkit_error ("could not read password from %s: %m", what);
-    return -1;
-  }
-
-  if (*password && r > 0 && (*password)[r-1] == '\n')
-    (*password)[r-1] = '\0';
-
-  return 0;
-}
-
-int
-nbdkit_nanosleep (unsigned sec, unsigned nsec)
+do_nbdkit_nanosleep (unsigned sec, unsigned nsec)
 {
   struct timespec ts;
 
@@ -593,7 +123,7 @@ nbdkit_nanosleep (unsigned sec, unsigned nsec)
 }
 
 const char *
-nbdkit_export_name (void)
+do_nbdkit_export_name (void)
 {
   struct connection *conn = threadlocal_get_conn ();
 
@@ -606,7 +136,7 @@ nbdkit_export_name (void)
 }
 
 int
-nbdkit_peer_name (struct sockaddr *addr, socklen_t *addrlen)
+do_nbdkit_peer_name (struct sockaddr *addr, socklen_t *addrlen)
 {
   struct connection *conn = threadlocal_get_conn ();
   int s;
diff --git a/server/quit.c b/server/quit.c
index 13fef437..9b96d33a 100644
--- a/server/quit.c
+++ b/server/quit.c
@@ -106,7 +106,7 @@ handle_quit (int sig)
 }
 
 void
-nbdkit_shutdown (void)
+do_nbdkit_shutdown (void)
 {
   set_quit ();
 }
diff --git a/lib/lib.h b/lib/lib.h
new file mode 100644
index 00000000..317602ef
--- /dev/null
+++ b/lib/lib.h
@@ -0,0 +1,102 @@
+/* nbdkit
+ * 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
+ * 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.
+ */
+
+#ifndef NBDKIT_LIB_H
+#define NBDKIT_LIB_H
+
+#include <stdarg.h>
+#include <stdbool.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+
+typedef const char *(*do_nbdkit_export_name_t) (void);
+typedef int (*do_nbdkit_nanosleep_t) (unsigned sec, unsigned nsec);
+typedef int (*do_nbdkit_peer_name_t) (struct sockaddr *addr, socklen_t *addrlen);
+typedef void (*do_nbdkit_set_error_t) (int err);
+typedef void (*do_nbdkit_shutdown_t) (void);
+typedef void (*do_nbdkit_verror_t) (const char *fs, va_list args);
+typedef const char *(*threadlocal_get_name_t) (void);
+typedef size_t (*threadlocal_get_instance_num_t) (void);
+
+#ifdef IN_NBDKIT_LIB
+
+/* After nbdkit_private_init is called, these global function pointers
+ * are set up to point back to the corresponding functions in the
+ * server.
+ */
+extern do_nbdkit_export_name_t do_nbdkit_export_name;
+extern do_nbdkit_nanosleep_t do_nbdkit_nanosleep;
+extern do_nbdkit_peer_name_t do_nbdkit_peer_name;
+extern do_nbdkit_set_error_t do_nbdkit_set_error;
+extern do_nbdkit_shutdown_t do_nbdkit_shutdown;
+extern do_nbdkit_verror_t do_nbdkit_verror;
+extern threadlocal_get_name_t threadlocal_get_name;
+extern threadlocal_get_instance_num_t threadlocal_get_instance_num;
+
+/* And a pointer to the verbose flag. */
+extern bool *verbose;
+
+/* Replacement vfprintf.  Awkwardly this is duplicated in server/ too. */
+#if !HAVE_VFPRINTF_PERCENT_M
+#include <stdio.h>
+#define vfprintf replace_vfprintf
+extern int replace_vfprintf (FILE *f, const char *fmt, va_list args)
+  __attribute__((__format__ (printf, 2, 0)));
+#endif
+
+/* Declare program_name. */
+#if HAVE_DECL_PROGRAM_INVOCATION_SHORT_NAME == 1
+#include <errno.h>
+#define program_name program_invocation_short_name
+#else
+#define program_name "nbdkit"
+#endif
+
+#endif /* IN_NBDKIT_LIB */
+
+/* Defines the private function which is used by the server to
+ * initialize libnbdkit.so at runtime.  This ABI may change at any
+ * time, which is why nbdkit and the corresponding libnbdkit.so must
+ * always be shipped together.
+ */
+extern void libnbdkit_private_init (const char *expected_version,
+                                    bool *verbose,
+                                    do_nbdkit_export_name_t do_nbdkit_export_name,
+                                    do_nbdkit_nanosleep_t do_nbdkit_nanosleep,
+                                    do_nbdkit_peer_name_t do_nbdkit_peer_name,
+                                    do_nbdkit_set_error_t do_nbdkit_set_error,
+                                    do_nbdkit_shutdown_t do_nbdkit_shutdown,
+                                    do_nbdkit_verror_t do_nbdkit_verror,
+                                    threadlocal_get_name_t threadlocal_get_name,
+                                    threadlocal_get_instance_num_t threadlocal_get_instance_num);
+
+#endif /* NBDKIT_LIB_H */
diff --git a/server/debug.c b/lib/debug.c
similarity index 98%
rename from server/debug.c
rename to lib/debug.c
index cf511945..261bd712 100644
--- a/server/debug.c
+++ b/lib/debug.c
@@ -38,7 +38,7 @@
 #include <string.h>
 #include <errno.h>
 
-#include "internal.h"
+#include "lib.h"
 
 /* Called with flockfile (stderr) taken. */
 static void
@@ -65,7 +65,7 @@ nbdkit_vdebug (const char *fs, va_list args)
 {
   int err = errno;
 
-  if (!verbose)
+  if (!*verbose)
     return;
   flockfile (stderr);
   prologue ();
@@ -85,7 +85,7 @@ nbdkit_debug (const char *fs, ...)
   va_list args;
   int err = errno;
 
-  if (!verbose)
+  if (!*verbose)
     return;
 
   flockfile (stderr);
diff --git a/server/extents.c b/lib/extents.c
similarity index 99%
rename from server/extents.c
rename to lib/extents.c
index 2d609652..8e3c8632 100644
--- a/server/extents.c
+++ b/lib/extents.c
@@ -41,10 +41,10 @@
 #include <errno.h>
 #include <assert.h>
 
+#include "nbdkit-filter.h"
+
 #include "minmax.h"
 
-#include "internal.h"
-
 /* Cap nr_extents to avoid sending over-large replies to the client,
  * and to avoid a plugin with frequent alternations consuming too much
  * memory.
diff --git a/lib/init.c b/lib/init.c
new file mode 100644
index 00000000..ee70607e
--- /dev/null
+++ b/lib/init.c
@@ -0,0 +1,83 @@
+/* nbdkit
+ * 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
+ * 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.
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <string.h>
+
+#include "lib.h"
+
+bool *verbose;
+
+do_nbdkit_export_name_t do_nbdkit_export_name;
+do_nbdkit_nanosleep_t do_nbdkit_nanosleep;
+do_nbdkit_peer_name_t do_nbdkit_peer_name;
+do_nbdkit_set_error_t do_nbdkit_set_error;
+do_nbdkit_shutdown_t do_nbdkit_shutdown;
+do_nbdkit_verror_t do_nbdkit_verror;
+threadlocal_get_name_t threadlocal_get_name;
+threadlocal_get_instance_num_t threadlocal_get_instance_num;
+
+void
+libnbdkit_private_init (const char *expected_version,
+                        bool *_verbose,
+                        do_nbdkit_export_name_t _do_nbdkit_export_name,
+                        do_nbdkit_nanosleep_t _do_nbdkit_nanosleep,
+                        do_nbdkit_peer_name_t _do_nbdkit_peer_name,
+                        do_nbdkit_set_error_t _do_nbdkit_set_error,
+                        do_nbdkit_shutdown_t _do_nbdkit_shutdown,
+                        do_nbdkit_verror_t _do_nbdkit_verror,
+                        threadlocal_get_name_t _threadlocal_get_name,
+                        threadlocal_get_instance_num_t _threadlocal_get_instance_num)
+{
+  if (strcmp (expected_version, PACKAGE_VERSION) != 0) {
+    fprintf (stderr,
+             "packaging error: "
+             "nbdkit and libnbdkit.so versions do not match\n");
+    abort ();
+  }
+
+  verbose = _verbose;
+
+  do_nbdkit_export_name = _do_nbdkit_export_name;
+  do_nbdkit_nanosleep = _do_nbdkit_nanosleep;
+  do_nbdkit_peer_name = _do_nbdkit_peer_name;
+  do_nbdkit_set_error = _do_nbdkit_set_error;
+  do_nbdkit_shutdown = _do_nbdkit_shutdown;
+  do_nbdkit_verror = _do_nbdkit_verror;
+
+  threadlocal_get_name = _threadlocal_get_name;
+  threadlocal_get_instance_num = _threadlocal_get_instance_num;
+}
diff --git a/lib/log.c b/lib/log.c
new file mode 100644
index 00000000..f3955c0b
--- /dev/null
+++ b/lib/log.c
@@ -0,0 +1,59 @@
+/* nbdkit
+ * 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
+ * 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.
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+#include <errno.h>
+
+#include "lib.h"
+
+/* Note: preserves the previous value of errno. */
+void
+nbdkit_verror (const char *fs, va_list args)
+{
+  do_nbdkit_verror (fs, args);
+}
+
+/* Note: preserves the previous value of errno. */
+void
+nbdkit_error (const char *fs, ...)
+{
+  va_list args;
+
+  va_start (args, fs);
+  nbdkit_verror (fs, args);
+  va_end (args);
+}
diff --git a/lib/parse.c b/lib/parse.c
new file mode 100644
index 00000000..cd3c88ac
--- /dev/null
+++ b/lib/parse.c
@@ -0,0 +1,341 @@
+/* nbdkit
+ * Copyright (C) 2013-2019 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.
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <inttypes.h>
+#include <limits.h>
+#include <string.h>
+#include <ctype.h>
+
+#include "nbdkit-plugin.h"
+
+#include "cleanup.h"
+
+/* Common code for parsing integers. */
+#define PARSE_COMMON_TAIL                                               \
+  if (errno != 0) {                                                     \
+    nbdkit_error ("%s: could not parse number: \"%s\": %m",             \
+                  what, str);                                           \
+    return -1;                                                          \
+  }                                                                     \
+  if (end == str) {                                                     \
+    nbdkit_error ("%s: empty string where we expected a number",        \
+                  what);                                                \
+    return -1;                                                          \
+  }                                                                     \
+  if (*end) {                                                           \
+    nbdkit_error ("%s: could not parse number: \"%s\": trailing garbage", \
+                  what, str);                                           \
+    return -1;                                                          \
+  }                                                                     \
+                                                                        \
+  if (rp)                                                               \
+    *rp = r;                                                            \
+  return 0
+
+/* Functions for parsing signed integers. */
+int
+nbdkit_parse_int (const char *what, const char *str, int *rp)
+{
+  long r;
+  char *end;
+
+  errno = 0;
+  r = strtol (str, &end, 0);
+#if INT_MAX != LONG_MAX
+  if (r < INT_MIN || r > INT_MAX)
+    errno = ERANGE;
+#endif
+  PARSE_COMMON_TAIL;
+}
+
+int
+nbdkit_parse_int8_t (const char *what, const char *str, int8_t *rp)
+{
+  long r;
+  char *end;
+
+  errno = 0;
+  r = strtol (str, &end, 0);
+  if (r < INT8_MIN || r > INT8_MAX)
+    errno = ERANGE;
+  PARSE_COMMON_TAIL;
+}
+
+int
+nbdkit_parse_int16_t (const char *what, const char *str, int16_t *rp)
+{
+  long r;
+  char *end;
+
+  errno = 0;
+  r = strtol (str, &end, 0);
+  if (r < INT16_MIN || r > INT16_MAX)
+    errno = ERANGE;
+  PARSE_COMMON_TAIL;
+}
+
+int
+nbdkit_parse_int32_t (const char *what, const char *str, int32_t *rp)
+{
+  long r;
+  char *end;
+
+  errno = 0;
+  r = strtol (str, &end, 0);
+#if INT32_MAX != LONG_MAX
+  if (r < INT32_MIN || r > INT32_MAX)
+    errno = ERANGE;
+#endif
+  PARSE_COMMON_TAIL;
+}
+
+int
+nbdkit_parse_int64_t (const char *what, const char *str, int64_t *rp)
+{
+  long long r;
+  char *end;
+
+  errno = 0;
+  r = strtoll (str, &end, 0);
+#if INT64_MAX != LONGLONG_MAX
+  if (r < INT64_MIN || r > INT64_MAX)
+    errno = ERANGE;
+#endif
+  PARSE_COMMON_TAIL;
+}
+
+/* Functions for parsing unsigned integers. */
+
+/* strtou* functions have surprising behaviour if the first character
+ * (after whitespace) is '-', so reject this early.
+ */
+#define PARSE_ERROR_IF_NEGATIVE                                         \
+  do {                                                                  \
+    while (isspace (*str))                                              \
+      str++;                                                            \
+    if (*str == '-') {                                                  \
+      nbdkit_error ("%s: negative numbers are not allowed", what);      \
+      return -1;                                                        \
+    }                                                                   \
+  } while (0)
+
+int
+nbdkit_parse_unsigned (const char *what, const char *str, unsigned *rp)
+{
+  unsigned long r;
+  char *end;
+
+  PARSE_ERROR_IF_NEGATIVE;
+  errno = 0;
+  r = strtoul (str, &end, 0);
+#if UINT_MAX != ULONG_MAX
+  if (r > UINT_MAX)
+    errno = ERANGE;
+#endif
+  PARSE_COMMON_TAIL;
+}
+
+int
+nbdkit_parse_uint8_t (const char *what, const char *str, uint8_t *rp)
+{
+  unsigned long r;
+  char *end;
+
+  PARSE_ERROR_IF_NEGATIVE;
+  errno = 0;
+  r = strtoul (str, &end, 0);
+  if (r > UINT8_MAX)
+    errno = ERANGE;
+  PARSE_COMMON_TAIL;
+}
+
+int
+nbdkit_parse_uint16_t (const char *what, const char *str, uint16_t *rp)
+{
+  unsigned long r;
+  char *end;
+
+  PARSE_ERROR_IF_NEGATIVE;
+  errno = 0;
+  r = strtoul (str, &end, 0);
+  if (r > UINT16_MAX)
+    errno = ERANGE;
+  PARSE_COMMON_TAIL;
+}
+
+int
+nbdkit_parse_uint32_t (const char *what, const char *str, uint32_t *rp)
+{
+  unsigned long r;
+  char *end;
+
+  PARSE_ERROR_IF_NEGATIVE;
+  errno = 0;
+  r = strtoul (str, &end, 0);
+#if UINT32_MAX != ULONG_MAX
+  if (r > UINT32_MAX)
+    errno = ERANGE;
+#endif
+  PARSE_COMMON_TAIL;
+}
+
+int
+nbdkit_parse_uint64_t (const char *what, const char *str, uint64_t *rp)
+{
+  unsigned long long r;
+  char *end;
+
+  PARSE_ERROR_IF_NEGATIVE;
+  errno = 0;
+  r = strtoull (str, &end, 0);
+#if UINT64_MAX != ULONGLONG_MAX
+  if (r > UINT64_MAX)
+    errno = ERANGE;
+#endif
+  PARSE_COMMON_TAIL;
+}
+
+/* Parse a string as a size with possible scaling suffix, or return -1
+ * after reporting the error.
+ */
+int64_t
+nbdkit_parse_size (const char *str)
+{
+  int64_t size;
+  char *end;
+  uint64_t scale = 1;
+
+  /* Disk sizes cannot usefully exceed off_t (which is signed) and
+   * cannot be negative.  */
+  /* XXX Should we also parse things like '1.5M'? */
+  /* XXX Should we allow hex? If so, hex cannot use scaling suffixes,
+   * because some of them are valid hex digits */
+  errno = 0;
+  size = strtoimax (str, &end, 10);
+  if (str == end) {
+    nbdkit_error ("could not parse size string (%s)", str);
+    return -1;
+  }
+  if (size < 0) {
+    nbdkit_error ("size cannot be negative (%s)", str);
+    return -1;
+  }
+  if (errno) {
+    nbdkit_error ("size (%s) exceeds maximum value", str);
+    return -1;
+  }
+
+  switch (*end) {
+    /* No suffix */
+  case '\0':
+    end--; /* Safe, since we already filtered out empty string */
+    break;
+
+    /* Powers of 1024 */
+  case 'e': case 'E':
+    scale *= 1024;
+    /* fallthru */
+  case 'p': case 'P':
+    scale *= 1024;
+    /* fallthru */
+  case 't': case 'T':
+    scale *= 1024;
+    /* fallthru */
+  case 'g': case 'G':
+    scale *= 1024;
+    /* fallthru */
+  case 'm': case 'M':
+    scale *= 1024;
+    /* fallthru */
+  case 'k': case 'K':
+    scale *= 1024;
+    /* fallthru */
+  case 'b': case 'B':
+    break;
+
+    /* "sectors", ie. units of 512 bytes, even if that's not the real
+     * sector size */
+  case 's': case 'S':
+    scale = 512;
+    break;
+
+  default:
+    nbdkit_error ("could not parse size: unknown suffix '%s'", end);
+    return -1;
+  }
+
+  /* XXX Maybe we should support 'MiB' as a synonym for 'M'; and 'MB'
+   * for powers of 1000, for similarity to GNU tools. But for now,
+   * anything beyond 'M' is dropped.  */
+  if (end[1]) {
+    nbdkit_error ("could not parse size: unknown suffix '%s'", end);
+    return -1;
+  }
+
+  if (INT64_MAX / scale < size) {
+    nbdkit_error ("overflow computing size (%s)", str);
+    return -1;
+  }
+
+  return size * scale;
+}
+
+/* Parse a string as a boolean, or return -1 after reporting the error.
+ */
+int
+nbdkit_parse_bool (const char *str)
+{
+  if (!strcmp (str, "1") ||
+      !strcasecmp (str, "true") ||
+      !strcasecmp (str, "t") ||
+      !strcasecmp (str, "yes") ||
+      !strcasecmp (str, "y") ||
+      !strcasecmp (str, "on"))
+    return 1;
+
+  if (!strcmp (str, "0") ||
+      !strcasecmp (str, "false") ||
+      !strcasecmp (str, "f") ||
+      !strcasecmp (str, "no") ||
+      !strcasecmp (str, "n") ||
+      !strcasecmp (str, "off"))
+    return 0;
+
+  nbdkit_error ("could not decipher boolean (%s)", str);
+  return -1;
+}
diff --git a/lib/password.c b/lib/password.c
new file mode 100644
index 00000000..8452c153
--- /dev/null
+++ b/lib/password.c
@@ -0,0 +1,152 @@
+/* nbdkit
+ * Copyright (C) 2013-2019 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.
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <termios.h>
+
+#include "nbdkit-plugin.h"
+
+/* Read a password from configuration value. */
+
+static int read_password_from_fd (const char *what, int fd, char **password);
+
+int
+nbdkit_read_password (const char *value, char **password)
+{
+  int tty, err;
+  struct termios orig, temp;
+  ssize_t r;
+  size_t n;
+
+  *password = NULL;
+
+  /* Read from stdin. */
+  if (strcmp (value, "-") == 0) {
+    printf ("password: ");
+
+    /* Set no echo. */
+    tty = isatty (0);
+    if (tty) {
+      tcgetattr (0, &orig);
+      temp = orig;
+      temp.c_lflag &= ~ECHO;
+      tcsetattr (0, TCSAFLUSH, &temp);
+    }
+
+    r = getline (password, &n, stdin);
+    err = errno;
+
+    /* Restore echo. */
+    if (tty)
+      tcsetattr (0, TCSAFLUSH, &orig);
+
+    /* Complete the printf above. */
+    printf ("\n");
+
+    if (r == -1) {
+      errno = err;
+      nbdkit_error ("could not read password from stdin: %m");
+      return -1;
+    }
+    if (*password && r > 0 && (*password)[r-1] == '\n')
+      (*password)[r-1] = '\0';
+  }
+
+  /* Read from numbered file descriptor. */
+  else if (value[0] == '-') {
+    int fd;
+
+    if (nbdkit_parse_int ("password file descriptor", &value[1], &fd) == -1)
+      return -1;
+    if (read_password_from_fd (&value[1], fd, password) == -1)
+      return -1;
+  }
+
+  /* Read password from a file. */
+  else if (value[0] == '+') {
+    int fd;
+
+    fd = open (&value[1], O_CLOEXEC | O_RDONLY);
+    if (fd == -1) {
+      nbdkit_error ("open %s: %m", &value[1]);
+      return -1;
+    }
+    if (read_password_from_fd (&value[1], fd, password) == -1)
+      return -1;
+  }
+
+  /* Parameter is the password. */
+  else {
+    *password = strdup (value);
+    if (*password == NULL) {
+      nbdkit_error ("strdup: %m");
+      return -1;
+    }
+  }
+
+  return 0;
+}
+
+static int
+read_password_from_fd (const char *what, int fd, char **password)
+{
+  FILE *fp;
+  size_t n;
+  ssize_t r;
+  int err;
+
+  fp = fdopen (fd, "r");
+  if (fp == NULL) {
+    nbdkit_error ("fdopen %s: %m", what);
+    close (fd);
+    return -1;
+  }
+  r = getline (password, &n, fp);
+  err = errno;
+  fclose (fp);
+  if (r == -1) {
+    errno = err;
+    nbdkit_error ("could not read password from %s: %m", what);
+    return -1;
+  }
+
+  if (*password && r > 0 && (*password)[r-1] == '\n')
+    (*password)[r-1] = '\0';
+
+  return 0;
+}
diff --git a/lib/path.c b/lib/path.c
new file mode 100644
index 00000000..244c2b0a
--- /dev/null
+++ b/lib/path.c
@@ -0,0 +1,97 @@
+/* nbdkit
+ * Copyright (C) 2013-2019 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.
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <limits.h>
+
+#include "nbdkit-plugin.h"
+
+#include "cleanup.h"
+#include "get-current-dir-name.h"
+
+char *
+nbdkit_absolute_path (const char *path)
+{
+  CLEANUP_FREE char *pwd = NULL;
+  char *ret;
+
+  if (path == NULL || *path == '\0') {
+    nbdkit_error ("cannot convert null or empty path to an absolute path");
+    return NULL;
+  }
+
+  if (*path == '/') {
+    ret = strdup (path);
+    if (!ret) {
+      nbdkit_error ("strdup: %m");
+      return NULL;
+    }
+    return ret;
+  }
+
+  pwd = get_current_dir_name ();
+  if (pwd == NULL) {
+    nbdkit_error ("get_current_dir_name: %m");
+    return NULL;
+  }
+
+  if (asprintf (&ret, "%s/%s", pwd, path) == -1) {
+    nbdkit_error ("asprintf: %m");
+    return NULL;
+  }
+
+  return ret;
+}
+
+char *
+nbdkit_realpath (const char *path)
+{
+  char *ret;
+
+  if (path == NULL || *path == '\0') {
+    nbdkit_error ("cannot resolve a null or empty path");
+    return NULL;
+  }
+
+  ret = realpath (path, NULL);
+  if (ret == NULL) {
+    nbdkit_error ("realpath: %s: %m", path);
+    return NULL;
+  }
+
+  return ret;
+}
diff --git a/lib/slow.c b/lib/slow.c
new file mode 100644
index 00000000..af9ce3ec
--- /dev/null
+++ b/lib/slow.c
@@ -0,0 +1,78 @@
+/* nbdkit
+ * Copyright (C) 2013-2019 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.
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+
+#include "nbdkit-plugin.h"
+
+#include "lib.h"
+
+/* Slow functions whose implementation depends on server internals are
+ * simply bounced into the server.
+ */
+
+const char *
+nbdkit_export_name (void)
+{
+  return do_nbdkit_export_name ();
+}
+
+int
+nbdkit_nanosleep (unsigned sec, unsigned nsec)
+{
+  return do_nbdkit_nanosleep (sec, nsec);
+}
+
+int
+nbdkit_peer_name (struct sockaddr *addr, socklen_t *addrlen)
+{
+  return do_nbdkit_peer_name (addr, addrlen);
+}
+
+void
+nbdkit_set_error (int err)
+{
+  do_nbdkit_set_error (err);
+}
+
+void
+nbdkit_shutdown (void)
+{
+  do_nbdkit_shutdown ();
+}
diff --git a/server/test-public.c b/lib/test-public.c
similarity index 99%
rename from server/test-public.c
rename to lib/test-public.c
index fe347d44..d680e7ad 100644
--- a/server/test-public.c
+++ b/lib/test-public.c
@@ -41,7 +41,8 @@
 #include <string.h>
 #include <unistd.h>
 
-#include "internal.h"
+#include "nbdkit-plugin.h"
+#include "nbdkit-filter.h"
 
 static bool error_flagged;
 
diff --git a/lib/vfprintf.c b/lib/vfprintf.c
new file mode 100644
index 00000000..6e330a41
--- /dev/null
+++ b/lib/vfprintf.c
@@ -0,0 +1,65 @@
+/* nbdkit
+ * Copyright (C) 2013-2018 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.
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+#include <errno.h>
+
+#include "lib.h"
+
+#if !HAVE_VFPRINTF_PERCENT_M
+/* Work around lack of %m in BSD */
+#undef vfprintf
+
+/* Call the real vfprintf after first changing %m into strerror(errno). */
+int
+replace_vfprintf (FILE *f, const char *fmt, va_list args)
+{
+  char *repl = NULL;
+  char *p = strstr (fmt, "%m"); /* assume strstr doesn't touch errno */
+  int ret;
+
+  /* We only handle the first %m; if a user passes more than one, they
+   * deserve broken output.
+   */
+  if (p && asprintf (&repl, "%.*s%s%s", (int) (p - fmt), fmt, strerror (errno),
+                     p + 2) > 0)
+    fmt = repl;
+  ret = vfprintf (f, fmt, args);
+  free (repl);
+  return ret;
+}
+#endif
diff --git a/wrapper.c b/wrapper.c
index 6aef81a1..eb0ba8ba 100644
--- a/wrapper.c
+++ b/wrapper.c
@@ -168,12 +168,16 @@ main (int argc, char *argv[])
     }
   }
 
-  /* Needed for plugins written in OCaml. */
+  /* Use libnbdkit.so from lib/.  The plugins/ocaml/.libs path is
+   * needed for plugins written in OCaml.
+   */
   s = getenv ("LD_LIBRARY_PATH");
   if (s)
-    r = asprintf (&s, "%s/plugins/ocaml/.libs:%s", builddir, s);
+    r = asprintf (&s, "%s/lib/.libs:%s/plugins/ocaml/.libs:%s",
+                  builddir, builddir, s);
   else
-    r = asprintf (&s, "%s/plugins/ocaml/.libs", builddir);
+    r = asprintf (&s, "%s/lib/.libs:%s/plugins/ocaml/.libs",
+                  builddir, builddir);
   if (r < 0) {
     perror ("asprintf");
     exit (EXIT_FAILURE);
@@ -182,9 +186,11 @@ main (int argc, char *argv[])
   free (s);
   s = getenv ("LIBRARY_PATH");
   if (s)
-    r = asprintf (&s, "%s/plugins/ocaml/.libs:%s", builddir, s);
+    r = asprintf (&s, "%s/lib/.libs:%s/plugins/ocaml/.libs:%s",
+                  builddir, builddir, s);
   else
-    r = asprintf (&s, "%s/plugins/ocaml/.libs", builddir);
+    r = asprintf (&s, "%s/lib/.libs:%s/plugins/ocaml/.libs",
+                  builddir, builddir);
   if (r < 0) {
     perror ("asprintf");
     exit (EXIT_FAILURE);
@@ -193,7 +199,7 @@ main (int argc, char *argv[])
   free (s);
 
   /* Absolute path of the real nbdkit command. */
-  passthru_format ("%s/server/nbdkit", builddir);
+  passthru_format ("%s/server/.libs/nbdkit", builddir);
 
   /* Option parsing.  We don't really parse options here.  We are only
    * interested in which options have arguments and which need
diff --git a/.gitignore b/.gitignore
index 4bb035e1..2f9d45a9 100644
--- a/.gitignore
+++ b/.gitignore
@@ -64,6 +64,7 @@ plugins/*/*.3
 /include/nbdkit-version.h
 /INSTALL
 /install-sh
+/lib/test-public
 /libtool
 /ltmain.sh
 /missing
@@ -80,7 +81,6 @@ plugins/*/*.3
 /server/nbdkit
 /server/nbdkit.pc
 /server/synopsis.c
-/server/test-public
 /stamp-h1
 /tests/__pycache__/
 /tests/disk
diff --git a/lib/libnbdkit.syms b/lib/libnbdkit.syms
new file mode 100644
index 00000000..a8a0cb74
--- /dev/null
+++ b/lib/libnbdkit.syms
@@ -0,0 +1,76 @@
+# nbdkit
+# Copyright (C) 2018-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 linker script controls the visibility of symbols in the
+# libnbdkit.so library.  We want to export some symbols to plugins,
+# but at the same time we don't want plugins to be able to call
+# arbitrary functions from nbdkit, so this script lists only the
+# symbols we want to export.
+
+{
+  # The functions we want plugins and filters to call.
+  global:
+    nbdkit_absolute_path;
+    nbdkit_add_extent;
+    nbdkit_debug;
+    nbdkit_error;
+    nbdkit_export_name;
+    nbdkit_extents_count;
+    nbdkit_extents_free;
+    nbdkit_extents_new;
+    nbdkit_get_extent;
+    nbdkit_nanosleep;
+    nbdkit_parse_bool;
+    nbdkit_parse_int16_t;
+    nbdkit_parse_int32_t;
+    nbdkit_parse_int64_t;
+    nbdkit_parse_int8_t;
+    nbdkit_parse_int;
+    nbdkit_parse_size;
+    nbdkit_parse_uint16_t;
+    nbdkit_parse_uint32_t;
+    nbdkit_parse_uint64_t;
+    nbdkit_parse_uint8_t;
+    nbdkit_parse_unsigned;
+    nbdkit_peer_name;
+    nbdkit_read_password;
+    nbdkit_realpath;
+    nbdkit_set_error;
+    nbdkit_shutdown;
+    nbdkit_vdebug;
+    nbdkit_verror;
+
+    # Private function that must only be called by the server.
+    libnbdkit_private_init;
+
+  # Everything else is hidden.
+  local: *;
+};
-- 
2.25.0




More information about the Libguestfs mailing list