[Devel] [PATCH hci-8.1 v2 2/6] dm-qcow2: truncate unused tail of image on suspend
Alexander Ivanov
alexander.ivanov at virtuozzo.com
Thu Oct 8 10:38:32 MSK 2026
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 at virtuozzo.com>
---
drivers/md/dm-qcow2-map.c | 53 ++++++++++++++++++++++++++++++++++++
drivers/md/dm-qcow2-target.c | 12 +++++++-
drivers/md/dm-qcow2.h | 1 +
3 files changed, 65 insertions(+), 1 deletion(-)
diff --git a/drivers/md/dm-qcow2-map.c b/drivers/md/dm-qcow2-map.c
index 78ee7cf5e881..7f2ffe7b2405 100644
--- a/drivers/md/dm-qcow2-map.c
+++ b/drivers/md/dm-qcow2-map.c
@@ -2208,6 +2208,59 @@ 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. 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) {
+ 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 3e47e66b4007..3cbec3b9b407 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, "dm-qcow2: Can't find used end: %lld", 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);
--
2.53.0
More information about the Devel
mailing list