The NBD protocol is adding an extension to let servers advertise
initialization state to the client: whether the image contains holes,
and whether it is known to read as all zeroes. This patch wires up
the backend to send the data, although it won't be until the next
couple of patches allow filters and plugins to report through new
callbacks that a client finally gets to see new bits set.
Signed-off-by: Eric Blake <eblake(a)redhat.com>
---
server/backend.c | 26 +++++++++++++
server/filters.c | 18 ++++++++-
server/internal.h | 12 +++++-
server/plugins.c | 18 ++++++++-
server/protocol-handshake-newstyle.c | 56 ++++++++++++++++++++++++++--
5 files changed, 123 insertions(+), 7 deletions(-)
diff --git a/server/backend.c b/server/backend.c
index 208c07b..3ebe1f4 100644
--- a/server/backend.c
+++ b/server/backend.c
@@ -456,6 +456,32 @@ backend_can_cache (struct backend *b, struct connection *conn)
return h->can_cache;
}
+int
+backend_init_sparse (struct backend *b, struct connection *conn)
+{
+ struct b_conn_handle *h = &conn->handles[b->i];
+
+ controlpath_debug ("%s: init_sparse", b->name);
+
+ assert (h->handle && (h->state & HANDLE_CONNECTED));
+ if (h->init_sparse == -1)
+ h->init_sparse = b->init_sparse (b, conn, h->handle);
+ return h->init_sparse;
+}
+
+int
+backend_init_zero (struct backend *b, struct connection *conn)
+{
+ struct b_conn_handle *h = &conn->handles[b->i];
+
+ controlpath_debug ("%s: init_zero", b->name);
+
+ assert (h->handle && (h->state & HANDLE_CONNECTED));
+ if (h->init_zero == -1)
+ h->init_zero = b->init_zero (b, conn, h->handle);
+ return h->init_zero;
+}
+
int
backend_pread (struct backend *b, struct connection *conn,
void *buf, uint32_t count, uint64_t offset,
diff --git a/server/filters.c b/server/filters.c
index ed026f5..6b9d0b8 100644
--- a/server/filters.c
+++ b/server/filters.c
@@ -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
@@ -574,6 +574,20 @@ filter_can_cache (struct backend *b, struct connection *conn, void
*handle)
return backend_can_cache (b->next, conn);
}
+static int
+filter_init_sparse (struct backend *b, struct connection *conn, void *handle)
+{
+ /* TODO Allow filter to control this. */
+ return 0;
+}
+
+static int
+filter_init_zero (struct backend *b, struct connection *conn, void *handle)
+{
+ /* TODO Allow filter to control this. */
+ return 0;
+}
+
static int
filter_pread (struct backend *b, struct connection *conn, void *handle,
void *buf, uint32_t count, uint64_t offset,
@@ -705,6 +719,8 @@ static struct backend filter_functions = {
.can_fua = filter_can_fua,
.can_multi_conn = filter_can_multi_conn,
.can_cache = filter_can_cache,
+ .init_sparse = filter_init_sparse,
+ .init_zero = filter_init_zero,
.pread = filter_pread,
.pwrite = filter_pwrite,
.flush = filter_flush,
diff --git a/server/internal.h b/server/internal.h
index a1fa730..435c0b4 100644
--- a/server/internal.h
+++ b/server/internal.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
@@ -186,6 +186,8 @@ struct b_conn_handle {
int can_multi_conn;
int can_extents;
int can_cache;
+ int init_sparse;
+ int init_zero;
};
static inline void
@@ -204,6 +206,8 @@ reset_b_conn_handle (struct b_conn_handle *h)
h->can_multi_conn = -1;
h->can_extents = -1;
h->can_cache = -1;
+ h->init_sparse = -1;
+ h->init_zero = -1;
}
struct connection {
@@ -353,6 +357,8 @@ struct backend {
int (*can_multi_conn) (struct backend *, struct connection *conn,
void *handle);
int (*can_cache) (struct backend *, struct connection *conn, void *handle);
+ int (*init_sparse) (struct backend *, struct connection *conn, void *handle);
+ int (*init_zero) (struct backend *, struct connection *conn, void *handle);
int (*pread) (struct backend *, struct connection *conn, void *handle,
void *buf, uint32_t count, uint64_t offset,
@@ -420,6 +426,10 @@ extern int backend_can_multi_conn (struct backend *b, struct
connection *conn)
__attribute__((__nonnull__ (1, 2)));
extern int backend_can_cache (struct backend *b, struct connection *conn)
__attribute__((__nonnull__ (1, 2)));
+extern int backend_init_sparse (struct backend *b, struct connection *conn)
+ __attribute__((__nonnull__ (1, 2)));
+extern int backend_init_zero (struct backend *b, struct connection *conn)
+ __attribute__((__nonnull__ (1, 2)));
extern int backend_pread (struct backend *b, struct connection *conn,
void *buf, uint32_t count, uint64_t offset,
diff --git a/server/plugins.c b/server/plugins.c
index 79d98b8..9b98cc6 100644
--- a/server/plugins.c
+++ b/server/plugins.c
@@ -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
@@ -432,6 +432,20 @@ plugin_can_cache (struct backend *b, struct connection *conn, void
*handle)
return NBDKIT_CACHE_NONE;
}
+static int
+plugin_init_sparse (struct backend *b, struct connection *conn, void *handle)
+{
+ /* TODO Allow plugin to control this. */
+ return 0;
+}
+
+static int
+plugin_init_zero (struct backend *b, struct connection *conn, void *handle)
+{
+ /* TODO Allow plugin to control this. */
+ return 0;
+}
+
/* Plugins and filters can call this to set the true errno, in cases
* where !errno_is_preserved.
*/
@@ -686,6 +700,8 @@ static struct backend plugin_functions = {
.can_fua = plugin_can_fua,
.can_multi_conn = plugin_can_multi_conn,
.can_cache = plugin_can_cache,
+ .init_sparse = plugin_init_sparse,
+ .init_zero = plugin_init_zero,
.pread = plugin_pread,
.pwrite = plugin_pwrite,
.flush = plugin_flush,
diff --git a/server/protocol-handshake-newstyle.c b/server/protocol-handshake-newstyle.c
index 7179186..5ba358c 100644
--- a/server/protocol-handshake-newstyle.c
+++ b/server/protocol-handshake-newstyle.c
@@ -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
@@ -138,6 +138,45 @@ send_newstyle_option_reply_info_export (struct connection *conn,
return 0;
}
+static int
+send_newstyle_option_reply_info_init_state (struct connection *conn,
+ uint32_t option, uint32_t reply)
+{
+ struct nbd_fixed_new_option_reply fixed_new_option_reply;
+ struct nbd_fixed_new_option_reply_info_init init;
+ uint16_t flags = 0;
+ int r;
+
+ r = backend_init_sparse (backend, conn);
+ if (r == -1)
+ return r;
+ if (r)
+ flags |= NBD_INIT_SPARSE;
+
+ r = backend_init_zero (backend, conn);
+ if (r == -1)
+ return r;
+ if (r)
+ flags |= NBD_INIT_ZERO;
+
+ fixed_new_option_reply.magic = htobe64 (NBD_REP_MAGIC);
+ fixed_new_option_reply.option = htobe32 (option);
+ fixed_new_option_reply.reply = htobe32 (reply);
+ fixed_new_option_reply.replylen = htobe32 (sizeof init);
+ init.info = htobe16 (NBD_INFO_INIT_STATE);
+ init.flags = htobe16 (flags);
+
+ if (conn->send (conn,
+ &fixed_new_option_reply,
+ sizeof fixed_new_option_reply, SEND_MORE) == -1 ||
+ conn->send (conn, &init, sizeof init, 0) == -1) {
+ nbdkit_error ("write: %s: %m", name_of_nbd_opt (option));
+ return -1;
+ }
+
+ return 0;
+}
+
static int
send_newstyle_option_reply_meta_context (struct connection *conn,
uint32_t option, uint32_t reply,
@@ -496,16 +535,25 @@ negotiate_handshake_newstyle_options (struct connection *conn)
exportsize) == -1)
return -1;
+ /* The spec states that we may offer gratuitous replies,
+ * whether or not the client has requested them. Offering
+ * NBD_INFO_INIT_STATE unconditionally is easy to do.
+ */
+ if (send_newstyle_option_reply_info_init_state (conn, option,
+ NBD_REP_INFO) == -1)
+ return -1;
+
/* For now we ignore all other info requests (but we must
- * ignore NBD_INFO_EXPORT if it was requested, because we
- * replied already above). Therefore this loop doesn't do
- * much at the moment.
+ * ignore NBD_INFO_EXPORT or NBD_INFO_INIT_STATE if either was
+ * requested, because we replied already above). Therefore this
+ * loop doesn't do much at the moment.
*/
for (i = 0; i < nrinfos; ++i) {
memcpy (&info, &data[4 + exportnamelen + 2 + i*2], 2);
info = be16toh (info);
switch (info) {
case NBD_INFO_EXPORT: /* ignore - reply sent above */ break;
+ case NBD_INFO_INIT_STATE: /* ignore - reply sent above */ break;
default:
debug ("newstyle negotiation: %s: "
"ignoring NBD_INFO_* request %u (%s)",
--
2.24.1