Commit 6b69e485 authored by Darrick J. Wong's avatar Darrick J. Wong

xfs: standardize extent size hint validation

While chasing a bug involving invalid extent size hints being propagated
into newly created realtime files, I noticed that the xfs_ioctl_setattr
checks for the extent size hints weren't the same as the ones now
encoded in libxfs and used for validation in repair and mkfs.

Because the checks in libxfs are more stringent than the ones in the
ioctl, it's possible for a live system to set inode flags that
immediately result in corruption warnings.  Specifically, it's possible
to set an extent size hint on an rtinherit directory without checking if
the hint is aligned to the realtime extent size, which makes no sense
since that combination is used only to seed new realtime files.

Replace the open-coded and inadequate checks with the libxfs verifier
versions and update the code comments a bit.
Signed-off-by: default avatarDarrick J. Wong <djwong@kernel.org>
Reviewed-by: default avatarBrian Foster <bfoster@redhat.com>
Reviewed-by: default avatarChristoph Hellwig <hch@lst.de>
parent 0f934251
...@@ -559,8 +559,17 @@ xfs_dinode_calc_crc( ...@@ -559,8 +559,17 @@ xfs_dinode_calc_crc(
/* /*
* Validate di_extsize hint. * Validate di_extsize hint.
* *
* The rules are documented at xfs_ioctl_setattr_check_extsize(). * 1. Extent size hint is only valid for directories and regular files.
* These functions must be kept in sync with each other. * 2. FS_XFLAG_EXTSIZE is only valid for regular files.
* 3. FS_XFLAG_EXTSZINHERIT is only valid for directories.
* 4. Hint cannot be larger than MAXTEXTLEN.
* 5. Can be changed on directories at any time.
* 6. Hint value of 0 turns off hints, clears inode flags.
* 7. Extent size must be a multiple of the appropriate block size.
* For realtime files, this is the rt extent size.
* 8. For non-realtime files, the extent size hint must be limited
* to half the AG size to avoid alignment extending the extent beyond the
* limits of the AG.
*/ */
xfs_failaddr_t xfs_failaddr_t
xfs_inode_validate_extsize( xfs_inode_validate_extsize(
...@@ -616,8 +625,15 @@ xfs_inode_validate_extsize( ...@@ -616,8 +625,15 @@ xfs_inode_validate_extsize(
/* /*
* Validate di_cowextsize hint. * Validate di_cowextsize hint.
* *
* The rules are documented at xfs_ioctl_setattr_check_cowextsize(). * 1. CoW extent size hint can only be set if reflink is enabled on the fs.
* These functions must be kept in sync with each other. * The inode does not have to have any shared blocks, but it must be a v3.
* 2. FS_XFLAG_COWEXTSIZE is only valid for directories and regular files;
* for a directory, the hint is propagated to new files.
* 3. Can be changed on files & directories at any time.
* 4. Hint value of 0 turns off hints, clears inode flags.
* 5. Extent size must be a multiple of the appropriate block size.
* 6. The extent size hint must be limited to half the AG size to avoid
* alignment extending the extent beyond the limits of the AG.
*/ */
xfs_failaddr_t xfs_failaddr_t
xfs_inode_validate_cowextsize( xfs_inode_validate_cowextsize(
......
...@@ -1267,20 +1267,8 @@ xfs_ioctl_setattr_get_trans( ...@@ -1267,20 +1267,8 @@ xfs_ioctl_setattr_get_trans(
} }
/* /*
* extent size hint validation is somewhat cumbersome. Rules are: * Validate a proposed extent size hint. For regular files, the hint can only
* * be changed if no extents are allocated.
* 1. extent size hint is only valid for directories and regular files
* 2. FS_XFLAG_EXTSIZE is only valid for regular files
* 3. FS_XFLAG_EXTSZINHERIT is only valid for directories.
* 4. can only be changed on regular files if no extents are allocated
* 5. can be changed on directories at any time
* 6. extsize hint of 0 turns off hints, clears inode flags.
* 7. Extent size must be a multiple of the appropriate block size.
* 8. for non-realtime files, the extent size hint must be limited
* to half the AG size to avoid alignment extending the extent beyond the
* limits of the AG.
*
* Please keep this function in sync with xfs_scrub_inode_extsize.
*/ */
static int static int
xfs_ioctl_setattr_check_extsize( xfs_ioctl_setattr_check_extsize(
...@@ -1288,86 +1276,50 @@ xfs_ioctl_setattr_check_extsize( ...@@ -1288,86 +1276,50 @@ xfs_ioctl_setattr_check_extsize(
struct fileattr *fa) struct fileattr *fa)
{ {
struct xfs_mount *mp = ip->i_mount; struct xfs_mount *mp = ip->i_mount;
xfs_extlen_t size; xfs_failaddr_t failaddr;
xfs_fsblock_t extsize_fsb; uint16_t new_diflags;
if (!fa->fsx_valid) if (!fa->fsx_valid)
return 0; return 0;
if (S_ISREG(VFS_I(ip)->i_mode) && ip->i_df.if_nextents && if (S_ISREG(VFS_I(ip)->i_mode) && ip->i_df.if_nextents &&
((ip->i_extsize << mp->m_sb.sb_blocklog) != fa->fsx_extsize)) XFS_FSB_TO_B(mp, ip->i_extsize) != fa->fsx_extsize)
return -EINVAL;
if (fa->fsx_extsize == 0)
return 0;
extsize_fsb = XFS_B_TO_FSB(mp, fa->fsx_extsize);
if (extsize_fsb > MAXEXTLEN)
return -EINVAL; return -EINVAL;
if (XFS_IS_REALTIME_INODE(ip) || if (fa->fsx_extsize & mp->m_blockmask)
(fa->fsx_xflags & FS_XFLAG_REALTIME)) {
size = mp->m_sb.sb_rextsize << mp->m_sb.sb_blocklog;
} else {
size = mp->m_sb.sb_blocksize;
if (extsize_fsb > mp->m_sb.sb_agblocks / 2)
return -EINVAL; return -EINVAL;
}
if (fa->fsx_extsize % size) new_diflags = xfs_flags2diflags(ip, fa->fsx_xflags);
return -EINVAL;
return 0; failaddr = xfs_inode_validate_extsize(ip->i_mount,
XFS_B_TO_FSB(mp, fa->fsx_extsize),
VFS_I(ip)->i_mode, new_diflags);
return failaddr != NULL ? -EINVAL : 0;
} }
/*
* CoW extent size hint validation rules are:
*
* 1. CoW extent size hint can only be set if reflink is enabled on the fs.
* The inode does not have to have any shared blocks, but it must be a v3.
* 2. FS_XFLAG_COWEXTSIZE is only valid for directories and regular files;
* for a directory, the hint is propagated to new files.
* 3. Can be changed on files & directories at any time.
* 4. CoW extsize hint of 0 turns off hints, clears inode flags.
* 5. Extent size must be a multiple of the appropriate block size.
* 6. The extent size hint must be limited to half the AG size to avoid
* alignment extending the extent beyond the limits of the AG.
*
* Please keep this function in sync with xfs_scrub_inode_cowextsize.
*/
static int static int
xfs_ioctl_setattr_check_cowextsize( xfs_ioctl_setattr_check_cowextsize(
struct xfs_inode *ip, struct xfs_inode *ip,
struct fileattr *fa) struct fileattr *fa)
{ {
struct xfs_mount *mp = ip->i_mount; struct xfs_mount *mp = ip->i_mount;
xfs_extlen_t size; xfs_failaddr_t failaddr;
xfs_fsblock_t cowextsize_fsb; uint64_t new_diflags2;
uint16_t new_diflags;
if (!fa->fsx_valid) if (!fa->fsx_valid)
return 0; return 0;
if (!(fa->fsx_xflags & FS_XFLAG_COWEXTSIZE)) if (fa->fsx_cowextsize & mp->m_blockmask)
return 0;
if (!xfs_sb_version_hasreflink(&ip->i_mount->m_sb))
return -EINVAL;
if (fa->fsx_cowextsize == 0)
return 0;
cowextsize_fsb = XFS_B_TO_FSB(mp, fa->fsx_cowextsize);
if (cowextsize_fsb > MAXEXTLEN)
return -EINVAL;
size = mp->m_sb.sb_blocksize;
if (cowextsize_fsb > mp->m_sb.sb_agblocks / 2)
return -EINVAL; return -EINVAL;
if (fa->fsx_cowextsize % size) new_diflags = xfs_flags2diflags(ip, fa->fsx_xflags);
return -EINVAL; new_diflags2 = xfs_flags2diflags2(ip, fa->fsx_xflags);
return 0; failaddr = xfs_inode_validate_cowextsize(ip->i_mount,
XFS_B_TO_FSB(mp, fa->fsx_cowextsize),
VFS_I(ip)->i_mode, new_diflags, new_diflags2);
return failaddr != NULL ? -EINVAL : 0;
} }
static int static int
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment