All Virtuozzo development lists (kernel + QEMU)
 help / color / mirror / Atom feed
From: Konstantin Khorenko <khorenko@virtuozzo.com>
Subject: Re: [Devel] [PATCH VZ10 v3 09/11] drivers/md/dm-qcow2: do discards during backward merge only for writable image
Date: Tue, 25 Aug 2026 19:12:18 +0200	[thread overview]
Message-ID: <6b94991c-a771-4b20-842c-3bd2e48da063@virtuozzo.com> (raw)
In-Reply-To: <20260825122430.252094-10-andrey.zhadchenko@virtuozzo.com>

as committed, a read-only backward merge of an image with internal snapshots
completes "successfully" while silently skipping every cluster under a shared L1 entry
(parse_l1() still refuses write-mode parsing of shared entries, so the merge qio ends with "nothing to merge") -

data loss once the source is dropped from the chain.
Patch 10 fixes exactly this and its own commit message confirms the data loss.
Suggest squashing/reordering, or rejecting RO sources with nb_snapshots != 0 in patch 9.

--
Best regards,

Konstantin Khorenko,
Virtuozzo Linux Kernel Team

On 8/25/26 14:24, Andrey Zhadchenko wrote:
> Backward merge discards every merged cluster from the image we merge
> from: the L2 entry is zeroed and refcounts are decremented, which
> dirties the image metadata. This requires the image file to be opened
> for write, while merge-in-the-middle images usually opened read-only.
> 
> Skip the discard step when the merged image is read-only:
>  - complete the merge qio right after its data is written to the lower
> delta, without the L1/L2 entry update;
>  - process READs on such image in the regular way. Previously reads would
> cause out-of-order merge for present cluster and then requeue. Without
> discard it will loop.
>  - do not break COW at L1. Note that due to how merge machinery works,
> we can't merge without unuse and internal snaphots (to be addressed
> in the next patches).
>  - do not clear the dirty bit on merge completion: the image was never
> modified.
> 
> Just in case add warning and end qio if we somehow encounter non-service
> write qio for read-only images during the merge.
> 
> https://virtuozzo.atlassian.net/browse/VSTOR-138288
> Feature: dm-qcow2: block device over QCOW2 files driver
> Signed-off-by: Andrey Zhadchenko <andrey.zhadchenko@virtuozzo.com>
> ---
>  drivers/md/dm-qcow2-cmd.c | 21 +++++++++++++--------
>  drivers/md/dm-qcow2-map.c | 27 ++++++++++++++++++++++++++-
>  drivers/md/dm-qcow2.h     |  5 +++++
>  3 files changed, 44 insertions(+), 9 deletions(-)
> 
> diff --git a/drivers/md/dm-qcow2-cmd.c b/drivers/md/dm-qcow2-cmd.c
> index c15c46a0fe8b7..fa3762c58372b 100644
> --- a/drivers/md/dm-qcow2-cmd.c
> +++ b/drivers/md/dm-qcow2-cmd.c
> @@ -264,7 +264,7 @@ static int qcow2_merge_backward_start(struct qcow2_target *tgt, int efd, u32 dep
>  	lower = qcow2->lower;
>  	if (!lower)
>  		return -ENOENT;
> -	if (!(lower->file->f_mode & FMODE_WRITE))
> +	if (!qcow2_file_is_writable(lower))
>  		return -EACCES;
>  	if (qcow2->clu_size != lower->clu_size)
>  		return -EOPNOTSUPP;
> @@ -315,11 +315,14 @@ void qcow2_merge_backward_work(struct work_struct *work)
>  	 * there would be problems with unusing them:
>  	 * we'd have to freeze IO going to all data clusters
>  	 * under every L1 entry related to several snapshots.
> +	 * Readonly images skip this stage.
>  	 */
> -	ret = qcow2_break_l1cow(tgt, qcow2);
> -	if (ret) {
> -		QC_ERR(tgt->ti, "Can't break L1 COW");
> -		goto out_err;
> +	if (qcow2_file_is_writable(qcow2)) {
> +		ret = qcow2_break_l1cow(tgt, qcow2);
> +		if (ret) {
> +			QC_ERR(tgt->ti, "Can't break L1 COW");
> +			goto out_err;
> +		}
>  	}
>  
>  	backward_merge_update_stage(tgt, BACKWARD_MERGE_STAGE_SET_DIRTY);
> @@ -388,9 +391,11 @@ static int qcow2_merge_backward_complete(struct qcow2_target *tgt)
>  	qcow2_flush_deferred_activity(tgt, qcow2); /* Delayed md pages */
>  	qcow2->lower = NULL;
>  
> -	ret = qcow2_set_image_file_features(qcow2, false);
> -	if (ret < 0)
> -		QC_ERR(tgt->ti, "Can't unuse merged img (%d)", ret);
> +	if (qcow2_file_is_writable(qcow2)) {
> +		ret = qcow2_set_image_file_features(qcow2, false);
> +		if (ret < 0)
> +			QC_ERR(tgt->ti, "Can't unuse merged img (%d)", ret);
> +	}
>  	qcow2_destroy(qcow2);
>  
>  	tgt->backward_merge.state = BACKWARD_MERGE_STOPPED;
> diff --git a/drivers/md/dm-qcow2-map.c b/drivers/md/dm-qcow2-map.c
> index aa11956ac1d8c..c790b2ad3787e 100644
> --- a/drivers/md/dm-qcow2-map.c
> +++ b/drivers/md/dm-qcow2-map.c
> @@ -2688,6 +2688,12 @@ static void backward_merge_write_complete(struct qcow2_target *tgt, struct qio *
>  		return;
>  	}
>  
> +	/* Skip discard for read-only source images */
> +	if (!qcow2_file_is_writable(qcow2)) {
> +		qio_endio(qio);
> +		return;
> +	}
> +
>  	WARN_ON_ONCE(qio->flags & QIO_IS_DISCARD_FL);
>  	qio->flags |= QIO_IS_DISCARD_FL;
>  
> @@ -2731,6 +2737,17 @@ static int prepare_backward_merge(struct qcow2 *qcow2, struct qio **qio,
>  	struct qio *aux_qio;
>  	int ret;
>  
> +	/* Readonly image mappings remain stable, so reads just go through */
> +	if (!qcow2_file_is_writable(qcow2)) {
> +		if (!op_is_write((*qio)->bi_op))
> +			return 1;
> +		if (WARN_ON_ONCE(!fake_merge_qio(*qio))) {
> +			(*qio)->bi_status = BLK_STS_IOERR;
> +			qio_endio(*qio);
> +			return 0;
> +		}
> +	}
> +
>  	if (!map->data_clu_alloced) {
>  		WARN_ON_ONCE(map->clu_is_cow); /* Strange COW at L1 */
>  		if (fake_merge_qio(*qio)) {
> @@ -3640,7 +3657,15 @@ static void process_one_qio(struct qcow2 *qcow2, struct qio *qio)
>  	if (!handle_metadata(qcow2, &qio, &map))
>  		return;
>  
> -	if (unlikely(qcow2->backward_merge_in_process)) {
> +	/*
> +	 * Merge machinery makes out of order merges for present
> +	 * clusters when it sees the reads. But if the merge does
> +	 * not discard the cluser mapping, it will spin endlessly.
> +	 * So process only actual merge qios or reads from writable
> +	 * images.
> +	 */
> +	if (unlikely(qcow2->backward_merge_in_process) &&
> +	    (fake_merge_qio(qio) || qcow2_file_is_writable(qcow2))) {
>  		submit_top_delta_read(&map, qio);
>  		return;
>  	}
> diff --git a/drivers/md/dm-qcow2.h b/drivers/md/dm-qcow2.h
> index 0f006f1ae48cc..d9b8c38e093f5 100644
> --- a/drivers/md/dm-qcow2.h
> +++ b/drivers/md/dm-qcow2.h
> @@ -460,6 +460,11 @@ static inline bool qcow2_wants_check(struct qcow2_target *tgt)
>  	return !!(tgt->md_writeback_error|tgt->truncate_error);
>  }
>  
> +static inline bool qcow2_file_is_writable(struct qcow2 *qcow2)
> +{
> +	return qcow2->file->f_mode & FMODE_WRITE;
> +}
> +
>  static inline void remap_to_clu(struct qcow2 *qcow2, struct qio *qio, loff_t clu_pos)
>  {
>  	qio->bi_iter.bi_sector &= (to_sector(qcow2->clu_size) - 1);


  reply	other threads:[~2026-08-25 17:12 UTC|newest]

Thread overview: 16+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-08-25 12:24 [Devel] [PATCH VZ10 v3 00/11] dm-qcow2: improve discard and read-only merge handling Andrey Zhadchenko
2026-08-25 12:24 ` [Devel] [PATCH VZ10 v3 01/11] drivers/md/dm-qcow2: do not attach a bvec to discard qios Andrey Zhadchenko
2026-08-25 12:24 ` [Devel] [PATCH VZ10 v3 02/11] drivers/md/dm-qcow2: fix revert_cluster_alloc() for ext_l2 case Andrey Zhadchenko
2026-08-25 12:24 ` [Devel] [PATCH VZ10 v3 03/11] drivers/md/dm-qcow2: generalize COW index update machinery Andrey Zhadchenko
2026-08-25 12:24 ` [Devel] [PATCH VZ10 v3 04/11] drivers/md/dm-qcow2: update metadata on whole cluster discard Andrey Zhadchenko
2026-08-25 12:24 ` [Devel] [PATCH VZ10 v3 05/11] drivers/md/dm-qcow2: never trigger COW or allocation on discard Andrey Zhadchenko
2026-08-25 12:24 ` [Devel] [PATCH VZ10 v3 06/11] drivers/md/dm-qcow2: set L2_READS_ALL_ZEROES after some discards Andrey Zhadchenko
2026-08-25 17:10   ` Konstantin Khorenko
2026-08-25 12:24 ` [Devel] [PATCH VZ10 v3 07/11] drivers/md/dm-qcow2: update metadata on subclusters discard Andrey Zhadchenko
2026-08-25 12:24 ` [Devel] [PATCH VZ10 v3 08/11] drivers/md/dm-qcow2: unmap cluster when discard clears last allocated subclusters Andrey Zhadchenko
2026-08-25 12:24 ` [Devel] [PATCH VZ10 v3 09/11] drivers/md/dm-qcow2: do discards during backward merge only for writable image Andrey Zhadchenko
2026-08-25 17:12   ` Konstantin Khorenko [this message]
2026-08-25 12:24 ` [Devel] [PATCH VZ10 v3 10/11] drivers/md/dm-qcow2: allow shared L1 entries during merge from RO image Andrey Zhadchenko
2026-08-25 12:24 ` [Devel] [PATCH VZ10 v3 11/11] drivers/md/dm-qcow2: respect zeroes during merge Andrey Zhadchenko
2026-08-25 16:54 ` [Devel] [PATCH VZ10 v3 00/11] dm-qcow2: improve discard and read-only merge handling Pavel Tikhomirov
2026-08-26 10:27 ` Vasileios Almpanis

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=6b94991c-a771-4b20-842c-3bd2e48da063@virtuozzo.com \
    --to=khorenko@virtuozzo.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.