summaryrefslogtreecommitdiff
path: root/fs/xfs/scrub/repair.c
diff options
context:
space:
mode:
authorDarrick J. Wong <djwong@kernel.org>2021-09-01 10:59:05 -0700
committerDarrick J. Wong <djwong@kernel.org>2021-09-17 18:55:01 -0700
commitfe73785474465088443b88ffa203de7eb74bc62a (patch)
treea7a23e5deab00a1ae397dddc942427e6641d48a3 /fs/xfs/scrub/repair.c
parent571cbbcaa818fbc554d347f547e2bc6b0cb3cd1a (diff)
xfs: repair extended attributes
If the extended attributes look bad, try to sift through the rubble to find whatever keys/values we can, stage a new attribute structure in a temporary file and use the atomic extent swapping mechanism to commit the results in bulk. Signed-off-by: Darrick J. Wong <djwong@kernel.org>
Diffstat (limited to 'fs/xfs/scrub/repair.c')
-rw-r--r--fs/xfs/scrub/repair.c137
1 files changed, 136 insertions, 1 deletions
diff --git a/fs/xfs/scrub/repair.c b/fs/xfs/scrub/repair.c
index e111bdfb391b..b5d0f23b9b6d 100644
--- a/fs/xfs/scrub/repair.c
+++ b/fs/xfs/scrub/repair.c
@@ -28,6 +28,7 @@
#include "xfs_ag_resv.h"
#include "xfs_quota.h"
#include "xfs_bmap.h"
+#include "xfs_dir2.h"
#include "xfs_da_format.h"
#include "xfs_da_btree.h"
#include "xfs_attr.h"
@@ -38,7 +39,7 @@
#include "xfs_health.h"
#include "xfs_bmap_btree.h"
#include "xfs_trans_space.h"
-#include "xfs_dir2.h"
+#include "xfs_swapext.h"
#include "xfs_xchgrange.h"
#include "scrub/scrub.h"
#include "scrub/common.h"
@@ -1668,6 +1669,17 @@ xrep_metadata_inode_forks(
return error;
}
+ /* Clear the attr forks since metadata shouldn't have that. */
+ if (xfs_inode_hasattr(sc->ip)) {
+ if (!dirty) {
+ dirty = true;
+ xfs_trans_ijoin(sc->tp, sc->ip, 0);
+ }
+ error = xrep_xattr_reset_fork(sc, sc->ip);
+ if (error)
+ return error;
+ }
+
/*
* If we modified the inode, roll the transaction but don't rejoin the
* inode to the new transaction because xrep_bmap_data can do that.
@@ -2029,3 +2041,126 @@ out:
xfs_buf_delwri_cancel(&buffers_list);
return error;
}
+
+/*
+ * See if this buffer can pass the given ->verify_struct() function.
+ *
+ * If the buffer already has ops attached and they're not the ones that were
+ * passed in, we reject the buffer. Otherwise, we perform the structure test
+ * (note that we do not check CRCs) and return the outcome of the test. The
+ * buffer ops and error state are left unchanged.
+ */
+bool
+xrep_buf_verify_struct(
+ struct xfs_buf *bp,
+ const struct xfs_buf_ops *ops)
+{
+ const struct xfs_buf_ops *old_ops = bp->b_ops;
+ xfs_failaddr_t fa;
+ int old_error;
+
+ if (old_ops) {
+ if (old_ops != ops)
+ return false;
+ }
+
+ old_error = bp->b_error;
+ bp->b_ops = ops;
+ fa = bp->b_ops->verify_struct(bp);
+ bp->b_ops = old_ops;
+ bp->b_error = old_error;
+
+ return fa == NULL;
+}
+
+/*
+ * Fill out the swapext request and resource estimation structures in
+ * preparation for swapping the contents of a metadata file that we've rebuilt
+ * in the temp file.
+ */
+int
+xrep_swapext_prep(
+ struct xfs_scrub *sc,
+ int whichfork,
+ struct xfs_swapext_req *req,
+ struct xfs_swapext_res *res)
+{
+ struct xfs_ifork *ifp, *tifp;
+ int state = 0;
+
+ ASSERT(whichfork != XFS_COW_FORK);
+
+ /* Both files should have the relevant forks. */
+ ifp = XFS_IFORK_PTR(sc->ip, whichfork);
+ tifp = XFS_IFORK_PTR(sc->tempip, whichfork);
+ if (!ifp || !tifp) {
+ ASSERT(0);
+ return -EINVAL;
+ }
+
+ memset(res, 0, sizeof(struct xfs_swapext_res));
+ req->ip1 = sc->tempip;
+ req->ip2 = sc->ip;
+ req->startoff1 = 0;
+ req->startoff2 = 0;
+ req->whichfork = whichfork;
+ req->blockcount = XFS_MAX_FILEOFF;
+ req->req_flags = 0;
+
+ /*
+ * If we're repairing xattrs or directories, always try to convert ip2
+ * to short format after swapping.
+ */
+ if (whichfork == XFS_ATTR_FORK || S_ISDIR(VFS_I(sc->ip)->i_mode))
+ req->req_flags |= XFS_SWAP_REQ_FILE2_CVT_SF;
+
+ /*
+ * Deal with either fork being in local format. The swapext code only
+ * knows how to exchange block mappings for regular files, so we only
+ * have to know about local format for xattrs and directories.
+ */
+ if (ifp->if_format == XFS_DINODE_FMT_LOCAL)
+ state |= 1;
+ if (tifp->if_format == XFS_DINODE_FMT_LOCAL)
+ state |= 2;
+ switch (state) {
+ case 0:
+ /* Both files have mapped extents; use the regular estimate. */
+ return xfs_xchg_range_estimate(req, res);
+ case 1:
+ /*
+ * The file being repaired is in local format, but the temp
+ * file has mapped extents. To perform the swap, the file
+ * being repaired will be reinitialized to have an empty extent
+ * map, so the number of exchanges is the temporary file's
+ * extent count.
+ */
+ res->ip1_bcount = sc->tempip->i_nblocks;
+ res->nr_exchanges = tifp->if_nextents;
+ break;
+ case 2:
+ /*
+ * The temporary file is in local format, but the file being
+ * repaired has mapped extents. To perform the swap, the temp
+ * file will be converted to have a single block, so the number
+ * of exchanges is (worst case) the extent count of the file
+ * being repaired plus one more.
+ */
+ res->ip1_bcount = 1;
+ res->ip2_bcount = sc->ip->i_nblocks;
+ res->nr_exchanges = ifp->if_nextents;
+ break;
+ case 3:
+ /*
+ * Both forks are in local format. To perform the swap, the
+ * file being repaired will be reinitialized to have an empty
+ * extent map and the temp file will be converted to have a
+ * single block. Only one exchange is required.
+ */
+ res->ip1_bcount = 1;
+ res->nr_exchanges = 1;
+ break;
+ }
+
+ return xfs_swapext_estimate_overhead(req, res);
+}