@@ -375,3 +375,22 @@ 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, pptr);
+ xfs_parent_scratch_init(NULL, ip, pptr, scr);
+ scr->args.op_flags |= XFS_DA_OP_REMOVE;
+
+ return xfs_attr_set(&scr->args);
+}
@@ -115,4 +115,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__ */
@@ -118,6 +118,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;
@@ -130,6 +133,7 @@ static void
xrep_pptr_teardown(
struct xrep_pptrs *rp)
{
+ xfs_dirent_hook_del(rp->sc->mp, &rp->hooks);
xchk_iscan_teardown(&rp->iscan);
mutex_destroy(&rp->lock);
xfblob_destroy(rp->pptr_names);
@@ -144,6 +148,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;
@@ -184,6 +190,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);
@@ -265,6 +277,34 @@ 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)
+{
+ struct xrep_pptr pptr = {
+ .action = XREP_PPTR_REMOVE,
+ .namelen = name->len,
+ .p_ino = dp->i_ino,
+ .p_gen = VFS_IC(dp)->i_generation,
+ };
+ int error;
+
+ trace_xrep_pptr_remove_pointer(rp->sc->tempip, dp, 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
@@ -553,6 +593,50 @@ 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);
+ else
+ error = xrep_pptr_remove_pointer(rp, p->name, p->dp);
+ 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(
@@ -576,8 +660,24 @@ xrep_pptr_setup_scan(
/* Retry iget every tenth of a second for up to 30 seconds. */
xchk_iscan_start(sc, 30000, 100, &rp->iscan);
+ /*
+ * 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_teardown(&rp->iscan);
+ mutex_destroy(&rp->lock);
+ xfblob_destroy(rp->pptr_names);
out_entries:
xfarray_destroy(rp->parent_ptrs);
return error;
@@ -1372,6 +1372,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,
@@ -1408,6 +1409,7 @@ DEFINE_EVENT(xrep_pptr_scan_class, name, \
const struct xfs_name *name), \
TP_ARGS(ip, dp, 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) */