The NBD protocol is adding an extension for servers to report the
initial state of the image (for now, whether it is sparse and/or reads
as all zeroes). Time to expose this to clients, via the new API
nbd_get_init_flags(). The patch requires refreshing the
nbd-protocol.h file from a corresponding contemporary nbdkit commit.
Testing is possible with recent enough qemu.
---
.gitignore | 1 +
generator/generator | 40 +++++++++++++--
generator/states-newstyle-opt-go.c | 12 ++++-
interop/Makefile.am | 9 +++-
interop/init-zero.c | 78 ++++++++++++++++++++++++++++++
interop/init-zero.sh | 56 +++++++++++++++++++++
lib/flags.c | 14 +++++-
lib/internal.h | 10 ++--
lib/nbd-protocol.h | 13 ++++-
9 files changed, 220 insertions(+), 13 deletions(-)
create mode 100644 interop/init-zero.c
create mode 100755 interop/init-zero.sh
diff --git a/.gitignore b/.gitignore
index 7536021..e356392 100644
--- a/.gitignore
+++ b/.gitignore
@@ -70,6 +70,7 @@ Makefile.in
/html/*.?.html
/include/libnbd.h
/interop/dirty-bitmap
+/interop/init-zero
/interop/interop-nbdkit
/interop/interop-nbdkit-tls-certs
/interop/interop-nbdkit-tls-certs-allow-enabled
diff --git a/generator/generator b/generator/generator
index dea9bf7..0addc9d 100755
--- a/generator/generator
+++ b/generator/generator
@@ -979,7 +979,7 @@ let tls_enum = {
}
let all_enums = [ tls_enum ]
-(* Flags. *)
+(* Flags. See also Constants below. *)
let cmd_flags = {
flag_prefix = "CMD_FLAG";
flags = [
@@ -1969,7 +1969,8 @@ are free to pass in other contexts."
^ non_blocking_test_call_description;
see_also = [SectionLink "Flag calls";
Link "add_meta_context";
- Link "block_status"; Link "aio_block_status"];
+ Link "block_status"; Link "aio_block_status";
+ Link "get_init_flags"];
};
"get_protocol", {
@@ -1990,6 +1991,30 @@ Most modern NBD servers use C<\"newstyle-fixed\">.
Link "get_tls_negotiated"];
};
+ "get_init_flags", {
+ default_call with
+ args = []; ret = RInt;
+ permitted_states = [ Connected; Closed ];
+ shortdesc = "return any init state flags advertised by the server";
+ longdesc = "\
+Return the bitwise-OR of any initial state flags advertised by the
+server. These flags may include C<LIBNBD_INIT_SPARSE> if the export
+is not fully allocated, or C<LIBNBD_INIT_ZERO> if the export is known
+to start with all contents reading as zeroes. Other flags might be
+added in the future.
+
+Note that the flags advertised by the server refer only to the point
+in time when the connection was established; libnbd does not track if
+the results may have been rendered stale by intervening actions such
+as write requests. Also, since both initial state bits and block
+status are orthogonal extensions within the NBD protocol, there is no
+requirement that a server's advertisement match what could be learned
+by checking block status.
+"
+^ non_blocking_test_call_description;
+ see_also = [Link "get_handshake_flags"; Link "block_status"];
+ };
+
"get_size", {
default_call with
args = []; ret = RInt64;
@@ -2292,7 +2317,7 @@ return only one extent per metadata context where that extent
does not exceed C<count> bytes; however, libnbd does not
validate that the server obeyed the flag.";
see_also = [Link "add_meta_context"; Link "can_meta_context";
- Link "aio_block_status"];
+ Link "aio_block_status"; Link "get_init_flags"];
};
"poll", {
@@ -2619,7 +2644,8 @@ as described in L<libnbd(3)/Completion callbacks>.
Other parameters behave as documented in L<nbd_block_status(3)>.";
see_also = [SectionLink "Issuing asynchronous commands";
- Link "can_meta_context"; Link "block_status"];
+ Link "can_meta_context"; Link "block_status";
+ Link "get_init_flags"];
};
"aio_get_fd", {
@@ -3031,6 +3057,7 @@ let first_version = [
"set_uri_allow_transports", (1, 2);
"set_uri_allow_tls", (1, 2);
"set_uri_allow_local_file", (1, 2);
+ "get_init_flags", (1, 2);
(* These calls are proposed for a future version of libnbd, but
* have not been added to any released version so far.
@@ -3039,7 +3066,7 @@ let first_version = [
*)
]
-(* Constants, etc. *)
+(* Constants, etc. See also Enums and Flags above. *)
let constants = [
"AIO_DIRECTION_READ", 1;
"AIO_DIRECTION_WRITE", 2;
@@ -3048,6 +3075,9 @@ let constants = [
"READ_DATA", 1;
"READ_HOLE", 2;
"READ_ERROR", 3;
+
+ "INIT_SPARSE", 1 lsl 0;
+ "INIT_ZERO", 1 lsl 1;
]
let metadata_namespaces = [
diff --git a/generator/states-newstyle-opt-go.c b/generator/states-newstyle-opt-go.c
index 57ad55a..c2ef01e 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-2019 Red Hat Inc.
+ * Copyright (C) 2013-2020 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
@@ -59,6 +59,8 @@ STATE_MACHINE {
switch (send_from_wbuf (h)) {
case -1: SET_NEXT_STATE (%.DEAD); return 0;
case 0:
+ /* For now, we don't allow the client to configure which infos to
+ * request, but instead rely on what the server volunteers. */
h->sbuf.nrinfos = 0;
h->wbuf = &h->sbuf;
h->wlen = 2;
@@ -130,6 +132,14 @@ STATE_MACHINE {
return 0;
}
break;
+ case NBD_INFO_INIT_STATE:
+ if (len != sizeof h->sbuf.or.payload.init) {
+ SET_NEXT_STATE (%.DEAD);
+ set_error (0, "handshake: incorrect NBD_INFO_INIT_STATE option reply
length");
+ return 0;
+ }
+ h->initflags = be16toh (h->sbuf.or.payload.init.flags);
+ break;
default:
/* XXX Handle other info types, like NBD_INFO_BLOCK_SIZE */
debug (h, "skipping unknown NBD_REP_INFO type %d",
diff --git a/interop/Makefile.am b/interop/Makefile.am
index 345be7c..ed5b553 100644
--- a/interop/Makefile.am
+++ b/interop/Makefile.am
@@ -1,5 +1,5 @@
# nbd client library in userspace
-# Copyright (C) 2013-2019 Red Hat Inc.
+# Copyright (C) 2013-2020 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
@@ -50,12 +50,14 @@ check_PROGRAMS += \
dirty-bitmap \
socket-activation-qemu-nbd \
structured-read \
+ init-zero \
$(NULL)
TESTS += \
interop-qemu-nbd \
dirty-bitmap.sh \
socket-activation-qemu-nbd \
structured-read.sh \
+ init-zero.sh \
$(NULL)
# tls tests assume the pre-existence of files created in ../tests/Makefile.am,
@@ -140,6 +142,11 @@ structured_read_CPPFLAGS = -I$(top_srcdir)/include
structured_read_CFLAGS = $(WARNINGS_CFLAGS)
structured_read_LDADD = $(top_builddir)/lib/libnbd.la
+init_zero_SOURCES = init-zero.c
+init_zero_CPPFLAGS = -I$(top_srcdir)/include
+init_zero_CFLAGS = $(WARNINGS_CFLAGS)
+init_zero_LDADD = $(top_builddir)/lib/libnbd.la
+
endif HAVE_QEMU_NBD
if HAVE_NBDKIT
diff --git a/interop/init-zero.c b/interop/init-zero.c
new file mode 100644
index 0000000..0cf1d87
--- /dev/null
+++ b/interop/init-zero.c
@@ -0,0 +1,78 @@
+/* NBD client library in userspace
+ * Copyright (C) 2013-2020 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 init state queries. */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <assert.h>
+#include <stdbool.h>
+#include <errno.h>
+
+#include <libnbd.h>
+
+/* Depends on init-zero.sh setting things up so that we are given a
+ * socket to a server advertising NBD_INIT_ZERO.
+ */
+
+int
+main (int argc, char *argv[])
+{
+ struct nbd_handle *nbd;
+ int r;
+
+ if (argc < 2) {
+ fprintf (stderr, "%s socket\n", argv[0]);
+ exit (EXIT_FAILURE);
+ }
+
+ nbd = nbd_create ();
+ if (nbd == NULL) {
+ fprintf (stderr, "%s\n", nbd_get_error ());
+ exit (EXIT_FAILURE);
+ }
+
+ if (nbd_connect_unix (nbd, argv[1]) == -1) {
+ fprintf (stderr, "%s\n", nbd_get_error ());
+ exit (EXIT_FAILURE);
+ }
+
+ r = nbd_get_init_flags (nbd);
+ if (r == -1) {
+ fprintf (stderr, "%s\n", nbd_get_error ());
+ exit (EXIT_FAILURE);
+ }
+
+ if (!(r & LIBNBD_INIT_ZERO)) {
+ fprintf (stderr, "Missing expected zero init state\n");
+ exit (EXIT_FAILURE);
+ }
+
+ if (nbd_shutdown (nbd, 0) == -1) {
+ fprintf (stderr, "%s\n", nbd_get_error ());
+ exit (EXIT_FAILURE);
+ }
+
+ nbd_close (nbd);
+
+ exit (EXIT_SUCCESS);
+}
diff --git a/interop/init-zero.sh b/interop/init-zero.sh
new file mode 100755
index 0000000..349f55e
--- /dev/null
+++ b/interop/init-zero.sh
@@ -0,0 +1,56 @@
+#!/usr/bin/env bash
+# nbd client library in userspace
+# Copyright (C) 2019-2020 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 structured read callbacks.
+
+source ../tests/functions.sh
+set -e
+set -x
+
+requires qemu-img --version
+requires qemu-nbd --version
+
+sock=`mktemp -u`
+files="init-zero.qcow2 $sock"
+rm -f $files
+cleanup_fn rm -f $files
+
+# qemu 5.0 added ability to track if a qcow2 image is all zeroes
+# Probe to see if qemu-nbd exposes that feature, before running test
+qemu-img create -f qcow2 init-zero.qcow2 64k
+qemu-nbd -f qcow2 -k $sock init-zero.qcow2&
+cleanup_fn kill $!
+tries=0
+while test ! -e $sock && test $tries -lt 5; do
+ sleep 1
+ tries=$((tries + 1))
+done
+
+case $(qemu-nbd --list -k $sock) in
+ *'init state'*zero*)
+ # Run the test.
+ $VG ./init-zero $sock
+ status=$?
+ ;;
+ *)
+ echo "$0: skipping because qemu-nbd does not support all-zero bit"
+ status=77
+ ;;
+esac
+
+exit $status
diff --git a/lib/flags.c b/lib/flags.c
index d55d10a..62c3252 100644
--- a/lib/flags.c
+++ b/lib/flags.c
@@ -1,5 +1,5 @@
/* NBD client library in userspace
- * Copyright (C) 2013-2019 Red Hat Inc.
+ * Copyright (C) 2013-2020 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
@@ -143,6 +143,18 @@ nbd_unlocked_can_meta_context (struct nbd_handle *h, const char
*name)
return 0;
}
+int
+nbd_unlocked_get_init_flags (struct nbd_handle *h)
+{
+ if (h->eflags == 0) {
+ set_error (EINVAL, "server has not returned init flags, "
+ "you need to connect to the server first");
+ return -1;
+ }
+
+ return h->initflags;
+}
+
int64_t
nbd_unlocked_get_size (struct nbd_handle *h)
{
diff --git a/lib/internal.h b/lib/internal.h
index 6eb50d8..aa555b5 100644
--- a/lib/internal.h
+++ b/lib/internal.h
@@ -1,5 +1,5 @@
/* nbd client library in userspace: internal definitions
- * Copyright (C) 2013-2019 Red Hat Inc.
+ * Copyright (C) 2013-2020 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
@@ -97,12 +97,13 @@ struct nbd_handle {
uint16_t gflags;
/* Export size and per-export flags, received during handshake. NB:
- * These are *both* *only* valid if eflags != 0. This is because
- * all servers should set NBD_FLAG_HAS_FLAGS, so eflags should
- * always be != 0, and we set both fields at the same time.
+ * These are *only* valid if eflags != 0. This is because all
+ * servers should set NBD_FLAG_HAS_FLAGS, so eflags should always be
+ * != 0, and we set the other fields at the same time.
*/
uint64_t exportsize;
uint16_t eflags;
+ uint16_t initflags;
/* Flags set by the state machine to tell what protocol and whether
* TLS was negotiated.
@@ -159,6 +160,7 @@ struct nbd_handle {
struct nbd_fixed_new_option_reply option_reply;
union {
struct nbd_fixed_new_option_reply_info_export export;
+ struct nbd_fixed_new_option_reply_info_init init;
struct {
struct nbd_fixed_new_option_reply_meta_context context;
char str[NBD_MAX_STRING];
diff --git a/lib/nbd-protocol.h b/lib/nbd-protocol.h
index df0b4c6..50531a0 100644
--- a/lib/nbd-protocol.h
+++ b/lib/nbd-protocol.h
@@ -1,5 +1,5 @@
/* nbdkit
- * Copyright (C) 2013-2019 Red Hat Inc.
+ * Copyright (C) 2013-2020 Red Hat Inc.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
@@ -146,6 +146,7 @@ struct nbd_fixed_new_option_reply {
#define NBD_INFO_NAME 1
#define NBD_INFO_DESCRIPTION 2
#define NBD_INFO_BLOCK_SIZE 3
+#define NBD_INFO_INIT_STATE 4
/* NBD_INFO_EXPORT reply (follows fixed_new_option_reply). */
struct nbd_fixed_new_option_reply_info_export {
@@ -154,6 +155,16 @@ struct nbd_fixed_new_option_reply_info_export {
uint16_t eflags; /* per-export flags */
} NBD_ATTRIBUTE_PACKED;
+/* NBD_INFO_INIT_STATE reply. */
+struct nbd_fixed_new_option_reply_info_init {
+ uint16_t info; /* NBD_INFO_INIT_STATE */
+ uint16_t flags; /* per-export init flags */
+} NBD_ATTRIBUTE_PACKED;
+
+/* Constants for use in reply to NBD_INFO_INIT_STATE. */
+#define NBD_INIT_SPARSE (1 << 0)
+#define NBD_INIT_ZERO (1 << 1)
+
/* NBD_REP_META_CONTEXT reply (follows fixed_new_option_reply). */
struct nbd_fixed_new_option_reply_meta_context {
uint32_t context_id; /* metadata context ID */
--
2.24.1