diff options
Diffstat (limited to 'fs/xfs/xfs_reflink.c')
-rw-r--r-- | fs/xfs/xfs_reflink.c | 66 |
1 files changed, 64 insertions, 2 deletions
diff --git a/fs/xfs/xfs_reflink.c b/fs/xfs/xfs_reflink.c index b93faa819894..0c73aa441c47 100644 --- a/fs/xfs/xfs_reflink.c +++ b/fs/xfs/xfs_reflink.c @@ -1348,6 +1348,13 @@ xfs_reflink_remap_blocks( len = min_t(xfs_filblks_t, XFS_B_TO_FSB(mp, remap_len), XFS_MAX_FILEOFF); + /* + * Make sure the end is aligned with a rt extent (if desired), since + * the end of the range could be EOF. + */ + if (xfs_inode_has_bigrtextents(dest)) + len = roundup_64(len, mp->m_sb.sb_rextsize); + trace_xfs_reflink_remap_blocks(src, srcoff, len, dest, destoff); while (len > 0) { @@ -1421,6 +1428,50 @@ xfs_reflink_zero_posteof( &xfs_buffered_write_iomap_ops); } +/* Adjust the length of the remap operation to end on a rt extent boundary. */ +STATIC int +xfs_reflink_remap_adjust_rtlen( + struct xfs_inode *src, + loff_t pos_in, + struct xfs_inode *dest, + loff_t pos_out, + loff_t *len, + unsigned int remap_flags) +{ + struct xfs_mount *mp = src->i_mount; + uint32_t mod; + + div_u64_rem(*len, XFS_FSB_TO_B(mp, mp->m_sb.sb_rextsize), &mod); + + /* + * We previously checked the rtextent alignment of both offsets, so we + * now have to check the alignment of the length. The VFS remap prep + * function can change the length on us, so we can only make length + * adjustments after that. If the length is aligned to an rtextent, + * we're trivially good to go. + * + * Otherwise, the length is not aligned to an rt extent. If the source + * file's range ends at EOF, the VFS ensured that the dest file's range + * also ends at EOF. The actual remap function will round the (byte) + * length up to the nearest rtextent unit, so we're ok here too. + */ + if (mod == 0 || pos_in + *len == i_size_read(VFS_I(src))) + return 0; + + /* + * Otherwise, the only thing we can do is round the request length down + * to an rt extent boundary. If the caller doesn't allow that, we are + * finished. + */ + if (!(remap_flags & REMAP_FILE_CAN_SHORTEN)) + return -EINVAL; + + /* Back off by a single extent. */ + (*len) -= mod; + trace_xfs_reflink_remap_adjust_rtlen(src, pos_in, *len, dest, pos_out); + return 0; +} + /* * Prepare two files for range cloning. Upon a successful return both inodes * will have the iolock and mmaplock held, the page cache of the out file will @@ -1480,11 +1531,22 @@ xfs_reflink_remap_prep( if (IS_DAX(inode_in) || IS_DAX(inode_out)) goto out_unlock; - ret = generic_remap_file_range_prep(file_in, pos_in, file_out, pos_out, - len, remap_flags); + ASSERT(is_power_of_2(xfs_inode_alloc_unitsize(dest))); + + ret = __generic_remap_file_range_prep(file_in, pos_in, file_out, + pos_out, len, remap_flags, + xfs_inode_alloc_unitsize(dest)); if (ret || *len == 0) goto out_unlock; + /* Make sure the end is aligned with a rt extent. */ + if (xfs_inode_has_bigrtextents(src)) { + ret = xfs_reflink_remap_adjust_rtlen(src, pos_in, dest, + pos_out, len, remap_flags); + if (ret || *len == 0) + goto out_unlock; + } + /* Attach dquots to dest inode before changing block map */ ret = xfs_qm_dqattach(dest); if (ret) |