Commit 908ca30b authored by Richard W.M. Jones's avatar Richard W.M. Jones
Browse files

ublk: Add new nbdublk program

parent 27a9bb45
......@@ -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
......@@ -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
......
......@@ -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.
......
......@@ -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
......@@ -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
......@@ -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"
......
......@@ -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)>.
......
......@@ -1061,6 +1061,7 @@ L<nbddump(1)>,
L<nbdfuse(1)>,
L<nbdinfo(1)>,
L<nbdsh(1)>,
L<nbdublk(1)>,
L<qemu(1)>.
=head1 AUTHORS
......
......@@ -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)>,
......
......@@ -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)>,
......
......@@ -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.
......
......@@ -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)>.
......
# 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
/* 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. */
/* 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);