mirror of
https://github.com/torvalds/linux.git
synced 2024-11-11 06:31:49 +00:00
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:
commit
948961964b
@ -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);
|
||||||
|
Loading…
Reference in New Issue
Block a user