[Libguestfs] [PATCH libnbd] ublk: Add new nbdublk program

Richard W.M. Jones
Thu, 25 Aug 2022 12:10:56 -0000

---
 .gitignore                  |   3 +
 Makefile.am                 |   3 +-
 README.md                   |   2 +
 bash-completion/Makefile.am |   8 +-
 bash-completion/nbdsh       |   6 +
 configure.ac                |  20 ++
 copy/nbdcopy.pod            |   1 +
 docs/libnbd.pod             |   1 +
 fuse/nbdfuse.pod            |   1 +
 info/nbdinfo.pod            |   1 +
 run.in                      |   1 +
 sh/nbdsh.pod                |   1 +
 ublk/Makefile.am            |  68 +++++
 ublk/nbdublk.c              | 569 ++++++++++++++++++++++++++++++++++++
 ublk/nbdublk.h              |  47 +++
 ublk/nbdublk.pod            | 228 +++++++++++++++
 ublk/not.cpp                |  23 ++
 ublk/tgt.c                  | 332 +++++++++++++++++++++
 18 files changed, 1312 insertions(+), 3 deletions(-)

diff --git a/.gitignore b/.gitignore
index bd4650dd77..071393fbf6 100644
--- a/.gitignore
+++ b/.gitignore
@@ -32,6 +32,7 @@ Makefile.in
 /bash-completion/nbddump
 /bash-completion/nbdfuse
 /bash-completion/nbdinfo
+/bash-completion/nbdublk
 /common/include/test-array-size
 /common/include/test-checked-overflow
 /common/include/test-ispowerof2
@@ -240,4 +241,6 @@ Makefile.in
 /tests/shutdown-flags
 /tests/synch-parallel
 /tests/synch-parallel-tls
+/ublk/nbdublk
+/ublk/nbdublk.1
 /valgrind/suppressions
diff --git a/Makefile.am b/Makefile.am
index 09a56db04b..dab4ffab46 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -49,6 +49,7 @@ SUBDIRS = \
 	copy \
 	dump \
 	fuse \
+	ublk \
 	ocaml \
 	ocaml/examples \
 	ocaml/tests \
@@ -81,7 +82,7 @@ maintainer-check-extra-dist:
 	@echo PASS: EXTRA_DIST tests

 check-valgrind: all
-	@for d in tests info copy fuse ocaml/tests interop; do \
+	@for d in tests info copy fuse ublk ocaml/tests interop; do \
 	    $(MAKE) -C $$d check-valgrind || exit 1; \
 	done

diff --git a/README.md b/README.md
index 9e9169a467..b4aa0d5a42 100644
--- a/README.md
+++ b/README.md
@@ -18,6 +18,7 @@ The key features are:
 * Hexdump tool (nbddump) to print NBD content.
 * Query tool (nbdinfo) to query NBD servers.
 * FUSE support (nbdfuse) to mount NBD in the local filesystem.
+* Linux ublk support (nbdublk) to create the userspace block device.

 For documentation, see the [docs](docs/) and [examples](examples/)
 subdirectories.
@@ -103,6 +104,7 @@ Optional:
 * OCaml and ocamlfind are both needed to generate the OCaml bindings.
 * Python >= 3.3 to build the Python 3 bindings and NBD shell (nbdsh).
 * FUSE 3 to build the nbdfuse program.
+* Linux >= 6.0 and ublksrv library to build nbdublk program.
 * go and cgo, for compiling the golang bindings and tests.
 * bash-completion >= 1.99 for tab completion.

diff --git a/bash-completion/Makefile.am b/bash-completion/Makefile.am
index cab8ffbd8c..dc5d98c75f 100644
--- a/bash-completion/Makefile.am
+++ b/bash-completion/Makefile.am
@@ -24,7 +24,7 @@ EXTRA_DIST = \

 if HAVE_BASH_COMPLETION

-bashcomp_DATA = nbddump nbdfuse nbdsh
+bashcomp_DATA = nbddump nbdfuse nbdsh nbdublk

 if HAVE_LIBXML2
 bashcomp_DATA += nbdcopy nbdinfo
@@ -46,6 +46,10 @@ nbdinfo: nbdsh
 	rm -f $@
 	$(LN_S) $(srcdir)/nbdsh $@

-CLEANFILES += nbdcopy nbddump nbdfuse nbdinfo
+nbdublk: nbdsh
+	rm -f $@
+	$(LN_S) $(srcdir)/nbdsh $@
+
+CLEANFILES += nbdcopy nbddump nbdfuse nbdinfo nbdublk

 endif
diff --git a/bash-completion/nbdsh b/bash-completion/nbdsh
index a3420038a1..bba0b46fc7 100644
--- a/bash-completion/nbdsh
+++ b/bash-completion/nbdsh
@@ -67,9 +67,15 @@ _nbdsh ()
     _libnbd_command nbdsh
 }

+_nbdublk ()
+{
+    _libnbd_command nbdublk
+}
+
 # Install the handler function.
 complete -o default -F _nbdcopy nbdcopy
 complete -o default -F _nbddump nbddump
 complete -o default -F _nbdfuse nbdfuse
 complete -o default -F _nbdinfo nbdinfo
 complete -o default -F _nbdsh nbdsh
+complete -o default -F _nbdublk nbdublk
diff --git a/configure.ac b/configure.ac
index dbb4d250f7..135a2bb0fa 100644
--- a/configure.ac
+++ b/configure.ac
@@ -337,6 +337,24 @@ AS_IF([test "x$enable_fuse" != "xno"],[
 ])
 AM_CONDITIONAL([HAVE_FUSE],[test "x$enable_fuse" != "xno"])

