diff --git a/fs/xfs/libxfs/xfs_exchmaps.c b/fs/xfs/libxfs/xfs_exchmaps.c index 6a66b6075e0a..f527fce46def 100644 --- a/fs/xfs/libxfs/xfs_exchmaps.c +++ b/fs/xfs/libxfs/xfs_exchmaps.c @@ -49,7 +49,7 @@ struct xfs_exchmaps_adjacent { .right2 = { .br_startblock = HOLESTARTBLOCK }, \ } -/* Information to reset reflink flag / CoW fork state after an exchange. */ +/* Information to reset CoW fork state after an exchange. */ /* * If the reflink flag is set on either inode, make sure it has an incore CoW @@ -127,9 +127,7 @@ xmi_has_more_exchange_work(const struct xfs_exchmaps_intent *xmi) static inline bool xmi_has_postop_work(const struct xfs_exchmaps_intent *xmi) { - return xmi->xmi_flags & (XFS_EXCHMAPS_CLEAR_INO1_REFLINK | - XFS_EXCHMAPS_CLEAR_INO2_REFLINK | - __XFS_EXCHMAPS_INO2_SHORTFORM); + return xmi->xmi_flags & __XFS_EXCHMAPS_INO2_SHORTFORM; } /* Check all mappings to make sure we can actually exchange them. */ @@ -525,18 +523,6 @@ free: return error; } -/* Clear the reflink flag after an exchange. */ -static inline void -xfs_exchmaps_clear_reflink( - struct xfs_trans *tp, - struct xfs_inode *ip) -{ - trace_xfs_reflink_unset_inode_flag(ip); - - ip->i_diflags2 &= ~XFS_DIFLAG2_REFLINK; - xfs_trans_log_inode(tp, ip, XFS_ILOG_CORE); -} - /* Finish whatever work might come after an exchange operation. */ static int xfs_exchmaps_do_postop_work( @@ -557,16 +543,6 @@ xfs_exchmaps_do_postop_work( return error; } - if (xmi->xmi_flags & XFS_EXCHMAPS_CLEAR_INO1_REFLINK) { - xfs_exchmaps_clear_reflink(tp, xmi->xmi_ip1); - xmi->xmi_flags &= ~XFS_EXCHMAPS_CLEAR_INO1_REFLINK; - } - - if (xmi->xmi_flags & XFS_EXCHMAPS_CLEAR_INO2_REFLINK) { - xfs_exchmaps_clear_reflink(tp, xmi->xmi_ip2); - xmi->xmi_flags &= ~XFS_EXCHMAPS_CLEAR_INO2_REFLINK; - } - return 0; } @@ -948,46 +924,14 @@ xfs_exchmaps_intent_destroy_cache(void) } /* - * Decide if we will exchange the reflink flags between the two files after the - * exchange. The only time we want to do this is if we're exchanging all - * mappings under EOF and the inode reflink flags have different states. + * Allocate and initialize a new incore intent item from a request. */ -static inline bool -xmi_can_exchange_reflink_flags( - const struct xfs_exchmaps_req *req, - unsigned int reflink_state) -{ - struct xfs_mount *mp = req->ip1->i_mount; - - /* - * The INO1_WRITTEN optimization can skip exchanging hole and - * unwritten mappings, which means we cannot guarantee that all - * shared extents actually moved to the other file. Clearing the - * reflink flag of an inode that still holds shared extents breaks - * the CoW write path, so refuse to exchange the flags in that case. - */ - if (req->flags & XFS_EXCHMAPS_INO1_WRITTEN) - return false; - - if (hweight32(reflink_state) != 1) - return false; - if (req->startoff1 != 0 || req->startoff2 != 0) - return false; - if (req->blockcount != XFS_B_TO_FSB(mp, req->ip1->i_disk_size)) - return false; - if (req->blockcount != XFS_B_TO_FSB(mp, req->ip2->i_disk_size)) - return false; - return true; -} - - -/* Allocate and initialize a new incore intent item from a request. */ struct xfs_exchmaps_intent * xfs_exchmaps_init_intent( const struct xfs_exchmaps_req *req) { struct xfs_exchmaps_intent *xmi; - unsigned int rs = 0; + struct xfs_mount *mp = req->ip1->i_mount; xmi = kmem_cache_zalloc(xfs_exchmaps_intent_cache, GFP_NOFS | __GFP_NOFAIL); @@ -1005,27 +949,18 @@ xfs_exchmaps_init_intent( return xmi; } - if (req->flags & XFS_EXCHMAPS_SET_SIZES) { - xmi->xmi_flags |= XFS_EXCHMAPS_SET_SIZES; - xmi->xmi_isize1 = req->ip2->i_disk_size; - xmi->xmi_isize2 = req->ip1->i_disk_size; - } - - /* Record the state of each inode's reflink flag before the op. */ - if (xfs_is_reflink_inode(req->ip1)) - rs |= 1; - if (xfs_is_reflink_inode(req->ip2)) - rs |= 2; - /* - * Figure out if we're clearing the reflink flags (which effectively - * exchanges them) after the operation. + * If the caller wanted, set each file's size to that file's exchange + * offset + length exchanged from the other file because the ranges in + * each file might be different lengths. */ - if (xmi_can_exchange_reflink_flags(req, rs)) { - if (rs & 1) - xmi->xmi_flags |= XFS_EXCHMAPS_CLEAR_INO1_REFLINK; - if (rs & 2) - xmi->xmi_flags |= XFS_EXCHMAPS_CLEAR_INO2_REFLINK; + if (req->flags & XFS_EXCHMAPS_SET_SIZES) { + loff_t off1 = XFS_FSB_TO_B(mp, xmi->xmi_startoff1); + loff_t off2 = XFS_FSB_TO_B(mp, xmi->xmi_startoff2); + + xmi->xmi_flags |= XFS_EXCHMAPS_SET_SIZES; + xmi->xmi_isize1 = off1 + (req->ip2->i_disk_size - off2); + xmi->xmi_isize2 = off2 + (req->ip1->i_disk_size - off1); } if (S_ISDIR(VFS_I(xmi->xmi_ip2)->i_mode) || diff --git a/fs/xfs/libxfs/xfs_log_format.h b/fs/xfs/libxfs/xfs_log_format.h index a4e1b3eb425c..6e620380d5dc 100644 --- a/fs/xfs/libxfs/xfs_log_format.h +++ b/fs/xfs/libxfs/xfs_log_format.h @@ -927,10 +927,8 @@ struct xfs_xmi_log_format { */ #define XFS_EXCHMAPS_INO1_WRITTEN (1ULL << 2) -/* Clear the reflink flag from inode1 after the operation. */ +/* No longer set, but older intents may carry them; keep them valid. */ #define XFS_EXCHMAPS_CLEAR_INO1_REFLINK (1ULL << 3) - -/* Clear the reflink flag from inode2 after the operation. */ #define XFS_EXCHMAPS_CLEAR_INO2_REFLINK (1ULL << 4) #define XFS_EXCHMAPS_LOGGED_FLAGS (XFS_EXCHMAPS_ATTR_FORK | \ diff --git a/fs/xfs/xfs_bmap_util.c b/fs/xfs/xfs_bmap_util.c index 268d159339d0..865aec3ae5ab 100644 --- a/fs/xfs/xfs_bmap_util.c +++ b/fs/xfs/xfs_bmap_util.c @@ -1544,7 +1544,6 @@ xfs_swap_extents( struct xfs_bstat *sbp = &sxp->sx_stat; int src_log_flags, target_log_flags; int error = 0; - uint64_t f; int resblks = 0; unsigned int flags = 0; struct timespec64 ctime, mtime; @@ -1700,14 +1699,12 @@ xfs_swap_extents( if (error) goto out_trans_cancel; - /* Do we have to swap reflink flags? */ - if ((ip->i_diflags2 & XFS_DIFLAG2_REFLINK) ^ - (tip->i_diflags2 & XFS_DIFLAG2_REFLINK)) { - f = ip->i_diflags2 & XFS_DIFLAG2_REFLINK; - ip->i_diflags2 &= ~XFS_DIFLAG2_REFLINK; - ip->i_diflags2 |= tip->i_diflags2 & XFS_DIFLAG2_REFLINK; - tip->i_diflags2 &= ~XFS_DIFLAG2_REFLINK; - tip->i_diflags2 |= f & XFS_DIFLAG2_REFLINK; + /* Either inode may now own shared blocks; flag both, with CoW forks. */ + if ((ip->i_diflags2 | tip->i_diflags2) & XFS_DIFLAG2_REFLINK) { + ip->i_diflags2 |= XFS_DIFLAG2_REFLINK; + tip->i_diflags2 |= XFS_DIFLAG2_REFLINK; + xfs_ifork_init_cow(ip); + xfs_ifork_init_cow(tip); } /* Swap the cow forks. */ diff --git a/fs/xfs/xfs_exchrange.c b/fs/xfs/xfs_exchrange.c index fafb4e3f065c..935b561dfd3c 100644 --- a/fs/xfs/xfs_exchrange.c +++ b/fs/xfs/xfs_exchrange.c @@ -290,17 +290,20 @@ retry: goto out_unlock; /* - * If the caller wanted us to exchange the contents of two complete - * files of unequal length, exchange the incore sizes now. This should - * be safe because we flushed both files' page caches, exchanged all - * the mappings, and updated the ondisk sizes. + * If the caller wanted, set each file's size to that file's exchange + * offset + length exchanged from the other file because the ranges in + * each file might be different lengths. This should be safe because + * we flushed both files' page caches, exchanged all the mappings, and + * updated the ondisk sizes. */ if (fxr->flags & XFS_EXCHANGE_RANGE_TO_EOF) { - loff_t temp; + loff_t old_ip1_size = i_size_read(VFS_I(ip1)); + loff_t old_ip2_size = i_size_read(VFS_I(ip2)); - temp = i_size_read(VFS_I(ip2)); - i_size_write(VFS_I(ip2), i_size_read(VFS_I(ip1))); - i_size_write(VFS_I(ip1), temp); + i_size_write(VFS_I(ip2), fxr->file2_offset + + (old_ip1_size - fxr->file1_offset)); + i_size_write(VFS_I(ip1), fxr->file1_offset + + (old_ip2_size - fxr->file2_offset)); } out_unlock: @@ -445,6 +448,30 @@ xfs_exchange_range_checks( return blen == fxr->length ? 0 : -EINVAL; } +/* Flush all relevant dirty pagecache near the range to be exchanged. */ +static inline int +xfs_exchange_range_flush( + struct xfs_inode *ip, + const struct xfs_exchrange *fxr, + loff_t offset) +{ + /* + * If TO_EOF is set, the ondisk file size update logic depends on the + * ondisk file size matching the incore file size. Flush everything. + */ + if (fxr->flags & XFS_EXCHANGE_RANGE_TO_EOF) + return filemap_write_and_wait(VFS_I(ip)->i_mapping); + + /* + * Because we're updating the ondisk mappings, flush all dirty data + * between the ondisk size and the exchange offset to reduce the chance + * of zeroed file contents in that gap after a crash. + */ + return filemap_write_and_wait_range(VFS_I(ip)->i_mapping, + min(offset, ip->i_disk_size), + offset + fxr->length - 1); +} + /* * Check that the two inodes are eligible for range exchanges, the ranges make * sense, and then flush all dirty data. Caller must ensure that the inodes @@ -470,15 +497,11 @@ xfs_exchange_range_prep( if (!same_inode) inode_dio_wait(inode2); - error = filemap_write_and_wait_range(inode1->i_mapping, - fxr->file1_offset, - fxr->file1_offset + fxr->length - 1); + error = xfs_exchange_range_flush(XFS_I(inode1), fxr, fxr->file1_offset); if (error) return error; - error = filemap_write_and_wait_range(inode2->i_mapping, - fxr->file2_offset, - fxr->file2_offset + fxr->length - 1); + error = xfs_exchange_range_flush(XFS_I(inode2), fxr, fxr->file2_offset); if (error) return error;