Eric Blake
2021-Dec-03 23:17 UTC
[Libguestfs] [libnbd PATCH 00/13] libnbd patches for NBD_OPT_EXTENDED_HEADERS
Available here: https://repo.or.cz/libnbd/ericb.git/shortlog/refs/tags/exthdr-v1 I also want to do followup patches to teach 'nbdinfo --map' and 'nbdcopy' to utilize 64-bit extents. Eric Blake (13): golang: Simplify nbd_block_status callback array copy block_status: Refactor array storage protocol: Add definitions for extended headers protocol: Prepare to send 64-bit requests protocol: Prepare to receive 64-bit replies protocol: Accept 64-bit holes during pread generator: Add struct nbd_extent in prep for 64-bit extents block_status: Track 64-bit extents internally block_status: Accept 64-bit extents during block status api: Add [aio_]nbd_block_status_64 api: Add three functions for controlling extended headers generator: Actually request extended headers interop: Add test of 64-bit block status lib/internal.h | 31 ++- lib/nbd-protocol.h | 61 ++++- generator/API.ml | 237 ++++++++++++++++-- generator/API.mli | 3 +- generator/C.ml | 24 +- generator/GoLang.ml | 35 ++- generator/Makefile.am | 3 +- generator/OCaml.ml | 20 +- generator/Python.ml | 29 ++- generator/state_machine.ml | 52 +++- generator/states-issue-command.c | 31 ++- .../states-newstyle-opt-extended-headers.c | 90 +++++++ generator/states-newstyle-opt-starttls.c | 10 +- generator/states-reply-structured.c | 220 ++++++++++++---- generator/states-reply.c | 31 ++- lib/handle.c | 27 +- lib/rw.c | 105 +++++++- python/t/110-defaults.py | 3 +- python/t/120-set-non-defaults.py | 4 +- python/t/465-block-status-64.py | 56 +++++ ocaml/helpers.c | 22 +- ocaml/nbd-c.h | 3 +- ocaml/tests/Makefile.am | 5 +- ocaml/tests/test_110_defaults.ml | 4 +- ocaml/tests/test_120_set_non_defaults.ml | 5 +- ocaml/tests/test_465_block_status_64.ml | 58 +++++ tests/meta-base-allocation.c | 111 +++++++- interop/Makefile.am | 6 + interop/large-status.c | 186 ++++++++++++++ interop/large-status.sh | 49 ++++ .gitignore | 1 + golang/Makefile.am | 3 +- golang/handle.go | 6 + golang/libnbd_110_defaults_test.go | 8 + golang/libnbd_120_set_non_defaults_test.go | 12 + golang/libnbd_465_block_status_64_test.go | 119 +++++++++ 36 files changed, 1511 insertions(+), 159 deletions(-) create mode 100644 generator/states-newstyle-opt-extended-headers.c create mode 100644 python/t/465-block-status-64.py create mode 100644 ocaml/tests/test_465_block_status_64.ml create mode 100644 interop/large-status.c create mode 100755 interop/large-status.sh create mode 100644 golang/libnbd_465_block_status_64_test.go -- 2.33.1
Eric Blake
2021-Dec-03 23:17 UTC
[Libguestfs] [libnbd PATCH 01/13] golang: Simplify nbd_block_status callback array copy
In the block status callback glue code, we need to copy a C uint32_t[]
into a golang []uint32. The copy is necessary since the lifetime of
the C array is not guaranteed to outlive whatever the Go callback may
have done with what it was handed; copying ensures that the user's Go
code doesn't have to worry about lifetime issues. But we don't have
to have quite so many casts and pointer additions: since we can assume
C.uint32_t and uint32 occupy the same amount of memory (even though
they are different types), we can exploit Go's ability to treat an
unsafe pointer as if it were an oversized array, take a slice of that
array, and then use idiomatic Go to copy from the slice.
https://github.com/golang/go/wiki/cgo#turning-c-arrays-into-go-slices
---
generator/GoLang.ml | 15 +++++++++------
1 file changed, 9 insertions(+), 6 deletions(-)
diff --git a/generator/GoLang.ml b/generator/GoLang.ml
index eb3aa263..d3b7dc79 100644
--- a/generator/GoLang.ml
+++ b/generator/GoLang.ml
@@ -1,6 +1,6 @@
(* hey emacs, this is OCaml code: -*- tuareg -*- *)
(* nbd client library in userspace: generator
- * Copyright (C) 2013-2020 Red Hat Inc.
+ * Copyright (C) 2013-2021 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
@@ -514,11 +514,14 @@ let
/* Closures. */
func copy_uint32_array (entries *C.uint32_t, count C.size_t) []uint32 {
- ret := make([]uint32, int (count))
- for i := 0; i < int (count); i++ {
- entry := (*C.uint32_t) (unsafe.Pointer(uintptr(unsafe.Pointer(entries))
+ (unsafe.Sizeof(*entries) * uintptr(i))))
- ret[i] = uint32 (*entry)
- }
+ /* https://github.com/golang/go/wiki/cgo#turning-c-arrays-into-go-slices */
+ unsafePtr := unsafe.Pointer(entries)
+ /* Max structured reply payload is 64M, so this array size is more than
+ * sufficient for the underlying slice we want to access.
+ */
+ arrayPtr := (*[1 << 20]uint32)(unsafePtr)
+ ret := make([]uint32, count)
+ copy(ret, arrayPtr[:count:count])
return ret
}
";
--
2.33.1
Eric Blake
2021-Dec-03 23:17 UTC
[Libguestfs] [libnbd PATCH 02/13] block_status: Refactor array storage
For 32-bit block status, we were able to cheat and use an array with
an odd number of elements, with array[0] holding the context id, and
passing &array[1] to the user's callback. But once we have 64-bit
extents, we can no longer abuse array element 0 like that. Split out
a new state to receive the context id separately from the extents
array. No behavioral change, other than the rare possibility of
landing in the new state.
---
lib/internal.h | 1 +
generator/state_machine.ml | 11 +++++-
generator/states-reply-structured.c | 58 ++++++++++++++++++++---------
3 files changed, 51 insertions(+), 19 deletions(-)
diff --git a/lib/internal.h b/lib/internal.h
index 0e205aba..7e96e8e9 100644
--- a/lib/internal.h
+++ b/lib/internal.h
@@ -274,6 +274,7 @@ struct nbd_handle {
size_t querynum;
/* When receiving block status, this is used. */
+ uint32_t bs_contextid;
uint32_t *bs_entries;
/* Commands which are waiting to be issued [meaning the request
diff --git a/generator/state_machine.ml b/generator/state_machine.ml
index 3bc77f24..99652948 100644
--- a/generator/state_machine.ml
+++ b/generator/state_machine.ml
@@ -1,6 +1,6 @@
(* hey emacs, this is OCaml code: -*- tuareg -*- *)
(* nbd client library in userspace: state machine definition
- * Copyright (C) 2013-2020 Red Hat Inc.
+ * Copyright (C) 2013-2021 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
@@ -862,10 +862,17 @@ and
external_events = [];
};
+ State {
+ default_state with
+ name = "RECV_BS_CONTEXTID";
+ comment = "Receive contextid of structured reply block-status
payload";
+ external_events = [];
+ };
+
State {
default_state with
name = "RECV_BS_ENTRIES";
- comment = "Receive a structured reply block-status payload";
+ comment = "Receive entries array of structured reply block-status
payload";
external_events = [];
};
diff --git a/generator/states-reply-structured.c
b/generator/states-reply-structured.c
index 70010474..e1da850d 100644
--- a/generator/states-reply-structured.c
+++ b/generator/states-reply-structured.c
@@ -1,5 +1,5 @@
/* nbd client library in userspace: state machine
- * Copyright (C) 2013-2019 Red Hat Inc.
+ * Copyright (C) 2013-2021 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
@@ -185,19 +185,10 @@ STATE_MACHINE {
set_error (0, "not expecting NBD_REPLY_TYPE_BLOCK_STATUS
here");
return 0;
}
- /* We read the context ID followed by all the entries into a
- * single array and deal with it at the end.
- */
- free (h->bs_entries);
- h->bs_entries = malloc (length);
- if (h->bs_entries == NULL) {
- SET_NEXT_STATE (%.DEAD);
- set_error (errno, "malloc");
- return 0;
- }
- h->rbuf = h->bs_entries;
- h->rlen = length;
- SET_NEXT_STATE (%RECV_BS_ENTRIES);
+ /* Start by reading the context ID. */
+ h->rbuf = &h->bs_contextid;
+ h->rlen = sizeof h->bs_contextid;
+ SET_NEXT_STATE (%RECV_BS_CONTEXTID);
return 0;
}
else {
@@ -452,9 +443,41 @@ STATE_MACHINE {
}
return 0;
+ REPLY.STRUCTURED_REPLY.RECV_BS_CONTEXTID:
+ struct command *cmd = h->reply_cmd;
+ uint32_t length;
+
+ switch (recv_into_rbuf (h)) {
+ case -1: SET_NEXT_STATE (%.DEAD); return 0;
+ case 1:
+ save_reply_state (h);
+ SET_NEXT_STATE (%.READY);
+ return 0;
+ case 0:
+ length = be32toh (h->sbuf.sr.structured_reply.length);
+
+ assert (cmd); /* guaranteed by CHECK */
+ assert (cmd->type == NBD_CMD_BLOCK_STATUS);
+ assert (length >= 12);
+ length -= sizeof h->bs_contextid;
+
+ free (h->bs_entries);
+ h->bs_entries = malloc (length);
+ if (h->bs_entries == NULL) {
+ SET_NEXT_STATE (%.DEAD);
+ set_error (errno, "malloc");
+ return 0;
+ }
+ h->rbuf = h->bs_entries;
+ h->rlen = length;
+ SET_NEXT_STATE (%RECV_BS_ENTRIES);
+ }
+ return 0;
+
REPLY.STRUCTURED_REPLY.RECV_BS_ENTRIES:
struct command *cmd = h->reply_cmd;
uint32_t length;
+ uint32_t count;
size_t i;
uint32_t context_id;
struct meta_context *meta_context;
@@ -473,15 +496,16 @@ STATE_MACHINE {
assert (CALLBACK_IS_NOT_NULL (cmd->cb.fn.extent));
assert (h->bs_entries);
assert (length >= 12);
+ count = (length - sizeof h->bs_contextid) / sizeof *h->bs_entries;
/* Need to byte-swap the entries returned, but apart from that we
* don't validate them.
*/
- for (i = 0; i < length/4; ++i)
+ for (i = 0; i < count; ++i)
h->bs_entries[i] = be32toh (h->bs_entries[i]);
/* Look up the context ID. */
- context_id = h->bs_entries[0];
+ context_id = be32toh (h->bs_contextid);
for (meta_context = h->meta_contexts;
meta_context;
meta_context = meta_context->next)
@@ -494,7 +518,7 @@ STATE_MACHINE {
if (CALL_CALLBACK (cmd->cb.fn.extent,
meta_context->name, cmd->offset,
- &h->bs_entries[1], (length-4) / 4,
+ h->bs_entries, count,
&error) == -1)
if (cmd->error == 0)
cmd->error = error ? error : EPROTO;
--
2.33.1
Eric Blake
2021-Dec-03 23:17 UTC
[Libguestfs] [libnbd PATCH 03/13] protocol: Add definitions for extended headers
Add the magic numbers and new structs necessary to implement the NBD
protocol extension of extended headers providing 64-bit lengths.
---
lib/nbd-protocol.h | 61 ++++++++++++++++++++++++++++++++++++++--------
1 file changed, 51 insertions(+), 10 deletions(-)
diff --git a/lib/nbd-protocol.h b/lib/nbd-protocol.h
index e5d6404b..7247d775 100644
--- a/lib/nbd-protocol.h
+++ b/lib/nbd-protocol.h
@@ -1,5 +1,5 @@
/* nbdkit
- * Copyright (C) 2013-2020 Red Hat Inc.
+ * Copyright (C) 2013-2021 Red Hat Inc.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
@@ -124,6 +124,7 @@ struct nbd_fixed_new_option_reply {
#define NBD_OPT_STRUCTURED_REPLY 8
#define NBD_OPT_LIST_META_CONTEXT 9
#define NBD_OPT_SET_META_CONTEXT 10
+#define NBD_OPT_EXTENDED_HEADERS 11
#define NBD_REP_ERR(val) (0x80000000 | (val))
#define NBD_REP_IS_ERR(val) (!!((val) & 0x80000000))
@@ -188,6 +189,13 @@ struct nbd_block_descriptor {
uint32_t status_flags; /* block type (hole etc) */
} NBD_ATTRIBUTE_PACKED;
+/* NBD_REPLY_TYPE_BLOCK_STATUS_EXT block descriptor. */
+struct nbd_block_descriptor_ext {
+ uint64_t length; /* length of block */
+ uint32_t status_flags; /* block type (hole etc) */
+ uint32_t pad; /* must be zero */
+} NBD_ATTRIBUTE_PACKED;
+
/* Request (client -> server). */
struct nbd_request {
uint32_t magic; /* NBD_REQUEST_MAGIC. */
@@ -197,6 +205,14 @@ struct nbd_request {
uint64_t offset; /* Request offset. */
uint32_t count; /* Request length. */
} NBD_ATTRIBUTE_PACKED;
+struct nbd_request_ext {
+ uint32_t magic; /* NBD_REQUEST_EXT_MAGIC. */
+ uint16_t flags; /* Request flags. */
+ uint16_t type; /* Request type. */
+ uint64_t handle; /* Opaque handle. */
+ uint64_t offset; /* Request offset. */
+ uint64_t count; /* Request length. */
+} NBD_ATTRIBUTE_PACKED;
/* Simple reply (server -> client). */
struct nbd_simple_reply {
@@ -204,6 +220,13 @@ struct nbd_simple_reply {
uint32_t error; /* NBD_SUCCESS or one of NBD_E*. */
uint64_t handle; /* Opaque handle. */
} NBD_ATTRIBUTE_PACKED;
+struct nbd_simple_reply_ext {
+ uint32_t magic; /* NBD_SIMPLE_REPLY_EXT_MAGIC. */
+ uint32_t error; /* NBD_SUCCESS or one of NBD_E*. */
+ uint64_t handle; /* Opaque handle. */
+ uint64_t pad1; /* Must be 0. */
+ uint64_t pad2; /* Must be 0. */
+} NBD_ATTRIBUTE_PACKED;
/* Structured reply (server -> client). */
struct nbd_structured_reply {
@@ -213,6 +236,14 @@ struct nbd_structured_reply {
uint64_t handle; /* Opaque handle. */
uint32_t length; /* Length of payload which follows. */
} NBD_ATTRIBUTE_PACKED;
+struct nbd_structured_reply_ext {
+ uint32_t magic; /* NBD_STRUCTURED_REPLY_EXT_MAGIC. */
+ uint16_t flags; /* NBD_REPLY_FLAG_* */
+ uint16_t type; /* NBD_REPLY_TYPE_* */
+ uint64_t handle; /* Opaque handle. */
+ uint64_t length; /* Length of payload which follows. */
+ uint64_t pad; /* Must be 0. */
+} NBD_ATTRIBUTE_PACKED;
struct nbd_structured_reply_offset_data {
uint64_t offset; /* offset */
@@ -224,15 +255,23 @@ struct nbd_structured_reply_offset_hole {
uint32_t length; /* Length of hole. */
} NBD_ATTRIBUTE_PACKED;
+struct nbd_structured_reply_offset_hole_ext {
+ uint64_t offset;
+ uint64_t length; /* Length of hole. */
+} NBD_ATTRIBUTE_PACKED;
+
struct nbd_structured_reply_error {
uint32_t error; /* NBD_E* error number */
uint16_t len; /* Length of human readable error. */
/* Followed by human readable error string, and possibly more structure. */
} NBD_ATTRIBUTE_PACKED;
-#define NBD_REQUEST_MAGIC 0x25609513
-#define NBD_SIMPLE_REPLY_MAGIC 0x67446698
-#define NBD_STRUCTURED_REPLY_MAGIC 0x668e33ef
+#define NBD_REQUEST_MAGIC 0x25609513
+#define NBD_REQUEST_EXT_MAGIC 0x21e41c71
+#define NBD_SIMPLE_REPLY_MAGIC 0x67446698
+#define NBD_SIMPLE_REPLY_EXT_MAGIC 0x60d12fd6
+#define NBD_STRUCTURED_REPLY_MAGIC 0x668e33ef
+#define NBD_STRUCTURED_REPLY_EXT_MAGIC 0x6e8a278c
/* Structured reply flags. */
#define NBD_REPLY_FLAG_DONE (1<<0)
@@ -241,12 +280,14 @@ struct nbd_structured_reply_error {
#define NBD_REPLY_TYPE_IS_ERR(val) (!!((val) & (1<<15)))
/* Structured reply types. */
-#define NBD_REPLY_TYPE_NONE 0
-#define NBD_REPLY_TYPE_OFFSET_DATA 1
-#define NBD_REPLY_TYPE_OFFSET_HOLE 2
-#define NBD_REPLY_TYPE_BLOCK_STATUS 5
-#define NBD_REPLY_TYPE_ERROR NBD_REPLY_TYPE_ERR (1)
-#define NBD_REPLY_TYPE_ERROR_OFFSET NBD_REPLY_TYPE_ERR (2)
+#define NBD_REPLY_TYPE_NONE 0
+#define NBD_REPLY_TYPE_OFFSET_DATA 1
+#define NBD_REPLY_TYPE_OFFSET_HOLE 2
+#define NBD_REPLY_TYPE_OFFSET_HOLE_EXT 3
+#define NBD_REPLY_TYPE_BLOCK_STATUS 5
+#define NBD_REPLY_TYPE_BLOCK_STATUS_EXT 6
+#define NBD_REPLY_TYPE_ERROR NBD_REPLY_TYPE_ERR (1)
+#define NBD_REPLY_TYPE_ERROR_OFFSET NBD_REPLY_TYPE_ERR (2)
/* NBD commands. */
#define NBD_CMD_READ 0
--
2.33.1
Eric Blake
2021-Dec-03 23:17 UTC
[Libguestfs] [libnbd PATCH 04/13] protocol: Prepare to send 64-bit requests
Support sending 64-bit requests if extended headers were negotiated.
At this point, h->extended_headers is permanently false (we can't
enable it until all other aspects of the protocol have likewise been
converted).
---
lib/internal.h | 12 ++++++++---
generator/states-issue-command.c | 31 +++++++++++++++++++----------
generator/states-reply-structured.c | 2 +-
lib/rw.c | 10 ++++------
4 files changed, 34 insertions(+), 21 deletions(-)
diff --git a/lib/internal.h b/lib/internal.h
index 7e96e8e9..07378588 100644
--- a/lib/internal.h
+++ b/lib/internal.h
@@ -1,5 +1,5 @@
/* nbd client library in userspace: internal definitions
- * Copyright (C) 2013-2020 Red Hat Inc.
+ * Copyright (C) 2013-2021 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
@@ -106,6 +106,9 @@ struct nbd_handle {
char *tls_username; /* Username, NULL = use current username */
char *tls_psk_file; /* PSK filename, NULL = no PSK */
+ /* Extended headers. */
+ bool extended_headers; /* If we negotiated NBD_OPT_EXTENDED_HEADERS */
+
/* Desired metadata contexts. */
bool request_sr;
string_vector request_meta_contexts;
@@ -242,7 +245,10 @@ struct nbd_handle {
/* Issuing a command must use a buffer separate from sbuf, for the
* case when we interrupt a request to service a reply.
*/
- struct nbd_request request;
+ union {
+ struct nbd_request request;
+ struct nbd_request_ext request_ext;
+ } req;
bool in_write_payload;
bool in_write_shutdown;
@@ -347,7 +353,7 @@ struct command {
uint16_t type;
uint64_t cookie;
uint64_t offset;
- uint32_t count;
+ uint64_t count;
void *data; /* Buffer for read/write */
struct command_cb cb;
enum state state; /* State to resume with on next POLLIN */
diff --git a/generator/states-issue-command.c b/generator/states-issue-command.c
index a8101144..7b1d6dc7 100644
--- a/generator/states-issue-command.c
+++ b/generator/states-issue-command.c
@@ -1,5 +1,5 @@
/* nbd client library in userspace: state machine
- * Copyright (C) 2013-2020 Red Hat Inc.
+ * Copyright (C) 2013-2021 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
@@ -41,14 +41,23 @@ STATE_MACHINE {
return 0;
}
- h->request.magic = htobe32 (NBD_REQUEST_MAGIC);
- h->request.flags = htobe16 (cmd->flags);
- h->request.type = htobe16 (cmd->type);
- h->request.handle = htobe64 (cmd->cookie);
- h->request.offset = htobe64 (cmd->offset);
- h->request.count = htobe32 ((uint32_t) cmd->count);
- h->wbuf = &h->request;
- h->wlen = sizeof (h->request);
+ /* These fields are coincident between req.request and req.request_ext */
+ h->req.request.flags = htobe16 (cmd->flags);
+ h->req.request.type = htobe16 (cmd->type);
+ h->req.request.handle = htobe64 (cmd->cookie);
+ h->req.request.offset = htobe64 (cmd->offset);
+ if (h->extended_headers) {
+ h->req.request_ext.magic = htobe32 (NBD_REQUEST_EXT_MAGIC);
+ h->req.request_ext.count = htobe64 (cmd->count);
+ h->wlen = sizeof (h->req.request_ext);
+ }
+ else {
+ assert (cmd->count <= UINT32_MAX);
+ h->req.request.magic = htobe32 (NBD_REQUEST_MAGIC);
+ h->req.request.count = htobe32 (cmd->count);
+ h->wlen = sizeof (h->req.request);
+ }
+ h->wbuf = &h->req;
if (cmd->type == NBD_CMD_WRITE || cmd->next)
h->wflags = MSG_MORE;
SET_NEXT_STATE (%SEND_REQUEST);
@@ -73,7 +82,7 @@ STATE_MACHINE {
assert (h->cmds_to_issue != NULL);
cmd = h->cmds_to_issue;
- assert (cmd->cookie == be64toh (h->request.handle));
+ assert (cmd->cookie == be64toh (h->req.request.handle));
if (cmd->type == NBD_CMD_WRITE) {
h->wbuf = cmd->data;
h->wlen = cmd->count;
@@ -119,7 +128,7 @@ STATE_MACHINE {
assert (!h->wlen);
assert (h->cmds_to_issue != NULL);
cmd = h->cmds_to_issue;
- assert (cmd->cookie == be64toh (h->request.handle));
+ assert (cmd->cookie == be64toh (h->req.request.handle));
h->cmds_to_issue = cmd->next;
if (h->cmds_to_issue_tail == cmd)
h->cmds_to_issue_tail = NULL;
diff --git a/generator/states-reply-structured.c
b/generator/states-reply-structured.c
index e1da850d..5524e000 100644
--- a/generator/states-reply-structured.c
+++ b/generator/states-reply-structured.c
@@ -34,7 +34,7 @@ structured_reply_in_bounds (uint64_t offset, uint32_t length,
offset + length > cmd->offset + cmd->count) {
set_error (0, "range of structured reply is out of bounds, "
"offset=%" PRIu64 ", cmd->offset=%"
PRIu64 ", "
- "length=%" PRIu32 ", cmd->count=%" PRIu32
": "
+ "length=%" PRIu32 ", cmd->count=%" PRIu64
": "
"this is likely to be a bug in the NBD server",
offset, cmd->offset, length, cmd->count);
return false;
diff --git a/lib/rw.c b/lib/rw.c
index 4ade7508..16c2e848 100644
--- a/lib/rw.c
+++ b/lib/rw.c
@@ -1,5 +1,5 @@
/* NBD client library in userspace
- * Copyright (C) 2013-2020 Red Hat Inc.
+ * Copyright (C) 2013-2021 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
@@ -216,13 +216,11 @@ nbd_internal_command_common (struct nbd_handle *h,
}
break;
- /* Other commands are currently limited by the 32 bit field in the
- * command structure on the wire, but in future we hope to support
- * 64 bit values here with a change to the NBD protocol which is
- * being discussed upstream.
+ /* Other commands are limited by the 32 bit field in the command
+ * structure on the wire, unless extended headers were negotiated.
*/
default:
- if (count > UINT32_MAX) {
+ if (!h->extended_headers && count > UINT32_MAX) {
set_error (ERANGE, "request too large: maximum request size is
%" PRIu32,
UINT32_MAX);
goto err;
--
2.33.1
Eric Blake
2021-Dec-03 23:17 UTC
[Libguestfs] [libnbd PATCH 05/13] protocol: Prepare to receive 64-bit replies
Support receiving headers for 64-bit replies if extended headers were
negotiated. We already insist that the server not send us too much
payload in one reply, so we can exploit that and merge the 64-bit
length back into a normalized 32-bit field for the rest of the payload
length calculations. The NBD protocol specifically made extended
simple and structured replies both occupy 32 bytes, while the handle
field is still in the same offset between all reply types.
Note that if we negotiate extended headers, but a non-compliant server
replies with a non-extended header, we will stall waiting for the
server to send more bytes rather than noticing that the magic number
is wrong. The alternative would be to read just the first 4 bytes of
magic, then determine how many more bytes to expect; but that would
require more states and syscalls, and not worth it since the typical
server will be compliant.
At this point, h->extended_headers is permanently false (we can't
enable it until all other aspects of the protocol have likewise been
converted).
---
lib/internal.h | 8 +++-
generator/states-reply-structured.c | 59 +++++++++++++++++++----------
generator/states-reply.c | 31 +++++++++++----
3 files changed, 68 insertions(+), 30 deletions(-)
diff --git a/lib/internal.h b/lib/internal.h
index 07378588..c9f84441 100644
--- a/lib/internal.h
+++ b/lib/internal.h
@@ -222,8 +222,12 @@ struct nbd_handle {
} __attribute__((packed)) or;
struct nbd_export_name_option_reply export_name_reply;
struct nbd_simple_reply simple_reply;
+ struct nbd_simple_reply_ext simple_reply_ext;
struct {
- struct nbd_structured_reply structured_reply;
+ union {
+ struct nbd_structured_reply structured_reply;
+ struct nbd_structured_reply_ext structured_reply_ext;
+ } hdr;
union {
struct nbd_structured_reply_offset_data offset_data;
struct nbd_structured_reply_offset_hole offset_hole;
@@ -233,7 +237,7 @@ struct nbd_handle {
uint64_t offset; /* Only used for NBD_REPLY_TYPE_ERROR_OFFSET */
} __attribute__((packed)) error;
} payload;
- } __attribute__((packed)) sr;
+ } sr;
uint16_t gflags;
uint32_t cflags;
uint32_t len;
diff --git a/generator/states-reply-structured.c
b/generator/states-reply-structured.c
index 5524e000..1b675e8d 100644
--- a/generator/states-reply-structured.c
+++ b/generator/states-reply-structured.c
@@ -45,19 +45,23 @@ structured_reply_in_bounds (uint64_t offset, uint32_t
length,
STATE_MACHINE {
REPLY.STRUCTURED_REPLY.START:
- /* We've only read the simple_reply. The structured_reply is longer,
- * so read the remaining part.
+ /* We've only read the simple_reply. Unless we have extended headers,
+ * the structured_reply is longer, so read the remaining part.
*/
if (!h->structured_replies) {
set_error (0, "server sent unexpected structured reply");
SET_NEXT_STATE(%.DEAD);
return 0;
}
- h->rbuf = &h->sbuf;
- h->rbuf += sizeof h->sbuf.simple_reply;
- h->rlen = sizeof h->sbuf.sr.structured_reply;
- h->rlen -= sizeof h->sbuf.simple_reply;
- SET_NEXT_STATE (%RECV_REMAINING);
+ if (h->extended_headers)
+ SET_NEXT_STATE (%CHECK);
+ else {
+ h->rbuf = &h->sbuf;
+ h->rbuf += sizeof h->sbuf.simple_reply;
+ h->rlen = sizeof h->sbuf.sr.hdr.structured_reply;
+ h->rlen -= sizeof h->sbuf.simple_reply;
+ SET_NEXT_STATE (%RECV_REMAINING);
+ }
return 0;
REPLY.STRUCTURED_REPLY.RECV_REMAINING:
@@ -75,12 +79,21 @@ STATE_MACHINE {
struct command *cmd = h->reply_cmd;
uint16_t flags, type;
uint64_t cookie;
- uint32_t length;
+ uint64_t length;
- flags = be16toh (h->sbuf.sr.structured_reply.flags);
- type = be16toh (h->sbuf.sr.structured_reply.type);
- cookie = be64toh (h->sbuf.sr.structured_reply.handle);
- length = be32toh (h->sbuf.sr.structured_reply.length);
+ flags = be16toh (h->sbuf.sr.hdr.structured_reply.flags);
+ type = be16toh (h->sbuf.sr.hdr.structured_reply.type);
+ cookie = be64toh (h->sbuf.sr.hdr.structured_reply.handle);
+ if (h->extended_headers) {
+ length = be64toh (h->sbuf.sr.hdr.structured_reply_ext.length);
+ if (h->sbuf.sr.hdr.structured_reply_ext.pad) {
+ set_error (0, "server sent non-zero padding in structured reply
header");
+ SET_NEXT_STATE(%.DEAD);
+ return 0;
+ }
+ }
+ else
+ length = be32toh (h->sbuf.sr.hdr.structured_reply.length);
assert (cmd);
assert (cmd->cookie == cookie);
@@ -97,6 +110,10 @@ STATE_MACHINE {
SET_NEXT_STATE (%.DEAD);
return 0;
}
+ /* For convenience, we now normalize extended replies into compact,
+ * doable since we validated length fits in 32 bits.
+ */
+ h->sbuf.sr.hdr.structured_reply.length = length;
if (NBD_REPLY_TYPE_IS_ERR (type)) {
if (length < sizeof h->sbuf.sr.payload.error.error) {
@@ -207,7 +224,7 @@ STATE_MACHINE {
SET_NEXT_STATE (%.READY);
return 0;
case 0:
- length = be32toh (h->sbuf.sr.structured_reply.length);
+ length = h->sbuf.sr.hdr.structured_reply.length; /* normalized in CHECK
*/
msglen = be16toh (h->sbuf.sr.payload.error.error.len);
if (msglen > length - sizeof h->sbuf.sr.payload.error.error ||
msglen > sizeof h->sbuf.sr.payload.error.msg) {
@@ -233,9 +250,9 @@ STATE_MACHINE {
SET_NEXT_STATE (%.READY);
return 0;
case 0:
- length = be32toh (h->sbuf.sr.structured_reply.length);
+ length = h->sbuf.sr.hdr.structured_reply.length; /* normalized in CHECK
*/
msglen = be16toh (h->sbuf.sr.payload.error.error.len);
- type = be16toh (h->sbuf.sr.structured_reply.type);
+ type = be16toh (h->sbuf.sr.hdr.structured_reply.type);
length -= sizeof h->sbuf.sr.payload.error.error + msglen;
@@ -281,7 +298,7 @@ STATE_MACHINE {
return 0;
case 0:
error = be32toh (h->sbuf.sr.payload.error.error.error);
- type = be16toh (h->sbuf.sr.structured_reply.type);
+ type = be16toh (h->sbuf.sr.hdr.structured_reply.type);
assert (cmd); /* guaranteed by CHECK */
error = nbd_internal_errno_of_nbd_error (error);
@@ -339,7 +356,7 @@ STATE_MACHINE {
SET_NEXT_STATE (%.READY);
return 0;
case 0:
- length = be32toh (h->sbuf.sr.structured_reply.length);
+ length = h->sbuf.sr.hdr.structured_reply.length; /* normalized in CHECK
*/
offset = be64toh (h->sbuf.sr.payload.offset_data.offset);
assert (cmd); /* guaranteed by CHECK */
@@ -377,7 +394,7 @@ STATE_MACHINE {
SET_NEXT_STATE (%.READY);
return 0;
case 0:
- length = be32toh (h->sbuf.sr.structured_reply.length);
+ length = h->sbuf.sr.hdr.structured_reply.length; /* normalized in CHECK
*/
offset = be64toh (h->sbuf.sr.payload.offset_data.offset);
assert (cmd); /* guaranteed by CHECK */
@@ -454,7 +471,7 @@ STATE_MACHINE {
SET_NEXT_STATE (%.READY);
return 0;
case 0:
- length = be32toh (h->sbuf.sr.structured_reply.length);
+ length = h->sbuf.sr.hdr.structured_reply.length; /* normalized in CHECK
*/
assert (cmd); /* guaranteed by CHECK */
assert (cmd->type == NBD_CMD_BLOCK_STATUS);
@@ -489,7 +506,7 @@ STATE_MACHINE {
SET_NEXT_STATE (%.READY);
return 0;
case 0:
- length = be32toh (h->sbuf.sr.structured_reply.length);
+ length = h->sbuf.sr.hdr.structured_reply.length; /* normalized in CHECK
*/
assert (cmd); /* guaranteed by CHECK */
assert (cmd->type == NBD_CMD_BLOCK_STATUS);
@@ -535,7 +552,7 @@ STATE_MACHINE {
REPLY.STRUCTURED_REPLY.FINISH:
uint16_t flags;
- flags = be16toh (h->sbuf.sr.structured_reply.flags);
+ flags = be16toh (h->sbuf.sr.hdr.structured_reply.flags);
if (flags & NBD_REPLY_FLAG_DONE) {
SET_NEXT_STATE (%^FINISH_COMMAND);
}
diff --git a/generator/states-reply.c b/generator/states-reply.c
index 9099a76a..949e982e 100644
--- a/generator/states-reply.c
+++ b/generator/states-reply.c
@@ -1,5 +1,5 @@
/* nbd client library in userspace: state machine
- * Copyright (C) 2013-2019 Red Hat Inc.
+ * Copyright (C) 2013-2021 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
@@ -68,7 +68,10 @@ STATE_MACHINE {
assert (h->rlen == 0);
h->rbuf = &h->sbuf;
- h->rlen = sizeof h->sbuf.simple_reply;
+ if (h->extended_headers)
+ h->rlen = sizeof h->sbuf.simple_reply_ext;
+ else
+ h->rlen = sizeof h->sbuf.simple_reply;
r = h->sock->ops->recv (h, h->sock, h->rbuf, h->rlen);
if (r == -1) {
@@ -113,13 +116,27 @@ STATE_MACHINE {
uint64_t cookie;
magic = be32toh (h->sbuf.simple_reply.magic);
- if (magic == NBD_SIMPLE_REPLY_MAGIC) {
+ switch (magic) {
+ case NBD_SIMPLE_REPLY_MAGIC:
+ case NBD_SIMPLE_REPLY_EXT_MAGIC:
+ if ((magic == NBD_SIMPLE_REPLY_MAGIC) == h->extended_headers)
+ goto invalid;
+ if (magic == NBD_SIMPLE_REPLY_EXT_MAGIC &&
+ (h->sbuf.simple_reply_ext.pad1 || h->sbuf.simple_reply_ext.pad2))
{
+ set_error (0, "server sent non-zero padding in simple reply
header");
+ SET_NEXT_STATE (%.DEAD);
+ return 0;
+ }
SET_NEXT_STATE (%SIMPLE_REPLY.START);
- }
- else if (magic == NBD_STRUCTURED_REPLY_MAGIC) {
+ break;
+ case NBD_STRUCTURED_REPLY_MAGIC:
+ case NBD_STRUCTURED_REPLY_EXT_MAGIC:
+ if ((magic == NBD_STRUCTURED_REPLY_MAGIC) == h->extended_headers)
+ goto invalid;
SET_NEXT_STATE (%STRUCTURED_REPLY.START);
- }
- else {
+ break;
+ default:
+ invalid:
SET_NEXT_STATE (%.DEAD); /* We've probably lost synchronization. */
set_error (0, "invalid reply magic");
return 0;
--
2.33.1
Eric Blake
2021-Dec-03 23:17 UTC
[Libguestfs] [libnbd PATCH 06/13] protocol: Accept 64-bit holes during pread
Even though we don't allow the user to request NBD_CMD_READ with more
than 64M (and even if we did, our API signature caps us at SIZE_MAX,
which is 32 bits on a 32-bit machine), the NBD extension to allow
64-bit requests implies that for symmetry we have to be able to
support 64-bit holes over the wire. Note that we don't have to change
the signature of the callback for nbd_pread_structured; nor is it
worth adding a counterpart to LIBNBD_READ_HOLE, because it is unlikely
that a user callback will ever need to distinguish between which size
was sent over the wire, when the value is always less than 32 bits.
While we cannot guarantee which size structured reply the server will
use, it is easy enough to handle both sizes, even for a non-compliant
server that sends wide replies when extended headers were not
negotiated. Of course, until a later patch enables extended headers
negotiation, no compliant server will trigger the new code here.
---
lib/internal.h | 1 +
generator/states-reply-structured.c | 41 +++++++++++++++++++++++++----
2 files changed, 37 insertions(+), 5 deletions(-)
diff --git a/lib/internal.h b/lib/internal.h
index c9f84441..06f3a65c 100644
--- a/lib/internal.h
+++ b/lib/internal.h
@@ -231,6 +231,7 @@ struct nbd_handle {
union {
struct nbd_structured_reply_offset_data offset_data;
struct nbd_structured_reply_offset_hole offset_hole;
+ struct nbd_structured_reply_offset_hole_ext offset_hole_ext;
struct {
struct nbd_structured_reply_error error;
char msg[NBD_MAX_STRING]; /* Common to all error types */
diff --git a/generator/states-reply-structured.c
b/generator/states-reply-structured.c
index 1b675e8d..a3e0e2ac 100644
--- a/generator/states-reply-structured.c
+++ b/generator/states-reply-structured.c
@@ -26,15 +26,16 @@
* requesting command.
*/
static bool
-structured_reply_in_bounds (uint64_t offset, uint32_t length,
+structured_reply_in_bounds (uint64_t offset, uint64_t length,
const struct command *cmd)
{
if (offset < cmd->offset ||
offset >= cmd->offset + cmd->count ||
- offset + length > cmd->offset + cmd->count) {
+ length > cmd->offset + cmd->count ||
+ offset > cmd->offset + cmd->count - length) {
set_error (0, "range of structured reply is out of bounds, "
"offset=%" PRIu64 ", cmd->offset=%"
PRIu64 ", "
- "length=%" PRIu32 ", cmd->count=%" PRIu64
": "
+ "length=%" PRIu64 ", cmd->count=%" PRIu64
": "
"this is likely to be a bug in the NBD server",
offset, cmd->offset, length, cmd->count);
return false;
@@ -182,6 +183,25 @@ STATE_MACHINE {
SET_NEXT_STATE (%RECV_OFFSET_HOLE);
return 0;
}
+ else if (type == NBD_REPLY_TYPE_OFFSET_HOLE_EXT) {
+ if (cmd->type != NBD_CMD_READ) {
+ SET_NEXT_STATE (%.DEAD);
+ set_error (0, "invalid command for receiving offset-hole chunk,
"
+ "cmd->type=%" PRIu16 ", "
+ "this is likely to be a bug in the server",
+ cmd->type);
+ return 0;
+ }
+ if (length != sizeof h->sbuf.sr.payload.offset_hole_ext) {
+ SET_NEXT_STATE (%.DEAD);
+ set_error (0, "invalid length in
NBD_REPLY_TYPE_OFFSET_HOLE_EXT");
+ return 0;
+ }
+ h->rbuf = &h->sbuf.sr.payload.offset_hole_ext;
+ h->rlen = sizeof h->sbuf.sr.payload.offset_hole_ext;
+ SET_NEXT_STATE (%RECV_OFFSET_HOLE);
+ return 0;
+ }
else if (type == NBD_REPLY_TYPE_BLOCK_STATUS) {
if (cmd->type != NBD_CMD_BLOCK_STATUS) {
SET_NEXT_STATE (%.DEAD);
@@ -415,7 +435,8 @@ STATE_MACHINE {
REPLY.STRUCTURED_REPLY.RECV_OFFSET_HOLE:
struct command *cmd = h->reply_cmd;
uint64_t offset;
- uint32_t length;
+ uint64_t length;
+ uint16_t type;
switch (recv_into_rbuf (h)) {
case -1: SET_NEXT_STATE (%.DEAD); return 0;
@@ -425,7 +446,14 @@ STATE_MACHINE {
return 0;
case 0:
offset = be64toh (h->sbuf.sr.payload.offset_hole.offset);
- length = be32toh (h->sbuf.sr.payload.offset_hole.length);
+ type = be16toh (h->sbuf.sr.hdr.structured_reply.type);
+
+ if (type == NBD_REPLY_TYPE_OFFSET_HOLE)
+ length = be32toh (h->sbuf.sr.payload.offset_hole.length);
+ else {
+ /* XXX Insist on h->extended_headers? */
+ length = be64toh (h->sbuf.sr.payload.offset_hole_ext.length);
+ }
assert (cmd); /* guaranteed by CHECK */
@@ -443,7 +471,10 @@ STATE_MACHINE {
/* The spec states that 0-length requests are unspecified, but
* 0-length replies are broken. Still, it's easy enough to support
* them as an extension, and this works even when length == 0.
+ * Although length is 64 bits, the bounds check above ensures that
+ * it is no larger than the 64M cap we put on NBD_CMD_READ.
*/
+ assert (length <= SIZE_MAX);
memset (cmd->data + offset, 0, length);
if (CALLBACK_IS_NOT_NULL (cmd->cb.fn.chunk)) {
int error = cmd->error;
--
2.33.1
Eric Blake
2021-Dec-03 23:17 UTC
[Libguestfs] [libnbd PATCH 07/13] generator: Add struct nbd_extent in prep for 64-bit extents
The existing nbd_block_status() callback is permanently stuck with an
array of uint32_t pairs (len/2 extents), and exposing 64-bit extents
requires a new API. Before we get there, we first need a way to
express a struct containing uint64_t length and uint32_t flags across
the various language bindings in the callback that will be used by the
new API.
For the language bindings, we have to construct an array of a similar
struct in the target language's preferred format. The bindings for
Python and OCaml were relatively straightforward; the Golang bindings
took a bit more effort for me to write. Temporary unused attributes
are needed to keep the compiler happy until a later patch exposes a
new API using the new callback type.
---
generator/API.ml | 12 +++++++++++-
generator/API.mli | 3 ++-
generator/C.ml | 24 +++++++++++++++++++++---
generator/GoLang.ml | 24 ++++++++++++++++++++++++
generator/OCaml.ml | 21 +++++++++++++++++----
generator/Python.ml | 30 ++++++++++++++++++++++++++----
ocaml/helpers.c | 22 +++++++++++++++++++++-
ocaml/nbd-c.h | 3 ++-
golang/handle.go | 6 ++++++
9 files changed, 130 insertions(+), 15 deletions(-)
diff --git a/generator/API.ml b/generator/API.ml
index cf2e7543..70ae721d 100644
--- a/generator/API.ml
+++ b/generator/API.ml
@@ -42,6 +42,7 @@
| BytesPersistOut of string * string
| Closure of closure
| Enum of string * enum
+| Extent64 of string
| Fd of string
| Flags of string * flags
| Int of string
@@ -142,6 +143,14 @@ let extent_closure
"nr_entries");
CBMutable (Int "error") ]
}
+let extent64_closure = {
+ cbname = "extent64";
+ cbargs = [ CBString "metacontext";
+ CBUInt64 "offset";
+ CBArrayAndLen (Extent64 "entries",
+ "nr_entries");
+ CBMutable (Int "error") ]
+}
let list_closure = {
cbname = "list";
cbargs = [ CBString "name"; CBString "description" ]
@@ -151,7 +160,8 @@ let context_closure cbargs = [ CBString "name"
]
}
let all_closures = [ chunk_closure; completion_closure;
- debug_closure; extent_closure; list_closure;
+ debug_closure; extent_closure; extent64_closure;
+ list_closure;
context_closure ]
(* Enums. *)
diff --git a/generator/API.mli b/generator/API.mli
index d284637f..922d8120 100644
--- a/generator/API.mli
+++ b/generator/API.mli
@@ -1,6 +1,6 @@
(* hey emacs, this is OCaml code: -*- tuareg -*- *)
(* nbd client library in userspace: the API
- * Copyright (C) 2013-2020 Red Hat Inc.
+ * Copyright (C) 2013-2021 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
@@ -52,6 +52,7 @@ and
| BytesPersistOut of string * string
| Closure of closure (** function pointer + void *opaque *)
| Enum of string * enum (** enum/union type, int in C *)
+| Extent64 of string (** extent descriptor, struct nbd_extent in C *)
| Fd of string (** file descriptor *)
| Flags of string * flags (** flags, uint32_t in C *)
| Int of string (** small int *)
diff --git a/generator/C.ml b/generator/C.ml
index 797af531..7b0be583 100644
--- a/generator/C.ml
+++ b/generator/C.ml
@@ -1,6 +1,6 @@
(* hey emacs, this is OCaml code: -*- tuareg -*- *)
(* nbd client library in userspace: generate the C API and documentation
- * Copyright (C) 2013-2020 Red Hat Inc.
+ * Copyright (C) 2013-2021 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
@@ -90,6 +90,7 @@ let
| Closure { cbname } ->
[ sprintf "%s_callback" cbname; sprintf "%s_user_data"
cbname ]
| Enum (n, _) -> [n]
+| Extent64 n -> [n]
| Fd n -> [n]
| Flags (n, _) -> [n]
| Int n -> [n]
@@ -152,6 +153,9 @@ and
| Enum (n, _) ->
if types then pr "int ";
pr "%s" n
+ | Extent64 n ->
+ if types then pr "nbd_extent ";
+ pr "%s" n
| Flags (n, _) ->
if types then pr "uint32_t ";
pr "%s" n
@@ -238,6 +242,11 @@ and
pr "%s, " n;
if types then pr "size_t ";
pr "%s" len
+ | CBArrayAndLen (Extent64 n, len) ->
+ if types then pr "nbd_extent *";
+ pr "%s, " n;
+ if types then pr "size_t ";
+ pr "%s" len
| CBArrayAndLen _ -> assert false
| CBBytesIn (n, len) ->
if types then pr "const void *";
@@ -388,6 +397,13 @@ let
pr "extern int nbd_get_errno (void);\n";
pr "#define LIBNBD_HAVE_NBD_GET_ERRNO 1\n";
pr "\n";
+ pr "/* This is used in the callback for nbd_block_status_64.\n";
+ pr " */\n";
+ pr "typedef struct {\n";
+ pr " uint64_t length;\n";
+ pr " uint32_t flags;\n";
+ pr "} nbd_extent;\n";
+ pr "\n";
print_closure_structs ();
List.iter (
fun (name, { args; optargs; ret }) ->
@@ -630,7 +646,7 @@ let
pr " char *%s_printable =\n" n;
pr " nbd_internal_printable_string_list (%s);\n" n
| BytesOut _ | BytesPersistOut _
- | Bool _ | Closure _ | Enum _ | Flags _ | Fd _ | Int _
+ | Bool _ | Closure _ | Enum _ | Extent64 _ | Flags _ | Fd _ | Int _
| Int64 _ | SizeT _
| SockAddrAndLen _ | UInt _ | UInt32 _ | UInt64 _ | UIntPtr _ -> ()
) args;
@@ -645,6 +661,7 @@ let
pr " %s=\\\"%%s\\\" %s=%%zu" n count
| Closure { cbname } -> pr " %s=<fun>" cbname
| Enum (n, _) -> pr " %s=%%d" n
+ | Extent64 _ -> assert false (* only used in extent64_closure *)
| Flags (n, _) -> pr " %s=0x%%x" n
| Fd n | Int n -> pr " %s=%%d" n
| Int64 n -> pr " %s=%%\" PRIi64 \"" n
@@ -674,6 +691,7 @@ let
pr ", %s_printable ? %s_printable : \"\", %s" n n
count
| Closure { cbname } -> ()
| Enum (n, _) -> pr ", %s" n
+ | Extent64 _ -> assert false (* only used in extent64_closure *)
| Flags (n, _) -> pr ", %s" n
| Fd n | Int n | Int64 n | SizeT n -> pr ", %s" n
| SockAddrAndLen (_, len) -> pr ", (int) %s" len
@@ -697,7 +715,7 @@ let
| StringList n ->
pr " free (%s_printable);\n" n
| BytesOut _ | BytesPersistOut _
- | Bool _ | Closure _ | Enum _ | Flags _ | Fd _ | Int _
+ | Bool _ | Closure _ | Enum _ | Extent64 _ | Flags _ | Fd _ | Int _
| Int64 _ | SizeT _
| SockAddrAndLen _ | UInt _ | UInt32 _ | UInt64 _ | UIntPtr _ -> ()
) args;
diff --git a/generator/GoLang.ml b/generator/GoLang.ml
index d3b7dc79..7363063f 100644
--- a/generator/GoLang.ml
+++ b/generator/GoLang.ml
@@ -49,6 +49,7 @@ let
| BytesPersistOut (n, len) -> n
| Closure { cbname } -> cbname
| Enum (n, _) -> n
+ | Extent64 n -> n
| Fd n -> n
| Flags (n, _) -> n
| Int n -> n
@@ -71,6 +72,7 @@ let
| BytesPersistOut _ -> "AioBuffer"
| Closure { cbname } -> sprintf "%sCallback" (camel_case cbname)
| Enum (_, { enum_prefix }) -> camel_case enum_prefix
+ | Extent64 _ -> assert false (* only used in extent64_closure *)
| Fd _ -> "int"
| Flags (_, { flag_prefix }) -> camel_case flag_prefix
| Int _ -> "int"
@@ -261,6 +263,7 @@ let
pr " c_%s.user_data = C.alloc_cbid(C.long(%s_cbid))\n"
cbname cbname
| Enum (n, _) ->
pr " c_%s := C.int (%s)\n" n n
+ | Extent64 _ -> assert false (* only used in extent64_closure *)
| Fd n ->
pr " c_%s := C.int (%s)\n" n n
| Flags (n, _) ->
@@ -333,6 +336,7 @@ let
| BytesPersistOut (n, len) -> pr ", c_%s, c_%s" n len
| Closure { cbname } -> pr ", c_%s" cbname
| Enum (n, _) -> pr ", c_%s" n
+ | Extent64 _ -> assert false (* only used in extent64_closure *)
| Fd n -> pr ", c_%s" n
| Flags (n, _) -> pr ", c_%s" n
| Int n -> pr ", c_%s" n
@@ -524,6 +528,18 @@ let
copy(ret, arrayPtr[:count:count])
return ret
}
+
+func copy_extent_array (entries *C.nbd_extent, count C.size_t) []LibnbdExtent {
+ unsafePtr := unsafe.Pointer(entries)
+ arrayPtr := (*[1 << 20]C.nbd_extent)(unsafePtr)
+ slice := arrayPtr[:count:count]
+ ret := make([]LibnbdExtent, count)
+ for i := 0; i < int (count); i++ {
+ ret[i].Length = uint64 (slice[i].length)
+ ret[i].Flags = uint32 (slice[i].flags)
+ }
+ return ret
+}
";
List.iter (
@@ -537,6 +553,8 @@ let
match cbarg with
| CBArrayAndLen (UInt32 n, _) ->
pr "%s []uint32" n;
+ | CBArrayAndLen (Extent64 n, _) ->
+ pr "%s []LibnbdExtent" n;
| CBBytesIn (n, len) ->
pr "%s []byte" n;
| CBInt n ->
@@ -563,6 +581,8 @@ let
match cbarg with
| CBArrayAndLen (UInt32 n, count) ->
pr "%s *C.uint32_t, %s C.size_t" n count
+ | CBArrayAndLen (Extent64 n, count) ->
+ pr "%s *C.nbd_extent, %s C.size_t" n count
| CBBytesIn (n, len) ->
pr "%s unsafe.Pointer, %s C.size_t" n len
| CBInt n ->
@@ -605,6 +625,8 @@ let
match cbarg with
| CBArrayAndLen (UInt32 n, count) ->
pr "copy_uint32_array (%s, %s)" n count
+ | CBArrayAndLen (Extent64 n, count) ->
+ pr "copy_extent_array (%s, %s)" n count
| CBBytesIn (n, len) ->
pr "C.GoBytes (%s, C.int (%s))" n len
| CBInt n ->
@@ -756,6 +778,8 @@ let
match cbarg with
| CBArrayAndLen (UInt32 n, count) ->
pr "uint32_t *%s, size_t %s" n count
+ | CBArrayAndLen (Extent64 n, count) ->
+ pr "nbd_extent *%s, size_t %s" n count
| CBBytesIn (n, len) ->
pr "void *%s, size_t %s" n len
| CBInt n ->
diff --git a/generator/OCaml.ml b/generator/OCaml.ml
index 1349609b..eac42668 100644
--- a/generator/OCaml.ml
+++ b/generator/OCaml.ml
@@ -1,6 +1,6 @@
(* hey emacs, this is OCaml code: -*- tuareg -*- *)
(* nbd client library in userspace: generator
- * Copyright (C) 2013-2020 Red Hat Inc.
+ * Copyright (C) 2013-2021 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
@@ -44,6 +44,7 @@ and
| Closure { cbargs } ->
sprintf "(%s)" (ocaml_closuredecl_to_string cbargs)
| Enum (_, { enum_prefix }) -> sprintf "%s.t" enum_prefix
+ | Extent64 _ -> "extent"
| Fd _ -> "Unix.file_descr"
| Flags (_, { flag_prefix }) -> sprintf "%s.t list" flag_prefix
| Int _ -> "int"
@@ -100,6 +101,7 @@ let
| BytesPersistOut (n, len) -> n
| Closure { cbname } -> cbname
| Enum (n, _) -> n
+ | Extent64 n -> n
| Fd n -> n
| Flags (n, _) -> n
| Int n -> n
@@ -145,6 +147,9 @@ let
type cookie = int64
+type extent = int64 * int32
+(** Length and flags of an extent in block_status_64 callback. *)
+
";
List.iter (
@@ -246,6 +251,7 @@ let
exception Error of string * int
exception Closed of string
type cookie = int64
+type extent = int64 * int32
(* Give the exceptions names so that they can be raised from the C code. *)
let () @@ -469,7 +475,8 @@ let
let argnames List.map (
function
- | CBArrayAndLen (UInt32 n, _) | CBBytesIn (n, _)
+ | CBArrayAndLen (UInt32 n, _) | CBArrayAndLen (Extent64 n, _)
+ | CBBytesIn (n, _)
| CBInt n | CBInt64 n
| CBMutable (Int n) | CBString n | CBUInt n | CBUInt64 n ->
n ^ "v"
@@ -497,6 +504,9 @@ let
| CBArrayAndLen (UInt32 n, count) ->
pr " %sv = nbd_internal_ocaml_alloc_int32_array (%s, %s);\n"
n n count;
+ | CBArrayAndLen (Extent64 n, count) ->
+ pr " %sv = nbd_internal_ocaml_alloc_extent64_array (%s,
%s);\n"
+ n n count;
| CBBytesIn (n, len) ->
pr " %sv = caml_alloc_initialized_string (%s, %s);\n" n len n
| CBInt n | CBUInt n ->
@@ -520,7 +530,7 @@ let
List.iter (
function
- | CBArrayAndLen (UInt32 _, _)
+ | CBArrayAndLen (_, _)
| CBBytesIn _
| CBInt _
| CBInt64 _
@@ -529,7 +539,7 @@ let
| CBUInt64 _ -> ()
| CBMutable (Int n) ->
pr " *%s = Int_val (Field (%sv, 0));\n" n n
- | CBArrayAndLen _ | CBMutable _ -> assert false
+ | CBMutable _ -> assert false
) cbargs;
pr " if (Is_exception_result (rv)) {\n";
@@ -544,6 +554,7 @@ let
pr "}\n";
pr "\n";
pr "static int\n";
+ pr "__attribute__((unused)) /* XXX temporary hack */\n";
pr "%s_wrapper " cbname;
C.print_cbarg_list ~wrap:true cbargs;
pr "\n";
@@ -659,6 +670,7 @@ let
pr " %s_callback.free = free_user_data;\n" cbname
| Enum (n, { enum_prefix }) ->
pr " int %s = %s_val (%sv);\n" n enum_prefix n
+ | Extent64 _ -> assert false (* only used in extent64_closure *)
| Fd n ->
pr " /* OCaml Unix.file_descr is just an int, at least on Unix.
*/\n";
pr " int %s = Int_val (%sv);\n" n n
@@ -739,6 +751,7 @@ let
| BytesPersistOut _
| Closure _
| Enum _
+ | Extent64 _
| Fd _
| Flags _
| Int _
diff --git a/generator/Python.ml b/generator/Python.ml
index 4ab18f62..4212e2ac 100644
--- a/generator/Python.ml
+++ b/generator/Python.ml
@@ -158,6 +158,7 @@ let
let print_python_closure_wrapper { cbname; cbargs } pr "/* Wrapper for
%s callback. */\n" cbname;
pr "static int\n";
+ pr "__attribute__((unused)) /* XXX temporary hack */\n";
pr "%s_wrapper " cbname;
C.print_cbarg_list ~wrap:true cbargs;
pr "\n";
@@ -169,7 +170,8 @@ let
pr " PyObject *py_args, *py_ret;\n";
List.iter (
function
- | CBArrayAndLen (UInt32 n, _) ->
+ | CBArrayAndLen (UInt32 n, _)
+ | CBArrayAndLen (Extent64 n, _) ->
pr " PyObject *py_%s = NULL;\n" n
| CBMutable (Int n) ->
pr " PyObject *py_%s = NULL;\n" n
@@ -187,6 +189,16 @@ let
pr " if (!py_e_%s) { PyErr_PrintEx (0); goto out; }\n" n;
pr " PyList_SET_ITEM (py_%s, i_%s, py_e_%s);\n" n n n;
pr " }\n"
+ | CBArrayAndLen (Extent64 n, len) ->
+ pr " py_%s = PyList_New (%s);\n" n len;
+ pr " size_t i_%s;\n" n;
+ pr " for (i_%s = 0; i_%s < %s; ++i_%s) {\n" n n len n;
+ pr " PyObject *py_e_%s = Py_BuildValue
(\"OO\",\n" n;
+ pr " PyLong_FromUnsignedLong (%s[i_%s].length),\n" n n;
+ pr " PyLong_FromUnsignedLong (%s[i_%s].flags));\n" n n;
+ pr " if (!py_e_%s) { PyErr_PrintEx (0); goto out; }\n" n;
+ pr " PyList_SET_ITEM (py_%s, i_%s, py_e_%s);\n" n n n;
+ pr " }\n"
| CBBytesIn _
| CBInt _
| CBInt64 _ -> ()
@@ -209,7 +221,7 @@ let
pr " py_args = Py_BuildValue (\"(\"";
List.iter (
function
- | CBArrayAndLen (UInt32 n, len) -> pr " \"O\""
+ | CBArrayAndLen (_, len) -> pr " \"O\""
| CBBytesIn (n, len) -> pr " \"y#\""
| CBInt n -> pr " \"i\""
| CBInt64 n -> pr " \"L\""
@@ -217,12 +229,13 @@ let
| CBString n -> pr " \"s\""
| CBUInt n -> pr " \"I\""
| CBUInt64 n -> pr " \"K\""
- | CBArrayAndLen _ | CBMutable _ -> assert false
+ | CBMutable _ -> assert false
) cbargs;
pr " \")\"";
List.iter (
function
| CBArrayAndLen (UInt32 n, _) -> pr ", py_%s" n
+ | CBArrayAndLen (Extent64 n, _) -> pr ", py_%s" n
| CBBytesIn (n, len) -> pr ", %s, (int) %s" n len
| CBMutable (Int n) -> pr ", py_%s" n
| CBInt n | CBInt64 n
@@ -259,7 +272,8 @@ let
pr " out:\n";
List.iter (
function
- | CBArrayAndLen (UInt32 n, _) ->
+ | CBArrayAndLen (UInt32 n, _)
+ | CBArrayAndLen (Extent64 n, _) ->
pr " Py_XDECREF (py_%s);\n" n
| CBMutable (Int n) ->
pr " if (py_%s) {\n" n;
@@ -307,6 +321,7 @@ let
cbname cbname cbname;
pr " .free = free_user_data };\n"
| Enum (n, _) -> pr " int %s;\n" n
+ | Extent64 _ -> assert false (* only used in extent64_closure *)
| Flags (n, _) ->
pr " uint32_t %s_u32;\n" n;
pr " unsigned int %s; /* really uint32_t */\n" n
@@ -360,6 +375,7 @@ let
| BytesPersistOut (_, count) -> pr " \"O\""
| Closure _ -> pr " \"O\""
| Enum _ -> pr " \"i\""
+ | Extent64 _ -> assert false (* only used in extent64_closure *)
| Flags _ -> pr " \"I\""
| Fd n | Int n -> pr " \"i\""
| Int64 n -> pr " \"L\""
@@ -388,6 +404,7 @@ let
| BytesOut (_, count) -> pr ", &%s" count
| Closure { cbname } -> pr ", &py_%s_fn" cbname
| Enum (n, _) -> pr ", &%s" n
+ | Extent64 _ -> assert false
| Flags (n, _) -> pr ", &%s" n
| Fd n | Int n | SizeT n | Int64 n -> pr ", &%s" n
| Path n -> pr ", PyUnicode_FSConverter, &py_%s" n
@@ -452,6 +469,7 @@ let
pr " Py_INCREF (py_%s_fn);\n" cbname;
pr " %s_user_data->fn = py_%s_fn;\n" cbname cbname
| Enum _ -> ()
+ | Extent64 _ -> ()
| Flags (n, _) -> pr " %s_u32 = %s;\n" n n
| Fd _ | Int _ -> ()
| Int64 n -> pr " %s_i64 = %s;\n" n n
@@ -484,6 +502,7 @@ let
| BytesPersistOut (n, _) -> pr ", %s_buf->data,
%s_buf->len" n n
| Closure { cbname } -> pr ", %s" cbname
| Enum (n, _) -> pr ", %s" n
+ | Extent64 _ -> assert false (* only used in extent64_closure *)
| Flags (n, _) -> pr ", %s_u32" n
| Fd n | Int n -> pr ", %s" n
| Int64 n -> pr ", %s_i64" n
@@ -532,6 +551,7 @@ let
| BytesPersistIn _ | BytesPersistOut _
| Closure _
| Enum _
+ | Extent64 _
| Flags _
| Fd _ | Int _
| Int64 _
@@ -577,6 +597,7 @@ let
| Closure { cbname } ->
pr " free_user_data (%s_user_data);\n" cbname
| Enum _ -> ()
+ | Extent64 _ -> ()
| Flags _ -> ()
| Fd _ | Int _ -> ()
| Int64 _ -> ()
@@ -820,6 +841,7 @@ let
| BytesPersistOut (n, _) -> n, None, Some (sprintf
"%s._o" n)
| Closure { cbname } -> cbname, None, None
| Enum (n, _) -> n, None, None
+ | Extent64 _ -> assert false (* only used in extent64_closure *)
| Flags (n, _) -> n, None, None
| Fd n | Int n -> n, None, None
| Int64 n -> n, None, None
diff --git a/ocaml/helpers.c b/ocaml/helpers.c
index 90333cd7..d15ffaf3 100644
--- a/ocaml/helpers.c
+++ b/ocaml/helpers.c
@@ -1,5 +1,5 @@
/* NBD client library in userspace
- * Copyright (C) 2013-2019 Red Hat Inc.
+ * Copyright (C) 2013-2021 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
@@ -112,6 +112,26 @@ nbd_internal_ocaml_alloc_int32_array (uint32_t *a, size_t
len)
CAMLreturn (rv);
}
+value
+nbd_internal_ocaml_alloc_extent64_array (nbd_extent *a, size_t len)
+{
+ CAMLparam0 ();
+ CAMLlocal3 (s, v, rv);
+ size_t i;
+
+ rv = caml_alloc (len, 0);
+ for (i = 0; i < len; ++i) {
+ s = caml_alloc (2, 0);
+ v = caml_copy_int64 (a[i].length);
+ Store_field (s, 0, v);
+ v = caml_copy_int32 (a[i].flags);
+ Store_field (s, 1, v);
+ Store_field (rv, i, s);
+ }
+
+ CAMLreturn (rv);
+}
+
/* Common code when an exception is raised in an OCaml callback and
* the wrapper has to deal with it. Callbacks are not supposed to
* raise exceptions, so we print it. We also handle Assert_failure
diff --git a/ocaml/nbd-c.h b/ocaml/nbd-c.h
index 9f362fa1..3b66049d 100644
--- a/ocaml/nbd-c.h
+++ b/ocaml/nbd-c.h
@@ -1,5 +1,5 @@
/* NBD client library in userspace
- * Copyright (C) 2013-2019 Red Hat Inc.
+ * Copyright (C) 2013-2021 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
@@ -61,6 +61,7 @@ extern void nbd_internal_ocaml_raise_closed (const char *func)
Noreturn;
extern const char **nbd_internal_ocaml_string_list (value);
extern value nbd_internal_ocaml_alloc_int32_array (uint32_t *, size_t);
+extern value nbd_internal_ocaml_alloc_extent64_array (nbd_extent *, size_t);
extern void nbd_internal_ocaml_exception_in_wrapper (const char *, value);
/* Extract an NBD handle from an OCaml heap value. */
diff --git a/golang/handle.go b/golang/handle.go
index c8d9485d..43a0e489 100644
--- a/golang/handle.go
+++ b/golang/handle.go
@@ -58,6 +58,12 @@ func (h *Libnbd) String() string {
return "&Libnbd{}"
}
+/* Used for block status callback. */
+type LibnbdExtent struct {
+ Length uint64 // length of the extent
+ Flags uint32 // flags describing properties of the extent
+}
+
/* All functions (except Close) return ([result,] LibnbdError). */
type LibnbdError struct {
Op string // operation which failed
--
2.33.1
Eric Blake
2021-Dec-03 23:17 UTC
[Libguestfs] [libnbd PATCH 08/13] block_status: Track 64-bit extents internally
When extended headers are in use, the server can send us 64-bit
extents, even for a 32-bit query (if the server knows the entire image
is data, for example). For maximum flexibility, we are thus better
off storing 64-bit lengths internally, even if we have to convert it
back to 32-bit lengths when invoking the user's 32-bit callback. The
next patch will then add a new API for letting the user access the
full 64-bit extent information. The goal is to let both APIs work all
the time, regardless of the size extents that the server actually
answered with.
Note that when using the old nbd_block_status() API with a server that
lacks extended headers, we now add a double-conversion speed penalty
(converting the server's 32-bit answer into 64-bit internally and back
to 32-bit for the callback). But the speed penalty will not be a
problem for applications using the new nbd_block_status_64() API (we
have to give a 64-bit answer no matter what the server answered), and
ideally the situation will become less common as more servers learn
extended headers. So for now I chose to unconditionally use a 64-bit
internal representation; but if it turns out to have noticeable
degredation, we could tweak things to conditionally retain 32-bit
internal representation for servers lacking extended headers at the
expense of more code maintenance.
One of the trickier aspects of this patch is auditing that both the
user's extent and our malloc'd shim get cleaned up once on all
possible paths, so that there is neither a leak nor a double free.
---
lib/internal.h | 7 +++-
generator/states-reply-structured.c | 31 ++++++++++-----
lib/handle.c | 4 +-
lib/rw.c | 59 ++++++++++++++++++++++++++++-
4 files changed, 85 insertions(+), 16 deletions(-)
diff --git a/lib/internal.h b/lib/internal.h
index 06f3a65c..4800df83 100644
--- a/lib/internal.h
+++ b/lib/internal.h
@@ -75,7 +75,7 @@ struct export {
struct command_cb {
union {
- nbd_extent_callback extent;
+ nbd_extent64_callback extent;
nbd_chunk_callback chunk;
nbd_list_callback list;
nbd_context_callback context;
@@ -286,7 +286,10 @@ struct nbd_handle {
/* When receiving block status, this is used. */
uint32_t bs_contextid;
- uint32_t *bs_entries;
+ union {
+ nbd_extent *normal; /* Our 64-bit preferred internal form */
+ uint32_t *narrow; /* 32-bit form of NBD_REPLY_TYPE_BLOCK_STATUS */
+ } bs_entries;
/* Commands which are waiting to be issued [meaning the request
* packet is sent to the server]. This is used as a simple linked
diff --git a/generator/states-reply-structured.c
b/generator/states-reply-structured.c
index a3e0e2ac..71c761e9 100644
--- a/generator/states-reply-structured.c
+++ b/generator/states-reply-structured.c
@@ -494,6 +494,7 @@ STATE_MACHINE {
REPLY.STRUCTURED_REPLY.RECV_BS_CONTEXTID:
struct command *cmd = h->reply_cmd;
uint32_t length;
+ uint32_t count;
switch (recv_into_rbuf (h)) {
case -1: SET_NEXT_STATE (%.DEAD); return 0;
@@ -508,15 +509,19 @@ STATE_MACHINE {
assert (cmd->type == NBD_CMD_BLOCK_STATUS);
assert (length >= 12);
length -= sizeof h->bs_contextid;
+ count = length / (2 * sizeof (uint32_t));
- free (h->bs_entries);
- h->bs_entries = malloc (length);
- if (h->bs_entries == NULL) {
+ /* Read raw data into a subset of h->bs_entries, then expand it
+ * into place later later during byte-swapping.
+ */
+ free (h->bs_entries.normal);
+ h->bs_entries.normal = malloc (count * sizeof *h->bs_entries.normal);
+ if (h->bs_entries.normal == NULL) {
SET_NEXT_STATE (%.DEAD);
set_error (errno, "malloc");
return 0;
}
- h->rbuf = h->bs_entries;
+ h->rbuf = h->bs_entries.narrow;
h->rlen = length;
SET_NEXT_STATE (%RECV_BS_ENTRIES);
}
@@ -528,6 +533,7 @@ STATE_MACHINE {
uint32_t count;
size_t i;
uint32_t context_id;
+ uint32_t *raw;
struct meta_context *meta_context;
switch (recv_into_rbuf (h)) {
@@ -542,15 +548,20 @@ STATE_MACHINE {
assert (cmd); /* guaranteed by CHECK */
assert (cmd->type == NBD_CMD_BLOCK_STATUS);
assert (CALLBACK_IS_NOT_NULL (cmd->cb.fn.extent));
- assert (h->bs_entries);
+ assert (h->bs_entries.normal);
assert (length >= 12);
- count = (length - sizeof h->bs_contextid) / sizeof *h->bs_entries;
+ count = (length - sizeof h->bs_contextid) / (2 * sizeof (uint32_t));
/* Need to byte-swap the entries returned, but apart from that we
- * don't validate them.
+ * don't validate them. Reverse order is essential, since we are
+ * expanding in-place from narrow to wider type.
*/
- for (i = 0; i < count; ++i)
- h->bs_entries[i] = be32toh (h->bs_entries[i]);
+ raw = h->bs_entries.narrow;
+ for (i = count; i > 0; ) {
+ --i;
+ h->bs_entries.normal[i].flags = be32toh (raw[i * 2 + 1]);
+ h->bs_entries.normal[i].length = be32toh (raw[i * 2]);
+ }
/* Look up the context ID. */
context_id = be32toh (h->bs_contextid);
@@ -566,7 +577,7 @@ STATE_MACHINE {
if (CALL_CALLBACK (cmd->cb.fn.extent,
meta_context->name, cmd->offset,
- h->bs_entries, count,
+ h->bs_entries.normal, count,
&error) == -1)
if (cmd->error == 0)
cmd->error = error ? error : EPROTO;
diff --git a/lib/handle.c b/lib/handle.c
index cbb37e89..74fe87ec 100644
--- a/lib/handle.c
+++ b/lib/handle.c
@@ -1,5 +1,5 @@
/* NBD client library in userspace
- * Copyright (C) 2013-2020 Red Hat Inc.
+ * Copyright (C) 2013-2021 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
@@ -123,7 +123,7 @@ nbd_close (struct nbd_handle *h)
/* Free user callbacks first. */
nbd_unlocked_clear_debug_callback (h);
- free (h->bs_entries);
+ free (h->bs_entries.normal);
nbd_internal_reset_size_and_flags (h);
nbd_internal_free_option (h);
free_cmd_list (h->cmds_to_issue);
diff --git a/lib/rw.c b/lib/rw.c
index 16c2e848..f36f4e15 100644
--- a/lib/rw.c
+++ b/lib/rw.c
@@ -42,6 +42,50 @@ wait_for_command (struct nbd_handle *h, int64_t cookie)
return r == -1 ? -1 : 0;
}
+/* Convert from 64-bit to 32-bit extent callback. */
+static int
+nbd_convert_extent (void *data, const char *metacontext, uint64_t offset,
+ nbd_extent *entries, size_t nr_entries, int *error)
+{
+ nbd_extent_callback *cb = data;
+ uint32_t *array = malloc (nr_entries * 2 * sizeof *array);
+ size_t i;
+ int ret;
+
+ if (array == NULL) {
+ set_error (*error = errno, "malloc");
+ return -1;
+ }
+
+ for (i = 0; i < nr_entries; i++) {
+ array[i * 2] = entries[i].length;
+ array[i * 2 + 1] = entries[i].flags;
+ /* If an extent is larger than 32 bits, silently truncate the rest
+ * of the server's response. Technically, such a server was
+ * non-compliant if the client did not negotiate extended headers,
+ * but it is easier to let the caller make progress than to make
+ * the call fail. Rather than track the connection's alignment,
+ * just blindly truncate the large extent to 4G-64M.
+ */
+ if (entries[i].length > UINT32_MAX) {
+ array[i++ * 2] = -MAX_REQUEST_SIZE;
+ break;
+ }
+ }
+
+ ret = CALL_CALLBACK (*cb, metacontext, offset, array, i * 2, error);
+ free (array);
+ return ret;
+}
+
+static void
+nbd_convert_extent_free (void *data)
+{
+ nbd_extent_callback *cb = data;
+ FREE_CALLBACK (*cb);
+ free (cb);
+}
+
/* Issue a read command and wait for the reply. */
int
nbd_unlocked_pread (struct nbd_handle *h, void *buf,
@@ -469,12 +513,23 @@ nbd_unlocked_aio_block_status (struct nbd_handle *h,
nbd_completion_callback *completion,
uint32_t flags)
{
- struct command_cb cb = { .fn.extent = *extent,
+ nbd_extent_callback *shim = malloc (sizeof *shim);
+ struct command_cb cb = { .fn.extent.callback = nbd_convert_extent,
+ .fn.extent.user_data = shim,
+ .fn.extent.free = nbd_convert_extent_free,
.completion = *completion };
+ if (shim == NULL) {
+ set_error (errno, "malloc");
+ return -1;
+ }
+ *shim = *extent;
+ SET_CALLBACK_TO_NULL (*extent);
+
if (h->strict & LIBNBD_STRICT_COMMANDS) {
if (!h->structured_replies) {
set_error (ENOTSUP, "server does not support structured
replies");
+ FREE_CALLBACK (cb.fn.extent);
return -1;
}
@@ -482,11 +537,11 @@ nbd_unlocked_aio_block_status (struct nbd_handle *h,
set_error (ENOTSUP, "did not negotiate any metadata contexts, "
"either you did not call nbd_add_meta_context before
"
"connecting or the server does not support it");
+ FREE_CALLBACK (cb.fn.extent);
return -1;
}
}
- SET_CALLBACK_TO_NULL (*extent);
SET_CALLBACK_TO_NULL (*completion);
return nbd_internal_command_common (h, flags, NBD_CMD_BLOCK_STATUS, offset,
count, EINVAL, NULL, &cb);
--
2.33.1
Eric Blake
2021-Dec-03 23:17 UTC
[Libguestfs] [libnbd PATCH 09/13] block_status: Accept 64-bit extents during block status
Support a server giving us a 64-bit extent. Note that the protocol
says a server should not give a 64-bit answer when extended headers
are not negotiated, but since the client's size is merely a hint, it
is possible for a server to have a 64-bit answer even when the
original query was 32 bits. At any rate, it is just as easy for us to
always support the new chunk type as it is to complain when it is used
incorrectly by the server, and the user's 32-bit callback doesn't have
to care which size the server's result used (either the server's
result was a 32-bit value, or our shim silently truncates it, but the
user still makes progress). Of course, until a later patch enables
extended headers negotiation, no compliant server will trigger the new
code here.
Implementation-wise, we don't care if we will be narrowing from the
server's 16-byte extent (including explicit padding) to a 12-byte
struct, or if our 'nbd_extent' type has implicit padding and is thus
also 16 bytes; either way, the order of our byte-swapping traversal is
safe.
---
lib/internal.h | 1 +
generator/states-reply-structured.c | 75 +++++++++++++++++++++++------
2 files changed, 60 insertions(+), 16 deletions(-)
diff --git a/lib/internal.h b/lib/internal.h
index 4800df83..97abf4f2 100644
--- a/lib/internal.h
+++ b/lib/internal.h
@@ -289,6 +289,7 @@ struct nbd_handle {
union {
nbd_extent *normal; /* Our 64-bit preferred internal form */
uint32_t *narrow; /* 32-bit form of NBD_REPLY_TYPE_BLOCK_STATUS */
+ struct nbd_block_descriptor_ext *wide; /* NBD_REPLY_TYPE_BLOCK_STATUS_EXT
*/
} bs_entries;
/* Commands which are waiting to be issued [meaning the request
diff --git a/generator/states-reply-structured.c
b/generator/states-reply-structured.c
index 71c761e9..29b1c3d8 100644
--- a/generator/states-reply-structured.c
+++ b/generator/states-reply-structured.c
@@ -22,6 +22,8 @@
#include <stdint.h>
#include <inttypes.h>
+#include "minmax.h"
+
/* Structured reply must be completely inside the bounds of the
* requesting command.
*/
@@ -202,7 +204,8 @@ STATE_MACHINE {
SET_NEXT_STATE (%RECV_OFFSET_HOLE);
return 0;
}
- else if (type == NBD_REPLY_TYPE_BLOCK_STATUS) {
+ else if (type == NBD_REPLY_TYPE_BLOCK_STATUS ||
+ type == NBD_REPLY_TYPE_BLOCK_STATUS_EXT) {
if (cmd->type != NBD_CMD_BLOCK_STATUS) {
SET_NEXT_STATE (%.DEAD);
set_error (0, "invalid command for receiving block-status chunk,
"
@@ -211,12 +214,19 @@ STATE_MACHINE {
cmd->type);
return 0;
}
- /* XXX We should be able to skip the bad reply in these two cases. */
- if (length < 12 || ((length-4) & 7) != 0) {
+ /* XXX We should be able to skip the bad reply in these cases. */
+ if (type == NBD_REPLY_TYPE_BLOCK_STATUS &&
+ (length < 12 || (length-4) % (2 * sizeof(uint32_t)))) {
SET_NEXT_STATE (%.DEAD);
set_error (0, "invalid length in NBD_REPLY_TYPE_BLOCK_STATUS");
return 0;
}
+ if (type == NBD_REPLY_TYPE_BLOCK_STATUS_EXT &&
+ (length < 20 || (length-4) % sizeof(struct
nbd_block_descriptor_ext))) {
+ SET_NEXT_STATE (%.DEAD);
+ set_error (0, "invalid length in
NBD_REPLY_TYPE_BLOCK_STATUS_EXT");
+ return 0;
+ }
if (CALLBACK_IS_NULL (cmd->cb.fn.extent)) {
SET_NEXT_STATE (%.DEAD);
set_error (0, "not expecting NBD_REPLY_TYPE_BLOCK_STATUS
here");
@@ -495,6 +505,7 @@ STATE_MACHINE {
struct command *cmd = h->reply_cmd;
uint32_t length;
uint32_t count;
+ uint16_t type;
switch (recv_into_rbuf (h)) {
case -1: SET_NEXT_STATE (%.DEAD); return 0;
@@ -504,24 +515,33 @@ STATE_MACHINE {
return 0;
case 0:
length = h->sbuf.sr.hdr.structured_reply.length; /* normalized in CHECK
*/
+ type = be16toh (h->sbuf.sr.hdr.structured_reply.type);
assert (cmd); /* guaranteed by CHECK */
assert (cmd->type == NBD_CMD_BLOCK_STATUS);
assert (length >= 12);
length -= sizeof h->bs_contextid;
- count = length / (2 * sizeof (uint32_t));
+ if (type == NBD_REPLY_TYPE_BLOCK_STATUS)
+ count = length / (2 * sizeof (uint32_t));
+ else {
+ assert (type == NBD_REPLY_TYPE_BLOCK_STATUS_EXT);
+ /* XXX Insist on h->extended_headers? */
+ count = length / sizeof (struct nbd_block_descriptor_ext);
+ }
- /* Read raw data into a subset of h->bs_entries, then expand it
+ /* Read raw data into an overlap of h->bs_entries, then move it
* into place later later during byte-swapping.
*/
free (h->bs_entries.normal);
- h->bs_entries.normal = malloc (count * sizeof *h->bs_entries.normal);
+ h->bs_entries.normal = malloc (MAX (count * sizeof
*h->bs_entries.normal,
+ length));
if (h->bs_entries.normal == NULL) {
SET_NEXT_STATE (%.DEAD);
set_error (errno, "malloc");
return 0;
}
- h->rbuf = h->bs_entries.narrow;
+ h->rbuf = type == NBD_REPLY_TYPE_BLOCK_STATUS
+ ? h->bs_entries.narrow : (void *) h->bs_entries.wide;
h->rlen = length;
SET_NEXT_STATE (%RECV_BS_ENTRIES);
}
@@ -533,7 +553,7 @@ STATE_MACHINE {
uint32_t count;
size_t i;
uint32_t context_id;
- uint32_t *raw;
+ uint16_t type;
struct meta_context *meta_context;
switch (recv_into_rbuf (h)) {
@@ -544,23 +564,46 @@ STATE_MACHINE {
return 0;
case 0:
length = h->sbuf.sr.hdr.structured_reply.length; /* normalized in CHECK
*/
+ type = be16toh (h->sbuf.sr.hdr.structured_reply.type);
assert (cmd); /* guaranteed by CHECK */
assert (cmd->type == NBD_CMD_BLOCK_STATUS);
assert (CALLBACK_IS_NOT_NULL (cmd->cb.fn.extent));
assert (h->bs_entries.normal);
assert (length >= 12);
- count = (length - sizeof h->bs_contextid) / (2 * sizeof (uint32_t));
+ length -= sizeof h->bs_contextid;
/* Need to byte-swap the entries returned, but apart from that we
- * don't validate them. Reverse order is essential, since we are
- * expanding in-place from narrow to wider type.
+ * don't validate them.
*/
- raw = h->bs_entries.narrow;
- for (i = count; i > 0; ) {
- --i;
- h->bs_entries.normal[i].flags = be32toh (raw[i * 2 + 1]);
- h->bs_entries.normal[i].length = be32toh (raw[i * 2]);
+ if (type == NBD_REPLY_TYPE_BLOCK_STATUS) {
+ uint32_t *raw = h->bs_entries.narrow;
+
+ /* Expanding in-place from narrow to wide, must use reverse order. */
+ count = length / (2 * sizeof (uint32_t));
+ for (i = count; i > 0; ) {
+ --i;
+ h->bs_entries.normal[i].flags = be32toh (raw[i * 2 + 1]);
+ h->bs_entries.normal[i].length = be32toh (raw[i * 2]);
+ }
+ }
+ else {
+ struct nbd_block_descriptor_ext *wide = h->bs_entries.wide;
+
+ /* ABI determines whether nbd_extent is 12 or 16 bytes, but the
+ * server sent us 16 bytes, so we must process in forward order.
+ */
+ assert (type == NBD_REPLY_TYPE_BLOCK_STATUS_EXT);
+ count = length / sizeof (struct nbd_block_descriptor_ext);
+ for (i = 0; i < count; i++) {
+ h->bs_entries.normal[i].length = be64toh (wide[i].length);
+ h->bs_entries.normal[i].flags = be32toh (wide[i].status_flags);
+ if (wide[i].pad) {
+ set_error (0, "server sent non-zero padding in block
status");
+ SET_NEXT_STATE(%.DEAD);
+ return 0;
+ }
+ }
}
/* Look up the context ID. */
--
2.33.1
Eric Blake
2021-Dec-03 23:17 UTC
[Libguestfs] [libnbd PATCH 10/13] api: Add [aio_]nbd_block_status_64
Overcome the inherent 32-bit limitation of our existing
nbd_block_status command by adding a 64-bit variant. The command sent
to the server does not change, but the user's callback is now handed
64-bit information regardless of whether the server replies with 32-
or 64-bit extents.
Unit tests prove that the new API works in each of C, Python, OCaml,
and Go bindings. We can also get rid of the temporary hack added to
appease the compiler in an earlier patch.
---
generator/API.ml | 138 +++++++++++++++++++---
generator/OCaml.ml | 1 -
generator/Python.ml | 1 -
lib/rw.c | 48 ++++++--
python/t/465-block-status-64.py | 56 +++++++++
ocaml/tests/Makefile.am | 5 +-
ocaml/tests/test_465_block_status_64.ml | 58 +++++++++
tests/meta-base-allocation.c | 111 +++++++++++++++--
golang/Makefile.am | 3 +-
golang/libnbd_465_block_status_64_test.go | 119 +++++++++++++++++++
10 files changed, 503 insertions(+), 37 deletions(-)
create mode 100644 python/t/465-block-status-64.py
create mode 100644 ocaml/tests/test_465_block_status_64.ml
create mode 100644 golang/libnbd_465_block_status_64_test.go
diff --git a/generator/API.ml b/generator/API.ml
index 70ae721d..1a452a24 100644
--- a/generator/API.ml
+++ b/generator/API.ml
@@ -1071,7 +1071,7 @@ "add_meta_context", {
During connection libnbd can negotiate zero or more metadata
contexts with the server. Metadata contexts are features (such
as C<\"base:allocation\">) which describe information returned
-by the L<nbd_block_status(3)> command (for
C<\"base:allocation\">
+by the L<nbd_block_status_64(3)> command (for
C<\"base:allocation\">
this is whether blocks of data are allocated, zero or sparse).
This call adds one metadata context to the list to be negotiated.
@@ -1098,7 +1098,7 @@ "add_meta_context", {
Other metadata contexts are server-specific, but include
C<\"qemu:dirty-bitmap:...\"> and
C<\"qemu:allocation-depth\"> for
qemu-nbd (see qemu-nbd I<-B> and I<-A> options).";
- see_also = [Link "block_status"; Link
"can_meta_context";
+ see_also = [Link "block_status_64"; Link
"can_meta_context";
Link "get_nr_meta_contexts"; Link
"get_meta_context";
Link "clear_meta_contexts"];
};
@@ -1111,14 +1111,14 @@ "get_nr_meta_contexts", {
During connection libnbd can negotiate zero or more metadata
contexts with the server. Metadata contexts are features (such
as C<\"base:allocation\">) which describe information returned
-by the L<nbd_block_status(3)> command (for
C<\"base:allocation\">
+by the L<nbd_block_status_64(3)> command (for
C<\"base:allocation\">
this is whether blocks of data are allocated, zero or sparse).
This command returns how many meta contexts have been added to
the list to request from the server via L<nbd_add_meta_context(3)>.
The server is not obligated to honor all of the requests; to see
what it actually supports, see L<nbd_can_meta_context(3)>.";
- see_also = [Link "block_status"; Link
"can_meta_context";
+ see_also = [Link "block_status_64"; Link
"can_meta_context";
Link "add_meta_context"; Link
"get_meta_context";
Link "clear_meta_contexts"];
};
@@ -1131,13 +1131,13 @@ "get_meta_context", {
During connection libnbd can negotiate zero or more metadata
contexts with the server. Metadata contexts are features (such
as C<\"base:allocation\">) which describe information returned
-by the L<nbd_block_status(3)> command (for
C<\"base:allocation\">
+by the L<nbd_block_status_64(3)> command (for
C<\"base:allocation\">
this is whether blocks of data are allocated, zero or sparse).
This command returns the i'th meta context request, as added by
L<nbd_add_meta_context(3)>, and bounded by
L<nbd_get_nr_meta_contexts(3)>.";
- see_also = [Link "block_status"; Link
"can_meta_context";
+ see_also = [Link "block_status_64"; Link
"can_meta_context";
Link "add_meta_context"; Link
"get_nr_meta_contexts";
Link "clear_meta_contexts"];
};
@@ -1151,7 +1151,7 @@ "clear_meta_contexts", {
During connection libnbd can negotiate zero or more metadata
contexts with the server. Metadata contexts are features (such
as C<\"base:allocation\">) which describe information returned
-by the L<nbd_block_status(3)> command (for
C<\"base:allocation\">
+by the L<nbd_block_status_64(3)> command (for
C<\"base:allocation\">
this is whether blocks of data are allocated, zero or sparse).
This command resets the list of meta contexts to request back to
@@ -1160,7 +1160,7 @@ "clear_meta_contexts", {
negotiation mode is selected (see L<nbd_set_opt_mode(3)>), for
altering the list of attempted contexts between subsequent export
queries.";
- see_also = [Link "block_status"; Link
"can_meta_context";
+ see_also = [Link "block_status_64"; Link
"can_meta_context";
Link "add_meta_context"; Link
"get_nr_meta_contexts";
Link "get_meta_context"; Link
"set_opt_mode"];
};
@@ -1727,7 +1727,7 @@ "can_meta_context", {
^ non_blocking_test_call_description;
see_also = [SectionLink "Flag calls"; Link "opt_info";
Link "add_meta_context";
- Link "block_status"; Link
"aio_block_status"];
+ Link "block_status_64"; Link
"aio_block_status_64"];
};
"get_protocol", {
@@ -2124,7 +2124,7 @@ "block_status", {
optargs = [ OFlags ("flags", cmd_flags, Some
["REQ_ONE"]) ];
ret = RErr;
permitted_states = [ Connected ];
- shortdesc = "send block status command to the NBD server";
+ shortdesc = "send block status command to the NBD server, with 32-bit
callback";
longdesc = "\
Issue the block status command to the NBD server. If
supported by the server, this causes metadata context
@@ -2139,7 +2139,12 @@ "block_status", {
The NBD protocol does not yet have a way for a client to learn if
the server will enforce an even smaller maximum block status size,
although a future extension may add a constraint visible in
-L<nbd_get_block_size(3)>.
+L<nbd_get_block_size(3)>. Furthermore, this function is inherently
+limited to reporting extents no larger than 32 bits in size. If the
+server replies with a larger extent, the length of that extent will
+be truncated to just below 32 bits and any further extents from the
+server will be ignored. To get the full extent information from a
+server that supports 64-bit extents, you must use
L<nbd_block_status_64(3)>.
Depending on which metadata contexts were enabled before
connecting (see L<nbd_add_meta_context(3)>) and which are
@@ -2182,10 +2187,79 @@ "block_status", {
does not exceed C<count> bytes; however, libnbd does not
validate that the server obeyed the flag."
^ strict_call_description;
- see_also = [Link "add_meta_context"; Link
"can_meta_context";
+ see_also = [Link "block_status_64";
+ Link "add_meta_context"; Link
"can_meta_context";
Link "aio_block_status"; Link
"set_strict_mode"];
};
+ "block_status_64", {
+ default_call with
+ args = [ UInt64 "count"; UInt64 "offset"; Closure
extent64_closure ];
+ optargs = [ OFlags ("flags", cmd_flags, Some
["REQ_ONE"]) ];
+ ret = RErr;
+ permitted_states = [ Connected ];
+ shortdesc = "send block status command to the NBD server, with 64-bit
callback";
+ longdesc = "\
+Issue the block status command to the NBD server. If
+supported by the server, this causes metadata context
+information about blocks beginning from the specified
+offset to be returned. The C<count> parameter is a hint: the
+server may choose to return less status, or the final block
+may extend beyond the requested range. If multiple contexts
+are supported, the number of blocks and cumulative length
+of those blocks need not be identical between contexts.
+
+Note that not all servers can support a C<count> of 4GiB or larger.
+The NBD protocol does not yet have a way for a client to learn if
+the server will enforce an even smaller maximum block status size,
+although a future extension may add a constraint visible in
+L<nbd_get_block_size(3)>.
+
+Depending on which metadata contexts were enabled before
+connecting (see L<nbd_add_meta_context(3)>) and which are
+supported by the server (see L<nbd_can_meta_context(3)>) this call
+returns information about extents by calling back to the
+C<extent64> function. The callback cannot call C<nbd_*> APIs on
the
+same handle since it holds the handle lock and will
+cause a deadlock. If the callback returns C<-1>, and no earlier
+error has been detected, then the overall block status command
+will fail with any non-zero value stored into the callback's
+C<error> parameter (with a default of C<EPROTO>); but any further
+contexts will still invoke the callback.
+
+The C<extent64> function is called once per type of metadata available,
+with the C<user_data> passed to this function. The C<metacontext>
+parameter is a string such as C<\"base:allocation\">. The
C<entries>
+array is an array of B<nbd_extent> structs, containing length (in bytes)
+of the block and a status/flags field which is specific to the metadata
+context. (The number of array entries passed to the function is
+C<nr_entries>.) The NBD protocol document in the section about
+C<NBD_REPLY_TYPE_BLOCK_STATUS_EXT> describes the meaning of this array;
+for contexts known to libnbd, B<E<lt>libnbd.hE<gt>> contains
constants
+beginning with C<LIBNBD_STATE_> that may help decipher the values.
+On entry to the callback, the C<error> parameter contains the errno
+value of any previously detected error.
+
+It is possible for the extent function to be called
+more times than you expect (if the server is buggy),
+so always check the C<metacontext> field to ensure you
+are receiving the data you expect. It is also possible
+that the extent function is not called at all, even for
+metadata contexts that you requested. This indicates
+either that the server doesn't support the context
+or for some other reason cannot return the data.
+
+The C<flags> parameter may be C<0> for no flags, or may contain
+C<LIBNBD_CMD_FLAG_REQ_ONE> meaning that the server should
+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."
+^ strict_call_description;
+ see_also = [Link "block_status";
+ Link "add_meta_context"; Link
"can_meta_context";
+ Link "aio_block_status_64"; Link
"set_strict_mode"];
+ };
+
"poll", {
default_call with
args = [ Int "timeout" ]; ret = RInt;
@@ -2634,7 +2708,7 @@ "aio_block_status", {
OFlags ("flags", cmd_flags, Some
["REQ_ONE"]) ];
ret = RCookie;
permitted_states = [ Connected ];
- shortdesc = "send block status command to the NBD server";
+ shortdesc = "send block status command to the NBD server, with 32-bit
callback";
longdesc = "\
Send the block status command to the NBD server.
@@ -2642,13 +2716,45 @@ "aio_block_status", {
Or supply the optional C<completion_callback> which will be invoked
as described in L<libnbd(3)/Completion callbacks>.
-Other parameters behave as documented in L<nbd_block_status(3)>."
+Other parameters behave as documented in L<nbd_block_status(3)>.
+
+This function is inherently limited to reporting extents no larger
+than 32 bits in size. If the server replies with a larger extent,
+the length of that extent will be truncated to just below 32 bits
+and any further extents from the server will be ignored. To get
+the full extent information from a server that supports 64-bit
+extents, you must use L<nbd_aio_block_status_64(3)>.
+"
^ strict_call_description;
see_also = [SectionLink "Issuing asynchronous commands";
+ Link "aio_block_status_64";
Link "can_meta_context"; Link
"block_status";
Link "set_strict_mode"];
};
+ "aio_block_status_64", {
+ default_call with
+ args = [ UInt64 "count"; UInt64 "offset"; Closure
extent64_closure ];
+ optargs = [ OClosure completion_closure;
+ OFlags ("flags", cmd_flags, Some
["REQ_ONE"]) ];
+ ret = RCookie;
+ permitted_states = [ Connected ];
+ shortdesc = "send block status command to the NBD server";
+ longdesc = "\
+Send the block status command to the NBD server.
+
+To check if the command completed, call L<nbd_aio_command_completed(3)>.
+Or supply the optional C<completion_callback> which will be invoked
+as described in L<libnbd(3)/Completion callbacks>.
+
+Other parameters behave as documented in L<nbd_block_status_64(3)>."
+^ strict_call_description;
+ see_also = [SectionLink "Issuing asynchronous commands";
+ Link "aio_block_status";
+ Link "can_meta_context"; Link
"block_status_64";
+ Link "set_strict_mode"];
+ };
+
"aio_get_fd", {
default_call with
args = []; ret = RFd;
@@ -3130,6 +3236,10 @@ let first_version "get_private_data", (1,
8);
"get_uri", (1, 8);
+ (* Added in 1.11.x development cycle, will be stable and supported in 1.12.
*)
+ "block_status_64", (1, 12);
+ "aio_block_status_64", (1, 12);
+
(* These calls are proposed for a future version of libnbd, but
* have not been added to any released version so far.
"get_tls_certificates", (1, ??);
diff --git a/generator/OCaml.ml b/generator/OCaml.ml
index eac42668..fd9dfdec 100644
--- a/generator/OCaml.ml
+++ b/generator/OCaml.ml
@@ -554,7 +554,6 @@ let
pr "}\n";
pr "\n";
pr "static int\n";
- pr "__attribute__((unused)) /* XXX temporary hack */\n";
pr "%s_wrapper " cbname;
C.print_cbarg_list ~wrap:true cbargs;
pr "\n";
diff --git a/generator/Python.ml b/generator/Python.ml
index 4212e2ac..e32270cf 100644
--- a/generator/Python.ml
+++ b/generator/Python.ml
@@ -158,7 +158,6 @@ let
let print_python_closure_wrapper { cbname; cbargs } pr "/* Wrapper for
%s callback. */\n" cbname;
pr "static int\n";
- pr "__attribute__((unused)) /* XXX temporary hack */\n";
pr "%s_wrapper " cbname;
C.print_cbarg_list ~wrap:true cbargs;
pr "\n";
diff --git a/lib/rw.c b/lib/rw.c
index f36f4e15..5454adb7 100644
--- a/lib/rw.c
+++ b/lib/rw.c
@@ -194,7 +194,7 @@ nbd_unlocked_zero (struct nbd_handle *h,
return wait_for_command (h, cookie);
}
-/* Issue a block status command and wait for the reply. */
+/* Issue a block status command and wait for the reply, 32-bit callback. */
int
nbd_unlocked_block_status (struct nbd_handle *h,
uint64_t count, uint64_t offset,
@@ -212,6 +212,25 @@ nbd_unlocked_block_status (struct nbd_handle *h,
return wait_for_command (h, cookie);
}
+/* Issue a block status command and wait for the reply, 64-bit callback. */
+int
+nbd_unlocked_block_status_64 (struct nbd_handle *h,
+ uint64_t count, uint64_t offset,
+ nbd_extent64_callback *extent64,
+ uint32_t flags)
+{
+ int64_t cookie;
+ nbd_completion_callback c = NBD_NULL_COMPLETION;
+
+ cookie = nbd_unlocked_aio_block_status_64 (h, count, offset, extent64,
&c,
+ flags);
+ if (cookie == -1)
+ return -1;
+
+ assert (CALLBACK_IS_NULL (*extent64));
+ return wait_for_command (h, cookie);
+}
+
/* count_err represents the errno to return if bounds check fail */
int64_t
nbd_internal_command_common (struct nbd_handle *h,
@@ -514,10 +533,10 @@ nbd_unlocked_aio_block_status (struct nbd_handle *h,
uint32_t flags)
{
nbd_extent_callback *shim = malloc (sizeof *shim);
- struct command_cb cb = { .fn.extent.callback = nbd_convert_extent,
- .fn.extent.user_data = shim,
- .fn.extent.free = nbd_convert_extent_free,
- .completion = *completion };
+ nbd_extent64_callback wrapper = { .callback = nbd_convert_extent,
+ .user_data = shim,
+ .free = nbd_convert_extent_free, };
+ int ret;
if (shim == NULL) {
set_error (errno, "malloc");
@@ -526,10 +545,25 @@ nbd_unlocked_aio_block_status (struct nbd_handle *h,
*shim = *extent;
SET_CALLBACK_TO_NULL (*extent);
+ ret = nbd_unlocked_aio_block_status_64 (h, count, offset, &wrapper,
+ completion, flags);
+ FREE_CALLBACK (wrapper);
+ return ret;
+}
+
+int64_t
+nbd_unlocked_aio_block_status_64 (struct nbd_handle *h,
+ uint64_t count, uint64_t offset,
+ nbd_extent64_callback *extent64,
+ nbd_completion_callback *completion,
+ uint32_t flags)
+{
+ struct command_cb cb = { .fn.extent = *extent64,
+ .completion = *completion };
+
if (h->strict & LIBNBD_STRICT_COMMANDS) {
if (!h->structured_replies) {
set_error (ENOTSUP, "server does not support structured
replies");
- FREE_CALLBACK (cb.fn.extent);
return -1;
}
@@ -537,11 +571,11 @@ nbd_unlocked_aio_block_status (struct nbd_handle *h,
set_error (ENOTSUP, "did not negotiate any metadata contexts, "
"either you did not call nbd_add_meta_context before
"
"connecting or the server does not support it");
- FREE_CALLBACK (cb.fn.extent);
return -1;
}
}
+ SET_CALLBACK_TO_NULL (*extent64);
SET_CALLBACK_TO_NULL (*completion);
return nbd_internal_command_common (h, flags, NBD_CMD_BLOCK_STATUS, offset,
count, EINVAL, NULL, &cb);
diff --git a/python/t/465-block-status-64.py b/python/t/465-block-status-64.py
new file mode 100644
index 00000000..94d7b465
--- /dev/null
+++ b/python/t/465-block-status-64.py
@@ -0,0 +1,56 @@
+# libnbd Python bindings
+# Copyright (C) 2010-2021 Red Hat Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+import os
+
+import nbd
+
+script = "%s/../tests/meta-base-allocation.sh" %
os.getenv("srcdir", ".")
+
+h = nbd.NBD()
+h.add_meta_context("base:allocation")
+h.connect_command(["nbdkit", "-s",
"--exit-with-parent", "-v", "sh", script])
+
+entries = []
+
+
+def f(user_data, metacontext, offset, e, err):
+ global entries
+ assert user_data == 42
+ assert err.value == 0
+ if metacontext != "base:allocation":
+ return
+ entries = e
+
+
+h.block_status_64(65536, 0, lambda *args: f(42, *args))
+print("entries = %r" % entries)
+assert entries == [(8192, 0),
+ (8192, 1),
+ (16384, 3),
+ (16384, 2),
+ (16384, 0)]
+
+h.block_status_64(1024, 32256, lambda *args: f(42, *args))
+print("entries = %r" % entries)
+assert entries == [(512, 3),
+ (16384, 2)]
+
+h.block_status_64(1024, 32256, lambda *args: f(42, *args),
+ nbd.CMD_FLAG_REQ_ONE)
+print("entries = %r" % entries)
+assert entries == [(512, 3)]
diff --git a/ocaml/tests/Makefile.am b/ocaml/tests/Makefile.am
index 6fac8b7c..489b030a 100644
--- a/ocaml/tests/Makefile.am
+++ b/ocaml/tests/Makefile.am
@@ -1,5 +1,5 @@
# nbd client library in userspace
-# Copyright (C) 2013-2020 Red Hat Inc.
+# Copyright (C) 2013-2021 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
@@ -35,6 +35,7 @@ EXTRA_DIST = \
test_405_pread_structured.ml \
test_410_pwrite.ml \
test_460_block_status.ml \
+ test_465_block_status_64.ml \
test_500_aio_pread.ml \
test_505_aio_pread_structured_callback.ml \
test_510_aio_pwrite.ml \
@@ -62,6 +63,7 @@ tests_bc = \
test_405_pread_structured.bc \
test_410_pwrite.bc \
test_460_block_status.bc \
+ test_465_block_status_64.bc \
test_500_aio_pread.bc \
test_505_aio_pread_structured_callback.bc \
test_510_aio_pwrite.bc \
@@ -86,6 +88,7 @@ tests_opt = \
test_405_pread_structured.opt \
test_410_pwrite.opt \
test_460_block_status.opt \
+ test_465_block_status_64.opt \
test_500_aio_pread.opt \
test_505_aio_pread_structured_callback.opt \
test_510_aio_pwrite.opt \
diff --git a/ocaml/tests/test_465_block_status_64.ml
b/ocaml/tests/test_465_block_status_64.ml
new file mode 100644
index 00000000..a27a8ad4
--- /dev/null
+++ b/ocaml/tests/test_465_block_status_64.ml
@@ -0,0 +1,58 @@
+(* hey emacs, this is OCaml code: -*- tuareg -*- *)
+(* libnbd OCaml test case
+ * Copyright (C) 2013-2021 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
+ *)
+
+open Printf
+
+let script + try
+ let srcdir = Sys.getenv "srcdir" in
+ sprintf "%s/../../tests/meta-base-allocation.sh" srcdir
+ with
+ Not_found -> failwith "error: srcdir is not defined"
+
+let entries = ref [||]
+let f user_data metacontext offset e err + assert (user_data = 42);
+ assert (!err = 0);
+ if metacontext = "base:allocation" then
+ entries := e;
+ 0
+
+let () + let nbd = NBD.create () in
+ NBD.add_meta_context nbd "base:allocation";
+ NBD.connect_command nbd ["nbdkit"; "-s";
"--exit-with-parent"; "-v";
+ "sh"; script];
+
+ NBD.block_status_64 nbd 65536_L 0_L (f 42);
+ assert (!entries = [| 8192_L, 0_l;
+ 8192_L, 1_l;
+ 16384_L, 3_l;
+ 16384_L, 2_l;
+ 16384_L, 0_l |]);
+
+ NBD.block_status_64 nbd 1024_L 32256_L (f 42);
+ assert (!entries = [| 512_L, 3_l;
+ 16384_L, 2_l |]);
+
+ let flags = let open NBD.CMD_FLAG in [REQ_ONE] in
+ NBD.block_status_64 nbd 1024_L 32256_L (f 42) ~flags;
+ assert (!entries = [| 512_L, 3_l |])
+
+let () = Gc.compact ()
diff --git a/tests/meta-base-allocation.c b/tests/meta-base-allocation.c
index 401c0c88..a2847d6e 100644
--- a/tests/meta-base-allocation.c
+++ b/tests/meta-base-allocation.c
@@ -1,5 +1,5 @@
/* NBD client library in userspace
- * Copyright (C) 2013-2020 Red Hat Inc.
+ * Copyright (C) 2013-2021 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
@@ -32,10 +32,13 @@
#define BOGUS_CONTEXT "x-libnbd:nosuch"
-static int check_extent (void *data,
- const char *metacontext,
- uint64_t offset,
- uint32_t *entries, size_t nr_entries, int *error);
+static int check_extent32 (void *data, const char *metacontext,
+ uint64_t offset,
+ uint32_t *entries, size_t nr_entries, int *error);
+
+static int check_extent64 (void *data, const char *metacontext,
+ uint64_t offset,
+ nbd_extent *entries, size_t nr_entries, int *error);
int
main (int argc, char *argv[])
@@ -149,27 +152,51 @@ main (int argc, char *argv[])
/* Read the block status. */
id = 1;
if (nbd_block_status (nbd, 65536, 0,
- (nbd_extent_callback) { .callback = check_extent,
.user_data = &id },
+ (nbd_extent_callback) { .callback = check_extent32,
+ .user_data = &id },
0) == -1) {
fprintf (stderr, "%s\n", nbd_get_error ());
exit (EXIT_FAILURE);
}
+ if (nbd_block_status_64 (nbd, 65536, 0,
+ (nbd_extent64_callback) { .callback =
check_extent64,
+ .user_data = &id },
+ 0) == -1) {
+ fprintf (stderr, "%s\n", nbd_get_error ());
+ exit (EXIT_FAILURE);
+ }
id = 2;
if (nbd_block_status (nbd, 1024, 32768-512,
- (nbd_extent_callback) { .callback = check_extent,
.user_data = &id },
+ (nbd_extent_callback) { .callback = check_extent32,
+ .user_data = &id },
0) == -1) {
fprintf (stderr, "%s\n", nbd_get_error ());
exit (EXIT_FAILURE);
}
+ if (nbd_block_status_64 (nbd, 1024, 32768-512,
+ (nbd_extent64_callback) { .callback =
check_extent64,
+ .user_data = &id },
+ 0) == -1) {
+ fprintf (stderr, "%s\n", nbd_get_error ());
+ exit (EXIT_FAILURE);
+ }
id = 3;
if (nbd_block_status (nbd, 1024, 32768-512,
- (nbd_extent_callback) { .callback = check_extent,
.user_data = &id },
+ (nbd_extent_callback) { .callback = check_extent32,
+ .user_data = &id },
LIBNBD_CMD_FLAG_REQ_ONE) == -1) {
fprintf (stderr, "%s\n", nbd_get_error ());
exit (EXIT_FAILURE);
}
+ if (nbd_block_status_64 (nbd, 1024, 32768-512,
+ (nbd_extent64_callback) { .callback =
check_extent64,
+ .user_data = &id },
+ LIBNBD_CMD_FLAG_REQ_ONE) == -1) {
+ fprintf (stderr, "%s\n", nbd_get_error ());
+ exit (EXIT_FAILURE);
+ }
if (nbd_shutdown (nbd, 0) == -1) {
fprintf (stderr, "%s\n", nbd_get_error ());
@@ -181,10 +208,8 @@ main (int argc, char *argv[])
}
static int
-check_extent (void *data,
- const char *metacontext,
- uint64_t offset,
- uint32_t *entries, size_t nr_entries, int *error)
+check_extent32 (void *data, const char *metacontext, uint64_t offset,
+ uint32_t *entries, size_t nr_entries, int *error)
{
size_t i;
int id;
@@ -238,3 +263,65 @@ check_extent (void *data,
return 0;
}
+
+static int
+check_extent64 (void *data, const char *metacontext, uint64_t offset,
+ nbd_extent *entries, size_t nr_entries, int *error)
+{
+ size_t i;
+ int id;
+
+ id = * (int *)data;
+
+ printf ("extent: id=%d, metacontext=%s, offset=%" PRIu64 ",
"
+ "nr_entries=%zu, error=%d\n",
+ id, metacontext, offset, nr_entries, *error);
+
+ assert (*error == 0);
+ if (strcmp (metacontext, LIBNBD_CONTEXT_BASE_ALLOCATION) == 0) {
+ for (i = 0; i < nr_entries; i++) {
+ printf ("\t%zu\tlength=%" PRIu64 ", status=%" PRIu32
"\n",
+ i, entries[i].length, entries[i].flags);
+ }
+ fflush (stdout);
+
+ switch (id) {
+ case 1:
+ assert (nr_entries == 5);
+ assert (entries[0].length == 8192);
+ assert (entries[0].flags == 0);
+ assert (entries[1].length == 8192);
+ assert (entries[1].flags == LIBNBD_STATE_HOLE);
+ assert (entries[2].length == 16384);
+ assert (entries[2].flags == (LIBNBD_STATE_HOLE|LIBNBD_STATE_ZERO));
+ assert (entries[3].length == 16384);
+ assert (entries[3].flags == LIBNBD_STATE_ZERO);
+ assert (entries[4].length == 16384);
+ assert (entries[4].flags == 0);
+ break;
+
+ case 2:
+ assert (nr_entries == 2);
+ assert (entries[0].length == 512);
+ assert (entries[0].flags == (LIBNBD_STATE_HOLE|LIBNBD_STATE_ZERO));
+ assert (entries[1].length == 16384);
+ assert (entries[1].flags == LIBNBD_STATE_ZERO);
+ break;
+
+ case 3:
+ assert (nr_entries == 1);
+ assert (entries[0].length == 512);
+ assert (entries[0].flags == (LIBNBD_STATE_HOLE|LIBNBD_STATE_ZERO));
+ break;
+
+ default:
+ abort ();
+ }
+
+ }
+ else
+ fprintf (stderr, "warning: ignored unexpected meta context %s\n",
+ metacontext);
+
+ return 0;
+}
diff --git a/golang/Makefile.am b/golang/Makefile.am
index 10fb8934..e861f5fa 100644
--- a/golang/Makefile.am
+++ b/golang/Makefile.am
@@ -1,5 +1,5 @@
# nbd client library in userspace
-# Copyright (C) 2013-2020 Red Hat Inc.
+# Copyright (C) 2013-2021 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
@@ -39,6 +39,7 @@ source_files = \
libnbd_405_pread_structured_test.go \
libnbd_410_pwrite_test.go \
libnbd_460_block_status_test.go \
+ libnbd_465_block_status_64_test.go \
libnbd_500_aio_pread_test.go \
libnbd_510_aio_pwrite_test.go \
libnbd_590_aio_copy_test.go \
diff --git a/golang/libnbd_465_block_status_64_test.go
b/golang/libnbd_465_block_status_64_test.go
new file mode 100644
index 00000000..40635875
--- /dev/null
+++ b/golang/libnbd_465_block_status_64_test.go
@@ -0,0 +1,119 @@
+/* libnbd golang tests
+ * Copyright (C) 2013-2021 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
+ */
+
+package libnbd
+
+import (
+ "fmt"
+ "os"
+ "strings"
+ "testing"
+)
+
+var entries64 []LibnbdExtent
+
+func mcf64(metacontext string, offset uint64, e []LibnbdExtent, error *int) int
{
+ if *error != 0 {
+ panic("expected *error == 0")
+ }
+ if metacontext == "base:allocation" {
+ entries64 = e
+ }
+ return 0
+}
+
+// Seriously WTF?
+func mc64_compare(a1 []LibnbdExtent, a2 []LibnbdExtent) bool {
+ if len(a1) != len(a2) {
+ return false
+ }
+ for i := 0; i < len(a1); i++ {
+ if a1[i] != a2[i] {
+ return false
+ }
+ }
+ return true
+}
+
+func mc64_to_string(a []LibnbdExtent) string {
+ ss := make([]string, len(a))
+ for i := 0; i < len(a); i++ {
+ ss[i] = fmt.Sprintf("%#v", a[i])
+ }
+ return strings.Join(ss, ", ")
+}
+
+func Test465BlockStatus64(t *testing.T) {
+ srcdir := os.Getenv("abs_top_srcdir")
+ script := srcdir + "/tests/meta-base-allocation.sh"
+
+ h, err := Create()
+ if err != nil {
+ t.Fatalf("could not create handle: %s", err)
+ }
+ defer h.Close()
+
+ err = h.AddMetaContext("base:allocation")
+ if err != nil {
+ t.Fatalf("%s", err)
+ }
+ err = h.ConnectCommand([]string{
+ "nbdkit", "-s", "--exit-with-parent",
"-v",
+ "sh", script,
+ })
+ if err != nil {
+ t.Fatalf("%s", err)
+ }
+
+ err = h.BlockStatus64(65536, 0, mcf64, nil)
+ if err != nil {
+ t.Fatalf("%s", err)
+ }
+ if !mc64_compare(entries64, []LibnbdExtent{
+ {8192, 0},
+ {8192, 1},
+ {16384, 3},
+ {16384, 2},
+ {16384, 0},
+ }) {
+ t.Fatalf("unexpected entries (1): %s", mc64_to_string(entries64))
+ }
+
+ err = h.BlockStatus64(1024, 32256, mcf64, nil)
+ if err != nil {
+ t.Fatalf("%s", err)
+ }
+ if !mc64_compare(entries64, []LibnbdExtent{
+ {512, 3},
+ {16384, 2},
+ }) {
+ t.Fatalf("unexpected entries (2): %s", mc64_to_string(entries64))
+ }
+
+ var optargs BlockStatus64Optargs
+ optargs.FlagsSet = true
+ optargs.Flags = CMD_FLAG_REQ_ONE
+ err = h.BlockStatus64(1024, 32256, mcf64, &optargs)
+ if err != nil {
+ t.Fatalf("%s", err)
+ }
+ if !mc64_compare(entries64, []LibnbdExtent{{512, 3}}) {
+ t.Fatalf("unexpected entries (3): %s", mc64_to_string(entries64))
+ }
+
+}
--
2.33.1
Eric Blake
2021-Dec-03 23:17 UTC
[Libguestfs] [libnbd PATCH 11/13] api: Add three functions for controlling extended headers
The new NBD_OPT_EXTENDED_HEADERS feature is worth using by default,
but there may be cases where the user explicitly wants to stick with
the older 32-bit headers. nbd_set_request_extended_headers() will let
the client override the default, nbd_get_request_extended_headers()
determines the current state of the request, and
nbd_get_extended_headers_negotiated() determines what the client and
server actually settled on. These use
nbd_set_request_structured_headers() and friends as a template.
Note that this patch just adds the API but ignores the state variable;
the next one will then tweak the state machine to actually request
structured headers when the state variable is set.
---
lib/internal.h | 1 +
generator/API.ml | 89 ++++++++++++++++++++--
lib/handle.c | 23 ++++++
python/t/110-defaults.py | 3 +-
python/t/120-set-non-defaults.py | 4 +-
ocaml/tests/test_110_defaults.ml | 4 +-
ocaml/tests/test_120_set_non_defaults.ml | 5 +-
golang/libnbd_110_defaults_test.go | 8 ++
golang/libnbd_120_set_non_defaults_test.go | 12 +++
9 files changed, 137 insertions(+), 12 deletions(-)
diff --git a/lib/internal.h b/lib/internal.h
index 97abf4f2..a579e413 100644
--- a/lib/internal.h
+++ b/lib/internal.h
@@ -107,6 +107,7 @@ struct nbd_handle {
char *tls_psk_file; /* PSK filename, NULL = no PSK */
/* Extended headers. */
+ bool request_eh; /* Whether to request extended headers */
bool extended_headers; /* If we negotiated NBD_OPT_EXTENDED_HEADERS */
/* Desired metadata contexts. */
diff --git a/generator/API.ml b/generator/API.ml
index 1a452a24..e45f0c86 100644
--- a/generator/API.ml
+++ b/generator/API.ml
@@ -675,6 +675,63 @@ "get_tls_psk_file", {
};
*)
+ "set_request_extended_headers", {
+ default_call with
+ args = [Bool "request"]; ret = RErr;
+ permitted_states = [ Created ];
+ shortdesc = "control use of extended headers";
+ longdesc = "\
+By default, libnbd tries to negotiate extended headers with the
+server, as this protocol extension permits the use of 64-bit
+zero, trim, and block status actions. However,
+for integration testing, it can be useful to clear this flag
+rather than find a way to alter the server to fail the negotiation
+request.";
+ see_also = [Link "get_request_extended_headers";
+ Link "set_handshake_flags"; Link
"set_strict_mode";
+ Link "get_extended_headers_negotiated";
+ Link "zero"; Link "trim"; Link
"cache";
+ Link "block_status_64";
+ Link "set_request_structured_replies"];
+ };
+
+ "get_request_extended_headers", {
+ default_call with
+ args = []; ret = RBool;
+ may_set_error = false;
+ shortdesc = "see if extended headers are attempted";
+ longdesc = "\
+Return the state of the request extended headers flag on this
+handle.
+
+B<Note:> If you want to find out if extended headers were actually
+negotiated on a particular connection use
+L<nbd_get_extended_headers_negotiated(3)> instead.";
+ see_also = [Link "set_request_extended_headers";
+ Link "get_extended_headers_negotiated";
+ Link "get_request_extended_headers"];
+ };
+
+ "get_extended_headers_negotiated", {
+ default_call with
+ args = []; ret = RBool;
+ permitted_states = [ Negotiating; Connected; Closed ];
+ shortdesc = "see if extended headers are in use";
+ longdesc = "\
+After connecting you may call this to find out if the connection is
+using extended headers. When extended headers are not in use, commands
+are limited to a 32-bit length, even when the libnbd API uses a 64-bit
+variable to express the length. But even when extended headers are
+supported, the server may enforce other limits, visible through
+L<nbd_get_block_size(3)>.";
+ see_also = [Link "set_request_extended_headers";
+ Link "get_request_extended_headers";
+ Link "zero"; Link "trim"; Link
"cache";
+ Link "block_status_64"; Link
"get_block_size";
+ Link "get_protocol";
+ Link "get_structured_replies_negotiated"];
+ };
+
"set_request_structured_replies", {
default_call with
args = [Bool "request"]; ret = RErr;
@@ -690,7 +747,8 @@ "set_request_structured_replies", {
see_also = [Link "get_request_structured_replies";
Link "set_handshake_flags"; Link
"set_strict_mode";
Link "get_structured_replies_negotiated";
- Link "can_meta_context"; Link "can_df"];
+ Link "can_meta_context"; Link "can_df";
+ Link "set_request_extended_headers"];
};
"get_request_structured_replies", {
@@ -706,7 +764,8 @@ "get_request_structured_replies", {
negotiated on a particular connection use
L<nbd_get_structured_replies_negotiated(3)> instead.";
see_also = [Link "set_request_structured_replies";
- Link "get_structured_replies_negotiated"];
+ Link "get_structured_replies_negotiated";
+ Link "get_request_extended_headers"];
};
"get_structured_replies_negotiated", {
@@ -719,7 +778,8 @@ "get_structured_replies_negotiated", {
using structured replies.";
see_also = [Link "set_request_structured_replies";
Link "get_request_structured_replies";
- Link "get_protocol"];
+ Link "get_protocol";
+ Link "get_extended_headers_negotiated"];
};
"set_handshake_flags", {
@@ -2035,7 +2095,9 @@ "trim", {
or there is an error. Note this will generally return an error
if L<nbd_can_trim(3)> is false or L<nbd_is_read_only(3)> is true.
-Note that not all servers can support a C<count> of 4GiB or larger.
+Note that not all servers can support a C<count> of 4GiB or larger;
+L<nbd_get_extended_headers_negotiated(3)> indicates which servers
+will parse a request larger than 32 bits.
The NBD protocol does not yet have a way for a client to learn if
the server will enforce an even smaller maximum trim size, although
a future extension may add a constraint visible in
@@ -2066,7 +2128,9 @@ "cache", {
this command. Note this will generally return an error if
L<nbd_can_cache(3)> is false.
-Note that not all servers can support a C<count> of 4GiB or larger.
+Note that not all servers can support a C<count> of 4GiB or larger;
+L<nbd_get_extended_headers_negotiated(3)> indicates which servers
+will parse a request larger than 32 bits.
The NBD protocol does not yet have a way for a client to learn if
the server will enforce an even smaller maximum cache size, although
a future extension may add a constraint visible in
@@ -2095,7 +2159,9 @@ "zero", {
or there is an error. Note this will generally return an error if
L<nbd_can_zero(3)> is false or L<nbd_is_read_only(3)> is true.
-Note that not all servers can support a C<count> of 4GiB or larger.
+Note that not all servers can support a C<count> of 4GiB or larger;
+L<nbd_get_extended_headers_negotiated(3)> indicates which servers
+will parse a request larger than 32 bits.
The NBD protocol does not yet have a way for a client to learn if
the server will enforce an even smaller maximum zero size, although
a future extension may add a constraint visible in
@@ -2135,7 +2201,9 @@ "block_status", {
are supported, the number of blocks and cumulative length
of those blocks need not be identical between contexts.
-Note that not all servers can support a C<count> of 4GiB or larger.
+Note that not all servers can support a C<count> of 4GiB or larger;
+L<nbd_get_extended_headers_negotiated(3)> indicates which servers
+will parse a request larger than 32 bits.
The NBD protocol does not yet have a way for a client to learn if
the server will enforce an even smaller maximum block status size,
although a future extension may add a constraint visible in
@@ -2209,7 +2277,9 @@ "block_status_64", {
are supported, the number of blocks and cumulative length
of those blocks need not be identical between contexts.
-Note that not all servers can support a C<count> of 4GiB or larger.
+Note that not all servers can support a C<count> of 4GiB or larger;
+L<nbd_get_extended_headers_negotiated(3)> indicates which servers
+will parse a request larger than 32 bits.
The NBD protocol does not yet have a way for a client to learn if
the server will enforce an even smaller maximum block status size,
although a future extension may add a constraint visible in
@@ -3239,6 +3309,9 @@ let first_version (* Added in 1.11.x development cycle,
will be stable and supported in 1.12. *)
"block_status_64", (1, 12);
"aio_block_status_64", (1, 12);
+ "set_request_extended_headers", (1, 12);
+ "get_request_extended_headers", (1, 12);
+ "get_extended_headers_negotiated", (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/lib/handle.c b/lib/handle.c
index 74fe87ec..9b96c7f7 100644
--- a/lib/handle.c
+++ b/lib/handle.c
@@ -63,6 +63,7 @@ nbd_create (void)
h->unique = 1;
h->tls_verify_peer = true;
+ h->request_eh = true;
h->request_sr = true;
h->uri_allow_transports = LIBNBD_ALLOW_TRANSPORT_MASK;
@@ -356,6 +357,28 @@ nbd_unlocked_clear_meta_contexts (struct nbd_handle *h)
return 0;
}
+
+int
+nbd_unlocked_set_request_extended_headers (struct nbd_handle *h,
+ bool request)
+{
+ h->request_eh = request;
+ return 0;
+}
+
+/* NB: may_set_error = false. */
+int
+nbd_unlocked_get_request_extended_headers (struct nbd_handle *h)
+{
+ return h->request_eh;
+}
+
+int
+nbd_unlocked_get_extended_headers_negotiated (struct nbd_handle *h)
+{
+ return h->extended_headers;
+}
+
int
nbd_unlocked_set_request_structured_replies (struct nbd_handle *h,
bool request)
diff --git a/python/t/110-defaults.py b/python/t/110-defaults.py
index fb961cfd..ecd4dfda 100644
--- a/python/t/110-defaults.py
+++ b/python/t/110-defaults.py
@@ -1,5 +1,5 @@
# libnbd Python bindings
-# Copyright (C) 2010-2020 Red Hat Inc.
+# Copyright (C) 2010-2021 Red Hat Inc.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -21,6 +21,7 @@ h = nbd.NBD()
assert h.get_export_name() == ""
assert h.get_full_info() is False
assert h.get_tls() == nbd.TLS_DISABLE
+assert h.get_request_extended_headers() is True
assert h.get_request_structured_replies() 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 3da0c23e..b34fb508 100644
--- a/python/t/120-set-non-defaults.py
+++ b/python/t/120-set-non-defaults.py
@@ -1,5 +1,5 @@
# libnbd Python bindings
-# Copyright (C) 2010-2020 Red Hat Inc.
+# Copyright (C) 2010-2021 Red Hat Inc.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -31,6 +31,8 @@ assert h.get_tls() == nbd.TLS_DISABLE
if h.supports_tls():
h.set_tls(nbd.TLS_ALLOW)
assert h.get_tls() == nbd.TLS_ALLOW
+h.set_request_extended_headers(False)
+assert h.get_request_extended_headers() is False
h.set_request_structured_replies(False)
assert h.get_request_structured_replies() is False
try:
diff --git a/ocaml/tests/test_110_defaults.ml b/ocaml/tests/test_110_defaults.ml
index f5886fca..5fe448b6 100644
--- a/ocaml/tests/test_110_defaults.ml
+++ b/ocaml/tests/test_110_defaults.ml
@@ -1,6 +1,6 @@
(* hey emacs, this is OCaml code: -*- tuareg -*- *)
(* libnbd OCaml test case
- * Copyright (C) 2013-2020 Red Hat Inc.
+ * Copyright (C) 2013-2021 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
@@ -25,6 +25,8 @@ let
assert (info = false);
let tls = NBD.get_tls nbd in
assert (tls = NBD.TLS.DISABLE);
+ let eh = NBD.get_request_extended_headers nbd in
+ assert (eh = true);
let sr = NBD.get_request_structured_replies nbd in
assert (sr = 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 b660e5d5..47914d9c 100644
--- a/ocaml/tests/test_120_set_non_defaults.ml
+++ b/ocaml/tests/test_120_set_non_defaults.ml
@@ -1,6 +1,6 @@
(* hey emacs, this is OCaml code: -*- tuareg -*- *)
(* libnbd OCaml test case
- * Copyright (C) 2013-2020 Red Hat Inc.
+ * Copyright (C) 2013-2021 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
@@ -37,6 +37,9 @@ let
let tls = NBD.get_tls nbd in
assert (tls = NBD.TLS.ALLOW);
);
+ NBD.set_request_extended_headers nbd false;
+ let eh = NBD.get_request_extended_headers nbd in
+ assert (eh = false);
NBD.set_request_structured_replies nbd false;
let sr = NBD.get_request_structured_replies nbd in
assert (sr = false);
diff --git a/golang/libnbd_110_defaults_test.go
b/golang/libnbd_110_defaults_test.go
index b3ceb45d..659ea18c 100644
--- a/golang/libnbd_110_defaults_test.go
+++ b/golang/libnbd_110_defaults_test.go
@@ -51,6 +51,14 @@ func Test110Defaults(t *testing.T) {
t.Fatalf("unexpected tls state")
}
+ eh, err := h.GetRequestExtendedHeaders()
+ if err != nil {
+ t.Fatalf("could not get extended headers state: %s", err)
+ }
+ if eh != true {
+ t.Fatalf("unexpected extended headers state")
+ }
+
sr, err := h.GetRequestStructuredReplies()
if err != nil {
t.Fatalf("could not get structured replies state: %s", err)
diff --git a/golang/libnbd_120_set_non_defaults_test.go
b/golang/libnbd_120_set_non_defaults_test.go
index f112456c..d27ec5dc 100644
--- a/golang/libnbd_120_set_non_defaults_test.go
+++ b/golang/libnbd_120_set_non_defaults_test.go
@@ -81,6 +81,18 @@ func Test120SetNonDefaults(t *testing.T) {
}
}
+ err = h.SetRequestExtendedHeaders(false)
+ if err != nil {
+ t.Fatalf("could not set extended headers state: %s", err)
+ }
+ eh, err := h.GetRequestExtendedHeaders()
+ if err != nil {
+ t.Fatalf("could not get extended headers state: %s", err)
+ }
+ if eh != false {
+ t.Fatalf("unexpected extended headers state")
+ }
+
err = h.SetRequestStructuredReplies(false)
if err != nil {
t.Fatalf("could not set structured replies state: %s", err)
--
2.33.1
Eric Blake
2021-Dec-03 23:17 UTC
[Libguestfs] [libnbd PATCH 12/13] generator: Actually request extended headers
This is the culmination of the previous patches preparation work for
using extended headers when possible. The new states in the state
machine are copied extensively from our handling of
OPT_STRUCTURED_REPLY.
At the same time I posted this patch, I had patches for qemu-nbd to
support extended headers as server (nbdkit is a bit tougher). The
interop tests still pass when using a new enough qemu-nbd, showing
that we have cross-project interoperability and therefore an extension
worth standardizing.
---
generator/Makefile.am | 3 +-
generator/state_machine.ml | 41 +++++++++
.../states-newstyle-opt-extended-headers.c | 90 +++++++++++++++++++
generator/states-newstyle-opt-starttls.c | 10 +--
4 files changed, 138 insertions(+), 6 deletions(-)
create mode 100644 generator/states-newstyle-opt-extended-headers.c
diff --git a/generator/Makefile.am b/generator/Makefile.am
index 594d23cf..c889eb7f 100644
--- a/generator/Makefile.am
+++ b/generator/Makefile.am
@@ -1,5 +1,5 @@
# nbd client library in userspace
-# Copyright (C) 2013-2020 Red Hat Inc.
+# Copyright (C) 2013-2021 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
@@ -30,6 +30,7 @@ states_code = \
states-issue-command.c \
states-magic.c \
states-newstyle-opt-export-name.c \
+ states-newstyle-opt-extended-headers.c \
states-newstyle-opt-list.c \
states-newstyle-opt-go.c \
states-newstyle-opt-meta-context.c \
diff --git a/generator/state_machine.ml b/generator/state_machine.ml
index 99652948..ad8eba5e 100644
--- a/generator/state_machine.ml
+++ b/generator/state_machine.ml
@@ -295,6 +295,7 @@ and
* NEGOTIATING after OPT_STRUCTURED_REPLY or any failed OPT_GO.
*)
Group ("OPT_STARTTLS", newstyle_opt_starttls_state_machine);
+ Group ("OPT_EXTENDED_HEADERS",
newstyle_opt_extended_headers_state_machine);
Group ("OPT_STRUCTURED_REPLY",
newstyle_opt_structured_reply_state_machine);
Group ("OPT_META_CONTEXT",
newstyle_opt_meta_context_state_machine);
Group ("OPT_GO", newstyle_opt_go_state_machine);
@@ -432,6 +433,46 @@ and
};
]
+(* Fixed newstyle NBD_OPT_EXTENDED_HEADERS option.
+ * Implementation: generator/states-newstyle-opt-extended-headers.c
+ *)
+and newstyle_opt_extended_headers_state_machine = [
+ State {
+ default_state with
+ name = "START";
+ comment = "Try to negotiate newstyle NBD_OPT_EXTENDED_HEADERS";
+ external_events = [];
+ };
+
+ State {
+ default_state with
+ name = "SEND";
+ comment = "Send newstyle NBD_OPT_EXTENDED_HEADERS negotiation
request";
+ external_events = [ NotifyWrite, "" ];
+ };
+
+ State {
+ default_state with
+ name = "RECV_REPLY";
+ comment = "Receive newstyle NBD_OPT_EXTENDED_HEADERS option
reply";
+ external_events = [ NotifyRead, "" ];
+ };
+
+ State {
+ default_state with
+ name = "RECV_REPLY_PAYLOAD";
+ comment = "Receive any newstyle NBD_OPT_EXTENDED_HEADERS reply
payload";
+ external_events = [ NotifyRead, "" ];
+ };
+
+ State {
+ default_state with
+ name = "CHECK_REPLY";
+ comment = "Check newstyle NBD_OPT_EXTENDED_HEADERS option reply";
+ external_events = [];
+ };
+]
+
(* Fixed newstyle NBD_OPT_STRUCTURED_REPLY option.
* Implementation: generator/states-newstyle-opt-structured-reply.c
*)
diff --git a/generator/states-newstyle-opt-extended-headers.c
b/generator/states-newstyle-opt-extended-headers.c
new file mode 100644
index 00000000..e2c9890e
--- /dev/null
+++ b/generator/states-newstyle-opt-extended-headers.c
@@ -0,0 +1,90 @@
+/* nbd client library in userspace: state machine
+ * Copyright (C) 2013-2021 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
+ */
+
+/* State machine for negotiating NBD_OPT_EXTENDED_HEADERS. */
+
+STATE_MACHINE {
+ NEWSTYLE.OPT_EXTENDED_HEADERS.START:
+ assert (h->gflags & LIBNBD_HANDSHAKE_FLAG_FIXED_NEWSTYLE);
+ if (!h->request_eh) {
+ SET_NEXT_STATE (%^OPT_STRUCTURED_REPLY.START);
+ return 0;
+ }
+
+ h->sbuf.option.version = htobe64 (NBD_NEW_VERSION);
+ h->sbuf.option.option = htobe32 (NBD_OPT_EXTENDED_HEADERS);
+ h->sbuf.option.optlen = htobe32 (0);
+ h->wbuf = &h->sbuf;
+ h->wlen = sizeof h->sbuf.option;
+ SET_NEXT_STATE (%SEND);
+ return 0;
+
+ NEWSTYLE.OPT_EXTENDED_HEADERS.SEND:
+ switch (send_from_wbuf (h)) {
+ case -1: SET_NEXT_STATE (%.DEAD); return 0;
+ case 0:
+ h->rbuf = &h->sbuf;
+ h->rlen = sizeof h->sbuf.or.option_reply;
+ SET_NEXT_STATE (%RECV_REPLY);
+ }
+ return 0;
+
+ NEWSTYLE.OPT_EXTENDED_HEADERS.RECV_REPLY:
+ switch (recv_into_rbuf (h)) {
+ case -1: SET_NEXT_STATE (%.DEAD); return 0;
+ case 0:
+ if (prepare_for_reply_payload (h, NBD_OPT_EXTENDED_HEADERS) == -1) {
+ SET_NEXT_STATE (%.DEAD);
+ return 0;
+ }
+ SET_NEXT_STATE (%RECV_REPLY_PAYLOAD);
+ }
+ return 0;
+
+ NEWSTYLE.OPT_EXTENDED_HEADERS.RECV_REPLY_PAYLOAD:
+ switch (recv_into_rbuf (h)) {
+ case -1: SET_NEXT_STATE (%.DEAD); return 0;
+ case 0: SET_NEXT_STATE (%CHECK_REPLY);
+ }
+ return 0;
+
+ NEWSTYLE.OPT_EXTENDED_HEADERS.CHECK_REPLY:
+ uint32_t reply;
+
+ reply = be32toh (h->sbuf.or.option_reply.reply);
+ switch (reply) {
+ case NBD_REP_ACK:
+ debug (h, "negotiated extended headers on this connection");
+ h->extended_headers = true;
+ break;
+ default:
+ if (handle_reply_error (h) == -1) {
+ SET_NEXT_STATE (%.DEAD);
+ return 0;
+ }
+
+ debug (h, "extended headers are not supported by this server");
+ h->extended_headers = false;
+ break;
+ }
+
+ /* Next option. */
+ SET_NEXT_STATE (%^OPT_STRUCTURED_REPLY.START);
+ return 0;
+
+} /* END STATE MACHINE */
diff --git a/generator/states-newstyle-opt-starttls.c
b/generator/states-newstyle-opt-starttls.c
index 9eab023b..2aec3f3d 100644
--- a/generator/states-newstyle-opt-starttls.c
+++ b/generator/states-newstyle-opt-starttls.c
@@ -1,5 +1,5 @@
/* nbd client library in userspace: state machine
- * Copyright (C) 2013-2020 Red Hat Inc.
+ * Copyright (C) 2013-2021 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
@@ -23,7 +23,7 @@ STATE_MACHINE {
assert (h->gflags & LIBNBD_HANDSHAKE_FLAG_FIXED_NEWSTYLE);
/* If TLS was not requested we skip this option and go to the next one. */
if (h->tls == LIBNBD_TLS_DISABLE) {
- SET_NEXT_STATE (%^OPT_STRUCTURED_REPLY.START);
+ SET_NEXT_STATE (%^OPT_EXTENDED_HEADERS.START);
return 0;
}
@@ -102,7 +102,7 @@ STATE_MACHINE {
debug (h,
"server refused TLS (%s), continuing with unencrypted
connection",
reply == NBD_REP_ERR_POLICY ? "policy" : "not
supported");
- SET_NEXT_STATE (%^OPT_STRUCTURED_REPLY.START);
+ SET_NEXT_STATE (%^OPT_EXTENDED_HEADERS.START);
return 0;
}
return 0;
@@ -121,7 +121,7 @@ STATE_MACHINE {
nbd_internal_crypto_debug_tls_enabled (h);
/* Continue with option negotiation. */
- SET_NEXT_STATE (%^OPT_STRUCTURED_REPLY.START);
+ SET_NEXT_STATE (%^OPT_EXTENDED_HEADERS.START);
return 0;
}
/* Continue handshake. */
@@ -144,7 +144,7 @@ STATE_MACHINE {
debug (h, "connection is using TLS");
/* Continue with option negotiation. */
- SET_NEXT_STATE (%^OPT_STRUCTURED_REPLY.START);
+ SET_NEXT_STATE (%^OPT_EXTENDED_HEADERS.START);
return 0;
}
/* Continue handshake. */
--
2.33.1
Eric Blake
2021-Dec-03 23:17 UTC
[Libguestfs] [libnbd PATCH 13/13] interop: Add test of 64-bit block status
Prove that we can round-trip a block status request larger than 4G
through a new-enough qemu-nbd. Also serves as a unit test of our shim
for converting internal 64-bit representation back to the older 32-bit
nbd_block_status callback interface.
---
interop/Makefile.am | 6 ++
interop/large-status.c | 186 ++++++++++++++++++++++++++++++++++++++++
interop/large-status.sh | 49 +++++++++++
.gitignore | 1 +
4 files changed, 242 insertions(+)
create mode 100644 interop/large-status.c
create mode 100755 interop/large-status.sh
diff --git a/interop/Makefile.am b/interop/Makefile.am
index 3a8d5677..96c0a0f6 100644
--- a/interop/Makefile.am
+++ b/interop/Makefile.am
@@ -20,6 +20,7 @@ include $(top_srcdir)/subdir-rules.mk
EXTRA_DIST = \
dirty-bitmap.sh \
interop-qemu-storage-daemon.sh \
+ large-status.sh \
list-exports-nbd-config \
list-exports-test-dir/disk1 \
list-exports-test-dir/disk2 \
@@ -129,6 +130,7 @@ check_PROGRAMS += \
list-exports-qemu-nbd \
socket-activation-qemu-nbd \
dirty-bitmap \
+ large-status \
structured-read \
$(NULL)
TESTS += \
@@ -138,6 +140,7 @@ TESTS += \
list-exports-qemu-nbd \
socket-activation-qemu-nbd \
dirty-bitmap.sh \
+ large-status.sh \
structured-read.sh \
$(NULL)
@@ -227,6 +230,9 @@ socket_activation_qemu_nbd_LDADD =
$(top_builddir)/lib/libnbd.la
dirty_bitmap_SOURCES = dirty-bitmap.c
dirty_bitmap_LDADD = $(top_builddir)/lib/libnbd.la
+large_status_SOURCES = large-status.c
+large_status_LDADD = $(top_builddir)/lib/libnbd.la
+
structured_read_SOURCES = structured-read.c
structured_read_LDADD = $(top_builddir)/lib/libnbd.la
diff --git a/interop/large-status.c b/interop/large-status.c
new file mode 100644
index 00000000..3cc040fe
--- /dev/null
+++ b/interop/large-status.c
@@ -0,0 +1,186 @@
+/* NBD client library in userspace
+ * Copyright (C) 2013-2021 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 64-bit block status with qemu. */
+
+#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>
+
+static const char *bitmap;
+
+struct data {
+ bool req_one; /* input: true if req_one was passed to request */
+ int count; /* input: count of expected remaining calls */
+ bool seen_base; /* output: true if base:allocation encountered */
+ bool seen_dirty; /* output: true if qemu:dirty-bitmap encountered */
+};
+
+static int
+cb32 (void *opaque, const char *metacontext, uint64_t offset,
+ uint32_t *entries, size_t len, int *error)
+{
+ struct data *data = opaque;
+
+ assert (offset == 0);
+ assert (data->count-- > 0);
+
+ if (strcmp (metacontext, LIBNBD_CONTEXT_BASE_ALLOCATION) == 0) {
+ assert (!data->seen_base);
+ data->seen_base = true;
+
+ /* Data block offset 0 size 64k, remainder is hole */
+ assert (len == 4);
+ assert (entries[0] == 65536);
+ assert (entries[1] == 0);
+ /* libnbd had to truncate qemu's >4G answer */
+ assert (entries[2] == 4227858432);
+ assert (entries[3] == (LIBNBD_STATE_HOLE|LIBNBD_STATE_ZERO));
+ }
+ else if (strcmp (metacontext, bitmap) == 0) {
+ assert (!data->seen_dirty);
+ data->seen_dirty = true;
+
+ /* Dirty block at offset 5G-64k, remainder is clean */
+ /* libnbd had to truncate qemu's >4G answer */
+ assert (len == 2);
+ assert (entries[0] == 4227858432);
+ assert (entries[1] == 0);
+ }
+ else {
+ fprintf (stderr, "unexpected context %s\n", metacontext);
+ exit (EXIT_FAILURE);
+ }
+ return 0;
+}
+
+static int
+cb64 (void *opaque, const char *metacontext, uint64_t offset,
+ nbd_extent *entries, size_t len, int *error)
+{
+ struct data *data = opaque;
+
+ assert (offset == 0);
+ assert (data->count-- > 0);
+
+ if (strcmp (metacontext, LIBNBD_CONTEXT_BASE_ALLOCATION) == 0) {
+ assert (!data->seen_base);
+ data->seen_base = true;
+
+ /* Data block offset 0 size 64k, remainder is hole */
+ assert (len == 2);
+ assert (entries[0].length == 65536);
+ assert (entries[0].flags == 0);
+ assert (entries[1].length == 5368643584ULL);
+ assert (entries[1].flags == (LIBNBD_STATE_HOLE|LIBNBD_STATE_ZERO));
+ }
+ else if (strcmp (metacontext, bitmap) == 0) {
+ assert (!data->seen_dirty);
+ data->seen_dirty = true;
+
+ /* Dirty block at offset 5G-64k, remainder is clean */
+ assert (len == 2);
+ assert (entries[0].length == 5368643584ULL);
+ assert (entries[0].flags == 0);
+ assert (entries[1].length == 65536);
+ assert (entries[1].flags == 1);
+ }
+ else {
+ fprintf (stderr, "unexpected context %s\n", metacontext);
+ exit (EXIT_FAILURE);
+ }
+ return 0;
+}
+
+int
+main (int argc, char *argv[])
+{
+ struct nbd_handle *nbd;
+ int64_t exportsize;
+ struct data data;
+
+ if (argc < 3) {
+ fprintf (stderr, "%s bitmap qemu-nbd [args ...]\n", argv[0]);
+ exit (EXIT_FAILURE);
+ }
+ bitmap = argv[1];
+
+ nbd = nbd_create ();
+ if (nbd == NULL) {
+ fprintf (stderr, "%s\n", nbd_get_error ());
+ exit (EXIT_FAILURE);
+ }
+
+ nbd_add_meta_context (nbd, LIBNBD_CONTEXT_BASE_ALLOCATION);
+ nbd_add_meta_context (nbd, bitmap);
+
+ if (nbd_connect_systemd_socket_activation (nbd, &argv[2]) == -1) {
+ fprintf (stderr, "%s\n", nbd_get_error ());
+ exit (EXIT_FAILURE);
+ }
+
+ exportsize = nbd_get_size (nbd);
+ if (exportsize == -1) {
+ fprintf (stderr, "%s\n", nbd_get_error ());
+ exit (EXIT_FAILURE);
+ }
+
+ if (nbd_get_extended_headers_negotiated (nbd) != 1) {
+ fprintf (stderr, "skipping: qemu-nbd lacks extended headers\n");
+ exit (77);
+ }
+
+ /* Prove that we can round-trip a >4G block status request */
+ data = (struct data) { .count = 2, };
+ if (nbd_block_status_64 (nbd, exportsize, 0,
+ (nbd_extent64_callback) { .callback = cb64,
+ .user_data = &data },
+ 0) == -1) {
+ fprintf (stderr, "%s\n", nbd_get_error ());
+ exit (EXIT_FAILURE);
+ }
+ assert (data.seen_base && data.seen_dirty);
+
+ /* Check libnbd's handling of a >4G response through older interface
*/
+ data = (struct data) { .count = 2, };
+ if (nbd_block_status (nbd, exportsize, 0,
+ (nbd_extent_callback) { .callback = cb32,
+ .user_data = &data },
+ 0) == -1) {
+ fprintf (stderr, "%s\n", nbd_get_error ());
+ exit (EXIT_FAILURE);
+ }
+ assert (data.seen_base && data.seen_dirty);
+
+ 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/large-status.sh b/interop/large-status.sh
new file mode 100755
index 00000000..58fbdd36
--- /dev/null
+++ b/interop/large-status.sh
@@ -0,0 +1,49 @@
+#!/usr/bin/env bash
+# nbd client library in userspace
+# Copyright (C) 2019-2021 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 reading qemu dirty-bitmap.
+
+source ../tests/functions.sh
+set -e
+set -x
+
+requires qemu-img bitmap --help
+requires qemu-nbd --version
+
+# This test uses the qemu-nbd -B option.
+if ! qemu-nbd --help | grep -sq -- -B; then
+ echo "$0: skipping because qemu-nbd does not support the -B
option"
+ exit 77
+fi
+
+files="large-status.qcow2"
+rm -f $files
+cleanup_fn rm -f $files
+
+# Create mostly-sparse file with intentionally different data vs. dirty areas
+# (64k data, 5G-64k hole,zero; 5G-64k clean, 64k dirty)
+qemu-img create -f qcow2 large-status.qcow2 5G
+qemu-img bitmap --add --enable -f qcow2 large-status.qcow2 bitmap0
+qemu-io -f qcow2 -c "w -z $((5*1024*1024*1024 - 64*1024)) 64k" \
+ large-status.qcow2
+qemu-img bitmap --disable -f qcow2 large-status.qcow2 bitmap0
+qemu-io -f qcow2 -c 'w 0 64k' large-status.qcow2
+
+# Run the test.
+$VG ./large-status qemu:dirty-bitmap:bitmap0 \
+ qemu-nbd -f qcow2 -B bitmap0 large-status.qcow2
diff --git a/.gitignore b/.gitignore
index 3ecdceaf..cbc5b88d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -100,6 +100,7 @@ Makefile.in
/interop/interop-qemu-nbd
/interop/interop-qemu-nbd-tls-certs
/interop/interop-qemu-nbd-tls-psk
+/interop/large-status
/interop/list-exports-nbd-server
/interop/list-exports-nbdkit
/interop/list-exports-qemu-nbd
--
2.33.1
Laszlo Ersek
2021-Dec-10 08:16 UTC
[Libguestfs] [libnbd PATCH 00/13] libnbd patches for NBD_OPT_EXTENDED_HEADERS
On 12/04/21 00:17, Eric Blake wrote:> Available here: https://repo.or.cz/libnbd/ericb.git/shortlog/refs/tags/exthdr-v1 > > I also want to do followup patches to teach 'nbdinfo --map' and > 'nbdcopy' to utilize 64-bit extents. > > Eric Blake (13): > golang: Simplify nbd_block_status callback array copy > block_status: Refactor array storage > protocol: Add definitions for extended headers > protocol: Prepare to send 64-bit requests > protocol: Prepare to receive 64-bit replies > protocol: Accept 64-bit holes during pread > generator: Add struct nbd_extent in prep for 64-bit extents > block_status: Track 64-bit extents internally > block_status: Accept 64-bit extents during block status > api: Add [aio_]nbd_block_status_64 > api: Add three functions for controlling extended headers > generator: Actually request extended headers > interop: Add test of 64-bit block status > > lib/internal.h | 31 ++- > lib/nbd-protocol.h | 61 ++++- > generator/API.ml | 237 ++++++++++++++++-- > generator/API.mli | 3 +- > generator/C.ml | 24 +- > generator/GoLang.ml | 35 ++- > generator/Makefile.am | 3 +- > generator/OCaml.ml | 20 +- > generator/Python.ml | 29 ++- > generator/state_machine.ml | 52 +++- > generator/states-issue-command.c | 31 ++- > .../states-newstyle-opt-extended-headers.c | 90 +++++++ > generator/states-newstyle-opt-starttls.c | 10 +- > generator/states-reply-structured.c | 220 ++++++++++++---- > generator/states-reply.c | 31 ++- > lib/handle.c | 27 +- > lib/rw.c | 105 +++++++- > python/t/110-defaults.py | 3 +- > python/t/120-set-non-defaults.py | 4 +- > python/t/465-block-status-64.py | 56 +++++ > ocaml/helpers.c | 22 +- > ocaml/nbd-c.h | 3 +- > ocaml/tests/Makefile.am | 5 +- > ocaml/tests/test_110_defaults.ml | 4 +- > ocaml/tests/test_120_set_non_defaults.ml | 5 +- > ocaml/tests/test_465_block_status_64.ml | 58 +++++ > tests/meta-base-allocation.c | 111 +++++++- > interop/Makefile.am | 6 + > interop/large-status.c | 186 ++++++++++++++ > interop/large-status.sh | 49 ++++ > .gitignore | 1 + > golang/Makefile.am | 3 +- > golang/handle.go | 6 + > golang/libnbd_110_defaults_test.go | 8 + > golang/libnbd_120_set_non_defaults_test.go | 12 + > golang/libnbd_465_block_status_64_test.go | 119 +++++++++ > 36 files changed, 1511 insertions(+), 159 deletions(-) > create mode 100644 generator/states-newstyle-opt-extended-headers.c > create mode 100644 python/t/465-block-status-64.py > create mode 100644 ocaml/tests/test_465_block_status_64.ml > create mode 100644 interop/large-status.c > create mode 100755 interop/large-status.sh > create mode 100644 golang/libnbd_465_block_status_64_test.go >I figured I should slowly / gradually review this series, and as a *pre-requisite* for it, first apply the spec patch, and then read through the spec with something like $ git show --color -U1000 In other words, read the whole spec, just highlight the new additions. Now, I see Vladimir has made several comments on the spec patch; will those comments necessitate a respin of the libnbd series? If so, how intrusive are the changes going to be? I'm hesitant to start my review if significant changes are already foreseen. Thanks, Laszlo