aboutsummaryrefslogtreecommitdiff
path: root/block/nbd-client.c
diff options
context:
space:
mode:
Diffstat (limited to 'block/nbd-client.c')
-rw-r--r--block/nbd-client.c30
1 files changed, 26 insertions, 4 deletions
diff --git a/block/nbd-client.c b/block/nbd-client.c
index a3b70d1400..150af9cc46 100644
--- a/block/nbd-client.c
+++ b/block/nbd-client.c
@@ -269,15 +269,37 @@ static int nbd_parse_blockstatus_payload(NBDClientSession *client,
extent->length = payload_advance32(&payload);
extent->flags = payload_advance32(&payload);
- if (extent->length == 0 ||
- (client->info.min_block && !QEMU_IS_ALIGNED(extent->length,
- client->info.min_block))) {
+ if (extent->length == 0) {
error_setg(errp, "Protocol error: server sent status chunk with "
- "invalid length");
+ "zero length");
return -EINVAL;
}
/*
+ * A server sending unaligned block status is in violation of the
+ * protocol, but as qemu-nbd 3.1 is such a server (at least for
+ * POSIX files that are not a multiple of 512 bytes, since qemu
+ * rounds files up to 512-byte multiples but lseek(SEEK_HOLE)
+ * still sees an implicit hole beyond the real EOF), it's nicer to
+ * work around the misbehaving server. If the request included
+ * more than the final unaligned block, truncate it back to an
+ * aligned result; if the request was only the final block, round
+ * up to the full block and change the status to fully-allocated
+ * (always a safe status, even if it loses information).
+ */
+ if (client->info.min_block && !QEMU_IS_ALIGNED(extent->length,
+ client->info.min_block)) {
+ trace_nbd_parse_blockstatus_compliance("extent length is unaligned");
+ if (extent->length > client->info.min_block) {
+ extent->length = QEMU_ALIGN_DOWN(extent->length,
+ client->info.min_block);
+ } else {
+ extent->length = client->info.min_block;
+ extent->flags = 0;
+ }
+ }
+
+ /*
* We used NBD_CMD_FLAG_REQ_ONE, so the server should not have
* sent us any more than one extent, nor should it have included
* status beyond our request in that extent. However, it's easy