From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from mail.openvz.org (mail.openvz.org [69.168.225.77]) by lore.virtuozzo.com (Postfix) with ESMTPS id 5B013813D0 for ; Mon, 9 Nov 2026 14:03:52 +0000 (UTC) Received: from mail.openvz.org (localhost [127.0.0.1]) by mail.openvz.org (8.14.4/8.14.4) with ESMTP id 6A9E2dPM032628; Mon, 9 Nov 2026 17:02:40 +0300 DKIM-Filter: OpenDKIM Filter v2.11.0 mail.openvz.org 6A9E2dPM032628 Authentication-Results: mail.openvz.org; dkim=fail reason="signature verification failed" (2048-bit key) header.d=virtuozzo.com header.i=@virtuozzo.com header.b="Tii/vvM5" Received: from mail-ej1-f72.google.com (mail-ej1-f72.google.com [209.85.218.72]) by mail.openvz.org (8.14.4/8.14.4) with ESMTP id 6A9E2Y8W032607 (version=TLSv1/SSLv3 cipher=AES128-GCM-SHA256 bits=128 verify=FAIL) for ; Mon, 9 Nov 2026 17:02:34 +0300 DKIM-Filter: OpenDKIM Filter v2.11.0 mail.openvz.org 6A9E2Y8W032607 Received: by mail-ej1-f72.google.com with SMTP id a640c23a62f3a-c42f98f354fso75489766b.0 for ; Mon, 09 Nov 2026 06:02:34 -0800 (PST) X-Forwarded-Encrypted: i=2; AL+hErFMrFOfjVwzHL74f2u+tM6NG23EMwgK2CbhGnmopGQpT2UAmJY8Mk/xKqgdZc7sULF9Nf9DBQ==@openvz.org X-Gm-Message-State: APuU4Z+JxWUTbaI0dYR3eCt0+b7wmPigEcEnC/K/vqkjOoJZpDjX8D3G BkyTrl5XhxoJLmg20m08/q+xm/Z4F23CzXfKkNNsVqcuWitomBbSAyUfN6+WUnu0hwqeUoLPbpt HNEBHIvEm7ox4PYmTYQhMq55piCv3/YlXCEtrYH/iopayDKKjJjz4Ww== X-Gm-Gg: AYBFou0thCgD916ltDmw6447DVj4Wr0JBTAQKC+PgLuljVMOBJTDODkGrGoTi29OxJs 1TGIqekelKP25rsKCT0HwpkZ7Fjcf1/XV/2W7yfRuunENtaHZQyB3whdOlf2wYNlOzdoiwiZC0p i7q+PrA5ZxsYZy9Q3IMP8gSjEz0s96sWcK8tk+GfBIH+Lk76RjXSN9N1FK86y4Pf19XJWhQw0wF IZ9d49u45yov4fqYYw7ZRPymBjrTLMzOyvsu2ttXZH89EchqI7UyhoWAtvPIt5cmyj36q917Ywr IIPxiHDt8vRZUe1m3Oq7/BukWGus6mcRo8YFMeVhRyiz5PPWqquCxVAvpnc7RChOwW+PSbdBK0h FEbN43w/ZoxqukUw80n6njWakE/c= X-Received: by 2002:a17:907:d308:b0:c42:82ac:b6a9 with SMTP id a640c23a62f3a-c4d6d585c83mr299452666b.29.1794232954009; Mon, 09 Nov 2026 06:02:34 -0800 (PST) X-Received: by 2002:a17:907:d308:b0:c42:82ac:b6a9 with SMTP id a640c23a62f3a-c4d6d585c83mr299448666b.29.1794232953381; Mon, 09 Nov 2026 06:02:33 -0800 (PST) ARC-Seal: i=1; a=rsa-sha256; t=1794232953; cv=none; d=google.com; s=arc-20260327; b=D6OM132cJ9to4wMwDY6LbFNjQOAAw8ttyZnvKPO7yPa4HeNJ/OArIlt9zUnQfGbC8I /hjvjjkVs2FML532n4whVsCGKSVchh51GRpX72w+LIGOjDV/8QhCYoI8NZ1F9OpVmKg+ 3WkMRJZ5aRK+LmmgbPUYNOvNB04bE/beUKdpS0sTiw39DrcfLv7qAfIL83NCJQ+wFBhO 1tIZT4wfS4BdXhuLqKCXe7uEvqp/HqQeFHtezozQoxFd2sJv2M3dccTIL8stFFTBeEaa hCJTI8XLiXENs7+bdQBgCaI1ep79sx7p9H7SZKXuQ64Yn/E6x1HHLoJ4MPZgq2qXcIyS B7hw== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20260327; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:dkim-signature; bh=IlXE4mQu6jb9KsBxp3YQnryCzTJL2k8zx9S6g0RzlH0=; fh=CMILZ+goaghq6B4UpCDhf0EQp6kYD0LAlmOkUiVCOvs=; b=GURdyOyA4NwOOAPbKcMbRWoRjyJkUJi/YKLogAJtcORc+IPKCVVhEfug1jh8tyDH3x LeHUUJISAfNeacF4CWZ0eEtLCdl4312hHEXwtVLvVodpuSfsi3fHS6JUgLTVLzzy66DF hOBdu9+k9P54FlHdXFpkZZv2SrBCcHXptY5e0BmFygYbsNf/FEuYHoGjSJEoKElC3fo/ jT1SEKZ2RpWewUNR3+dd2y7lAbIorDLJr5VhyqAs0U2KLWDdOQ+h0oLmK0Rlel7EWk76 SFlcf5pqEL9eTFxUJvuXoGHsykv+JB7isiLPN+4/uNH3Md/tL0H2Zb5jaHy8Cpj/+mc6 keDQ==; dara=google.com ARC-Authentication-Results: i=1; mx.google.com; dkim=pass header.i=@virtuozzo.com header.s=relay header.b="Tii/vvM5"; spf=pass (google.com: domain of alexander.ivanov@virtuozzo.com designates 130.117.225.111 as permitted sender) smtp.mailfrom=alexander.ivanov@virtuozzo.com; dmarc=pass (p=QUARANTINE sp=QUARANTINE dis=NONE) header.from=virtuozzo.com Received: from relay.virtuozzo.com (relay.virtuozzo.com. [130.117.225.111]) by mx.google.com with ESMTPS id ffacd0b85a97d-49597e00654si8876399f8f.287.2026.11.09.06.02.33 for (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 09 Nov 2026 06:02:33 -0800 (PST) Received-SPF: pass (google.com: domain of alexander.ivanov@virtuozzo.com designates 130.117.225.111 as permitted sender) client-ip=130.117.225.111; Authentication-Results: mx.google.com; dkim=pass header.i=@virtuozzo.com header.s=relay header.b="Tii/vvM5"; spf=pass (google.com: domain of alexander.ivanov@virtuozzo.com designates 130.117.225.111 as permitted sender) smtp.mailfrom=alexander.ivanov@virtuozzo.com; dmarc=pass (p=QUARANTINE sp=QUARANTINE dis=NONE) header.from=virtuozzo.com DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=virtuozzo.com; s=relay; h=MIME-Version:Message-ID:Date:Subject:From: Content-Type; bh=IlXE4mQu6jb9KsBxp3YQnryCzTJL2k8zx9S6g0RzlH0=; b=Tii/vvM5f+ka tgrPYQbXQEd4LOlPv9gSrF2g4RrcdX3HJZcf9hYhPE2qzsby3nTa4N86GYp5IPebfnzcqTJASkCFT d6OotnG2tS/LetD1d+1SYgcuQ6ViX+0eJ2lpR5gJiEVYA+xEQL8saNM7Wy60yss1U6jKFvf1bn/bt tTtx3eHsb3uWnjaTJXo19BA01pYBit/LpbN4fALJwj3TANZgaXAsxG43Qji7td+HSYpOireZZolpd Z4+e1r4oOxAkD4ctdIRXAM9V95Iz/h232dtKfb5cCtGlZLcpccxUY7BAWyjaQ658gW2Xf2DREtzTj TbHDnn5/2hctVTEjpuJowg==; Received: from ch-vpn.virtuozzo.com ([130.117.225.6] helo=sky) by relay.virtuozzo.com with esmtps (TLS1.3) tls TLS_AES_256_GCM_SHA384 (Exim 4.96) (envelope-from ) id 1xQPsN-005vFF-04; Mon, 09 Nov 2026 15:01:39 +0100 From: Alexander Ivanov To: andrey.zhadchenko@virtuozzo.com Date: Mon, 9 Nov 2026 15:01:43 +0100 Message-ID: <20261109140143.1408038-11-alexander.ivanov@virtuozzo.com> X-Mailer: git-send-email 2.53.0 In-Reply-To: <20261109140143.1408038-1-alexander.ivanov@virtuozzo.com> References: <20261109140143.1408038-1-alexander.ivanov@virtuozzo.com> MIME-Version: 1.0 X-OZ-Fwd: true Cc: devel@openvz.org Subject: [Devel] [PATCH hci-8.1 v11 10/10] dm-qcow2: truncate unused tail of image on suspend X-BeenThere: devel@openvz.org X-Mailman-Version: 2.1.12 Precedence: list List-Id: OpenVZ development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Content-Type: text/plain; charset="us-ascii" Content-Transfer-Encoding: 7bit Sender: devel-bounces@openvz.org Errors-To: devel-bounces@openvz.org Discard punches holes in place of freed clusters, so they hold no physical extents, but it doesn't change the file size. On postsuspend we truncate the image file only down to file_preallocated_area_start, i.e. we drop preallocations only. So if clusters at the end of the file are freed (refcount dropped to zero), the file size stays the same and the image 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, clamped to file_preallocated_area_start since the last cluster may be partial. Refcount ranges with no allocated refblock are skipped at once. The scan walks R1 entries normal I/O may never touch, so it validates them itself instead of letting calc_r2_page_id() WARN on a corrupted one. 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. A failed relocation of the reftable leaves file_preallocated_area_start beyond the space covered by the reftable, so the scan starts at most from its end. Truncate the file to the used end. Fall back to the old behaviour on scan error and after a failed R1/R2 writeback, which leaves refcounts on disk stale. Refcounts may also be below real usage after a crash, since both QEMU with lazy_refcounts and this driver write L2 before refcounts. The scan would then cut off clusters L2 still points to, even in a session without guest writes. With kernel_sets_dirty_bit such an image has the dirty bit set at open, and the disabled dirty bit check lets it be opened RW, so skip the scan for it. In the default mode userspace sets the dirty bit on every RW open, so the bit doesn't mean the image is inconsistent. There the scan is safe only because userspace checks the image before an RW open. A v2 image has no dirty bit, so the opened_dirty guard never applies to it, and it is covered only by that check too. The scan trusts refcounts, as the rest of the driver does. Only a walk over all L2 tables would catch a zeroed or misplaced R1 entry, so corrupted refcounts are left to the same image check as stale ones. Close and reload to RO go through postsuspend too. On remove, __dm_destroy() calls the presuspend and postsuspend hooks if the live table isn't suspended yet. It neither stops the queue nor waits for I/O, but the device is idle there, because dm_lock_for_deletion() refuses to remove an open device. On table swap, do_resume() suspends the old (RW) table before dm_swap_table(). So the file is shrunk in these cases as well. https://virtuozzo.atlassian.net/browse/VSTOR-148171 Feature: dm-qcow2: block device over QCOW2 files driver Signed-off-by: Alexander Ivanov --- drivers/md/dm-ploop-target.c | 2 +- drivers/md/dm-qcow2-map.c | 63 ++++++++++++++++++++++++++++++++++-- drivers/md/dm-qcow2-target.c | 21 +++++++++--- drivers/md/dm-qcow2.h | 1 + 4 files changed, 80 insertions(+), 7 deletions(-) diff --git a/drivers/md/dm-ploop-target.c b/drivers/md/dm-ploop-target.c index d8279c79ef56..b869c92bbe72 100644 --- a/drivers/md/dm-ploop-target.c +++ b/drivers/md/dm-ploop-target.c @@ -447,7 +447,7 @@ static struct ploop_worker *ploop_worker_create(struct ploop *ploop, * * FIXME: We must implement metadata reload on resume. After that we can * implement preallocation truncation on suspend (see dm-qcow2 as it already - * implements that in qcow2_parse_metadata and qcow2_truncate_preallocations). + * implements that in qcow2_parse_metadata and qcow2_truncate_unused_tail). */ static int ploop_ctr(struct dm_target *ti, unsigned int argc, char **argv) { diff --git a/drivers/md/dm-qcow2-map.c b/drivers/md/dm-qcow2-map.c index 3bcaceab1a1d..621bc9b18e26 100644 --- a/drivers/md/dm-qcow2-map.c +++ b/drivers/md/dm-qcow2-map.c @@ -1090,6 +1090,11 @@ static int calc_refcounters_map(struct qcow2 *qcow2, loff_t pos, return -EIO; } +static bool r1_entry_is_valid(struct qcow2 *qcow2, u64 entry) +{ + return !(entry & R1_RESERVED_ZERO_MASK) && CLU_OFF(qcow2, entry) == 0; +} + static int calc_r2_page_id(struct qcow2 *qcow2, struct qcow2_map_item *r1, struct qcow2_map_item *r2) { @@ -1097,8 +1102,7 @@ static int calc_r2_page_id(struct qcow2 *qcow2, struct qcow2_map_item *r1, u32 bits = qcow2->refblock_bits; loff_t pos; - if (WARN_ON_ONCE((entry & R1_RESERVED_ZERO_MASK) || - (CLU_OFF(qcow2, entry) != 0))) + if (WARN_ON_ONCE(!r1_entry_is_valid(qcow2, entry))) return -EIO; /* The corresponding refcount block has not yet been allocated */ @@ -2168,6 +2172,61 @@ int qcow2_truncate_safe(struct file *file, loff_t new_len) return do_ftruncate(file, new_len, 0); } +/* + * Return the end of the last cluster with non-zero refcount. Must be + * called on a suspended device after md writeback. + */ +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 = min(qcow2->file_preallocated_area_start, + qcow2->reftable_max_file_size); + pos = round_down(pos - 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; + + if (!r1_entry_is_valid(qcow2, get_u64_from_be_page(r1_md->page, + r1.index_in_page))) + return -EIO; + + ret = calc_r2_page_id(qcow2, &r1, &r2); + if (ret == -ENOENT) { + 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 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 e28c47d4e728..30b6bb5c98f4 100644 --- a/drivers/md/dm-qcow2-target.c +++ b/drivers/md/dm-qcow2-target.c @@ -1061,21 +1061,34 @@ static void qcow2_dtr(struct dm_target *ti) qcow2_tgt_destroy(tgt); } -static void qcow2_truncate_preallocations(struct dm_target *ti) +static void qcow2_truncate_unused_tail(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; + + if (tgt->md_writeback_error) { + QC_INFO(ti, "Skip shrinking to used end after md writeback error"); + } else if (kernel_sets_dirty_bit && qcow2->opened_dirty) { + QC_INFO(ti, "Skip shrinking to used end of dirty image"); + } else { + 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; ret = qcow2_truncate_safe(qcow2->file, end); if (ret) { - QC_ERR(ti, "Can't truncate preallocations"); + QC_ERR(ti, "Can't truncate unused tail"); tgt->truncate_error = true; return; } @@ -1156,7 +1169,7 @@ static void qcow2_postsuspend(struct dm_target *ti) int ret; qcow2_flush_deferred_activity_all(to_qcow2_target(ti)); - qcow2_truncate_preallocations(ti); + qcow2_truncate_unused_tail(ti); if (dm_table_get_mode(ti->table) & FMODE_WRITE) { ret = qcow2_set_image_file_features(qcow2, false); diff --git a/drivers/md/dm-qcow2.h b/drivers/md/dm-qcow2.h index ebab22d505e8..ed427a0a7699 100644 --- a/drivers/md/dm-qcow2.h +++ b/drivers/md/dm-qcow2.h @@ -416,6 +416,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 _______________________________________________ Devel mailing list Devel@openvz.org https://lists.openvz.org/mailman/listinfo/devel