diff options
| -rw-r--r-- | fs/xfs/xfs_reflink.c | 198 | 
1 files changed, 163 insertions, 35 deletions
| diff --git a/fs/xfs/xfs_reflink.c b/fs/xfs/xfs_reflink.c index 724806c7ce3e..0a32b54456eb 100644 --- a/fs/xfs/xfs_reflink.c +++ b/fs/xfs/xfs_reflink.c @@ -341,9 +341,41 @@ xfs_find_trim_cow_extent(  	return 0;  } -/* Allocate all CoW reservations covering a range of blocks in a file. */ -int -xfs_reflink_allocate_cow( +static int +xfs_reflink_convert_unwritten( +	struct xfs_inode	*ip, +	struct xfs_bmbt_irec	*imap, +	struct xfs_bmbt_irec	*cmap, +	bool			convert_now) +{ +	xfs_fileoff_t		offset_fsb = imap->br_startoff; +	xfs_filblks_t		count_fsb = imap->br_blockcount; +	int			error; + +	/* +	 * cmap might larger than imap due to cowextsize hint. +	 */ +	xfs_trim_extent(cmap, offset_fsb, count_fsb); + +	/* +	 * COW fork extents are supposed to remain unwritten until we're ready +	 * to initiate a disk write.  For direct I/O we are going to write the +	 * data and need the conversion, but for buffered writes we're done. +	 */ +	if (!convert_now || cmap->br_state == XFS_EXT_NORM) +		return 0; + +	trace_xfs_reflink_convert_cow(ip, cmap); + +	error = xfs_reflink_convert_cow_locked(ip, offset_fsb, count_fsb); +	if (!error) +		cmap->br_state = XFS_EXT_NORM; + +	return error; +} + +static int +xfs_reflink_fill_cow_hole(  	struct xfs_inode	*ip,  	struct xfs_bmbt_irec	*imap,  	struct xfs_bmbt_irec	*cmap, @@ -352,25 +384,12 @@ xfs_reflink_allocate_cow(  	bool			convert_now)  {  	struct xfs_mount	*mp = ip->i_mount; -	xfs_fileoff_t		offset_fsb = imap->br_startoff; -	xfs_filblks_t		count_fsb = imap->br_blockcount;  	struct xfs_trans	*tp; -	int			nimaps, error = 0; -	bool			found;  	xfs_filblks_t		resaligned; -	xfs_extlen_t		resblks = 0; - -	ASSERT(xfs_isilocked(ip, XFS_ILOCK_EXCL)); -	if (!ip->i_cowfp) { -		ASSERT(!xfs_is_reflink_inode(ip)); -		xfs_ifork_init_cow(ip); -	} - -	error = xfs_find_trim_cow_extent(ip, imap, cmap, shared, &found); -	if (error || !*shared) -		return error; -	if (found) -		goto convert; +	xfs_extlen_t		resblks; +	int			nimaps; +	int			error; +	bool			found;  	resaligned = xfs_aligned_fsb_count(imap->br_startoff,  		imap->br_blockcount, xfs_get_cowextsz_hint(ip)); @@ -386,17 +405,17 @@ xfs_reflink_allocate_cow(  	*lockmode = XFS_ILOCK_EXCL; -	/* -	 * Check for an overlapping extent again now that we dropped the ilock. -	 */  	error = xfs_find_trim_cow_extent(ip, imap, cmap, shared, &found);  	if (error || !*shared)  		goto out_trans_cancel; +  	if (found) {  		xfs_trans_cancel(tp);  		goto convert;  	} +	ASSERT(cmap->br_startoff > imap->br_startoff); +  	/* Allocate the entire reservation as unwritten blocks. */  	nimaps = 1;  	error = xfs_bmapi_write(tp, ip, imap->br_startoff, imap->br_blockcount, @@ -416,26 +435,135 @@ xfs_reflink_allocate_cow(  	 */  	if (nimaps == 0)  		return -ENOSPC; +  convert: -	xfs_trim_extent(cmap, offset_fsb, count_fsb); -	/* -	 * COW fork extents are supposed to remain unwritten until we're ready -	 * to initiate a disk write.  For direct I/O we are going to write the -	 * data and need the conversion, but for buffered writes we're done. -	 */ -	if (!convert_now || cmap->br_state == XFS_EXT_NORM) -		return 0; -	trace_xfs_reflink_convert_cow(ip, cmap); -	error = xfs_reflink_convert_cow_locked(ip, offset_fsb, count_fsb); -	if (!error) -		cmap->br_state = XFS_EXT_NORM; +	return xfs_reflink_convert_unwritten(ip, imap, cmap, convert_now); + +out_trans_cancel: +	xfs_trans_cancel(tp);  	return error; +} + +static int +xfs_reflink_fill_delalloc( +	struct xfs_inode	*ip, +	struct xfs_bmbt_irec	*imap, +	struct xfs_bmbt_irec	*cmap, +	bool			*shared, +	uint			*lockmode, +	bool			convert_now) +{ +	struct xfs_mount	*mp = ip->i_mount; +	struct xfs_trans	*tp; +	int			nimaps; +	int			error; +	bool			found; + +	do { +		xfs_iunlock(ip, *lockmode); +		*lockmode = 0; + +		error = xfs_trans_alloc_inode(ip, &M_RES(mp)->tr_write, 0, 0, +				false, &tp); +		if (error) +			return error; + +		*lockmode = XFS_ILOCK_EXCL; + +		error = xfs_find_trim_cow_extent(ip, imap, cmap, shared, +				&found); +		if (error || !*shared) +			goto out_trans_cancel; + +		if (found) { +			xfs_trans_cancel(tp); +			break; +		} + +		ASSERT(isnullstartblock(cmap->br_startblock) || +		       cmap->br_startblock == DELAYSTARTBLOCK); + +		/* +		 * Replace delalloc reservation with an unwritten extent. +		 */ +		nimaps = 1; +		error = xfs_bmapi_write(tp, ip, cmap->br_startoff, +				cmap->br_blockcount, +				XFS_BMAPI_COWFORK | XFS_BMAPI_PREALLOC, 0, +				cmap, &nimaps); +		if (error) +			goto out_trans_cancel; + +		xfs_inode_set_cowblocks_tag(ip); +		error = xfs_trans_commit(tp); +		if (error) +			return error; + +		/* +		 * Allocation succeeded but the requested range was not even +		 * partially satisfied?  Bail out! +		 */ +		if (nimaps == 0) +			return -ENOSPC; +	} while (cmap->br_startoff + cmap->br_blockcount <= imap->br_startoff); + +	return xfs_reflink_convert_unwritten(ip, imap, cmap, convert_now);  out_trans_cancel:  	xfs_trans_cancel(tp);  	return error;  } +/* Allocate all CoW reservations covering a range of blocks in a file. */ +int +xfs_reflink_allocate_cow( +	struct xfs_inode	*ip, +	struct xfs_bmbt_irec	*imap, +	struct xfs_bmbt_irec	*cmap, +	bool			*shared, +	uint			*lockmode, +	bool			convert_now) +{ +	int			error; +	bool			found; + +	ASSERT(xfs_isilocked(ip, XFS_ILOCK_EXCL)); +	if (!ip->i_cowfp) { +		ASSERT(!xfs_is_reflink_inode(ip)); +		xfs_ifork_init_cow(ip); +	} + +	error = xfs_find_trim_cow_extent(ip, imap, cmap, shared, &found); +	if (error || !*shared) +		return error; + +	/* CoW fork has a real extent */ +	if (found) +		return xfs_reflink_convert_unwritten(ip, imap, cmap, +				convert_now); + +	/* +	 * CoW fork does not have an extent and data extent is shared. +	 * Allocate a real extent in the CoW fork. +	 */ +	if (cmap->br_startoff > imap->br_startoff) +		return xfs_reflink_fill_cow_hole(ip, imap, cmap, shared, +				lockmode, convert_now); + +	/* +	 * CoW fork has a delalloc reservation. Replace it with a real extent. +	 * There may or may not be a data fork mapping. +	 */ +	if (isnullstartblock(cmap->br_startblock) || +	    cmap->br_startblock == DELAYSTARTBLOCK) +		return xfs_reflink_fill_delalloc(ip, imap, cmap, shared, +				lockmode, convert_now); + +	/* Shouldn't get here. */ +	ASSERT(0); +	return -EFSCORRUPTED; +} +  /*   * Cancel CoW reservations for some block range of an inode.   * |