diff mbox series

[6/6] xfs: refactor xfs_file_fallocate

Message ID 20240827065123.1762168-7-hch@lst.de (mailing list archive)
State New
Headers show
Series [1/6] block: remove checks for FALLOC_FL_NO_HIDE_STALE | expand

Commit Message

Christoph Hellwig Aug. 27, 2024, 6:50 a.m. UTC
Refactor xfs_file_fallocate into separate helpers for each mode,
two factors for i_size handling and a single switch statement over the
supported modes.

Signed-off-by: Christoph Hellwig <hch@lst.de>
---
 fs/xfs/xfs_file.c | 330 +++++++++++++++++++++++++++++-----------------
 1 file changed, 208 insertions(+), 122 deletions(-)

Comments

Darrick J. Wong Aug. 27, 2024, 4:07 p.m. UTC | #1
On Tue, Aug 27, 2024 at 08:50:50AM +0200, Christoph Hellwig wrote:
> Refactor xfs_file_fallocate into separate helpers for each mode,
> two factors for i_size handling and a single switch statement over the
> supported modes.
> 
> Signed-off-by: Christoph Hellwig <hch@lst.de>

Much less complicated now! :)

Reviewed-by: Darrick J. Wong <djwong@kernel.org>

--D

> ---
>  fs/xfs/xfs_file.c | 330 +++++++++++++++++++++++++++++-----------------
>  1 file changed, 208 insertions(+), 122 deletions(-)
> 
> diff --git a/fs/xfs/xfs_file.c b/fs/xfs/xfs_file.c
> index 489bc1b173c268..f6e4912769a0d5 100644
> --- a/fs/xfs/xfs_file.c
> +++ b/fs/xfs/xfs_file.c
> @@ -852,6 +852,192 @@ static inline bool xfs_file_sync_writes(struct file *filp)
>  	return false;
>  }
>  
> +static int
> +xfs_falloc_newsize(
> +	struct file		*file,
> +	int			mode,
> +	loff_t			offset,
> +	loff_t			len,
> +	loff_t			*new_size)
> +{
> +	struct inode		*inode = file_inode(file);
> +
> +	if ((mode & FALLOC_FL_KEEP_SIZE) || offset + len <= i_size_read(inode))
> +		return 0;
> +	*new_size = offset + len;
> +	return inode_newsize_ok(inode, *new_size);
> +}
> +
> +static int
> +xfs_falloc_setsize(
> +	struct file		*file,
> +	loff_t			new_size)
> +{
> +	struct iattr iattr = {
> +		.ia_valid	= ATTR_SIZE,
> +		.ia_size	= new_size,
> +	};
> +
> +	if (!new_size)
> +		return 0;
> +	return xfs_vn_setattr_size(file_mnt_idmap(file), file_dentry(file),
> +			&iattr);
> +}
> +
> +static int
> +xfs_falloc_collapse_range(
> +	struct file		*file,
> +	loff_t			offset,
> +	loff_t			len)
> +{
> +	struct inode		*inode = file_inode(file);
> +	loff_t			new_size = i_size_read(inode) - len;
> +	int			error;
> +
> +	if (!xfs_is_falloc_aligned(XFS_I(inode), offset, len))
> +		return -EINVAL;
> +
> +	/*
> +	 * There is no need to overlap collapse range with EOF, in which case it
> +	 * is effectively a truncate operation
> +	 */
> +	if (offset + len >= i_size_read(inode))
> +		return -EINVAL;
> +
> +	error = xfs_collapse_file_space(XFS_I(inode), offset, len);
> +	if (error)
> +		return error;
> +	return xfs_falloc_setsize(file, new_size);
> +}
> +
> +static int
> +xfs_falloc_insert_range(
> +	struct file		*file,
> +	loff_t			offset,
> +	loff_t			len)
> +{
> +	struct inode		*inode = file_inode(file);
> +	loff_t			isize = i_size_read(inode);
> +	int			error;
> +
> +	if (!xfs_is_falloc_aligned(XFS_I(inode), offset, len))
> +		return -EINVAL;
> +
> +	/*
> +	 * New inode size must not exceed ->s_maxbytes, accounting for
> +	 * possible signed overflow.
> +	 */
> +	if (inode->i_sb->s_maxbytes - isize < len)
> +		return -EFBIG;
> +
> +	/* Offset should be less than i_size */
> +	if (offset >= isize)
> +		return -EINVAL;
> +
> +	error = xfs_falloc_setsize(file, isize + len);
> +	if (error)
> +		return error;
> +
> +	/*
> +	 * Perform hole insertion now that the file size has been updated so
> +	 * that if we crash during the operation we don't leave shifted extents
> +	 * past EOF and hence losing access to the data that is contained within

"...and hence lose access to the data..."

> +	 * them.
> +	 */
> +	return xfs_insert_file_space(XFS_I(inode), offset, len);
> +}
> +
> +/*
> + * Punch a hole and prealloc the range.  We use a hole punch rather than
> + * unwritten extent conversion for two reasons:
> + *
> + *   1.) Hole punch handles partial block zeroing for us.
> + *   2.) If prealloc returns ENOSPC, the file range is still zero-valued by
> + *	 virtue of the hole punch.
> + */
> +static int
> +xfs_falloc_zero_range(
> +	struct file		*file,
> +	int			mode,
> +	loff_t			offset,
> +	loff_t			len)
> +{
> +	struct inode		*inode = file_inode(file);
> +	unsigned int		blksize = i_blocksize(inode);
> +	loff_t			new_size = 0;
> +	int			error;
> +
> +	trace_xfs_zero_file_space(XFS_I(inode));
> +
> +	error = xfs_falloc_newsize(file, mode, offset, len, &new_size);
> +	if (error)
> +		return error;
> +
> +	error = xfs_free_file_space(XFS_I(inode), offset, len);
> +	if (error)
> +		return error;
> +
> +	len = round_up(offset + len, blksize) - round_down(offset, blksize);
> +	offset = round_down(offset, blksize);
> +	error = xfs_alloc_file_space(XFS_I(inode), offset, len);
> +	if (error)
> +		return error;
> +	return xfs_falloc_setsize(file, new_size);
> +}
> +
> +static int
> +xfs_falloc_unshare_range(
> +	struct file		*file,
> +	int			mode,
> +	loff_t			offset,
> +	loff_t			len)
> +{
> +	struct inode		*inode = file_inode(file);
> +	loff_t			new_size = 0;
> +	int			error;
> +
> +	error = xfs_falloc_newsize(file, mode, offset, len, &new_size);
> +	if (error)
> +		return error;
> +
> +	error = xfs_reflink_unshare(XFS_I(inode), offset, len);
> +	if (error)
> +		return error;
> +
> +	error = xfs_alloc_file_space(XFS_I(inode), offset, len);
> +	if (error)
> +		return error;
> +	return xfs_falloc_setsize(file, new_size);
> +}
> +
> +static int
> +xfs_falloc_allocate_range(
> +	struct file		*file,
> +	int			mode,
> +	loff_t			offset,
> +	loff_t			len)
> +{
> +	struct inode		*inode = file_inode(file);
> +	loff_t			new_size = 0;
> +	int			error;
> +
> +	/*
> +	 * If always_cow mode we can't use preallocations and thus should not
> +	 * create them.
> +	 */
> +	if (xfs_is_always_cow_inode(XFS_I(inode)))
> +		return -EOPNOTSUPP;
> +
> +	error = xfs_falloc_newsize(file, mode, offset, len, &new_size);
> +	if (error)
> +		return error;
> +
> +	error = xfs_alloc_file_space(XFS_I(inode), offset, len);
> +	if (error)
> +		return error;
> +	return xfs_falloc_setsize(file, new_size);
> +}
> +
>  #define	XFS_FALLOC_FL_SUPPORTED						\
>  		(FALLOC_FL_KEEP_SIZE | FALLOC_FL_PUNCH_HOLE |		\
>  		 FALLOC_FL_COLLAPSE_RANGE | FALLOC_FL_ZERO_RANGE |	\
> @@ -868,8 +1054,6 @@ xfs_file_fallocate(
>  	struct xfs_inode	*ip = XFS_I(inode);
>  	long			error;
>  	uint			iolock = XFS_IOLOCK_EXCL | XFS_MMAPLOCK_EXCL;
> -	loff_t			new_size = 0;
> -	bool			do_file_insert = false;
>  
>  	if (!S_ISREG(inode->i_mode))
>  		return -EINVAL;
> @@ -894,129 +1078,31 @@ xfs_file_fallocate(
>  	if (error)
>  		goto out_unlock;
>  
> -	if (mode & FALLOC_FL_PUNCH_HOLE) {
> +	switch (mode & FALLOC_FL_MODE_MASK) {
> +	case FALLOC_FL_PUNCH_HOLE:
>  		error = xfs_free_file_space(ip, offset, len);
> -		if (error)
> -			goto out_unlock;
> -	} else if (mode & FALLOC_FL_COLLAPSE_RANGE) {
> -		if (!xfs_is_falloc_aligned(ip, offset, len)) {
> -			error = -EINVAL;
> -			goto out_unlock;
> -		}
> -
> -		/*
> -		 * There is no need to overlap collapse range with EOF,
> -		 * in which case it is effectively a truncate operation
> -		 */
> -		if (offset + len >= i_size_read(inode)) {
> -			error = -EINVAL;
> -			goto out_unlock;
> -		}
> -
> -		new_size = i_size_read(inode) - len;
> -
> -		error = xfs_collapse_file_space(ip, offset, len);
> -		if (error)
> -			goto out_unlock;
> -	} else if (mode & FALLOC_FL_INSERT_RANGE) {
> -		loff_t		isize = i_size_read(inode);
> -
> -		if (!xfs_is_falloc_aligned(ip, offset, len)) {
> -			error = -EINVAL;
> -			goto out_unlock;
> -		}
> -
> -		/*
> -		 * New inode size must not exceed ->s_maxbytes, accounting for
> -		 * possible signed overflow.
> -		 */
> -		if (inode->i_sb->s_maxbytes - isize < len) {
> -			error = -EFBIG;
> -			goto out_unlock;
> -		}
> -		new_size = isize + len;
> -
> -		/* Offset should be less than i_size */
> -		if (offset >= isize) {
> -			error = -EINVAL;
> -			goto out_unlock;
> -		}
> -		do_file_insert = true;
> -	} else {
> -		if (!(mode & FALLOC_FL_KEEP_SIZE) &&
> -		    offset + len > i_size_read(inode)) {
> -			new_size = offset + len;
> -			error = inode_newsize_ok(inode, new_size);
> -			if (error)
> -				goto out_unlock;
> -		}
> -
> -		if (mode & FALLOC_FL_ZERO_RANGE) {
> -			/*
> -			 * Punch a hole and prealloc the range.  We use a hole
> -			 * punch rather than unwritten extent conversion for two
> -			 * reasons:
> -			 *
> -			 *   1.) Hole punch handles partial block zeroing for us.
> -			 *   2.) If prealloc returns ENOSPC, the file range is
> -			 *       still zero-valued by virtue of the hole punch.
> -			 */
> -			unsigned int blksize = i_blocksize(inode);
> -
> -			trace_xfs_zero_file_space(ip);
> -
> -			error = xfs_free_file_space(ip, offset, len);
> -			if (error)
> -				goto out_unlock;
> -
> -			len = round_up(offset + len, blksize) -
> -			      round_down(offset, blksize);
> -			offset = round_down(offset, blksize);
> -		} else if (mode & FALLOC_FL_UNSHARE_RANGE) {
> -			error = xfs_reflink_unshare(ip, offset, len);
> -			if (error)
> -				goto out_unlock;
> -		} else {
> -			/*
> -			 * If always_cow mode we can't use preallocations and
> -			 * thus should not create them.
> -			 */
> -			if (xfs_is_always_cow_inode(ip)) {
> -				error = -EOPNOTSUPP;
> -				goto out_unlock;
> -			}
> -		}
> -
> -		error = xfs_alloc_file_space(ip, offset, len);
> -		if (error)
> -			goto out_unlock;
> -	}
> -
> -	/* Change file size if needed */
> -	if (new_size) {
> -		struct iattr iattr;
> -
> -		iattr.ia_valid = ATTR_SIZE;
> -		iattr.ia_size = new_size;
> -		error = xfs_vn_setattr_size(file_mnt_idmap(file),
> -					    file_dentry(file), &iattr);
> -		if (error)
> -			goto out_unlock;
> -	}
> -
> -	/*
> -	 * Perform hole insertion now that the file size has been
> -	 * updated so that if we crash during the operation we don't
> -	 * leave shifted extents past EOF and hence losing access to
> -	 * the data that is contained within them.
> -	 */
> -	if (do_file_insert) {
> -		error = xfs_insert_file_space(ip, offset, len);
> -		if (error)
> -			goto out_unlock;
> +		break;
> +	case FALLOC_FL_COLLAPSE_RANGE:
> +		error = xfs_falloc_collapse_range(file, offset, len);
> +		break;
> +	case FALLOC_FL_INSERT_RANGE:
> +		error = xfs_falloc_insert_range(file, offset, len);
> +		break;
> +	case FALLOC_FL_ZERO_RANGE:
> +		error = xfs_falloc_zero_range(file, mode, offset, len);
> +		break;
> +	case FALLOC_FL_UNSHARE_RANGE:
> +		error = xfs_falloc_unshare_range(file, mode, offset, len);
> +		break;
> +	case FALLOC_FL_ALLOCATE_RANGE:
> +		error = xfs_falloc_allocate_range(file, mode, offset, len);
> +		break;
> +	default:
> +		error = -EOPNOTSUPP;
> +		break;
>  	}
>  
> -	if (xfs_file_sync_writes(file))
> +	if (!error && xfs_file_sync_writes(file))
>  		error = xfs_log_force_inode(ip);
>  
>  out_unlock:
> -- 
> 2.43.0
> 
>
Christoph Hellwig Aug. 28, 2024, 4:07 a.m. UTC | #2
On Tue, Aug 27, 2024 at 09:07:03AM -0700, Darrick J. Wong wrote:
> On Tue, Aug 27, 2024 at 08:50:50AM +0200, Christoph Hellwig wrote:
> > Refactor xfs_file_fallocate into separate helpers for each mode,
> > two factors for i_size handling and a single switch statement over the
> > supported modes.
> > 
> > Signed-off-by: Christoph Hellwig <hch@lst.de>
> 
> Much less complicated now! :)

A little more LOC, though.  But I think that's worth the tradeoff.
Dave Chinner Aug. 29, 2024, 3:11 a.m. UTC | #3
On Tue, Aug 27, 2024 at 08:50:50AM +0200, Christoph Hellwig wrote:
> Refactor xfs_file_fallocate into separate helpers for each mode,
> two factors for i_size handling and a single switch statement over the
> supported modes.
> 
> Signed-off-by: Christoph Hellwig <hch@lst.de>
> ---
>  fs/xfs/xfs_file.c | 330 +++++++++++++++++++++++++++++-----------------
>  1 file changed, 208 insertions(+), 122 deletions(-)

Much nicer. :)

And it made an existing issue in the code quite obvious, too:

> +/*
> + * Punch a hole and prealloc the range.  We use a hole punch rather than
> + * unwritten extent conversion for two reasons:
> + *
> + *   1.) Hole punch handles partial block zeroing for us.
> + *   2.) If prealloc returns ENOSPC, the file range is still zero-valued by
> + *	 virtue of the hole punch.
> + */
> +static int
> +xfs_falloc_zero_range(
> +	struct file		*file,
> +	int			mode,
> +	loff_t			offset,
> +	loff_t			len)
> +{
> +	struct inode		*inode = file_inode(file);
> +	unsigned int		blksize = i_blocksize(inode);
> +	loff_t			new_size = 0;
> +	int			error;
> +
> +	trace_xfs_zero_file_space(XFS_I(inode));
> +
> +	error = xfs_falloc_newsize(file, mode, offset, len, &new_size);
> +	if (error)
> +		return error;
> +
> +	error = xfs_free_file_space(XFS_I(inode), offset, len);
> +	if (error)
> +		return error;
> +
> +	len = round_up(offset + len, blksize) - round_down(offset, blksize);
> +	offset = round_down(offset, blksize);
> +	error = xfs_alloc_file_space(XFS_I(inode), offset, len);
> +	if (error)
> +		return error;
> +	return xfs_falloc_setsize(file, new_size);
> +}

Our zeroing operation always does preallocation, but....


> +static int
> +xfs_falloc_allocate_range(
> +	struct file		*file,
> +	int			mode,
> +	loff_t			offset,
> +	loff_t			len)
> +{
> +	struct inode		*inode = file_inode(file);
> +	loff_t			new_size = 0;
> +	int			error;
> +
> +	/*
> +	 * If always_cow mode we can't use preallocations and thus should not
> +	 * create them.
> +	 */
> +	if (xfs_is_always_cow_inode(XFS_I(inode)))
> +		return -EOPNOTSUPP;

... our preallocation operation always returns -EOPNOTSUPP for
COW mode.

Should the zeroing code also have this COW mode check in it after
the hole punch has run so we don't do unnecessary prealloc there?

-Dave.
Christoph Hellwig Aug. 29, 2024, 3:56 a.m. UTC | #4
On Thu, Aug 29, 2024 at 01:11:11PM +1000, Dave Chinner wrote:
> > +xfs_falloc_allocate_range(
> > +	struct file		*file,
> > +	int			mode,
> > +	loff_t			offset,
> > +	loff_t			len)
> > +{
> > +	struct inode		*inode = file_inode(file);
> > +	loff_t			new_size = 0;
> > +	int			error;
> > +
> > +	/*
> > +	 * If always_cow mode we can't use preallocations and thus should not
> > +	 * create them.
> > +	 */
> > +	if (xfs_is_always_cow_inode(XFS_I(inode)))
> > +		return -EOPNOTSUPP;
> 
> ... our preallocation operation always returns -EOPNOTSUPP for
> COW mode.
> 
> Should the zeroing code also have this COW mode check in it after
> the hole punch has run so we don't do unnecessary prealloc there?

The low-level block allocation helper just returns early without
doing work (move a bit, but not changed in behavior earlier in the
series).  So it won't actually do the prealloc.

> 
> -Dave.
> -- 
> Dave Chinner
> david@fromorbit.com
---end quoted text---
diff mbox series

Patch

diff --git a/fs/xfs/xfs_file.c b/fs/xfs/xfs_file.c
index 489bc1b173c268..f6e4912769a0d5 100644
--- a/fs/xfs/xfs_file.c
+++ b/fs/xfs/xfs_file.c
@@ -852,6 +852,192 @@  static inline bool xfs_file_sync_writes(struct file *filp)
 	return false;
 }
 
+static int
+xfs_falloc_newsize(
+	struct file		*file,
+	int			mode,
+	loff_t			offset,
+	loff_t			len,
+	loff_t			*new_size)
+{
+	struct inode		*inode = file_inode(file);
+
+	if ((mode & FALLOC_FL_KEEP_SIZE) || offset + len <= i_size_read(inode))
+		return 0;
+	*new_size = offset + len;
+	return inode_newsize_ok(inode, *new_size);
+}
+
+static int
+xfs_falloc_setsize(
+	struct file		*file,
+	loff_t			new_size)
+{
+	struct iattr iattr = {
+		.ia_valid	= ATTR_SIZE,
+		.ia_size	= new_size,
+	};
+
+	if (!new_size)
+		return 0;
+	return xfs_vn_setattr_size(file_mnt_idmap(file), file_dentry(file),
+			&iattr);
+}
+
+static int
+xfs_falloc_collapse_range(
+	struct file		*file,
+	loff_t			offset,
+	loff_t			len)
+{
+	struct inode		*inode = file_inode(file);
+	loff_t			new_size = i_size_read(inode) - len;
+	int			error;
+
+	if (!xfs_is_falloc_aligned(XFS_I(inode), offset, len))
+		return -EINVAL;
+
+	/*
+	 * There is no need to overlap collapse range with EOF, in which case it
+	 * is effectively a truncate operation
+	 */
+	if (offset + len >= i_size_read(inode))
+		return -EINVAL;
+
+	error = xfs_collapse_file_space(XFS_I(inode), offset, len);
+	if (error)
+		return error;
+	return xfs_falloc_setsize(file, new_size);
+}
+
+static int
+xfs_falloc_insert_range(
+	struct file		*file,
+	loff_t			offset,
+	loff_t			len)
+{
+	struct inode		*inode = file_inode(file);
+	loff_t			isize = i_size_read(inode);
+	int			error;
+
+	if (!xfs_is_falloc_aligned(XFS_I(inode), offset, len))
+		return -EINVAL;
+
+	/*
+	 * New inode size must not exceed ->s_maxbytes, accounting for
+	 * possible signed overflow.
+	 */
+	if (inode->i_sb->s_maxbytes - isize < len)
+		return -EFBIG;
+
+	/* Offset should be less than i_size */
+	if (offset >= isize)
+		return -EINVAL;
+
+	error = xfs_falloc_setsize(file, isize + len);
+	if (error)
+		return error;
+
+	/*
+	 * Perform hole insertion now that the file size has been updated so
+	 * that if we crash during the operation we don't leave shifted extents
+	 * past EOF and hence losing access to the data that is contained within
+	 * them.
+	 */
+	return xfs_insert_file_space(XFS_I(inode), offset, len);
+}
+
+/*
+ * Punch a hole and prealloc the range.  We use a hole punch rather than
+ * unwritten extent conversion for two reasons:
+ *
+ *   1.) Hole punch handles partial block zeroing for us.
+ *   2.) If prealloc returns ENOSPC, the file range is still zero-valued by
+ *	 virtue of the hole punch.
+ */
+static int
+xfs_falloc_zero_range(
+	struct file		*file,
+	int			mode,
+	loff_t			offset,
+	loff_t			len)
+{
+	struct inode		*inode = file_inode(file);
+	unsigned int		blksize = i_blocksize(inode);
+	loff_t			new_size = 0;
+	int			error;
+
+	trace_xfs_zero_file_space(XFS_I(inode));
+
+	error = xfs_falloc_newsize(file, mode, offset, len, &new_size);
+	if (error)
+		return error;
+
+	error = xfs_free_file_space(XFS_I(inode), offset, len);
+	if (error)
+		return error;
+
+	len = round_up(offset + len, blksize) - round_down(offset, blksize);
+	offset = round_down(offset, blksize);
+	error = xfs_alloc_file_space(XFS_I(inode), offset, len);
+	if (error)
+		return error;
+	return xfs_falloc_setsize(file, new_size);
+}
+
+static int
+xfs_falloc_unshare_range(
+	struct file		*file,
+	int			mode,
+	loff_t			offset,
+	loff_t			len)
+{
+	struct inode		*inode = file_inode(file);
+	loff_t			new_size = 0;
+	int			error;
+
+	error = xfs_falloc_newsize(file, mode, offset, len, &new_size);
+	if (error)
+		return error;
+
+	error = xfs_reflink_unshare(XFS_I(inode), offset, len);
+	if (error)
+		return error;
+
+	error = xfs_alloc_file_space(XFS_I(inode), offset, len);
+	if (error)
+		return error;
+	return xfs_falloc_setsize(file, new_size);
+}
+
+static int
+xfs_falloc_allocate_range(
+	struct file		*file,
+	int			mode,
+	loff_t			offset,
+	loff_t			len)
+{
+	struct inode		*inode = file_inode(file);
+	loff_t			new_size = 0;
+	int			error;
+
+	/*
+	 * If always_cow mode we can't use preallocations and thus should not
+	 * create them.
+	 */
+	if (xfs_is_always_cow_inode(XFS_I(inode)))
+		return -EOPNOTSUPP;
+
+	error = xfs_falloc_newsize(file, mode, offset, len, &new_size);
+	if (error)
+		return error;
+
+	error = xfs_alloc_file_space(XFS_I(inode), offset, len);
+	if (error)
+		return error;
+	return xfs_falloc_setsize(file, new_size);
+}
+
 #define	XFS_FALLOC_FL_SUPPORTED						\
 		(FALLOC_FL_KEEP_SIZE | FALLOC_FL_PUNCH_HOLE |		\
 		 FALLOC_FL_COLLAPSE_RANGE | FALLOC_FL_ZERO_RANGE |	\
@@ -868,8 +1054,6 @@  xfs_file_fallocate(
 	struct xfs_inode	*ip = XFS_I(inode);
 	long			error;
 	uint			iolock = XFS_IOLOCK_EXCL | XFS_MMAPLOCK_EXCL;
-	loff_t			new_size = 0;
-	bool			do_file_insert = false;
 
 	if (!S_ISREG(inode->i_mode))
 		return -EINVAL;
@@ -894,129 +1078,31 @@  xfs_file_fallocate(
 	if (error)
 		goto out_unlock;
 
-	if (mode & FALLOC_FL_PUNCH_HOLE) {
+	switch (mode & FALLOC_FL_MODE_MASK) {
+	case FALLOC_FL_PUNCH_HOLE:
 		error = xfs_free_file_space(ip, offset, len);
-		if (error)
-			goto out_unlock;
-	} else if (mode & FALLOC_FL_COLLAPSE_RANGE) {
-		if (!xfs_is_falloc_aligned(ip, offset, len)) {
-			error = -EINVAL;
-			goto out_unlock;
-		}
-
-		/*
-		 * There is no need to overlap collapse range with EOF,
-		 * in which case it is effectively a truncate operation
-		 */
-		if (offset + len >= i_size_read(inode)) {
-			error = -EINVAL;
-			goto out_unlock;
-		}
-
-		new_size = i_size_read(inode) - len;
-
-		error = xfs_collapse_file_space(ip, offset, len);
-		if (error)
-			goto out_unlock;
-	} else if (mode & FALLOC_FL_INSERT_RANGE) {
-		loff_t		isize = i_size_read(inode);
-
-		if (!xfs_is_falloc_aligned(ip, offset, len)) {
-			error = -EINVAL;
-			goto out_unlock;
-		}
-
-		/*
-		 * New inode size must not exceed ->s_maxbytes, accounting for
-		 * possible signed overflow.
-		 */
-		if (inode->i_sb->s_maxbytes - isize < len) {
-			error = -EFBIG;
-			goto out_unlock;
-		}
-		new_size = isize + len;
-
-		/* Offset should be less than i_size */
-		if (offset >= isize) {
-			error = -EINVAL;
-			goto out_unlock;
-		}
-		do_file_insert = true;
-	} else {
-		if (!(mode & FALLOC_FL_KEEP_SIZE) &&
-		    offset + len > i_size_read(inode)) {
-			new_size = offset + len;
-			error = inode_newsize_ok(inode, new_size);
-			if (error)
-				goto out_unlock;
-		}
-
-		if (mode & FALLOC_FL_ZERO_RANGE) {
-			/*
-			 * Punch a hole and prealloc the range.  We use a hole
-			 * punch rather than unwritten extent conversion for two
-			 * reasons:
-			 *
-			 *   1.) Hole punch handles partial block zeroing for us.
-			 *   2.) If prealloc returns ENOSPC, the file range is
-			 *       still zero-valued by virtue of the hole punch.
-			 */
-			unsigned int blksize = i_blocksize(inode);
-
-			trace_xfs_zero_file_space(ip);
-
-			error = xfs_free_file_space(ip, offset, len);
-			if (error)
-				goto out_unlock;
-
-			len = round_up(offset + len, blksize) -
-			      round_down(offset, blksize);
-			offset = round_down(offset, blksize);
-		} else if (mode & FALLOC_FL_UNSHARE_RANGE) {
-			error = xfs_reflink_unshare(ip, offset, len);
-			if (error)
-				goto out_unlock;
-		} else {
-			/*
-			 * If always_cow mode we can't use preallocations and
-			 * thus should not create them.
-			 */
-			if (xfs_is_always_cow_inode(ip)) {
-				error = -EOPNOTSUPP;
-				goto out_unlock;
-			}
-		}
-
-		error = xfs_alloc_file_space(ip, offset, len);
-		if (error)
-			goto out_unlock;
-	}
-
-	/* Change file size if needed */
-	if (new_size) {
-		struct iattr iattr;
-
-		iattr.ia_valid = ATTR_SIZE;
-		iattr.ia_size = new_size;
-		error = xfs_vn_setattr_size(file_mnt_idmap(file),
-					    file_dentry(file), &iattr);
-		if (error)
-			goto out_unlock;
-	}
-
-	/*
-	 * Perform hole insertion now that the file size has been
-	 * updated so that if we crash during the operation we don't
-	 * leave shifted extents past EOF and hence losing access to
-	 * the data that is contained within them.
-	 */
-	if (do_file_insert) {
-		error = xfs_insert_file_space(ip, offset, len);
-		if (error)
-			goto out_unlock;
+		break;
+	case FALLOC_FL_COLLAPSE_RANGE:
+		error = xfs_falloc_collapse_range(file, offset, len);
+		break;
+	case FALLOC_FL_INSERT_RANGE:
+		error = xfs_falloc_insert_range(file, offset, len);
+		break;
+	case FALLOC_FL_ZERO_RANGE:
+		error = xfs_falloc_zero_range(file, mode, offset, len);
+		break;
+	case FALLOC_FL_UNSHARE_RANGE:
+		error = xfs_falloc_unshare_range(file, mode, offset, len);
+		break;
+	case FALLOC_FL_ALLOCATE_RANGE:
+		error = xfs_falloc_allocate_range(file, mode, offset, len);
+		break;
+	default:
+		error = -EOPNOTSUPP;
+		break;
 	}
 
-	if (xfs_file_sync_writes(file))
+	if (!error && xfs_file_sync_writes(file))
 		error = xfs_log_force_inode(ip);
 
 out_unlock: