From: Darrick J. Wong <djwong@xxxxxxxxxx> Use the nlink hooks to keep our tempfile's parent pointers up to date. Signed-off-by: Darrick J. Wong <djwong@xxxxxxxxxx> --- fs/xfs/libxfs/xfs_parent.c | 25 ++++++++++ fs/xfs/libxfs/xfs_parent.h | 4 ++ fs/xfs/scrub/parent_repair.c | 110 ++++++++++++++++++++++++++++++++++++++++++ fs/xfs/scrub/trace.h | 2 + 4 files changed, 141 insertions(+) diff --git a/fs/xfs/libxfs/xfs_parent.c b/fs/xfs/libxfs/xfs_parent.c index 5f07bd3debee..a2575bf44c89 100644 --- a/fs/xfs/libxfs/xfs_parent.c +++ b/fs/xfs/libxfs/xfs_parent.c @@ -363,3 +363,28 @@ xfs_parent_set( return xfs_attr_set(&scr->args); } + +/* + * Remove the parent pointer (@rec -> @name) from @ip immediately. Caller must + * not have a transaction or hold the ILOCK. The update will not use logged + * xattrs. This is for specialized repair functions only. The scratchpad need + * not be initialized. + */ +int +xfs_parent_unset( + struct xfs_inode *ip, + const struct xfs_parent_name_irec *pptr, + struct xfs_parent_scratch *scr) +{ + xfs_parent_irec_to_disk(&scr->rec, NULL, NULL, pptr); + + memset(&scr->args, 0, sizeof(struct xfs_da_args)); + scr->args.attr_filter = XFS_ATTR_PARENT; + scr->args.dp = ip; + scr->args.geo = ip->i_mount->m_attr_geo; + scr->args.name = (const unsigned char *)&scr->rec; + scr->args.namelen = sizeof(struct xfs_parent_name_rec); + scr->args.whichfork = XFS_ATTR_FORK; + + return xfs_attr_set(&scr->args); +} diff --git a/fs/xfs/libxfs/xfs_parent.h b/fs/xfs/libxfs/xfs_parent.h index effbccdf6b0e..a7fc621b82c4 100644 --- a/fs/xfs/libxfs/xfs_parent.h +++ b/fs/xfs/libxfs/xfs_parent.h @@ -117,4 +117,8 @@ int xfs_parent_set(struct xfs_inode *ip, const struct xfs_parent_name_irec *pptr, struct xfs_parent_scratch *scratch); +int xfs_parent_unset(struct xfs_inode *ip, + const struct xfs_parent_name_irec *rec, + struct xfs_parent_scratch *scratch); + #endif /* __XFS_PARENT_H__ */ diff --git a/fs/xfs/scrub/parent_repair.c b/fs/xfs/scrub/parent_repair.c index d80d1a466c02..4aec32081c6d 100644 --- a/fs/xfs/scrub/parent_repair.c +++ b/fs/xfs/scrub/parent_repair.c @@ -119,6 +119,9 @@ struct xrep_pptrs { /* Mutex protecting parent_ptrs, pptr_names. */ struct mutex lock; + /* Hook to capture directory entry updates. */ + struct xfs_dirent_hook hooks; + /* Stashed parent pointer updates. */ struct xfarray *parent_ptrs; @@ -131,6 +134,7 @@ static void xrep_pptr_teardown( struct xrep_pptrs *rp) { + xfs_dirent_hook_del(rp->sc->mp, &rp->hooks); xchk_iscan_finish(&rp->iscan); mutex_destroy(&rp->lock); xfblob_destroy(rp->pptr_names); @@ -145,6 +149,8 @@ xrep_setup_parent( struct xrep_pptrs *rp; int error; + xchk_fshooks_enable(sc, XCHK_FSHOOKS_DIRENTS); + error = xrep_tempfile_create(sc, S_IFREG); if (error) return error; @@ -185,6 +191,12 @@ xrep_pptr_replay_update( trace_xrep_pptr_createname(sc->tempip, &rp->pptr); return xfs_parent_set(sc->tempip, &rp->pptr, &rp->pptr_scratch); + } else if (pptr->action == XREP_PPTR_REMOVE) { + /* Remove parent pointer. */ + trace_xrep_pptr_removename(sc->tempip, &rp->pptr); + + return xfs_parent_unset(sc->tempip, &rp->pptr, + &rp->pptr_scratch); } ASSERT(0); @@ -268,6 +280,36 @@ xrep_pptr_add_pointer( return xfarray_append(rp->parent_ptrs, &pptr); } +/* + * Remember that we want to remove a parent pointer from the tempfile. These + * stashed actions will be replayed later. + */ +STATIC int +xrep_pptr_remove_pointer( + struct xrep_pptrs *rp, + const struct xfs_name *name, + const struct xfs_inode *dp, + xfs_dir2_dataptr_t diroffset) +{ + struct xrep_pptr pptr = { + .action = XREP_PPTR_REMOVE, + .namelen = name->len, + .p_ino = dp->i_ino, + .p_gen = VFS_IC(dp)->i_generation, + .p_diroffset = diroffset, + }; + int error; + + trace_xrep_pptr_remove_pointer(rp->sc->tempip, dp, diroffset, name); + + error = xfblob_store(rp->pptr_names, &pptr.name_cookie, name->name, + name->len); + if (error) + return error; + + return xfarray_append(rp->parent_ptrs, &pptr); +} + /* * Examine an entry of a directory. If this dirent leads us back to the file * whose parent pointers we're rebuilding, add a pptr to the temporary @@ -500,6 +542,12 @@ xrep_pptr_rebuild_tree( if (error) return error; + /* + * Abort the inode scan so that the live hooks won't stash any more + * directory updates. + */ + xchk_iscan_abort(&rp->iscan); + error = xrep_pptr_replay_updates(rp); if (error) return error; @@ -522,6 +570,52 @@ xrep_pptr_rebuild_tree( return xchk_xattr_walk(sc, sc->tempip, xrep_pptr_dump_tempptr, rp); } +/* + * Capture dirent updates being made by other threads which are relevant to the + * file being repaired. + */ +STATIC int +xrep_pptr_live_update( + struct notifier_block *nb, + unsigned long action, + void *data) +{ + struct xfs_dirent_update_params *p = data; + struct xrep_pptrs *rp; + struct xfs_scrub *sc; + int error; + + rp = container_of(nb, struct xrep_pptrs, hooks.delta_hook.nb); + sc = rp->sc; + + if (action != XFS_DIRENT_CHILD_DELTA) + return NOTIFY_DONE; + + /* + * This thread updated a dirent that points to the file that we're + * repairing, so stash the update for replay against the temporary + * file. + */ + if (p->ip->i_ino == sc->ip->i_ino && + xchk_iscan_want_live_update(&rp->iscan, p->dp->i_ino)) { + mutex_lock(&rp->lock); + if (p->delta > 0) + error = xrep_pptr_add_pointer(rp, p->name, p->dp, + p->diroffset); + else + error = xrep_pptr_remove_pointer(rp, p->name, p->dp, + p->diroffset); + mutex_unlock(&rp->lock); + if (error) + goto out_abort; + } + + return NOTIFY_DONE; +out_abort: + xchk_iscan_abort(&rp->iscan); + return NOTIFY_DONE; +} + /* Set up the filesystem scan so we can look for pptrs. */ STATIC int xrep_pptr_setup_scan( @@ -545,8 +639,24 @@ xrep_pptr_setup_scan( /* Retry iget every tenth of a second for up to 30 seconds. */ xchk_iscan_start(&rp->iscan, 30000, 100); + /* + * Hook into the dirent update code. The hook only operates on inodes + * that were already scanned, and the scanner thread takes each inode's + * ILOCK, which means that any in-progress inode updates will finish + * before we can scan the inode. + */ + ASSERT(sc->flags & XCHK_FSHOOKS_DIRENTS); + xfs_hook_setup(&rp->hooks.delta_hook, xrep_pptr_live_update); + error = xfs_dirent_hook_add(sc->mp, &rp->hooks); + if (error) + goto out_scan; + return 0; +out_scan: + xchk_iscan_finish(&rp->iscan); + mutex_destroy(&rp->lock); + xfblob_destroy(rp->pptr_names); out_entries: xfarray_destroy(rp->parent_ptrs); return error; diff --git a/fs/xfs/scrub/trace.h b/fs/xfs/scrub/trace.h index 1fb9832a113a..283a1cedf368 100644 --- a/fs/xfs/scrub/trace.h +++ b/fs/xfs/scrub/trace.h @@ -1354,6 +1354,7 @@ DEFINE_EVENT(xrep_pptr_class, name, \ TP_PROTO(struct xfs_inode *ip, const struct xfs_parent_name_irec *pptr), \ TP_ARGS(ip, pptr)) DEFINE_XREP_PPTR_CLASS(xrep_pptr_createname); +DEFINE_XREP_PPTR_CLASS(xrep_pptr_removename); DEFINE_XREP_PPTR_CLASS(xrep_pptr_dumpname); DECLARE_EVENT_CLASS(xrep_pptr_scan_class, @@ -1393,6 +1394,7 @@ DEFINE_EVENT(xrep_pptr_scan_class, name, \ unsigned int diroffset, const struct xfs_name *name), \ TP_ARGS(ip, dp, diroffset, name)) DEFINE_XREP_PPTR_SCAN_CLASS(xrep_pptr_add_pointer); +DEFINE_XREP_PPTR_SCAN_CLASS(xrep_pptr_remove_pointer); #endif /* IS_ENABLED(CONFIG_XFS_ONLINE_REPAIR) */