Skip to content

Commit 9489619

Browse files
author
Darrick J. Wong
committed
Merge tag 'maxrefcount-fixes-6.2_2022-12-01' of git://git.kernel.org/pub/scm/linux/kernel/git/djwong/xfs-linux into xfs-6.2-mergeD
xfs: fix broken MAXREFCOUNT handling This series fixes a bug in the refcount code where we don't merge records correctly if the refcount is hovering around MAXREFCOUNT. This fixes regressions in xfs/179 when fsdax is enabled. xfs/179 itself will be modified to exploit the bug through the pagecache path. Signed-off-by: Darrick J. Wong <[email protected]> * tag 'maxrefcount-fixes-6.2_2022-12-01' of git://git.kernel.org/pub/scm/linux/kernel/git/djwong/xfs-linux: xfs: estimate post-merge refcounts correctly xfs: hoist refcount record merge predicates
2 parents 575689f + b25d198 commit 9489619

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
@@ -815,11 +815,136 @@ xfs_refcount_find_right_extents(
815815
/* Is this extent valid? */
816816
static inline bool
817817
xfs_refc_valid(
818-
struct xfs_refcount_irec *rc)
818+
const struct xfs_refcount_irec *rc)
819819
{
820820
return rc->rc_startblock != NULLAGBLOCK;
821821
}
822822

823+
static inline xfs_nlink_t
824+
xfs_refc_merge_refcount(
825+
const struct xfs_refcount_irec *irec,
826+
enum xfs_refc_adjust_op adjust)
827+
{
828+
/* Once a record hits MAXREFCOUNT, it is pinned there forever */
829+
if (irec->rc_refcount == MAXREFCOUNT)
830+
return MAXREFCOUNT;
831+
return irec->rc_refcount + adjust;
832+
}
833+
834+
static inline bool
835+
xfs_refc_want_merge_center(
836+
const struct xfs_refcount_irec *left,
837+
const struct xfs_refcount_irec *cleft,
838+
const struct xfs_refcount_irec *cright,
839+
const struct xfs_refcount_irec *right,
840+
bool cleft_is_cright,
841+
enum xfs_refc_adjust_op adjust,
842+
unsigned long long *ulenp)
843+
{
844+
unsigned long long ulen = left->rc_blockcount;
845+
xfs_nlink_t new_refcount;
846+
847+
/*
848+
* To merge with a center record, both shoulder records must be
849+
* adjacent to the record we want to adjust. This is only true if
850+
* find_left and find_right made all four records valid.
851+
*/
852+
if (!xfs_refc_valid(left) || !xfs_refc_valid(right) ||
853+
!xfs_refc_valid(cleft) || !xfs_refc_valid(cright))
854+
return false;
855+
856+
/* There must only be one record for the entire range. */
857+
if (!cleft_is_cright)
858+
return false;
859+
860+
/* The shoulder record refcounts must match the new refcount. */
861+
new_refcount = xfs_refc_merge_refcount(cleft, adjust);
862+
if (left->rc_refcount != new_refcount)
863+
return false;
864+
if (right->rc_refcount != new_refcount)
865+
return false;
866+
867+
/*
868+
* The new record cannot exceed the max length. ulen is a ULL as the
869+
* individual record block counts can be up to (u32 - 1) in length
870+
* hence we need to catch u32 addition overflows here.
871+
*/
872+
ulen += cleft->rc_blockcount + right->rc_blockcount;
873+
if (ulen >= MAXREFCEXTLEN)
874+
return false;
875+
876+
*ulenp = ulen;
877+
return true;
878+
}
879+
880+
static inline bool
881+
xfs_refc_want_merge_left(
882+
const struct xfs_refcount_irec *left,
883+
const struct xfs_refcount_irec *cleft,
884+
enum xfs_refc_adjust_op adjust)
885+
{
886+
unsigned long long ulen = left->rc_blockcount;
887+
xfs_nlink_t new_refcount;
888+
889+
/*
890+
* For a left merge, the left shoulder record must be adjacent to the
891+
* start of the range. If this is true, find_left made left and cleft
892+
* contain valid contents.
893+
*/
894+
if (!xfs_refc_valid(left) || !xfs_refc_valid(cleft))
895+
return false;
896+
897+
/* Left shoulder record refcount must match the new refcount. */
898+
new_refcount = xfs_refc_merge_refcount(cleft, adjust);
899+
if (left->rc_refcount != new_refcount)
900+
return false;
901+
902+
/*
903+
* The new record cannot exceed the max length. ulen is a ULL as the
904+
* individual record block counts can be up to (u32 - 1) in length
905+
* hence we need to catch u32 addition overflows here.
906+
*/
907+
ulen += cleft->rc_blockcount;
908+
if (ulen >= MAXREFCEXTLEN)
909+
return false;
910+
911+
return true;
912+
}
913+
914+
static inline bool
915+
xfs_refc_want_merge_right(
916+
const struct xfs_refcount_irec *cright,
917+
const struct xfs_refcount_irec *right,
918+
enum xfs_refc_adjust_op adjust)
919+
{
920+
unsigned long long ulen = right->rc_blockcount;
921+
xfs_nlink_t new_refcount;
922+
923+
/*
924+
* For a right merge, the right shoulder record must be adjacent to the
925+
* end of the range. If this is true, find_right made cright and right
926+
* contain valid contents.
927+
*/
928+
if (!xfs_refc_valid(right) || !xfs_refc_valid(cright))
929+
return false;
930+
931+
/* Right shoulder record refcount must match the new refcount. */
932+
new_refcount = xfs_refc_merge_refcount(cright, adjust);
933+
if (right->rc_refcount != new_refcount)
934+
return false;
935+
936+
/*
937+
* The new record cannot exceed the max length. ulen is a ULL as the
938+
* individual record block counts can be up to (u32 - 1) in length
939+
* hence we need to catch u32 addition overflows here.
940+
*/
941+
ulen += cright->rc_blockcount;
942+
if (ulen >= MAXREFCEXTLEN)
943+
return false;
944+
945+
return true;
946+
}
947+
823948
/*
824949
* Try to merge with any extents on the boundaries of the adjustment range.
825950
*/
@@ -861,23 +986,15 @@ xfs_refcount_merge_extents(
861986
(cleft.rc_blockcount == cright.rc_blockcount);
862987

863988
/* Try to merge left, cleft, and right. cleft must == cright. */
864-
ulen = (unsigned long long)left.rc_blockcount + cleft.rc_blockcount +
865-
right.rc_blockcount;
866-
if (xfs_refc_valid(&left) && xfs_refc_valid(&right) &&
867-
xfs_refc_valid(&cleft) && xfs_refc_valid(&cright) && cequal &&
868-
left.rc_refcount == cleft.rc_refcount + adjust &&
869-
right.rc_refcount == cleft.rc_refcount + adjust &&
870-
ulen < MAXREFCEXTLEN) {
989+
if (xfs_refc_want_merge_center(&left, &cleft, &cright, &right, cequal,
990+
adjust, &ulen)) {
871991
*shape_changed = true;
872992
return xfs_refcount_merge_center_extents(cur, &left, &cleft,
873993
&right, ulen, aglen);
874994
}
875995

876996
/* Try to merge left and cleft. */
877-
ulen = (unsigned long long)left.rc_blockcount + cleft.rc_blockcount;
878-
if (xfs_refc_valid(&left) && xfs_refc_valid(&cleft) &&
879-
left.rc_refcount == cleft.rc_refcount + adjust &&
880-
ulen < MAXREFCEXTLEN) {
997+
if (xfs_refc_want_merge_left(&left, &cleft, adjust)) {
881998
*shape_changed = true;
882999
error = xfs_refcount_merge_left_extent(cur, &left, &cleft,
8831000
agbno, aglen);
@@ -893,10 +1010,7 @@ xfs_refcount_merge_extents(
8931010
}
8941011

8951012
/* Try to merge cright and right. */
896-
ulen = (unsigned long long)right.rc_blockcount + cright.rc_blockcount;
897-
if (xfs_refc_valid(&right) && xfs_refc_valid(&cright) &&
898-
right.rc_refcount == cright.rc_refcount + adjust &&
899-
ulen < MAXREFCEXTLEN) {
1013+
if (xfs_refc_want_merge_right(&cright, &right, adjust)) {
9001014
*shape_changed = true;
9011015
return xfs_refcount_merge_right_extent(cur, &right, &cright,
9021016
aglen);

0 commit comments

Comments
 (0)