diff options
Diffstat (limited to 'fs/xfs/scrub/parent_repair.c')
-rw-r--r-- | fs/xfs/scrub/parent_repair.c | 97 |
1 files changed, 96 insertions, 1 deletions
diff --git a/fs/xfs/scrub/parent_repair.c b/fs/xfs/scrub/parent_repair.c index b3fbb0ed5ac7..73ba8bcb63c8 100644 --- a/fs/xfs/scrub/parent_repair.c +++ b/fs/xfs/scrub/parent_repair.c @@ -30,6 +30,7 @@ #include "scrub/repair.h" #include "scrub/iscan.h" #include "scrub/parent.h" +#include "scrub/orphanage.h" struct xrep_findparent_info { /* The directory currently being scanning, and a readdir context. */ @@ -361,6 +362,34 @@ xrep_parent_scan( return 0; } +static inline struct xrep_orphanage_req * +xrep_parent_orphanage_req( + struct xfs_scrub *sc) +{ + return sc->buf; +} + +static inline unsigned char * +xrep_parent_orphanage_namebuf( + struct xfs_scrub *sc) +{ + return (unsigned char *)(((struct xrep_orphanage_req *)sc->buf) + 1); +} + +/* Set up for a parent repair. */ +int +xrep_setup_parent( + struct xfs_scrub *sc) +{ + /* We need a buffer for the orphanage request and a name buffer. */ + sc->buf = kvmalloc(xrep_orphanage_req_sizeof(), + GFP_KERNEL | __GFP_NOWARN | __GFP_RETRY_MAYFAIL); + if (!sc->buf) + return -ENOMEM; + + return xrep_orphanage_try_create(sc); +} + /* * Repairing The Directory Parent Pointer * ====================================== @@ -395,6 +424,72 @@ xrep_parent_reset_dir( spaceres); } +/* + * 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_parent_move_to_orphanage( + struct xfs_scrub *sc) +{ + struct xrep_orphanage_req *orph = xrep_parent_orphanage_req(sc); + unsigned char *namebuf = xrep_parent_orphanage_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 we can't, release the iolock on the child, and then try to iolock + * the orphanage and child at the same time. Use trylock for the + * second lock so that we don't ABBA deadlock the system. + */ + if (!xrep_orphanage_ilock_nowait(sc, XFS_IOLOCK_EXCL)) { + xfs_ino_t orig_parent, new_parent; + + 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); +} + int xrep_parent( struct xfs_scrub *sc) @@ -423,7 +518,7 @@ xrep_parent( if (error) return error; if (parent_ino == NULLFSINO) - return -EFSCORRUPTED; + return xrep_parent_move_to_orphanage(sc); reset_parent: /* If the '..' entry is already set to the parent inode, we're done. */ |