From nobody Mon Sep 28 00:51:54 2026 Delivered-To: importer@patchew.org Authentication-Results: mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass(p=quarantine dis=none) header.from=openvz.org ARC-Seal: i=1; a=rsa-sha256; t=1787141260; cv=none; d=zohomail.com; s=zohoarc; b=hcyXCcko4m1Pkuoo2FoAW6HhQ6IT7lOzlvBwDSkxx8grAvUwMQ4J23HBS66esUqxQrgglZNXpb9pP5N8cVvOqPJQV7wxw3EKkAY41zlFDBKzqDIgQW+cZz8zUU0aznEIIT3zo1wN3mfjy5Y5gQRnN1PUNvvY0lPeLp8EQH09g4M= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1787141260; h=Content-Transfer-Encoding:Cc:Cc:Date:Date:From:From:In-Reply-To:List-Subscribe:List-Post:List-Id:List-Archive:List-Help:List-Unsubscribe:MIME-Version:Message-ID:References:Sender:Subject:Subject:To:To:Message-Id:Reply-To; bh=cur+bApssPVrehRrI9kBxD8OeSdeWe3bZ8wFT4KT/AA=; b=Vkr2XPrRVwB0GnwYwBNyRv0y8ZcvAdk+VDff042d+zJ7UC9S4s2HOyG/sUqZDaVt5Ei7+Zh/aJSv8x/OGb81cpbiHHBMJRQgz0L5uLIjUmJDjf9UQy/gSfLxT71z1HgAOFr5mqW4S3DVO7X/silf22j6fOC2a+yUX9fgIons+YM= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass header.from= (p=quarantine dis=none) Return-Path: Received: from lists1p.gnu.org (lists1p.gnu.org [209.51.188.17]) by mx.zohomail.com with SMTPS id 1787141260604478.8452763313579; Wed, 19 Aug 2026 05:07:40 -0700 (PDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists1p.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1wwf3n-00047x-KV; Wed, 19 Aug 2026 08:06:23 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists1p.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1wwf3d-00045b-CR for qemu-devel@nongnu.org; Wed, 19 Aug 2026 08:06:14 -0400 Received: from mail-wm1-x32d.google.com ([2a00:1450:4864:20::32d]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1wwf3U-0007pt-Tc for qemu-devel@nongnu.org; Wed, 19 Aug 2026 08:06:07 -0400 Received: by mail-wm1-x32d.google.com with SMTP id 5b1f17b1804b1-4956869750eso5844005e9.2 for ; Wed, 19 Aug 2026 05:06:04 -0700 (PDT) Received: from athena.sw.ru ([2a06:5b06:b600:300:258d:1706:7089:f765]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-499aa1111a7sm59244785e9.6.2026.08.19.05.06.02 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 19 Aug 2026 05:06:02 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=openvz.org; s=google; t=1787141163; x=1787745963; darn=nongnu.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to:content-type; bh=cur+bApssPVrehRrI9kBxD8OeSdeWe3bZ8wFT4KT/AA=; b=UCyxoNXNlMHb9ilsJZ3Gm8kQJbCubONbyLDON4yclI0XPXGh3chFEGVd4DPtNNOEzC yja9lNv44VQtggwBL7BurDCmNsUk0Fhc8JyHKMdR87tSrjnnX9XegyknUYL2u+e7wllw 2XnYck6RXAM6nArppnJYo0MB4gONOQIkQkxRnoSKOoLLvyQzCEaI8ucdP4Mbp26DUTKP 3+jTISK4KoNcA50Au7Ue+Cr/ScZPJQnQ2/MedNzJqvf9GSYj/O2ZFnOw35GcVEzBG0C2 r39odFgqxHu1tUbFEd2O+jSgFeYdSRqykLVmyA5EkLTC4Vbu7R6fNHT6SzTxxHhLd/au Foxw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1787141163; x=1787745963; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to:content-type; bh=cur+bApssPVrehRrI9kBxD8OeSdeWe3bZ8wFT4KT/AA=; b=faPm81CcZkmvjjADnDWSUlw89YNlm5ZAZFbhM92AmQlN7hqnkWukG1MbWrJFZfFArX rpuKjASu5dPlGMOQyHOSlc0tTPSdwyE4mhU/aCz/X6iSbr4wF1kxDrDqcCQH64J8nZPp yWA67KYjZdGcopW3IY4Vns4Iz69uBwRfENk8V6sDmBWHzfOjFCkTodJ0zuleK7a1zzpw fVvlYSh7tZKsjDSrWM0kDv0tQ959BcANKrOFim9r0EVhKyOS7d2Ch+U3iXCrK0wOUcAz bZLBZPFYTbYRDWQ+H4Y2hoBdTcs5vRJCeXByh8Jjlwny0S/clhftSAeTpInV6CUETtFq 4reg== X-Gm-Message-State: AOJu0Yz6XmE9PUxMsvoQI87Doc6wcBo3XTr4MoYyaQ3sxSgYMXwwBLaL zi4zqA5obgqH/sqtOo6ujrJs/7NaT+vQUTBrpOlmUOp6RR0CBQ+bO5tszliH2jO0NLnDPbRfZDh zP6KL X-Gm-Gg: AR+sD10jCHgYCIe3mnB3PzbvlWsW1PFAlEqH54yA4Ax7RwbVhgnxWK5q/nHTJ3nYS2t buLl84oLxACvtD6a8jrwRQfikstiE6/HRpKdSi8q9zVZkPMWBDpOXKtIGtagW0/edb6kTZPIVrE /toKZqr2B/NV6YGFe4Ad4/uD91WXLJpj4+0dr0TtB+T3dZdiCI+XFcXLCevX862zeO1chrbbMMP 8DeXcwGNNvjJgO7Qnc5dgniWnYOrVl8joc4T8jT/lPEn95DPzj8MnxH2n2d8TAM+X9KQHF2Igpb uyy1Ghzmv2HxhQhphCrA/fS1NdGRd7utIi/clrW9K7RUywFu1HmkKUbJG9fFGPp6hgW4VDoG3fT kaIvupKn8p/SYZG8WKfnMxGa7wU0+0W4fiGvAwNHLGGYh72TsXc+sdvAtDKvQgsa2t2mgWt05BW oXIfoiLGdiz7EUPe2Lh4tLiGXTxLdV+F2qYcyc1pfRT2xW5Sg2h2eiaAJLbxbAbZXkZzUb X-Received: by 2002:a05:600c:5253:b0:499:79b9:e226 with SMTP id 5b1f17b1804b1-499aa109edbmr65780725e9.0.1787141162992; Wed, 19 Aug 2026 05:06:02 -0700 (PDT) From: "Denis V. Lunev" To: qemu-devel@nongnu.org Cc: qemu-block@nongnu.org, "Denis V. Lunev" , Andrey Drobyshev , Kevin Wolf , Hanna Reitz Subject: [PATCH v3 1/5] qcow2: do not clear the dirty bit when reopening a read-only node Date: Wed, 19 Aug 2026 14:05:54 +0200 Message-ID: <20260819120558.3870413-2-den@openvz.org> X-Mailer: git-send-email 2.53.0 In-Reply-To: <20260819120558.3870413-1-den@openvz.org> References: <20260819120558.3870413-1-den@openvz.org> MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Received-SPF: pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) client-ip=209.51.188.17; envelope-from=qemu-devel-bounces+importer=patchew.org@nongnu.org; helo=lists1p.gnu.org; Received-SPF: pass client-ip=2a00:1450:4864:20::32d; envelope-from=den@openvz.org; helo=mail-wm1-x32d.google.com X-Spam_score_int: -20 X-Spam_score: -2.1 X-Spam_bar: -- X-Spam_report: (-2.1 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, RCVD_IN_DNSWL_NONE=-0.0001, SPF_HELO_NONE=0.001, SPF_PASS=-0.001 autolearn=unavailable autolearn_force=no X-Spam_action: no action X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: qemu development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org Sender: qemu-devel-bounces+importer=patchew.org@nongnu.org X-ZohoMail-DKIM: pass (identity @openvz.org) X-ZM-MESSAGEID: 1787141263281158500 Content-Type: text/plain; charset="utf-8" From: Denis V. Lunev qcow2_reopen_prepare() clears the dirty bit whenever the node is reopened read-only, with an unguarded header write. A read-only node can still be dirty, inherited from an earlier writable session, and it holds no BLK_PERM_WRITE to resolve that. A read-only to read-only reopen of a dirty image therefore fails outright: $ qemu-io -r -f qcow2 dirty.qcow2 <<< $'reopen -r\nquit' qemu-io: failed while preparing to reopen image 'dirty.qcow2' Where the file node below is writable the write is not refused early, and bdrv_co_write_req_prepare() aborts on its BLK_PERM_WRITE assertion instead. Clear it only for a node that is writable now, the predicate qcow2_do_open() already uses for the repair. bdrv_is_writable() also excludes an inactive node, whose header must not be touched either. Signed-off-by: Denis V. Lunev Reviewed-by: Andrey Drobyshev CC: Kevin Wolf CC: Hanna Reitz CC: Andrey Drobyshev --- block/qcow2.c | 8 +++--- tests/qemu-iotests/039 | 50 ++++++++++++++++++++++++++++++++++++++ tests/qemu-iotests/039.out | 20 +++++++++++++++ 3 files changed, 75 insertions(+), 3 deletions(-) diff --git a/block/qcow2.c b/block/qcow2.c index 7292dd036c..1543255eba 100644 --- a/block/qcow2.c +++ b/block/qcow2.c @@ -2102,9 +2102,11 @@ qcow2_reopen_prepare(BDRVReopenState *state,BlockReo= penQueue *queue, goto fail; } =20 - ret =3D qcow2_mark_clean(state->bs); - if (ret < 0) { - goto fail; + if (bdrv_is_writable(state->bs)) { + ret =3D qcow2_mark_clean(state->bs); + if (ret < 0) { + goto fail; + } } } =20 diff --git a/tests/qemu-iotests/039 b/tests/qemu-iotests/039 index 94a8bfe754..3d0c073d65 100755 --- a/tests/qemu-iotests/039 +++ b/tests/qemu-iotests/039 @@ -95,6 +95,40 @@ $QEMU_IMG info --image-opts \ # The dirty bit must still be set: this open never wrote any guest data _qcow2_dump_header | grep incompatible_features =20 +echo +echo "=3D=3D Read-only reopen must not clear the dirty bit =3D=3D" + +# A read-only node cannot write the header, and must keep the dirty bit +$QEMU_IO -r -c "reopen -r" -c "read -P 0x5a 0 512" "$TEST_IMG" \ + | _filter_qemu_io + +# The dirty bit must still be set +_qcow2_dump_header | grep incompatible_features + +echo +echo "=3D=3D Read-only reopen must not write through a writable file node = =3D=3D" + +# The write the header update needs is refused by the permission system +echo "{'execute': 'qmp_capabilities'} + {'execute': 'blockdev-reopen', + 'arguments': {'options': [{'node-name': 'drive', + 'driver': 'qcow2', + 'read-only': true, + 'file': 'prot'}]}} + {'execute': 'quit'}" \ + | $QEMU -qmp stdio -nographic -nodefaults \ + -blockdev "{'node-name': 'prot', + 'driver': 'file', + 'filename': '$TEST_IMG'}" \ + -blockdev "{'node-name': 'drive', + 'driver': 'qcow2', + 'file': 'prot', + 'read-only': true}" \ + | _filter_qmp + +# The dirty bit must still be set +_qcow2_dump_header | grep incompatible_features + echo echo "=3D=3D Repairing the image file must succeed =3D=3D" =20 @@ -108,6 +142,22 @@ echo "=3D=3D Data should still be accessible after rep= air =3D=3D" =20 $QEMU_IO -c "read -P 0x5a 0 512" "$TEST_IMG" | _filter_qemu_io =20 +echo +echo "=3D=3D A read-write to read-only reopen must clear the dirty bit =3D= =3D" + +_make_test_img -o "compat=3D1.1,lazy_refcounts=3Don" $size + +# The kill keeps the close from clearing the bit, so the header shows what +# the reopen did with it +_NO_VALGRIND \ +$QEMU_IO -c "write -P 0x5a 0 512" \ + -c "reopen -r" \ + -c "sigraise $(kill -l KILL)" "$TEST_IMG" 2>&1 \ + | _filter_qemu_io + +# The dirty bit must not be set +_qcow2_dump_header | grep incompatible_features + echo echo "=3D=3D Opening a dirty image read/write should repair it =3D=3D" =20 diff --git a/tests/qemu-iotests/039.out b/tests/qemu-iotests/039.out index c66361128f..ce8ee57721 100644 --- a/tests/qemu-iotests/039.out +++ b/tests/qemu-iotests/039.out @@ -27,6 +27,19 @@ incompatible_features [0] =3D=3D Read-only open must not crash on close =3D=3D incompatible_features [0] =20 +=3D=3D Read-only reopen must not clear the dirty bit =3D=3D +read 512/512 bytes at offset 0 +512 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +incompatible_features [0] + +=3D=3D Read-only reopen must not write through a writable file node =3D=3D +QMP_VERSION +{"return": {}} +{"return": {}} +{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event"= : "SHUTDOWN", "data": {"guest": false, "reason": "host-qmp-quit"}} +{"return": {}} +incompatible_features [0] + =3D=3D Repairing the image file must succeed =3D=3D ERROR cluster 5 refcount=3D0 reference=3D1 Rebuilding refcount structure @@ -45,6 +58,13 @@ incompatible_features [] read 512/512 bytes at offset 0 512 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) =20 +=3D=3D A read-write to read-only reopen must clear the dirty bit =3D=3D +Formatting 'TEST_DIR/t.IMGFMT', fmt=3DIMGFMT size=3D134217728 +wrote 512/512 bytes at offset 0 +512 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +./common.rc: Killed ( VALGRIND_QEMU=3D"${VALGRIND_QEMU_IO}" _qemu_proc_exe= c "${VALGRIND_LOGFILE}" "$QEMU_IO_PROG" $QEMU_IO_ARGS "$@" ) +incompatible_features [] + =3D=3D Opening a dirty image read/write should repair it =3D=3D Formatting 'TEST_DIR/t.IMGFMT', fmt=3DIMGFMT size=3D134217728 wrote 512/512 bytes at offset 0 --=20 2.53.0 From nobody Mon Sep 28 00:51:54 2026 Delivered-To: importer@patchew.org Authentication-Results: mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass(p=quarantine dis=none) header.from=openvz.org ARC-Seal: i=1; a=rsa-sha256; t=1787141265; cv=none; d=zohomail.com; s=zohoarc; b=Jf/dgxedFkvfNjB35Be0AJgLdGlOESqdZTchYTh/GrILC/h3AnNe5d3mI4m2/1xJwiPLe9gq4MaHXaWLse9ZGk8k4fHWd5/+6O8bS966jdwRKbuGyDVtSZBXoiWqTKcDFKsbUkNfV1Z8xYLD5xMETPaNyK/l6r2fOUTmQ5R8mrU= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1787141265; h=Content-Transfer-Encoding:Cc:Cc:Date:Date:From:From:In-Reply-To:List-Subscribe:List-Post:List-Id:List-Archive:List-Help:List-Unsubscribe:MIME-Version:Message-ID:References:Sender:Subject:Subject:To:To:Message-Id:Reply-To; bh=pmb9e2aAFvXxu+UaCLIym+x4WJEYWHUpLcXH/GTdPrQ=; b=kxht7LiFPk6JXSTnNx+mlb/8M2YWUE3PHRlpPlhHZgXlSAGft5aD7YMArf24+PH+jgrYTaFe7+vSOvOva1HA2X73YWDuI5GDA6VkUXV9Ze/f+cDjgiajMYSrJO6+1we/muNkRUd1mEPtnR+7d79WrpwLa/hDZNB6lHMkEH2gt+o= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass header.from= (p=quarantine dis=none) Return-Path: Received: from lists1p.gnu.org (lists1p.gnu.org [209.51.188.17]) by mx.zohomail.com with SMTPS id 1787141265173750.1689186055623; Wed, 19 Aug 2026 05:07:45 -0700 (PDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists1p.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1wwf3o-00048P-61; Wed, 19 Aug 2026 08:06:24 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists1p.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1wwf3f-00045e-0Y for qemu-devel@nongnu.org; Wed, 19 Aug 2026 08:06:18 -0400 Received: from mail-wr1-x433.google.com ([2a00:1450:4864:20::433]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1wwf3W-0007qB-7X for qemu-devel@nongnu.org; Wed, 19 Aug 2026 08:06:14 -0400 Received: by mail-wr1-x433.google.com with SMTP id ffacd0b85a97d-47f7872abb6so501269f8f.3 for ; Wed, 19 Aug 2026 05:06:05 -0700 (PDT) Received: from athena.sw.ru ([2a06:5b06:b600:300:258d:1706:7089:f765]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-499aa1111a7sm59244785e9.6.2026.08.19.05.06.03 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 19 Aug 2026 05:06:03 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=openvz.org; s=google; t=1787141165; x=1787745965; darn=nongnu.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to:content-type; bh=pmb9e2aAFvXxu+UaCLIym+x4WJEYWHUpLcXH/GTdPrQ=; b=CFTB46OJfxzqJNE1pXUOoCKvB8t8FRynuGNeiC1bJ2ql8oQ5KfQKNED7Zx3zQ/0QbD myYtPFfpRdeVg5DGIPmaUXclGZQmXde+1x8DbQkU71193lRB0McF2wnjPZfZf5y9CaI6 0xDYmocXGGJ80DYME30vOs1cTyyhKgVn7pE48nTbQxjL3tZCfZk8QLEVYSRNTuEW8gVq GbbkVHwTIusGucg4KrZawor6sUSx2zEK5jUKxc/oVbDQIlE7VsCBnVNN4raugQ8V2AiV 8D0HtRrcoLpxrCOxyVpV1XPVElI0sEYlBwjPNXogBhLIsZWXeNnjPIf/ZTyov1g0fhdi eknQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1787141165; x=1787745965; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to:content-type; bh=pmb9e2aAFvXxu+UaCLIym+x4WJEYWHUpLcXH/GTdPrQ=; b=C9gM3KSsvQ8zylBys8MnPF+QQA6Gi/zEiKKbXUe4amU0xACGNkGdld4B88yng7n+dr wvdUz2Z38kvy548eUiriiNnIInH8zVurfN6WcCC5WOZMF1XH8mAStRQVrDOVRXjcVMpz YIo+w5tCHEMJ0apwA3ZsBwW3jPS4nYyaWRFqdRkJRj3dJa1S+ROTzP17xvE+bQWsum9v lSo0qoJbdiSPRBUXiuV9lxBhqCwMD38vtuqWesWPBgRWxAG879ZPD7YeBWEWmNfUdHJb i8lMn4k9UX8nVgvqTphnbnsz5K1axL7lf8Q/SjemrS5ZDMpoNi16J1R2zf5tNwUoqa2a qWcA== X-Gm-Message-State: AOJu0Yy+JvXs1bzF+fnCQQ4k3VquJ5ZJU+UmtVExB21PAe35JXP/oT7F IyPJRQJ/sS3YC44YtHJpq5yaKt7AiZgpAg9BlzcSbtR4z4eIBRbcJpw+tVWJ1//xSAsmeWpknvA AcRLs X-Gm-Gg: AR+sD12AJicQE3mB2TgGe5pHzxEqUJtLXfNhew1xXzIjXcIDXTSX5A3Bx9e8coAMpLP xsU6+w4iuNUGQKkPXIpYtAj5vJg+zKubfmoKhN8/2D/FCh0yeVWDs1KdPzSzBRsQ00u1twJSVL6 tSNYawp6lojnmQDQZc8+HX/kxsCB04NCHEqx3beEG2yuMLQAhTQ5Ge4QPSboRySfNkksq2sGMyf ghEdrMirZKyXT5xRg7JW+zL1OFtyRP4/kUOAIClBNKbjnREMtSUXre4b/z5d02Ah8zw1dzIR/8q kdeYWgPCtP7hjKA59vMM/2zGQhLtm33wlK754KE7LuYU+TNeU8bnwPBDFkJJ7Guvg4hEBXpCGAq KoxGOqk8cMjBUqcA0eu6Y5qWmocEf34zcbg13N99Q0hFAjN0l7JbG2vW8mWq8weRMFIYuhgvhv5 WdrzAbjggZLGyVTbf79+AM+m9AgBiR6yygfIsVzCWEVBQQXvM75IMTOmqkkQ== X-Received: by 2002:a05:600c:19c7:b0:495:7a04:b006 with SMTP id 5b1f17b1804b1-499aa192422mr61140545e9.8.1787141164426; Wed, 19 Aug 2026 05:06:04 -0700 (PDT) From: "Denis V. Lunev" To: qemu-devel@nongnu.org Cc: qemu-block@nongnu.org, "Denis V. Lunev" , Kevin Wolf , Hanna Reitz , Andrey Drobyshev Subject: [PATCH v3 2/5] block: reject a reopen of an unusable node instead of crashing Date: Wed, 19 Aug 2026 14:05:55 +0200 Message-ID: <20260819120558.3870413-3-den@openvz.org> X-Mailer: git-send-email 2.53.0 In-Reply-To: <20260819120558.3870413-1-den@openvz.org> References: <20260819120558.3870413-1-den@openvz.org> MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Received-SPF: pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) client-ip=209.51.188.17; envelope-from=qemu-devel-bounces+importer=patchew.org@nongnu.org; helo=lists1p.gnu.org; Received-SPF: pass client-ip=2a00:1450:4864:20::433; envelope-from=den@openvz.org; helo=mail-wr1-x433.google.com X-Spam_score_int: -20 X-Spam_score: -2.1 X-Spam_bar: -- X-Spam_report: (-2.1 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, RCVD_IN_DNSWL_NONE=-0.0001, SPF_HELO_NONE=0.001, SPF_PASS=-0.001 autolearn=ham autolearn_force=no X-Spam_action: no action X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: qemu development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org Sender: qemu-devel-bounces+importer=patchew.org@nongnu.org X-ZohoMail-DKIM: pass (identity @openvz.org) X-ZM-MESSAGEID: 1787141267023158500 Content-Type: text/plain; charset="utf-8" From: Denis V. Lunev qcow2_signal_corruption() drops bs->drv, so a node can lose its driver at any time and a reopen has to expect that. Both ends of the path assume otherwise: $ qemu-io -c "read 0 64k" -c "reopen -r" corrupt.qcow2 qcow2: Marking image as corrupt: Cluster allocation offset 0x1200 unaligned (L2 offset: 0x40000, L2 index: 0); ... Segmentation fault bdrv_reopen_queue_child() dereferences bs->drv while descending into the children the node opened itself, and bdrv_reopen_prepare() asserts on it. commit_clean() reopens the base of a commit job back to read-only, so a base which goes corrupt under the job arrives here too. There is nothing to reopen for such a node, so stop descending into its children and let bdrv_reopen_prepare() report what every caller of bdrv_reopen() already handles. bdrv_reopen_commit() and bdrv_reopen_abort() keep their assertion, only a prepared entry reaches them. Signed-off-by: Denis V. Lunev CC: Kevin Wolf CC: Hanna Reitz CC: Andrey Drobyshev Reviewed-by: Andrey Drobyshev --- block.c | 14 +++++++++++++- tests/qemu-iotests/060 | 30 ++++++++++++++++++++++++++++++ tests/qemu-iotests/060.out | 13 +++++++++++++ 3 files changed, 56 insertions(+), 1 deletion(-) diff --git a/block.c b/block.c index f0a6042e61..e39f15816a 100644 --- a/block.c +++ b/block.c @@ -4486,6 +4486,11 @@ bdrv_reopen_queue_child(BlockReopenQueue *bs_queue, = BlockDriverState *bs, !qdict_haskey(options, "backing.driver"); } =20 + /* An unusable node is rejected by bdrv_reopen_prepare(), do not desce= nd */ + if (!bs->drv) { + return bs_queue; + } + QLIST_FOREACH(child, &bs->children, next) { QDict *new_child_options =3D NULL; bool child_keep_old =3D keep_old_opts; @@ -4881,9 +4886,16 @@ bdrv_reopen_prepare(BDRVReopenState *reopen_state, B= lockReopenQueue *queue, bool drv_prepared =3D false; =20 assert(reopen_state !=3D NULL); - assert(reopen_state->bs->drv !=3D NULL); GLOBAL_STATE_CODE(); + drv =3D reopen_state->bs->drv; + if (drv =3D=3D NULL) { + GRAPH_RDLOCK_GUARD_MAINLOOP(); + + error_setg(errp, "Block node '%s' has no driver left to reopen", + bdrv_get_device_or_node_name(reopen_state->bs)); + return -ENOMEDIUM; + } =20 /* This function and each driver's bdrv_reopen_prepare() remove * entries from reopen_state->options as they are processed, so diff --git a/tests/qemu-iotests/060 b/tests/qemu-iotests/060 index 5cd21a6f68..ce49fc34ec 100755 --- a/tests/qemu-iotests/060 +++ b/tests/qemu-iotests/060 @@ -486,6 +486,36 @@ echo # Image should not have been marked corrupt _img_info --format-specific | grep 'corrupt:' =20 +echo +echo "=3D=3D=3D Testing the reopen of an image corrupted at runtime =3D=3D= =3D" +echo + +_make_test_img 64M +poke_file "$TEST_IMG" "$l1_offset" "\x00\x00\x00\x00\x2a\x2a\x2a\x2a" + +# The read leaves the node unusable, the reopen must report that +echo "{'execute': 'qmp_capabilities'} + {'execute': 'human-monitor-command', + 'arguments': {'command-line': 'qemu-io drive \"read 0 512\"'}} + {'execute': 'blockdev-reopen', + 'arguments': {'options': [{'node-name': 'drive', + 'driver': 'qcow2', + 'read-only': true, + 'file': { + 'driver': 'file', + 'filename': '$TEST_IMG' + }}]}} + {'execute': 'quit'}" \ + | $QEMU -qmp stdio -nographic -nodefaults \ + -blockdev "{'node-name': 'drive', + 'driver': 'qcow2', + 'file': { + 'driver': 'file', + 'filename': '$TEST_IMG' + }}" \ + 2>&1 \ + | _filter_qmp | _filter_qemu_io + # success, all done echo "*** done" rm -f $seq.full diff --git a/tests/qemu-iotests/060.out b/tests/qemu-iotests/060.out index a37bf446e9..ad1912a43b 100644 --- a/tests/qemu-iotests/060.out +++ b/tests/qemu-iotests/060.out @@ -436,4 +436,17 @@ qcow2: Image is corrupt: L2 table offset 0x2a2a2a00 un= aligned (L1 index: 0); fur {"return": {}} =20 corrupt: false + +=3D=3D=3D Testing the reopen of an image corrupted at runtime =3D=3D=3D + +Formatting 'TEST_DIR/t.IMGFMT', fmt=3DIMGFMT size=3D67108864 +QMP_VERSION +{"return": {}} +qcow2: Marking image as corrupt: L2 table offset 0x2a2a2a00 unaligned (L1 = index: 0); further corruption events will be suppressed +{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event"= : "BLOCK_IMAGE_CORRUPTED", "data": {"device": "", "msg": "L2 table offset 0= x2a2a2a00 unaligned (L1 index: 0)", "node-name": "drive", "fatal": true}} +read failed: Input/output error +{"return": ""} +{"error": {"class": "GenericError", "desc": "Block node 'drive' has no dri= ver left to reopen"}} +{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event"= : "SHUTDOWN", "data": {"guest": false, "reason": "host-qmp-quit"}} +{"return": {}} *** done --=20 2.53.0 From nobody Mon Sep 28 00:51:54 2026 Delivered-To: importer@patchew.org Authentication-Results: mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass(p=quarantine dis=none) header.from=openvz.org ARC-Seal: i=1; a=rsa-sha256; t=1787141262; cv=none; d=zohomail.com; s=zohoarc; b=GJraKFgWEzB5I5Qks11ZqOiuAw2AYir7XXhIUeU8OK4OGFT9NZQwE1GNsjbZAioXRAKJ+dS8GXJuhoShP+4o+O9NDdzoR6drw9tWmbSgDtbHWbjl5DtIU+DiW6T+6YL8Q6F7LMyosfQarJXxiM2Y/yw3hFcMnMi3Y0g1ZNcl/qE= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1787141262; h=Content-Transfer-Encoding:Cc:Cc:Date:Date:From:From:In-Reply-To:List-Subscribe:List-Post:List-Id:List-Archive:List-Help:List-Unsubscribe:MIME-Version:Message-ID:References:Sender:Subject:Subject:To:To:Message-Id:Reply-To; bh=lUGQaUR+CLxPkQrQ5gkup6JnciUavYiHeppLJtzEdQE=; b=MK3CVmUq+F/9UfH9DpwcTHEtIpgC11SG/FLEBrEBqCWB8ZC/aR+ZrfXkqrh9IgqoXPl1jM67XRjPtOM0sVIOvJJzcxXHZ4yo6kOf6sWRfgVII1BaPoESth2EwoOKTKyRpBn0HsFnlbgk2zglHjWnczvIzvx9UjtSY6rmhrhNjTg= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass header.from= (p=quarantine dis=none) Return-Path: Received: from lists1p.gnu.org (lists1p.gnu.org [209.51.188.17]) by mx.zohomail.com with SMTPS id 1787141261857134.07515584757152; Wed, 19 Aug 2026 05:07:41 -0700 (PDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists1p.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1wwf3q-00049a-2G; Wed, 19 Aug 2026 08:06:26 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists1p.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1wwf3j-00046c-9h for qemu-devel@nongnu.org; Wed, 19 Aug 2026 08:06:20 -0400 Received: from mail-wm1-x32d.google.com ([2a00:1450:4864:20::32d]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1wwf3d-0007qS-4n for qemu-devel@nongnu.org; Wed, 19 Aug 2026 08:06:16 -0400 Received: by mail-wm1-x32d.google.com with SMTP id 5b1f17b1804b1-4954d29264cso4850875e9.2 for ; Wed, 19 Aug 2026 05:06:07 -0700 (PDT) Received: from athena.sw.ru ([2a06:5b06:b600:300:258d:1706:7089:f765]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-499aa1111a7sm59244785e9.6.2026.08.19.05.06.04 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 19 Aug 2026 05:06:05 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=openvz.org; s=google; t=1787141166; x=1787745966; darn=nongnu.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to:content-type; bh=lUGQaUR+CLxPkQrQ5gkup6JnciUavYiHeppLJtzEdQE=; b=i/0BHDwImWyLGxiDE1R9plYiufwsI5b43Uijzvq5EV8sUnx1pLivCqiJsb2C6uus3W BVjzACGl5AUYTfU4fpIiT9wgiXrD8wd+xq5dyqYu5dewvD0sYyEQLT8m6DYknPj47QCI tFrdzuiLx6NyZqi9gVgoUSOG8xV15WCgdLbyDVNqnoFA+AmU7ltcZu6+LtLID7MXInCG AVEYEXl9XvogbCvmdOMIj/XPJQWBbXssZcB//5fnkugm7vfs7CyaziIsItAol2qr/dqa csWju1gYodEB7LRtb6yoyDgTtuKycj7agGqDZ1vICLZOr9cmQEKGf4kSOkQDryw1+VWc Y1Hg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1787141166; x=1787745966; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to:content-type; bh=lUGQaUR+CLxPkQrQ5gkup6JnciUavYiHeppLJtzEdQE=; b=p57W7wuofVsAlWfGiZGbD75DkaoJYIvIVqgMYqgGqMUee4BFRKZK25of7xqBgIxrBa SXxSnDGAfAH6jQBvVlnxNOtIeTA4aihjrx6bs0H4dqQVmvE/NHSNXj8WftC/f/cU4By2 e/FyYwuvCq4HQplQgQsL7lz4UMKkCGQiCL6LSSI/CffokB5LMNwZY/N9WZMJgf6JeSBA dADNRU1FUGfxcqlh/BNDXTHrci1GzBlqHgkoChqtIQ/3mehXC+dSwy6spVLN4GRvGQHb DfIdUg3MEuvnTZW70uDEpyZ4nDTbnFeJhs/p1DWwvm+67UKWCnj6RCqpJk2AMiKWT9Cl 2BOg== X-Gm-Message-State: AOJu0YygGpLK5A2KMYs8190vKCcZsZcjxzf3sdvfsp3WVm4yiXyhQTPg haLUjh+EtyPix66zmbhovobYrDcrs4WJrgH2NQRm0QnNgwBhjLRr/sAHg2sdLkpqvo1nEueemRA zZJs2 X-Gm-Gg: AR+sD120dNO7DoNyjgw8jRZg40ssBQprxhFsUzxErwUK/AUBX/Y6wPtyZc3WZuV0TY+ 6I14Go40PlAaPL0EcfiJyLJceKiHnVheB5LZCJojk5lCv8x+w4hKiMPIAwZiaEg73BjF3vMdMzs uL+AyLqwpNwtXPZP+w+iLqpcpgKvbqmwFpl7XP2PGykLkmNLYGHhZO0IjQ/Ukzb5DhTfJrsqXP0 +Oit2vrHpuBccreOVdb45oBTamJ1W+G3LYKqdW9GxXYSk+HG+7d5Vat4PlpR6A/X6V8xN3N+wG3 gxNoaNL6NNiEhCSrt69iYC8uIHDJA/lpXX4CzkmGpP/fi3KnwBvo1ExpVPGYpl1uVmhacPwdP7h zW88kVHblunuYLWOE31HC5/FHAHd35kCpzr1Fa5Gw9pZ7X6PA0Ww3+EGKQY/NLspJ7M4tcLQtUz uQ38exZkA+mN49pU+HIBuwBLQD6XPslnTaKgdjCvNn7M+juZdff7lfnsVPmA== X-Received: by 2002:a05:600c:8b27:b0:499:7024:9d4a with SMTP id 5b1f17b1804b1-499aa16a958mr87712855e9.8.1787141165724; Wed, 19 Aug 2026 05:06:05 -0700 (PDT) From: "Denis V. Lunev" To: qemu-devel@nongnu.org Cc: qemu-block@nongnu.org, "Denis V. Lunev" , Kevin Wolf , Hanna Reitz , Andrey Drobyshev Subject: [PATCH v3 3/5] block: let bdrv_reopen_commit_post() report a failure Date: Wed, 19 Aug 2026 14:05:56 +0200 Message-ID: <20260819120558.3870413-4-den@openvz.org> X-Mailer: git-send-email 2.53.0 In-Reply-To: <20260819120558.3870413-1-den@openvz.org> References: <20260819120558.3870413-1-den@openvz.org> MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Received-SPF: pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) client-ip=209.51.188.17; envelope-from=qemu-devel-bounces+importer=patchew.org@nongnu.org; helo=lists1p.gnu.org; Received-SPF: pass client-ip=2a00:1450:4864:20::32d; envelope-from=den@openvz.org; helo=mail-wm1-x32d.google.com X-Spam_score_int: -20 X-Spam_score: -2.1 X-Spam_bar: -- X-Spam_report: (-2.1 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, RCVD_IN_DNSWL_NONE=-0.0001, SPF_HELO_NONE=0.001, SPF_PASS=-0.001 autolearn=ham autolearn_force=no X-Spam_action: no action X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: qemu development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org Sender: qemu-devel-bounces+importer=patchew.org@nongnu.org X-ZohoMail-DKIM: pass (identity @openvz.org) X-ZM-MESSAGEID: 1787141263255158500 Content-Type: text/plain; charset="utf-8" From: Denis V. Lunev The callback runs after bdrv_reopen_multiple() has committed the transaction, so it cannot reject the reopen. It can still find that the node it has just made writable is unusable, and has no way to say so: bdrv_reopen() returns success and the caller carries on. Give it a return value and an Error argument. The reopen stays committed, the error only reports that the node is gone. Every queued node still gets its callback, the first error is the one reported. A callback may leave its node without a driver, and so may the I/O of a later bdrv_reopen_prepare(), so do not assume that the nodes still ahead of it in the queue have one. qcow2 is the only implementation and does not fail yet. An error therefore means one of two things now, either that the reopen was denied and nothing changed, or that it went through and left a tree which cannot be used. Nothing is undone in the second case: the node is beyond repair by another reopen, and a caller which reacts to the error by reopening anything is making it worse. bdrv_reopen_multiple() says so, nothing else changes. Signed-off-by: Denis V. Lunev CC: Kevin Wolf CC: Hanna Reitz CC: Andrey Drobyshev Reviewed-by: Andrey Drobyshev --- block.c | 24 +++++++++++++++++++++--- block/qcow2.c | 4 +++- include/block/block_int-common.h | 9 +++++++-- 3 files changed, 31 insertions(+), 6 deletions(-) diff --git a/block.c b/block.c index e39f15816a..b29202c8d5 100644 --- a/block.c +++ b/block.c @@ -4582,6 +4582,10 @@ void bdrv_reopen_queue_free(BlockReopenQueue *bs_que= ue) * If all devices prepare successfully, then the changes are committed * to all devices. * + * A failure means either that the reopen was denied and nothing changed, + * or that it went through and a driver then found the node unusable. In + * the second case nothing is undone and the tree is no longer usable. + * * All affected nodes must be drained between bdrv_reopen_queue() and * bdrv_reopen_multiple(). * @@ -4658,15 +4662,29 @@ int bdrv_reopen_multiple(BlockReopenQueue *bs_queue= , Error **errp) tran_commit(tran); bdrv_graph_wrunlock(); =20 + ret =3D 0; QTAILQ_FOREACH_REVERSE(bs_entry, bs_queue, entry) { BlockDriverState *bs =3D bs_entry->state.bs; + Error *local_err =3D NULL; + int commit_ret; =20 - if (bs->drv->bdrv_reopen_commit_post) { - bs->drv->bdrv_reopen_commit_post(&bs_entry->state); + if (!bs->drv || !bs->drv->bdrv_reopen_commit_post) { + continue; + } + + commit_ret =3D bs->drv->bdrv_reopen_commit_post(&bs_entry->state, + &local_err); + assert(commit_ret >=3D 0 || local_err); + + if (commit_ret < 0 && ret =3D=3D 0) { + /* Committed already, so report the first failure and go on */ + error_propagate(errp, local_err); + ret =3D commit_ret; + } else { + error_free(local_err); } } =20 - ret =3D 0; goto cleanup; =20 abort: diff --git a/block/qcow2.c b/block/qcow2.c index 1543255eba..553a94d003 100644 --- a/block/qcow2.c +++ b/block/qcow2.c @@ -2145,7 +2145,7 @@ static void qcow2_reopen_commit(BDRVReopenState *stat= e) g_free(state->opaque); } =20 -static void qcow2_reopen_commit_post(BDRVReopenState *state) +static int qcow2_reopen_commit_post(BDRVReopenState *state, Error **errp) { GRAPH_RDLOCK_GUARD_MAINLOOP(); =20 @@ -2163,6 +2163,8 @@ static void qcow2_reopen_commit_post(BDRVReopenState = *state) bdrv_get_node_name(state->bs)); } } + + return 0; } =20 static void qcow2_reopen_abort(BDRVReopenState *state) diff --git a/include/block/block_int-common.h b/include/block/block_int-com= mon.h index 147c08155f..035e54d434 100644 --- a/include/block/block_int-common.h +++ b/include/block/block_int-common.h @@ -239,8 +239,13 @@ struct BlockDriver { BDRVReopenState *reopen_state, BlockReopenQueue *queue, Error **er= rp); void GRAPH_UNLOCKED_PTR (*bdrv_reopen_commit)( BDRVReopenState *reopen_state); - void GRAPH_UNLOCKED_PTR (*bdrv_reopen_commit_post)( - BDRVReopenState *reopen_state); + /* + * Runs once the reopen is committed, so it cannot reject it. Returns = 0, + * or a negative errno with @errp set to report that the node it has + * just reopened is unusable, which it may leave without a driver. + */ + int GRAPH_UNLOCKED_PTR (*bdrv_reopen_commit_post)( + BDRVReopenState *reopen_state, Error **errp); void GRAPH_UNLOCKED_PTR (*bdrv_reopen_abort)( BDRVReopenState *reopen_state); void (*bdrv_join_options)(QDict *options, QDict *old_options); --=20 2.53.0 From nobody Mon Sep 28 00:51:54 2026 Delivered-To: importer@patchew.org Authentication-Results: mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass(p=quarantine dis=none) header.from=openvz.org ARC-Seal: i=1; a=rsa-sha256; t=1787141224; cv=none; d=zohomail.com; s=zohoarc; b=VlnmE4gCeYP7TbRjRoZzDN7YVA3RxCf1Z2RyGZMwwzZx7SZF2Bpe/UEQ0NsA6qQBnNj4cjRv/tTDysIhShZvN0uLd3qBCccuFqcavA6kbZe/gy4BVMv4s3+cMB3P5i4EGS4RmMRxOTfwvCeBzDugh6wbKo4M0ooWs9bzfe8kvj8= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1787141224; h=Content-Transfer-Encoding:Cc:Cc:Date:Date:From:From:In-Reply-To:List-Subscribe:List-Post:List-Id:List-Archive:List-Help:List-Unsubscribe:MIME-Version:Message-ID:References:Sender:Subject:Subject:To:To:Message-Id:Reply-To; bh=HWuPTwu7BBNInsioIn++QUXErXu52JLqTBsasESnDi8=; b=Fg8jzelzEjnuCDBmcjiaKfMKK8hSBmcK9r4JiiUVJltDSdOrO9ai8dUDG9o7QRgB3P2h4b+yvKQZSY4a8WVfSKscJwLj8tddUZRtPUe39SUJACa9n5YXgvnv0tLvG7n8n3UjBfvOjVxq0C1x1O/V8IXuVWm3nufpsYLAukiMH3E= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass header.from= (p=quarantine dis=none) Return-Path: Received: from lists1p.gnu.org (lists1p.gnu.org [209.51.188.17]) by mx.zohomail.com with SMTPS id 1787141224652283.64479257480025; Wed, 19 Aug 2026 05:07:04 -0700 (PDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists1p.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1wwf3p-00048k-51; Wed, 19 Aug 2026 08:06:25 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists1p.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1wwf3j-00046T-66 for qemu-devel@nongnu.org; Wed, 19 Aug 2026 08:06:20 -0400 Received: from mail-wm1-x332.google.com ([2a00:1450:4864:20::332]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1wwf3d-0007qm-5e for qemu-devel@nongnu.org; Wed, 19 Aug 2026 08:06:16 -0400 Received: by mail-wm1-x332.google.com with SMTP id 5b1f17b1804b1-4980dc26022so9898545e9.1 for ; Wed, 19 Aug 2026 05:06:08 -0700 (PDT) Received: from athena.sw.ru ([2a06:5b06:b600:300:258d:1706:7089:f765]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-499aa1111a7sm59244785e9.6.2026.08.19.05.06.05 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 19 Aug 2026 05:06:06 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=openvz.org; s=google; t=1787141167; x=1787745967; darn=nongnu.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to:content-type; bh=HWuPTwu7BBNInsioIn++QUXErXu52JLqTBsasESnDi8=; b=uEMcH0aGv2VClF+LtdOiw6spWavtOzA7vG1xbxcEKfDPyyiRganjZERrGJZfTmxXml O+XNVlaZ8Du/JeisjJ2GACubPObseX2woeZCsbFn9dexybRjKORvEn40mOynEewUQPbB Al/tZOQANGLVM9zpBKK9D99u9kbq8i5gOuLy389WZbiYriu916ldB1KL/8QEwVoX0av7 d4sgIhjsIHtpRtzF8ScMAGEoPZPi6urVUoWlNEFwgyu2IgY80wLSAQ7s4Wiu6vngPa1D AANEXUMmAS4wBAsLYzqe6lgWZsCTbgdUjPhxVa43sn2y1da9NfDhLJTdFg4wtw7yhNMa 7UXw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1787141167; x=1787745967; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to:content-type; bh=HWuPTwu7BBNInsioIn++QUXErXu52JLqTBsasESnDi8=; b=ZiJBqKKUjbBYb5bdYyJk8Njfex4Al+EGZasu9XSJaxvmgTZ8iU12/XMaw/0RjHMg8d 73AUwkvIKPVY/NJiiakSYW5Rfe8PpJCyXh2hP9+qaMv7eemJilM4sIFra/Cs183H30JI DJVjuiZsob2WWKJ4G1I1UdCvDl/w7YctCHSLsXg5VVLPMMn5QP0EHe6y7MJaD3qcOv2J Xy2w0HvvE0/XzMOYr44PlHXMbB/FbF2FD5muIwuVwfD6tzbD5HinljLCl2MCvsp3jJHv I8n4gwyEBdFC2t/u7wO6gGipYgJRi/3CdICH8VfFYvSNfdxjFzvbgC5mEgbmsysj6j6L 6JXg== X-Gm-Message-State: AOJu0YxZwuOH9uXlAhH3p6st43eXT5hPd9d/GcrXUghqjpznIV7ot5AH 1iJ89e6DBshjM0lygbJWrBU+R9LXUCRlcgFMXY6ADc9hC5Z4wHPjg/fpY2fgDjlFTQ4oQdlAF6A hzWH0 X-Gm-Gg: AR+sD11wm1fF/Q1CdR9j1vdi3SyTrteFeK+6X6soGZD3g2UePn7yGLrLjs2ckepFYw4 oIJzlyGpPY22UxZ/XV+uoOZ42ih2CY2bzyHWWmAZAl3cde09MXeTR5mqaL85Ij5GaTN0LPJXa9V hvnFeAVlBX6vWG7QSTQj1pYCZTaQDkoHIDubHA9aQsSoGn1G/WexCnvrbBU4TIlP0nT7cspIrLj 5AMfXH+JSsrhY0gjZMWZqAbRkIJrR4c64o1DR4CTe0e9UeGJzWoBklXO+sbZ1ShakYxBn41FAW4 K6BNLVkkFBzOCJ9Kd4K+Dh/iomRElara7rjpbfGv7744lD6ruKXpt+L8KPSJsPHChFRBCxD5Ylf r/v2WSxR7S0980oVQy6Kg/FvEW+7wedyHAyM8I8QvLmroniqf8ExByD1Hw5HkjpzKQCp1TT/co/ HpvKbo4nm280fLmun92c7pnkX9qV93GLJPVX5Ns27jorbTSyYuBERxVgBQOg== X-Received: by 2002:a05:600c:6091:b0:499:8aff:59b6 with SMTP id 5b1f17b1804b1-499aa1ead50mr93721075e9.14.1787141166863; Wed, 19 Aug 2026 05:06:06 -0700 (PDT) From: "Denis V. Lunev" To: qemu-devel@nongnu.org Cc: qemu-block@nongnu.org, "Denis V. Lunev" , Kevin Wolf , Hanna Reitz , Andrey Drobyshev Subject: [PATCH v3 4/5] block: remember the flags a reopen starts from Date: Wed, 19 Aug 2026 14:05:57 +0200 Message-ID: <20260819120558.3870413-5-den@openvz.org> X-Mailer: git-send-email 2.53.0 In-Reply-To: <20260819120558.3870413-1-den@openvz.org> References: <20260819120558.3870413-1-den@openvz.org> MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Received-SPF: pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) client-ip=209.51.188.17; envelope-from=qemu-devel-bounces+importer=patchew.org@nongnu.org; helo=lists1p.gnu.org; Received-SPF: pass client-ip=2a00:1450:4864:20::332; envelope-from=den@openvz.org; helo=mail-wm1-x332.google.com X-Spam_score_int: -20 X-Spam_score: -2.1 X-Spam_bar: -- X-Spam_report: (-2.1 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, RCVD_IN_DNSWL_NONE=-0.0001, SPF_HELO_NONE=0.001, SPF_PASS=-0.001 autolearn=ham autolearn_force=no X-Spam_action: no action X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: qemu development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org Sender: qemu-devel-bounces+importer=patchew.org@nongnu.org X-ZohoMail-DKIM: pass (identity @openvz.org) X-ZM-MESSAGEID: 1787141227164158500 Content-Type: text/plain; charset="utf-8" From: Denis V. Lunev bdrv_reopen_commit() updates bs->open_flags, so by the time .bdrv_reopen_commit_post() runs a driver can no longer tell whether the node has just become writable or was writable all along. Only the transition is worth reacting to. Record the flags while the queue is built, next to the other pre-reopen state BDRVReopenState already keeps, and let a driver ask about them the way it asks about the node itself. The predicate which bdrv_is_writable_after_reopen() spells out gets a name for that, and stays private to block.c. Signed-off-by: Denis V. Lunev CC: Kevin Wolf CC: Hanna Reitz CC: Andrey Drobyshev Reviewed-by: Andrey Drobyshev --- block.c | 17 ++++++++++++++--- include/block/block-common.h | 1 + include/block/block_int-common.h | 2 ++ 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/block.c b/block.c index b29202c8d5..6280a13610 100644 --- a/block.c +++ b/block.c @@ -2193,14 +2193,18 @@ static int bdrv_reopen_get_flags(BlockReopenQueue *= q, BlockDriverState *bs) return bs->open_flags; } =20 +/* An inactive node may not be written to, even though it is not read-only= */ +static bool bdrv_flags_writable(int flags) +{ + return (flags & (BDRV_O_RDWR | BDRV_O_INACTIVE)) =3D=3D BDRV_O_RDWR; +} + /* Returns whether the image file can be written to after the reopen queue= @q * has been successfully applied, or right now if @q is NULL. */ static bool bdrv_is_writable_after_reopen(BlockDriverState *bs, BlockReopenQueue *q) { - int flags =3D bdrv_reopen_get_flags(q, bs); - - return (flags & (BDRV_O_RDWR | BDRV_O_INACTIVE)) =3D=3D BDRV_O_RDWR; + return bdrv_flags_writable(bdrv_reopen_get_flags(q, bs)); } =20 /* @@ -2214,6 +2218,12 @@ bool bdrv_is_writable(BlockDriverState *bs) return bdrv_is_writable_after_reopen(bs, NULL); } =20 +bool bdrv_reopen_was_writable(const BDRVReopenState *state) +{ + GLOBAL_STATE_CODE(); + return bdrv_flags_writable(state->old_flags); +} + static char *bdrv_child_user_desc(BdrvChild *c) { GLOBAL_STATE_CODE(); @@ -4473,6 +4483,7 @@ bdrv_reopen_queue_child(BlockReopenQueue *bs_queue, B= lockDriverState *bs, bs_entry->state.options =3D options; bs_entry->state.explicit_options =3D explicit_options; bs_entry->state.flags =3D flags; + bs_entry->state.old_flags =3D bs->open_flags; =20 /* * If keep_old_opts is false then it means that unspecified diff --git a/include/block/block-common.h b/include/block/block-common.h index 895ea17541..eb2dd8aff1 100644 --- a/include/block/block-common.h +++ b/include/block/block-common.h @@ -358,6 +358,7 @@ typedef QTAILQ_HEAD(BlockReopenQueue, BlockReopenQueueE= ntry) BlockReopenQueue; typedef struct BDRVReopenState { BlockDriverState *bs; int flags; + int old_flags; /* bs->open_flags is updated on commit */ BlockdevDetectZeroesOptions detect_zeroes; bool backing_missing; BlockDriverState *old_backing_bs; /* keep pointer for permissions upda= te */ diff --git a/include/block/block_int-common.h b/include/block/block_int-com= mon.h index 035e54d434..4ea1a78494 100644 --- a/include/block/block_int-common.h +++ b/include/block/block_int-common.h @@ -1346,6 +1346,8 @@ char *create_tmp_file(Error **errp); void bdrv_parse_filename_strip_prefix(const char *filename, const char *pr= efix, QDict *options); =20 +bool bdrv_reopen_was_writable(const BDRVReopenState *state); + =20 int bdrv_check_qiov_request(int64_t offset, int64_t bytes, QEMUIOVector *qiov, size_t qiov_offset, --=20 2.53.0 From nobody Mon Sep 28 00:51:54 2026 Delivered-To: importer@patchew.org Authentication-Results: mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass(p=quarantine dis=none) header.from=openvz.org ARC-Seal: i=1; a=rsa-sha256; t=1787141224; cv=none; d=zohomail.com; s=zohoarc; b=dzql5CclzwZwYRCqt9Z90H/ucekdv8T1xX3LMu0U5tCSSLdwth6Yz/FNtRo0ww7b9AjBZS2mbBqD7yjfnCcHwj3qwUw/WJUuvnR+4oplXkqkSKKNW4jcHdJdHtYp4rktULDpKrOxljAhJfk7pdKy5Mc9p9rMbre1CSkqWPHthBA= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1787141224; h=Content-Transfer-Encoding:Cc:Cc:Date:Date:From:From:In-Reply-To:List-Subscribe:List-Post:List-Id:List-Archive:List-Help:List-Unsubscribe:MIME-Version:Message-ID:References:Sender:Subject:Subject:To:To:Message-Id:Reply-To; bh=+Qz5AEGImTZP6KcLGs93xFGKU7NSCedSN1v3EMrliUY=; b=Gx/dIiWLNxi/Wo7HzceQX+S03eZnHMWgTBI+TGa0dnWMFTF9OlPUmakjHVhWyFB54DrSlzX353/V0w8O8lpg5QmlNQ9Y+WcqoZ4nEOiOZd0cYtRrL7cea/GNUADQodtg/tk28ldqb7HwZQHsw1wf33n6e1NyNjdgB+bjJXsgTw0= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass header.from= (p=quarantine dis=none) Return-Path: Received: from lists1p.gnu.org (lists1p.gnu.org [209.51.188.17]) by mx.zohomail.com with SMTPS id 1787141224665912.7346292753456; Wed, 19 Aug 2026 05:07:04 -0700 (PDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists1p.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1wwf3r-0004AL-Gi; Wed, 19 Aug 2026 08:06:27 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists1p.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1wwf3j-00046d-Aq for qemu-devel@nongnu.org; Wed, 19 Aug 2026 08:06:20 -0400 Received: from mail-wm1-x32f.google.com ([2a00:1450:4864:20::32f]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1wwf3d-0007r5-50 for qemu-devel@nongnu.org; Wed, 19 Aug 2026 08:06:17 -0400 Received: by mail-wm1-x32f.google.com with SMTP id 5b1f17b1804b1-4954a2e73a9so6208685e9.3 for ; Wed, 19 Aug 2026 05:06:09 -0700 (PDT) Received: from athena.sw.ru ([2a06:5b06:b600:300:258d:1706:7089:f765]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-499aa1111a7sm59244785e9.6.2026.08.19.05.06.07 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 19 Aug 2026 05:06:07 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=openvz.org; s=google; t=1787141168; x=1787745968; darn=nongnu.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to:content-type; bh=+Qz5AEGImTZP6KcLGs93xFGKU7NSCedSN1v3EMrliUY=; b=LM7oXQVrfDplwqU4lpaI+EhBS1PWG9xEfU3WCt7uPMhhDGM+kaTxXo/4uhPgrF6XYP UOwL9aMcqrD+Zz0QwvrvEDgdvunWmkzICmjD8ZEkugk6dfrH0o/NXTzmozNX1Q5GAbma 2McK1X8L2PCeOGmeGnV60pKybFaCdcJX0oCBIoVDwcPMX35NMkmqlIEKwb0UbdObqC3T N349d7mRbwWlfbosQToaMdVOH6tMfcy6y66z1q0YO7BlbqVX56nur2xg+JipStBoYH2b 1qSD2j+B+KeSWCjmWrAKnlr1LXLCJGzoIq005B3JfuJHK+skdb4f9SADaIeryBAVTgMU KQhw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1787141168; x=1787745968; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to:content-type; bh=+Qz5AEGImTZP6KcLGs93xFGKU7NSCedSN1v3EMrliUY=; b=ChkBDK5fmU8DpxOIUmUb61QbD6VQnStEyg2NqkYt1bvxPtclvwCYH+HZM26cG7z8Of jsrSWu0gLLK7xj8e0RcCGE0sK+ldBuyg0Rv0rBHGddkWShlSlzCKUUy6iZSzz5r4zVH0 4uKJ/leX20v1R1y6CSyzgMQCKW1e/vlBzgv85pc+ty+LXJDepfL0y/SPNUhDD07Q8qTH 6t2JjVTdWDF29w4wQJmmSYkjoby7dDcTMLgMsnk9m/RZLBYn7eQ2mzwAGgdO/nHYinaZ OFG3Z+L015GJWab/1KK6wNtjxKFydegcLXTzjegWSCOpI/1VT5BPA25neX4clOod2gzU uQTg== X-Gm-Message-State: AOJu0YwgKS7/SL7ZiG46oybLRUunhGJy2yyspSsVD7ScM99iWc7cKTOU IivKcbVx/0ZK4zxyn3K8TjP/IHUFl3U+PoXgeEQuug1Xri3DZaiVAXEgihE7bMk57NRNJEJiBgv y3Xl2 X-Gm-Gg: AR+sD13/29OZkWJLRvvkdmZ1N0hDWpj77q5VLzPEEQCcjOo5AKY42O6be1a6abIfaIC PEXDh1lDrOR/+oBKcFCGntAKbHkNbf4UIblFWIuEAixd53jfFnmAFaFf/vGo7OAvU5XsaASCkHA qbqvSJ4svvfMiAtmtiMWxzSdYoAPYGg/wFU8cej/XRPNRZ5uZxO3XLtV6A+QmFqsRLtpSi601QI o/6ievSUpCNOg788CVuwszQhQp5PLm4CqIKO7XRC5+SJzZUeN9Em6FyeNq7ZxnAoLk6IX7eVmHC hdKmXo+3/FhsXFRBeMlZQRw3WNDKCS6eazdCLYK9+wmbKFZuGLmaV+uq7XUCIn1R5cjo0Rx8IgY rGhKI9RkErq1jFfieaOODvBBQPMX3lrKGat2ptH5sOAnXeCdn05fJDDJSA5iQLlAHiICPyxCPS2 TSzvo8G4geJAWBqiQLnxBjGyP0/ND/2hH0XyeNVQPcYvGJE2CzWsK/62tEZg== X-Received: by 2002:a05:600c:a412:b0:499:ae94:be05 with SMTP id 5b1f17b1804b1-499ae94be48mr31316465e9.0.1787141168067; Wed, 19 Aug 2026 05:06:08 -0700 (PDT) From: "Denis V. Lunev" To: qemu-devel@nongnu.org Cc: qemu-block@nongnu.org, "Denis V. Lunev" , Kevin Wolf , Hanna Reitz , Andrey Drobyshev Subject: [PATCH v3 5/5] qcow2: repair a dirty image when it becomes writable Date: Wed, 19 Aug 2026 14:05:58 +0200 Message-ID: <20260819120558.3870413-6-den@openvz.org> X-Mailer: git-send-email 2.53.0 In-Reply-To: <20260819120558.3870413-1-den@openvz.org> References: <20260819120558.3870413-1-den@openvz.org> MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Received-SPF: pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) client-ip=209.51.188.17; envelope-from=qemu-devel-bounces+importer=patchew.org@nongnu.org; helo=lists1p.gnu.org; Received-SPF: pass client-ip=2a00:1450:4864:20::32f; envelope-from=den@openvz.org; helo=mail-wm1-x32f.google.com X-Spam_score_int: -20 X-Spam_score: -2.1 X-Spam_bar: -- X-Spam_report: (-2.1 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, RCVD_IN_DNSWL_NONE=-0.0001, SPF_HELO_NONE=0.001, SPF_PASS=-0.001 autolearn=ham autolearn_force=no X-Spam_action: no action X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: qemu development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org Sender: qemu-devel-bounces+importer=patchew.org@nongnu.org X-ZohoMail-DKIM: pass (identity @openvz.org) X-ZM-MESSAGEID: 1787141227328158500 Content-Type: text/plain; charset="utf-8" From: Denis V. Lunev A dirty image must be repaired before anything allocates a cluster in it. qcow2_do_open() does that, but only for a node that is writable from the start. A node opened read-only skips it, and nothing revisits the question once that node becomes writable, which block-commit does routinely: commit_active_start() and commit_start() reopen the base read-write for the duration of the job. With lazy refcounts the on-disk refcount block then still accounts for the metadata clusters only, so the allocator restarts at the front of the image and hands out clusters that L2 entries point at. Two guest offsets end up sharing one host cluster. Nothing fails, the corrupt bit stays clear, and a clean close clears the dirty bit, so no later open repairs the image either. The bit also stays set for the whole writable session, so a node which is merely writable says nothing. Refusing the reopen instead is simpler and keeps it atomic, but it leaves nowhere to go: the base belongs to a chain the VM has open, so the qemu-img check -r such an error would ask for cannot take the write lock it needs. The repair does the trick in most cases anyway. Do the repair in qcow2_reopen_commit_post(), the earliest point where the node is writable. An inactive node is skipped: bdrv_activate() calls qcow2_do_open() again through qcow2_co_invalidate_cache(). commit_post cannot reject the reopen, so a failed repair leaves only what qcow2_signal_corruption() does, take the driver away from the node, rather than let writes alias live clusters. Return the error and skip the bitmaps. Signed-off-by: Denis V. Lunev CC: Kevin Wolf CC: Hanna Reitz CC: Andrey Drobyshev Reviewed-by: Andrey Drobyshev --- block/qcow2.c | 21 +++++++ qapi/block-core.json | 16 +++++ tests/qemu-iotests/039 | 60 ++++++++++++++++++ tests/qemu-iotests/039.out | 36 +++++++++++ tests/qemu-iotests/040 | 122 +++++++++++++++++++++++++++++++++++++ tests/qemu-iotests/040.out | 4 +- 6 files changed, 257 insertions(+), 2 deletions(-) diff --git a/block/qcow2.c b/block/qcow2.c index 553a94d003..e91523699f 100644 --- a/block/qcow2.c +++ b/block/qcow2.c @@ -2147,8 +2147,29 @@ static void qcow2_reopen_commit(BDRVReopenState *sta= te) =20 static int qcow2_reopen_commit_post(BDRVReopenState *state, Error **errp) { + ERRP_GUARD(); + BDRVQcow2State *s =3D state->bs->opaque; + GRAPH_RDLOCK_GUARD_MAINLOOP(); =20 + if (!bdrv_reopen_was_writable(state) && bdrv_is_writable(state->bs) && + (s->incompatible_features & QCOW2_INCOMPAT_DIRTY)) { + BdrvCheckResult result =3D {0}; + int ret; + + ret =3D bdrv_check(state->bs, &result, BDRV_FIX_ERRORS | BDRV_FIX_= LEAKS); + if (ret < 0 || result.check_errors || !state->bs->drv) { + ret =3D ret < 0 ? ret : -EIO; + /* No write may reach an image whose refcounts are unaccounted= */ + state->bs->drv =3D NULL; + error_setg_errno(errp, -ret, "Could not repair dirty image '%s= '", + bdrv_get_device_or_node_name(state->bs)); + error_append_hint(errp, "The image is left dirty and this node= " + "holds it open until the node is removed\n"); + return ret; + } + } + if (state->flags & BDRV_O_RDWR) { Error *local_err =3D NULL; =20 diff --git a/qapi/block-core.json b/qapi/block-core.json index 199efc1e00..9aec081f7b 100644 --- a/qapi/block-core.json +++ b/qapi/block-core.json @@ -1891,6 +1891,11 @@ # size to match the size of the smaller top, you can safely truncate # it yourself once the commit operation successfully completes. # +# The base is opened read-write for the duration of the job. A dirty +# qcow2 base is repaired first, which reads all of its metadata and +# holds up every other request while it runs. The command fails if +# that repair does not succeed. +# # @job-id: identifier for the newly-created block job. If omitted, # the device name will be used. (Since 2.7) # @@ -4989,6 +4994,17 @@ # transaction, so if one of them fails then the whole transaction is # cancelled. # +# An error is also returned when a device cannot be used once it has +# been reopened. Such a reopen is not undone, so an error does not +# always mean that nothing has changed. A node the driver gave up on +# serves nothing at all: it keeps its image open, makes +# `query-named-block-nodes` fail for as long as it is in the graph, +# and `blockdev-del` removes it only once nothing refers to it. +# +# Reopening a dirty qcow2 image read-write repairs it first, which +# reads all of its metadata and holds up every other request while it +# runs. +# # The command receives a list of block devices to reopen. For each # one of them, the top-level @node-name option (from # `BlockdevOptions`) must be specified and is used to select the block diff --git a/tests/qemu-iotests/039 b/tests/qemu-iotests/039 index 3d0c073d65..3f37c36ca8 100755 --- a/tests/qemu-iotests/039 +++ b/tests/qemu-iotests/039 @@ -33,6 +33,7 @@ status=3D1 # failure is the default! _cleanup() { _cleanup_test_img + rm -f "$TEST_DIR/blkdebug.conf" } trap "_cleanup; exit \$status" 0 1 2 3 15 =20 @@ -176,6 +177,65 @@ $QEMU_IO -c "write 0 512" "$TEST_IMG" | _filter_qemu_io # The dirty bit must not be set _qcow2_dump_header | grep incompatible_features =20 +echo +echo "=3D=3D Reopening a dirty image read/write should repair it =3D=3D" + +_make_test_img -o "compat=3D1.1,lazy_refcounts=3Don" $size + +_NO_VALGRIND \ +$QEMU_IO -c "write -P 0x5a 0 512" \ + -c "sigraise $(kill -l KILL)" "$TEST_IMG" 2>&1 \ + | _filter_qemu_io + +# The dirty bit must be set +_qcow2_dump_header | grep incompatible_features + +# Without the repair this write would alias the cluster at offset 0 +$QEMU_IO -r -c "reopen -w" \ + -c "write -P 0xb1 1M 512" \ + -c "read -P 0x5a 0 512" "$TEST_IMG" | _filter_qemu_io + +_check_test_img + +echo +echo "=3D=3D A read/write reopen must not check the image =3D=3D" + +_make_test_img -o "compat=3D1.1,lazy_refcounts=3Don" $size + +_NO_VALGRIND \ +$QEMU_IO -c "write -P 0x5a 0 512" \ + -c "reopen -o l2-cache-size=3D1M" \ + -c "sigraise $(kill -l KILL)" "$TEST_IMG" 2>&1 \ + | _filter_qemu_io + +# The dirty bit must still be set, it belongs to the running session +_qcow2_dump_header | grep incompatible_features + +echo +echo "=3D=3D A failed repair must fail the reopen =3D=3D" + +_make_test_img -o "compat=3D1.1,lazy_refcounts=3Don" $size + +_NO_VALGRIND \ +$QEMU_IO -c "write -P 0x5a 0 512" \ + -c "sigraise $(kill -l KILL)" "$TEST_IMG" 2>&1 \ + | _filter_qemu_io + +cat > "$TEST_DIR/blkdebug.conf" <&1 \ + | _filter_testdir | _filter_qemu_io | _filter_generated_node_ids + +# The corrupt bit needs a write of its own, so the image is only left dirty +_qcow2_dump_header | grep incompatible_features + echo echo "=3D=3D Creating an image file with lazy_refcounts=3Doff =3D=3D" =20 diff --git a/tests/qemu-iotests/039.out b/tests/qemu-iotests/039.out index ce8ee57721..cc6ca3ab95 100644 --- a/tests/qemu-iotests/039.out +++ b/tests/qemu-iotests/039.out @@ -79,6 +79,42 @@ wrote 512/512 bytes at offset 0 512 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) incompatible_features [] =20 +=3D=3D Reopening a dirty image read/write should repair it =3D=3D +Formatting 'TEST_DIR/t.IMGFMT', fmt=3DIMGFMT size=3D134217728 +wrote 512/512 bytes at offset 0 +512 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +./common.rc: Killed ( VALGRIND_QEMU=3D"${VALGRIND_QEMU_IO}" _qemu_proc_exe= c "${VALGRIND_LOGFILE}" "$QEMU_IO_PROG" $QEMU_IO_ARGS "$@" ) +incompatible_features [0] +ERROR cluster 5 refcount=3D0 reference=3D1 +Rebuilding refcount structure +Repairing cluster 1 refcount=3D1 reference=3D0 +Repairing cluster 2 refcount=3D1 reference=3D0 +wrote 512/512 bytes at offset 1048576 +512 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 512/512 bytes at offset 0 +512 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +No errors were found on the image. + +=3D=3D A read/write reopen must not check the image =3D=3D +Formatting 'TEST_DIR/t.IMGFMT', fmt=3DIMGFMT size=3D134217728 +wrote 512/512 bytes at offset 0 +512 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +./common.rc: Killed ( VALGRIND_QEMU=3D"${VALGRIND_QEMU_IO}" _qemu_proc_exe= c "${VALGRIND_LOGFILE}" "$QEMU_IO_PROG" $QEMU_IO_ARGS "$@" ) +incompatible_features [0] + +=3D=3D A failed repair must fail the reopen =3D=3D +Formatting 'TEST_DIR/t.IMGFMT', fmt=3DIMGFMT size=3D134217728 +wrote 512/512 bytes at offset 0 +512 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +./common.rc: Killed ( VALGRIND_QEMU=3D"${VALGRIND_QEMU_IO}" _qemu_proc_exe= c "${VALGRIND_LOGFILE}" "$QEMU_IO_PROG" $QEMU_IO_ARGS "$@" ) +ERROR cluster 5 refcount=3D0 reference=3D1 +Rebuilding refcount structure +qemu-io: ERROR writing refblock: Input/output error +qemu-io: Could not repair dirty image 'NODE_NAME': Input/output error +The image is left dirty and this node holds it open until the node is remo= ved +read failed: No medium found +incompatible_features [0] + =3D=3D Creating an image file with lazy_refcounts=3Doff =3D=3D Formatting 'TEST_DIR/t.IMGFMT', fmt=3DIMGFMT size=3D134217728 wrote 512/512 bytes at offset 0 diff --git a/tests/qemu-iotests/040 b/tests/qemu-iotests/040 index 5c18e413ec..452c87f9cd 100755 --- a/tests/qemu-iotests/040 +++ b/tests/qemu-iotests/040 @@ -951,6 +951,128 @@ class TestCommitWithOverriddenBacking(iotests.QMPTest= Case): self.vm.qmp('block-job-complete', device=3D'commit') self.vm.event_wait('BLOCK_JOB_COMPLETED') =20 +QCOW2_INCOMPAT_FEATURES_OFFSET =3D 72 +QCOW2_INCOMPAT_DIRTY =3D 1 << 0 + +image_size =3D 4 * 1024 * 1024 +dirty_base =3D os.path.join(iotests.test_dir, 'dirty-base.img') +mid =3D os.path.join(iotests.test_dir, 'dirty-mid.img') +top =3D os.path.join(iotests.test_dir, 'dirty-top.img') + + +class TestCommitDirtyBase(iotests.QMPTestCase): + def setUp(self) -> None: + if iotests.imgfmt !=3D 'qcow2': + self.case_skip('the dirty bit is a qcow2 feature') + iotests.qemu_img_create('-f', iotests.imgfmt, '-o', + 'compat=3D1.1,lazy_refcounts=3Don', dirty_= base, + str(image_size)) + # Killing the process leaves the refcounts of the written cluster = stale + iotests.qemu_io_popen('-t', 'writethrough', + '-c', 'write -P 0x5a 0 512', + '-c', 'sigraise 9', dirty_base).communicate() + iotests.qemu_img_create('-f', iotests.imgfmt, '-b', dirty_base, + '-F', iotests.imgfmt, mid) + iotests.qemu_img_create('-f', iotests.imgfmt, '-b', mid, + '-F', iotests.imgfmt, top) + # The commit has to allocate for this, which is where the stale + # refcounts hand out the cluster holding the data written above + qemu_io('-c', 'write -P 0xb1 1M 512', mid) + + self.vm =3D iotests.VM() + self.vm.launch() + self.vm.cmd('blockdev-add', driver=3D'file', filename=3Ddirty_base, + node_name=3D'base-file') + + self.assertEqual(self.incompatible_features(), QCOW2_INCOMPAT_DIRT= Y) + + def tearDown(self) -> None: + if self.vm.is_running(): + self.vm.shutdown() + for image in (dirty_base, mid, top): + os.remove(image) + + def add_chain(self, base_file: str) -> None: + self.vm.cmd('blockdev-add', driver=3Diotests.imgfmt, file=3Dbase_f= ile, + node_name=3D'base', read_only=3DTrue) + self.vm.cmd('blockdev-add', driver=3D'file', filename=3Dmid, + node_name=3D'mid-file') + self.vm.cmd('blockdev-add', driver=3Diotests.imgfmt, file=3D'mid-f= ile', + node_name=3D'mid', backing=3D'base') + self.vm.cmd('blockdev-add', driver=3D'file', filename=3Dtop, + node_name=3D'top-file') + self.vm.cmd('blockdev-add', driver=3Diotests.imgfmt, file=3D'top-f= ile', + node_name=3D'top', backing=3D'mid') + + def check_base(self) -> None: + result =3D iotests.qemu_img_check(dirty_base) + self.assertEqual(result['check-errors'], 0) + self.assertEqual(result.get('corruptions', 0), 0) + # Without the repair the commit would have aliased this cluster + qemu_io('-c', 'read -P 0x5a 0 512', '-c', 'read -P 0xb1 1M 512', + dirty_base) + + def incompatible_features(self) -> int: + with open(dirty_base, 'rb') as img: + img.seek(QCOW2_INCOMPAT_FEATURES_OFFSET) + return struct.unpack('>Q', img.read(8))[0] + + def test_commit_repairs_base(self) -> None: + self.add_chain('base-file') + + self.vm.cmd('block-commit', job_id=3D'job0', device=3D'top', + top_node=3D'mid', base_node=3D'base') + self.wait_until_completed(drive=3D'job0') + + self.vm.shutdown() + self.assertEqual(self.incompatible_features(), 0) + self.check_base() + + def test_active_commit_repairs_base(self) -> None: + self.add_chain('base-file') + + # Without top-node the whole chain commits, through + # commit_active_start() rather than commit_start() + self.vm.cmd('block-commit', job_id=3D'job0', device=3D'top', + base_node=3D'base') + self.complete_and_wait(drive=3D'job0') + + self.vm.shutdown() + self.assertEqual(self.incompatible_features(), 0) + self.check_base() + + def test_failed_repair_fails_the_commit(self) -> None: + self.vm.cmd('blockdev-add', driver=3D'blkdebug', image=3D'base-fil= e', + node_name=3D'base-blkdebug', + inject_error=3D[{'event': 'none', 'iotype': 'write', + 'errno': 5}]) + self.add_chain('base-blkdebug') + + result =3D self.vm.qmp('block-commit', job_id=3D'job0', device=3D'= top', + top_node=3D'mid', base_node=3D'base') + self.assert_qmp(result, 'error/class', 'GenericError') + self.assertIn("Could not repair dirty image 'base'", + result['error']['desc']) + + # The base is left in the graph, and nothing can be queried while + # it is there + result =3D self.vm.qmp('query-named-block-nodes', flat=3DTrue) + self.assert_qmp(result, 'error/desc', 'Block device base is ejecte= d') + + # It only goes away once nothing refers to it + result =3D self.vm.qmp('blockdev-del', node_name=3D'base') + self.assert_qmp(result, 'error/desc', + "Node 'base' is busy: node is used as backing hd o= f " + "'mid'") + + # Marking the image corrupt needs a write of its own, which fails = too + self.assertEqual(self.incompatible_features(), QCOW2_INCOMPAT_DIRT= Y) + + # Nothing can use the base any more, and that is what is reported + result =3D self.vm.qmp('block-commit', job_id=3D'job1', device=3D'= top', + top_node=3D'mid', base_node=3D'base') + self.assert_qmp(result, 'error/desc', 'Device has no medium') + if __name__ =3D=3D '__main__': iotests.main(supported_fmts=3D['qcow2', 'qed'], supported_protocols=3D['file']) diff --git a/tests/qemu-iotests/040.out b/tests/qemu-iotests/040.out index 1bb1dc5f0e..f3cbf73a01 100644 --- a/tests/qemu-iotests/040.out +++ b/tests/qemu-iotests/040.out @@ -1,5 +1,5 @@ -................................................................. +.................................................................... ---------------------------------------------------------------------- -Ran 65 tests +Ran 68 tests =20 OK --=20 2.53.0