From nobody Wed Aug 26 08:11:27 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=1787578746; cv=none; d=zohomail.com; s=zohoarc; b=jCj8F9E+jPA5tF3ayjyLb6eNX60ZHZRd7K9kv+oPLAvRb7QP8oebcssojazpYYRtMjk+A/NQaBOlJtj7tPKck4eZfphhs91V6G4G+tKIOdXzMOcs956INMPleG8flvDqyT5y3u4TfcIrA3MSPLW0GeSAO7zWYeTVe9Waq/kxiho= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1787578746; 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=DEtOnidl3ou2oeXSipGVMeNr8jrN0dWWhLCJjPRUG+M=; b=mXdh0YaN+/hrSLL6lEE5KEtnS++0+J6moE/k1BaKO7LW4LzPPdxrjzTU5KLOUvgIm/zIVXZ0uDTFLoRyqbfwTkgK7AlAqNgHRc81Af3cr0nkcTi+wHQBfB569geAfllicea5KoyvDvjYrKeIodxk+GPZgrCZeyUIEjo2OoqKklI= 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 178757874685137.42875441454282; Mon, 24 Aug 2026 06:39:06 -0700 (PDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists1p.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1wyUru-0003pb-1A; Mon, 24 Aug 2026 09:37:42 -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 1wyUrq-0003mK-9O for qemu-devel@nongnu.org; Mon, 24 Aug 2026 09:37:38 -0400 Received: from mail-ej1-x632.google.com ([2a00:1450:4864:20::632]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1wyUrn-0002aD-5x for qemu-devel@nongnu.org; Mon, 24 Aug 2026 09:37:38 -0400 Received: by mail-ej1-x632.google.com with SMTP id a640c23a62f3a-c15cf78d1a2so394268766b.1 for ; Mon, 24 Aug 2026 06:37:34 -0700 (PDT) Received: from athena.sw.ru ([2a06:5b06:b600:300:9048:7bf0:d3e2:2b9e]) by smtp.gmail.com with ESMTPSA id a640c23a62f3a-c24966f58dfsm1236658166b.38.2026.08.24.06.37.32 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 24 Aug 2026 06:37:33 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=openvz.org; s=google; t=1787578654; x=1788183454; 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=DEtOnidl3ou2oeXSipGVMeNr8jrN0dWWhLCJjPRUG+M=; b=t5xqf9ohc3T81fZ8kR5NL4EV6cmBQ8ielBV0/ppefeyJ45ElMJVCWJk73mIqScZHT5 rNW1EO+mHxVZfrTmQrlTgs1Yk+m+bEqInou21zAXqk3/S6NKhTp90ipOtUegV0vQ4guW uSUdl8ejL00HPYOdYdNphMJF6gmGV7qStbExj8WfdnNHZauKvOjSET01qZtwe9vPhZhG fYEhFZ1M3cEv9EYQjGCFLtqMlUrDPe5XSMTxn9y43FOqshbu5i+rdbsxnlQvazio07p5 iUaBxi2kTBVuaojkqF10GrEbA3ufaTf5uOgAZy/O9j0vxlfHRQNK6IyK+JILgDPlWBR2 FBrw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1787578654; x=1788183454; 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=DEtOnidl3ou2oeXSipGVMeNr8jrN0dWWhLCJjPRUG+M=; b=FTh3o0THrCf1SpaXOBl5rr5YjC92mIWRoaFsqhSYmJB0FukMkaMh3UVPby8LgKhxQN IW5yTjEkHpV9bKSzKgEQ0IG7uvSZIgEXcX73CktxD1KYdzUN0psmOeBfwYSUgKByYIqE hPdXlkHhaXyO8EURDP5MWMAv7alfQeZTOn5GxZTVQsWub9eJp8RJnyKyDiR/4U5R5BL9 IWVUuoghwHBVQ0HXfHTPVpJ4QBj9sUUTXWMLU+xZzRJxZUMJ8gg7SrXJvz2Rcn63spkY j57DVsPwSkXtzSGLRhTqAVT8laJ3OQsE9mJ1/RxLD8xI5Jf2IgZRf6du2ty0GclIYpB0 SrJg== X-Gm-Message-State: AFuF++kRnm7TIMvIfmMGDEbuuRYAU7miBTKXcEJIL3wIJfmUQCuHyOur AiDwsdJ096vAF2djPQcHFGXnpQwwEoMuI1AY37ni3FGhaYugLPQgL1GQVNYsYVpjpXw7KgIb+1c yEIb+ X-Gm-Gg: AR+sD13IqX8ZnEFTKJ5r/wuCEhzxrAG1ygNFYUrLpE2as6kKd1WUHJsBCJg+Q0oOZCK ZX9OYDTwvXvY308K5g1/TZ7hATi7WXvHbyNGqVzha36ii1UVqlCfPEHGJtkFt839QEa8vSjuRlJ GkxxvD0luWBYTS6sMQjKzQaUQklT8CpY7FQpAV2r0ocCAID+ZHltu+f+uaGOF05lNVgQ47jas8M IIFRrfGZnPVYnUPxJ1F3pTTRB/fRcrUKI0S/n2JcjELkEN67LqhxuaSHBpbhz2Eu6zDoDXnJ2ZS tMv+wg7XFysYpLxRG8lfFlSrhC/J+k6S/g/j3XdYwphNExvMsCmnJinahfQi6HWtBFPzcqEzcNA IRyFbaEmNJ5bTz/AY5d8hSOzqydFyw9RtbqfPqpJPklXT2d/9WFfsuh235mVHh5bdVMiARQZ+Lr ACUuVXwfYopb3cgMAusGotMjOsHWvAlIzGsXUpS1DRQ1CAAn0HW3fwfNmF2w== X-Received: by 2002:a17:907:6d15:b0:c24:4128:c19d with SMTP id a640c23a62f3a-c246a2e9378mr2726316666b.4.1787578653591; Mon, 24 Aug 2026 06:37:33 -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 , qemu-stable@nongnu.org Subject: [PATCH v4 1/5] qcow2: do not clear the dirty bit when reopening a read-only node Date: Mon, 24 Aug 2026 15:37:25 +0200 Message-ID: <20260824133729.1141990-2-den@openvz.org> X-Mailer: git-send-email 2.53.0 In-Reply-To: <20260824133729.1141990-1-den@openvz.org> References: <20260824133729.1141990-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::632; envelope-from=den@openvz.org; helo=mail-ej1-x632.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: 1787578748313158500 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 Cc: qemu-stable@nongnu.org --- 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 Wed Aug 26 08:11:27 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=1787578748; cv=none; d=zohomail.com; s=zohoarc; b=SijgW4vDHL5H3O/0IK2dlncD0euYdyVULnD0HiS70GB45fDbDObTKrxVw/MnhZEcW2K8oVtREzAHUlFeufFZ9jtvrP4wKNw3j/zX7FlmCDUPeOPtNfvycwB9EzdSvEsVgl41xmXnKWcAXQ+4sAYEA/gIJn6O/CDL4MzVqnk6t3I= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1787578748; 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=tT1pWzDf1PzRnWrUuIrKA5QxwcckvDOrTMYkBiLpWdc=; b=F22OMgka/yRGzFQABaenNlKN0iVPx87HgZUX2Wej4tfKIYiYXy2+1IVfRfYKFXgBJBPizNGFCn+8c4eMP14siq5LYFggEGswJHdX50Nsxf+9Dh0kWDa6Fsk6HFkTcxjjWyErHd5agxi8XmoNKLmPw7TQI7Xm8ltlbOsSrAz4iOs= 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 1787578748684632.6636777782461; Mon, 24 Aug 2026 06:39:08 -0700 (PDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists1p.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1wyUru-0003qZ-HH; Mon, 24 Aug 2026 09:37:42 -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 1wyUrs-0003o1-0o for qemu-devel@nongnu.org; Mon, 24 Aug 2026 09:37:40 -0400 Received: from mail-ej1-x62b.google.com ([2a00:1450:4864:20::62b]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1wyUro-0002ab-FK for qemu-devel@nongnu.org; Mon, 24 Aug 2026 09:37:39 -0400 Received: by mail-ej1-x62b.google.com with SMTP id a640c23a62f3a-c169ae1cb26so890666366b.1 for ; Mon, 24 Aug 2026 06:37:36 -0700 (PDT) Received: from athena.sw.ru ([2a06:5b06:b600:300:9048:7bf0:d3e2:2b9e]) by smtp.gmail.com with ESMTPSA id a640c23a62f3a-c24966f58dfsm1236658166b.38.2026.08.24.06.37.33 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 24 Aug 2026 06:37:34 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=openvz.org; s=google; t=1787578655; x=1788183455; 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=tT1pWzDf1PzRnWrUuIrKA5QxwcckvDOrTMYkBiLpWdc=; b=JOgAM1j/rIn5cOhgDXJfIxXUlVkB0XcviSkSZE8q2kGhDaGCr8IKOouhZk0COpaU7d E/c298VM4hqm+E5GejZcBWegZ7jJNX5ci+pTc+TBKthJ+deDcMe7MpZvUeXZHlBMJOzp 4u7dEsnVMmqR2SbS3LobXZUTqAx7q9w/ixwnL1ESfSS9kAVEzJR3Ey5hdfIr7+UHo5zj h4diJLpeUToCCXn1OdryQ/FvNh6QqUBCn0NOk4SzDgM6FbVe93oFe+5tLEdtXYZtmSyg 4R6Bh3l/tbLmCcIbrecDvK5b0WVqAqfgAutl8tBlCtjcjNaxGroFjHCEBHj2sV5lwqgN 777Q== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1787578655; x=1788183455; 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=tT1pWzDf1PzRnWrUuIrKA5QxwcckvDOrTMYkBiLpWdc=; b=OzCflVbxTSyo8LkwAE7XzYUmRmTna6XBVXsLHYlasGPIlvSFW9CJyoI/W0oB2+vGKK nZzbdjUBGFzOrdEtxxIflFn4Z8SfEIkyirZSwkaVWk6cDxzo/xHftco+8x7s7yrqXbC7 xwlDnshYw8LZJMu4PW8uXS5pMF7EU0FAPv3FpngaYdbals1Plcoq3pDjLCOzv0CkB387 h64RQOZvUyUGbDH+iqffjSK5EXHw6VvGy0T99oEmdRhnC2s4xwSuCmh2qNzTMvBe5/d4 pAXSPedybixn8k4xe3DWkASalWc3spmPt8crQ+2ZTQGOynPZIOV9gmPZlwgav7VxzjSW frSQ== X-Gm-Message-State: AFuF++nGnHbguXMiyIlIoiavLJ7nK2/jQTG7mEwGlOZ25SewaSNpGaDE t3yudPTJmOzmXbfqyh3nCa6hH7Vyeey2SEVrQE9FFmzCm2qswndqRYv2d5GAx8Uii37nUVHLeNT rCXsB X-Gm-Gg: AR+sD10fPnwZVLiMABQ2USlME7cuvC+vQLGKsQmwVCn4KYjUT+24wupqeRieY9L5fR4 x1J6MReB8SuEg+7d+/hhy5VsDwhSLYhxePihHmlVVfzEnMxmMtjD5Z+Nd0fBGtaRDVwUAN4dmUh zzXt3mWwJHDxWuIYgO/J6DCoGV4eqAJhBEIBhl5TiBuqS/HWF6Eg63T6lFDFV3WxQAo4Gm5yJ6h W4XQ85UGX+zdEPz1k2I6Ej/91CHD+otpqFm3Vw8K6kvzWxq1n5D3XCVDGMYGH0GCJTSeqUQxbmM vdnchz9WDceqN9DpbPsRQX7O3DgV+JyXVlKeZW2kiU32ODfjS3UWVFYVbanlaxsv1WTz2o/WCNT X5oozPbuDBZdeqQlIL61u2F5gY1WGlIiw0Ezm3dYfMBJPzd2UTv+rW6q4drN47AfP086Hn2xIkt a9KoigavZ6zybtOuKVX+oB4grh3ZpxMU9l8aKFipz/A4U0MnzpRfBfgoEyBg== X-Received: by 2002:a17:907:3e89:b0:c21:7c98:d9ec with SMTP id a640c23a62f3a-c244d6349a2mr3085489266b.5.1787578654840; Mon, 24 Aug 2026 06:37:34 -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 , qemu-stable@nongnu.org Subject: [PATCH v4 2/5] block: reject a reopen of an unusable node instead of crashing Date: Mon, 24 Aug 2026 15:37:26 +0200 Message-ID: <20260824133729.1141990-3-den@openvz.org> X-Mailer: git-send-email 2.53.0 In-Reply-To: <20260824133729.1141990-1-den@openvz.org> References: <20260824133729.1141990-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::62b; envelope-from=den@openvz.org; helo=mail-ej1-x62b.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: 1787578750237158500 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 Reviewed-by: Andrey Drobyshev CC: Kevin Wolf CC: Hanna Reitz CC: Andrey Drobyshev Cc: qemu-stable@nongnu.org --- 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 Wed Aug 26 08:11:27 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=1787578744; cv=none; d=zohomail.com; s=zohoarc; b=MWMmOumursn/SruNhc9VP29Jjkapw36pNl84mvBFRpK6/8ejcwETanSPnfGRPUl7qiC/cZLD5yTXhKtNYXfRMwyExkSYVlosl0jPins3a5OaZbAXVF4hzhcnKWFzlt3Go4k4wUv/nnySiVThndoluDHnBlo7DcAo8E17b61kOhQ= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1787578744; 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=3aZMmyQcXUw8u2baSyqRnIYWSMubnlt40sgnbbIDHKE=; b=Xc3VGB3C3eFP8h8UwY+clHClKgP/FY7csXobRVQp83hdxbb/DXhF1AxmxAJq06dCLVsO+mTyMSVT2ThbmsuiaMCUiLorCu6FGJolr8STuT5++ZgOZMONcjLYmRYqntntbsZeahzd+OR6PJ+IxQQLx/LGuIQ3VVvhk5yIn3rlh+E= 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 1787578744518359.6385919340572; Mon, 24 Aug 2026 06:39:04 -0700 (PDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists1p.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1wyUru-0003qa-Hi; Mon, 24 Aug 2026 09:37:42 -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 1wyUrs-0003oD-9P for qemu-devel@nongnu.org; Mon, 24 Aug 2026 09:37:40 -0400 Received: from mail-ej1-x631.google.com ([2a00:1450:4864:20::631]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1wyUrp-0002ax-HO for qemu-devel@nongnu.org; Mon, 24 Aug 2026 09:37:39 -0400 Received: by mail-ej1-x631.google.com with SMTP id a640c23a62f3a-c15cd3fd760so416124866b.2 for ; Mon, 24 Aug 2026 06:37:37 -0700 (PDT) Received: from athena.sw.ru ([2a06:5b06:b600:300:9048:7bf0:d3e2:2b9e]) by smtp.gmail.com with ESMTPSA id a640c23a62f3a-c24966f58dfsm1236658166b.38.2026.08.24.06.37.34 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 24 Aug 2026 06:37:35 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=openvz.org; s=google; t=1787578656; x=1788183456; 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=3aZMmyQcXUw8u2baSyqRnIYWSMubnlt40sgnbbIDHKE=; b=RzTieUe9jF2ZEXgQLiowbAu/ae22NExkAcVnEqMoj+nzSRGjroWpc2XV9q7NV9mtcz hM5lRJ8vSbeDf0Sh+/I+55gag1WAmf9TaZHLAvATXGabGohmYA/mYZEDTggwGG7ZcnWw yje7adlRo60+/B77IqJedR6SWEoWLMHKilecYleOH6FT0VuusRU7NHSfEycBGQA8YodV tdkKW+dOjgKqvHmKlj0XBbEu3eLnXOwNPRFKlLQVw9zD5soR91EkrpnIAtdxxFZ00664 y66d0wsc54zIFewsq1M0ZohRDvENFs5A3jujotdxq4HOTy4ew4YWDGU/pwgAqiYF/zGx wk+A== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1787578656; x=1788183456; 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=3aZMmyQcXUw8u2baSyqRnIYWSMubnlt40sgnbbIDHKE=; b=pWonIvfEEEMTNs+A/VFDGqH+k0mVZoJPjyDISvfShu0A5YQ0m3tD/8nnS47pGfzjnE Dhi2/8wehJY8Rbh9HWZOWea/kINQguYrKfSmGdrkCh3oBDn97cQKhK8vmvvUC6FphGqP rYV6UUqlMpxHPRoc4lBz8PZyzdscHulhunRtG05djhLlZ9VDtA/KCbuvq/l4GmWDnahM BeeIKekYdObzYZiEYOXk0+mHUPoLRfBFDe+GXH+6vmR752bI9YHAEl/InjFjXlv6xVZC DcvPTtIDGbweyUTt1pL+RLrzHxzPHEjjX3CkKXvRMZ1Au2B7pSlfuuoi776R6u9QQkDR BzZQ== X-Gm-Message-State: AFuF++nUPuqa8SFsYxgTStFPOK0fyBzGjkVP+ro4r19czH/HmSu4UoWj 2sOwY8uaKicGduZTH8CoFPhfHwBN2naWS6osaFpK6o0ahjBiZcA3LqpliGxVLGSyvP4+EEOzMEB Qa1/e X-Gm-Gg: AR+sD1377Hzu2hEH9yahGq0GOWGi49z+p+4KNPwa6J8Z8fxUp1lRKMyCJUXcw1IS3+M zfF9pHmRh7bjQIoShOXhF4Oz8aYo/WgkIvMWcGhf+kJAba26d2p4A4mCKzsP4/s6HLwYPwHzCQk mpIck4wBxC4vIpjhf18Ye2w+WXZGos24y/lnn29oErh0NUNS+mUETN3cmh5hb6ZE/69LXy0/TS9 g6H2Ve/U+fVeuwQ3yumUgRkJtujLoXNiF4ZG0CsMUu1u7IHehfpfgRS8/Yp0F6aKnr01shWnA2w nwQP2lKxSioN0BJLLBH9uTA6BSbOmNjvb2XGB/mV2dI8Y77OwnTTp0+DO0ZvQWCB5fxOAqXppGB AY2WmA2TsYsQAQw7kw38eDrWV7jeFMlL79p+2WqI30iT/86hv1JTPFyoTskqLFBA5hDlC+Q1xVX 4Zz/4GWpou6HLD2rnNCKxg/bcZWj1+UXNWnTEZ9dzgYZeP3ZIFUZ4pWT+W/xwhB8h2Ol4+ X-Received: by 2002:a17:907:845:b0:c20:fed2:898b with SMTP id a640c23a62f3a-c2492782dbemr1777435966b.23.1787578655928; Mon, 24 Aug 2026 06:37:35 -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 , qemu-stable@nongnu.org Subject: [PATCH v4 3/5] block: let bdrv_reopen_commit_post() report a failure Date: Mon, 24 Aug 2026 15:37:27 +0200 Message-ID: <20260824133729.1141990-4-den@openvz.org> X-Mailer: git-send-email 2.53.0 In-Reply-To: <20260824133729.1141990-1-den@openvz.org> References: <20260824133729.1141990-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::631; envelope-from=den@openvz.org; helo=mail-ej1-x631.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: 1787578746123158500 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 Reviewed-by: Andrey Drobyshev CC: Kevin Wolf CC: Hanna Reitz CC: Andrey Drobyshev Cc: qemu-stable@nongnu.org --- 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 Wed Aug 26 08:11:27 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=1787578732; cv=none; d=zohomail.com; s=zohoarc; b=UukJtIhzXmqrHlk+6F8zgdt1bXhGri+z8byP+Hl1I07eFfxq4pJrNzEONApF3/T3o+HYDMMiN7fLse7vOa5dbO8sUQ5Wlt4+rpJzAmIc8trk++yF0gscx8OlOsfcTgfQhqUQ2eBI/CkT+ZIIG8TcMy+Zp+7DqCuyJ8DxG16b7NM= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1787578732; 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=u+3tMmW+CbA3lTRhol/qEtVSa2RFqO8eSTJOauSbEqc=; b=hJHClFe067zAUqSQVseKRLCA9Rigz/f0tfWd8BfTrdGYL+vOFxYQmSiKDayrvb3AnwGj3q6lqSh068XPpkJf0StkbuUY6+0NbRSI84ThuP0vI7y/Zaw6ABmmDKQvQoNY4HVqlQaXT1JfxQ7sMpy/qhpf7O2u8FQADF8IhzBknxw= 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 1787578732439766.3939215278615; Mon, 24 Aug 2026 06:38:52 -0700 (PDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists1p.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1wyUrw-0003rd-4M; Mon, 24 Aug 2026 09:37:44 -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 1wyUrt-0003ox-Lo for qemu-devel@nongnu.org; Mon, 24 Aug 2026 09:37:41 -0400 Received: from mail-ed1-x52f.google.com ([2a00:1450:4864:20::52f]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1wyUrq-0002bK-HB for qemu-devel@nongnu.org; Mon, 24 Aug 2026 09:37:41 -0400 Received: by mail-ed1-x52f.google.com with SMTP id 4fb4d7f45d1cf-6a1542cdb53so4243599a12.2 for ; Mon, 24 Aug 2026 06:37:38 -0700 (PDT) Received: from athena.sw.ru ([2a06:5b06:b600:300:9048:7bf0:d3e2:2b9e]) by smtp.gmail.com with ESMTPSA id a640c23a62f3a-c24966f58dfsm1236658166b.38.2026.08.24.06.37.36 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 24 Aug 2026 06:37:36 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=openvz.org; s=google; t=1787578657; x=1788183457; 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=u+3tMmW+CbA3lTRhol/qEtVSa2RFqO8eSTJOauSbEqc=; b=UE3pxQ+dQqNcmJEfq4Gyv3r5LghGAU0w0Mns/BuSIQLHNB9A7mq4aShHq9dVVrdbPB JrDjsjCO+zXGhZlblsYYOlKNBP35e6ssF8/aoFIdUH3+4e1qn09mTEnM0eHCucvqDCno NUn1MCKlUgXsFSgaxbECK4byPURGdOon4MfzmgfuCqUrcCvsi2K13GIsZyJ5pD/hIb9N gyQ1000+p80WDoFiDzv7kfMeU6MbvY1Pbk1Pgz+R2UzswPl7O56WM4G9xVVVXk/Zn6AJ kXZukANeCKO8lbFFPq+LzHJjQ1ZHnB5XLFO4O60LrlDTSDo3d4reRO3/GTLlf8OLNtD4 yHqg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1787578657; x=1788183457; 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=u+3tMmW+CbA3lTRhol/qEtVSa2RFqO8eSTJOauSbEqc=; b=I7YvdDglMng7suSEV6OmDmkbvNGNiKVPv2QYboJJev4gHrXg8nOI+sxKFlfnIrEJJc P4RdePPOdjA6QK0Ihw5xuPUdn6RauXehAMXSRYdq02sVIt/PZbMqnf+QG5J04pUJwvWd KwqF7r0Q9nAMvX5ge0QaMn0HM4pnyRNCNeoH3Bpbuz1k154y99z6fpgNjOU2uhvjM4Ie QUNsJZe4nNGF+LMjYd/1gC7cDC+3kNZVDraA3sSogmylubjAWED83MgFdtvm/bNl0X36 efjokw02IGsoSlHIfLbjYA0EfOXLaaMy17fqfqYmWbUvDJRKPn8/u0ylBR6Mg0E8He/D fG+A== X-Gm-Message-State: AFuF++kQ0dbKEl4/Xi35/qmQEc4DseVqEy4dm6BGkcb/BS51cqa2srW4 OMMMibI6kQp/dMuvi93eAcFQIRSLkcreyFoLndoRAMlK4HgctfjqYlaE2x9rv3oA9nHgaWMFisB ki0yK X-Gm-Gg: AR+sD10s2Df+N66XlyjVwCyZ4CLFI2uPnAtznsLHqS6B11b7F0AF7PaCRAyeqrZKx9M uHLu2FIpzpwRgmFieFLCt9jX5saJueU53Z9q5YdSXMZ2W7xFwOeq01a9hn3SIPGXm2ufzy4Pnrf YvW7OeSBImIpJsO6I9P+yKdyVSmko+EtP3YIl0RKJcP1QQECIqJ5rFYCih4Uii/5UFnbHj3Yata l0bQuIcmw6bVMDmEWKPRRIklXu2rdTq5fGkFyeb+moZeiAzgfhXeZmintIgnHrM9Qx1Rpnl+YR7 yB7aVTmj0L6jUtOCueWzJihei4nz6jqCxjelFXpF+Itc3ZObvVeuRjIj6zozcsTQxdbLkhk0/nD MTFbvXC2ClxMu+RuJJU3v2s+1EszOhS6+oZHU51leT0kMTp9L0WFyXhoAQncfkp48jBNKBaoXcG HJNv8rsZblc5BzQUqJyxcYckLX8/MRhYnOTfo5FNx8bRm9h7rpZAOdfRJMbCXns2AH/Eq+v8C47 w== X-Received: by 2002:a17:907:948a:b0:c1f:922d:34c3 with SMTP id a640c23a62f3a-c24926a1c3dmr1823989966b.14.1787578656987; Mon, 24 Aug 2026 06:37:36 -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 , qemu-stable@nongnu.org Subject: [PATCH v4 4/5] block: remember the flags a reopen starts from Date: Mon, 24 Aug 2026 15:37:28 +0200 Message-ID: <20260824133729.1141990-5-den@openvz.org> X-Mailer: git-send-email 2.53.0 In-Reply-To: <20260824133729.1141990-1-den@openvz.org> References: <20260824133729.1141990-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::52f; envelope-from=den@openvz.org; helo=mail-ed1-x52f.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: 1787578734120158500 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 Reviewed-by: Andrey Drobyshev CC: Kevin Wolf CC: Hanna Reitz CC: Andrey Drobyshev Cc: qemu-stable@nongnu.org --- 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 Wed Aug 26 08:11:27 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=1787578739; cv=none; d=zohomail.com; s=zohoarc; b=drDmr/6uWz/94tDmNWQptTAkSbpBlVx6D+gI4V49hakv8kkAi7T55lkov9wlifrp4FkKQA+iiMWzGTjsT4HeljKMPaX4jEX3zSrQcxxYlhLPbBFgN8uxumFmdvzEEadv7UWRbVqhiUnYe0mhb59Izm73D4Lq37uJDC9MUGW3uAQ= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1787578739; 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=s9XFnRBTPgsRdQGCK5DBH78jDcPvK2A0wh6KqOPVhV8=; b=fTTPHEMVZB59TWHq6GH4VDmkkstodVWg757Ohcxu32XUJsaRQ8tJxxjyOH7px3j+V5bMzjUpV0aj5wP4gSSjt7tboud09bV9DNk/MITj20qlTkqPXKmEezd2VWVdTrOyuf2AFG6pBXPd0MRoAUGIOy4ugsPz30RGEVHU5qacsCg= 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 1787578739278197.7580151724212; Mon, 24 Aug 2026 06:38:59 -0700 (PDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists1p.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1wyUrx-0003tr-Vx; Mon, 24 Aug 2026 09:37:46 -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 1wyUrv-0003rP-TM for qemu-devel@nongnu.org; Mon, 24 Aug 2026 09:37:43 -0400 Received: from mail-ej1-x636.google.com ([2a00:1450:4864:20::636]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1wyUrs-0002cC-5M for qemu-devel@nongnu.org; Mon, 24 Aug 2026 09:37:43 -0400 Received: by mail-ej1-x636.google.com with SMTP id a640c23a62f3a-c15cf78d1a2so394279566b.1 for ; Mon, 24 Aug 2026 06:37:39 -0700 (PDT) Received: from athena.sw.ru ([2a06:5b06:b600:300:9048:7bf0:d3e2:2b9e]) by smtp.gmail.com with ESMTPSA id a640c23a62f3a-c24966f58dfsm1236658166b.38.2026.08.24.06.37.37 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 24 Aug 2026 06:37:37 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=openvz.org; s=google; t=1787578659; x=1788183459; 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=s9XFnRBTPgsRdQGCK5DBH78jDcPvK2A0wh6KqOPVhV8=; b=UTCdIlnmOfbnsGxSLZUf4GeO+C4jxN5hsRDcefQtY89+c5jxxH+0nBZKbppKHf7K5v DMP+suIqstvXgbRnDjVMFxAUnk8e+YfXqnNK2jkwbRl/NJx78au3M9AOQVwIqvLqJ299 OhbSiqnb18T6xZ4TCzi8fEOWQ5cIpUWZrdCCqq5BWT9eqT7lT+pD5fFw/QRgWBGBDmHk r1+erbVHTOSdE/peCefiwgMGMS6YswIiRQhpSxItE8vq9BdNPLYGZF3DYF/XLRTdhqH8 XPJZQqxsOoAQxz4KhXkaT7H7N67IHzo81yr60aljK2KTI1UbzbfUDg1bwjWtHfRDgEOf 7dZg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1787578659; x=1788183459; 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=s9XFnRBTPgsRdQGCK5DBH78jDcPvK2A0wh6KqOPVhV8=; b=TNiH4RNjm2G2ePZhDoI8P0WJT9s7v085mo1Nz3aR7M3+uWY7m6Kvu5ekbncQ7oWFZx 8aXo5I7FRAaPb7DrOqz8ZhhSpYS9IEKxbJN8/PmZvHHtbUq0iOm22/sgWEATPnUNdLZ4 AAjiuoK4rO80h3oAxUGWYgGZu/9o5jt10XWnBhB5r2lx1Oa1CJ2juRFyxr7GytkxMJ0c /fbdBycePFJNltUo5zmmd3ZPV7nItdiyvK24AQgZNn9hFq+HpOIaaDaL25lSeAyRElVD 3MGlM9paYb8+/58Gpnt5g+HzF2qsVzA8AFgxwtnegxv0AM87Q5PFbSsWEsRpVHozzHVb ED5Q== X-Gm-Message-State: AFuF++kX2aOMsB33xR8tRlU19uVt8mvZ6BDjdVVgDtqrn8Lqn6gUQpy1 NG08GvN7a5/qs+7WwSW5ymxuTTrJaQosdMJl15Up1WgH3vkNxoVivP+avMn/pepalU2c2dah9hT ME4GH X-Gm-Gg: AR+sD12rhal/wJhf5wpMIU7brNzVSObgHXOvlDIj0YRojYLm+HdEj/iRdYDsp2cWHlc PNxgryDr7rhFCeu4+0FF2aOA+gAattFXZl0KLcSjak2RarEHJHiZCs0P4MGKpXmxsiqXwv+1OAB UjxUeIyfySc2x+TloNt5/rCwMdFpeFM5cH79oEIAhWYsA0wy7sZB/2YdbJ2OFwWB2JRiTvpoNw1 AjliZ7rl9JK4Bvdx2iyrtdP2UUZYP/5QR6O1txM4xwzZXbKSJTkonbrmy6rqzcX+cyvAZ8p5OKW yiQj1fnyNtNyDN0nxe9eK09prMxo9vVMkGFk2jmEN8aCzQsHB1vkv9GzGWlMEcEksLdA/i/iq7W Xl+Q3y4R2twzCM/HbweBGM1b1IPK4WyCFWsaVG1ORww1NF8FHrAHrKUIxh6e1WPDtEPk7cgyNsn +Yibpfsg+y+FNQTx5fdaEZ35rorUEbt0ktVRaLj6sf+CRO5cPXGmSRVBafJQ== X-Received: by 2002:a17:907:948b:b0:c16:6a42:c7d6 with SMTP id a640c23a62f3a-c246a346d3amr3081412166b.9.1787578658405; Mon, 24 Aug 2026 06:37:38 -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 , Eric Blake , Markus Armbruster , qemu-stable@nongnu.org Subject: [PATCH v4 5/5] qcow2: repair a dirty image when it becomes writable Date: Mon, 24 Aug 2026 15:37:29 +0200 Message-ID: <20260824133729.1141990-6-den@openvz.org> X-Mailer: git-send-email 2.53.0 In-Reply-To: <20260824133729.1141990-1-den@openvz.org> References: <20260824133729.1141990-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::636; envelope-from=den@openvz.org; helo=mail-ej1-x636.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: 1787578740227158500 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 takes the driver away from the node instead, which is what stops writes from aliasing live clusters. qcow2_signal_corruption() does that as well, but it also sends BLOCK_IMAGE_CORRUPTED and sets the corrupt bit, which qcow2_do_open() honours by refusing every later read-write open. The image is dirty and unrepaired, not corrupt, and qemu-img check -r still fixes it, so neither belongs here. Return the error and skip the bitmaps. Signed-off-by: Denis V. Lunev Reviewed-by: Andrey Drobyshev CC: Kevin Wolf CC: Hanna Reitz CC: Eric Blake CC: Markus Armbruster CC: Andrey Drobyshev Cc: qemu-stable@nongnu.org --- block/qcow2.c | 21 +++++++ qapi/block-core.json | 19 ++++++ 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, 260 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..940249a5e5 100644 --- a/qapi/block-core.json +++ b/qapi/block-core.json @@ -1852,6 +1852,9 @@ # r/w -> r/o, if needed). The new backing file string is written into # the image file metadata, and the QEMU internal strings are updated. # +# A dirty qcow2 image is repaired during that reopen, which blocks +# other requests and can fail the command. +# # @image-node-name: The name of the block driver state node of the # image to modify. The "device" argument is used to verify # "image-node-name" is in the chain described by "device". @@ -1891,6 +1894,9 @@ # 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. A dirty qcow2 base is repaired +# first, which blocks other requests and can fail the command. +# # @job-id: identifier for the newly-created block job. If omitted, # the device name will be used. (Since 2.7) # @@ -2902,6 +2908,9 @@ # On successful completion the image file is updated to drop the # backing file and the `BLOCK_JOB_COMPLETED` event is emitted. # +# The top image is opened read-write. A dirty qcow2 image is repaired +# first, which blocks other requests and can fail the command. +# # In case @device is a filter node, `block-stream` modifies the first # non-filter overlay node below it to point to the new backing node # instead of modifying @device itself. @@ -4989,6 +4998,16 @@ # 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 +# blocks other requests and can fail the command. +# # 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