diff options
author | Darrick J. Wong <darrick.wong@oracle.com> | 2016-10-03 18:11:37 +0200 |
---|---|---|
committer | Darrick J. Wong <darrick.wong@oracle.com> | 2016-10-06 01:26:04 +0200 |
commit | 0613f16cd20174d701853f1580ad44a9a6791ff2 (patch) | |
tree | a638ec8569cb6151628075d75c8dd6bb1fafd9cc /fs/xfs/xfs_reflink.c | |
parent | xfs: report shared extent mappings to userspace correctly (diff) | |
download | linux-0613f16cd20174d701853f1580ad44a9a6791ff2.tar.xz linux-0613f16cd20174d701853f1580ad44a9a6791ff2.zip |
xfs: implement CoW for directio writes
For O_DIRECT writes to shared blocks, we have to CoW them just like
we would with buffered writes. For writes that are not block-aligned,
just bounce them to the page cache.
For block-aligned writes, however, we can do better than that. Use
the same mechanisms that we employ for buffered CoW to set up a
delalloc reservation, allocate all the blocks at once, issue the
writes against the new blocks and use the same ioend functions to
remap the blocks after the write. This should be fairly performant.
Christoph discovered that xfs_reflink_allocate_cow_range may stumble
over invalid entries in the extent array given that it drops the ilock
but still expects the index to be stable. Simple fixing it to a new
lookup for every iteration still isn't correct given that
xfs_bmapi_allocate will trigger a BUG_ON() if hitting a hole, and
there is nothing preventing a xfs_bunmapi_cow call removing extents
once we dropped the ilock either.
This patch duplicates the inner loop of xfs_bmapi_allocate into a
helper for xfs_reflink_allocate_cow_range so that it can be done under
the same ilock critical section as our CoW fork delayed allocation.
The directio CoW warts will be revisited in a later patch.
Signed-off-by: Darrick J. Wong <darrick.wong@oracle.com>
Signed-off-by: Christoph Hellwig <hch@lst.de>
Diffstat (limited to 'fs/xfs/xfs_reflink.c')
-rw-r--r-- | fs/xfs/xfs_reflink.c | 107 |
1 files changed, 104 insertions, 3 deletions
diff --git a/fs/xfs/xfs_reflink.c b/fs/xfs/xfs_reflink.c index 8c3211ce1215..a30be03395fb 100644 --- a/fs/xfs/xfs_reflink.c +++ b/fs/xfs/xfs_reflink.c @@ -229,7 +229,8 @@ static int __xfs_reflink_reserve_cow( struct xfs_inode *ip, xfs_fileoff_t *offset_fsb, - xfs_fileoff_t end_fsb) + xfs_fileoff_t end_fsb, + bool *skipped) { struct xfs_bmbt_irec got, prev, imap; xfs_fileoff_t orig_end_fsb; @@ -262,8 +263,10 @@ __xfs_reflink_reserve_cow( end_fsb = orig_end_fsb = imap.br_startoff + imap.br_blockcount; /* Not shared? Just report the (potentially capped) extent. */ - if (!shared) + if (!shared) { + *skipped = true; goto done; + } /* * Fork all the shared blocks from our write offset until the end of @@ -309,6 +312,7 @@ xfs_reflink_reserve_cow_range( { struct xfs_mount *mp = ip->i_mount; xfs_fileoff_t offset_fsb, end_fsb; + bool skipped = false; int error; trace_xfs_reflink_reserve_cow_range(ip, offset, count); @@ -318,7 +322,8 @@ xfs_reflink_reserve_cow_range( xfs_ilock(ip, XFS_ILOCK_EXCL); while (offset_fsb < end_fsb) { - error = __xfs_reflink_reserve_cow(ip, &offset_fsb, end_fsb); + error = __xfs_reflink_reserve_cow(ip, &offset_fsb, end_fsb, + &skipped); if (error) { trace_xfs_reflink_reserve_cow_range_error(ip, error, _RET_IP_); @@ -330,6 +335,102 @@ xfs_reflink_reserve_cow_range( return error; } +/* Allocate all CoW reservations covering a range of blocks in a file. */ +static int +__xfs_reflink_allocate_cow( + struct xfs_inode *ip, + xfs_fileoff_t *offset_fsb, + xfs_fileoff_t end_fsb) +{ + struct xfs_mount *mp = ip->i_mount; + struct xfs_bmbt_irec imap; + struct xfs_defer_ops dfops; + struct xfs_trans *tp; + xfs_fsblock_t first_block; + xfs_fileoff_t next_fsb; + int nimaps = 1, error; + bool skipped = false; + + xfs_defer_init(&dfops, &first_block); + + error = xfs_trans_alloc(mp, &M_RES(mp)->tr_write, 0, 0, + XFS_TRANS_RESERVE, &tp); + if (error) + return error; + + xfs_ilock(ip, XFS_ILOCK_EXCL); + + next_fsb = *offset_fsb; + error = __xfs_reflink_reserve_cow(ip, &next_fsb, end_fsb, &skipped); + if (error) + goto out_trans_cancel; + + if (skipped) { + *offset_fsb = next_fsb; + goto out_trans_cancel; + } + + xfs_trans_ijoin(tp, ip, 0); + error = xfs_bmapi_write(tp, ip, *offset_fsb, next_fsb - *offset_fsb, + XFS_BMAPI_COWFORK, &first_block, + XFS_EXTENTADD_SPACE_RES(mp, XFS_DATA_FORK), + &imap, &nimaps, &dfops); + if (error) + goto out_trans_cancel; + + /* We might not have been able to map the whole delalloc extent */ + *offset_fsb = min(*offset_fsb + imap.br_blockcount, next_fsb); + + error = xfs_defer_finish(&tp, &dfops, NULL); + if (error) + goto out_trans_cancel; + + error = xfs_trans_commit(tp); + +out_unlock: + xfs_iunlock(ip, XFS_ILOCK_EXCL); + return error; +out_trans_cancel: + xfs_defer_cancel(&dfops); + xfs_trans_cancel(tp); + goto out_unlock; +} + +/* Allocate all CoW reservations covering a part of a file. */ +int +xfs_reflink_allocate_cow_range( + struct xfs_inode *ip, + xfs_off_t offset, + xfs_off_t count) +{ + struct xfs_mount *mp = ip->i_mount; + xfs_fileoff_t offset_fsb = XFS_B_TO_FSBT(mp, offset); + xfs_fileoff_t end_fsb = XFS_B_TO_FSB(mp, offset + count); + int error; + + ASSERT(xfs_is_reflink_inode(ip)); + + trace_xfs_reflink_allocate_cow_range(ip, offset, count); + + /* + * Make sure that the dquots are there. + */ + error = xfs_qm_dqattach(ip, 0); + if (error) + return error; + + while (offset_fsb < end_fsb) { + error = __xfs_reflink_allocate_cow(ip, &offset_fsb, end_fsb); + if (error) { + trace_xfs_reflink_allocate_cow_range_error(ip, error, + _RET_IP_); + break; + } + } + + return error; +} + /* * Find the CoW reservation (and whether or not it needs block allocation) * for a given byte offset of a file. |