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 <djwong@kernel.org>
 -----BEGIN PGP SIGNATURE-----
 
 iQIzBAABCgAdFiEEUzaAxoMeQq6m2jMV+H93GTRKtOsFAmOI5VUACgkQ+H93GTRK
 tOsf+A/9GsOyXhF2dAPt2zvDpoiHvR/y42/LQUoKah2cxUXOhfPIu53xoRgNgNOh
 1O3D1s9HjudI1Bo6/R+xeijq/upKGmKmOJL52vULy8UqsbDLvV8RHQ5YfHbIIghW
 HiYsD3aX5qhTKr7KHMrJzZcokSTAIGgIAT3QK7X0BrWUSXK12BN53A5D3XSFEMPu
 K1rws+XEZkOlgNti1DqTrRa6OdU+Y0CKOobNciNGTEBi6Gg4QFaudoobrBAYGNpQ
 R2icLAJG2lxlv3VRr2GqfgZ4XFh+q8TmXfb0tF+/rS/XWeDoElci+t4zpyDyeetH
 H4IdfXuiJMw9UUj8h7N299qRYlUFhfqfwMs/9FBU+W8UTV9ke6796r+/3ARcUino
 GoHwNpXEQZHatU5zVj1v2/6yVOCLapoz1WHa3pkbvwBGPKvWS2DGs0Mx5W28b/QE
 M/uRj/CL4re0lEqsnNHu0J2tMiLb5A33pkdAZTfsoOwXdjpZdiXCuUHJzD71UDb8
 hePXm5c9XBSSLIV44bpgBCDqu7acybENRUXJ2+1kBebxaKEpH8nrPKIuHRzX2Ulh
 KfCZwwMLR24AYYHcFRsb8p4QtBaSu0jF4WJQJoggSsHErQfDdNDfTr5nL86kF+3P
 zp9/8WOi4ZfRGAMOS/kTjZRFnG+hDE+EqvAzuNyf9vlytF84KZU=
 =qgYH
 -----END PGP SIGNATURE-----

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 <djwong@kernel.org>

* 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
This commit is contained in:
Darrick J. Wong 2022-12-01 09:35:52 -08:00
commit 948961964b

View File

