summaryrefslogtreecommitdiff
path: root/fs/xfs/xfs_reflink.c
diff options
context:
space:
mode:
Diffstat (limited to 'fs/xfs/xfs_reflink.c')
-rw-r--r--fs/xfs/xfs_reflink.c66
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)