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.
113 lines
3.8 KiB
113 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 |
|
|
|
|