Add support for calling NBD_CMD_BLOCK_STATUS on the remote server when
it is supported. I could have parsed that the server's id response to
NBD_OPT_SET_META_CONTEXT is the same as what later appears in
NBD_REPLY_TYPE_BLOCK_STATUS, but didn't think it worth the effort as
long as we expect exactly one meta context.
Signed-off-by: Eric Blake <eblake(a)redhat.com>
---
plugins/nbd/nbd.c | 135 +++++++++++++++++++++++++++++++++++++++++++---
1 file changed, 129 insertions(+), 6 deletions(-)
diff --git a/plugins/nbd/nbd.c b/plugins/nbd/nbd.c
index 8eb8a31..9986d6c 100644
--- a/plugins/nbd/nbd.c
+++ b/plugins/nbd/nbd.c
@@ -130,6 +130,7 @@ struct transaction {
uint64_t offset;
uint32_t count;
uint32_t err;
+ struct nbdkit_extents *extents;
struct transaction *next;
};
@@ -140,6 +141,7 @@ struct handle {
int flags;
int64_t size;
bool structured;
+ bool extents;
pthread_t reader;
/* Prevents concurrent threads from interleaving writes to server */
@@ -286,7 +288,7 @@ nbd_request_raw (struct handle *h, uint16_t flags, uint16_t type,
static int
nbd_request_full (struct handle *h, uint16_t flags, uint16_t type,
uint64_t offset, uint32_t count, const void *req_buf,
- void *rep_buf)
+ void *rep_buf, struct nbdkit_extents *extents)
{
int err;
struct transaction *trans;
@@ -308,6 +310,7 @@ nbd_request_full (struct handle *h, uint16_t flags, uint16_t type,
trans->buf = rep_buf;
trans->count = rep_buf ? count : 0;
trans->offset = offset;
+ trans->extents = extents;
nbd_lock (h);
if (h->dead) {
nbd_unlock (h);
@@ -340,7 +343,7 @@ static int
nbd_request (struct handle *h, uint16_t flags, uint16_t type, uint64_t offset,
uint32_t count)
{
- return nbd_request_full (h, flags, type, offset, count, NULL, NULL);
+ return nbd_request_full (h, flags, type, offset, count, NULL, NULL, NULL);
}
/* Read a reply, and look up the fd corresponding to the transaction.
@@ -358,6 +361,12 @@ nbd_reply_raw (struct handle *h, int *fd)
struct transaction *trans;
void *buf = NULL;
uint32_t count;
+ uint32_t id;
+ struct {
+ uint32_t length;
+ uint32_t flags;
+ } *extents = NULL;
+ size_t nextents = 0;
int error = NBD_SUCCESS;
bool more = false;
uint64_t offset = 0; /* absolute offset of structured read chunk from buf */
@@ -452,6 +461,34 @@ nbd_reply_raw (struct handle *h, int *fd)
}
zero = true;
break;
+ case NBD_REPLY_TYPE_BLOCK_STATUS:
+ if (!h->extents) {
+ nbdkit_error ("block status response without negotiation");
+ free (buf);
+ return nbd_mark_dead (h);
+ }
+ if (rep.structured.length < sizeof *extents ||
+ rep.structured.length % sizeof *extents != sizeof id) {
+ nbdkit_error ("structured reply OFFSET_HOLE size incorrect");
+ free (buf);
+ return nbd_mark_dead (h);
+ }
+ nextents = rep.structured.length / sizeof *extents;
+ extents = malloc (rep.structured.length - sizeof id);
+ if (!extents) {
+ nbdkit_error ("malloc: %m");
+ return nbd_mark_dead (h);
+ }
+ memcpy (&id, buf, sizeof id);
+ id = be32toh (id);
+ nbdkit_debug ("parsing %zu extents for context id %" PRId32,
+ nextents, id);
+ memcpy (extents, (char *) buf + sizeof id, sizeof *extents * nextents);
+ for (size_t i = 0; i < nextents; i++) {
+ extents[i].length = be32toh (extents[i].length);
+ extents[i].flags = be32toh (extents[i].flags);
+ }
+ break;
default:
if (NBD_REPLY_TYPE_IS_ERR (rep.structured.type)) {
uint16_t errlen;
@@ -497,9 +534,29 @@ nbd_reply_raw (struct handle *h, int *fd)
trans = find_trans_by_cookie (h, rep.simple.handle, !more);
if (!trans) {
nbdkit_error ("reply with unexpected cookie %#" PRIx64,
rep.simple.handle);
+ free (extents);
return nbd_mark_dead (h);
}
+ if (nextents) {
+ if (!trans->extents) {
+ nbdkit_error ("block status response to a non-status command");
+ free (extents);
+ return nbd_mark_dead (h);
+ }
+ offset = trans->offset;
+ for (size_t i = 0; i < nextents; i++) {
+ /* We rely on the fact that NBDKIT_EXTENT_* match NBD_STATE_* */
+ if (nbdkit_add_extent (trans->extents, offset, extents[i].length,
+ extents[i].flags) == -1) {
+ error = EINVAL;
+ break;
+ }
+ offset += extents[i].length;
+ }
+ free (extents);
+ }
+
if (!more) {
*fd = trans->u.fds[1];
if (!error)
@@ -686,8 +743,11 @@ nbd_newstyle_recv_option_reply (struct handle *h, uint32_t option,
static int
nbd_newstyle_haggle (struct handle *h)
{
+ const char *const query = "base:allocation";
struct new_option opt;
uint32_t exportnamelen = htobe32 (strlen (export));
+ uint32_t nrqueries = htobe32 (1);
+ uint32_t querylen = htobe32 (strlen (query));
/* For now, we make no NBD_INFO_* requests, relying on the server to
send its defaults. TODO: nbdkit should let plugins report block
sizes, at which point we should request NBD_INFO_BLOCK_SIZE and
@@ -710,9 +770,47 @@ nbd_newstyle_haggle (struct handle *h)
NULL) < 0)
return -1;
if (reply.reply == NBD_REP_ACK) {
- nbdkit_debug ("structured replies enabled");
+ nbdkit_debug ("structured replies enabled, trying
NBD_OPT_SET_META_CONTEXT");
h->structured = true;
- /* TODO: block status */
+
+ opt.version = htobe64 (NEW_VERSION);
+ opt.option = htobe32 (NBD_OPT_SET_META_CONTEXT);
+ opt.optlen = htobe32 (sizeof exportnamelen + strlen (export) +
+ sizeof nrqueries + sizeof querylen + strlen (query));
+ if (write_full (h->fd, &opt, sizeof opt) ||
+ write_full (h->fd, &exportnamelen, sizeof exportnamelen) ||
+ write_full (h->fd, export, strlen (export)) ||
+ write_full (h->fd, &nrqueries, sizeof nrqueries) ||
+ write_full (h->fd, &querylen, sizeof querylen) ||
+ write_full (h->fd, query, strlen (query))) {
+ nbdkit_error ("unable to request NBD_OPT_SET_META_CONTEXT: %m");
+ return -1;
+ }
+ if (nbd_newstyle_recv_option_reply (h, NBD_OPT_SET_META_CONTEXT, &reply,
+ NULL) < 0)
+ return -1;
+ if (reply.reply == NBD_REP_META_CONTEXT) {
+ /* Cheat: we asked for exactly one context. We could double
+ check that the server is replying with exactly the
+ "base:allocation" context, and then remember the id it tells
+ us to later confirm that responses to NBD_CMD_BLOCK_STATUS
+ match up; but in the absence of multiple contexts, it's
+ easier to just assume the server is compliant, and will reuse
+ the same id, without bothering to check further. */
+ nbdkit_debug ("extents enabled");
+ h->extents = true;
+ if (nbd_newstyle_recv_option_reply (h, NBD_OPT_SET_META_CONTEXT, &reply,
+ NULL) < 0)
+ return -1;
+ }
+ if (reply.reply != NBD_REP_ACK) {
+ if (h->extents) {
+ nbdkit_error ("unexpected response to set meta context");
+ return -1;
+ }
+ nbdkit_debug ("ignoring meta context response %s",
+ name_of_nbd_rep (reply.reply));
+ }
}
else {
nbdkit_debug ("structured replies disabled");
@@ -1008,6 +1106,14 @@ nbd_can_multi_conn (void *handle)
return h->flags & NBD_FLAG_CAN_MULTI_CONN;
}
+static int
+nbd_can_extents (void *handle)
+{
+ struct handle *h = handle;
+
+ return h->extents;
+}
+
/* Read data from the file. */
static int
nbd_pread (void *handle, void *buf, uint32_t count, uint64_t offset,
@@ -1024,7 +1130,7 @@ nbd_pread (void *handle, void *buf, uint32_t count, uint64_t
offset,
when we register our plugin? */
if (h->structured)
memset (buf, 0, count);
- c = nbd_request_full (h, 0, NBD_CMD_READ, offset, count, NULL, buf);
+ c = nbd_request_full (h, 0, NBD_CMD_READ, offset, count, NULL, buf, NULL);
return c < 0 ? c : nbd_reply (h, c);
}
@@ -1038,7 +1144,7 @@ nbd_pwrite (void *handle, const void *buf, uint32_t count, uint64_t
offset,
assert (!(flags & ~NBDKIT_FLAG_FUA));
c = nbd_request_full (h, flags & NBDKIT_FLAG_FUA ? NBD_CMD_FLAG_FUA : 0,
- NBD_CMD_WRITE, offset, count, buf, NULL);
+ NBD_CMD_WRITE, offset, count, buf, NULL, NULL);
return c < 0 ? c : nbd_reply (h, c);
}
@@ -1086,6 +1192,21 @@ nbd_flush (void *handle, uint32_t flags)
return c < 0 ? c : nbd_reply (h, c);
}
+/* Read extents of the file. */
+static int
+nbd_extents (void *handle, uint32_t count, uint64_t offset,
+ uint32_t flags, struct nbdkit_extents *extents)
+{
+ struct handle *h = handle;
+ int c;
+
+ assert (!(flags & ~NBDKIT_FLAG_REQ_ONE) && h->extents);
+ c = nbd_request_full (h, flags & NBDKIT_FLAG_REQ_ONE ? NBD_CMD_FLAG_REQ_ONE : 0,
+ NBD_CMD_BLOCK_STATUS, offset, count, NULL, NULL,
+ extents);
+ return c < 0 ? c : nbd_reply (h, c);
+}
+
static struct nbdkit_plugin plugin = {
.name = "nbd",
.longname = "nbdkit nbd plugin",
@@ -1104,11 +1225,13 @@ static struct nbdkit_plugin plugin = {
.can_zero = nbd_can_zero,
.can_fua = nbd_can_fua,
.can_multi_conn = nbd_can_multi_conn,
+ .can_extents = nbd_can_extents,
.pread = nbd_pread,
.pwrite = nbd_pwrite,
.zero = nbd_zero,
.flush = nbd_flush,
.trim = nbd_trim,
+ .extents = nbd_extents,
.errno_is_preserved = 1,
};
--
2.20.1