diff options
-rw-r--r-- | fs/xfs/scrub/parent.c | 16 | ||||
-rw-r--r-- | fs/xfs/scrub/parent_repair.c | 95 | ||||
-rw-r--r-- | fs/xfs/scrub/repair.h | 2 | ||||
-rw-r--r-- | fs/xfs/scrub/scrub.c | 2 | ||||
-rw-r--r-- | fs/xfs/scrub/trace.h | 1 |
5 files changed, 115 insertions, 1 deletions
diff --git a/fs/xfs/scrub/parent.c b/fs/xfs/scrub/parent.c index c42ac90b6c99..5d8cc599fef7 100644 --- a/fs/xfs/scrub/parent.c +++ b/fs/xfs/scrub/parent.c @@ -24,6 +24,22 @@ xchk_setup_parent( struct xfs_scrub *sc, struct xfs_inode *ip) { + int error; + + /* + * If we're attempting a repair having failed a previous repair due to + * being unable to lock an inode (TRY_HARDER), we need to freeze the + * filesystem to make the repair happen. Note that we don't bother + * with the fs freeze when TRY_HARDER is set but IFLAG_REPAIR isn't, + * because a plain scrub is allowed to return with INCOMPLETE set. + */ + if ((sc->sm->sm_flags & XFS_SCRUB_IFLAG_REPAIR) && + (sc->flags & XCHK_TRY_HARDER)) { + error = xchk_fs_freeze(sc); + if (error) + return error; + } + return xchk_setup_inode_contents(sc, ip, 0); } diff --git a/fs/xfs/scrub/parent_repair.c b/fs/xfs/scrub/parent_repair.c index 9c8cc7c2c206..3d3993ba920d 100644 --- a/fs/xfs/scrub/parent_repair.c +++ b/fs/xfs/scrub/parent_repair.c @@ -19,9 +19,11 @@ #include "xfs_da_format.h" #include "xfs_da_btree.h" #include "xfs_dir2.h" +#include "xfs_bmap_btree.h" #include "xfs_dir2_priv.h" #include "xfs_trans_space.h" #include "xfs_iwalk.h" +#include "xfs_health.h" #include "scrub/xfs_scrub.h" #include "scrub/scrub.h" #include "scrub/common.h" @@ -203,3 +205,96 @@ xrep_scan_for_parents( return xfs_iwalk(sc->mp, sc->tp, 0, 0, xrep_parents_scan_inode, 0, &rps); } + +/* + * Repairing Parent Pointers + * ========================= + * + * Currently, only directories support parent pointers (in the form of '..' + * entries), so we simply scan the filesystem and update the '..' entry. + * + * Note that because the only parent pointer is the dotdot entry, we won't + * touch an unhealthy directory, since the directory repair code is perfectly + * capable of rebuilding a directory with the proper parent inode. + */ +struct xrep_parent { + struct xfs_scrub *sc; + + /* Potential parent pointer. */ + xfs_ino_t parent_ino; +}; + +/* + * If this directory entry points to the directory we're rebuilding, then the + * directory we're scanning is the parent. Remember the parent. + */ +STATIC int +xrep_parent_absorb( + struct xfs_inode *dp, + struct xfs_name *name, + unsigned int dtype, + void *data) +{ + struct xrep_parent *rp = data; + int error = 0; + + /* Uhoh, more than one parent for a dir? */ + if (rp->parent_ino != NULLFSINO) + return -EFSCORRUPTED; + + if (xchk_should_terminate(rp->sc, &error)) + return error; + + /* We found a potential parent; remember this. */ + rp->parent_ino = dp->i_ino; + return 0; +} + +int +xrep_parent( + struct xfs_scrub *sc) +{ + struct xrep_parent rp = { + .sc = sc, + .parent_ino = NULLFSINO, + }; + unsigned int sick, checked; + unsigned int spaceres; + int error; + + /* + * Avoid sick directories. The parent pointer scrubber dropped the + * ILOCK, but we still hold IOLOCK_EXCL on the directory, so there + * shouldn't be anyone else clearing the directory's sick status. + */ + xfs_inode_measure_sickness(sc->ip, &sick, &checked); + if (sick & XFS_SICK_INO_DIR) + return -EFSCORRUPTED; + + /* Scan the entire directory tree for the directory's parent. */ + error = xrep_scan_for_parents(sc, sc->ip->i_ino, xrep_parent_absorb, + &rp); + if (error) + return error; + + /* If we still don't have a parent, bail out. */ + if (!xrep_parent_acceptable(sc, rp.parent_ino)) + return 0; + + trace_xrep_parent_dir(sc->ip, rp.parent_ino); + + /* Reserve more space just in case we have to expand the dir. */ + spaceres = XFS_RENAME_SPACE_RES(sc->mp, 2); + error = xfs_trans_reserve_more(sc->tp, spaceres, 0); + if (error) + return error; + + /* Re-take the ILOCK, we're going to need it to modify the dir. */ + sc->ilock_flags |= XFS_ILOCK_EXCL; + xfs_ilock(sc->ip, XFS_ILOCK_EXCL); + + /* Replace the dotdot entry. */ + xfs_trans_ijoin(sc->tp, sc->ip, 0); + return xfs_dir_replace(sc->tp, sc->ip, &xfs_name_dotdot, rp.parent_ino, + spaceres); +} diff --git a/fs/xfs/scrub/repair.h b/fs/xfs/scrub/repair.h index 481a80d6667b..b135c655ccbd 100644 --- a/fs/xfs/scrub/repair.h +++ b/fs/xfs/scrub/repair.h @@ -90,6 +90,7 @@ int xrep_bmap_data(struct xfs_scrub *sc); int xrep_bmap_attr(struct xfs_scrub *sc); int xrep_symlink(struct xfs_scrub *sc); int xrep_dir(struct xfs_scrub *sc); +int xrep_parent(struct xfs_scrub *sc); int xrep_xattr(struct xfs_scrub *sc); int xrep_fscounters(struct xfs_scrub *sc); #ifdef CONFIG_XFS_QUOTA @@ -217,6 +218,7 @@ xrep_rmapbt_setup( #define xrep_bmap_attr xrep_notsupported #define xrep_symlink xrep_notsupported #define xrep_dir xrep_notsupported +#define xrep_parent xrep_notsupported #define xrep_xattr xrep_notsupported #define xrep_fscounters xrep_notsupported #define xrep_quota xrep_notsupported diff --git a/fs/xfs/scrub/scrub.c b/fs/xfs/scrub/scrub.c index b71e193195f9..657db43394a9 100644 --- a/fs/xfs/scrub/scrub.c +++ b/fs/xfs/scrub/scrub.c @@ -318,7 +318,7 @@ static const struct xchk_meta_ops meta_scrub_ops[] = { .type = ST_INODE, .setup = xchk_setup_parent, .scrub = xchk_parent, - .repair = xrep_notsupported, + .repair = xrep_parent, }, [XFS_SCRUB_TYPE_RTBITMAP] = { /* realtime bitmap */ .type = ST_FS, diff --git a/fs/xfs/scrub/trace.h b/fs/xfs/scrub/trace.h index 8d78f66ddfb9..b53fc32b61da 100644 --- a/fs/xfs/scrub/trace.h +++ b/fs/xfs/scrub/trace.h @@ -1223,6 +1223,7 @@ DEFINE_EVENT(xrep_dir_class, name, \ TP_PROTO(struct xfs_inode *dp, xfs_ino_t parent_ino), \ TP_ARGS(dp, parent_ino)) DEFINE_XREP_DIR_CLASS(xrep_dir_reset_fork); +DEFINE_XREP_DIR_CLASS(xrep_parent_dir); #define XFS_DIR3_FTYPE_STR \ { XFS_DIR3_FT_UNKNOWN, "unknown" }, \ |