From: "Denis V. Lunev" <den@virtuozzo.com>
To: Alexander Ivanov <alexander.ivanov@virtuozzo.com>
Cc: devel@openvz.org
Subject: Re: [Devel] [PATCH hci-8.1] drivers/md/dm-qcow2: truncate unused tail of image on suspend
Date: Tue, 6 Oct 2026 16:45:57 +0200 [thread overview]
Message-ID: <b1ff03dc-7756-4217-9aa2-e32e236cd2ba@virtuozzo.com> (raw)
In-Reply-To: <20261006144428.341824-1-alexander.ivanov@virtuozzo.com>
On 10/6/26 16:44, 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
> the end of the file 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 min(file_preallocated_area_start, 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 | 77 ++++++++++++++++++++++++++++++++++++
> drivers/md/dm-qcow2-target.c | 12 +++++-
> drivers/md/dm-qcow2.h | 1 +
> 3 files changed, 89 insertions(+), 1 deletion(-)
>
> diff --git a/drivers/md/dm-qcow2-map.c b/drivers/md/dm-qcow2-map.c
> index 78ee7cf5e881..39d465343d8b 100644
> --- a/drivers/md/dm-qcow2-map.c
> +++ b/drivers/md/dm-qcow2-map.c
> @@ -2208,6 +2208,83 @@ int qcow2_truncate_safe(struct file *file, loff_t new_len)
> return do_ftruncate(file, new_len, 0);
> }
>
> +static int get_md_page_sync(struct qcow2 *qcow2, u64 id, struct md_page **md)
> +{
> + int ret;
> +
> + spin_lock_irq(&qcow2->md_pages_lock);
> + *md = qcow2_md_page_find_or_postpone(qcow2, id, NULL);
> + spin_unlock_irq(&qcow2->md_pages_lock);
> + if (*md)
> + return 0;
> +
> + ret = qcow2_alloc_and_insert_md_page(qcow2, id, md);
> + if (ret)
> + return ret;
> +
> + ret = qcow2_rw_page_sync(READ, qcow2, id, (*md)->page);
> + spin_lock_irq(&qcow2->md_pages_lock);
> + if (ret)
> + qcow2_md_page_erase(qcow2, *md);
> + else
> + (*md)->status |= MD_UPTODATE;
> + spin_unlock_irq(&qcow2->md_pages_lock);
> + if (ret)
> + qcow2_free_md_page(*md);
> + return ret;
> +}
> +
> +/*
> + * Returns the end of the last cluster, which is used according
> + * to refcount table. Clusters freed by discard may leave the tail
> + * of file unused. 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_size - 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 = get_md_page_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 = get_md_page_sync(qcow2, r2.page_id, &r2.md);
> + if (ret)
> + return ret;
> +
> + for (i = r2.index_in_page; i >= 0; i--, pos -= clu_size) {
> + if (get_r2_entry(qcow2, r2.md, i))
> + return pos + clu_size;
> + }
> + }
> +
> + 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 be09615eb8e2..95c8b114d92f 100644
> --- a/drivers/md/dm-qcow2-target.c
> +++ b/drivers/md/dm-qcow2-target.c
> @@ -1016,11 +1016,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, "dm-qcow2: Can't find used end: %lld", used_end);
> + else if (used_end < end)
> + end = used_end;
> + }
> +
> if (end == qcow2->file_size)
> return;
>
> diff --git a/drivers/md/dm-qcow2.h b/drivers/md/dm-qcow2.h
> index d9b8c38e093f..2c5eb7578316 100644
> --- a/drivers/md/dm-qcow2.h
> +++ b/drivers/md/dm-qcow2.h
> @@ -409,6 +409,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);
ploop?
_______________________________________________
Devel mailing list
Devel@openvz.org
https://lists.openvz.org/mailman/listinfo/devel
next prev parent reply other threads:[~2026-10-06 14:47 UTC|newest]
Thread overview: 6+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-06 14:44 Alexander Ivanov
2026-10-06 14:45 ` Denis V. Lunev [this message]
2026-10-06 14:49 ` no-reply
2026-10-06 15:52 ` Konstantin Khorenko
2026-10-06 15:56 ` Denis V. Lunev
2026-10-06 17:39 ` Konstantin Khorenko
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=b1ff03dc-7756-4217-9aa2-e32e236cd2ba@virtuozzo.com \
--to=den@virtuozzo.com \
--cc=alexander.ivanov@virtuozzo.com \
--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