Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

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: 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>

+21 -4
+21 -4
fs/xfs/libxfs/xfs_refcount.c
··· 820 820 return rc->rc_startblock != NULLAGBLOCK; 821 821 } 822 822 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 + 823 834 static inline bool 824 835 xfs_refc_want_merge_center( 825 836 const struct xfs_refcount_irec *left, ··· 842 831 unsigned long long *ulenp) 843 832 { 844 833 unsigned long long ulen = left->rc_blockcount; 834 + xfs_nlink_t new_refcount; 845 835 846 836 /* 847 837 * To merge with a center record, both shoulder records must be ··· 858 846 return false; 859 847 860 848 /* The shoulder record refcounts must match the new refcount. */ 861 - if (left->rc_refcount != cleft->rc_refcount + adjust) 849 + new_refcount = xfs_refc_merge_refcount(cleft, adjust); 850 + if (left->rc_refcount != new_refcount) 862 851 return false; 863 - if (right->rc_refcount != cleft->rc_refcount + adjust) 852 + if (right->rc_refcount != new_refcount) 864 853 return false; 865 854 866 855 /* ··· 884 871 enum xfs_refc_adjust_op adjust) 885 872 { 886 873 unsigned long long ulen = left->rc_blockcount; 874 + xfs_nlink_t new_refcount; 887 875 888 876 /* 889 877 * For a left merge, the left shoulder record must be adjacent to the ··· 895 881 return false; 896 882 897 883 /* Left shoulder record refcount must match the new refcount. */ 898 - if (left->rc_refcount != cleft->rc_refcount + adjust) 884 + new_refcount = xfs_refc_merge_refcount(cleft, adjust); 885 + if (left->rc_refcount != new_refcount) 899 886 return false; 900 887 901 888 /* ··· 918 903 enum xfs_refc_adjust_op adjust) 919 904 { 920 905 unsigned long long ulen = right->rc_blockcount; 906 + xfs_nlink_t new_refcount; 921 907 922 908 /* 923 909 * For a right merge, the right shoulder record must be adjacent to the ··· 929 913 return false; 930 914 931 915 /* Right shoulder record refcount must match the new refcount. */ 932 - if (right->rc_refcount != cright->rc_refcount + adjust) 916 + new_refcount = xfs_refc_merge_refcount(cright, adjust); 917 + if (right->rc_refcount != new_refcount) 933 918 return false; 934 919 935 920 /*