Blame SOURCES/kvm-nbd-client-Work-around-server-BLOCK_STATUS-misalignm.patch

7711c0
From 00edcf411c7d8e58faa15f2cb07829b8f7f74a60 Mon Sep 17 00:00:00 2001
7711c0
From: John Snow <jsnow@redhat.com>
7711c0
Date: Mon, 6 May 2019 17:56:16 +0200
7711c0
Subject: [PATCH 06/53] nbd-client: Work around server BLOCK_STATUS
7711c0
 misalignment at EOF
7711c0
7711c0
RH-Author: John Snow <jsnow@redhat.com>
7711c0
Message-id: <20190506175629.11079-7-jsnow@redhat.com>
7711c0
Patchwork-id: 87194
7711c0
O-Subject: [RHEL-7.7 qemu-kvm-rhev PATCH 06/19] nbd-client: Work around server BLOCK_STATUS misalignment at EOF
7711c0
Bugzilla: 1692018
7711c0
RH-Acked-by: Max Reitz <mreitz@redhat.com>
7711c0
RH-Acked-by: Stefano Garzarella <sgarzare@redhat.com>
7711c0
RH-Acked-by: Thomas Huth <thuth@redhat.com>
7711c0
7711c0
From: Eric Blake <eblake@redhat.com>
7711c0
7711c0
The NBD spec is clear that a server that advertises a minimum block
7711c0
size should reply to NBD_CMD_BLOCK_STATUS with extents aligned
7711c0
accordingly. However, we know that the qemu NBD server implementation
7711c0
has had a corner-case bug where it is not compliant with the spec,
7711c0
present since the introduction of NBD_CMD_BLOCK_STATUS in qemu 2.12
7711c0
(and unlikely to be patched in time for 4.0). Namely, when qemu is
7711c0
serving a file that is not a multiple of 512 bytes, it rounds the size
7711c0
advertised over NBD up to the next sector boundary (someday, I'd like
7711c0
to fix that to be byte-accurate, but it's a much bigger audit not
7711c0
appropriate for this release); yet if the final sector contains data
7711c0
prior to EOF, lseek(SEEK_HOLE) will point to the implicit hole
7711c0
mid-sector which qemu then reported over NBD.
7711c0
7711c0
We are well within our rights to hang up on a server that can't follow
7711c0
the spec, but it is more useful to try and keep the connection alive
7711c0
in spite of the problem. Do so by tracing a message about the problem,
7711c0
and then either truncating the request back to an aligned boundary (if
7711c0
it covered more than the final sector) or widening it out to the full
7711c0
boundary with a forced status of data (since truncating would result
7711c0
in 0 bytes, but we have to make progress, and valid since data is a
7711c0
default-safe answer). And in practice, since the problem only happens
7711c0
on a sector that starts with data and ends with a hole, we are going
7711c0
to want to read that full sector anyway (where qemu as the server
7711c0
fills in the tail beyond EOF with appropriate NUL bytes).
7711c0
7711c0
Easy reproduction:
7711c0
$ printf %1000d 1 > file
7711c0
$ qemu-nbd -f raw -t file & pid=$!
7711c0
$ qemu-img map --output=json -f raw nbd://localhost:10809
7711c0
qemu-img: Could not read file metadata: Invalid argument
7711c0
$ kill $pid
7711c0
7711c0
where the patched version instead succeeds with:
7711c0
[{ "start": 0, "length": 1024, "depth": 0, "zero": false, "data": true}]
7711c0
7711c0
Signed-off-by: Eric Blake <eblake@redhat.com>
7711c0
Message-Id: <20190326171317.4036-1-eblake@redhat.com>
7711c0
Reviewed-by: Vladimir Sementsov-Ogievskiy <vsementsov@virtuozzo.com>
7711c0
(cherry picked from commit 737d3f524481bb2ef68d3eba1caa636ff143e16a)
7711c0
Signed-off-by: John Snow <jsnow@redhat.com>
7711c0
Signed-off-by: Miroslav Rezanina <mrezanin@redhat.com>
7711c0
---
7711c0
 block/nbd-client.c | 30 ++++++++++++++++++++++++++----
7711c0
 1 file changed, 26 insertions(+), 4 deletions(-)
7711c0
7711c0
diff --git a/block/nbd-client.c b/block/nbd-client.c
7711c0
index eee909f..09e20b2 100644
7711c0
--- a/block/nbd-client.c
7711c0
+++ b/block/nbd-client.c
7711c0
@@ -256,15 +256,37 @@ static int nbd_parse_blockstatus_payload(NBDClientSession *client,
7711c0
     extent->length = payload_advance32(&payload);
7711c0
     extent->flags = payload_advance32(&payload);
7711c0
 
7711c0
-    if (extent->length == 0 ||
7711c0
-        (client->info.min_block && !QEMU_IS_ALIGNED(extent->length,
7711c0
-                                                    client->info.min_block))) {
7711c0
+    if (extent->length == 0) {
7711c0
         error_setg(errp, "Protocol error: server sent status chunk with "
7711c0
-                   "invalid length");
7711c0
+                   "zero length");
7711c0
         return -EINVAL;
7711c0
     }
7711c0
 
7711c0
     /*
7711c0
+     * A server sending unaligned block status is in violation of the
7711c0
+     * protocol, but as qemu-nbd 3.1 is such a server (at least for
7711c0
+     * POSIX files that are not a multiple of 512 bytes, since qemu
7711c0
+     * rounds files up to 512-byte multiples but lseek(SEEK_HOLE)
7711c0
+     * still sees an implicit hole beyond the real EOF), it's nicer to
7711c0
+     * work around the misbehaving server. If the request included
7711c0
+     * more than the final unaligned block, truncate it back to an
7711c0
+     * aligned result; if the request was only the final block, round
7711c0
+     * up to the full block and change the status to fully-allocated
7711c0
+     * (always a safe status, even if it loses information).
7711c0
+     */
7711c0
+    if (client->info.min_block && !QEMU_IS_ALIGNED(extent->length,
7711c0
+                                                   client->info.min_block)) {
7711c0
+        trace_nbd_parse_blockstatus_compliance("extent length is unaligned");
7711c0
+        if (extent->length > client->info.min_block) {
7711c0
+            extent->length = QEMU_ALIGN_DOWN(extent->length,
7711c0
+                                             client->info.min_block);
7711c0
+        } else {
7711c0
+            extent->length = client->info.min_block;
7711c0
+            extent->flags = 0;
7711c0
+        }
7711c0
+    }
7711c0
+
7711c0
+    /*
7711c0
      * We used NBD_CMD_FLAG_REQ_ONE, so the server should not have
7711c0
      * sent us any more than one extent, nor should it have included
7711c0
      * status beyond our request in that extent. However, it's easy
7711c0
-- 
7711c0
1.8.3.1
7711c0