From nobody Tue Jun 9 01:47:29 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=dupond.be ARC-Seal: i=1; a=rsa-sha256; t=1780916712; cv=none; d=zohomail.com; s=zohoarc; b=MHNCUirYVlYCNe6HDrHRUPTicZgTCwUM+vRkLeTwSjC0kPGg/jDHbBjo4zNb+mHCvky+2gM7Czw4whYqJOpfXW5c0H01+co53LuoK1jq32tellLOiVa2mxOdEE+K34t9LghNSLfKpFUDkFHy3yAMyI5vQjB01gHbWOxICgQkjfk= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1780916712; 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=ImXwFcjGNeGxBq1Hn6LVUNqP61YvpAoEcEaKTkM3cyg=; b=GRV8S8Q1WRdpiDB+gKwTlu/5O1lDdoS//69PGc8y4lF8T62R+BaPr0fsLuaDLqfegSx2IWwgLEnkehaEpvBJEMI97ZiOYXljQMdn2FeJ7dcZRuWJIxxuXYBQcz9JyUoMX9HP6gssdfWiZJySMGcpZkd+IjBVmHOgLwWQGw6mbxk= 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 178091671175649.00192427284935; Mon, 8 Jun 2026 04:05:11 -0700 (PDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists1p.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1wWXmb-0000oW-DH; Mon, 08 Jun 2026 07:04:41 -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 1wWXmX-0000mZ-M1; Mon, 08 Jun 2026 07:04:37 -0400 Received: from apollo.dupie.be ([2001:bc8:3f2a:101::1]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1wWXmU-0006uR-AI; Mon, 08 Jun 2026 07:04:37 -0400 Received: from lt-jeanlouis (unknown [IPv6:2a02:a03f:fafb:301:46c3:62e6:fe62:23a1]) by apollo.dupie.be (Postfix) with ESMTPSA id F23A41520F54; Mon, 08 Jun 2026 13:04:28 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=dupond.be; s=dkim; t=1780916669; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=ImXwFcjGNeGxBq1Hn6LVUNqP61YvpAoEcEaKTkM3cyg=; b=N7JZnNf6jon7lEJytduDKbSWY7xDvUqPFwz7bpKYFO67rQ8E6kJfE+xuw/hHS95cMa2KXX MXcfJNpiE+6HeEGY79vIGrjUD+y6siNIBgNZPziheDMx8bABxEbpqV0nsHrKWlYgCbWBfM hdTdn2ySxraaSCqQvcJarbuDvdaDvyQaxhfgRE0DklzFQGqU2/ktomFKsy55acQbx+qTV/ Lzwm8RqFwhLQmI66lLdmB+ZEcIMLHojvhhy+hcIh6WAdmRS/oGweRcCKTqcN3g3wI3gCTg +G/HM/FiqvRKgYsqNBiwYxflpsDhLIAG9ZyI9UIjqNLfW/bH/FzmDCqJxDmc1A== From: Jean-Louis Dupond To: qemu-devel@nongnu.org Cc: Kevin Wolf , Vladimir Sementsov-Ogievskiy , Hanna Reitz , qemu-block@nongnu.org, Fiona Ebner , John Snow , Jean-Louis Dupond Subject: [PATCH 1/3] block: add bdrv_open_backing_chain_until Date: Mon, 8 Jun 2026 13:03:44 +0200 Message-ID: <20260608110346.3231998-2-jean-louis@dupond.be> X-Mailer: git-send-email 2.54.0 In-Reply-To: <20260608110346.3231998-1-jean-louis@dupond.be> References: <20260608110346.3231998-1-jean-louis@dupond.be> 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=2001:bc8:3f2a:101::1; envelope-from=jean-louis@dupond.be; helo=apollo.dupie.be 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, 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 @dupond.be) X-ZM-MESSAGEID: 1780916718769154100 Content-Type: text/plain; charset="utf-8" Add a new function called bdrv_open_backing_chain_until. This is used to open a block up until a specified backing chain. Modified bdrv_open_inherit to accept a new arg 'open_backing' to force to to no open the next backing. Adjusting all references to keep the existing behaviour. Signed-off-by: Jean-Louis Dupond --- block.c | 65 +++++++++++++++++++++++++----- block/mirror.c | 2 +- include/block/block-global-state.h | 7 +++- 3 files changed, 62 insertions(+), 12 deletions(-) diff --git a/block.c b/block.c index f0a6042e61..a4f4ce1c21 100644 --- a/block.c +++ b/block.c @@ -83,6 +83,7 @@ static QLIST_HEAD(, BlockDriver) bdrv_drivers =3D static BlockDriverState *bdrv_open_inherit(const char *filename, const char *reference, QDict *options, int flags, + bool open_backing, BlockDriverState *parent, const BdrvChildClass *child_cla= ss, BdrvChildRole child_role, @@ -3617,6 +3618,7 @@ out: * TODO Can this be unified with bdrv_open_image()? */ int bdrv_open_backing_file(BlockDriverState *bs, QDict *parent_options, + bool open_backing, const char *bdref_key, Error **errp) { ERRP_GUARD(); @@ -3695,9 +3697,9 @@ int bdrv_open_backing_file(BlockDriverState *bs, QDic= t *parent_options, qdict_put_str(options, "driver", bs->backing_format); } =20 - backing_hd =3D bdrv_open_inherit(backing_filename, reference, options,= 0, bs, - &child_of_bds, bdrv_backing_role(bs), t= rue, - errp); + backing_hd =3D bdrv_open_inherit(backing_filename, reference, options,= 0, + open_backing, bs, &child_of_bds, + bdrv_backing_role(bs), true, errp); if (!backing_hd) { bs->open_flags |=3D BDRV_O_NO_BACKING; error_prepend(errp, "Could not open backing file: "); @@ -3733,6 +3735,48 @@ free_exit: return ret; } =20 +int +bdrv_open_backing_chain_until(BlockDriverState *top_bs, + const char *base_filename, + Error **errp) +{ + BlockDriverState *base_bs =3D NULL; + BlockDriverState *curr =3D top_bs; + int ret; + + GLOBAL_STATE_CODE(); + + if (!base_filename) { + return 0; + } + + while (!(base_bs =3D bdrv_find_backing_image(top_bs, base_filename))) { + QDict *options; + + options =3D qdict_clone_shallow(curr->options); + ret =3D bdrv_open_backing_file(curr, options, false, "backing", er= rp); + qobject_unref(options); + if (ret < 0) { + return ret; + } + + bdrv_graph_rdlock_main_loop(); + if (!curr->backing) { + bdrv_graph_rdunlock_main_loop(); + error_setg(errp, + "Did not find '%s' in the backing chain of '%s'", + base_filename, top_bs->filename); + return -ENOENT; + } + + /* Switch to the next layer */ + curr =3D curr->backing->bs; + bdrv_graph_rdunlock_main_loop(); + } + + return 0; +} + static BlockDriverState * bdrv_open_child_bs(const char *filename, QDict *options, const char *bdref= _key, BlockDriverState *parent, const BdrvChildClass *child_c= lass, @@ -3767,7 +3811,7 @@ bdrv_open_child_bs(const char *filename, QDict *optio= ns, const char *bdref_key, goto done; } =20 - bs =3D bdrv_open_inherit(filename, reference, image_options, 0, + bs =3D bdrv_open_inherit(filename, reference, image_options, 0, true, parent, child_class, child_role, parse_filename, errp); if (!bs) { @@ -3897,8 +3941,8 @@ BlockDriverState *bdrv_open_blockdev_ref(BlockdevRef = *ref, Error **errp) =20 } =20 - bs =3D bdrv_open_inherit(NULL, reference, qdict, 0, NULL, NULL, 0, fal= se, - errp); + bs =3D bdrv_open_inherit(NULL, reference, qdict, 0, true, NULL, NULL, = 0, + false, errp); obj =3D NULL; qobject_unref(obj); visit_free(v); @@ -3986,7 +4030,7 @@ out: */ static BlockDriverState * no_coroutine_fn bdrv_open_inherit(const char *filename, const char *reference, QDict *opti= ons, - int flags, BlockDriverState *parent, + int flags, bool open_backing, BlockDriverState *parent, const BdrvChildClass *child_class, BdrvChildRole child_r= ole, bool parse_filename, Error **errp) { @@ -4199,8 +4243,9 @@ bdrv_open_inherit(const char *filename, const char *r= eference, QDict *options, } =20 /* If there is a backing file, use it */ - if ((flags & BDRV_O_NO_BACKING) =3D=3D 0) { - ret =3D bdrv_open_backing_file(bs, options, "backing", &local_err); + if ((flags & BDRV_O_NO_BACKING) =3D=3D 0 && open_backing) { + ret =3D bdrv_open_backing_file(bs, options, open_backing, "backing= ", + &local_err); if (ret < 0) { goto close_and_fail; } @@ -4283,7 +4328,7 @@ BlockDriverState *bdrv_open(const char *filename, con= st char *reference, { GLOBAL_STATE_CODE(); =20 - return bdrv_open_inherit(filename, reference, options, flags, NULL, + return bdrv_open_inherit(filename, reference, options, flags, true, NU= LL, NULL, 0, true, errp); } =20 diff --git a/block/mirror.c b/block/mirror.c index 089856f4a8..a4dde5d36d 100644 --- a/block/mirror.c +++ b/block/mirror.c @@ -785,7 +785,7 @@ static int mirror_exit_common(Job *job) bdrv_graph_rdlock_main_loop(); assert(!bdrv_backing_chain_next(target_bs)); ret =3D bdrv_open_backing_file(bdrv_skip_filters(target_bs), NULL, - "backing", &local_err); + true, "backing", &local_err); bdrv_graph_rdunlock_main_loop(); if (ret < 0) { error_report_err(local_err); diff --git a/include/block/block-global-state.h b/include/block/block-globa= l-state.h index ed89999f0f..c7becd9d73 100644 --- a/include/block/block-global-state.h +++ b/include/block/block-global-state.h @@ -110,7 +110,12 @@ bdrv_set_backing_hd(BlockDriverState *bs, BlockDriverS= tate *backing_hd, Error **errp); =20 int bdrv_open_backing_file(BlockDriverState *bs, QDict *parent_options, - const char *bdref_key, Error **errp); + bool open_backing, const char *bdref_key, + Error **errp); + +int bdrv_open_backing_chain_until(BlockDriverState *top_bs, + const char *base_filename, + Error **errp); =20 BlockDriverState * no_coroutine_fn bdrv_open(const char *filename, const char *reference, QDict *options, --=20 2.54.0 From nobody Tue Jun 9 01:47:29 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=dupond.be ARC-Seal: i=1; a=rsa-sha256; t=1780916735; cv=none; d=zohomail.com; s=zohoarc; b=Wz53hHtMTxC6QLIcO1BMjLPf+RUOx+pC0rPngKrb/zzF6VkjgrOV0J1jpZu9pv7S1fw9uBpRpLMPoHYAket8ZByHEAhKW+nGarXXKzcwMgCOt0+Tm06ugvFhVK0pOwNol9ffgOXDC0DXln2dnvJqeKnP+Qj1XRPNTrvPYL7ON0g= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1780916735; 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=1gSIJbMghTr9Bgu4o/y7W2q9KZXCsTPkVKrbUvq4OkU=; b=NOyEeweyYUYeTGsSlLwJvCaRl9cflc57zxBQwcXlv7Kar4JrCJdOYj5CyxbiTflZxtNmKTSAGitCQ5HMp2BIlqEGtA+h51F+RTkomhWR30+/jOezzo2MQuL69tE2tMEjFJYg6fJajBApierdXJVq5MpSXxYAmvMm9YNBxDBM6vM= 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 1780916734931926.2946281553756; Mon, 8 Jun 2026 04:05:34 -0700 (PDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists1p.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1wWXme-0000pB-D3; Mon, 08 Jun 2026 07:04: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 1wWXmY-0000n7-GW; Mon, 08 Jun 2026 07:04:39 -0400 Received: from apollo.dupie.be ([2001:bc8:3f2a:101::1]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1wWXmU-0006uw-VK; Mon, 08 Jun 2026 07:04:38 -0400 Received: from lt-jeanlouis (unknown [IPv6:2a02:a03f:fafb:301:46c3:62e6:fe62:23a1]) by apollo.dupie.be (Postfix) with ESMTPSA id 5A6F91520F65; Mon, 08 Jun 2026 13:04:29 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=dupond.be; s=dkim; t=1780916669; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=1gSIJbMghTr9Bgu4o/y7W2q9KZXCsTPkVKrbUvq4OkU=; b=Y9zuXxepJbIhpR5CX7/c+1x8xKToB+btjMw8P4AS3tB7kgJUd7xZRVuET89IlPCNxzKMLw zurp8V91S945EvHTmtQaMM05YxGXILVOrqGhdr2u/L82zgCWDdE+kfBw9rf5FQbugoKNBy waWL9VWwANQOrdQc+RboJAGc8K/zrw6n8x8JSGZr9updvfVjXtNbfZLyPWq1OTk/szGjv1 CUH6MM/39b/3gWFb5lJQQ67DbLnzbvRmTieZpzuEERK9aAvqOMNutGkaP+Zdx0bk4ydEL4 a4IT+ZdD+Xun1MQA/ND+3dQe3Q2L+GnS8ak3nCkUM/lIpUWWerXviIqumM55Pg== From: Jean-Louis Dupond To: qemu-devel@nongnu.org Cc: Kevin Wolf , Vladimir Sementsov-Ogievskiy , Hanna Reitz , qemu-block@nongnu.org, Fiona Ebner , John Snow , Jean-Louis Dupond Subject: [PATCH 2/3] block: support measure for commit Date: Mon, 8 Jun 2026 13:03:45 +0200 Message-ID: <20260608110346.3231998-3-jean-louis@dupond.be> X-Mailer: git-send-email 2.54.0 In-Reply-To: <20260608110346.3231998-1-jean-louis@dupond.be> References: <20260608110346.3231998-1-jean-louis@dupond.be> 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=2001:bc8:3f2a:101::1; envelope-from=jean-louis@dupond.be; helo=apollo.dupie.be 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, 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 @dupond.be) X-ZM-MESSAGEID: 1780916738165158500 Content-Type: text/plain; charset="utf-8" Add a new option to qemu-img measure to specify the base node you want to merge the image into. This will open all the images between top and base, and calculate the size required for merging this layer into the base image. Also modify the calculation so it takes into account the discard-no-unref setting of the base image. If discard-no-unref is enabled on the base, discarded blocks in the layers above the base will not free clusters in the base image, but will only mark them ZERO. [1]: https://gitlab.com/qemu-project/qemu/-/issues/2369 Signed-off-by: Jean-Louis Dupond --- block/qcow2.c | 90 ++++++++++++++++++++++++++++---- qemu-img-cmds.hx | 4 +- qemu-img.c | 37 +++++++++++-- tests/qemu-iotests/178.out.qcow2 | 6 +-- tests/qemu-iotests/178.out.raw | 6 +-- 5 files changed, 121 insertions(+), 22 deletions(-) diff --git a/block/qcow2.c b/block/qcow2.c index 81fd299b4c..3e3578e831 100644 --- a/block/qcow2.c +++ b/block/qcow2.c @@ -5231,6 +5231,8 @@ static BlockMeasureInfo *qcow2_measure(QemuOpts *opts= , BlockDriverState *in_bs, bool has_backing_file; bool has_luks; bool extended_l2; + BlockDriverState *base_bs =3D NULL; + bool base_discard_no_unref =3D false; size_t l2e_size; =20 /* Parse image creation options */ @@ -5311,6 +5313,21 @@ static BlockMeasureInfo *qcow2_measure(QemuOpts *opt= s, BlockDriverState *in_bs, goto err; } =20 + base_bs =3D bdrv_find_base(in_bs); + /* + * bdrv_find_base() returns in_bs itself when the image has no bac= king + * chain. Then this can stay NULL. + */ + if (base_bs =3D=3D in_bs) { + base_bs =3D NULL; + } + if (base_bs && base_bs->drv && + !strcmp(base_bs->drv->format_name, "qcow2")) { + BDRVQcow2State *base_s =3D base_bs->opaque; + + base_discard_no_unref =3D base_s->discard_no_unref; + } + virtual_size =3D ROUND_UP(ssize, cluster_size); =20 if (has_backing_file) { @@ -5326,8 +5343,10 @@ static BlockMeasureInfo *qcow2_measure(QemuOpts *opt= s, BlockDriverState *in_bs, =20 for (offset =3D 0; offset < ssize; offset +=3D pnum) { int ret; + int retp =3D 0; + bool count =3D false; =20 - ret =3D bdrv_block_status_above(in_bs, NULL, offset, + ret =3D bdrv_block_status_above(in_bs, base_bs, offset, ssize - offset, &pnum, NULL, NULL); if (ret < 0) { @@ -5336,15 +5355,68 @@ static BlockMeasureInfo *qcow2_measure(QemuOpts *op= ts, BlockDriverState *in_bs, goto err; } =20 - if (ret & BDRV_BLOCK_ZERO) { + if (ret & BDRV_BLOCK_ZERO && !base_bs && + !base_discard_no_unref) { /* Skip zero regions (safe with no backing file) */ - } else if ((ret & (BDRV_BLOCK_DATA | BDRV_BLOCK_ALLOCATED)= ) =3D=3D - (BDRV_BLOCK_DATA | BDRV_BLOCK_ALLOCATED)) { - /* Extend pnum to end of cluster for next iteration */ - pnum =3D ROUND_UP(offset + pnum, cluster_size) - offse= t; - - /* Count clusters we've seen */ - required +=3D offset % cluster_size + pnum; + } else { + /* + * If there is a base image in the chain, query its + * allocation status for this region so we can decide + * whether the cluster survives a commit into the base. + */ + if (base_bs) { + int64_t pnum_base =3D 0; + retp =3D bdrv_block_status_above(base_bs, NULL, of= fset, + ssize - offset, &pnum_base, NU= LL, + NULL); + if (retp < 0) { + error_setg_errno(&local_err, -retp, + "Unable to get block status of the bas= e"); + goto err; + } + /* + * If the base contiguous block is smaller, + * use that pnum, so the next iteration starts with + * the smallest offset. + */ + if (pnum_base > 0 && pnum_base < pnum) { + pnum =3D pnum_base; + } + } + + if ((ret & (BDRV_BLOCK_DATA | BDRV_BLOCK_ALLOCATED)) = =3D=3D + (BDRV_BLOCK_DATA | BDRV_BLOCK_ALLOCATED)) { + /* The overlay has its own data here; it is writte= n. */ + count =3D true; + } else if (base_discard_no_unref) { + /* + * With discard-no-unref enabled on the base, clus= ters + * allocated in the base keep their reference when= the + * overlay is committed (discarded clusters are on= ly + * marked zero), so count any cluster that has a v= alid + * offset in the base. + */ + count =3D retp & BDRV_BLOCK_OFFSET_VALID; + } else { + /* + * Without discard-no-unref, committing the overlay + * frees the base clusters that the overlay discar= ds + * (the overlay marks them zero). Every other clus= ter + * that is allocated in the base is retained, so c= ount + * it unless the overlay zeroes it out. + */ + count =3D (retp & BDRV_BLOCK_ALLOCATED) && + !((ret & BDRV_BLOCK_ALLOCATED) && + (ret & BDRV_BLOCK_ZERO)); + } + + if (count) { + /* Extend pnum to end of cluster for next iteratio= n */ + pnum =3D ROUND_UP(offset + pnum, cluster_size) - o= ffset; + + /* Count clusters we've seen */ + required +=3D offset % cluster_size + pnum; + } } } } diff --git a/qemu-img-cmds.hx b/qemu-img-cmds.hx index 6bc8265cfb..f3f5c63773 100644 --- a/qemu-img-cmds.hx +++ b/qemu-img-cmds.hx @@ -78,9 +78,9 @@ SRST ERST =20 DEF("measure", img_measure, -"measure [--output=3Dofmt] [-O output_fmt] [-o options] [--size N | [--obj= ect objectdef] [--image-opts] [-f fmt] [-l snapshot_param] filename]") +"measure [--output=3Dofmt] [-O output_fmt] [-o options] [--size N | [--obj= ect objectdef] [--image-opts] [-f fmt] [-l snapshot_param] [-b base] filena= me]") SRST -.. option:: measure [--output=3DOFMT] [-O OUTPUT_FMT] [-o OPTIONS] [--size= N | [--object OBJECTDEF] [--image-opts] [-f FMT] [-l SNAPSHOT_PARAM] FILEN= AME] +.. option:: measure [--output=3DOFMT] [-O OUTPUT_FMT] [-o OPTIONS] [--size= N | [--object OBJECTDEF] [--image-opts] [-f FMT] [-l SNAPSHOT_PARAM] [-b B= ASE] FILENAME] ERST =20 DEF("snapshot", img_snapshot, diff --git a/qemu-img.c b/qemu-img.c index c42dd4e995..0aa53d7ef8 100644 --- a/qemu-img.c +++ b/qemu-img.c @@ -5653,6 +5653,7 @@ static int img_measure(const img_cmd_t *ccmd, int arg= c, char **argv) BlockBackend *in_blk =3D NULL; BlockDriver *drv; const char *filename =3D NULL; + const char *base_filename =3D NULL; const char *fmt =3D NULL; const char *out_fmt =3D "raw"; char *options =3D NULL; @@ -5676,6 +5677,7 @@ static int img_measure(const img_cmd_t *ccmd, int arg= c, char **argv) {"image-opts", no_argument, 0, OPTION_IMAGE_OPTS}, {"source-image-opts", no_argument, 0, OPTION_IMAGE_OPTS}, /* img_c= onvert */ {"snapshot", required_argument, 0, 'l'}, + {"base", required_argument, 0, 'b'}, {"target-format", required_argument, 0, 'O'}, {"target-format-options", required_argument, 0, 'o'}, /* img_conve= rt */ {"options", required_argument, 0, 'o'}, @@ -5686,11 +5688,11 @@ static int img_measure(const img_cmd_t *ccmd, int a= rgc, char **argv) {0, 0, 0, 0} }; =20 - while ((c =3D getopt_long(argc, argv, "hf:l:O:o:Us:", + while ((c =3D getopt_long(argc, argv, "hf:l:b:O:o:Us:", long_options, NULL)) !=3D -1) { switch (c) { case 'h': - cmd_help(ccmd, "[-f FMT|--image-opts] [-l SNAPSHOT]\n" + cmd_help(ccmd, "[-f FMT|--image-opts] [-l SNAPSHOT] [-b BASE]\= n" " [-O TARGET_FMT] [-o TARGET_FMT_OPTS] [--output human|json]\n" " [--object OBJDEF] (--size SIZE | FILE)\n" , @@ -5701,6 +5703,8 @@ static int img_measure(const img_cmd_t *ccmd, int arg= c, char **argv) " instead of a file name (incompatible with --format)\n" " -l, --snapshot SNAPSHOT\n" " use this snapshot in FILE as source\n" +" -b, --base BASE\n" +" open FILE backing chain up to BASE (inclusive)\n" " -O, --target-format TARGET_FMT\n" " desired target/output image format (default: raw)\n" " -o TARGET_FMT_OPTS\n" @@ -5738,6 +5742,9 @@ static int img_measure(const img_cmd_t *ccmd, int arg= c, char **argv) snapshot_name =3D optarg; } break; + case 'b': + base_filename =3D optarg; + break; case 'O': out_fmt =3D optarg; break; @@ -5773,8 +5780,10 @@ static int img_measure(const img_cmd_t *ccmd, int ar= gc, char **argv) filename =3D argv[optind]; } =20 - if (!filename && (image_opts || fmt || snapshot_name || sn_opts)) { - error_report("--image-opts, -f, and -l require a filename argument= ."); + if (!filename && (image_opts || fmt || snapshot_name || sn_opts || + base_filename)) { + error_report("--image-opts, -f, -l, and -b require a filename " + "argument."); goto out; } if (filename && img_size !=3D -1) { @@ -5787,12 +5796,30 @@ static int img_measure(const img_cmd_t *ccmd, int a= rgc, char **argv) } =20 if (filename) { - in_blk =3D img_open(image_opts, filename, fmt, 0, + int src_flags =3D 0; + + /* + * When measuring with --base, avoid opening the full backing chai= n. + * We selectively open only up to the requested base afterwards. + */ + if (base_filename) { + src_flags |=3D BDRV_O_NO_BACKING; + } + + in_blk =3D img_open(image_opts, filename, fmt, src_flags, false, false, force_share); if (!in_blk) { goto out; } =20 + if (base_filename) { + if (bdrv_open_backing_chain_until(blk_bs(in_blk), base_filenam= e, + &local_err) < 0) { + error_report_err(local_err); + goto out; + } + } + if (sn_opts) { bdrv_snapshot_load_tmp(blk_bs(in_blk), qemu_opt_get(sn_opts, SNAPSHOT_OPT_ID), diff --git a/tests/qemu-iotests/178.out.qcow2 b/tests/qemu-iotests/178.out.= qcow2 index 61506b519f..0984a74a50 100644 --- a/tests/qemu-iotests/178.out.qcow2 +++ b/tests/qemu-iotests/178.out.qcow2 @@ -6,9 +6,9 @@ qemu-img: Either --size N or one filename must be specified. qemu-img: --size N cannot be used together with a filename. qemu-img: At most one filename argument is allowed. qemu-img: Either --size N or one filename must be specified. -qemu-img: --image-opts, -f, and -l require a filename argument. -qemu-img: --image-opts, -f, and -l require a filename argument. -qemu-img: --image-opts, -f, and -l require a filename argument. +qemu-img: --image-opts, -f, -l, and -b require a filename argument. +qemu-img: --image-opts, -f, -l, and -b require a filename argument. +qemu-img: --image-opts, -f, -l, and -b require a filename argument. qemu-img: Invalid option list: , qemu-img: Invalid parameter 'snapshot.foo' qemu-img: Failed in parsing snapshot param 'snapshot.foo=3Dbar' diff --git a/tests/qemu-iotests/178.out.raw b/tests/qemu-iotests/178.out.raw index 6d994a433a..81249e718b 100644 --- a/tests/qemu-iotests/178.out.raw +++ b/tests/qemu-iotests/178.out.raw @@ -6,9 +6,9 @@ qemu-img: Either --size N or one filename must be specified. qemu-img: --size N cannot be used together with a filename. qemu-img: At most one filename argument is allowed. qemu-img: Either --size N or one filename must be specified. -qemu-img: --image-opts, -f, and -l require a filename argument. -qemu-img: --image-opts, -f, and -l require a filename argument. -qemu-img: --image-opts, -f, and -l require a filename argument. +qemu-img: --image-opts, -f, -l, and -b require a filename argument. +qemu-img: --image-opts, -f, -l, and -b require a filename argument. +qemu-img: --image-opts, -f, -l, and -b require a filename argument. qemu-img: Invalid option list: , qemu-img: Invalid parameter 'snapshot.foo' qemu-img: Failed in parsing snapshot param 'snapshot.foo=3Dbar' --=20 2.54.0 From nobody Tue Jun 9 01:47:29 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=dupond.be ARC-Seal: i=1; a=rsa-sha256; t=1780916712; cv=none; d=zohomail.com; s=zohoarc; b=UZ0OQ2BzcvBFPfNWo0fdEXY1otcWAXd+amC1y1iVEa48sX1Byt3V8+sjuN50Q3UxD05t0376/UuXVfoOGye7a7/lHmDQsYDM8ovNMwFVtFSBu9L1/ss66tmALi5dGGM1osXKx94vuDPp1o8tnFp7N8uGdeSDyBNfRLeXDOte9j4= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1780916712; 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=9JvfuXR7szPhJ39nFbfdTuSR/jfEBTyesjCCNDkiDaY=; b=lSDU83Bt/ZzaPJaL7s63jqrft5pD/itwJOkkeGF3o0L2RZwCQRS79PmOmCgocy4vZp4OEyw7jRe0Rn6I/0bDcKWlUPsJMOsqF2HEoUEs4cRqpWtY4wT9/3TdPCCBdeOXb4iyATaNVXkdcun2cjfCCqzzGdfIzjeoU5IhkEa6lP8= 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 1780916712231850.6104343079876; Mon, 8 Jun 2026 04:05:12 -0700 (PDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists1p.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1wWXmd-0000p5-Sp; Mon, 08 Jun 2026 07:04:43 -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 1wWXmW-0000mX-QF; Mon, 08 Jun 2026 07:04:37 -0400 Received: from apollo.dupie.be ([2001:bc8:3f2a:101::1]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1wWXmU-0006v3-9p; Mon, 08 Jun 2026 07:04:36 -0400 Received: from lt-jeanlouis (unknown [IPv6:2a02:a03f:fafb:301:46c3:62e6:fe62:23a1]) by apollo.dupie.be (Postfix) with ESMTPSA id DFB4D1520F7B; Mon, 08 Jun 2026 13:04:29 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=dupond.be; s=dkim; t=1780916670; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=9JvfuXR7szPhJ39nFbfdTuSR/jfEBTyesjCCNDkiDaY=; b=g1fEhMggR5sdeW3S44CPDNWI812I1Wwek1rJzUnJNpP4UwudhykMGmTqZbeEFXm7AuX+AS B0ob9gAYUQXkYvzjeQazR+a0j0w85GYgnWIiW5qLF3vD+sp2i1ykjExYsMdfj4C+BD/S4Z nxm1eS00bmxuhuFMi6WLAd9nnVnP7hz6idCM6fvMlHN6rJltoIXERQWUIyFCWXKJwsxY+o 328ev11gYqeNeo08K1wygwOsJpmmg92+VGCOx7XsV3SeHWmH0QOrV8X9wl/gwTl3dWvNj5 FBUoShclpigmFYJubqcpH/FoVZqYZbiNWEj1JikpD0qw20UiWOCiE7SUYkIARw== From: Jean-Louis Dupond To: qemu-devel@nongnu.org Cc: Kevin Wolf , Vladimir Sementsov-Ogievskiy , Hanna Reitz , qemu-block@nongnu.org, Fiona Ebner , John Snow , Jean-Louis Dupond Subject: [PATCH 3/3] iotests/290: add test case for qemu-img measure Date: Mon, 8 Jun 2026 13:03:46 +0200 Message-ID: <20260608110346.3231998-4-jean-louis@dupond.be> X-Mailer: git-send-email 2.54.0 In-Reply-To: <20260608110346.3231998-1-jean-louis@dupond.be> References: <20260608110346.3231998-1-jean-louis@dupond.be> 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=2001:bc8:3f2a:101::1; envelope-from=jean-louis@dupond.be; helo=apollo.dupie.be 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, 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 @dupond.be) X-ZM-MESSAGEID: 1780916718339158500 Content-Type: text/plain; charset="utf-8" We create an image, write and discard some data in it, and then create a snapshot. In the snapshot we write and discard again some data. Then we measure the images with the base specified to calculate the merged image size. Finally we commit the image and check it's size. This scenario is executed for discard-no-unref enabled and disabled. Signed-off-by: Jean-Louis Dupond --- tests/qemu-iotests/290 | 45 ++++++++++++++++++++++++++++ tests/qemu-iotests/290.out | 61 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 106 insertions(+) diff --git a/tests/qemu-iotests/290 b/tests/qemu-iotests/290 index 776b59de1b..4702461aa8 100755 --- a/tests/qemu-iotests/290 +++ b/tests/qemu-iotests/290 @@ -92,6 +92,51 @@ for qcow2_compat in 0.10 1.1; do $QEMU_IMG map "$TEST_IMG" | _filter_testdir done =20 +echo +echo "### Test qemu-img measure for commit differences with 'discard-no-un= ref' option enabled" +echo + +for DISCARD_NO_UNREF in true false; do + echo "# Create a base image and fill it with data" + TEST_IMG=3D"$TEST_IMG.base" _make_test_img 128M + + $QEMU_IO -c 'write 0 8M' "$TEST_IMG.base" | _filter_qemu_io + $QEMU_IO -c 'write 10M 8M' "$TEST_IMG.base" | _filter_qemu_io + $QEMU_IO -c 'write 24M 32M' "$TEST_IMG.base" | _filter_qemu_io + $QEMU_IO -c 'write 56M 20M' "$TEST_IMG.base" | _filter_qemu_io + $QEMU_IO -c "reopen -o discard=3Dunmap,discard-no-unref=3D$DISCARD_NO_= UNREF" \ + -c 'discard 32M 10M' "$TEST_IMG.base" | _filter_qemu_io + + echo "# Create a top image and do some writes and discards" + TEST_IMG=3D"$TEST_IMG.top" _make_test_img -b "$TEST_IMG.base" -F $IMGF= MT 128M + + $QEMU_IO -c "reopen -o discard=3Dunmap,discard-no-unref=3D$DISCARD_NO_= UNREF" -c 'write 16M 8M' \ + -c 'discard 60M 20M' -c 'write 84M 10M' "$TEST_IMG.top" | _filter_= qemu_io + + FILE_JSON=3D"json:{ + 'file': { + 'driver': 'file', + 'filename': '$TEST_IMG.top' + }, + 'driver': 'qcow2', + 'discard': 'unmap', + 'discard-no-unref': '$DISCARD_NO_UNREF', + 'backing': { + 'driver': 'qcow2', + 'discard-no-unref': '$DISCARD_NO_UNREF', + 'file': { + 'driver': 'file', + 'filename': '$TEST_IMG.base' + }, + 'backing': null + }}" + echo "# Measure size with discard-no-unref=3D$DISCARD_NO_UNREF" + $QEMU_IMG measure --output=3Djson -O qcow2 "${FILE_JSON}" + echo "# Merging the top image into the base image" + $QEMU_IMG commit -t none -f qcow2 "${FILE_JSON}" + stat -c"base disk image file size in bytes: %s" "$TEST_IMG.base" +done + # success, all done echo "*** done" rm -f $seq.full diff --git a/tests/qemu-iotests/290.out b/tests/qemu-iotests/290.out index 22b476594f..6dafbfc2b9 100644 --- a/tests/qemu-iotests/290.out +++ b/tests/qemu-iotests/290.out @@ -58,4 +58,65 @@ read 131072/131072 bytes at offset 0 128 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) # Output of qemu-img map Offset Length Mapped to File + +### Test qemu-img measure for commit differences with 'discard-no-unref' o= ption enabled + +# Create a base image and fill it with data +Formatting 'TEST_DIR/t.IMGFMT.base', fmt=3DIMGFMT size=3D134217728 +wrote 8388608/8388608 bytes at offset 0 +8 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +wrote 8388608/8388608 bytes at offset 10485760 +8 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +wrote 33554432/33554432 bytes at offset 25165824 +32 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +wrote 20971520/20971520 bytes at offset 58720256 +20 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +discard 10485760/10485760 bytes at offset 33554432 +10 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +# Create a top image and do some writes and discards +Formatting 'TEST_DIR/t.IMGFMT.top', fmt=3DIMGFMT size=3D134217728 backing_= file=3DTEST_DIR/t.IMGFMT.base backing_fmt=3DIMGFMT +wrote 8388608/8388608 bytes at offset 16777216 +8 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +discard 20971520/20971520 bytes at offset 62914560 +20 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +wrote 10485760/10485760 bytes at offset 88080384 +10 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +# Measure size with discard-no-unref=3Dtrue +{ + "bitmaps": 0, + "required": 88408064, + "fully-allocated": 134545408 +} +# Merging the top image into the base image +Image committed. +base disk image file size in bytes: 88408064 +# Create a base image and fill it with data +Formatting 'TEST_DIR/t.IMGFMT.base', fmt=3DIMGFMT size=3D134217728 +wrote 8388608/8388608 bytes at offset 0 +8 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +wrote 8388608/8388608 bytes at offset 10485760 +8 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +wrote 33554432/33554432 bytes at offset 25165824 +32 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +wrote 20971520/20971520 bytes at offset 58720256 +20 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +discard 10485760/10485760 bytes at offset 33554432 +10 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +# Create a top image and do some writes and discards +Formatting 'TEST_DIR/t.IMGFMT.top', fmt=3DIMGFMT size=3D134217728 backing_= file=3DTEST_DIR/t.IMGFMT.base backing_fmt=3DIMGFMT +wrote 8388608/8388608 bytes at offset 16777216 +8 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +discard 20971520/20971520 bytes at offset 62914560 +20 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +wrote 10485760/10485760 bytes at offset 88080384 +10 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +# Measure size with discard-no-unref=3Dfalse +{ + "bitmaps": 0, + "required": 71630848, + "fully-allocated": 134545408 +} +# Merging the top image into the base image +Image committed. +base disk image file size in bytes: 71630848 *** done --=20 2.54.0