summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDarrick J. Wong <djwong@kernel.org>2021-09-01 10:59:02 -0700
committerDarrick J. Wong <djwong@kernel.org>2021-12-15 17:29:01 -0800
commitc22df3ec633046a3a194c4dd4a46ebae822a9b7e (patch)
treea26c8e8b40e20b1021aac425f9b2ca22456d39cc
parent3e15752ca0034fdfa221ca3ead3f2128a750e000 (diff)
xfs: support non-power-of-two rtextsize with exchange-range
The VFS exchange-range alignment checks use (fast) bitmasks to perform block alignment checks on the exchange parameters. Unfortunately, bitmasks require that the alignment size be a power of two. This isn't true for realtime devices, so we have to copy-pasta the VFS checks using long division for this to work properly. Signed-off-by: Darrick J. Wong <djwong@kernel.org>
-rw-r--r--fs/xfs/xfs_xchgrange.c93
1 files changed, 82 insertions, 11 deletions
diff --git a/fs/xfs/xfs_xchgrange.c b/fs/xfs/xfs_xchgrange.c
index 6ffbfb44be49..abc1eaeb63d9 100644
--- a/fs/xfs/xfs_xchgrange.c
+++ b/fs/xfs/xfs_xchgrange.c
@@ -346,6 +346,77 @@ xfs_swap_extent_forks(
return 0;
}
+/*
+ * Check the alignment of an exchange request when the allocation unit size
+ * isn't a power of two. The VFS helpers use (fast) bitmask-based alignment
+ * checks, but here we have to use slow long division.
+ */
+static int
+xfs_xchg_range_check_rtalign(
+ struct xfs_inode *ip1,
+ struct xfs_inode *ip2,
+ const struct file_xchg_range *fxr)
+{
+ struct xfs_mount *mp = ip1->i_mount;
+ uint32_t rextbytes;
+ uint64_t length = fxr->length;
+ uint64_t blen;
+ loff_t size1, size2;
+
+ rextbytes = XFS_FSB_TO_B(mp, mp->m_sb.sb_rextsize);
+ size1 = i_size_read(VFS_I(ip1));
+ size2 = i_size_read(VFS_I(ip2));
+
+ /* The start of both ranges must be aligned to a rt extent. */
+ if (!isaligned_64(fxr->file1_offset, rextbytes) ||
+ !isaligned_64(fxr->file2_offset, rextbytes))
+ return -EINVAL;
+
+ if (fxr->flags & FILE_XCHG_RANGE_TO_EOF)
+ length = max_t(int64_t, size1 - fxr->file1_offset,
+ size2 - fxr->file2_offset);
+
+ /*
+ * If the user wanted us to exchange up to the infile's EOF, round up
+ * to the next rt extent boundary for this check. Do the same for the
+ * outfile.
+ *
+ * Otherwise, reject the range length if it's not rt extent aligned.
+ * We already confirmed the starting offsets' rt extent block
+ * alignment.
+ */
+ if (fxr->file1_offset + length == size1)
+ blen = roundup_64(size1, rextbytes) - fxr->file1_offset;
+ else if (fxr->file2_offset + length == size2)
+ blen = roundup_64(size2, rextbytes) - fxr->file2_offset;
+ else if (!isaligned_64(length, rextbytes))
+ return -EINVAL;
+ else
+ blen = length;
+
+ /* Don't allow overlapped exchanges within the same file. */
+ if (ip1 == ip2 &&
+ fxr->file2_offset + blen > fxr->file1_offset &&
+ fxr->file1_offset + blen > fxr->file2_offset)
+ return -EINVAL;
+
+ /*
+ * Ensure that we don't exchange a partial EOF rt extent into the
+ * middle of another file.
+ */
+ if (isaligned_64(length, rextbytes))
+ return 0;
+
+ blen = length;
+ if (fxr->file2_offset + length < size2)
+ blen = rounddown_64(blen, rextbytes);
+
+ if (fxr->file1_offset + blen < size1)
+ blen = rounddown_64(blen, rextbytes);
+
+ return blen == length ? 0 : -EINVAL;
+}
+
/* Prepare two files to have their data exchanged. */
int
xfs_xchg_range_prep(
@@ -355,6 +426,7 @@ xfs_xchg_range_prep(
{
struct xfs_inode *ip1 = XFS_I(file_inode(file1));
struct xfs_inode *ip2 = XFS_I(file_inode(file2));
+ unsigned int alloc_unit = xfs_inode_alloc_unitsize(ip2);
int error;
trace_xfs_xchg_range_prep(ip1, fxr, ip2, 0);
@@ -363,18 +435,17 @@ xfs_xchg_range_prep(
if (XFS_IS_REALTIME_INODE(ip1) != XFS_IS_REALTIME_INODE(ip2))
return -EINVAL;
- /*
- * The alignment checks in the VFS helpers cannot deal with allocation
- * units that are not powers of 2. This can happen with the realtime
- * volume if the extent size is set. Note that alignment checks are
- * skipped if FULL_FILES is set.
- */
- if (!(fxr->flags & FILE_XCHG_RANGE_FULL_FILES) &&
- !is_power_of_2(xfs_inode_alloc_unitsize(ip2)))
- return -EOPNOTSUPP;
+ /* Check non-power of two alignment issues, if necessary. */
+ if (XFS_IS_REALTIME_INODE(ip2) && !is_power_of_2(alloc_unit)) {
+ error = xfs_xchg_range_check_rtalign(ip1, ip2, fxr);
+ if (error)
+ return error;
+
+ /* Do the VFS checks with the regular block alignment. */
+ alloc_unit = ip1->i_mount->m_sb.sb_blocksize;
+ }
- error = generic_xchg_file_range_prep(file1, file2, fxr,
- xfs_inode_alloc_unitsize(ip2));
+ error = generic_xchg_file_range_prep(file1, file2, fxr, alloc_unit);
if (error || fxr->length == 0)
return error;