Skip to content

Commit 50ec555

Browse files
author
Herton R. Krzesinski
committed
Merge: xfs: estimate post-merge refcounts correctly
MR: https://gitlab.com/redhat/centos-stream/src/kernel/centos-stream-9/-/merge_requests/1927 xfs: estimate post-merge refcounts correctly Bugzilla: https://bugzilla.redhat.com/show_bug.cgi?id=2161676 xfs/179 fails after it was recently modified for reproducing a bug regarding refcount overflowing: https://git.kernel.org/pub/scm/fs/xfs/xfstests-dev.git/commit/?h=for-next&id=56c8f841407c29808d370d06929a3f58c7afeee3 fixed by: 9d720a5: xfs: hoist refcount record merge predicates b25d198: xfs: estimate post-merge refcounts correctly commit b25d198 Author: Darrick J. Wong <djwong@kernel.org> Date: Wed Nov 30 09:25:51 2022 -0800 xfs: estimate post-merge refcounts correctly 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: 3172725 ("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: Bill O'Donnell <bodonnel@redhat.com> Approved-by: Brian Foster <bfoster@redhat.com> Approved-by: Eric Sandeen <esandeen@redhat.com> Signed-off-by: Herton R. Krzesinski <herton@redhat.com>
2 parents 90624f5 + 8bc54d3 commit 50ec555

File tree

1 file changed

+130
-16
lines changed

1 file changed

+130
-16
lines changed

fs/xfs/libxfs/xfs_refcount.c

Lines changed: 130 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -784,11 +784,136 @@ xfs_refcount_find_right_extents(
784784
/* Is this extent valid? */
785785
static inline bool
786786
xfs_refc_valid(
787-
struct xfs_refcount_irec *rc)
787+
const struct xfs_refcount_irec *rc)
788788
{
789789
return rc->rc_startblock != NULLAGBLOCK;
790790
}
791791

