linux/fs/xfs/libxfs
Dave Chinner 3f8a4f1d87 xfs: fix inode fork extent count overflow
[commit message is verbose for discussion purposes - will trim it
down later. Some questions about implementation details at the end.]

Zorro Lang recently ran a new test to stress single inode extent
counts now that they are no longer limited by memory allocation.
The test was simply:

# xfs_io -f -c "falloc 0 40t" /mnt/scratch/big-file
# ~/src/xfstests-dev/punch-alternating /mnt/scratch/big-file

This test uncovered a problem where the hole punching operation
appeared to finish with no error, but apparently only created 268M
extents instead of the 10 billion it was supposed to.

Further, trying to punch out extents that should have been present
resulted in success, but no change in the extent count. It looked
like a silent failure.

While running the test and observing the behaviour in real time,
I observed the extent coutn growing at ~2M extents/minute, and saw
this after about an hour:

# xfs_io -f -c "stat" /mnt/scratch/big-file |grep next ; \
> sleep 60 ; \
> xfs_io -f -c "stat" /mnt/scratch/big-file |grep next
fsxattr.nextents = 127657993
fsxattr.nextents = 129683339
#

And a few minutes later this:

# xfs_io -f -c "stat" /mnt/scratch/big-file |grep next
fsxattr.nextents = 4177861124
#

Ah, what? Where did that 4 billion extra extents suddenly come from?

Stop the workload, unmount, mount:

# xfs_io -f -c "stat" /mnt/scratch/big-file |grep next
fsxattr.nextents = 166044375
#

And it's back at the expected number. i.e. the extent count is
correct on disk, but it's screwed up in memory. I loaded up the
extent list, and immediately:

# xfs_io -f -c "stat" /mnt/scratch/big-file |grep next
fsxattr.nextents = 4192576215
#

It's bad again. So, where does that number come from?
xfs_fill_fsxattr():

                if (ip->i_df.if_flags & XFS_IFEXTENTS)
                        fa->fsx_nextents = xfs_iext_count(&ip->i_df);
                else
                        fa->fsx_nextents = ip->i_d.di_nextents;

And that's the behaviour I just saw in a nutshell. The on disk count
is correct, but once the tree is loaded into memory, it goes whacky.
Clearly there's something wrong with xfs_iext_count():

inline xfs_extnum_t xfs_iext_count(struct xfs_ifork *ifp)
{
        return ifp->if_bytes / sizeof(struct xfs_iext_rec);
}

Simple enough, but 134M extents is 2**27, and that's right about
where things went wrong. A struct xfs_iext_rec is 16 bytes in size,
which means 2**27 * 2**4 = 2**31 and we're right on target for an
integer overflow. And, sure enough:

