Eric Blake
2023-Aug-29 22:20 UTC
[Libguestfs] [libnbd PATCH 0/3] Simplify nbd_shutdown vs. opt mode
While working on a larger set of patches to make nbdinfo favor NBD_OPT_INFO over NBD_OPT_GO where possible (which requires use of nbd_set_opt_mode(,true) in more cases), I noticed that it got unwieldy to have to pick the correct shutdown function in all code paths. So I propose making the API smarter, by adding an opt-in flag that does the right thing on my behalf. If you have an idea for a better name for the flag, or think this functionality should be enabled by default, let me know. Part of the reason for choosing a new flag is that it becomes a compile-time witness of whether nbd_shutdown has the desired capability (if we allow it to auto-opt_abort without a flag, it's harder to tell whether we are running against an older libnbd where it errors out instead). Eric Blake (3): tests: Test behavior of nbd_shutdown during opt mode api: Add new COVER_OPT_MODE flag to nbd_shutdown info: Simplify shutdown calls generator/API.ml | 21 ++++-- lib/disconnect.c | 15 ++++ tests/Makefile.am | 5 ++ tests/shutdown-opt-mode.c | 149 ++++++++++++++++++++++++++++++++++++++ .gitignore | 1 + info/list.c | 8 +- info/main.c | 4 +- 7 files changed, 188 insertions(+), 15 deletions(-) create mode 100644 tests/shutdown-opt-mode.c -- 2.41.0
Eric Blake
2023-Aug-29 22:20 UTC
[Libguestfs] [libnbd PATCH 1/3] tests: Test behavior of nbd_shutdown during opt mode
When we added nbd_set_opt_mode (v1.4), we did not do anything special to nbd_shutdown(). As a result, clients that use opt mode when available, but which want to gracefully close a socket rather than just forcefully disconnect via nbd_close(), have to take care to check the current state and then decide between nbd_opt_abort or nbd_shutdown (if they call both, one of the two will give an error message about being used from the wrong state). Add some unit test coverage of this prior to enhancing the API to make a clean shutdown easier for clients. Signed-off-by: Eric Blake <eblake at redhat.com> --- tests/Makefile.am | 5 ++ tests/shutdown-opt-mode.c | 124 ++++++++++++++++++++++++++++++++++++++ .gitignore | 1 + 3 files changed, 130 insertions(+) create mode 100644 tests/shutdown-opt-mode.c diff --git a/tests/Makefile.am b/tests/Makefile.am index 52fadd9c..d2e9baee 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -189,6 +189,7 @@ check_PROGRAMS += \ errors-server-zerosize \ server-death \ shutdown-flags \ + shutdown-opt-mode \ get-size \ read-only-flag \ read-write-flag \ @@ -263,6 +264,7 @@ TESTS += \ errors-server-zerosize \ server-death \ shutdown-flags \ + shutdown-opt-mode \ get-size \ read-only-flag \ read-write-flag \ @@ -401,6 +403,9 @@ server_death_LDADD = $(top_builddir)/lib/libnbd.la shutdown_flags_SOURCES = shutdown-flags.c shutdown_flags_LDADD = $(top_builddir)/lib/libnbd.la +shutdown_opt_mode_SOURCES = shutdown-opt-mode.c +shutdown_opt_mode_LDADD = $(top_builddir)/lib/libnbd.la + get_size_SOURCES = get-size.c get_size_LDADD = $(top_builddir)/lib/libnbd.la diff --git a/tests/shutdown-opt-mode.c b/tests/shutdown-opt-mode.c new file mode 100644 index 00000000..34386220 --- /dev/null +++ b/tests/shutdown-opt-mode.c @@ -0,0 +1,124 @@ +/* NBD client library in userspace + * Copyright Red Hat + * + * 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 shutdown in relation to opt mode. + */ + +#include <config.h> + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <signal.h> + +#include <libnbd.h> + +static const char *progname; + +int +main (int argc, char *argv[]) +{ + struct nbd_handle *nbd; + const char *cmd_old[] = { "nbdkit", "--oldstyle", "-s", "--exit-with-parent", + "memory", "size=2m", NULL }; + const char *cmd_new[] = { "nbdkit", "-s", "--exit-with-parent", + "memory", "size=2m", NULL }; + + progname = argv[0]; + + /* Part 1: Request opt mode. With oldstyle, it is not possible. */ + nbd = nbd_create (); + if (nbd == NULL) { + fprintf (stderr, "%s: %s\n", progname, nbd_get_error ()); + exit (EXIT_FAILURE); + } + if (nbd_set_opt_mode (nbd, true) == -1) { + fprintf (stderr, "%s: %s\n", progname, nbd_get_error ()); + exit (EXIT_FAILURE); + } + if (nbd_connect_command (nbd, (char **)cmd_old) == -1) { + fprintf (stderr, "%s: %s\n", progname, nbd_get_error ()); + exit (EXIT_FAILURE); + } + if (nbd_aio_is_ready (nbd) != 1) { + fprintf (stderr, "%s: unexpected state\n", progname); + exit (EXIT_FAILURE); + } + + /* opt_abort fails, because we aren't in option negotiation. */ + if (nbd_opt_abort (nbd) != -1) { + fprintf (stderr, "%s: unexpected success of nbd_opt_abort\n", progname); + exit (EXIT_FAILURE); + } + if (nbd_get_errno () != EINVAL) { + fprintf (stderr, "%s: test failed: unexpected errno: %s\n", + progname, strerror (nbd_get_errno ())); + exit (EXIT_FAILURE); + } + /* Shutdown will succeed, since opt mode was not possible. */ + if (nbd_shutdown (nbd, 0) == -1) { + fprintf (stderr, "%s: %s\n", progname, nbd_get_error ()); + exit (EXIT_FAILURE); + } + nbd_close (nbd); + + /* Part 2: Request opt mode. With newstyle, it succeeds. */ + nbd = nbd_create (); + if (nbd == NULL) { + fprintf (stderr, "%s: %s\n", progname, nbd_get_error ()); + exit (EXIT_FAILURE); + } + if (nbd_set_opt_mode (nbd, true) == -1) { + fprintf (stderr, "%s: %s\n", progname, nbd_get_error ()); + exit (EXIT_FAILURE); + } + if (nbd_connect_command (nbd, (char **)cmd_new) == -1) { + fprintf (stderr, "%s: %s\n", progname, nbd_get_error ()); + exit (EXIT_FAILURE); + } + if (nbd_aio_is_negotiating (nbd) != 1) { + fprintf (stderr, "%s: unexpected state\n", progname); + exit (EXIT_FAILURE); + } + + /* Shutdown fails, because we did not pass flag. */ + if (nbd_shutdown (nbd, 0) != -1) { + fprintf (stderr, "%s: test failed: nbd_shutdown unexpectedly worked\n", + progname); + exit (EXIT_FAILURE); + } + if (nbd_get_errno () != EINVAL) { + fprintf (stderr, "%s: test failed: unexpected errno: %s\n", + progname, strerror (nbd_get_errno ())); + exit (EXIT_FAILURE); + } + /* But we can manually call nbd_opt_abort, which closes gracefully. */ + if (nbd_opt_abort (nbd) == -1) { + fprintf (stderr, "%s: %s\n", progname, nbd_get_error ()); + exit (EXIT_FAILURE); + } + if (nbd_aio_is_closed (nbd) != 1) { + fprintf (stderr, "%s: unexpected state\n", progname); + exit (EXIT_FAILURE); + } + nbd_close (nbd); + + /* Part 3: TODO: add flag to nbd_shutdown */ + exit (EXIT_SUCCESS); +} diff --git a/.gitignore b/.gitignore index 866d745a..bb2cdf99 100644 --- a/.gitignore +++ b/.gitignore @@ -268,6 +268,7 @@ Makefile.in /tests/read-write-flag /tests/server-death /tests/shutdown-flags +/tests/shutdown-opt-mode /tests/socket-activation-name /tests/synch-parallel /tests/synch-parallel-tls -- 2.41.0
Eric Blake
2023-Aug-29 22:20 UTC
[Libguestfs] [libnbd PATCH 2/3] api: Add new COVER_OPT_MODE flag to nbd_shutdown
When nbd_set_opt_mode() was introduced, we did not adjust the behavior of nbd_shutdown(), so as a result it fails if attempted after the initial connection but before nbd_opt_abort() or nbd_opt_go(). To avoid spurious error messages, at least nbdinfo has to perform special-casing on whether it is in opt mode (call nbd_opt_abort) or connected (call nbd_shutdown) prior to calling nbd_close. It would be nicer to just have a single API perform whatever is needed to gracefully shut the connection, without having to hard-code the special casing into each application. The approach taken here is to add a new flag; if the flag is absent (behavior of an older application), then nbd_opt_abort must still be invoked separately before attempting nbd_shutdown. But if the flag is present, then whether or not the server supports opt mode, the shutdown will be graceful. This patch updates the unit test to show the effect of the new flag, and the next commit will simplify nbdinfo by utilizing it. Signed-off-by: Eric Blake <eblake at redhat.com> --- generator/API.ml | 21 ++++++++++++++++----- lib/disconnect.c | 15 +++++++++++++++ tests/shutdown-opt-mode.c | 27 ++++++++++++++++++++++++++- 3 files changed, 57 insertions(+), 6 deletions(-) diff --git a/generator/API.ml b/generator/API.ml index 1a9c8141..0b837259 100644 --- a/generator/API.ml +++ b/generator/API.ml @@ -255,6 +255,7 @@ let shutdown_flags flag_prefix = "SHUTDOWN"; flags = [ "ABANDON_PENDING", 1 lsl 16; + "COVER_OPT_MODE", 1 lsl 17; ] } let all_flags = [ cmd_flags; handshake_flags; strict_flags; @@ -1231,7 +1232,8 @@ "set_opt_mode", { starting the connection. To leave the mode and proceed on to the ready state, you must use L<nbd_opt_go(3)> successfully; a failed L<nbd_opt_go(3)> returns to the negotiating state to allow a change of -export name before trying again. You may also use L<nbd_opt_abort(3)> +export name before trying again. You may also use L<nbd_opt_abort(3)>, +or the C<LIBNBD_SHUTDOWN_COVER_OPT_MODE> flag of L<nbd_shutdown(3)>, to end the connection without finishing negotiation."; example = Some "examples/list-exports.c"; see_also = [Link "get_opt_mode"; Link "aio_is_negotiating"; @@ -1240,7 +1242,7 @@ "set_opt_mode", { Link "opt_set_meta_context"; Link "opt_starttls"; Link "opt_structured_reply"; Link "set_tls"; Link "set_request_structured_replies"; - Link "aio_connect"]; + Link "aio_connect"; Link "shutdown"]; }; "get_opt_mode", { @@ -2667,7 +2669,7 @@ "shutdown", { default_call with args = []; optargs = [ OFlags ("flags", shutdown_flags, None) ]; ret = RErr; - permitted_states = [ Connected ]; + permitted_states = [ Negotiating; Connected ]; modifies_fd = true; shortdesc = "disconnect from the NBD server"; longdesc = "\ @@ -2678,7 +2680,7 @@ "shutdown", { This function works whether or not the handle is ready for transmission of commands. If more fine-grained control is -needed, see L<nbd_aio_disconnect(3)>. +needed, see L<nbd_aio_opt_abort(3)> and L<nbd_aio_disconnect(3)>. The C<flags> argument is a bitmask, including zero or more of the following shutdown flags: @@ -2693,12 +2695,21 @@ "shutdown", { issuing those commands before informing the server of the intent to disconnect. +=item C<LIBNBD_SHUTDOWN_COVER_OPT_MODE> = 0x20000 + +If the server is still in option negotiation mode +(see L<nbd_set_opt_mode(3)>), gracefully abandon the +connection as if by L<nbd_opt_abort(3)>, instead of +complaining that the server is not yet fully connected. +If option negotiation mode was not in use or was +completed by L<nbd_opt_go(3)>, this flag has no effect. + =back For convenience, the constant C<LIBNBD_SHUTDOWN_MASK> is available to describe all shutdown flags recognized by this build of libnbd. A future version of the library may add new flags."; - see_also = [Link "close"; Link "aio_disconnect"]; + see_also = [Link "close"; Link "aio_disconnect"; Link "aio_opt_abort"]; example = Some "examples/reads-and-writes.c"; }; diff --git a/lib/disconnect.c b/lib/disconnect.c index 083d6cd3..d250e739 100644 --- a/lib/disconnect.c +++ b/lib/disconnect.c @@ -30,6 +30,20 @@ int nbd_unlocked_shutdown (struct nbd_handle *h, uint32_t flags) { + /* If still negotiating, return an error unless COVER_OPT_ABORT lets + * us trigger opt_abort instead. + */ + if (nbd_internal_is_state_negotiating (get_next_state (h))) { + if (flags & LIBNBD_SHUTDOWN_COVER_OPT_MODE) { + if (nbd_unlocked_aio_opt_abort (h) == -1) + return -1; + goto wait; + } + set_error (EINVAL, "invalid state: COVER_OPT_ABORT not requested, but " + "the handle is still negotiating options with the server"); + return -1; + } + /* If ABANDON_PENDING, abort any commands that have not yet had any * bytes sent to the server, so NBD_CMD_DISC becomes next in line. */ @@ -50,6 +64,7 @@ nbd_unlocked_shutdown (struct nbd_handle *h, uint32_t flags) return -1; } + wait: while (!nbd_internal_is_state_closed (get_next_state (h)) && !nbd_internal_is_state_dead (get_next_state (h))) { if (nbd_unlocked_poll (h, -1) == -1) diff --git a/tests/shutdown-opt-mode.c b/tests/shutdown-opt-mode.c index 34386220..96547607 100644 --- a/tests/shutdown-opt-mode.c +++ b/tests/shutdown-opt-mode.c @@ -119,6 +119,31 @@ main (int argc, char *argv[]) } nbd_close (nbd); - /* Part 3: TODO: add flag to nbd_shutdown */ + /* Part 3: Use of flag avoids the need to manually do opt_abort. */ + nbd = nbd_create (); + if (nbd == NULL) { + fprintf (stderr, "%s: %s\n", progname, nbd_get_error ()); + exit (EXIT_FAILURE); + } + if (nbd_set_opt_mode (nbd, true) == -1) { + fprintf (stderr, "%s: %s\n", progname, nbd_get_error ()); + exit (EXIT_FAILURE); + } + if (nbd_connect_command (nbd, (char **)cmd_new) == -1) { + fprintf (stderr, "%s: %s\n", progname, nbd_get_error ()); + exit (EXIT_FAILURE); + } + if (nbd_aio_is_negotiating (nbd) != 1) { + fprintf (stderr, "%s: unexpected state\n", progname); + exit (EXIT_FAILURE); + } + + /* Shutdown succeeds, because of the flag. */ + if (nbd_shutdown (nbd, LIBNBD_SHUTDOWN_COVER_OPT_MODE) == -1) { + fprintf (stderr, "%s: %s\n", progname, nbd_get_error ()); + exit (EXIT_FAILURE); + } + nbd_close (nbd); + exit (EXIT_SUCCESS); } -- 2.41.0
Eric Blake
2023-Aug-29 22:20 UTC
[Libguestfs] [libnbd PATCH 3/3] info: Simplify shutdown calls
Depending on command line options and server capabilities, nbdinfo has legitimate reasons to either be in opt mode or fully connected at the time a handle is ready to close. Previously, to avoid unwanted error messages, we had to manually check state to call the correct function out of nbd_opt_abort or nbd_shutdown that would not fail; but now that nbd_shutdown has a new flag, it is simpler to not have to worry about it. Signed-off-by: Eric Blake <eblake at redhat.com> --- info/list.c | 8 +------- info/main.c | 4 +--- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/info/list.c b/info/list.c index 7848923f..d39e26e6 100644 --- a/info/list.c +++ b/info/list.c @@ -62,12 +62,6 @@ collect_exports (void) fprintf (stderr, "%s: %s\n", progname, nbd_get_error ()); exit (EXIT_FAILURE); } - if (probe_content) - /* Disconnect from the server to move the handle into a closed - * state, in case the server serializes further connections. - * But we can ignore errors in this case. - */ - nbd_opt_abort (nbd); } void @@ -127,7 +121,7 @@ list_all_exports (void) list_okay = false; if (probe_content) { - nbd_shutdown (nbd2, 0); + nbd_shutdown (nbd2, LIBNBD_SHUTDOWN_COVER_OPT_MODE); nbd_close (nbd2); } } diff --git a/info/main.c b/info/main.c index 572dd536..204290a5 100644 --- a/info/main.c +++ b/info/main.c @@ -354,9 +354,7 @@ main (int argc, char *argv[]) } free_exports (); - if (opt_mode) - nbd_opt_abort (nbd); - nbd_shutdown (nbd, 0); + nbd_shutdown (nbd, LIBNBD_SHUTDOWN_COVER_OPT_MODE); nbd_close (nbd); /* Close the output stream and copy it to the real stdout. */ -- 2.41.0
Richard W.M. Jones
2023-Sep-01 21:28 UTC
[Libguestfs] [libnbd PATCH 0/3] Simplify nbd_shutdown vs. opt mode
On Tue, Aug 29, 2023 at 05:20:40PM -0500, Eric Blake wrote:> While working on a larger set of patches to make nbdinfo favor > NBD_OPT_INFO over NBD_OPT_GO where possible (which requires use of > nbd_set_opt_mode(,true) in more cases), I noticed that it got unwieldy > to have to pick the correct shutdown function in all code paths. So I > propose making the API smarter, by adding an opt-in flag that does the > right thing on my behalf. > > If you have an idea for a better name for the flag, or think this > functionality should be enabled by default, let me know. Part of the > reason for choosing a new flag is that it becomes a compile-time > witness of whether nbd_shutdown has the desired capability (if we > allow it to auto-opt_abort without a flag, it's harder to tell whether > we are running against an older libnbd where it errors out instead).My feeling is this should be enabled by default, as that does the right thing by default. Whether or not we need to have a flag to disable it (ie the opposite sense to the proposed flag) is up to you. For the series: Reviewed-by: Richard W.M. Jones <rjones at redhat.com> Rich.> Eric Blake (3): > tests: Test behavior of nbd_shutdown during opt mode > api: Add new COVER_OPT_MODE flag to nbd_shutdown > info: Simplify shutdown calls > > generator/API.ml | 21 ++++-- > lib/disconnect.c | 15 ++++ > tests/Makefile.am | 5 ++ > tests/shutdown-opt-mode.c | 149 ++++++++++++++++++++++++++++++++++++++ > .gitignore | 1 + > info/list.c | 8 +- > info/main.c | 4 +- > 7 files changed, 188 insertions(+), 15 deletions(-) > create mode 100644 tests/shutdown-opt-mode.c > > -- > 2.41.0 > > _______________________________________________ > Libguestfs mailing list > Libguestfs at redhat.com > https://listman.redhat.com/mailman/listinfo/libguestfs-- Richard Jones, Virtualization Group, Red Hat http://people.redhat.com/~rjones Read my programming and virtualization blog: http://rwmj.wordpress.com Fedora Windows cross-compiler. Compile Windows programs, test, and build Windows installers. Over 100 libraries supported. http://fedoraproject.org/wiki/MinGW
Eric Blake
2023-Sep-05 13:47 UTC
[Libguestfs] [libnbd PATCH] info: Prefer NBD_OPT_INFO when possible
Now that libnbd supports extended headers, it is worth observing that the qemu implementation for NBD_CMD_BLOCK_STATUS that supports an extended header for filtering the server's response, as advertised by NBD_FLAG_BLOCK_STAT_PAYLOAD, is conditionally advertised. When using NBD_OPT_GO, the flag is only advertised if the client requested more than one meta context (as otherwise, there is no use filtering). But for NBD_OPT_INFO, the flag is advertised if extended headers are enabled, regardless of whether meta contexts are negotiated yet. In order for upcoming libnbd patches to add support for probing and using this bit reliably, we therefore need 'nbdinfo --can ...' to favor the information learned by NBD_OPT_INFO instead of NBD_OPT_GO, because that is lighter weight than figuring out whether an export supports at least two meta contexts that can then be negotiated. Do this by changing nbdinfo to prefer opt mode always, then touching up the few places (--map, --content) that need to ensure NBD_OPT_GO. This is made easier by the previous patches that make nbd_shutdown() work sanely regardless of whether we are still in opt mode. In turn, this patch flushed out a double-free bug in 'nbdkit file dir=...' when querying opt_info on the default name, so a couple of unit tests need to avoid false negatives on platforms where nbdkit commit 39d62de9 is not yet backported. Signed-off-by: Eric Blake <eblake at redhat.com> --- info/info-list-uris.sh | 9 ++++++++- info/info-uri.sh | 9 ++++++++- info/main.c | 14 +++++++++----- info/map.c | 7 +++++++ 4 files changed, 32 insertions(+), 7 deletions(-) diff --git a/info/info-list-uris.sh b/info/info-list-uris.sh index 0d6a16a0..d8ea9108 100755 --- a/info/info-list-uris.sh +++ b/info/info-list-uris.sh @@ -22,7 +22,14 @@ set -e set -x requires nbdkit --version -requires nbdkit file --version +# Avoid tickling a double-free bug in nbdkit 1.35.11 +requires nbdkit -U- -r file dir=. --run 'nbdsh --opt-mode -u "$uri" -c " +try: + h.opt_info() +except nbd.Error: + pass +h.opt_abort() +"' # This test requires nbdkit >= 1.22. minor=$( nbdkit --dump-config | grep ^version_minor | cut -d= -f2 ) diff --git a/info/info-uri.sh b/info/info-uri.sh index d491e904..ba0c36d2 100755 --- a/info/info-uri.sh +++ b/info/info-uri.sh @@ -24,7 +24,14 @@ set -e set -x requires nbdkit --version -requires nbdkit file --version +# Avoid tickling a double-free bug in nbdkit 1.35.11 +requires nbdkit -U- -r file dir=. --run 'nbdsh --opt-mode -u "$uri" -c " +try: + h.opt_info() +except nbd.Error: + pass +h.opt_abort() +"' requires nbdkit -U - null --run 'test "$uri" != ""' requires jq --version diff --git a/info/main.c b/info/main.c index 204290a5..80d38224 100644 --- a/info/main.c +++ b/info/main.c @@ -138,7 +138,6 @@ main (int argc, char *argv[]) size_t output_len = 0; bool content_flag = false, no_content_flag = false; bool list_okay = true; - bool opt_mode = false; progname = argv[0]; colour = isatty (STDOUT_FILENO); @@ -281,11 +280,9 @@ main (int argc, char *argv[]) nbd_set_uri_allow_local_file (nbd, true); /* Allow ?tls-psk-file. */ /* Set optional modes in the handle. */ - opt_mode = !can && !map && !size_only; - if (opt_mode) { - nbd_set_opt_mode (nbd, true); + nbd_set_opt_mode (nbd, true); + if (!can && !map && !size_only) nbd_set_full_info (nbd, true); - } if (map) nbd_add_meta_context (nbd, map); @@ -398,6 +395,13 @@ do_connect (struct nbd_handle *nbd) fprintf (stderr, "%s: %s\n", progname, nbd_get_error ()); exit (EXIT_FAILURE); } + + /* If we are in opt mode, request info on the original export name. + * However, ignoring failure at this time is okay, as later code + * may want to try an alternate export name. + */ + if (nbd_aio_is_negotiating (nbd)) + nbd_opt_info (nbd); } /* The URI field in output is not meaningful unless there's a diff --git a/info/map.c b/info/map.c index 594f24ff..399e0355 100644 --- a/info/map.c +++ b/info/map.c @@ -54,6 +54,13 @@ do_map (void) uint64_t offset, align, max_len; size_t prev_entries_size; + /* Map mode requires switching over to transmission phase. */ + if (nbd_aio_is_negotiating (nbd) && + nbd_opt_go (nbd) == -1) { + fprintf (stderr, "%s: %s\n", progname, nbd_get_error ()); + exit (EXIT_FAILURE); + } + /* Did we get the requested map? */ if (!nbd_can_meta_context (nbd, map)) { fprintf (stderr, -- 2.41.0