[PATCH RESEND 1/2] xfs: Add helper for checking per-inode extent count overflow

Chandan Babu R
2020-08-01T08:28:32Z

XFS does not check for possible overflow of per-inode extent counter
fields when adding extents to either data or attr fork.

For e.g.
1. Insert 5 million xattrs (each having a value size of 255 bytes) and
then delete 50% of them in an alternating manner.

2. On a 4k block sized XFS filesystem instance, the above causes 98511
extents to be created in the attr fork of the inode.

xfsaild/loop0 2035 [003] 9643.390490: probe:xfs_iflush_int: (ffffffffac6225c0) if_nextents=98511 inode=131

3. The incore inode fork extent counter is a signed 32-bit
quantity. However the on-disk extent counter is an unsigned 16-bit
quantity and hence cannot hold 98511 extents.

4. The following incorrect value is stored in the attr extent counter,
# xfs_db -f -c 'inode 131' -c 'print core.naextents' /dev/loop0
core.naextents = -32561

This commit adds a new helper function (i.e. xfs_trans_resv_ext_cnt())
to check for overflow of the per-inode data and xattr extent counters. A
future patch will use this function to make sure that a transaction will
not cause the extent counter to overflow.

Suggested-by: Darrick J. Wong <darrick.wong@oracle.com>
Signed-off-by: Chandan Babu R <chandanrlinux@gmail.com>
---
fs/xfs/libxfs/xfs_trans_resv.c | 33 +++++++++++++++++++++++++++++++++
fs/xfs/libxfs/xfs_trans_resv.h | 1 +
2 files changed, 34 insertions(+)

diff --git a/fs/xfs/libxfs/xfs_trans_resv.c b/fs/xfs/libxfs/xfs_trans_resv.c
index d1a0848cb52e..68d9833d403d 100644
--- a/fs/xfs/libxfs/xfs_trans_resv.c
+++ b/fs/xfs/libxfs/xfs_trans_resv.c
@@ -832,6 +832,39 @@ xfs_calc_sb_reservation(
return xfs_calc_buf_res(1, mp->m_sb.sb_sectsize);
}

+int
+xfs_trans_resv_ext_cnt(
+ struct xfs_inode *ip,
+ int whichfork,
+ int nr_to_add)
+{
+ struct xfs_ifork *ifp;
+ uint64_t max_exts = 0;
+ uint64_t nr_exts;
+
+ switch (whichfork) {
+ case XFS_DATA_FORK:
+ max_exts = MAXEXTNUM;
+ break;
+
+ case XFS_ATTR_FORK:
+ max_exts = MAXAEXTNUM;
+ break;
+
+ default:
+ ASSERT(0);
+ break;
+ }
+
+ ifp = XFS_IFORK_PTR(ip, whichfork);
+ nr_exts = ifp->if_nextents + nr_to_add;
+
+ if (nr_exts > max_exts)
+ return -EFBIG;
+
+ return 0;
+}
+
void
xfs_trans_resv_calc(
struct xfs_mount *mp,
diff --git a/fs/xfs/libxfs/xfs_trans_resv.h b/fs/xfs/libxfs/xfs_trans_resv.h
index 7241ab28cf84..8268a89caa16 100644
--- a/fs/xfs/libxfs/xfs_trans_resv.h
+++ b/fs/xfs/libxfs/xfs_trans_resv.h
@@ -93,5 +93,6 @@ struct xfs_trans_resv {

void xfs_trans_resv_calc(struct xfs_mount *mp, struct xfs_trans_resv *resp);
uint xfs_allocfree_log_count(struct xfs_mount *mp, uint num_ops);
+int xfs_trans_resv_ext_cnt(struct xfs_inode *ip, int whichfork, int nr_exts);

#endif /* __XFS_TRANS_RESV_H__ */
--
2.20.1