struct xfs_ifork {
        int                     if_bytes;       /* bytes in if_u1 */
....

Once we get 2**27 extents in a file, we overflow if_bytes and the
in-core extent count goes wrong. And when we reach 2**28 extents,
if_bytes wraps back to zero and things really start to go wrong
there. This is where the silent failure comes from - only the first
2**28 extents can be looked up directly due to the overflow, all the
extents above this index wrap back to somewhere in the first 2**28
extents. Hence with a regular pattern, trying to punch a hole in the
range that didn't have holes mapped to a hole in the first 2**28
extents and so "succeeded" without changing anything. Hence "silent
failure"...

Fix this by converting if_bytes to a int64_t and converting all the
index variables and size calculations to use int64_t types to avoid
overflows in future. Signed integers are still used to enable easy
detection of extent count underflows. This enables scalability of
extent counts to the limits of the on-disk format - MAXEXTNUM
(2**31) extents.

Current testing is at over 500M extents and still going:

fsxattr.nextents = 517310478

Reported-by: Zorro Lang <zlang@redhat.com>
Signed-off-by: Dave Chinner <dchinner@redhat.com>
Reviewed-by: Darrick J. Wong <darrick.wong@oracle.com>
Signed-off-by: Darrick J. Wong <darrick.wong@oracle.com>
2019-10-21 09:04:58 -07:00
..
xfs_ag_resv.c xfs: remove unused header files 2019-06-28 19:30:43 -07:00
xfs_ag_resv.h xfs: pass transaction lock while setting up agresv on cyclic metadata 2018-07-29 22:37:08 -07:00
xfs_ag.c xfs: remove unused flags arg from xfs_get_aghdr_buf() 2019-10-06 15:39:05 -07:00
xfs_ag.h xfs: add a new ioctl to describe allocation group geometry 2019-04-14 18:15:57 -07:00
xfs_alloc_btree.c xfs: track active state of allocation btree cursors 2019-10-21 09:04:58 -07:00
xfs_alloc_btree.h xfs: convert to SPDX license tags 2018-06-06 14:17:53 -07:00
xfs_alloc.c xfs: optimize near mode bnobt scans with concurrent cntbt lookups 2019-10-21 09:04:58 -07:00
xfs_alloc.h xfs: revert 1baa2800e6 ("xfs: remove the unused XFS_ALLOC_USERDATA flag") 2019-09-23 13:05:00 -07:00
xfs_attr_leaf.c xfs: fix inode fork extent count overflow 2019-10-21 09:04:58 -07:00
xfs_attr_leaf.h xfs: convert to SPDX license tags 2018-06-06 14:17:53 -07:00
xfs_attr_remote.c xfs: make attr lookup returns consistent 2019-08-30 22:43:57 -07:00
xfs_attr_remote.h xfs: convert to SPDX license tags 2018-06-06 14:17:53 -07:00
xfs_attr_sf.h xfs: convert to SPDX license tags 2018-06-06 14:17:53 -07:00
xfs_attr.c xfs: allocate xattr buffer on demand 2019-08-30 22:43:57 -07:00
xfs_attr.h xfs: allocate xattr buffer on demand 2019-08-30 22:43:57 -07:00
xfs_bit.c xfs: remove unused header files 2019-06-28 19:30:43 -07:00
xfs_bit.h xfs: convert to SPDX license tags 2018-06-06 14:17:53 -07:00
xfs_bmap_btree.c xfs: fix sign handling problem in xfs_bmbt_diff_two_keys 2019-08-28 08:31:01 -07:00
xfs_bmap_btree.h xfs: convert to SPDX license tags 2018-06-06 14:17:53 -07:00
xfs_bmap.c xfs: use a struct iomap in xfs_writepage_ctx 2019-10-21 08:51:59 -07:00
xfs_bmap.h xfs: use a struct iomap in xfs_writepage_ctx 2019-10-21 08:51:59 -07:00
xfs_btree.c xfs: remove all *_ITER_ABORT values 2019-08-29 21:22:41 -07:00
xfs_btree.h xfs: track active state of allocation btree cursors 2019-10-21 09:04:58 -07:00
xfs_cksum.h License cleanup: add SPDX GPL-2.0 license identifier to files with no license 2017-11-02 11:10:55 +01:00
xfs_da_btree.c fs: xfs: Remove KM_NOSLEEP and KM_SLEEP. 2019-08-26 12:06:22 -07:00
xfs_da_btree.h xfs: allocate xattr buffer on demand 2019-08-30 22:43:57 -07:00
xfs_da_format.c xfs: remove unused header files 2019-06-28 19:30:43 -07:00
xfs_da_format.h xfs: factor xfs_da3_blkinfo verification into common helper 2019-02-11 16:07:01 -08:00
xfs_defer.c fs: xfs: Remove KM_NOSLEEP and KM_SLEEP. 2019-08-26 12:06:22 -07:00
xfs_defer.h xfs: streamline defer op type handling 2018-12-12 08:47:16 -08:00
xfs_dir2_block.c xfs: move local to extent inode logging into bmap helper 2019-10-09 08:54:30 -07:00
xfs_dir2_data.c xfs: remove unused header files 2019-06-28 19:30:43 -07:00
xfs_dir2_leaf.c xfs: remove unused header files 2019-06-28 19:30:43 -07:00
xfs_dir2_node.c xfs: reverse search directory freespace indexes 2019-08-30 22:43:57 -07:00
xfs_dir2_priv.h xfs: convert to SPDX license tags 2018-06-06 14:17:53 -07:00
xfs_dir2_sf.c xfs: fix inode fork extent count overflow 2019-10-21 09:04:58 -07:00
xfs_dir2.c fs: xfs: Remove KM_NOSLEEP and KM_SLEEP. 2019-08-26 12:06:22 -07:00
xfs_dir2.h xfs: check directory name validity 2019-02-11 16:06:40 -08:00
xfs_dquot_buf.c xfs: remove unused header files 2019-06-28 19:30:43 -07:00
xfs_errortag.h xfs: cache unlinked pointers in an rhashtable 2019-02-11 16:07:01 -08:00
xfs_format.h xfs: move xfs_ino_geometry to xfs_shared.h 2019-06-28 19:25:35 -07:00
xfs_fs.h xfs: define a flags field for the AG geometry ioctl structure 2019-09-03 21:07:25 -07:00
xfs_health.h xfs: introduce new v5 bulkstat structure 2019-07-03 20:36:26 -07:00
xfs_ialloc_btree.c xfs: create simplified inode walk function 2019-07-02 09:40:05 -07:00
xfs_ialloc_btree.h xfs: create simplified inode walk function 2019-07-02 09:40:05 -07:00
xfs_ialloc.c xfs: fix maxicount division by zero error 2019-08-28 08:31:01 -07:00
xfs_ialloc.h xfs: refactor inode geometry setup routines 2019-06-12 08:37:40 -07:00
xfs_iext_tree.c xfs: fix inode fork extent count overflow 2019-10-21 09:04:58 -07:00
xfs_inode_buf.c xfs: remove unused header files 2019-06-28 19:30:43 -07:00
xfs_inode_buf.h xfs: convert to SPDX license tags 2018-06-06 14:17:53 -07:00
xfs_inode_fork.c xfs: fix inode fork extent count overflow 2019-10-21 09:04:58 -07:00
xfs_inode_fork.h xfs: fix inode fork extent count overflow 2019-10-21 09:04:58 -07:00
xfs_log_format.h xfs: refactor unmount record write 2018-07-23 09:08:01 -07:00
xfs_log_recover.h xfs: convert to SPDX license tags 2018-06-06 14:17:53 -07:00
xfs_log_rlimit.c xfs: remove unused header files 2019-06-28 19:30:43 -07:00
xfs_quota_defs.h xfs: change some error-less functions to void types 2019-05-01 20:26:30 -07:00
xfs_refcount_btree.c xfs: remove unused header files 2019-06-28 19:30:43 -07:00
xfs_refcount_btree.h xfs: pass transaction lock while setting up agresv on cyclic metadata 2018-07-29 22:37:08 -07:00
xfs_refcount.c xfs: remove unnecessary int returns from deferred refcount functions 2019-08-28 08:31:02 -07:00
xfs_refcount.h xfs: remove unnecessary int returns from deferred refcount functions 2019-08-28 08:31:02 -07:00
xfs_rmap_btree.c xfs: remove unused header files 2019-06-28 19:30:43 -07:00
xfs_rmap_btree.h xfs: pass transaction lock while setting up agresv on cyclic metadata 2018-07-29 22:37:08 -07:00
xfs_rmap.c xfs: remove all *_ITER_CONTINUE values 2019-08-30 22:43:56 -07:00
xfs_rmap.h xfs: reinitialize rm_flags when unpacking an offset into an rmap irec 2019-08-28 08:31:02 -07:00
xfs_rtbitmap.c xfs: remove unused header files 2019-06-28 19:30:43 -07:00
xfs_sb.c xfs: log proper length of superblock 2019-09-24 08:00:36 -07:00
xfs_sb.h xfs: change some error-less functions to void types 2019-05-01 20:26:30 -07:00
xfs_shared.h xfs: remove all *_ITER_CONTINUE values 2019-08-30 22:43:56 -07:00
xfs_symlink_remote.c xfs: remove unused header files 2019-06-28 19:30:43 -07:00
xfs_trans_inode.c xfs: sync up xfs_trans_inode with userspace 2019-07-15 08:10:34 -07:00
xfs_trans_resv.c xfs: remove unused header files 2019-06-28 19:30:43 -07:00
xfs_trans_resv.h xfs: convert to SPDX license tags 2018-06-06 14:17:53 -07:00
xfs_trans_space.h xfs: separate inode geometry 2019-06-12 08:37:40 -07:00
xfs_types.c xfs: remove unused header files 2019-06-28 19:30:43 -07:00
xfs_types.h xfs: add kmem allocation trace points 2019-08-26 17:43:14 -07:00