libnbd/0008-Add-nbddump-tool.patch

1224 lines
34 KiB
Diff

From 70337b56e672a484f39be89b79917f883204f149 Mon Sep 17 00:00:00 2001
From: "Richard W.M. Jones" <rjones@redhat.com>
Date: Thu, 30 Jun 2022 09:07:27 +0100
Subject: [PATCH] Add nbddump tool
You can already do this operation using:
nbdcopy -- $uri - | hexdump -C
but that is slow, especially for large, sparse disks. This tool uses
sparseness information to skip zero sections of the disk, and it has
nice colourized output.
(cherry picked from commit c4107b9a40d6451630dcccf1bf6596c8e56420be)
---
.gitignore | 3 +
Makefile.am | 1 +
README | 2 +
bash-completion/Makefile.am | 9 +-
bash-completion/nbdsh | 6 +
configure.ac | 1 +
copy/nbdcopy.pod | 3 +-
docs/libnbd.pod | 1 +
dump/Makefile.am | 79 ++++++
dump/dump-data.sh | 57 +++++
dump/dump-empty-qcow2.sh | 46 ++++
dump/dump-pattern.sh | 56 +++++
dump/dump.c | 464 ++++++++++++++++++++++++++++++++++++
dump/nbddump.pod | 116 +++++++++
dump/test-long-options.sh | 35 +++
dump/test-short-options.sh | 35 +++
dump/test-version.sh | 33 +++
fuse/nbdfuse.pod | 1 +
info/nbdinfo.pod | 1 +
run.in | 1 +
sh/nbdsh.pod | 1 +
21 files changed, 947 insertions(+), 4 deletions(-)
create mode 100644 dump/Makefile.am
create mode 100755 dump/dump-data.sh
create mode 100755 dump/dump-empty-qcow2.sh
create mode 100755 dump/dump-pattern.sh
create mode 100644 dump/dump.c
create mode 100644 dump/nbddump.pod
create mode 100755 dump/test-long-options.sh
create mode 100755 dump/test-short-options.sh
create mode 100755 dump/test-version.sh
diff --git a/.gitignore b/.gitignore
index 22aa43c..870e916 100644
--- a/.gitignore
+++ b/.gitignore
@@ -29,6 +29,7 @@ Makefile.in
/aclocal.m4
/autom4te.cache
/bash-completion/nbdcopy
+/bash-completion/nbddump
/bash-completion/nbdfuse
/bash-completion/nbdinfo
/common/include/test-checked-overflow
@@ -56,6 +57,8 @@ Makefile.in
!/docs/nbd_close.3
!/docs/nbd_create.pod
!/docs/nbd_get_err??.3
+/dump/nbddump
+/dump/nbddump.1
/examples/aio-connect-read
/examples/batched-read-write
/examples/connect-command
diff --git a/Makefile.am b/Makefile.am
index 303b95c..9e7a281 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -46,6 +46,7 @@ SUBDIRS = \
sh \
info \
copy \
+ dump \
fuse \
ocaml \
ocaml/examples \
diff --git a/README b/README
index e50c19d..4f9298e 100644
--- a/README
+++ b/README
@@ -23,6 +23,8 @@ The key features are:
* Copying tool (nbdcopy) for high performance copying and streaming.
+ * Hexdump tool (nbddump) to print NBD content.
+
* Query tool (nbdinfo) to query NBD servers.
* FUSE support (nbdfuse) to mount NBD in the local filesystem.
diff --git a/bash-completion/Makefile.am b/bash-completion/Makefile.am
index 41d7b13..cab8ffb 100644
--- a/bash-completion/Makefile.am
+++ b/bash-completion/Makefile.am
@@ -24,17 +24,20 @@ EXTRA_DIST = \
if HAVE_BASH_COMPLETION
-bashcomp_DATA = nbdfuse nbdsh
+bashcomp_DATA = nbddump nbdfuse nbdsh
if HAVE_LIBXML2
bashcomp_DATA += nbdcopy nbdinfo
endif HAVE_LIBXML2
-
nbdcopy: nbdsh
rm -f $@
$(LN_S) $(srcdir)/nbdsh $@
+nbddump: nbdsh
+ rm -f $@
+ $(LN_S) $(srcdir)/nbdsh $@
+
nbdfuse: nbdsh
rm -f $@
$(LN_S) $(srcdir)/nbdsh $@
@@ -43,6 +46,6 @@ nbdinfo: nbdsh
rm -f $@
$(LN_S) $(srcdir)/nbdsh $@
-CLEANFILES += nbdcopy nbdfuse nbdinfo
+CLEANFILES += nbdcopy nbddump nbdfuse nbdinfo
endif
diff --git a/bash-completion/nbdsh b/bash-completion/nbdsh
index a740be9..a342003 100644
--- a/bash-completion/nbdsh
+++ b/bash-completion/nbdsh
@@ -47,6 +47,11 @@ _nbdcopy ()
_libnbd_command nbdcopy
}
+_nbddump ()
+{
+ _libnbd_command nbddump
+}
+
_nbdfuse ()
{
_libnbd_command nbdfuse
@@ -64,6 +69,7 @@ _nbdsh ()
# 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
diff --git a/configure.ac b/configure.ac
index b2137cc..5467276 100644
--- a/configure.ac
+++ b/configure.ac
@@ -563,6 +563,7 @@ AC_CONFIG_FILES([Makefile
common/utils/Makefile
copy/Makefile
docs/Makefile
+ dump/Makefile
examples/Makefile
fuse/Makefile
fuzzing/Makefile
diff --git a/copy/nbdcopy.pod b/copy/nbdcopy.pod
index 54e4391..94c713f 100644
--- a/copy/nbdcopy.pod
+++ b/copy/nbdcopy.pod
@@ -285,7 +285,7 @@ Some examples follow.
In this example, L<qemu-nbd(8)> is run as a subprocess. The
subprocess opens F<disk.qcow2> and exposes it as NBD to nbdcopy.
nbdcopy streams this to stdout (C<->) into the pipe which is read by
-L<hexdump(1)>.
+L<hexdump(1)>. (See also L<nbddump(1)>)
=head2 nbdcopy -- [ qemu-nbd -f qcow2 disk.qcow2 ] [ nbdkit memory 1G ]
@@ -311,6 +311,7 @@ server will match a hole in the other.
=head1 SEE ALSO
L<libnbd(3)>,
+L<nbddump(1)>,
L<nbdfuse(1)>,
L<nbdinfo(1)>,
L<nbdsh(1)>,
diff --git a/docs/libnbd.pod b/docs/libnbd.pod
index 13facc6..076cafb 100644
--- a/docs/libnbd.pod
+++ b/docs/libnbd.pod
@@ -1044,6 +1044,7 @@ L<libnbd-release-notes-1.2(1)>.
L<libnbd-security(3)>,
L<nbdcopy(1)>,
+L<nbddump(1)>,
L<nbdfuse(1)>,
L<nbdinfo(1)>,
L<nbdsh(1)>,
diff --git a/dump/Makefile.am b/dump/Makefile.am
new file mode 100644
index 0000000..9fd4fed
--- /dev/null
+++ b/dump/Makefile.am
@@ -0,0 +1,79 @@
+# nbd client library in userspace
+# Copyright (C) 2020-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 = \
+ dump-data.sh \
+ dump-empty-qcow2.sh \
+ dump-pattern.sh \
+ nbddump.pod \
+ test-long-options.sh \
+ test-short-options.sh \
+ test-version.sh \
+ $(NULL)
+
+bin_PROGRAMS = nbddump
+
+nbddump_SOURCES = \
+ dump.c \
+ $(NULL)
+nbddump_CPPFLAGS = \
+ -I$(top_srcdir)/include \
+ -I$(top_srcdir)/common/include \
+ -I$(top_srcdir)/common/utils \
+ $(NULL)
+nbddump_CFLAGS = \
+ $(WARNINGS_CFLAGS) \
+ $(NULL)
+nbddump_LDADD = \
+ $(top_builddir)/common/utils/libutils.la \
+ $(top_builddir)/lib/libnbd.la \
+ $(NULL)
+
+if HAVE_POD
+
+man_MANS = \
+ nbddump.1 \
+ $(NULL)
+
+nbddump.1: nbddump.pod $(top_builddir)/podwrapper.pl
+ $(PODWRAPPER) --section=1 --man $@ \
+ --html $(top_builddir)/html/$@.html \
+ $<
+
+endif HAVE_POD
+
+TESTS_ENVIRONMENT = \
+ LIBNBD_DEBUG=1 \
+ $(MALLOC_CHECKS) \
+ EXPECTED_VERSION=$(VERSION) \
+ QEMU_NBD=$(QEMU_NBD) \
+ $(NULL)
+LOG_COMPILER = $(top_builddir)/run
+
+TESTS = \
+ dump-data.sh \
+ dump-empty-qcow2.sh \
+ dump-pattern.sh \
+ test-long-options.sh \
+ test-short-options.sh \
+ test-version.sh \
+ $(NULL)
+
+check-valgrind:
+ LIBNBD_VALGRIND=1 $(MAKE) check
diff --git a/dump/dump-data.sh b/dump/dump-data.sh
new file mode 100755
index 0000000..23d09da
--- /dev/null
+++ b/dump/dump-data.sh
@@ -0,0 +1,57 @@
+#!/usr/bin/env bash
+# nbd client library in userspace
+# Copyright (C) 2020-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
+
+. ../tests/functions.sh
+
+set -e
+set -x
+
+requires nbdkit --version
+requires nbdkit data --dump-plugin
+
+output=dump-data.out
+rm -f $output
+cleanup_fn rm -f $output
+
+nbdkit -U - data data='
+ @32768 1
+ @65535 "hello, world!"
+ @17825790 "spanning buffer boundary"
+ @20000000 0
+' --run 'nbddump "$uri"' > $output
+
+cat $output
+
+if [ "$(cat $output)" != '0000000000: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
+*
+0000008000: 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
+0000008010: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
+*
+000000fff0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 68 |...............h|
+0000010000: 65 6c 6c 6f 2c 20 77 6f 72 6c 64 21 00 00 00 00 |ello, world!....|
+0000010010: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
+*
+00010ffff0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 73 70 |..............sp|
+0001100000: 61 6e 6e 69 6e 67 20 62 75 66 66 65 72 20 62 6f |anning buffer bo|
+0001100010: 75 6e 64 61 72 79 00 00 00 00 00 00 00 00 00 00 |undary..........|
+0001100020: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
+*
+0001312d00: 00 |. |' ]; then
+ echo "$0: unexpected output from nbddump command"
+ exit 1
+fi
diff --git a/dump/dump-empty-qcow2.sh b/dump/dump-empty-qcow2.sh
new file mode 100755
index 0000000..c9e583b
--- /dev/null
+++ b/dump/dump-empty-qcow2.sh
@@ -0,0 +1,46 @@
+#!/usr/bin/env bash
+# nbd client library in userspace
+# Copyright (C) 2020-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
+
+. ../tests/functions.sh
+
+set -e
+set -x
+
+requires $QEMU_NBD --version
+requires qemu-img --version
+
+file=dump-empty-qcow2.qcow2
+output=dump-empty-qcow2.out
+rm -f $file $output
+cleanup_fn rm -f $file $output
+
+size=1G
+
+# Create a large, empty qcow2 file.
+qemu-img create -f qcow2 $file $size
+
+# Dump it and check the output.
+nbddump -- [ $QEMU_NBD -r -f qcow2 $file ] > $output
+cat $output
+
+if [ "$(cat $output)" != '0000000000: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
+*
+003ffffff0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|' ]; then
+ echo "$0: unexpected output from nbddump command"
+ exit 1
+fi
diff --git a/dump/dump-pattern.sh b/dump/dump-pattern.sh
new file mode 100755
index 0000000..e4016a8
--- /dev/null
+++ b/dump/dump-pattern.sh
@@ -0,0 +1,56 @@
+#!/usr/bin/env bash
+# nbd client library in userspace
+# Copyright (C) 2020-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
+
+. ../tests/functions.sh
+
+set -e
+set -x
+
+requires nbdkit --version
+requires nbdkit pattern --dump-plugin
+
+output=dump-pattern.out
+rm -f $output
+cleanup_fn rm -f $output
+
+nbdkit -U - pattern size=299 --run 'nbddump "$uri"' > $output
+
+cat $output
+
+if [ "$(cat $output)" != '0000000000: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 08 |................|
+0000000010: 00 00 00 00 00 00 00 10 00 00 00 00 00 00 00 18 |................|
+0000000020: 00 00 00 00 00 00 00 20 00 00 00 00 00 00 00 28 |....... .......(|
+0000000030: 00 00 00 00 00 00 00 30 00 00 00 00 00 00 00 38 |.......0.......8|
+0000000040: 00 00 00 00 00 00 00 40 00 00 00 00 00 00 00 48 |.......@.......H|
+0000000050: 00 00 00 00 00 00 00 50 00 00 00 00 00 00 00 58 |.......P.......X|
+0000000060: 00 00 00 00 00 00 00 60 00 00 00 00 00 00 00 68 |.......`.......h|
+0000000070: 00 00 00 00 00 00 00 70 00 00 00 00 00 00 00 78 |.......p.......x|
+0000000080: 00 00 00 00 00 00 00 80 00 00 00 00 00 00 00 88 |................|
+0000000090: 00 00 00 00 00 00 00 90 00 00 00 00 00 00 00 98 |................|
+00000000a0: 00 00 00 00 00 00 00 a0 00 00 00 00 00 00 00 a8 |................|
+00000000b0: 00 00 00 00 00 00 00 b0 00 00 00 00 00 00 00 b8 |................|
+00000000c0: 00 00 00 00 00 00 00 c0 00 00 00 00 00 00 00 c8 |................|
+00000000d0: 00 00 00 00 00 00 00 d0 00 00 00 00 00 00 00 d8 |................|
+00000000e0: 00 00 00 00 00 00 00 e0 00 00 00 00 00 00 00 e8 |................|
+00000000f0: 00 00 00 00 00 00 00 f0 00 00 00 00 00 00 00 f8 |................|
+0000000100: 00 00 00 00 00 00 01 00 00 00 00 00 00 00 01 08 |................|
+0000000110: 00 00 00 00 00 00 01 10 00 00 00 00 00 00 01 18 |................|
+0000000120: 00 00 00 00 00 00 01 20 00 00 00 |....... ... |' ]; then
+ echo "$0: unexpected output from nbddump command"
+ exit 1
+fi
diff --git a/dump/dump.c b/dump/dump.c
new file mode 100644
index 0000000..76af04c
--- /dev/null
+++ b/dump/dump.c
@@ -0,0 +1,464 @@
+/* NBD client library in userspace
+ * Copyright (C) 2020-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 <stdbool.h>
+#include <stdint.h>
+#include <inttypes.h>
+#include <string.h>
+#include <unistd.h>
+#include <ctype.h>
+#include <signal.h>
+#include <limits.h>
+#include <getopt.h>
+
+#include <libnbd.h>
+
+#include "minmax.h"
+#include "rounding.h"
+#include "version.h"
+#include "vector.h"
+
+DEFINE_VECTOR_TYPE (uint32_vector, uint32_t)
+
+static const char *progname;
+static struct nbd_handle *nbd;
+static bool colour;
+static uint64_t limit = UINT64_MAX; /* --length (unlimited by default) */
+static int64_t size; /* actual size */
+static bool can_meta_context; /* did we get extent data? */
+
+/* See do_connect () */
+static enum { MODE_URI = 1, MODE_SQUARE_BRACKET } mode;
+static char **args;
+
+/* Read buffer. */
+static unsigned char buffer[16*1024*1024];
+
+static void do_connect (void);
+static void do_dump (void);
+static void catch_signal (int);
+
+static void __attribute__((noreturn))
+usage (FILE *fp, int exitcode)
+{
+ fprintf (fp,
+"\n"
+"Hexdump the content of a disk over NBD:\n"
+"\n"
+" nbddump NBD-URI | [ CMD ARGS ... ]\n"
+"\n"
+"Other options:\n"
+"\n"
+" nbddump --help\n"
+" nbddump --version\n"
+"\n"
+"Examples:\n"
+"\n"
+" nbddump nbd://localhost\n"
+" nbddump -- [ qemu-nbd -r -f qcow2 file.qcow2 ]\n"
+"\n"
+"Please read the nbddump(1) manual page for full usage.\n"
+"\n"
+);
+ exit (exitcode);
+}
+
+int
+main (int argc, char *argv[])
+{
+ enum {
+ HELP_OPTION = CHAR_MAX + 1,
+ LONG_OPTIONS,
+ SHORT_OPTIONS,
+ COLOUR_OPTION,
+ NO_COLOUR_OPTION,
+ };
+ const char *short_options = "n:V";
+ const struct option long_options[] = {
+ { "help", no_argument, NULL, HELP_OPTION },
+ { "long-options", no_argument, NULL, LONG_OPTIONS },
+ { "short-options", no_argument, NULL, SHORT_OPTIONS },
+ { "version", no_argument, NULL, 'V' },
+
+ { "color", no_argument, NULL, COLOUR_OPTION },
+ { "colors", no_argument, NULL, COLOUR_OPTION },
+ { "colour", no_argument, NULL, COLOUR_OPTION },
+ { "colours", no_argument, NULL, COLOUR_OPTION },
+ { "no-color", no_argument, NULL, NO_COLOUR_OPTION },
+ { "no-colors", no_argument, NULL, NO_COLOUR_OPTION },
+ { "no-colour", no_argument, NULL, NO_COLOUR_OPTION },
+ { "no-colours", no_argument, NULL, NO_COLOUR_OPTION },
+ { "length", required_argument, NULL, 'n' },
+ { "limit", required_argument, NULL, 'n' },
+ { NULL }
+ };
+ int c;
+ size_t i;
+
+ progname = argv[0];
+ colour = isatty (STDOUT_FILENO);
+
+ 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 COLOUR_OPTION:
+ colour = true;
+ break;
+
+ case NO_COLOUR_OPTION:
+ colour = false;
+ break;
+
+ case 'n':
+ /* XXX Allow human sizes here. */
+ if (sscanf (optarg, "%" SCNu64, &limit) != 1) {
+ fprintf (stderr, "%s: could not parse --length option: %s\n",
+ progname, optarg);
+ exit (EXIT_FAILURE);
+ }
+ break;
+
+ case 'V':
+ display_version ("nbddump");
+ exit (EXIT_SUCCESS);
+
+ default:
+ usage (stderr, EXIT_FAILURE);
+ }
+ }
+
+ /* Is it a URI or subprocess? */
+ if (argc - optind >= 3 &&
+ strcmp (argv[optind], "[") == 0 &&
+ strcmp (argv[argc-1], "]") == 0) {
+ mode = MODE_SQUARE_BRACKET;
+ argv[argc-1] = NULL;
+ args = &argv[optind+1];
+ }
+ else if (argc - optind == 1) {
+ mode = MODE_URI;
+ args = &argv[optind];
+ }
+ else {
+ usage (stderr, EXIT_FAILURE);
+ }
+
+ /* Open the NBD side. */
+ nbd = nbd_create ();
+ if (nbd == NULL) {
+ fprintf (stderr, "%s: %s\n", progname, nbd_get_error ());
+ exit (EXIT_FAILURE);
+ }
+ nbd_set_uri_allow_local_file (nbd, true); /* Allow ?tls-psk-file. */
+ nbd_add_meta_context (nbd, LIBNBD_CONTEXT_BASE_ALLOCATION);
+
+ /* Connect to the server. */
+ do_connect ();
+ can_meta_context =
+ nbd_can_meta_context (nbd, LIBNBD_CONTEXT_BASE_ALLOCATION) > 0;
+
+ /* Get the size. */
+ size = nbd_get_size (nbd);
+ if (size == -1) {
+ fprintf (stderr, "%s: %s\n", progname, nbd_get_error ());
+ exit (EXIT_FAILURE);
+ }
+
+ /* Before dumping, make sure we restore the terminal on ^C etc. */
+ signal (SIGINT, catch_signal);
+ signal (SIGQUIT, catch_signal);
+ signal (SIGTERM, catch_signal);
+ signal (SIGHUP, catch_signal);
+
+ /* Dump the content. */
+ do_dump ();
+
+ nbd_shutdown (nbd, 0);
+ nbd_close (nbd);
+
+ exit (EXIT_SUCCESS);
+}
+
+/* Connect the handle to the server. */
+static void
+do_connect (void)
+{
+ int r;
+
+ switch (mode) {
+ case MODE_URI: /* NBD-URI */
+ r = nbd_connect_uri (nbd, args[0]);
+ break;
+
+ case MODE_SQUARE_BRACKET: /* [ CMD ARGS ... ] */
+ r = nbd_connect_systemd_socket_activation (nbd, args);
+ break;
+
+ default:
+ abort ();
+ }
+
+ if (r == -1) {
+ fprintf (stderr, "%s: %s\n", progname, nbd_get_error ());
+ exit (EXIT_FAILURE);
+ }
+}
+
+/* Various ANSI colours, suppressed if --no-colour / not tty output. */
+static void
+ansi_restore (void)
+{
+ if (colour)
+ fputs ("\033[0m", stdout);
+}
+
+static void
+ansi_blue (void)
+{
+ if (colour)
+ fputs ("\033[1;34m", stdout);
+}
+
+static void
+ansi_green (void)
+{
+ if (colour)
+ fputs ("\033[0;32m", stdout);
+}
+
+static void
+ansi_magenta (void)
+{
+ if (colour)
+ fputs ("\033[1;35m", stdout);
+}
+
+static void
+ansi_red (void)
+{
+ if (colour)
+ fputs ("\033[1;31m", stdout);
+}
+
+static void
+ansi_grey (void)
+{
+ if (colour)
+ fputs ("\033[0;90m", stdout);
+}
+
+static void
+catch_signal (int sig)
+{
+ printf ("\n");
+ ansi_restore ();
+ fflush (stdout);
+ _exit (EXIT_FAILURE);
+}
+
+/* Read the extent map for the next block and return true if it is all
+ * zeroes. This is conservative and returns false if we did not get
+ * the full extent map from the server, or if the server doesn't
+ * support base:allocation at all.
+ */
+static int
+extent_callback (void *user_data, const char *metacontext,
+ uint64_t offset,
+ uint32_t *entries, size_t nr_entries,
+ int *error)
+{
+ uint32_vector *list = user_data;
+ size_t i;
+
+ if (strcmp (metacontext, LIBNBD_CONTEXT_BASE_ALLOCATION) != 0)
+ return 0;
+
+ /* Just append the entries we got to the list. */
+ for (i = 0; i < nr_entries; ++i) {
+ if (uint32_vector_append (list, entries[i]) == -1) {
+ perror ("realloc");
+ exit (EXIT_FAILURE);
+ }
+ }
+ return 0;
+}
+
+static bool
+test_all_zeroes (uint64_t offset, size_t count)
+{
+ uint32_vector entries = empty_vector;
+ size_t i;
+ uint64_t count_read;
+
+ if (!can_meta_context)
+ return false;
+
+ /* Get the extent map for the block. Note the server doesn't need
+ * to return all requested data here. If it does not then we return
+ * false, causing the main code to do a full read. We could be
+ * smarter and keep asking the server (XXX).
+ */
+ if (nbd_block_status (nbd, count, offset,
+ (nbd_extent_callback) {
+ .callback = extent_callback,
+ .user_data = &entries },
+ 0) == -1) {
+ fprintf (stderr, "%s: %s\n", progname, nbd_get_error ());
+ exit (EXIT_FAILURE);
+ }
+
+ count_read = 0;
+ for (i = 0; i < entries.len; i += 2) {
+ uint32_t len = entries.ptr[i];
+ uint32_t type = entries.ptr[i+1];
+
+ count_read += len;
+ if (!(type & 2)) /* not zero */
+ return false;
+ }
+
+ /* Did we read at least the whole range wanted? */
+ if (count_read < count)
+ return false;
+
+ /* If we got here, we read the whole range and it was all zeroes. */
+ return true;
+}
+
+/* Hexdump the NBD data.
+ *
+ * XXX In future we could do this all asynch (including writing to
+ * stdout) which could make it very efficient.
+ */
+static void
+do_dump (void)
+{
+ /* If --no-colour, don't use unicode in the output. */
+ const char *splat = colour ? "☆" : "*";
+ const char *pipe = colour ? "│" : "|";
+ const char *dot = colour ? "·" : ".";
+ uint64_t offset = 0;
+ uint64_t count = size > limit ? limit : size;
+ size_t i, j, n;
+ char last[16];
+ bool printed_splat = false, same;
+
+ while (count) {
+ n = MIN (count, sizeof buffer);
+
+ if (! test_all_zeroes (offset, n)) {
+ if (nbd_pread (nbd, buffer, n, offset, 0) == -1) {
+ fprintf (stderr, "%s: %s\n", progname, nbd_get_error ());
+ exit (EXIT_FAILURE);
+ }
+ }
+ else {
+ memset (buffer, 0, n);
+ }
+
+ /* Make sure a multiple of 16 bytes gets written to the buffer. */
+ if (n & 15)
+ memset (&buffer[n], 0, 16 - (n & 15));
+
+ for (i = 0; i < n; i += 16) {
+ /* Is this line the same as the last line? (Squashing) */
+ same =
+ offset + i > 0 && /* first line is never squashed */
+ offset + i + 16 < size && /* last line is never squashed */
+ memcmp (&buffer[i], last, 16) == 0;
+ if (same) {
+ if (!printed_splat) {
+ printf ("%s\n", splat);
+ printed_splat = true;
+ }
+ continue;
+ }
+ printed_splat = false;
+ memcpy (last, &buffer[i], 16); /* Save the current line. */
+
+ /* Print the offset. */
+ ansi_green ();
+ printf ("%010zx", offset + i);
+ ansi_grey ();
+ printf (": ");
+
+ /* Print the hex codes. */
+ for (j = i; j < MIN (i+16, n); ++j) {
+ if (buffer[j])
+ ansi_blue ();
+ else
+ ansi_grey ();
+ printf ("%02x ", buffer[j]);
+ }
+ ansi_grey ();
+ for (; j < i+16; ++j)
+ printf (" ");
+
+ /* Print the ASCII codes. */
+ printf ("%s", pipe);
+ for (j = i; j < MIN (i+16, n); ++j) {
+ char c = (char) buffer[j];
+ if (isalnum (c)) {
+ ansi_red ();
+ printf ("%c", c);
+ }
+ else if (isprint (c)) {
+ ansi_magenta ();
+ printf ("%c", c);
+ }
+ else {
+ ansi_grey ();
+ printf ("%s", dot);
+ }
+ }
+ ansi_grey ();
+ for (; j < i+16; ++j)
+ printf (" ");
+ printf ("%s\n", pipe);
+ ansi_restore ();
+ }
+
+ offset += n;
+ count -= n;
+ }
+}
diff --git a/dump/nbddump.pod b/dump/nbddump.pod
new file mode 100644
index 0000000..5d7864d
--- /dev/null
+++ b/dump/nbddump.pod
@@ -0,0 +1,116 @@
+=head1 NAME
+
+nbddump - hexdump the content of a disk over NBD
+
+=head1 SYNOPSIS
+
+ nbddump NBD
+
+C<NBD> is an NBD URI or subprocess:
+
+ NBD := nbd://... | nbd+unix:// (or other URI formats)
+ | [ CMD ARGS ... ]
+
+=for paragraph
+
+ nbddump --help
+
+=for paragraph
+
+ nbddump --version
+
+=head1 DESCRIPTION
+
+nbddump prints the content of a disk from an NBD server using the
+usual hexdump format:
+
+ $ nbddump nbd://localhost
+ 0000: 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 │················│
+ 0010: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 │················│
+ ☆
+ 0100: 68 65 6c 6c 6f 2c 20 77 6f 72 6c 64 21 00 00 00 │hello, world!···│
+ 0110: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 │················│
+ ☆
+ 1000: 00 00 00 21 │···! │
+
+=head2 Output format
+
+The first field (before the C<:>) is the offset within the file, in
+hexadecimal.
+
+The second field shows the hex codes of bytes read from the file.
+
+The third field shows the ASCII equivalent characters (if printable).
+
+A splat character (C<☆>) indicates lines of repeated output which have
+been squashed. (Note this is not just for lines of zero bytes, but
+any case where the next line shown would be the same as the previous
+line.)
+
+=head2 Subprocess
+
+nbddump can also run an NBD server as a subprocess. This requires an
+NBD server which understands systemd socket activation, such as
+L<qemu-nbd(8)> or L<nbdkit(1)>.
+
+For example, to dump out a qcow2 file as raw data:
+
+ nbddump -- [ qemu-nbd -r -f qcow2 file.qcow2 ]
+
+Note that S<C<[ ... ]>> are separate parameters, and must be
+surrounded by spaces. C<--> separates nbddump parameters from
+subprocess parameters.
+
+=head1 OPTIONS
+
+=over 4
+
+=item B<--help>
+
+Display brief command line help and exit.
+
+=item B<--color>
+
+=item B<--colour>
+
+=item B<--no-color>
+
+=item B<--no-colour>
+
+Enable or disable ANSI colours in output. By default we use colours
+if the output seems to be a terminal, and disable them if not.
+
+=item B<--length=>N
+
+=item B<-n> N
+
+Dump up to I<N> bytes and then stop.
+
+=item B<-V>
+
+=item B<--version>
+
+Display the package name and version and exit.
+
+=back
+
+=head1 SEE ALSO
+
+L<libnbd(3)>,
+L<nbdcopy(1)>,
+L<nbdfuse(1)>,
+L<nbdinfo(1)>,
+L<nbdsh(1)>,
+L<hexdump(1)>,
+L<file(1)>,
+L<qemu-img(1)>,
+L<nbdkit(1)>,
+L<qemu-nbd(8)>.
+
+=head1 AUTHORS
+
+Richard W.M. Jones
+
+=head1 COPYRIGHT
+
+Copyright (C) 2022 Red Hat Inc.
diff --git a/dump/test-long-options.sh b/dump/test-long-options.sh
new file mode 100755
index 0000000..924c8f5
--- /dev/null
+++ b/dump/test-long-options.sh
@@ -0,0 +1,35 @@
+#!/usr/bin/env bash
+# nbd client library in userspace
+# Copyright (C) 2019-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
+
+# Test that nbddump --long-options looks sane.
+
+. ../tests/functions.sh
+set -e
+set -x
+
+output=test-long-options.out
+cleanup_fn rm -f $output
+
+$VG nbddump --long-options > $output
+if [ $? != 0 ]; then
+ echo "$0: unexpected exit status"
+ fail=1
+fi
+cat $output
+grep -- --length $output
+grep -- --version $output
diff --git a/dump/test-short-options.sh b/dump/test-short-options.sh
new file mode 100755
index 0000000..325f7df
--- /dev/null
+++ b/dump/test-short-options.sh
@@ -0,0 +1,35 @@
+#!/usr/bin/env bash
+# nbd client library in userspace
+# Copyright (C) 2019-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
+
+# Test that nbddump --short-options looks sane.
+
+. ../tests/functions.sh
+set -e
+set -x
+
+output=test-short-options.out
+cleanup_fn rm -f $output
+
+$VG nbddump --short-options > $output
+if [ $? != 0 ]; then
+ echo "$0: unexpected exit status"
+ fail=1
+fi
+cat $output
+grep -- -n $output
+grep -- -V $output
diff --git a/dump/test-version.sh b/dump/test-version.sh
new file mode 100755
index 0000000..fce4ed1
--- /dev/null
+++ b/dump/test-version.sh
@@ -0,0 +1,33 @@
+#!/usr/bin/env bash
+# nbd client library in userspace
+# Copyright (C) 2019 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
+
+# Test that nbddump --version looks sane.
+
+fail=0
+output=$($VG nbddump --version)
+if [ $? != 0 ]; then
+ echo "$0: unexpected exit status"
+ fail=1
+fi
+if [ "$output" != "nbddump $EXPECTED_VERSION
+libnbd $EXPECTED_VERSION" ]; then
+ echo "$0: unexpected output"
+ fail=1
+fi
+echo "$output"
+exit $fail
diff --git a/fuse/nbdfuse.pod b/fuse/nbdfuse.pod
index 7c1c817..daa79c1 100644
--- a/fuse/nbdfuse.pod
+++ b/fuse/nbdfuse.pod
@@ -412,6 +412,7 @@ The differences from nbdfuse are similar to the list above.
L<libnbd(3)>,
L<nbdcopy(1)>,
+L<nbddump(1)>,
L<nbdinfo(1)>,
L<nbdsh(1)>,
L<fusermount3(1)>,
diff --git a/info/nbdinfo.pod b/info/nbdinfo.pod
index 649cf1c..4733ecd 100644
--- a/info/nbdinfo.pod
+++ b/info/nbdinfo.pod
@@ -407,6 +407,7 @@ Display the package name and version and exit.
L<libnbd(3)>,
L<nbdcopy(1)>,
+L<nbddump(1)>,
L<nbdfuse(1)>,
L<nbdsh(1)>,
L<file(1)>,
diff --git a/run.in b/run.in
index 8a21906..2a171e5 100755
--- a/run.in
+++ b/run.in
@@ -58,6 +58,7 @@ b="$(cd @abs_builddir@ && pwd)"
# Set the PATH to contain all libnbd binaries.
prepend PATH "$b/copy"
+prepend PATH "$b/dump"
prepend PATH "$b/fuse"
prepend PATH "$b/info"
prepend PATH "$b/sh"
diff --git a/sh/nbdsh.pod b/sh/nbdsh.pod
index ca5d6af..c9dac4a 100644
--- a/sh/nbdsh.pod
+++ b/sh/nbdsh.pod
@@ -147,6 +147,7 @@ L<https://gitlab.com/nbdkit/libnbd/tree/master/python/examples>.
L<libnbd(3)>,
L<libnbd-security(3)>,
L<nbdcopy(1)>,
+L<nbddump(1)>,
L<nbdfuse(1)>,
L<nbdinfo(1)>,
L<qemu-img(1)>.
--
2.31.1