summaryrefslogtreecommitdiff
path: root/fs/xfs/scrub/dir_repair.c
diff options
context:
space:
mode:
Diffstat (limited to 'fs/xfs/scrub/dir_repair.c')
-rw-r--r--fs/xfs/scrub/dir_repair.c115
1 files changed, 111 insertions, 4 deletions
diff --git a/fs/xfs/scrub/dir_repair.c b/fs/xfs/scrub/dir_repair.c
index e209c04f5c39..071126d3fe12 100644
--- a/fs/xfs/scrub/dir_repair.c
+++ b/fs/xfs/scrub/dir_repair.c
@@ -36,6 +36,7 @@
#include "scrub/xfarray.h"
#include "scrub/xfblob.h"
#include "scrub/parent.h"
+#include "scrub/orphanage.h"
/*
* Directory Repair
@@ -82,6 +83,9 @@ struct xrep_dir {
/* nlink value of the corrected directory. */
xfs_nlink_t new_nlink;
+
+ /* Should we move this directory to the orphanage? */
+ bool move_orphanage;
};
/* Absorb up to 8 pages of dirents before we flush them to the temp dir. */
@@ -101,6 +105,13 @@ xrep_directory_namebuf(
return sc->buf;
}
+static inline struct xrep_orphanage_req *
+xrep_dir_orphanage_req(
+ struct xfs_scrub *sc)
+{
+ return sc->buf + MAXNAMELEN + 1;
+}
+
/* Set up for a directory repair. */
int
xrep_setup_directory(
@@ -109,16 +120,22 @@ xrep_setup_directory(
unsigned int sz;
int error;
+ error = xrep_orphanage_try_create(sc);
+ if (error)
+ return error;
+
error = xrep_tempfile_create(sc, S_IFDIR);
if (error)
return error;
/*
* We need a buffer to hold a directory entry name while we're building
- * the new directory, and later for the da state when we're freeing the
- * old directory blocks. We don't need both uses at the same time.
+ * the new directory, later for the da state when we're freeing the old
+ * directory blocks, and a request to move the directory to the
+ * orphanage. We don't need all three uses at the same time.
*/
- sz = max_t(unsigned int, MAXNAMELEN + 1, sizeof(struct xfs_da_args));
+ sz = max_t(unsigned int, xrep_orphanage_req_sizeof(),
+ sizeof(struct xfs_da_args));
sc->buf = kvmalloc(sz,
GFP_KERNEL | __GFP_NOWARN | __GFP_RETRY_MAYFAIL);
if (!sc->buf)
@@ -1154,7 +1171,79 @@ xrep_directory_find_parent(
if (rd->parent_ino != NULLFSINO)
return 0;
- return -EFSCORRUPTED;
+ /*
+ * Temporarily assign the root dir as the parent; we'll move this to
+ * the orphanage after swapping the dir contents.
+ */
+ rd->move_orphanage = true;
+ rd->parent_ino = rd->sc->mp->m_sb.sb_rootino;
+ return 0;
+}
+
+/*
+ * Move the current file to the orphanage. Caller must not hold any inode
+ * locks. Upon return, the scrub state will reflect the transaction, ijoin,
+ * and inode lock states.
+ */
+STATIC int
+xrep_dir_move_to_orphanage(
+ struct xfs_scrub *sc)
+{
+ struct xrep_orphanage_req *orph = xrep_dir_orphanage_req(sc);
+ unsigned char *namebuf = xrep_directory_namebuf(sc);
+ int error;
+
+ /* No orphanage? We can't fix this. */
+ if (!sc->orphanage)
+ return -EFSCORRUPTED;
+
+ /* If we can take the orphanage's iolock then we're ready to move. */
+ if (!xrep_orphanage_ilock_nowait(sc, XFS_IOLOCK_EXCL)) {
+ xfs_ino_t orig_parent, new_parent;
+
+ /*
+ * We may have to drop the lock on sc->ip to try to lock the
+ * orphanage. Therefore, look up the old dotdot entry for
+ * sc->ip so that we can compare it after we re-lock sc->ip.
+ */
+ orig_parent = xrep_dotdot_lookup(sc);
+
+ xchk_iunlock(sc, sc->ilock_flags);
+ error = xrep_orphanage_iolock_two(sc);
+ if (error)
+ return error;
+
+ /*
+ * If the parent changed or the child was unlinked while the
+ * child directory was unlocked, we don't need to move the
+ * child to the orphanage after all.
+ */
+ new_parent = xrep_dotdot_lookup(sc);
+
+ if (orig_parent != new_parent || VFS_I(sc->ip)->i_nlink == 0)
+ return 0;
+ }
+
+ /*
+ * Move the directory to the orphanage, and let scrub teardown unlock
+ * everything for us.
+ */
+ xrep_orphanage_compute_blkres(sc, orph);
+
+ error = xrep_orphanage_compute_name(orph, namebuf);
+ if (error)
+ return error;
+
+ error = xfs_trans_reserve_more(sc->tp,
+ orph->orphanage_blkres + orph->child_blkres, 0);
+ if (error)
+ return error;
+
+ error = xrep_orphanage_ilock_resv_quota(orph);
+ if (error)
+ return error;
+
+ return xrep_orphanage_adopt(orph);
}
/*
@@ -1236,6 +1325,24 @@ xrep_directory(
/* Swap in the good contents. */
error = xrep_directory_rebuild_tree(rd);
+ if (error || !rd->move_orphanage)
+ goto out_rd;
+
+ /*
+ * We hold ILOCK_EXCL on both the directory and the tempdir after a
+ * successful rebuild. Before we can move the directory to the
+ * orphanage, we must roll to a clean unjoined transaction and drop the
+ * ILOCKs on the dir and the temp dir. We still hold IOLOCK_EXCL on
+ * the dir, so nobody will be able to access it in the mean time.
+ */
+ error = xfs_trans_roll(&sc->tp);
+ if (error)
+ goto out_rd;
+
+ xchk_iunlock(sc, XFS_ILOCK_EXCL);
+ xrep_tempfile_iunlock(sc, XFS_ILOCK_EXCL);
+
+ error = xrep_dir_move_to_orphanage(sc);
out_names:
if (rd->dir_names)