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 0F9318017D for ; Wed, 28 Oct 2026 15:08:38 +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 69SF7Pnh021195; Wed, 28 Oct 2026 18:07:26 +0300 DKIM-Filter: OpenDKIM Filter v2.11.0 mail.openvz.org 69SF7Pnh021195 Authentication-Results: mail.openvz.org; dkim=fail reason="signature verification failed" (2048-bit key) header.d=virtuozzo.com header.i=@virtuozzo.com header.b="LYWJ2Jjj" Received: from mail-wr1-f69.google.com (mail-wr1-f69.google.com [209.85.221.69]) by mail.openvz.org (8.14.4/8.14.4) with ESMTP id 69SF7BUO021133 (version=TLSv1/SSLv3 cipher=AES128-GCM-SHA256 bits=128 verify=FAIL) for ; Wed, 28 Oct 2026 18:07:11 +0300 DKIM-Filter: OpenDKIM Filter v2.11.0 mail.openvz.org 69SF7BUO021133 Received: by mail-wr1-f69.google.com with SMTP id ffacd0b85a97d-48dfaaea30cso763268f8f.0 for ; Wed, 28 Oct 2026 08:07:11 -0700 (PDT) X-Forwarded-Encrypted: i=2; AKwUvByvsKcmhah9DHapMNDCpsg3hygrpkNymul0OfnzNt17B2rQP9IKSWjQNmRngjXs8r8TUC4gFg==@openvz.org X-Gm-Message-State: AFq9FYKYq1RBlo0zDNz6zuxzctSsyYlnDyRYV2lar/18UarZsX+3X6/Q r2fNrfZ7gSdBPDQC1mFlfL3vJuMl+bsYfnuIhTCTyDIZXjvIySu3Uba+ZJ+omCv4O/LdafmcTS0 7k+Z4jSNXQ29op1YnLp3nxDdULizDJRdkjidkHUvrPzWrxMT47eZzCA== X-Gm-Gg: AYBFou32okix8bXk6bW6mijqGTH0b5+TIAjK+3m1Vv3yvRwLb5Qr2X9gwwAMzin0JFH +DiYVYODdZuaKLKadAvUWcwRUZHdzB5wJtNSjV539MtZa8dalGmdx026wn3z5RQYgT7Qmim3eMe 8i8axP2LIIGSgrRQc+6X7F+WpCDh/yhN6DosOoifuvBTN/rzgtNAJgkcUwHqJNY8XWDrwBcWK49 hRBcdgTqH8lsmIFNCX4ucySmlS5sWMwcRGN7XWIsshhGUDnUsddK1YwORtBbn/g5A//bSbKsp5n Cmen7JHQv+TcyiSQ5M7hHlhh5tUXG1mLPiR6NjUStfdN9+9MGizkfcV+LIv/WwapccM/vQldRU7 26cDdA9uhs2CePK7FlqBx6iOGaDk= X-Received: by 2002:adf:e009:0:10b0:487:1575:ebe2 with SMTP id ffacd0b85a97d-48e5d5adbddmr4570819f8f.44.1793200031338; Wed, 28 Oct 2026 08:07:11 -0700 (PDT) X-Received: by 2002:adf:e009:0:10b0:487:1575:ebe2 with SMTP id ffacd0b85a97d-48e5d5adbddmr4570745f8f.44.1793200030641; Wed, 28 Oct 2026 08:07:10 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1793200030; cv=none; d=google.com; s=arc-20260327; b=r/PPKIHzEnHvMLTobkF5rXs07pSSYeHimmAxhlYYDhgTapL3bnGfELe0ghBHP4IUYX 8V5AFYTwhS3FaCPWeR2CKG8Xu+msDkEgjOjy2k4ymYLsctZ5N+x5f+jZjZEu0aXjajta yuzyGwNh1UO1U5VseF8YR4ireexU9kpFYfpf6IuGH3ibEvhf5CzNyMJjvds+EKNgoxgj ukWubPKdgFXJDBZuyVR7pjinQolSnEYWxpTFyaRbixsViKL/qFaS8oeZM0qNkwxKkVXe P2+BRvqC7U0Wmc21/1HnE9h3hQPhgLwA3Cc132KYHmzdZ5p9maDP4uV0FHxMRR7T+bnF 2EVg== 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=DxAF3kjWfOzuVnciKIVxMVwxCb6sW2Z+SIGM0VmP3SI=; fh=CMILZ+goaghq6B4UpCDhf0EQp6kYD0LAlmOkUiVCOvs=; b=qrjfid2J7syMm8DSsRN0rNoR+8NI4E57XE/+lEKG86kHu9KDaxPCPiglOMGD3KM669 Jn0Chtxq7A69mxE3FTsBJofy0KA+hb8aEwTKf+UwFpwWwuCcMlieaHlgIfm6Ox5OiJcD HB4izCjP3px1DOUtiNF5NKHo51DZL9DlpKtzzszsLxLbN424t5UJhp894INHP+AIy2rA qSSdmVtFOxG2bwHU//1GQCUla+2ks6nGVz7JJDJmHg52DnI28060099hpsJ2rFcbAG/K ztqv75QGwzGFH8/8q9/qnHnxxvRm6YVMrad+YYxCUCCWnyzioEV33801rCh+5ciBZBbp 87mQ==; dara=google.com ARC-Authentication-Results: i=1; mx.google.com; dkim=pass header.i=@virtuozzo.com header.s=relay header.b=LYWJ2Jjj; 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-48e3ecdb119si28288834f8f.111.2026.10.28.08.07.10 for (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 28 Oct 2026 08:07:10 -0700 (PDT) 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=LYWJ2Jjj; 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=DxAF3kjWfOzuVnciKIVxMVwxCb6sW2Z+SIGM0VmP3SI=; b=LYWJ2Jjj6otP vuiD/jFWeYWqBqTH2AAhSice/u/ASuJCW0J23/f5sl6sE2yEAW8hGytg4rX4nIEM3hU4Sm9uFiNmw bic5NAnHRxPIi6foedByNmB0dR1RK0LfluPYcN/XnNnUF7L36+2OPbDXY3yxuZytqjXMHNfL1ZPuJ 2hJqHKZAohQlO/AE0C+UPLt5mFsGujGG36It2MGegGEHnAsnV/Rgd7ymt6pJh5Ta5TSmnFjfASpBh MHTp2XwxnH+PCOvG4Jtzn068Lin1TnIHAtSCgIw8YCp4LXIUSq4XKat99YzkMWguJVQle9qVAdcQs PE08Ubzv6Lx0YRGFJIajqw==; 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 1xM5B4-00AtLl-1a; Wed, 28 Oct 2026 16:06:52 +0100 From: Alexander Ivanov To: andrey.zhadchenko@virtuozzo.com Date: Wed, 28 Oct 2026 16:06:45 +0100 Message-ID: <20261028150645.3222391-11-alexander.ivanov@virtuozzo.com> X-Mailer: git-send-email 2.53.0 In-Reply-To: <20261028150645.3222391-1-alexander.ivanov@virtuozzo.com> References: <20261028150645.3222391-1-alexander.ivanov@virtuozzo.com> MIME-Version: 1.0 X-OZ-Fwd: true Cc: devel@openvz.org Subject: [Devel] [PATCH hci-8.1 v9 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 any failed md writeback. A failed R1/R2 write leaves refcounts on disk stale. After a reverted L1/L2 writeback (lx_writeback_error) L1/L2 on disk may point to a cluster whose refcount was reverted to zero. 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. Otherwise userspace sets the dirty bit on every RW open, so the bit doesn't mean the image is inconsistent. Userspace checks the image before opening it RW, which find_unused_cluster() already relies on when it reuses clusters with zero refcount. 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 | 23 ++++++++++--- drivers/md/dm-qcow2.h | 1 + 4 files changed, 82 insertions(+), 7 deletions(-) diff --git a/drivers/md/dm-ploop-target.c b/drivers/md/dm-ploop-target.c index 3b589579741c..a94460c32500 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 1ecbb525b6aa..7391531211d9 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 */ @@ -2204,6 +2208,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 a9bdad198fde..4d92e5abfb5d 100644 --- a/drivers/md/dm-qcow2-target.c +++ b/drivers/md/dm-qcow2-target.c @@ -1058,21 +1058,36 @@ 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 (tgt->lx_writeback_error) { + QC_INFO(ti, "Skip shrinking to used end after reverted L1/L2 writeback"); + } 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; } @@ -1153,7 +1168,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 4113783b9d15..1909931e1598 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