diff options
| author | Dmitry Torokhov <dmitry.torokhov@gmail.com> | 2025-08-06 10:08:54 -0700 | 
|---|---|---|
| committer | Dmitry Torokhov <dmitry.torokhov@gmail.com> | 2025-08-06 10:08:54 -0700 | 
| commit | ab93e0dd72c37d378dd936f031ffb83ff2bd87ce (patch) | |
| tree | 4e5ed022e3c48cedd519954e4fb529dac0240c94 /fs/xfs/xfs_reflink.c | |
| parent | bcce05041b21888f10b80ea903dcfe51a25c586e (diff) | |
| parent | 4f67c41894674d351a4b4e7dd3471380b71b5bb3 (diff) | |
Merge branch 'next' into for-linus
Prepare input updates for 6.17 merge window.
Diffstat (limited to 'fs/xfs/xfs_reflink.c')
| -rw-r--r-- | fs/xfs/xfs_reflink.c | 146 | 
1 files changed, 115 insertions, 31 deletions
| diff --git a/fs/xfs/xfs_reflink.c b/fs/xfs/xfs_reflink.c index cc3b4df88110..ad3bcb76d805 100644 --- a/fs/xfs/xfs_reflink.c +++ b/fs/xfs/xfs_reflink.c @@ -293,7 +293,7 @@ xfs_bmap_trim_cow(  	return xfs_reflink_trim_around_shared(ip, imap, shared);  } -static int +int  xfs_reflink_convert_cow_locked(  	struct xfs_inode	*ip,  	xfs_fileoff_t		offset_fsb, @@ -786,35 +786,19 @@ xfs_reflink_update_quota(   * requirements as low as possible.   */  STATIC int -xfs_reflink_end_cow_extent( +xfs_reflink_end_cow_extent_locked( +	struct xfs_trans	*tp,  	struct xfs_inode	*ip,  	xfs_fileoff_t		*offset_fsb,  	xfs_fileoff_t		end_fsb)  {  	struct xfs_iext_cursor	icur;  	struct xfs_bmbt_irec	got, del, data; -	struct xfs_mount	*mp = ip->i_mount; -	struct xfs_trans	*tp;  	struct xfs_ifork	*ifp = xfs_ifork_ptr(ip, XFS_COW_FORK); -	unsigned int		resblks;  	int			nmaps;  	bool			isrt = XFS_IS_REALTIME_INODE(ip);  	int			error; -	resblks = XFS_EXTENTADD_SPACE_RES(mp, XFS_DATA_FORK); -	error = xfs_trans_alloc(mp, &M_RES(mp)->tr_write, resblks, 0, -			XFS_TRANS_RESERVE, &tp); -	if (error) -		return error; - -	/* -	 * Lock the inode.  We have to ijoin without automatic unlock because -	 * the lead transaction is the refcountbt record deletion; the data -	 * fork update follows as a deferred log item. -	 */ -	xfs_ilock(ip, XFS_ILOCK_EXCL); -	xfs_trans_ijoin(tp, ip, 0); -  	/*  	 * In case of racing, overlapping AIO writes no COW extents might be  	 * left by the time I/O completes for the loser of the race.  In that @@ -823,7 +807,7 @@ xfs_reflink_end_cow_extent(  	if (!xfs_iext_lookup_extent(ip, ifp, *offset_fsb, &icur, &got) ||  	    got.br_startoff >= end_fsb) {  		*offset_fsb = end_fsb; -		goto out_cancel; +		return 0;  	}  	/* @@ -837,7 +821,7 @@ xfs_reflink_end_cow_extent(  		if (!xfs_iext_next_extent(ifp, &icur, &got) ||  		    got.br_startoff >= end_fsb) {  			*offset_fsb = end_fsb; -			goto out_cancel; +			return 0;  		}  	}  	del = got; @@ -846,14 +830,14 @@ xfs_reflink_end_cow_extent(  	error = xfs_iext_count_extend(tp, ip, XFS_DATA_FORK,  			XFS_IEXT_REFLINK_END_COW_CNT);  	if (error) -		goto out_cancel; +		return error;  	/* Grab the corresponding mapping in the data fork. */  	nmaps = 1;  	error = xfs_bmapi_read(ip, del.br_startoff, del.br_blockcount, &data,  			&nmaps, 0);  	if (error) -		goto out_cancel; +		return error;  	/* We can only remap the smaller of the two extent sizes. */  	data.br_blockcount = min(data.br_blockcount, del.br_blockcount); @@ -882,7 +866,7 @@ xfs_reflink_end_cow_extent(  		error = xfs_bunmapi(NULL, ip, data.br_startoff,  				data.br_blockcount, 0, 1, &done);  		if (error) -			goto out_cancel; +			return error;  		ASSERT(done);  	} @@ -899,17 +883,45 @@ xfs_reflink_end_cow_extent(  	/* Remove the mapping from the CoW fork. */  	xfs_bmap_del_extent_cow(ip, &icur, &got, &del); -	error = xfs_trans_commit(tp); -	xfs_iunlock(ip, XFS_ILOCK_EXCL); -	if (error) -		return error; -  	/* Update the caller about how much progress we made. */  	*offset_fsb = del.br_startoff + del.br_blockcount;  	return 0; +} -out_cancel: -	xfs_trans_cancel(tp); +/* + * Remap part of the CoW fork into the data fork. + * + * We aim to remap the range starting at @offset_fsb and ending at @end_fsb + * into the data fork; this function will remap what it can (at the end of the + * range) and update @end_fsb appropriately.  Each remap gets its own + * transaction because we can end up merging and splitting bmbt blocks for + * every remap operation and we'd like to keep the block reservation + * requirements as low as possible. + */ +STATIC int +xfs_reflink_end_cow_extent( +	struct xfs_inode	*ip, +	xfs_fileoff_t		*offset_fsb, +	xfs_fileoff_t		end_fsb) +{ +	struct xfs_mount	*mp = ip->i_mount; +	struct xfs_trans	*tp; +	unsigned int		resblks; +	int			error; + +	resblks = XFS_EXTENTADD_SPACE_RES(mp, XFS_DATA_FORK); +	error = xfs_trans_alloc(mp, &M_RES(mp)->tr_write, resblks, 0, +			XFS_TRANS_RESERVE, &tp); +	if (error) +		return error; +	xfs_ilock(ip, XFS_ILOCK_EXCL); +	xfs_trans_ijoin(tp, ip, 0); + +	error = xfs_reflink_end_cow_extent_locked(tp, ip, offset_fsb, end_fsb); +	if (error) +		xfs_trans_cancel(tp); +	else +		error = xfs_trans_commit(tp);  	xfs_iunlock(ip, XFS_ILOCK_EXCL);  	return error;  } @@ -973,6 +985,78 @@ xfs_reflink_end_cow(  }  /* + * Fully remap all of the file's data fork at once, which is the critical part + * in achieving atomic behaviour. + * The regular CoW end path does not use function as to keep the block + * reservation per transaction as low as possible. + */ +int +xfs_reflink_end_atomic_cow( +	struct xfs_inode		*ip, +	xfs_off_t			offset, +	xfs_off_t			count) +{ +	xfs_fileoff_t			offset_fsb; +	xfs_fileoff_t			end_fsb; +	int				error = 0; +	struct xfs_mount		*mp = ip->i_mount; +	struct xfs_trans		*tp; +	unsigned int			resblks; + +	trace_xfs_reflink_end_cow(ip, offset, count); + +	offset_fsb = XFS_B_TO_FSBT(mp, offset); +	end_fsb = XFS_B_TO_FSB(mp, offset + count); + +	/* +	 * Each remapping operation could cause a btree split, so in the worst +	 * case that's one for each block. +	 */ +	resblks = (end_fsb - offset_fsb) * +			XFS_NEXTENTADD_SPACE_RES(mp, 1, XFS_DATA_FORK); + +	error = xfs_trans_alloc(mp, &M_RES(mp)->tr_atomic_ioend, resblks, 0, +			XFS_TRANS_RESERVE, &tp); +	if (error) +		return error; + +	xfs_ilock(ip, XFS_ILOCK_EXCL); +	xfs_trans_ijoin(tp, ip, 0); + +	while (end_fsb > offset_fsb && !error) { +		error = xfs_reflink_end_cow_extent_locked(tp, ip, &offset_fsb, +				end_fsb); +	} +	if (error) { +		trace_xfs_reflink_end_cow_error(ip, error, _RET_IP_); +		goto out_cancel; +	} +	error = xfs_trans_commit(tp); +	xfs_iunlock(ip, XFS_ILOCK_EXCL); +	return error; +out_cancel: +	xfs_trans_cancel(tp); +	xfs_iunlock(ip, XFS_ILOCK_EXCL); +	return error; +} + +/* Compute the largest atomic write that we can complete through software. */ +xfs_extlen_t +xfs_reflink_max_atomic_cow( +	struct xfs_mount	*mp) +{ +	/* We cannot do any atomic writes without out of place writes. */ +	if (!xfs_can_sw_atomic_write(mp)) +		return 0; + +	/* +	 * Atomic write limits must always be a power-of-2, according to +	 * generic_atomic_write_valid. +	 */ +	return rounddown_pow_of_two(xfs_calc_max_atomic_write_fsblocks(mp)); +} + +/*   * Free all CoW staging blocks that are still referenced by the ondisk refcount   * metadata.  The ondisk metadata does not track which inode created the   * staging extent, so callers must ensure that there are no cached inodes with | 