@ -815,11 +815,136 @@ out_error:
/* Is this extent valid? */ /* Is this extent valid? */
static inline bool static inline bool
xfs_refc_valid( xfs_refc_valid(
struct xfs_refcount_irec *rc) const struct xfs_refcount_irec *rc)
{ {
return rc->rc_startblock != NULLAGBLOCK; 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,
const struct xfs_refcount_irec *cleft,
const struct xfs_refcount_irec *cright,
const struct xfs_refcount_irec *right,
bool cleft_is_cright,
enum xfs_refc_adjust_op adjust,
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
* adjacent to the record we want to adjust. This is only true if
* find_left and find_right made all four records valid.
*/
if (!xfs_refc_valid(left) || !xfs_refc_valid(right) ||
!xfs_refc_valid(cleft) || !xfs_refc_valid(cright))
return false;
/* There must only be one record for the entire range. */
if (!cleft_is_cright)
return false;
/* The shoulder record refcounts must match the new refcount. */
new_refcount = xfs_refc_merge_refcount(cleft, adjust);
if (left->rc_refcount != new_refcount)
return false;
if (right->rc_refcount != new_refcount)
return false;
/*
* The new record cannot exceed the max length. ulen is a ULL as the
* individual record block counts can be up to (u32 - 1) in length
* hence we need to catch u32 addition overflows here.
*/
ulen += cleft->rc_blockcount + right->rc_blockcount;
if (ulen >= MAXREFCEXTLEN)
return false;
*ulenp = ulen;
return true;
}
static inline bool
xfs_refc_want_merge_left(
const struct xfs_refcount_irec *left,
const struct xfs_refcount_irec *cleft,
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
* start of the range. If this is true, find_left made left and cleft
* contain valid contents.
*/
if (!xfs_refc_valid(left) || !xfs_refc_valid(cleft))
return false;
/* Left shoulder record refcount must match the new refcount. */
new_refcount = xfs_refc_merge_refcount(cleft, adjust);
if (left->rc_refcount != new_refcount)
return false;
/*
* The new record cannot exceed the max length. ulen is a ULL as the
* individual record block counts can be up to (u32 - 1) in length
* hence we need to catch u32 addition overflows here.
*/
ulen += cleft->rc_blockcount;
if (ulen >= MAXREFCEXTLEN)
return false;
return true;
}
static inline bool
xfs_refc_want_merge_right(
const struct xfs_refcount_irec *cright,
const struct xfs_refcount_irec *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
* end of the range. If this is true, find_right made cright and right
* contain valid contents.
*/
if (!xfs_refc_valid(right) || !xfs_refc_valid(cright))
return false;
/* Right shoulder record refcount must match the new refcount. */
new_refcount = xfs_refc_merge_refcount(cright, adjust);
if (right->rc_refcount != new_refcount)
return false;
/*
* The new record cannot exceed the max length. ulen is a ULL as the
* individual record block counts can be up to (u32 - 1) in length
* hence we need to catch u32 addition overflows here.
*/
ulen += cright->rc_blockcount;
if (ulen >= MAXREFCEXTLEN)
return false;
return true;
}
/* /*
* Try to merge with any extents on the boundaries of the adjustment range. * Try to merge with any extents on the boundaries of the adjustment range.
*/ */
@ -861,23 +986,15 @@ xfs_refcount_merge_extents(
(cleft.rc_blockcount == cright.rc_blockcount); (cleft.rc_blockcount == cright.rc_blockcount);
/* Try to merge left, cleft, and right. cleft must == cright. */ /* Try to merge left, cleft, and right. cleft must == cright. */
ulen = (unsigned long long)left.rc_blockcount + cleft.rc_blockcount + if (xfs_refc_want_merge_center(&left, &cleft, &cright, &right, cequal,
right.rc_blockcount; adjust, &ulen)) {
if (xfs_refc_valid(&left) && xfs_refc_valid(&right) &&
xfs_refc_valid(&cleft) && xfs_refc_valid(&cright) && cequal &&
left.rc_refcount == cleft.rc_refcount + adjust &&
right.rc_refcount == cleft.rc_refcount + adjust &&
ulen < MAXREFCEXTLEN) {
*shape_changed = true; *shape_changed = true;
return xfs_refcount_merge_center_extents(cur, &left, &cleft, return xfs_refcount_merge_center_extents(cur, &left, &cleft,
&right, ulen, aglen); &right, ulen, aglen);
} }
/* Try to merge left and cleft. */ /* Try to merge left and cleft. */
ulen = (unsigned long long)left.rc_blockcount + cleft.rc_blockcount; if (xfs_refc_want_merge_left(&left, &cleft, adjust)) {
if (xfs_refc_valid(&left) && xfs_refc_valid(&cleft) &&
left.rc_refcount == cleft.rc_refcount + adjust &&
ulen < MAXREFCEXTLEN) {
*shape_changed = true; *shape_changed = true;
error = xfs_refcount_merge_left_extent(cur, &left, &cleft, error = xfs_refcount_merge_left_extent(cur, &left, &cleft,
agbno, aglen); agbno, aglen);
@ -893,10 +1010,7 @@ xfs_refcount_merge_extents(
} }
/* Try to merge cright and right. */ /* Try to merge cright and right. */
ulen = (unsigned long long)right.rc_blockcount + cright.rc_blockcount; if (xfs_refc_want_merge_right(&cright, &right, adjust)) {
if (xfs_refc_valid(&right) && xfs_refc_valid(&cright) &&
right.rc_refcount == cright.rc_refcount + adjust &&
ulen < MAXREFCEXTLEN) {
*shape_changed = true; *shape_changed = true;
return xfs_refcount_merge_right_extent(cur, &right, &cright, return xfs_refcount_merge_right_extent(cur, &right, &cright,
aglen); aglen);