xfs: condense extended attributes after a mapping exchange operation
authorDarrick J. Wong <djwong@kernel.org>
Mon, 15 Apr 2024 21:54:20 +0000 (14:54 -0700)
committerDarrick J. Wong <djwong@kernel.org>
Mon, 15 Apr 2024 21:54:20 +0000 (14:54 -0700)
Add a new file mapping exchange flag that enables us to perform
post-exchange processing on file2 once we're done exchanging the extent
mappings.  If we were swapping mappings between extended attribute
forks, we want to be able to convert file2's attr fork from block to
inline format.

(This implies that all fork contents are exchanged.)

This isn't used anywhere right now, but we need to have the basic ondisk
flags in place so that a future online xattr repair feature can create
salvaged attrs in a temporary file and exchange the attr fork mappings
when ready.  If one file is in extents format and the other is inline,
we will have to promote both to extents format to perform the exchange.
After the exchange, we can try to condense the fixed file's attr fork
back down to inline format if possible.

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

index 3b1f29e95fea677b0962ae0c69a31f4cefcd46c1..e46b314fa0cfdd590067e7573dccd6686a749aff 100644 (file)
 #include "xfs_errortag.h"
 #include "xfs_health.h"
 #include "xfs_exchmaps_item.h"
+#include "xfs_da_format.h"
+#include "xfs_da_btree.h"
+#include "xfs_attr_leaf.h"
+#include "xfs_attr.h"
 
 struct kmem_cache      *xfs_exchmaps_intent_cache;
 
@@ -121,7 +125,8 @@ 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_CLEAR_INO2_REFLINK |
+                                __XFS_EXCHMAPS_INO2_SHORTFORM);
 }
 
 /* Check all mappings to make sure we can actually exchange them. */
@@ -360,6 +365,36 @@ xfs_exchmaps_one_step(
        xmi_advance(xmi, irec1);
 }
 
+/* Convert inode2's leaf attr fork back to shortform, if possible.. */
+STATIC int
+xfs_exchmaps_attr_to_sf(
+       struct xfs_trans                *tp,
+       struct xfs_exchmaps_intent      *xmi)
+{
+       struct xfs_da_args      args = {
+               .dp             = xmi->xmi_ip2,
+               .geo            = tp->t_mountp->m_attr_geo,
+               .whichfork      = XFS_ATTR_FORK,
+               .trans          = tp,
+       };
+       struct xfs_buf          *bp;
+       int                     forkoff;
+       int                     error;
+
+       if (!xfs_attr_is_leaf(xmi->xmi_ip2))
+               return 0;
+
+       error = xfs_attr3_leaf_read(tp, xmi->xmi_ip2, 0, &bp);
+       if (error)
+               return error;
+
+       forkoff = xfs_attr_shortform_allfit(bp, xmi->xmi_ip2);
+       if (forkoff == 0)
+               return 0;
+
+       return xfs_attr3_leaf_to_shortform(bp, &args, forkoff);
+}
+
 /* Clear the reflink flag after an exchange. */
 static inline void
 xfs_exchmaps_clear_reflink(
@@ -378,6 +413,16 @@ xfs_exchmaps_do_postop_work(
        struct xfs_trans                *tp,
        struct xfs_exchmaps_intent      *xmi)
 {
+       if (xmi->xmi_flags & __XFS_EXCHMAPS_INO2_SHORTFORM) {
+               int                     error = 0;
+
+               if (xmi->xmi_flags & XFS_EXCHMAPS_ATTR_FORK)
+                       error = xfs_exchmaps_attr_to_sf(tp, xmi);
+               xmi->xmi_flags &= ~__XFS_EXCHMAPS_INO2_SHORTFORM;
+               if (error)
+                       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;
@@ -809,8 +854,10 @@ xfs_exchmaps_init_intent(
        xmi->xmi_isize1 = xmi->xmi_isize2 = -1;
        xmi->xmi_flags = req->flags & XFS_EXCHMAPS_PARAMS;
 
-       if (xfs_exchmaps_whichfork(xmi) == XFS_ATTR_FORK)
+       if (xfs_exchmaps_whichfork(xmi) == XFS_ATTR_FORK) {
+               xmi->xmi_flags |= __XFS_EXCHMAPS_INO2_SHORTFORM;
                return xmi;
+       }
 
        if (req->flags & XFS_EXCHMAPS_SET_SIZES) {
                xmi->xmi_flags |= XFS_EXCHMAPS_SET_SIZES;
@@ -1031,6 +1078,8 @@ xfs_exchange_mappings(
 {
        struct xfs_exchmaps_intent      *xmi;
 
+       BUILD_BUG_ON(XFS_EXCHMAPS_INTERNAL_FLAGS & XFS_EXCHMAPS_LOGGED_FLAGS);
+
        xfs_assert_ilocked(req->ip1, XFS_ILOCK_EXCL);
        xfs_assert_ilocked(req->ip2, XFS_ILOCK_EXCL);
        ASSERT(!(req->flags & ~XFS_EXCHMAPS_LOGGED_FLAGS));
index e8fc3f80c68c2721a16f1900f2bd1c397d2a68b3..d8718fca606e57f259a6c6e44d69d886e277c839 100644 (file)
@@ -27,6 +27,11 @@ struct xfs_exchmaps_intent {
        uint64_t                xmi_flags;      /* XFS_EXCHMAPS_* flags */
 };
 
+/* Try to convert inode2 from block to short format at the end, if possible. */
+#define __XFS_EXCHMAPS_INO2_SHORTFORM  (1ULL << 63)
+
+#define XFS_EXCHMAPS_INTERNAL_FLAGS    (__XFS_EXCHMAPS_INO2_SHORTFORM)
+
 /* flags that can be passed to xfs_exchmaps_{estimate,mappings} */
 #define XFS_EXCHMAPS_PARAMS            (XFS_EXCHMAPS_ATTR_FORK | \
                                         XFS_EXCHMAPS_SET_SIZES | \
index 729e728c2076f788936ac7c9100a8dca96836b4d..caef95f2c87cbfdc7cf963cddf4480410a7a567f 100644 (file)
@@ -4779,7 +4779,8 @@ DEFINE_XFBTREE_FREESP_EVENT(xfbtree_free_block);
        { XFS_EXCHMAPS_SET_SIZES,               "SETSIZES" }, \
        { XFS_EXCHMAPS_INO1_WRITTEN,            "INO1_WRITTEN" }, \
        { XFS_EXCHMAPS_CLEAR_INO1_REFLINK,      "CLEAR_INO1_REFLINK" }, \
-       { XFS_EXCHMAPS_CLEAR_INO2_REFLINK,      "CLEAR_INO2_REFLINK" }
+       { XFS_EXCHMAPS_CLEAR_INO2_REFLINK,      "CLEAR_INO2_REFLINK" }, \
+       { __XFS_EXCHMAPS_INO2_SHORTFORM,        "INO2_SF" }
 
 DEFINE_INODE_IREC_EVENT(xfs_exchmaps_mapping1_skip);
 DEFINE_INODE_IREC_EVENT(xfs_exchmaps_mapping1);