diff options
Diffstat (limited to 'fs/xfs/scrub/dir_repair.c')
-rw-r--r-- | fs/xfs/scrub/dir_repair.c | 115 |
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) |