[Libguestfs] [libnbd PATCH v2] api: Add set_request_block_size

Eric Blake eblake at redhat.com
Thu Feb 24 17:26:20 UTC 2022


Our testsuite coverage of nbd_get_block_size() is pretty sparse (the
recent commit 6f5fec2ea uses them in errors-server-unaligned.c for
debug purposes, and even that requires recent patches in nbdkit).  But
in the process of adding an interop test with qemu-nbd, I also noticed
that qemu-nbd (at least version 6.2) fails NBD_OPT_INFO for older
clients that don't request block size, and fudges the value to 1 for
NBD_OPT_GO for back-compat reasons.  We still want to request by
default, but now we need a knob, similar to the existing
set_full_info(), for overriding our defaults for testing purposes.
---

In v2:
- drop RFC
- fix bugs in implementation (the RFC version passed a wrong size over
  the wire, causing 'nbdkit nbd' to deadlock)
- actually implement interop-qemu-block-size.sh
- add test coverage of language bindings

 lib/internal.h                             |  5 +-
 generator/API.ml                           | 52 ++++++++++++--
 generator/states-newstyle-opt-go.c         | 27 ++++++--
 lib/handle.c                               | 14 ++++
 python/t/110-defaults.py                   |  1 +
 python/t/120-set-non-defaults.py           |  2 +
 ocaml/tests/test_110_defaults.ml           |  2 +
 ocaml/tests/test_120_set_non_defaults.ml   |  3 +
 interop/Makefile.am                        |  4 +-
 interop/interop-qemu-block-size.sh         | 80 ++++++++++++++++++++++
 golang/libnbd_110_defaults_test.go         |  8 +++
 golang/libnbd_120_set_non_defaults_test.go | 14 +++-
 12 files changed, 196 insertions(+), 16 deletions(-)
 create mode 100755 interop/interop-qemu-block-size.sh

diff --git a/lib/internal.h b/lib/internal.h
index 525499a9..3868a7f1 100644
--- a/lib/internal.h
+++ b/lib/internal.h
@@ -120,8 +120,9 @@ struct nbd_handle {
   uint8_t opt_current; /* 0 or one of NBD_OPT_* */
   struct command_cb opt_cb;

-  /* Full info mode. */
-  bool full_info;
+  /* Tweak what OPT_INFO/GO requests. */
+  bool request_block_size; /* default true, for INFO_BLOCK_SIZE */
+  bool full_info; /* default false, for INFO_NAME, INFO_DESCRIPTION */

   /* Sanitization for pread. */
   bool pread_initialize;
diff --git a/generator/API.ml b/generator/API.ml
index e63a10ee..3e948aa2 100644
--- a/generator/API.ml
+++ b/generator/API.ml
@@ -395,6 +395,40 @@   "get_export_name", {
                 Link "get_canonical_export_name"];
   };

