* other threads.
*/
+/* Create a dirent in the tempdir. */
+#define XREP_DIRENT_ADD (1)
+
+/* Remove a dirent from the tempdir. */
+#define XREP_DIRENT_REMOVE (2)
+
/* Directory entry to be restored in the new directory. */
struct xrep_dirent {
/* Cookie for retrieval of the dirent name. */
/* File type of the dirent. */
uint8_t ftype;
+
+ /* XREP_DIRENT_{ADD,REMOVE} */
+ uint8_t action;
};
/*
xfs_ino_t ino)
{
struct xrep_dirent dirent = {
+ .action = XREP_DIRENT_ADD,
.ino = ino,
.namelen = name->len,
.ftype = name->type,
return xfarray_append(rd->dir_entries, &dirent);
}
+/*
+ * Remember that we want to remove a dirent from the tempdir. These stashed
+ * actions will be replayed later.
+ */
+STATIC int
+xrep_dir_stash_removename(
+ struct xrep_dir *rd,
+ const struct xfs_name *name,
+ xfs_ino_t ino)
+{
+ struct xrep_dirent dirent = {
+ .action = XREP_DIRENT_REMOVE,
+ .ino = ino,
+ .namelen = name->len,
+ .ftype = name->type,
+ };
+ int error;
+
+ trace_xrep_dir_stash_removename(rd->sc->tempip, name, ino);
+
+ error = xfblob_storename(rd->dir_names, &dirent.name_cookie, name);
+ if (error)
+ return error;
+
+ return xfarray_append(rd->dir_entries, &dirent);
+}
+
/* Allocate an in-core record to hold entries while we rebuild the dir data. */
STATIC int
xrep_dir_salvage_entry(
return xfs_dir2_node_addname(&rd->args);
}
+/* Replay a stashed removename onto the temporary directory. */
+STATIC int
+xrep_dir_replay_removename(
+ struct xrep_dir *rd,
+ const struct xfs_name *name,
+ xfs_extlen_t total)
+{
+ struct xfs_inode *dp = rd->args.dp;
+ bool is_block, is_leaf;
+ int error;
+
+ ASSERT(S_ISDIR(VFS_I(dp)->i_mode));
+
+ xrep_dir_init_args(rd, dp, name);
+ rd->args.op_flags = 0;
+ rd->args.total = total;
+
+ trace_xrep_dir_replay_removename(dp, name, 0);
+
+ if (dp->i_df.if_format == XFS_DINODE_FMT_LOCAL)
+ return xfs_dir2_sf_removename(&rd->args);
+
+ error = xfs_dir2_isblock(&rd->args, &is_block);
+ if (error)
+ return error;
+ if (is_block)
+ return xfs_dir2_block_removename(&rd->args);
+
+ error = xfs_dir2_isleaf(&rd->args, &is_leaf);
+ if (error)
+ return error;
+ if (is_leaf)
+ return xfs_dir2_leaf_removename(&rd->args);
+
+ return xfs_dir2_node_removename(&rd->args);
+}
+
/*
* Add this stashed incore directory entry to the temporary directory.
* The caller must hold the tempdir's IOLOCK, must not hold any ILOCKs, and
xrep_tempfile_ilock(rd->sc);
xfs_trans_ijoin(rd->sc->tp, rd->sc->tempip, 0);
- /*
- * Create a replacement dirent in the temporary directory. Note that
- * _createname doesn't check for existing entries. There shouldn't be
- * any in the temporary dir, but we'll verify this in debug mode.
- */
+ switch (dirent->action) {
+ case XREP_DIRENT_ADD:
+ /*
+ * Create a replacement dirent in the temporary directory.
+ * Note that _createname doesn't check for existing entries.
+ * There shouldn't be any in the temporary dir, but we'll
+ * verify this in debug mode.
+ */
#ifdef DEBUG
- error = xchk_dir_lookup(rd->sc, rd->sc->tempip, xname, &ino);
- if (error != -ENOENT) {
- ASSERT(error != -ENOENT);
- goto out_cancel;
- }
+ error = xchk_dir_lookup(rd->sc, rd->sc->tempip, xname, &ino);
+ if (error != -ENOENT) {
+ ASSERT(error != -ENOENT);
+ goto out_cancel;
+ }
#endif
- error = xrep_dir_replay_createname(rd, xname, dirent->ino, resblks);
- if (error)
- goto out_cancel;
+ error = xrep_dir_replay_createname(rd, xname, dirent->ino,
+ resblks);
+ if (error)
+ goto out_cancel;
+
+ if (xname->type == XFS_DIR3_FT_DIR)
+ rd->subdirs++;
+ rd->dirents++;
+ break;
+ case XREP_DIRENT_REMOVE:
+ /*
+ * Remove a dirent from the temporary directory. Note that
+ * _removename doesn't check the inode target of the exist
+ * entry. There should be a perfect match in the temporary
+ * dir, but we'll verify this in debug mode.
+ */
+#ifdef DEBUG
+ error = xchk_dir_lookup(rd->sc, rd->sc->tempip, xname, &ino);
+ if (error) {
+ ASSERT(error != 0);
+ goto out_cancel;
+ }
+ if (ino != dirent->ino) {
+ ASSERT(ino == dirent->ino);
+ error = -EIO;
+ goto out_cancel;
+ }
+#endif
- if (xname->type == XFS_DIR3_FT_DIR)
- rd->subdirs++;
- rd->dirents++;
+ error = xrep_dir_replay_removename(rd, xname, resblks);
+ if (error)
+ goto out_cancel;
+
+ if (xname->type == XFS_DIR3_FT_DIR)
+ rd->subdirs--;
+ rd->dirents--;
+ break;
+ default:
+ ASSERT(0);
+ error = -EIO;
+ goto out_cancel;
+ }
/* Commit and unlock. */
error = xrep_trans_commit(rd->sc);
return 0;
}
+/*
+ * Capture dirent updates being made by other threads which are relevant to the
+ * directory being repaired.
+ */
+STATIC int
+xrep_dir_live_update(
+ struct notifier_block *nb,
+ unsigned long action,
+ void *data)
+{
+ struct xfs_dir_update_params *p = data;
+ struct xrep_dir *rd;
+ struct xfs_scrub *sc;
+ int error = 0;
+
+ rd = container_of(nb, struct xrep_dir, pscan.dhook.dirent_hook.nb);
+ sc = rd->sc;
+
+ /*
+ * This thread updated a child dirent in the directory that we're
+ * rebuilding. Stash the update for replay against the temporary
+ * directory.
+ */
+ if (p->dp->i_ino == sc->ip->i_ino &&
+ xchk_iscan_want_live_update(&rd->pscan.iscan, p->ip->i_ino)) {
+ mutex_lock(&rd->pscan.lock);
+ if (p->delta > 0)
+ error = xrep_dir_stash_createname(rd, p->name,
+ p->ip->i_ino);
+ else
+ error = xrep_dir_stash_removename(rd, p->name,
+ p->ip->i_ino);
+ mutex_unlock(&rd->pscan.lock);
+ if (error)
+ goto out_abort;
+ }
+
+ /*
+ * This thread updated another directory's child dirent that points to
+ * the directory that we're rebuilding, so remember the new dotdot
+ * target.
+ */
+ if (p->ip->i_ino == sc->ip->i_ino &&
+ xchk_iscan_want_live_update(&rd->pscan.iscan, p->dp->i_ino)) {
+ if (p->delta > 0) {
+ trace_xrep_dir_stash_createname(sc->tempip,
+ &xfs_name_dotdot,
+ p->dp->i_ino);
+
+ xrep_findparent_scan_found(&rd->pscan, p->dp->i_ino);
+ } else {
+ trace_xrep_dir_stash_removename(sc->tempip,
+ &xfs_name_dotdot,
+ rd->pscan.parent_ino);
+
+ xrep_findparent_scan_found(&rd->pscan, NULLFSINO);
+ }
+ }
+
+ return NOTIFY_DONE;
+out_abort:
+ xchk_iscan_abort(&rd->pscan.iscan);
+ return NOTIFY_DONE;
+}
+
/*
* Free all the directory blocks and reset the data fork. The caller must
* join the inode to the transaction. This function returns with the inode
if (error)
return error;
+ if (xchk_iscan_aborted(&rd->pscan.iscan))
+ return -ECANCELED;
+
/*
* Exchange the tempdir's data fork with the file being repaired. This
* recreates the transaction and re-takes the ILOCK in the scrub
if (error)
goto out_xfarray;
- error = xrep_findparent_scan_start(sc, &rd->pscan);
+ if (xfs_has_parent(sc->mp))
+ error = __xrep_findparent_scan_start(sc, &rd->pscan,
+ xrep_dir_live_update);
+ else
+ error = xrep_findparent_scan_start(sc, &rd->pscan);
if (error)
goto out_xfblob;