+dnl libublksrv is optional to build the nbdublk program.
+AC_ARG_ENABLE([ublk],
+    AS_HELP_STRING([--disable-ublk], [disable ublk (nbdublk) support]),
+    [],
+    [enable_ublk=yes])
+AS_IF([test "x$enable_ublk" != "xno"],[
+    PKG_CHECK_MODULES([UBLKSRV],[ublksrv],[
+        printf "ublksrv version is "; $PKG_CONFIG --modversion
ublksrv
+        AC_SUBST([UBLKSRV_CFLAGS])
+        AC_SUBST([UBLKSRV_LIBS])
+        AC_DEFINE([HAVE_UBLK],[1],[Define to 1 if you have ublk.])
+    ],[
+        enable_ublk=no
+        AC_MSG_WARN([libublksrv (ublk server) library and headers are missing,
so optional nbdublk program won't be built])
+    ])
+])
+AM_CONDITIONAL([HAVE_UBLK],[test "x$enable_ublk" != "xno"])
+
 dnl Check we have enough to run podwrapper.
 AC_CHECK_PROG([PERL],[perl],[perl],[no])
 AS_IF([test "x$PERL" != "xno"],[
@@ -605,6 +623,7 @@ AC_CONFIG_FILES([Makefile
                  sh/Makefile
                  tests/Makefile
                  tests/functions.sh
+                 ublk/Makefile
                  valgrind/Makefile])

 AC_OUTPUT
@@ -640,6 +659,7 @@ echo
 feature "TLS support"           test "x$HAVE_GNUTLS_TRUE" =
"x"
 feature "NBD URI support"       test "x$HAVE_LIBXML2_TRUE"
= "x"
 feature "FUSE support"          test "x$HAVE_FUSE_TRUE" =
"x"
+feature "ublk support"          test "x$HAVE_UBLK_TRUE" =
"x"
 feature "Manual pages"          test "x$HAVE_POD_TRUE" =
"x"
 feature "Bash tab completion"   test
"x$HAVE_BASH_COMPLETION_TRUE" = "x"

diff --git a/copy/nbdcopy.pod b/copy/nbdcopy.pod
index f06d1123a7..dc4e8dd428 100644
--- a/copy/nbdcopy.pod
+++ b/copy/nbdcopy.pod
@@ -304,6 +304,7 @@ L<nbddump(1)>,
 L<nbdfuse(1)>,
 L<nbdinfo(1)>,
 L<nbdsh(1)>,
+L<nbdublk(1)>,
 L<nbdkit(1)>,
 L<qemu-img(1)>.

diff --git a/docs/libnbd.pod b/docs/libnbd.pod
index dd880c3bff..7a01179a68 100644
--- a/docs/libnbd.pod
+++ b/docs/libnbd.pod
@@ -1061,6 +1061,7 @@ L<nbddump(1)>,
 L<nbdfuse(1)>,
 L<nbdinfo(1)>,
 L<nbdsh(1)>,
+L<nbdublk(1)>,
 L<qemu(1)>.

 =head1 AUTHORS
diff --git a/fuse/nbdfuse.pod b/fuse/nbdfuse.pod
index daa79c1050..6d23340df5 100644
--- a/fuse/nbdfuse.pod
+++ b/fuse/nbdfuse.pod
@@ -415,6 +415,7 @@ L<nbdcopy(1)>,
 L<nbddump(1)>,
 L<nbdinfo(1)>,
 L<nbdsh(1)>,
+L<nbdublk(1)>,
 L<fusermount3(1)>,
 L<mount.fuse3(8)>,
 L<nbd_connect_uri(3)>,
diff --git a/info/nbdinfo.pod b/info/nbdinfo.pod
index 7dfb9edb60..c3ec3ee73d 100644
--- a/info/nbdinfo.pod
+++ b/info/nbdinfo.pod
@@ -421,6 +421,7 @@ L<nbdcopy(1)>,
 L<nbddump(1)>,
 L<nbdfuse(1)>,
 L<nbdsh(1)>,
+L<nbdublk(1)>,
 L<file(1)>,
 L<jq(1)>,
 L<qemu-img(1)>,
diff --git a/run.in b/run.in
index 89226fd0e9..38c80db233 100755
--- a/run.in
+++ b/run.in
@@ -62,6 +62,7 @@ prepend PATH "$b/dump"
 prepend PATH "$b/fuse"
 prepend PATH "$b/info"
 prepend PATH "$b/sh"
+prepend PATH "$b/ublk"
 export PATH

 # Set LD_LIBRARY_PATH and DYLD_LIBRARY_PATH to contain library.
diff --git a/sh/nbdsh.pod b/sh/nbdsh.pod
index c9dac4a7ca..4d14118cd4 100644
--- a/sh/nbdsh.pod
+++ b/sh/nbdsh.pod
@@ -149,6 +149,7 @@ L<libnbd-security(3)>,
 L<nbdcopy(1)>,
 L<nbddump(1)>,
 L<nbdfuse(1)>,
+L<nbdublk(1)>,
 L<nbdinfo(1)>,
 L<qemu-img(1)>.

diff --git a/ublk/Makefile.am b/ublk/Makefile.am
new file mode 100644
index 0000000000..d3e1328ec6
--- /dev/null
+++ b/ublk/Makefile.am
@@ -0,0 +1,68 @@
+# nbd client library in userspace
+# Copyright (C) 2013-2022 Red Hat Inc.
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+include $(top_srcdir)/subdir-rules.mk
+
+EXTRA_DIST = \
+	nbdublk.pod \
+	$(NULL)
+
+TESTS_ENVIRONMENT = \
+	LIBNBD_DEBUG=1 \
+	$(MALLOC_CHECKS) \
+	EXPECTED_VERSION=$(VERSION) \
+	$(NULL)
+LOG_COMPILER = $(top_builddir)/run
+TESTS +
+if HAVE_UBLK
+
+bin_PROGRAMS = nbdublk
+
+nbdublk_SOURCES = \
+	nbdublk.c \
+	nbdublk.h \
+	tgt.c \
+	not.cpp \
+	$(NULL)
+nbdublk_CPPFLAGS = \
+	-I$(top_srcdir)/include \
+	-I$(top_srcdir)/common/include \
+	-I$(top_srcdir)/common/utils \
+	$(NULL)
+nbdublk_CFLAGS = $(WARNINGS_CFLAGS) $(UBLKSRV_CFLAGS)
+nbdublk_CXXFLAGS = $(WARNINGS_CFLAGS) $(UBLKSRV_CFLAGS)
+nbdublk_LDADD = \
+	$(top_builddir)/common/utils/libutils.la \
+	$(top_builddir)/lib/libnbd.la \
+	$(UBLKSRV_LIBS) \
+	$(NULL)
+
+if HAVE_POD
+
+man_MANS = \
+	nbdublk.1 \
+	$(NULL)
+
+nbdublk.1: nbdublk.pod $(top_builddir)/podwrapper.pl
+	$(PODWRAPPER) --section=1 --man $@ \
+	    --html $(top_builddir)/html/$@.html \
+	    $<
+
+endif HAVE_POD
+
+endif HAVE_UBLK
diff --git a/ublk/nbdublk.c b/ublk/nbdublk.c
new file mode 100644
index 0000000000..e5c7bf0253
--- /dev/null
+++ b/ublk/nbdublk.c
@@ -0,0 +1,569 @@
+/* NBD client library in userspace
+ * Copyright (C) 2013-2022 Red Hat Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/* ublk support. */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <limits.h>
+#include <signal.h>
+#include <getopt.h>
+
+#include <ublksrv.h>
+
+#include <libnbd.h>
+
+#include "nbdublk.h"
+
+#include "ispowerof2.h"
+#include "vector.h"
+#include "version.h"
+
+#define DEVICE_PREFIX "/dev/ublkb"
+#define DEVICE_PREFIX_LEN 10
+
+handles nbd = empty_vector;
+unsigned connections = 4;
+bool readonly = false;
+bool rotational;
+bool can_fua;
+uint64_t size;
+uint64_t min_block_size;
+uint64_t pref_block_size;
+bool verbose = false;
+
+/* The single control device.  This is a global so the signal handler
+ * can attempt to stop the device.
+ */
+static struct ublksrv_ctrl_dev *dev;
+
+enum mode {
+  MODE_URI,                  /* URI */
+  MODE_COMMAND,              /* --command */
+  MODE_FD,                   /* --fd */
+  MODE_SQUARE_BRACKET,       /* [ CMD ], same as --socket-activation*/
+  MODE_SOCKET_ACTIVATION,    /* --socket-activation */
+  MODE_TCP,                  /* --tcp */
+  MODE_UNIX,                 /* --unix */
+  MODE_VSOCK,                /* --vsock */
+};
+
+static void __attribute__((noreturn))
+usage (FILE *fp, int exitcode)
+{
+  fprintf (fp,
+"\n"
+"Mount NBD server as a virtual device:\n"
+"\n"
+#ifdef HAVE_LIBXML2
+"    nbdublk [-C N|--connections N] [-r] [-v|--verbose]\n"
+"            " DEVICE_PREFIX "<N> URI\n"
+"\n"
+"Other modes:\n"
+"\n"
+#endif
+"    nbdublk " DEVICE_PREFIX "<N> [ CMD [ARGS ...]
]\n"
+"    nbdublk " DEVICE_PREFIX "<N> --command CMD [ARGS
...]\n"
+"    nbdublk " DEVICE_PREFIX "<N> --fd N\n"
+"    nbdublk " DEVICE_PREFIX "<N> --tcp HOST PORT\n"
+"    nbdublk " DEVICE_PREFIX "<N> --unix SOCKET\n"
+"    nbdublk " DEVICE_PREFIX "<N> --vsock CID PORT\n"
+"\n"
+"You can also use just the device number or '-' to allocate
one:\n"
+"\n"
+"    nbdublk <N> ...\n"
+"    nbdublk - ...\n"
+"\n"
+"To unmount:\n"
+"\n"
+"    ublk del -n <N>\n"
+"\n"
+"Other options:\n"
+"\n"
+"    nbdublk --help\n"
+"    nbdublk -V|--version\n"
+"\n"
+"Please read the nbdublk(1) manual page for full usage.\n"
+"\n"
+);
+  exit (exitcode);
+}
+
+/* Which modes support multi-conn?  We cannot connect multiple times
+ * to subprocesses (since we'd have to launch multiple subprocesses).
+ */
+static bool
+mode_is_multi_conn_compatible (enum mode mode)
+{
+  switch (mode) {
+  case MODE_COMMAND:
+  case MODE_SQUARE_BRACKET:
+  case MODE_SOCKET_ACTIVATION:
+  case MODE_FD:
+    return false;
+  case MODE_URI:
+  case MODE_TCP:
+  case MODE_UNIX:
+  case MODE_VSOCK:
+    return true;
+  default:
+    abort ();
+  }
+}
+
+static struct nbd_handle *create_and_connect (enum mode mode,
+                                              int argc, char **argv);
+static void signal_handler (int sig);
+
+int
+main (int argc, char *argv[])
+{
+  enum mode mode = MODE_URI;
+  enum {
+    HELP_OPTION = CHAR_MAX + 1,
+    LONG_OPTIONS,
+    SHORT_OPTIONS,
+  };
+  /* Note the "+" means we stop processing as soon as we get to the
+   * first non-option argument (the device) and then we parse the rest
+   * of the command line without getopt.
+   */
+  const char *short_options = "+C:rvV";
+  const struct option long_options[] = {
+    { "help",               no_argument,       NULL, HELP_OPTION },
+    { "long-options",       no_argument,       NULL, LONG_OPTIONS },
+    { "connections",        required_argument, NULL, 'C' },
+    { "readonly",           no_argument,       NULL, 'r' },
+    { "read-only",          no_argument,       NULL, 'r' },
+    { "short-options",      no_argument,       NULL, SHORT_OPTIONS },
+    { "verbose",            no_argument,       NULL, 'v' },
+    { "version",            no_argument,       NULL, 'V' },
+
+    { NULL }
+  };
+  int c, r;
+  size_t i;
+  struct nbd_handle *h;
+  int64_t rs;
+  uint64_t max_block_size;
+  const char *s;
+  struct ublksrv_dev_data data = { .dev_id = -1 };
+  struct sigaction sa = { 0 };
+
+  for (;;) {
+    c = getopt_long (argc, argv, short_options, long_options, NULL);
+    if (c == -1)
+      break;
+
+    switch (c) {
+    case HELP_OPTION:
+      usage (stdout, EXIT_SUCCESS);
+
+    case LONG_OPTIONS:
+      for (i = 0; long_options[i].name != NULL; ++i) {
+        if (strcmp (long_options[i].name, "long-options") != 0
&&
+            strcmp (long_options[i].name, "short-options") != 0)
+          printf ("--%s\n", long_options[i].name);
+      }
+      exit (EXIT_SUCCESS);
+
+    case SHORT_OPTIONS:
+      for (i = 0; short_options[i]; ++i) {
+        if (short_options[i] != ':' && short_options[i] !=
'+')
+          printf ("-%c\n", short_options[i]);
+      }
+      exit (EXIT_SUCCESS);
+
+    case 'C':
+      if (sscanf (optarg, "%u", &connections) != 1 ||
+          connections < 1 || connections > 1024) {
+        fprintf (stderr, "%s: --connections parameter must be an unsigned
integer >= 1\n",
+                 argv[0]);
+        exit (EXIT_FAILURE);
+      }
+      break;
+
+    case 'r':
+      readonly = true;
+      break;
+
+    case 'v':
+      verbose = true;
+      break;
+
+    case 'V':
+      display_version ("nbdublk");
+      exit (EXIT_SUCCESS);
+
+    default:
+      usage (stderr, EXIT_FAILURE);
+    }
+  }
+
+  /* There must be at least 2 parameters (device and
+   * URI/--command/etc).
+   */
+  if (argc - optind < 2)
+    usage (stderr, EXIT_FAILURE);
+
+  /* Parse and check the device name. */
+  s = argv[optind++];
+  /* /dev/ublkc<N> */
+  if (strncmp (s, DEVICE_PREFIX, DEVICE_PREFIX_LEN) == 0) {
+    if (sscanf (&s[DEVICE_PREFIX_LEN], "%u", &data.dev_id) !=
1) {
+      fprintf (stderr, "%s: could not parse ublk device name: %s\n",
+               argv[0], s);
+      exit (EXIT_FAILURE);
+    }
+  }
+  else if (s[0] >= '0' && s[0] <= '9') {
+    if (sscanf (s, "%u", &data.dev_id) != 1) {
+      fprintf (stderr, "%s: could not parse ublk device name: %s\n",
+               argv[0], s);
+      exit (EXIT_FAILURE);
+    }
+  }
+  else if (s[0] == '-') {
+    data.dev_id = -1;           /* autoallocate */
+  }
+  else {
+    fprintf (stderr, "%s: expecting device name %s<N>\n",
+             argv[0], DEVICE_PREFIX);
+    exit (EXIT_FAILURE);
+  }
+
+  /* The next parameter is either a URI or a mode switch. */
+  if (strcmp (argv[optind], "--command") == 0 ||
+      strcmp (argv[optind], "--cmd") == 0) {
+    mode = MODE_COMMAND;
+    optind++;
+  }
+  else if (strcmp (argv[optind], "[") == 0) {
+    mode = MODE_SQUARE_BRACKET;
+    optind++;
+  }
+  else if (strcmp (argv[optind], "--socket-activation") == 0 ||
+           strcmp (argv[optind], "--systemd-socket-activation") == 0)
{
+    mode = MODE_SOCKET_ACTIVATION;
+    optind++;
+  }
+  else if (strcmp (argv[optind], "--fd") == 0) {
+    mode = MODE_FD;
+    optind++;
+  }
+  else if (strcmp (argv[optind], "--tcp") == 0) {
+    mode = MODE_TCP;
+    optind++;
+  }
+  else if (strcmp (argv[optind], "--unix") == 0) {
+    mode = MODE_UNIX;
+    optind++;
+  }
+  else if (strcmp (argv[optind], "--vsock") == 0) {
+    mode = MODE_VSOCK;
+    optind++;
+  }
+  /* This is undocumented, but allow either URI or --uri URI. */
+  else if (strcmp (argv[optind], "--uri") == 0) {
+    mode = MODE_URI;
+    optind++;
+  }
+  else if (argv[optind][0] == '-') {
+    fprintf (stderr, "%s: unknown mode: %s\n", argv[0],
argv[optind]);
+    usage (stderr, EXIT_FAILURE);
+  }
+
+#ifndef HAVE_LIBXML2
+  if (mode == MODE_URI) {
+    fprintf (stderr, "%s: URIs are not supported in this build of
libnbd\n",
+             argv[0]);
+    exit (EXIT_FAILURE);
+  }
+#endif
+
+  /* Check there are enough parameters following given the mode. */
+  switch (mode) {
+  case MODE_URI:
+  case MODE_FD:
+  case MODE_UNIX:
+    if (argc - optind != 1)
+      usage (stderr, EXIT_FAILURE);
+    break;
+  case MODE_TCP:
+  case MODE_VSOCK:
+    if (argc - optind != 2)
+      usage (stderr, EXIT_FAILURE);
+    break;
+  case MODE_COMMAND:
+  case MODE_SOCKET_ACTIVATION:
+    if (argc - optind < 1)
+      usage (stderr, EXIT_FAILURE);
+    break;
+  case MODE_SQUARE_BRACKET:
+    if (argc - optind < 2 || strcmp (argv[argc-1], "]") != 0)
+      usage (stderr, EXIT_FAILURE);
+    break;
+  }
+  /* At this point we know the command line is valid, and so can start
+   * opening FUSE and libnbd.
+   */
+
+  /* Create the libnbd handle and connect to it. */
+  h = create_and_connect (mode, argc, argv);
+  if (handles_append (&nbd, h) == -1) {
+    perror ("realloc");
+    exit (EXIT_FAILURE);
+  }
+
+  /* If the server supports multi-conn, and we are able to, try to
+   * open more handles.
+   */
+  if (connections > 1 &&
+      mode_is_multi_conn_compatible (mode) &&
+      nbd_can_multi_conn (nbd.ptr[0]) >= 1) {
+    if (handles_reserve (&nbd, connections-1) == -1) {
+      perror ("realloc");
+      exit (EXIT_FAILURE);
+    }
+    for (i = 2; i <= connections; ++i) {
+      h = create_and_connect (mode, argc, argv);
+      handles_append (&nbd, h); /* reserved above, so can't fail */
+    }
+  }
+  connections = (unsigned) nbd.len;
+
+  /* Get the size and preferred block sizes. */
+  rs = nbd_get_size (nbd.ptr[0]);
+  if (rs == -1) {
+    fprintf (stderr, "%s\n", nbd_get_error ());
+    exit (EXIT_FAILURE);
+  }
+  size = (uint64_t) rs;
+
+  rs = nbd_get_block_size (nbd.ptr[0], LIBNBD_SIZE_MAXIMUM);
+  if (rs <= 0 || rs > 64 * 1024 * 1024)
+    max_block_size = 64 * 1024 * 1024;
+  else
+    max_block_size = rs;
+  if (!is_power_of_2 (max_block_size)) {
+    fprintf (stderr,
+             "%s: %s block size is not a power of two: %" PRIu64
"\n",
+             argv[0], "maximum", max_block_size);
+    exit (EXIT_FAILURE);
+  }
+
+  rs = nbd_get_block_size (nbd.ptr[0], LIBNBD_SIZE_PREFERRED);
+  if (rs <= 0)
+    pref_block_size = 4096;
+  else
+    pref_block_size = rs;
+  if (!is_power_of_2 (pref_block_size)) {
+    fprintf (stderr,
+             "%s: %s block size is not a power of two: %" PRIu64
"\n",
+             argv[0], "preferred", pref_block_size);
+    exit (EXIT_FAILURE);
+  }
+
+  rs = nbd_get_block_size (nbd.ptr[0], LIBNBD_SIZE_MINIMUM);
+  if (rs <= 0)
+    min_block_size = 512; /* minimum that the kernel supports */
+  else
+    min_block_size = rs;
+  if (!is_power_of_2 (min_block_size)) {
+    fprintf (stderr,
+             "%s: %s block size is not a power of two: %" PRIu64
"\n",
+             argv[0], "minimum", min_block_size);
+    exit (EXIT_FAILURE);
+  }
+
+  /* If the remote NBD server is readonly, then act as if the '-r'
+   * flag was given on the nbdublk command line.
+   */
+  if (nbd_is_read_only (nbd.ptr[0]) > 0)
+    readonly = true;
+
+  rotational = nbd_is_rotational (nbd.ptr[0]) > 0;
+  can_fua = nbd_can_fua (nbd.ptr[0]) > 0;
+
+  if (verbose)
+    fprintf (stderr, "%s: size: %" PRIu64 " connections:
%u%s\n",
+             argv[0], size, connections, readonly ? " readonly" :
"");
+
+  /* Fill in other fields in 'data' struct. */
+  data.max_io_buf_bytes = max_block_size;
+  data.nr_hw_queues = connections;
+  data.queue_depth = 64;
+  data.tgt_type = "nbd";
+  data.tgt_ops = &tgt_type;
+  data.flags = 0;
+
+  dev = ublksrv_ctrl_init (&data);
+  if (!dev) {
+    fprintf (stderr, "%s: ublksrv_ctrl_init: %m\n", argv[0]);
+    exit (EXIT_FAILURE);
+  }
+
+  /* Register signal handlers to try to stop the device. */
+  sa.sa_handler = signal_handler;
+  sigaction (SIGHUP, &sa, NULL);
+  sigaction (SIGINT, &sa, NULL);
+  sigaction (SIGTERM, &sa, NULL);
+  sa.sa_handler = SIG_IGN;
+  sigaction (SIGPIPE, &sa, NULL);
+
+  r = ublksrv_ctrl_add_dev (dev);
+  if (r < 0) {
+    errno = -r;
+    fprintf (stderr, "%s: ublksrv_ctrl_add_dev: "DEVICE_PREFIX
"%d: %m\n",
+             argv[0], dev->dev_info.dev_id);
+    ublksrv_ctrl_deinit (dev);
+    exit (EXIT_FAILURE);
+  }
+
+  if (verbose)
+    fprintf (stderr, "%s: created %s%d\n",
+             argv[0], DEVICE_PREFIX, dev->dev_info.dev_id);
+
+  /* XXX nbdfuse creates a pid file.  However I reason that you can
+   * tell if the service is available when the block device is created
+   * so a pid file is not necessary.  May need to revisit this.
+   */
+
+  if (start_daemon (dev) == -1) {
+    ublksrv_ctrl_del_dev (dev);
+    ublksrv_ctrl_deinit (dev);
+    for (i = 0; i < nbd.len; ++i)
+      nbd_close (nbd.ptr[i]);
+    exit (EXIT_FAILURE);
+  }
+
+  /* Close ublk device. */
+  ublksrv_ctrl_del_dev (dev);
+  ublksrv_ctrl_deinit (dev);
+
+  /* Close NBD handle(s). */
+  for (i = 0; i < nbd.len; ++i)
+    nbd_close (nbd.ptr[i]);
+
+  exit (EXIT_SUCCESS);
+}
+
+/* Called from main() above to create an NBD handle and connect to it.
+ * For multi-conn, this may be called several times.
+ */
+static struct nbd_handle *
+create_and_connect (enum mode mode, int argc, char **argv)
+{
+  int fd;
+  uint32_t cid, port;
+  struct nbd_handle *h;
+
+  /* Create the libnbd handle. */
+  h = nbd_create ();
+  if (h == NULL) {
+    fprintf (stderr, "%s\n", nbd_get_error ());
+    exit (EXIT_FAILURE);
+  }
+  nbd_set_debug (h, verbose);
+
+  /* Connect to the NBD server synchronously. */
+  switch (mode) {
+  case MODE_URI:
+    if (nbd_connect_uri (h, argv[optind]) == -1) {
+      fprintf (stderr, "%s\n", nbd_get_error ());
+      exit (EXIT_FAILURE);
+    }
+    break;
+
+  case MODE_COMMAND:
+    if (nbd_connect_command (h, &argv[optind]) == -1) {
+      fprintf (stderr, "%s\n", nbd_get_error ());
+      exit (EXIT_FAILURE);
+    }
+    break;
+
+  case MODE_SQUARE_BRACKET:
+    /* This is the same as MODE_SOCKET_ACTIVATION but we must eat the
+     * closing square bracket on the command line.
+     */
+    assert (strcmp (argv[argc-1], "]") == 0); /* checked above */
+    argv[argc-1] = NULL;
+    /*FALLTHROUGH*/
+  case MODE_SOCKET_ACTIVATION:
+    if (nbd_connect_systemd_socket_activation (h, &argv[optind]) == -1) {
+      fprintf (stderr, "%s\n", nbd_get_error ());
+      exit (EXIT_FAILURE);
+    }
+    break;
+
+  case MODE_FD:
+    if (sscanf (argv[optind], "%d", &fd) != 1) {
+      fprintf (stderr, "%s: could not parse file descriptor: %s\n\n",
+               argv[0], argv[optind]);
+      exit (EXIT_FAILURE);
+    }
+    if (nbd_connect_socket (h, fd) == -1) {
+      fprintf (stderr, "%s\n", nbd_get_error ());
+      exit (EXIT_FAILURE);
+    }
+    break;
+
+  case MODE_TCP:
+    if (nbd_connect_tcp (h, argv[optind], argv[optind+1]) == -1) {
+      fprintf (stderr, "%s\n", nbd_get_error ());
+      exit (EXIT_FAILURE);
+    }
+    break;
+
+  case MODE_UNIX:
+    if (nbd_connect_unix (h, argv[optind]) == -1) {
+      fprintf (stderr, "%s\n", nbd_get_error ());
+      exit (EXIT_FAILURE);
+    }
+    break;
+
+  case MODE_VSOCK:
+    if (sscanf (argv[optind], "%" SCNu32, &cid) != 1) {
+      fprintf (stderr, "%s: could not parse vsock cid: %s\n\n",
+               argv[0], argv[optind]);
+      exit (EXIT_FAILURE);
+    }
+    if (sscanf (argv[optind+1], "%" SCNu32, &port) != 1) {
+      fprintf (stderr, "%s: could not parse vsock port: %s\n\n",
+               argv[0], argv[optind]);
+      exit (EXIT_FAILURE);
+    }
+    if (nbd_connect_vsock (h, cid, port) == -1) {
+      fprintf (stderr, "%s\n", nbd_get_error ());
+      exit (EXIT_FAILURE);
+    }
+    break;
+  }
+
+  return h;
+}
+
+static void
+signal_handler (int sig)
+{
+  /* XXX Racy, but not much else we can do. */
+  ublksrv_ctrl_stop_dev (dev);
+}
diff --git a/ublk/nbdublk.h b/ublk/nbdublk.h
new file mode 100644
index 0000000000..086352e9d1
--- /dev/null
+++ b/ublk/nbdublk.h
@@ -0,0 +1,47 @@
+/* NBD client library in userspace
+ * Copyright (C) 2013-2022 Red Hat Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef LIBNBD_NBDUBLK_H
+#define LIBNBD_NBDUBLK_H
+
+#include <stdbool.h>
+
+#include <ublksrv.h>
+
+#include "vector.h"
+
+DEFINE_VECTOR_TYPE (handles, struct nbd_handle *)
+
+#define UBLKSRV_TGT_TYPE_NBD 0
+
+extern handles nbd;
+extern unsigned connections;
+extern bool readonly;
+extern bool rotational;
+extern bool can_fua;
+extern char *filename;
+extern uint64_t size;
+extern uint64_t min_block_size;
+extern uint64_t pref_block_size;
+extern bool verbose;
+
+extern struct ublksrv_tgt_type tgt_type;
+
+extern int start_daemon (struct ublksrv_ctrl_dev *dev);
+
+#endif /* LIBNBD_NBDUBLK_H */
diff --git a/ublk/nbdublk.pod b/ublk/nbdublk.pod
new file mode 100644
index 0000000000..ef532f1b5b
--- /dev/null
+++ b/ublk/nbdublk.pod
@@ -0,0 +1,228 @@
+=head1 NAME
+
+nbdublk - connect network block device to a local device
+
+=head1 SYNOPSIS
+
+ nbdublk [-C N|--connections N] [-r] [-v|--verbose] /dev/ublkb<N> URI
+
+ nbdublk [-C N|--connections N] [-r] [-v|--verbose] <N> URI
+
+ nbdublk [-C N|--connections N] [-r] [-v|--verbose] - URI
+
+=for paragraph
+
+ nbdublk /dev/ublkb<N> [ CMD [ARGS ...] ]
+
+=for paragraph
+
+ nbdublk /dev/ublkb<N> --command CMD [ARGS ...]
+
+=for paragraph
+
+ nbdublk /dev/ublkb<N> --fd N
+
+=for paragraph
+
+ nbdublk /dev/ublkb<N> --tcp HOST PORT
+
+=for paragraph
+
+ nbdublk /dev/ublkb<N> --unix SOCKET
+
+=for paragraph
+
+ nbdublk /dev/ublkb<N> --vsock CID PORT
+
+To list devices:
+
+ ublk list
+
+To unmount:
+
+ ublk del -n <N>
+
+Other options:
+
+ nbdublk --help
+
+=for paragraph
+
+ nbdublk -V|--version
+
+=head1 DESCRIPTION
+
+nbdublk is used to create a Linux F</dev/ublkbI<N>> device from a
+network block device server.  Reads and writes to the virtual device
+are turned into reads and writes to the NBD server.
+
+The first parameter is the Linux device name of the form
+F</dev/ublkbI<N>> (for some number I<N>), for example
F</dev/ublkb0>,
+F</dev/ublkb1>, &c.  You can just use the number on its own, or use
+C<-> to get ublk to allocate an unused device.
+
+The second and following parameters refer to the NBD server, which can
+be local or remote.  The server can be specified as an NBD URI (like
+C<nbd://localhost>), or as an NBD server running as a subprocess of
+nbdublk (using S<C<[ ... ]>>), or in various other ways (see
+L</MODES>).
+
+Use L<ublk(8)> to list and delete devices.
+
+=head2 Requires Linux
+
+This program requires Linux E<ge> 6.0 and the C<ublk_drv.ko> kernel
+module.  You may need to load the kernel module and you usually have
+to run nbdublk as root.
+
+=head1 EXAMPLE
+
+Create an NBD ublk device connected to a remote NBD server:
+
+ # nbdublk /dev/ublkb1 nbd://pick
+
+List the device:
+
+ # ublk list
+ dev id 1: nr_hw_queues 4 queue_depth 64 block size 1 dev_capacity 0
+	 max rq size 67108864 daemon pid 32382 flags 0x0 state LIVE
+
+You can then use C</dev/ublkb1> as a regular device.  To disconnect
+the device use:
+
+ # ublk del -n 1
+
+=head1 OPTIONS
+
+=over 4
+
+=item B<--help>
+
+Display brief command line help and exit.
+
+=item B<-C> N
+
+=item B<--connections> N
+
+If multi-conn is used, use N connections to the server.  The default
+is 4.
+
+Multi-conn is enabled by default when possible.  Modes which run a
+subprocess, such as I<--command> are not able to use multi-conn.  Mode
+I<--fd> also cannot use multi-conn.  Also the server must advertise
+multi-conn (use L<nbdinfo(1)> to query what the server supports).
+
+=item B<-C 1>
+
+=item B<--connections 1>
+
+Disable multi-conn.  Only use a single connection to the NBD server.
+See L</THREAD MODEL> below.
+
+=item B<-r>
+
+=item B<--readonly>
+
+Access the network block device read-only.  The virtual file will have
+read-only permissions, and any writes will return errors.
+
+If the remote NBD server is read-only then this flag is added
+automatically.  (Check C<is_read_only:> field in the output of
+L<nbdinfo(1)>).
+
+=item B<-v>
+
+=item B<--verbose>
+
+Enable verbose messages to stderr.  This enables libnbd debugging and
+other messages.
+
+=item B<-V>
+
+=item B<--version>
+
+Display the package name and version and exit.
+
+=back
+
+=head1 MODES
+
+Modes are used to select the NBD server.  Possible modes are:
+
+=over 4
+
+=item nbdublk DEVICE URI
+
+This mode uses an NBD URI (see L<nbd_connect_uri(3)> and
+L<https://github.com/NetworkBlockDevice/nbd/blob/master/doc/uri.md>).
+For example this specifies a TLS-encrypted connection to
+C<example.com> port C<10809>, with export name C<disk>:
+
+ nbdfuse dir nbds://example.com/disk
+
+=item nbdublk DEVICE B<[> CMD [ARGS ...] B<]>
+
+Run an NBD server as a subprocess.  In this mode an NBD server can be
+run directly from the command line with nbdublk communicating with the
+server over a socket.  This requires that the NBD server supports
+systemd socket activation.  See L</EXAMPLES> above and
+L<nbd_connect_systemd_socket_activation(3)>.
+
+=item nbdublk DEVICE B<--command> CMD [ARGS ...]
+
+Select command mode.  In this mode an NBD server can be run directly
+from the command line with nbdublk communicating with the server over
+the server?s stdin/stdout.  Normally you would use this with
+C<nbdkit -s>.  See L<nbd_connect_command(3)>.
+
+=item nbdublk DEVICE B<--fd> N
+
+Select file descriptor mode.  In this mode a connected socket is
+passed to nbdublk.  nbdublk connects to the socket on the numbered
+file descriptor.  See also L<nbd_connect_socket(3)>.
+
+=item nbdublk DEVICE B<--tcp> HOST PORT
+
+Select TCP mode.  Connect to an NBD server on a host and port over an
+unencrypted TCP socket.  See also L<nbd_connect_tcp(3)>.
+
+=item nbdublk DEVICE B<--unix> SOCKET
+
+Select Unix mode.  Connect to an NBD server on a Unix domain socket.
+See also L<nbd_connect_unix(3)>.
+
+=item nbdublk DEVICE B<--vsock> CID PORT
+
+Select vsock mode.  Connect to an NBD server on a C<AF_VSOCK> socket.
+See also L<nbd_connect_vsock(3)>.
+
+=back
+
+=head1 SEE ALSO
+
+L<libnbd(3)>,
+L<nbdcopy(1)>,
+L<nbddump(1)>,
+L<nbdfuse(1)>,
+L<nbdinfo(1)>,
+L<nbdsh(1)>,
+L<ublk(8)>,
+L<nbd_connect_uri(3)>,
+L<nbd_connect_command(3)>,
+L<nbd_connect_socket(3)>,
+L<nbd_connect_systemd_socket_activation(3)>,
+L<nbd_connect_tcp(3)>,
+L<nbd_connect_unix(3)>,
+L<nbd_connect_vsock(3)>,
+L<nbdkit(1)>,
+L<nbdkit-loop(1)>,
+L<qemu-nbd(8)>,
+L<nbd-client(8)>.
+
+=head1 AUTHORS
+
+Richard W.M. Jones
+
+=head1 COPYRIGHT
+
+Copyright (C) 2019-2022 Red Hat Inc.
diff --git a/ublk/not.cpp b/ublk/not.cpp
new file mode 100644
index 0000000000..23970e346c
--- /dev/null
+++ b/ublk/not.cpp
@@ -0,0 +1,23 @@
+/* NBD client library in userspace
+ * Copyright (C) 2013-2022 Red Hat Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <config.h>
+
+/* This file does nothing except to force nbdublk to be linked as a
+ * C++ program because libublksrv requires it.
+ */
diff --git a/ublk/tgt.c b/ublk/tgt.c
new file mode 100644
index 0000000000..2a53804e42
--- /dev/null
+++ b/ublk/tgt.c
@@ -0,0 +1,332 @@
+/* NBD client library in userspace
+ * Copyright (C) 2013-2022 Red Hat Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <errno.h>
+#include <pthread.h>
+
+#include <ublksrv.h>
+
+#include <libnbd.h>
+
+#include "ispowerof2.h"
+
+#include "nbdublk.h"
+
+/* Per-thread information. */
+struct thread_info {
+  struct ublksrv_dev *dev;
+  size_t thread_num;
+  pthread_t thread;
+};
+
+static char jbuf[4096];
+static pthread_mutex_t jbuf_lock = PTHREAD_MUTEX_INITIALIZER;
+
+static void *
+io_thread (void *vpinfo)
+{
+  struct thread_info *thread_info = vpinfo;
+  struct ublksrv_dev *dev = thread_info->dev;
+  const unsigned dev_id = dev->ctrl_dev->dev_info.dev_id;
+  const size_t q_id = thread_info->thread_num;
+  struct ublksrv_queue *q;
+  int r;
+
+  pthread_mutex_lock (&jbuf_lock);
+  ublksrv_json_write_queue_info (dev->ctrl_dev, jbuf, sizeof jbuf,
+                                 q_id, gettid ());
+  pthread_mutex_unlock (&jbuf_lock);
+
+  q = ublksrv_queue_init (dev, q_id, NULL);
+  if (!q) {
+    perror ("ublksrv_queue_init");
+    return NULL;
+  }
+
+  if (verbose)
+    fprintf (stderr, "%s: ublk tid %d dev %d queue %d started\n",
+             "nbdublk", q->tid, dev_id, q->q_id);
+
+  for (;;) {
+    r = ublksrv_process_io (q);
+    if (r < 0) {
+      if (r != -ENODEV) { /* ENODEV is expected when the device is deleted */
+        errno = -r;
+        perror ("ublksrv_process_io");
+      }
+      break;
+    }
+  }
+
+  if (verbose)
+    fprintf (stderr, "%s: ublk tid %d dev %d queue %d exited\n",
+             "nbdublk", q->tid, dev_id, q->q_id);
+
+  ublksrv_queue_deinit (q);
+  return NULL;
+}
+
+static int
+set_parameters (struct ublksrv_ctrl_dev *ctrl_dev,
+                const struct ublksrv_dev *dev)
+{
+  struct ublksrv_ctrl_dev_info *dinfo = &ctrl_dev->dev_info;
+  const unsigned attrs +    (readonly ? UBLK_ATTR_READ_ONLY : 0) |
+    (rotational ? UBLK_ATTR_ROTATIONAL : 0) |
+    (can_fua ? UBLK_ATTR_FUA : 0);
+  struct ublk_params p = {
+    .types = UBLK_PARAM_TYPE_BASIC,
+    .basic = {
+      .attrs                = attrs,
+      .logical_bs_shift     = 9,
+      .physical_bs_shift    = 9,
+      .io_opt_shift         = log_2_bits (pref_block_size),
+      .io_min_shift         = log_2_bits (min_block_size),
+      .max_sectors          = dinfo->max_io_buf_bytes >> 9,
+      .dev_sectors          = dev->tgt.dev_size >> 9,
+    },
+    .discard = {
+      .max_discard_sectors  = UINT_MAX >> 9,
+      .max_discard_segments = 1,
+    },
+  };
+  int r;
+
+  pthread_mutex_lock (&jbuf_lock);
+  ublksrv_json_write_params (&p, jbuf, sizeof jbuf);
+  pthread_mutex_unlock (&jbuf_lock);
+
+  r = ublksrv_ctrl_set_params (ctrl_dev, &p);
+  if (r < 0) {
+    errno = -r;
+    perror ("ublksrv_ctrl_set_params");
+    return -1;
+  }
+
+  return 0;
+}
+
+int
+start_daemon (struct ublksrv_ctrl_dev *ctrl_dev)
+{
+  const struct ublksrv_ctrl_dev_info *dinfo = &ctrl_dev->dev_info;
+  struct thread_info *thread_info;
+  struct ublksrv_dev *dev;
+  size_t i;
+  int r;
+
+  if (verbose)
+    fprintf (stderr, "%s: starting daemon\n", "nbdublk");
+
+  r = ublksrv_ctrl_get_affinity(ctrl_dev);
+  if (r < 0) {
+    errno = r;
+    perror ("ublksrv_ctrl_get_affinity");
+    return -1;
+  }
+
+  thread_info = calloc (dinfo->nr_hw_queues, sizeof (struct thread_info));
+  if (thread_info == NULL) {
+    perror ("calloc");
+    return -1;
+  }
+
+  dev = ublksrv_dev_init (ctrl_dev);
+  if (!dev) {
+    /* Annoyingly libublksrv logs some not very useful information to
+     * syslog when this fails.
+     */
+    fprintf (stderr, "%s: ublksrv_dev_init failed: "
+             "there may be more information in syslog\n",
+             "nbdublk");
+    return -1;
+  }
+
+  /* Create the io threads. */
+  for (i = 0; i < dinfo->nr_hw_queues; ++i) {
+    thread_info[i].dev = dev;
+    thread_info[i].thread_num = i;
+    r = pthread_create (&thread_info[i].thread, NULL,
+                        io_thread, &thread_info[i]);
+    if (r != 0) {
+      errno = r;
+      perror ("pthread_create");
+      ublksrv_dev_deinit (dev);
+      return -1;
+    }
+  }
+
+  if (set_parameters (ctrl_dev, dev) == -1) {
+    ublksrv_dev_deinit (dev);
+    return -1;
+  }
+
+  /* Start the device. */
+  r = ublksrv_ctrl_start_dev (ctrl_dev, getpid ());
+  if (r < 0) {
+    errno = -r;
+    perror ("ublksrv_ctrl_start_dev");
+    ublksrv_dev_deinit (dev);
+    return -1;
+  }
+
+  ublksrv_ctrl_get_info (ctrl_dev);
+  ublksrv_ctrl_dump (ctrl_dev, jbuf);
+
+  /* Wait for threads to exit. */
+  for (i = 0; i < dinfo->nr_hw_queues; ++i)
+    pthread_join (thread_info[i].thread, NULL);
+
+  ublksrv_dev_deinit (dev);
+  free (thread_info);
+  return 0;
+}
+
+static int
+init_tgt (struct ublksrv_dev *dev, int type, int argc, char *argv[])
+{
+  const struct ublksrv_ctrl_dev_info  *info =
&dev->ctrl_dev->dev_info;
+  struct ublksrv_tgt_info *tgt = &dev->tgt;
+  struct ublksrv_tgt_base_json tgt_json = {
+    .type = type,
+    .name = "nbd",
+  };
+
+  if (verbose)
+    fprintf (stderr, "%s: init_tgt: type = %d\n",
"nbdublk", type);
+
+  if (type != UBLKSRV_TGT_TYPE_NBD)
+    return -1;
+
+  tgt_json.dev_size = tgt->dev_size = size;
+  tgt->tgt_ring_depth = info->queue_depth;
+  tgt->nr_fds = 0;
+
+  ublksrv_json_write_dev_info (dev->ctrl_dev, jbuf, sizeof jbuf);
+  ublksrv_json_write_target_base_info (jbuf, sizeof jbuf, &tgt_json);
+
+  return 0;
+}
+
+static int
+handle_io_async (struct ublksrv_queue *q, int tag)
+{
+  const struct ublksrv_io_desc *iod = ublksrv_get_iod (q, tag);
+  const unsigned op = ublksrv_get_op (iod);
+  const unsigned flags = ublksrv_get_flags (iod);
+  const bool fua = flags & UBLK_IO_F_FUA;
+  const bool alloc_zero = flags & UBLK_IO_F_NOUNMAP; /* else punch hole */
+  const size_t q_id = q->q_id; /* also the NBD handle number */
+  struct nbd_handle *h = nbd.ptr[q_id];
+  uint32_t nbd_flags = 0;
+  int r, res;
+
+  if (verbose)
+    fprintf (stderr, "%s: handle_io_async: tag = %d q_id = %zu\n",
+             "nbdublk", tag, q_id);
+
+  /* XXX reimplement this using asynch operations */
+  switch (op) {
+  case UBLK_IO_OP_READ:
+    r = nbd_pread (h, (void *) iod->addr, iod->nr_sectors << 9,
+                   iod->start_sector << 9, 0);
+    if (r == -1) {
+      fprintf (stderr, "%s: %s\n", "nbdublk", nbd_get_error
());
+      res = - (nbd_get_errno () ? : EINVAL);
+    }
+    else
+      res = iod->nr_sectors << 9; /* NBD always does complete op. */
+    break;
+
+  case UBLK_IO_OP_WRITE:
+    if (fua && can_fua)
+      nbd_flags |= LIBNBD_CMD_FLAG_FUA;
+
+    r = nbd_pwrite (h, (const void *) iod->addr, iod->nr_sectors <<
9,
+                   iod->start_sector << 9, nbd_flags);
+    if (r == -1) {
+      fprintf (stderr, "%s: %s\n", "nbdublk", nbd_get_error
());
+      res = - (nbd_get_errno () ? : EINVAL);
+    }
+    else
+      res = iod->nr_sectors << 9; /* NBD always does complete op. */
+    break;
+
+  case UBLK_IO_OP_FLUSH:
+    r = nbd_flush (h, 0);
+    if (r == -1) {
+      fprintf (stderr, "%s: %s\n", "nbdublk", nbd_get_error
());
+      res = - (nbd_get_errno () ? : EINVAL);
+    }
+    else
+      res = 0;
+    break;
+
+  case UBLK_IO_OP_DISCARD:
+    if (fua && can_fua)
+      nbd_flags |= LIBNBD_CMD_FLAG_FUA;
+
+    r = nbd_trim (h, iod->nr_sectors << 9, iod->start_sector
<< 9, nbd_flags);
+    if (r == -1) {
+      fprintf (stderr, "%s: %s\n", "nbdublk", nbd_get_error
());
+      res = - (nbd_get_errno () ? : EINVAL);
+    }
+    else
+      res = iod->nr_sectors << 9;
+    break;
+
+  case UBLK_IO_OP_WRITE_ZEROES:
+    if (fua && can_fua)
+      nbd_flags |= LIBNBD_CMD_FLAG_FUA;
+
+    if (alloc_zero)
+      nbd_flags |= LIBNBD_CMD_FLAG_NO_HOLE;
+
+    r = nbd_zero (h, iod->nr_sectors << 9, iod->start_sector
<< 9, nbd_flags);
+    if (r == -1) {
+      fprintf (stderr, "%s: %s\n", "nbdublk", nbd_get_error
());
+      res = - (nbd_get_errno () ? : EINVAL);
+    }
+    else
+      res = iod->nr_sectors << 9;
+    break;
+
+  default:
+    fprintf (stderr, "%s: unknown operation %u\n",
"nbdublk", op);
+    res = -ENOTSUP;
+    break;
+  }
+
+  ublksrv_complete_io (q, tag, res);
+
+  return 0;
+}
+
+struct ublksrv_tgt_type tgt_type = {
+  .type = UBLKSRV_TGT_TYPE_NBD,
+  .name = "nbd",
+  .init_tgt = init_tgt,
+  .handle_io_async = handle_io_async,
+};
--
2.37.0.rc2