---
.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