792+
static inline xfs_nlink_t
793+
xfs_refc_merge_refcount(
794+
const struct xfs_refcount_irec *irec,
795+
enum xfs_refc_adjust_op adjust)
796+
{
797+
/* Once a record hits MAXREFCOUNT, it is pinned there forever */
798+
if (irec->rc_refcount == MAXREFCOUNT)
799+
return MAXREFCOUNT;
800+
return irec->rc_refcount + adjust;
801+
}
802+
803+
static inline bool
804+
xfs_refc_want_merge_center(
805+
const struct xfs_refcount_irec *left,
806+
const struct xfs_refcount_irec *cleft,
807+
const struct xfs_refcount_irec *cright,
808+
const struct xfs_refcount_irec *right,
809+
bool cleft_is_cright,
810+
enum xfs_refc_adjust_op adjust,
811+
unsigned long long *ulenp)
812+
{
813+
unsigned long long ulen = left->rc_blockcount;
814+
xfs_nlink_t new_refcount;
815+
816+
/*
817+
* To merge with a center record, both shoulder records must be
818+
* adjacent to the record we want to adjust. This is only true if
819+
* find_left and find_right made all four records valid.
820+
*/
821+
if (!xfs_refc_valid(left) || !xfs_refc_valid(right) ||
822+
!xfs_refc_valid(cleft) || !xfs_refc_valid(cright))
823+
return false;
824+
825+
/* There must only be one record for the entire range. */
826+
if (!cleft_is_cright)
827+
return false;
828+
829+
/* The shoulder record refcounts must match the new refcount. */
830+
new_refcount = xfs_refc_merge_refcount(cleft, adjust);
831+
if (left->rc_refcount != new_refcount)
832+
return false;
833+
if (right->rc_refcount != new_refcount)
834+
return false;
835+
836+
/*
837+
* The new record cannot exceed the max length. ulen is a ULL as the
838+
* individual record block counts can be up to (u32 - 1) in length
839+
* hence we need to catch u32 addition overflows here.
840+
*/
841+
ulen += cleft->rc_blockcount + right->rc_blockcount;
842+
if (ulen >= MAXREFCEXTLEN)
843+
return false;
844+
845+
*ulenp = ulen;
846+
return true;
847+
}
848+
849+
static inline bool
850+
xfs_refc_want_merge_left(
851+
const struct xfs_refcount_irec *left,
852+
const struct xfs_refcount_irec *cleft,
853+
enum xfs_refc_adjust_op adjust)
854+
{
855+
unsigned long long ulen = left->rc_blockcount;
856+
xfs_nlink_t new_refcount;
857+
858+
/*
859+
* For a left merge, the left shoulder record must be adjacent to the
860+
* start of the range. If this is true, find_left made left and cleft
861+
* contain valid contents.
862+
*/
863+
if (!xfs_refc_valid(left) || !xfs_refc_valid(cleft))
864+
return false;
865+
866+
/* Left shoulder record refcount must match the new refcount. */
867+
new_refcount = xfs_refc_merge_refcount(cleft, adjust);
868+
if (left->rc_refcount != new_refcount)
869+
return false;
870+
871+
/*
872+
* The new record cannot exceed the max length. ulen is a ULL as the
873+
* individual record block counts can be up to (u32 - 1) in length
874+
* hence we need to catch u32 addition overflows here.
875+
*/
876+
ulen += cleft->rc_blockcount;
877+
if (ulen >= MAXREFCEXTLEN)
878+
return false;
879+
880+
return true;
881+
}
882+
883+
static inline bool
884+
xfs_refc_want_merge_right(
885+
const struct xfs_refcount_irec *cright,
886+
const struct xfs_refcount_irec *right,
887+
enum xfs_refc_adjust_op adjust)
888+
{
889+
unsigned long long ulen = right->rc_blockcount;
890+
xfs_nlink_t new_refcount;
891+
892+
/*
893+
* For a right merge, the right shoulder record must be adjacent to the
894+
* end of the range. If this is true, find_right made cright and right
895+
* contain valid contents.
896+
*/
897+
if (!xfs_refc_valid(right) || !xfs_refc_valid(cright))
898+
return false;
899+
900+
/* Right shoulder record refcount must match the new refcount. */
901+
new_refcount = xfs_refc_merge_refcount(cright, adjust);
902+
if (right->rc_refcount != new_refcount)
903+
return false;
904+
905+
/*
906+
* The new record cannot exceed the max length. ulen is a ULL as the
907+
* individual record block counts can be up to (u32 - 1) in length
908+
* hence we need to catch u32 addition overflows here.
909+
*/
910+
ulen += cright->rc_blockcount;
911+
if (ulen >= MAXREFCEXTLEN)
912+
return false;
913+
914+
return true;
915+
}
916+
792917
/*
793918
* Try to merge with any extents on the boundaries of the adjustment range.
794919
*/
@@ -830,23 +955,15 @@ xfs_refcount_merge_extents(
830955
(cleft.rc_blockcount == cright.rc_blockcount);
831956

832957
/* Try to merge left, cleft, and right. cleft must == cright. */
833-
ulen = (unsigned long long)left.rc_blockcount + cleft.rc_blockcount +
834-
right.rc_blockcount;
835-
if (xfs_refc_valid(&left) && xfs_refc_valid(&right) &&
836-
xfs_refc_valid(&cleft) && xfs_refc_valid(&cright) && cequal &&
837-
left.rc_refcount == cleft.rc_refcount + adjust &&
838-
right.rc_refcount == cleft.rc_refcount + adjust &&
839-
ulen < MAXREFCEXTLEN) {
958+
if (xfs_refc_want_merge_center(&left, &cleft, &cright, &right, cequal,
959+
adjust, &ulen)) {
840960
*shape_changed = true;
841961
return xfs_refcount_merge_center_extents(cur, &left, &cleft,
842962
&right, ulen, aglen);
843963
}
844964

845965
/* Try to merge left and cleft. */
846-
ulen = (unsigned long long)left.rc_blockcount + cleft.rc_blockcount;
847-
if (xfs_refc_valid(&left) && xfs_refc_valid(&cleft) &&
848-
left.rc_refcount == cleft.rc_refcount + adjust &&
849-
ulen < MAXREFCEXTLEN) {
966+
if (xfs_refc_want_merge_left(&left, &cleft, adjust)) {
850967
*shape_changed = true;
851968
error = xfs_refcount_merge_left_extent(cur, &left, &cleft,
852969
agbno, aglen);
@@ -862,10 +979,7 @@ xfs_refcount_merge_extents(
862979
}
863980

864981
/* Try to merge cright and right. */
865-
ulen = (unsigned long long)right.rc_blockcount + cright.rc_blockcount;
866-
if (xfs_refc_valid(&right) && xfs_refc_valid(&cright) &&
867-
right.rc_refcount == cright.rc_refcount + adjust &&
868-
ulen < MAXREFCEXTLEN) {
982+
if (xfs_refc_want_merge_right(&cright, &right, adjust)) {
869983
*shape_changed = true;
870984
return xfs_refcount_merge_right_extent(cur, &right, &cright,
871985
aglen);

0 commit comments

Comments
 (0)