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 830C08017D for ; Mon, 9 Nov 2026 12:00:56 +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 6A9BxgF8031436; Mon, 9 Nov 2026 14:59:43 +0300 DKIM-Filter: OpenDKIM Filter v2.11.0 mail.openvz.org 6A9BxgF8031436 Authentication-Results: mail.openvz.org; dkim=fail reason="signature verification failed" (2048-bit key) header.d=virtuozzo.com header.i=@virtuozzo.com header.b="UA2wCz8K" Received: from mail-wr1-f70.google.com (mail-wr1-f70.google.com [209.85.221.70]) by mail.openvz.org (8.14.4/8.14.4) with ESMTP id 6A9BxZix031408 (version=TLSv1/SSLv3 cipher=AES128-GCM-SHA256 bits=128 verify=FAIL) for ; Mon, 9 Nov 2026 14:59:35 +0300 DKIM-Filter: OpenDKIM Filter v2.11.0 mail.openvz.org 6A9BxZix031408 Received: by mail-wr1-f70.google.com with SMTP id ffacd0b85a97d-49034831bb3so787659f8f.1 for ; Mon, 09 Nov 2026 03:59:35 -0800 (PST) X-Forwarded-Encrypted: i=2; AL+hErETgUYJPD0/+jaLBCUgGSWEayIDGIXDODgnhYheOMRqd74IK+aoaPToIz2MHszse+BP6YRzIA==@openvz.org X-Gm-Message-State: APuU4Z8oeRrCrrxM/ORgKBjRAC+OXZjt3GNMjay7len4Vrlb5iftroZj Yw+UoryfTCRXmeFTCwWfnEUBtUZbA+F6NcIkFGwUY3ms4jLx+uLDVDLjzOoyV2s6OOQyaEwayig KSTlePbJ/aZk1lcdJRVyODq0fAt5YcERgZO4SqapV8nN6T351PwcuPw== X-Gm-Gg: AYBFou2NSx3kadBzpjrFeJWervPXokR+IP2cF5U0xMxW2fqWVY58ez5rfVCP1NtQg6z sHxQ2LpZgMus0aDs02fS03Y+1CA2UMacEqBNsnEZY2Ffjfuzvyd+Kc2cpyGndvx32NGxnkMjb+8 p3cwIhoIi99iYZztqZ2L8zUFJOIqB2EorE0uY9awLdRWyoGVMj6fxFmzUkUqq5iYWtWfyT+H2oP Y60h8xyf5srHYcJR5gbULW0S771/H9jM3Wca+7JgcVI3OG86lyCKJnB1nM7VE5/NPNWHxUGyF68 qpq91MDYjTJ/l+ulkKiOeH24W7GiLK1aMCgGey15Rif/UOkTCB10fUWV/rFh6tXuAV8eD8UThIO QETU6KOLULlmTsh0YW3rBh3aHGuam X-Received: by 2002:a05:6000:18a5:b0:490:411a:1eed with SMTP id ffacd0b85a97d-49583deb5e6mr7351816f8f.39.1794225575186; Mon, 09 Nov 2026 03:59:35 -0800 (PST) X-Received: by 2002:a05:6000:18a5:b0:490:411a:1eed with SMTP id ffacd0b85a97d-49583deb5e6mr7351767f8f.39.1794225574598; Mon, 09 Nov 2026 03:59:34 -0800 (PST) ARC-Seal: i=1; a=rsa-sha256; t=1794225574; cv=none; d=google.com; s=arc-20260327; b=MEA3IRsszP6opkWnO9LdHSgsycTHnF8kWupSnWDdW/O0uze3C7owt4Iih1aTWjWKBb V0o4zbDPdzyiLgadDLLf4Xmrw3L5+OyHYBDJBM6nTHfxWhosqpbuX4bDr/g4tRq5ISBM 8c7tRi1w/F6oq6kNdMuKX/CgsqPDrJQNv6OgHTySqIWIhaUY3gqwKwONJ5IWueTIET6B EzfX9gHC3gGnAX/f59Kdu9w77x0fUgfL+gelurnukgyhaAVXUHIkMlb0UW9Wfd1nT7Fc X4Oi5K19xK1tnucbdJ/VwzP3f+RftojOQz+uanXFrON/6qHaRQZItHjPozAARP+lODhY WeGQ== 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=vnH2ChGVnDTtaK8xQCwXZPfFmwwcnqcZDmOMSH+MUnw=; fh=CMILZ+goaghq6B4UpCDhf0EQp6kYD0LAlmOkUiVCOvs=; b=rA+m1mJI5zhjXHGVQaoZQWBZDz6Gm2a8xwqOdv5qH/pWj5WyhX22zkd3A0atzlHV2n CnBBg7ZxiQL4UA0uLXqm8wWjYv/CsONHsi+8QJvzkNd5lYKoKpL7gsdvKuciyh73yoZL mq5urAXQN099MRbLIvGEjPi6g+pk5E0980Ky7wYmbghVx3jnxwCAQb4OIcOoTk1NV8GS 3T+8uKnezh+MwSsJfdCB1YSkyjEXOPc5uiuLike465bktbI3phYgaWRTIYeUQGV5W6Ch AihpCLuf7ASvAwRB3Pc7GEnKj7HichmIB6ctXWVbEGSCYfs0swyLgHPsxGn868H84Jjl VRvA==; dara=google.com ARC-Authentication-Results: i=1; mx.google.com; dkim=pass header.i=@virtuozzo.com header.s=relay header.b=UA2wCz8K; 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-495986a2c44si8008486f8f.366.2026.11.09.03.59.34 for (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 09 Nov 2026 03:59:34 -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=UA2wCz8K; 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=vnH2ChGVnDTtaK8xQCwXZPfFmwwcnqcZDmOMSH+MUnw=; b=UA2wCz8KZD2g x08ULB13NyejXZesmYmMsRlh0ZXAclET7AmDkNhI3F10tjixLwMortigEnsRsMGwBCw5v8SSZe172 U2mK3hUcrupmMjH8jD4ssaYGG7B2fPIgFMd5w7GquFTA/cqR5ym6nhjtTGF8a4MZNKB4uKEOKbM7e 42TzdfOPUsmOONbZsreter2iax+j4u+uy706zNniKdUbNXk5FcsxnbMXadkyE0eIEvrsqZPD4Brs9 wq2s/2v7nJ9ohwqUJ8F04w1PzUnOww0tJthlRuTjisNZZaanNeC1pqqkDglPD7GsKKI5gT5FWV7Tu tPwkp7wCeFlS9BheGmSp7Q==; 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 1xQNxb-003a4R-07; Mon, 09 Nov 2026 12:58:55 +0100 From: Alexander Ivanov To: andrey.zhadchenko@virtuozzo.com Date: Mon, 9 Nov 2026 12:58:56 +0100 Message-ID: <20261109115856.1362416-11-alexander.ivanov@virtuozzo.com> X-Mailer: git-send-email 2.53.0 In-Reply-To: <20261109115856.1362416-1-alexander.ivanov@virtuozzo.com> References: <20261109115856.1362416-1-alexander.ivanov@virtuozzo.com> MIME-Version: 1.0 X-OZ-Fwd: true Cc: devel@openvz.org Subject: [Devel] [PATCH hci-8.1 v10 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. 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 27ae22495f37..d96532b3753e 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 */ @@ -2169,6 +2173,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 1b7a75239291..7587e8f7ac0b 100644 --- a/drivers/md/dm-qcow2-target.c +++ b/drivers/md/dm-qcow2-target.c @@ -1062,21 +1062,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; } @@ -1157,7 +1170,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 2a6203c86e3a..b0ee70bd5497 100644 --- a/drivers/md/dm-qcow2.h +++ b/drivers/md/dm-qcow2.h @@ -415,6 +415,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