+  "set_request_block_size", {
+    default_call with
+    args = [Bool "request"]; ret = RErr;
+    permitted_states = [ Created; Negotiating ];
+    shortdesc = "control whether NBD_OPT_GO requests block size";
+    longdesc = "\
+By default, when connecting to an export, libnbd requests that the
+server report any block size restrictions.  The NBD protocol states
+that a server may supply block sizes regardless of whether the client
+requests them, and libnbd will report those block sizes (see
+L<nbd_get_block_size(3)>); conversely, if a client does not request
+block sizes, the server may reject the connection instead of dealing
+with a client sending unaligned requests.  This function makes it
+possible to test server behavior by emulating older clients.
+
+Note that even when block size is requested, the server is not
+obligated to provide any.  Furthermore, if block sizes are provided
+(whether or not the client requested them), libnbd enforces alignment
+to those sizes unless L<nbd_set_strict_mode(3)> is used to bypass
+client-side safety checks.";
+    see_also = [Link "get_request_block_size"; Link "set_full_info";
+                Link "get_block_size"; Link "set_strict_mode"];
+  };
+
+  "get_request_block_size", {
+    default_call with
+    args = []; ret = RBool;
+    permitted_states = [];
+    shortdesc = "see if NBD_OPT_GO requests block size";
+    longdesc = "\
+Return the state of the block size request flag on this handle.";
+    see_also = [Link "set_request_block_size"];
+  };
+
   "set_full_info", {
     default_call with
     args = [Bool "request"]; ret = RErr;
@@ -413,10 +447,11 @@   "set_full_info", {
 Note that even when full info is requested, the server is not
 obligated to reply with all information that libnbd requested.
 Similarly, libnbd will ignore any optional server information that
-libnbd has not yet been taught to recognize.";
-    example = Some "examples/server-flags.c";
+libnbd has not yet been taught to recognize.  Furthermore, the
+hint to request block sizes is independently controlled via
+L<nbd_set_request_block_size(3)>.";
     see_also = [Link "get_full_info"; Link "get_canonical_export_name";
-                Link "get_export_description"];
+                Link "get_export_description"; Link "set_request_block_size"];
   };

   "get_full_info", {
@@ -1839,9 +1874,13 @@   "get_block_size", {
 =back

 Future NBD extensions may result in additional C<size_type> values.
+Note that by default, libnbd requests all available block sizes,
+but that a server may differ in what sizes it chooses to report
+if L<nbd_set_request_block_size(3)> alters whether the client
+requests sizes.
 "
 ^ non_blocking_test_call_description;
-    see_also = [Link "get_protocol";
+    see_also = [Link "get_protocol"; Link "set_request_block_size";
                 Link "get_size"; Link "opt_info"]
   };

@@ -1976,7 +2015,8 @@   "pread_structured", {
 ^ strict_call_description;
     see_also = [Link "can_df"; Link "pread";
                 Link "aio_pread_structured"; Link "get_block_size";
-                Link "set_strict_mode"; Link "set_pread_initialize"];
+                Link "set_strict_mode"; Link "set_pread_initialize";
+                Link "set_request_block_size"];
   };

   "pwrite", {
@@ -3201,6 +3241,8 @@ let first_version =
   (* Added in 1.11.x development cycle, will be stable and supported in 1.12. *)
   "set_pread_initialize", (1, 12);
   "get_pread_initialize", (1, 12);
+  "set_request_block_size", (1, 12);
+  "get_request_block_size", (1, 12);

   (* These calls are proposed for a future version of libnbd, but
    * have not been added to any released version so far.
diff --git a/generator/states-newstyle-opt-go.c b/generator/states-newstyle-opt-go.c
index eed769b3..b7354aed 100644
--- a/generator/states-newstyle-opt-go.c
+++ b/generator/states-newstyle-opt-go.c
@@ -1,5 +1,5 @@
 /* nbd client library in userspace: state machine
- * Copyright (C) 2013-2020 Red Hat Inc.
+ * 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
@@ -20,7 +20,12 @@

 STATE_MACHINE {
  NEWSTYLE.OPT_GO.START:
-  uint16_t nrinfos = h->full_info ? 3 : 1;
+  uint16_t nrinfos = 0;
+
+  if (h->request_block_size)
+    nrinfos++;
+  if (h->full_info)
+    nrinfos += 2;

   assert (h->gflags & LIBNBD_HANDSHAKE_FLAG_FIXED_NEWSTYLE);
   if (h->opt_current == NBD_OPT_INFO)
@@ -67,7 +72,12 @@ STATE_MACHINE {
   return 0;

  NEWSTYLE.OPT_GO.SEND_EXPORT:
-  uint16_t nrinfos = h->full_info ? 3 : 1;
+  uint16_t nrinfos = 0;
+
+  if (h->request_block_size)
+    nrinfos++;
+  if (h->full_info)
+    nrinfos += 2;

   switch (send_from_wbuf (h)) {
   case -1: SET_NEXT_STATE (%.DEAD); return 0;
@@ -80,14 +90,17 @@ STATE_MACHINE {
   return 0;

  NEWSTYLE.OPT_GO.SEND_NRINFOS:
-  uint16_t nrinfos = h->full_info ? 3 : 1;
+  uint16_t nrinfos = 0;

   switch (send_from_wbuf (h)) {
   case -1: SET_NEXT_STATE (%.DEAD); return 0;
   case 0:
-    h->sbuf.info[0] = htobe16 (NBD_INFO_BLOCK_SIZE);
-    h->sbuf.info[1] = htobe16 (NBD_INFO_NAME);
-    h->sbuf.info[2] = htobe16 (NBD_INFO_DESCRIPTION);
+    if (h->request_block_size)
+      h->sbuf.info[nrinfos++] = htobe16 (NBD_INFO_BLOCK_SIZE);
+    if (h->full_info) {
+      h->sbuf.info[nrinfos++] = htobe16 (NBD_INFO_NAME);
+      h->sbuf.info[nrinfos++] = htobe16 (NBD_INFO_DESCRIPTION);
+    }
     h->wbuf = &h->sbuf;
     h->wlen = sizeof h->sbuf.info[0] * nrinfos;
     SET_NEXT_STATE (%SEND_INFO);
diff --git a/lib/handle.c b/lib/handle.c
index 4f00c059..8713e184 100644
--- a/lib/handle.c
+++ b/lib/handle.c
@@ -64,6 +64,7 @@ nbd_create (void)
   h->unique = 1;
   h->tls_verify_peer = true;
   h->request_sr = true;
+  h->request_block_size = true;
   h->pread_initialize = true;

   h->uri_allow_transports = LIBNBD_ALLOW_TRANSPORT_MASK;
@@ -242,6 +243,19 @@ nbd_unlocked_get_export_name (struct nbd_handle *h)
   return copy;
 }

+int
+nbd_unlocked_set_request_block_size (struct nbd_handle *h, bool request)
+{
+  h->request_block_size = request;
+  return 0;
+}
+
+int
+nbd_unlocked_get_request_block_size (struct nbd_handle *h)
+{
+  return h->request_block_size;
+}
+
 int
 nbd_unlocked_set_full_info (struct nbd_handle *h, bool request)
 {
diff --git a/python/t/110-defaults.py b/python/t/110-defaults.py
index a4262dae..749c94f4 100644
--- a/python/t/110-defaults.py
+++ b/python/t/110-defaults.py
@@ -22,6 +22,7 @@ assert h.get_export_name() == ""
 assert h.get_full_info() is False
 assert h.get_tls() == nbd.TLS_DISABLE
 assert h.get_request_structured_replies() is True
+assert h.get_request_block_size() is True
 assert h.get_pread_initialize() is True
 assert h.get_handshake_flags() == nbd.HANDSHAKE_FLAG_MASK
 assert h.get_opt_mode() is False
diff --git a/python/t/120-set-non-defaults.py b/python/t/120-set-non-defaults.py
index e71c6ad0..61a4160c 100644
--- a/python/t/120-set-non-defaults.py
+++ b/python/t/120-set-non-defaults.py
@@ -33,6 +33,8 @@ if h.supports_tls():
     assert h.get_tls() == nbd.TLS_ALLOW
 h.set_request_structured_replies(False)
 assert h.get_request_structured_replies() is False
+h.set_request_block_size(False)
+assert h.get_request_block_size() is False
 h.set_pread_initialize(False)
 assert h.get_pread_initialize() is False
 try:
diff --git a/ocaml/tests/test_110_defaults.ml b/ocaml/tests/test_110_defaults.ml
index 04aa744a..0fdd42e4 100644
--- a/ocaml/tests/test_110_defaults.ml
+++ b/ocaml/tests/test_110_defaults.ml
@@ -28,6 +28,8 @@ let
       assert (tls = NBD.TLS.DISABLE);
       let sr = NBD.get_request_structured_replies nbd in
       assert (sr = true);
+      let bs = NBD.get_request_block_size nbd in
+      assert (bs = true);
       let init = NBD.get_pread_initialize nbd in
       assert (init = true);
       let flags = NBD.get_handshake_flags nbd in
diff --git a/ocaml/tests/test_120_set_non_defaults.ml b/ocaml/tests/test_120_set_non_defaults.ml
index f9498076..28e6b9fc 100644
--- a/ocaml/tests/test_120_set_non_defaults.ml
+++ b/ocaml/tests/test_120_set_non_defaults.ml
@@ -42,6 +42,9 @@ let
       NBD.set_request_structured_replies nbd false;
       let sr = NBD.get_request_structured_replies nbd in
       assert (sr = false);
+      NBD.set_request_block_size nbd false;
+      let bs = NBD.get_request_block_size nbd in
+      assert (bs = false);
       NBD.set_pread_initialize nbd false;
       let init = NBD.get_pread_initialize nbd in
       assert (init = false);
diff --git a/interop/Makefile.am b/interop/Makefile.am
index 56571660..27b1064a 100644
--- a/interop/Makefile.am
+++ b/interop/Makefile.am
@@ -1,5 +1,5 @@
 # nbd client library in userspace
-# Copyright (C) 2013-2021 Red Hat Inc.
+# 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
@@ -20,6 +20,7 @@ include $(top_srcdir)/subdir-rules.mk
 EXTRA_DIST = \
 	dirty-bitmap.sh \
 	interop-qemu-storage-daemon.sh \
+	interop-qemu-block-size.sh \
 	list-exports-nbd-config \
 	list-exports-test-dir/disk1 \
 	list-exports-test-dir/disk2 \
@@ -141,6 +142,7 @@ TESTS += \
 	socket-activation-qemu-nbd \
 	dirty-bitmap.sh \
 	structured-read.sh \
+	interop-qemu-block-size.sh \
 	$(NULL)

 interop_qemu_nbd_SOURCES = \
diff --git a/interop/interop-qemu-block-size.sh b/interop/interop-qemu-block-size.sh
new file mode 100755
index 00000000..4ce287fd
--- /dev/null
+++ b/interop/interop-qemu-block-size.sh
@@ -0,0 +1,80 @@
+#!/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 interop with qemu-nbd block sizes.
+
+source ../tests/functions.sh
+set -e
+set -x
+
+requires qemu-nbd --version
+requires nbdsh --version
+requires qemu-img --version
+requires truncate --version
+
+f="qemu-block-size.raw"
+sock=$(mktemp -u /tmp/interop-qemu.XXXXXX)
+rm -f $f $sock
+cleanup_fn rm -f $f $sock
+
+truncate --size=10M $f
+export sock
+fail=0
+
+# run_test FMT REQUEST EXP_INFO EXP_GO
+run_test() {
+    # No -t or -e, so qemu-nbd should exit once nbdsh disconnects
+    qemu-nbd -k $sock $1 $f &
+    pid=$!
+    $VG nbdsh -c - <<EOF || fail=1
+import os
+
+sock = os.environ["sock"]
+
+h.set_opt_mode(True)
+assert h.get_request_block_size()
+h.set_request_block_size($2)
+assert h.get_request_block_size() is $2
+h.connect_unix(sock)
+
+try:
+    h.opt_info()
+    assert h.get_block_size(nbd.SIZE_MINIMUM) == $3
+except nbd.Error:
+    assert $3 == 0
+
+h.opt_go()
+assert h.get_block_size(nbd.SIZE_MINIMUM) == $4
+
+h.shutdown()
+EOF
+    wait $pid || fail=1
+}
+
+# Without '-f raw', qemu-nbd forces sector granularity to prevent writing
+# to sector 0 from changing the disk type.  However, if the client does
+# not request block sizes, it reports a size then errors out for NBD_OPT_INFO,
+# while fudging size for NBD_OPT_GO.
+run_test '' True 512 512
+run_test '' False 0 1
+
+# With '-f raw', qemu-nbd always exposes byte-level granularity for files.
+run_test '-f raw' True 1 1
+run_test '-f raw' False 1 1
+
+exit $fail
diff --git a/golang/libnbd_110_defaults_test.go b/golang/libnbd_110_defaults_test.go
index ca7c1c41..f56c9656 100644
--- a/golang/libnbd_110_defaults_test.go
+++ b/golang/libnbd_110_defaults_test.go
@@ -59,6 +59,14 @@ func Test110Defaults(t *testing.T) {
 		t.Fatalf("unexpected structured replies state")
 	}

+	bs, err := h.GetRequestBlockSize()
+	if err != nil {
+		t.Fatalf("could not get block size state: %s", err)
+	}
+	if bs != true {
+		t.Fatalf("unexpected block size state")
+	}
+
 	init, err := h.GetPreadInitialize()
 	if err != nil {
 		t.Fatalf("could not get pread initialize state: %s", err)
diff --git a/golang/libnbd_120_set_non_defaults_test.go b/golang/libnbd_120_set_non_defaults_test.go
index 029f0db3..a4c411d0 100644
--- a/golang/libnbd_120_set_non_defaults_test.go
+++ b/golang/libnbd_120_set_non_defaults_test.go
@@ -1,5 +1,5 @@
 /* libnbd golang tests
- * Copyright (C) 2013-2021 Red Hat Inc.
+ * 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
@@ -93,6 +93,18 @@ func Test120SetNonDefaults(t *testing.T) {
 		t.Fatalf("unexpected structured replies state")
 	}

+	err = h.SetRequestBlockSize(false)
+	if err != nil {
+		t.Fatalf("could not set block size state: %s", err)
+	}
+	bs, err := h.GetRequestBlockSize()
+	if err != nil {
+		t.Fatalf("could not get block size state: %s", err)
+	}
+	if bs != false {
+		t.Fatalf("unexpected block size state")
+	}
+
 	err = h.SetPreadInitialize(false)
 	if err != nil {
 		t.Fatalf("could not set pread initialize state: %s", err)
-- 
2.35.1




More information about the Libguestfs mailing list