You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
114 lines
3.8 KiB
114 lines
3.8 KiB
From b445624f0882badf00da739c52e58a85c18ae002 Mon Sep 17 00:00:00 2001
|
|
From: "Darrick J. Wong" <djwong@kernel.org>
|
|
Date: Wed, 15 Mar 2023 15:56:35 +0100
|
|
Subject: [PATCH] xfs: estimate post-merge refcounts correctly
|
|
|
|
Source kernel commit: b25d1984aa884fc91a73a5a407b9ac976d441e9b
|
|
|
|
Upon enabling fsdax + reflink for XFS, xfs/179 began to report refcount
|
|
metadata corruptions after being run. Specifically, xfs_repair noticed
|
|
single-block refcount records that could be combined but had not been.
|
|
|
|
The root cause of this is improper MAXREFCOUNT edge case handling in
|
|
xfs_refcount_merge_extents. When we're trying to find candidates for a
|
|
refcount btree record merge, we compute the refcount attribute of the
|
|
merged record, but we fail to account for the fact that once a record
|
|
hits rc_refcount == MAXREFCOUNT, it is pinned that way forever. Hence
|
|
the computed refcount is wrong, and we fail to merge the extents.
|
|
|
|
Fix this by adjusting the merge predicates to compute the adjusted
|
|
refcount correctly.
|
|
|
|
Fixes: 3172725814f9 ("xfs: adjust refcount of an extent of blocks in refcount btree")
|
|
Signed-off-by: Darrick J. Wong <djwong@kernel.org>
|
|
Reviewed-by: Dave Chinner <dchinner@redhat.com>
|
|
Reviewed-by: Xiao Yang <yangx.jy@fujitsu.com>
|
|
Signed-off-by: Carlos Maiolino <cem@kernel.org>
|
|
Signed-off-by: Pavel Reichl <preichl@redhat.com>
|
|
---
|
|
libxfs/xfs_refcount.c | 25 +++++++++++++++++++++----
|
|
1 file changed, 21 insertions(+), 4 deletions(-)
|
|
|
|
diff --git a/libxfs/xfs_refcount.c b/libxfs/xfs_refcount.c
|
|
index f6167c5f..29258bdd 100644
|
|
--- a/libxfs/xfs_refcount.c
|
|
+++ b/libxfs/xfs_refcount.c
|
|
@@ -819,6 +819,17 @@ xfs_refc_valid(
|
|
return rc->rc_startblock != NULLAGBLOCK;
|
|
}
|
|
|
|
+static inline xfs_nlink_t
|
|
+xfs_refc_merge_refcount(
|
|
+ const struct xfs_refcount_irec *irec,
|
|
+ enum xfs_refc_adjust_op adjust)
|
|
+{
|
|
+ /* Once a record hits MAXREFCOUNT, it is pinned there forever */
|
|
+ if (irec->rc_refcount == MAXREFCOUNT)
|
|
+ return MAXREFCOUNT;
|
|
+ return irec->rc_refcount + adjust;
|
|
+}
|
|
+
|
|
static inline bool
|
|
xfs_refc_want_merge_center(
|
|
const struct xfs_refcount_irec *left,
|
|
@@ -830,6 +841,7 @@ xfs_refc_want_merge_center(
|
|
unsigned long long *ulenp)
|
|
{
|
|
unsigned long long ulen = left->rc_blockcount;
|
|
+ xfs_nlink_t new_refcount;
|
|
|
|
/*
|
|
* To merge with a center record, both shoulder records must be
|
|
@@ -845,9 +857,10 @@ xfs_refc_want_merge_center(
|
|
return false;
|
|
|
|
/* The shoulder record refcounts must match the new refcount. */
|
|
- if (left->rc_refcount != cleft->rc_refcount + adjust)
|
|
+ new_refcount = xfs_refc_merge_refcount(cleft, adjust);
|
|
+ if (left->rc_refcount != new_refcount)
|
|
return false;
|
|
- if (right->rc_refcount != cleft->rc_refcount + adjust)
|
|
+ if (right->rc_refcount != new_refcount)
|
|
return false;
|
|
|
|
/*
|
|
@@ -870,6 +883,7 @@ xfs_refc_want_merge_left(
|
|
enum xfs_refc_adjust_op adjust)
|
|
{
|
|
unsigned long long ulen = left->rc_blockcount;
|
|
+ xfs_nlink_t new_refcount;
|
|
|
|
/*
|
|
* For a left merge, the left shoulder record must be adjacent to the
|
|
@@ -880,7 +894,8 @@ xfs_refc_want_merge_left(
|
|
return false;
|
|
|
|
/* Left shoulder record refcount must match the new refcount. */
|
|
- if (left->rc_refcount != cleft->rc_refcount + adjust)
|
|
+ new_refcount = xfs_refc_merge_refcount(cleft, adjust);
|
|
+ if (left->rc_refcount != new_refcount)
|
|
return false;
|
|
|
|
/*
|
|
@@ -902,6 +917,7 @@ xfs_refc_want_merge_right(
|
|
enum xfs_refc_adjust_op adjust)
|
|
{
|
|
unsigned long long ulen = right->rc_blockcount;
|
|
+ xfs_nlink_t new_refcount;
|
|
|
|
/*
|
|
* For a right merge, the right shoulder record must be adjacent to the
|
|
@@ -912,7 +928,8 @@ xfs_refc_want_merge_right(
|
|
return false;
|
|
|
|
/* Right shoulder record refcount must match the new refcount. */
|
|
- if (right->rc_refcount != cright->rc_refcount + adjust)
|
|
+ new_refcount = xfs_refc_merge_refcount(cright, adjust);
|
|
+ if (right->rc_refcount != new_refcount)
|
|
return false;
|
|
|
|
/*
|
|
--
|
|
2.40.0
|
|
|