xfs: support non-power-of-two rtextsize with exchange-range
authorDarrick J. Wong <djwong@kernel.org>
Mon, 15 Apr 2024 21:54:23 +0000 (14:54 -0700)
committerDarrick J. Wong <djwong@kernel.org>
Mon, 15 Apr 2024 21:54:23 +0000 (14:54 -0700)
The generic exchange-range alignment checks use (fast) bitmasking
operations 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 with a non-power-of-two
extent size, so we have to copy-pasta the generic checks using long
division for this to work properly.

Signed-off-by: Darrick J. Wong <djwong@kernel.org>
Reviewed-by: Christoph Hellwig <hch@lst.de>
fs/xfs/xfs_exchrange.c

index 90baf12bd97f9597a2080a1b42338f9bbdbe70f5..c8a655c92c92f4e4f4c1680d8dada1d5023bdadb 100644 (file)
@@ -504,6 +504,75 @@ xfs_exchange_range_finish(
        return file_remove_privs(fxr->file2);
 }
 
+/*
+ * Check the alignment of an exchange request when the allocation unit size
+ * isn't a power of two.  The generic file-level helpers use (fast)
+ * bitmask-based alignment checks, but here we have to use slow long division.
+ */
+static int
+xfs_exchrange_check_rtalign(
+       const struct xfs_exchrange      *fxr,
+       struct xfs_inode                *ip1,
+       struct xfs_inode                *ip2,
+       unsigned int                    alloc_unit)
+{
+       uint64_t                        length = fxr->length;
+       uint64_t                        blen;
+       loff_t                          size1, size2;
+
+       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, alloc_unit) ||
+           !isaligned_64(fxr->file2_offset, alloc_unit))
+               return -EINVAL;
+
+       if (fxr->flags & XFS_EXCHANGE_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, alloc_unit) - fxr->file1_offset;
+       else if (fxr->file2_offset + length == size2)
+               blen = roundup_64(size2, alloc_unit) - fxr->file2_offset;
+       else if (!isaligned_64(length, alloc_unit))
+               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, alloc_unit))
+               return 0;
+
+       blen = length;
+       if (fxr->file2_offset + length < size2)
+               blen = rounddown_64(blen, alloc_unit);
+
+       if (fxr->file1_offset + blen < size1)
+               blen = rounddown_64(blen, alloc_unit);
+
+       return blen == length ? 0 : -EINVAL;
+}
+
 /* Prepare two files to have their data exchanged. */
 STATIC int
 xfs_exchrange_prep(
@@ -511,6 +580,7 @@ xfs_exchrange_prep(
        struct xfs_inode        *ip1,
        struct xfs_inode        *ip2)
 {
+       struct xfs_mount        *mp = ip2->i_mount;
        unsigned int            alloc_unit = xfs_inode_alloc_unitsize(ip2);
        int                     error;
 
@@ -520,13 +590,18 @@ xfs_exchrange_prep(
        if (XFS_IS_REALTIME_INODE(ip1) != XFS_IS_REALTIME_INODE(ip2))
                return -EINVAL;
 
-       /*
-        * The alignment checks in the generic 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.
-        */
-       if (!is_power_of_2(alloc_unit))
-               return -EOPNOTSUPP;
+       /* Check non-power of two alignment issues, if necessary. */
+       if (!is_power_of_2(alloc_unit)) {
+               error = xfs_exchrange_check_rtalign(fxr, ip1, ip2, alloc_unit);
+               if (error)
+                       return error;
+
+               /*
+                * Do the generic file-level checks with the regular block
+                * alignment.
+                */
+               alloc_unit = mp->m_sb.sb_blocksize;
+       }
 
        error = xfs_exchange_range_prep(fxr, alloc_unit);
        if (error || fxr->length == 0)