After the write component of a copy-write operation finishes, clean up the bookkeeping left behind. On error, we simply free the new blocks and pass the error up. If we succeed, however, then we must remove the old data fork mapping and move the cow fork mapping to the data fork. Signed-off-by: Darrick J. Wong <darrick.wong@xxxxxxxxxx> --- fs/xfs/xfs_aops.c | 23 +++++ fs/xfs/xfs_reflink.c | 223 ++++++++++++++++++++++++++++++++++++++++++++++++++ fs/xfs/xfs_reflink.h | 4 + 3 files changed, 248 insertions(+), 2 deletions(-) diff --git a/fs/xfs/xfs_aops.c b/fs/xfs/xfs_aops.c index 7179b25..8101d6a 100644 --- a/fs/xfs/xfs_aops.c +++ b/fs/xfs/xfs_aops.c @@ -195,7 +195,8 @@ xfs_finish_ioend( if (atomic_dec_and_test(&ioend->io_remaining)) { struct xfs_mount *mp = XFS_I(ioend->io_inode)->i_mount; - if (ioend->io_type == XFS_IO_UNWRITTEN) + if (ioend->io_type == XFS_IO_UNWRITTEN || + xfs_ioend_is_cow(ioend)) queue_work(mp->m_unwritten_workqueue, &ioend->io_work); else if (ioend->io_append_trans) queue_work(mp->m_data_workqueue, &ioend->io_work); @@ -221,6 +222,23 @@ xfs_end_io( } /* + * For a CoW extent, we need to move the mapping from the CoW fork + * to the data fork. If instead an error happened, just dump the + * new blocks. + */ + if (xfs_ioend_is_cow(ioend)) { + if (ioend->io_error) { + error = xfs_reflink_end_cow_failed(ip, ioend->io_offset, + ioend->io_size); + goto done; + } + error = xfs_reflink_end_cow(ip, ioend->io_offset, + ioend->io_size); + if (error) + goto done; + } + + /* * For unwritten extents we need to issue transactions to convert a * range to normal written extens after the data I/O has finished. * Detecting and handling completion IO errors is done individually @@ -235,7 +253,7 @@ xfs_end_io( } else if (ioend->io_append_trans) { error = xfs_setfilesize_ioend(ioend); } else { - ASSERT(!xfs_ioend_is_append(ioend)); + ASSERT(!xfs_ioend_is_append(ioend) || xfs_ioend_is_cow(ioend)); } done: @@ -274,6 +292,7 @@ xfs_alloc_ioend( ioend->io_offset = 0; ioend->io_size = 0; ioend->io_append_trans = NULL; + ioend->io_flags = 0; INIT_WORK(&ioend->io_work, xfs_end_io); return ioend; diff --git a/fs/xfs/xfs_reflink.c b/fs/xfs/xfs_reflink.c index 65d4c2d..9c1c262 100644 --- a/fs/xfs/xfs_reflink.c +++ b/fs/xfs/xfs_reflink.c @@ -321,3 +321,226 @@ xfs_reflink_find_cow_mapping( return 0; } + +/** + * xfs_reflink_end_cow_failed() -- Throw out a CoW mapping if there was an I/O + * error while writing the new block. + * @ip: The XFS inode. + * @offset: File offset, in bytes. + * @count: Number of bytes. + */ +int +xfs_reflink_end_cow_failed( + struct xfs_inode *ip, + xfs_off_t offset, + size_t count) +{ + struct xfs_bmbt_irec irec; + struct xfs_trans *tp; + struct xfs_ifork *ifp; + xfs_fileoff_t offset_fsb; + xfs_fileoff_t end_fsb; + xfs_filblks_t count_fsb; + xfs_fsblock_t firstfsb; + xfs_extnum_t idx; + struct xfs_bmap_free free_list; + int committed; + int error; + struct xfs_bmbt_rec_host *gotp; + + trace_xfs_reflink_end_cow_failed(ip, offset, count); + + offset_fsb = XFS_B_TO_FSBT(ip->i_mount, offset); + end_fsb = XFS_B_TO_FSB(ip->i_mount, (xfs_ufsize_t)offset + count); + count_fsb = (xfs_filblks_t)(end_fsb - offset_fsb); + + /* Start a rolling transaction to switch the mappings */ + tp = xfs_trans_alloc(ip->i_mount, XFS_TRANS_STRAT_WRITE); + error = xfs_trans_reserve(tp, &M_RES(ip->i_mount)->tr_write, 0, 0); + if (error) { + xfs_trans_cancel(tp); + goto out; + } + + xfs_ilock(ip, XFS_ILOCK_EXCL); + xfs_trans_ijoin(tp, ip, 0); + xfs_bmap_init(&free_list, &firstfsb); + + /* Go find the old extent in the CoW fork. */ + ifp = XFS_IFORK_PTR(ip, XFS_COW_FORK); + gotp = xfs_iext_bno_to_ext(ifp, offset_fsb, &idx); + while (gotp) { + xfs_bmbt_get_all(gotp, &irec); + + if (irec.br_startoff >= end_fsb) + break; + + ASSERT(!isnullstartblock(irec.br_startblock)); + xfs_trim_extent(&irec, offset_fsb, count_fsb); + trace_xfs_reflink_cow_remap(ip, &irec); + + /* Remove the EFD. */ +#if 0 + efd = xfs_trans_get_efd(tp, eio->rlei_efi, 1); + xfs_trans_undelete_extent(tp, efd, imap->br_startblock, + imap->br_blockcount); +#endif + + xfs_bmap_add_free(ip->i_mount, &free_list, irec.br_startblock, + irec.br_blockcount, NULL); + + /* Roll on... */ + idx++; + if (idx >= ifp->if_bytes / sizeof(xfs_bmbt_rec_t)) + break; + gotp = xfs_iext_get_ext(ifp, idx); + } + + /* Process all the deferred stuff. */ + error = xfs_bmap_finish(&tp, &free_list, &committed, NULL); + if (error) + goto out_cancel; + + error = xfs_trans_commit(tp); + xfs_iunlock(ip, XFS_ILOCK_EXCL); + if (error) + goto out; + return 0; + +out_cancel: + xfs_trans_cancel(tp); + xfs_iunlock(ip, XFS_ILOCK_EXCL); +out: + trace_xfs_reflink_end_cow_failed_error(ip, error, _RET_IP_); + return error; +} + +/** + * xfs_reflink_end_cow() -- Remap the data fork after a successful CoW. + * @ip: The XFS inode. + * @offset: File offset, in bytes. + * @count: Number of bytes. + */ +int +xfs_reflink_end_cow( + struct xfs_inode *ip, + xfs_off_t offset, + size_t count) +{ + struct xfs_bmbt_irec irec; + struct xfs_bmbt_irec imap; + int nimaps = 1; + struct xfs_trans *tp; + struct xfs_ifork *ifp; + xfs_fileoff_t offset_fsb; + xfs_fileoff_t end_fsb; + xfs_filblks_t count_fsb; + xfs_fsblock_t firstfsb; + xfs_extnum_t idx; + struct xfs_bmap_free free_list; + int committed; + int done; + int error; + unsigned int resblks; + struct xfs_bmbt_rec_host *gotp; + + trace_xfs_reflink_end_cow(ip, offset, count); + + offset_fsb = XFS_B_TO_FSBT(ip->i_mount, offset); + end_fsb = XFS_B_TO_FSB(ip->i_mount, (xfs_ufsize_t)offset + count); + count_fsb = (xfs_filblks_t)(end_fsb - offset_fsb); + + /* Start a rolling transaction to switch the mappings */ + resblks = XFS_EXTENTADD_SPACE_RES(ip->i_mount, XFS_DATA_FORK); + tp = xfs_trans_alloc(ip->i_mount, XFS_TRANS_STRAT_WRITE); + error = xfs_trans_reserve(tp, &M_RES(ip->i_mount)->tr_write, + resblks, 0); + if (error) { + xfs_trans_cancel(tp); + goto out; + } + + xfs_ilock(ip, XFS_ILOCK_EXCL); + xfs_trans_ijoin(tp, ip, 0); + xfs_bmap_init(&free_list, &firstfsb); + + /* Go find the old extent in the CoW fork. */ + ifp = XFS_IFORK_PTR(ip, XFS_COW_FORK); + gotp = xfs_iext_bno_to_ext(ifp, offset_fsb, &idx); + while (gotp) { + xfs_bmbt_get_all(gotp, &irec); + + if (irec.br_startoff >= end_fsb) + break; + + ASSERT(!isnullstartblock(irec.br_startblock)); + xfs_trim_extent(&irec, offset_fsb, count_fsb); + trace_xfs_reflink_cow_remap(ip, &irec); + + /* Unmap the old blocks in the data fork. */ + done = false; + while (!done) { + error = xfs_bunmapi(tp, ip, irec.br_startoff, + irec.br_blockcount, 0, 1, + &firstfsb, &free_list, &done); + if (error) + goto out_freelist; + + error = xfs_trans_roll(&tp, ip); + if (error) + goto out_freelist; + } + + /* Remove the EFD. */ +#if 0 + efd = xfs_trans_get_efd(tp, eio->rlei_efi, 1); + xfs_trans_undelete_extent(tp, efd, imap->br_startblock, + imap->br_blockcount); +#endif + + /* Map the new blocks into the data fork. */ + firstfsb = irec.br_startblock; + error = xfs_bmapi_write(tp, ip, irec.br_startoff, + irec.br_blockcount, + XFS_BMAPI_REMAP, &firstfsb, + irec.br_blockcount, &imap, &nimaps, + &free_list); + if (error) + goto out_freelist; + + /* Remove the mapping from the CoW fork. */ + error = xfs_bunmapi_cow(ip, &idx, &irec); + if (error) + goto out_freelist; + + error = xfs_trans_roll(&tp, ip); + if (error) + goto out_freelist; + + /* Roll on... */ + idx++; + if (idx >= ifp->if_bytes / sizeof(xfs_bmbt_rec_t)) + break; + gotp = xfs_iext_get_ext(ifp, idx); + } + + /* Process all the deferred stuff. */ + error = xfs_bmap_finish(&tp, &free_list, &committed, NULL); + if (error) + goto out_cancel; + + error = xfs_trans_commit(tp); + xfs_iunlock(ip, XFS_ILOCK_EXCL); + if (error) + goto out; + return 0; + +out_freelist: + xfs_bmap_cancel(&free_list); +out_cancel: + xfs_trans_cancel(tp); + xfs_iunlock(ip, XFS_ILOCK_EXCL); +out: + trace_xfs_reflink_end_cow_error(ip, error, _RET_IP_); + return error; +} diff --git a/fs/xfs/xfs_reflink.h b/fs/xfs/xfs_reflink.h index 1018ac9..8ec1ebb 100644 --- a/fs/xfs/xfs_reflink.h +++ b/fs/xfs/xfs_reflink.h @@ -23,5 +23,9 @@ extern int xfs_reflink_reserve_cow_range(struct xfs_inode *ip, xfs_off_t pos, extern bool xfs_reflink_is_cow_pending(struct xfs_inode *ip, xfs_off_t offset); extern int xfs_reflink_find_cow_mapping(struct xfs_inode *ip, xfs_off_t offset, struct xfs_bmbt_irec *imap, bool *need_alloc); +extern int xfs_reflink_end_cow_failed(struct xfs_inode *ip, xfs_off_t offset, + size_t count); +extern int xfs_reflink_end_cow(struct xfs_inode *ip, xfs_off_t offset, + size_t count); #endif /* __XFS_REFLINK_H */ _______________________________________________ xfs mailing list xfs@xxxxxxxxxxx http://oss.sgi.com/mailman/listinfo/xfs