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