OpenVZ / Virtuozzo kernel development (devel@openvz.org)
 help / color / mirror / Atom feed
From: "Denis V. Lunev" <den@virtuozzo.com>
To: Alexander Ivanov <alexander.ivanov@virtuozzo.com>, den@openvz.org
Cc: devel@openvz.org
Subject: Re: [Devel] [PATCH hci-8.1 v3 3/3] dm-qcow2: truncate unused tail of image on suspend
Date: Thu, 8 Oct 2026 12:58:32 +0200	[thread overview]
Message-ID: <882479d4-1490-4643-a774-945287d36008@virtuozzo.com> (raw)
In-Reply-To: <20261008100449.550882-4-alexander.ivanov@virtuozzo.com>

On 10/8/26 12:04, Alexander Ivanov wrote:
> On postsuspend we truncate the image file only down to
> file_preallocated_area_start, i.e. we drop preallocations only.
> Clusters at the end of the file freed by discard (refcount dropped
> to zero) are never given back, so the file size never shrinks.
>
> Add qcow2_calc_used_end(), which scans refcount blocks backward from
> file_preallocated_area_start and returns the end of the last cluster
> with non-zero refcount. Refcount ranges with no allocated refblock
> are skipped at once. Metadata pages are taken from md cache or read
> synchronously. This is called on suspended device after md writeback,
> so R1 and R2 can't change under us.
>
> Truncate the file to the used end. Skip the scan if md writeback
> failed, as refcounts on disk are stale then, and fall back to the old
> behaviour on scan error.
>
> https://virtuozzo.atlassian.net/browse/VSTOR-148171
> Feature: dm-qcow2: block device over QCOW2 files driver
>
> Signed-off-by: Alexander Ivanov <alexander.ivanov@virtuozzo.com>
> ---
>  drivers/md/dm-qcow2-map.c    | 56 ++++++++++++++++++++++++++++++++++++
>  drivers/md/dm-qcow2-target.c | 12 +++++++-
>  drivers/md/dm-qcow2.h        |  1 +
>  3 files changed, 68 insertions(+), 1 deletion(-)
>
> diff --git a/drivers/md/dm-qcow2-map.c b/drivers/md/dm-qcow2-map.c
> index 78ee7cf5e881..65ee6ae144bf 100644
> --- a/drivers/md/dm-qcow2-map.c
> +++ b/drivers/md/dm-qcow2-map.c
> @@ -2208,6 +2208,62 @@ int qcow2_truncate_safe(struct file *file, loff_t new_len)
>  	return do_ftruncate(file, new_len, 0);
>  }
>  
> +/*
> + * Returns the end of the last cluster, which is used according
> + * to refcount table, clamped to file_preallocated_area_start since
> + * the last cluster may be partial. Clusters freed by discard may
> + * leave the tail of file unused. Everything at or above
> + * file_preallocated_area_start is unused by construction, so the scan
> + * starts below it. This must be called on suspended device after md
> + * writeback, so nobody changes R1 and R2.
> + */
> +loff_t qcow2_calc_used_end(struct qcow2 *qcow2)
> +{
> +	u32 clu_size = qcow2->clu_size;
> +	struct qcow2_map_item r1, r2;
> +	struct md_page *r1_md = NULL;
> +	loff_t pos;
> +	s32 i;
> +	int ret;
> +
> +	pos = round_down(qcow2->file_preallocated_area_start - 1, clu_size);
> +	while (pos >= 0) {
> +		if (calc_refcounters_map(qcow2, pos, &r1, &r2) < 0)
> +			return -EIO;
> +
> +		if (!r1_md || r1_md->id != r1.page_id) {
> +			ret = qcow2_md_page_read_sync(qcow2, r1.page_id, &r1_md);
> +			if (ret)
> +				return ret;
> +		}
> +		r1.md = r1_md;
> +
> +		ret = calc_r2_page_id(qcow2, &r1, &r2);
> +		if (ret == -ENOENT) {
> +			/* No R2 cluster, so all clusters it covers are unused */
> +			pos = (loff_t)r1.index * qcow2->refblock_entries * clu_size;
> +			pos -= clu_size;
> +			continue;
> +		}
> +
> +		if (ret < 0)
> +			return ret;
> +
> +		ret = qcow2_md_page_read_sync(qcow2, r2.page_id, &r2.md);
> +		if (ret)
> +			return ret;
> +
> +		for (i = r2.index_in_page; i >= 0; i--, pos -= clu_size) {
> +			/* Last cluster is partial if size isn't cluster-aligned */
> +			if (get_r2_entry(qcow2, r2.md, i))
> +				return min_t(loff_t, pos + clu_size,
> +					     qcow2->file_preallocated_area_start);
> +		}
> +	}
> +
> +	return -EIO;
> +}
> +
>  static int truncate_prealloc_safe(struct qcow2 *qcow2, loff_t len, const char *func)
>  {
>  	loff_t prealloc_len, max_prealloc_len = qcow2->reftable_max_file_size;
> diff --git a/drivers/md/dm-qcow2-target.c b/drivers/md/dm-qcow2-target.c
> index b3d5d5e8c7c2..5a72d0f2e9ef 100644
> --- a/drivers/md/dm-qcow2-target.c
> +++ b/drivers/md/dm-qcow2-target.c
> @@ -1042,11 +1042,21 @@ static void qcow2_truncate_preallocations(struct dm_target *ti)
>  {
>  	struct qcow2_target *tgt = to_qcow2_target(ti);
>  	struct qcow2 *qcow2 = top_qcow2_protected(ti);
> -	loff_t end = qcow2->file_preallocated_area_start;
> +	loff_t used_end, end = qcow2->file_preallocated_area_start;
>  	int ret;
>  
>  	if (!(dm_table_get_mode(ti->table) & FMODE_WRITE))
>  		return;
> +
> +	/* Refcounts on disk are stale after failed md writeback */
> +	if (!tgt->md_writeback_error) {
> +		used_end = qcow2_calc_used_end(qcow2);
> +		if (used_end < 0)
> +			QC_ERR(ti, "Can't find used end: %pe", ERR_PTR(used_end));
> +		else
> +			end = used_end;
> +	}
> +
>  	if (end == qcow2->file_size)
>  		return;
>  
> diff --git a/drivers/md/dm-qcow2.h b/drivers/md/dm-qcow2.h
> index 69d3ae6fca8a..73d3cf175bbf 100644
> --- a/drivers/md/dm-qcow2.h
> +++ b/drivers/md/dm-qcow2.h
> @@ -411,6 +411,7 @@ void qcow2_ref_dec(struct qcow2_target *tgt, u8 ref_index);
>  int qcow2_inflight_ref_switch(struct qcow2_target *tgt);
>  void qcow2_flush_deferred_activity(struct qcow2_target *tgt, struct qcow2 *qcow2);
>  int qcow2_truncate_safe(struct file *file, loff_t new_len);
> +loff_t qcow2_calc_used_end(struct qcow2 *qcow2);
>  
>  void qcow2_merge_backward_work(struct work_struct *work);
>  void qcow2_merge_backward_cancel(struct qcow2_target *tgt);
Please say in the cover letter or the commit message that
close and the RO reload also go through postsuspend, so
readers don't have to work it out.

_______________________________________________
Devel mailing list
Devel@openvz.org
https://lists.openvz.org/mailman/listinfo/devel

  parent reply	other threads:[~2026-10-08 10:59 UTC|newest]

Thread overview: 10+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-10-08 10:04 [Devel] [PATCH hci-8.1 v3 0/3] " Alexander Ivanov
2026-10-08 10:04 ` [Devel] [PATCH hci-8.1 v3 1/3] dm-qcow2: add qcow2_md_page_read_sync() helper Alexander Ivanov
2026-10-08 10:09   ` no-reply
2026-10-08 10:57   ` Denis V. Lunev
2026-10-08 10:04 ` [Devel] [PATCH hci-8.1 v3 2/3] dm-qcow2: drop duplicate prefix from QC_ERR/QC_INFO messages Alexander Ivanov
2026-10-08 10:07   ` no-reply
2026-10-08 10:04 ` [Devel] [PATCH hci-8.1 v3 3/3] dm-qcow2: truncate unused tail of image on suspend Alexander Ivanov
2026-10-08 10:11   ` no-reply
2026-10-08 10:58   ` Denis V. Lunev [this message]
2026-10-08 10:59 ` [Devel] [PATCH hci-8.1 v3 0/3] " Denis V. Lunev

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=882479d4-1490-4643-a774-945287d36008@virtuozzo.com \
    --to=den@virtuozzo.com \
    --cc=alexander.ivanov@virtuozzo.com \
    --cc=den@openvz.org \
    --cc=devel@openvz.org \
    /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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox