summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDarrick J. Wong <djwong@kernel.org>2021-09-01 10:59:08 -0700
committerDarrick J. Wong <djwong@kernel.org>2021-10-22 16:40:46 -0700
commit83afbcfdb3ddd64b827c2197da6743effb5a9a3f (patch)
tree34dbcf4ebbffd084fc48138001bc81c305202469
parent6780caa2619855a1afc3751453a6ea1d9424c67e (diff)
xfs: move orphan files to the orphanagerepair-dirs_2021-10-22
If we can't find a parent for a file, move it to the orphanage. Signed-off-by: Darrick J. Wong <djwong@kernel.org>
-rw-r--r--fs/xfs/Makefile1
-rw-r--r--fs/xfs/scrub/dir_repair.c123
-rw-r--r--fs/xfs/scrub/orphanage.c381
-rw-r--r--fs/xfs/scrub/orphanage.h78
-rw-r--r--fs/xfs/scrub/parent.c10
-rw-r--r--fs/xfs/scrub/parent_repair.c97
-rw-r--r--fs/xfs/scrub/repair.h2
-rw-r--r--fs/xfs/scrub/scrub.c2
-rw-r--r--fs/xfs/scrub/scrub.h4
-rw-r--r--fs/xfs/scrub/trace.h1
-rw-r--r--fs/xfs/xfs_inode.c6
-rw-r--r--fs/xfs/xfs_inode.h1
12 files changed, 694 insertions, 12 deletions
diff --git a/fs/xfs/Makefile b/fs/xfs/Makefile
index 17178f9eeac5..a0730088782a 100644
--- a/fs/xfs/Makefile
+++ b/fs/xfs/Makefile
@@ -182,6 +182,7 @@ xfs-y += $(addprefix scrub/, \
fscounters_repair.o \
ialloc_repair.o \
inode_repair.o \
+ orphanage.o \
parent_repair.o \
refcount_repair.o \
repair.o \
diff --git a/fs/xfs/scrub/dir_repair.c b/fs/xfs/scrub/dir_repair.c
index 635b30fdd7b9..13f42acd9ca3 100644
--- a/fs/xfs/scrub/dir_repair.c
+++ b/fs/xfs/scrub/dir_repair.c
@@ -36,6 +36,7 @@
#include "scrub/xfarray.h"
#include "scrub/xfblob.h"
#include "scrub/parent.h"
+#include "scrub/orphanage.h"
/*
* Directory Repair
@@ -101,6 +102,13 @@ xrep_directory_namebuf(
return sc->buf;
}
+static inline struct xrep_orphanage_req *
+xrep_dir_orphanage_req(
+ struct xfs_scrub *sc)
+{
+ return sc->buf + MAXNAMELEN + 1;
+}
+
/* Set up for a directory repair. */
int
xrep_setup_directory(
@@ -109,16 +117,22 @@ xrep_setup_directory(
unsigned int sz;
int error;
+ error = xrep_orphanage_try_create(sc);
+ if (error)
+ return error;
+
error = xrep_tempfile_create(sc, S_IFDIR);
if (error)
return error;
/*
* We need a buffer to hold a directory entry name while we're building
- * the new directory, and later for the da state when we're freeing the
- * old directory blocks. We don't need both uses at the same time.
+ * the new directory, later for the da state when we're freeing the old
+ * directory blocks, and a request to move the directory to the
+ * orphanage. We don't need all three uses at the same time.
*/
- sz = max_t(unsigned int, MAXNAMELEN + 1, sizeof(struct xfs_da_args));
+ sz = max_t(unsigned int, xrep_orphanage_req_sizeof(),
+ sizeof(struct xfs_da_args));
sc->buf = kvmalloc(sz,
GFP_KERNEL | __GFP_NOWARN | __GFP_RETRY_MAYFAIL);
if (!sc->buf)
@@ -1098,7 +1112,8 @@ xrep_directory_rebuild_tree(
*/
STATIC int
xrep_directory_find_parent(
- struct xrep_dir *rd)
+ struct xrep_dir *rd,
+ bool *move_orphanage)
{
struct xfs_scrub *sc = rd->sc;
xfs_ino_t parent_ino;
@@ -1135,8 +1150,15 @@ xrep_directory_find_parent(
error = xrep_parent_scan(sc, &parent_ino);
if (error)
return error;
- if (parent_ino == NULLFSINO)
- return -EFSCORRUPTED;
+ if (parent_ino == NULLFSINO) {
+ /*
+ * Temporarily assign the root dir as the parent; we'll move
+ * this to the orphanage after swapping the dir contents.
+ */
+ *move_orphanage = true;
+ rd->parent_ino = sc->mp->m_sb.sb_rootino;
+ return 0;
+ }
foundit:
rd->parent_ino = parent_ino;
@@ -1144,6 +1166,72 @@ foundit:
}
/*
+ * 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_dir_move_to_orphanage(
+ struct xfs_scrub *sc)
+{
+ struct xrep_orphanage_req *orph = xrep_dir_orphanage_req(sc);
+ unsigned char *namebuf = xrep_directory_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);
+}
+
+/*
* Repair the directory metadata.
*
* XXX: Directory entry buffers can be multiple fsblocks in size. The buffer
@@ -1163,6 +1251,7 @@ xrep_directory(
.parent_ino = NULLFSINO,
.new_nlink = 2,
};
+ bool move_orphanage = false;
int error;
/* Set up some storage */
@@ -1186,7 +1275,7 @@ xrep_directory(
xchk_iunlock(sc, XFS_MMAPLOCK_EXCL);
/* Figure out who is going to be the parent of this directory. */
- error = xrep_directory_find_parent(&rd);
+ error = xrep_directory_find_parent(&rd, &move_orphanage);
if (error)
goto out_names;
@@ -1216,7 +1305,25 @@ xrep_directory(
xchk_iunlock(sc, XFS_ILOCK_EXCL);
xrep_tempfile_iunlock(sc, XFS_ILOCK_EXCL);
- return xrep_directory_rebuild_tree(&rd);
+ error = xrep_directory_rebuild_tree(&rd);
+ if (error || !move_orphanage)
+ return error;
+
+ /*
+ * We hold ILOCK_EXCL on both the directory and the tempdir after a
+ * successful rebuild. Before we can move the directory to the
+ * orphanage, we must roll to a clean unjoined transaction and drop the
+ * ILOCKs on the dir and the temp dir. We still hold IOLOCK_EXCL on
+ * the dir, so nobody will be able to access it in the mean time.
+ */
+ error = xfs_trans_roll(&sc->tp);
+ if (error)
+ return error;
+
+ xchk_iunlock(sc, XFS_ILOCK_EXCL);
+ xrep_tempfile_iunlock(sc, XFS_ILOCK_EXCL);
+
+ return xrep_dir_move_to_orphanage(sc);
out_names:
xfblob_destroy(rd.dir_names);
diff --git a/fs/xfs/scrub/orphanage.c b/fs/xfs/scrub/orphanage.c
new file mode 100644
index 000000000000..a57607b010d9
--- /dev/null
+++ b/fs/xfs/scrub/orphanage.c
@@ -0,0 +1,381 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2021 Oracle. All Rights Reserved.
+ * Author: Darrick J. Wong <djwong@kernel.org>
+ */
+#include "xfs.h"
+#include "xfs_fs.h"
+#include "xfs_shared.h"
+#include "xfs_format.h"
+#include "xfs_trans_resv.h"
+#include "xfs_mount.h"
+#include "xfs_log_format.h"
+#include "xfs_trans.h"
+#include "xfs_inode.h"
+#include "xfs_ialloc.h"
+#include "xfs_quota.h"
+#include "xfs_trans_space.h"
+#include "xfs_dir2.h"
+#include "xfs_icache.h"
+#include "xfs_bmap.h"
+#include "xfs_bmap_btree.h"
+#include "scrub/scrub.h"
+#include "scrub/common.h"
+#include "scrub/repair.h"
+#include "scrub/trace.h"
+#include "scrub/orphanage.h"
+
+#include <linux/namei.h>
+
+/* Make the orphanage owned by root. */
+STATIC int
+xrep_chown_orphanage(
+ struct xfs_scrub *sc,
+ struct xfs_inode *dp)
+{
+ struct xfs_trans *tp;
+ struct xfs_mount *mp = sc->mp;
+ struct xfs_dquot *udqp = NULL, *gdqp = NULL, *pdqp = NULL;
+ struct xfs_dquot *oldu = NULL, *oldg = NULL, *oldp = NULL;
+ struct inode *inode = VFS_I(dp);
+ int error;
+
+ error = xfs_qm_vop_dqalloc(dp, GLOBAL_ROOT_UID, GLOBAL_ROOT_GID, 0,
+ XFS_QMOPT_QUOTALL, &udqp, &gdqp, &pdqp);
+ if (error)
+ return error;
+
+ error = xfs_trans_alloc_ichange(dp, udqp, gdqp, pdqp, true, &tp);
+ if (error)
+ goto out_dqrele;
+
+ /*
+ * CAP_FSETID overrides the following restrictions:
+ *
+ * The set-user-ID and set-group-ID bits of a file will be
+ * cleared upon successful return from chown()
+ */
+ if ((inode->i_mode & (S_ISUID|S_ISGID)) && !capable(CAP_FSETID))
+ inode->i_mode &= ~(S_ISUID|S_ISGID);
+
+ /*
+ * Change the ownerships and register quota modifications
+ * in the transaction.
+ */
+ if (!uid_eq(inode->i_uid, GLOBAL_ROOT_UID)) {
+ if (XFS_IS_UQUOTA_ON(mp))
+ oldu = xfs_qm_vop_chown(tp, dp, &dp->i_udquot, udqp);
+ inode->i_uid = GLOBAL_ROOT_UID;
+ }
+ if (!gid_eq(inode->i_gid, GLOBAL_ROOT_GID)) {
+ if (XFS_IS_GQUOTA_ON(mp))
+ oldg = xfs_qm_vop_chown(tp, dp, &dp->i_gdquot, gdqp);
+ inode->i_gid = GLOBAL_ROOT_GID;
+ }
+ if (dp->i_projid != 0) {
+ if (XFS_IS_PQUOTA_ON(mp))
+ oldp = xfs_qm_vop_chown(tp, dp, &dp->i_pdquot, pdqp);
+ dp->i_projid = 0;
+ }
+
+ xfs_trans_log_inode(tp, dp, XFS_ILOG_CORE);
+
+ XFS_STATS_INC(mp, xs_ig_attrchg);
+
+ if (xfs_has_wsync(mp))
+ xfs_trans_set_sync(tp);
+ error = xfs_trans_commit(tp);
+
+ xfs_qm_dqrele(oldu);
+ xfs_qm_dqrele(oldg);
+ xfs_qm_dqrele(oldp);
+
+out_dqrele:
+ xfs_qm_dqrele(udqp);
+ xfs_qm_dqrele(gdqp);
+ xfs_qm_dqrele(pdqp);
+ return error;
+}
+
+#define ORPHANAGE "lost+found"
+
+/* Create the orphanage directory, and set sc->orphanage to it. */
+int
+xrep_orphanage_create(
+ struct xfs_scrub *sc)
+{
+ struct xfs_mount *mp = sc->mp;
+ struct dentry *root_dentry, *orphanage_dentry;
+ struct inode *root_inode = VFS_I(sc->mp->m_rootip);
+ struct inode *orphanage_inode;
+ int error;
+
+ if (xfs_is_shutdown(mp))
+ return -EIO;
+ if (xfs_is_readonly(mp)) {
+ sc->orphanage = NULL;
+ return 0;
+ }
+
+ ASSERT(sc->tp == NULL);
+ ASSERT(sc->orphanage == NULL);
+
+ /* Find the dentry for the root directory... */
+ root_dentry = d_find_alias(root_inode);
+ if (!root_dentry) {
+ error = -EFSCORRUPTED;
+ goto out;
+ }
+
+ /* ...which is a directory, right? */
+ if (!d_is_dir(root_dentry)) {
+ error = -EFSCORRUPTED;
+ goto out_dput_root;
+ }
+
+ /* Try to find the orphanage directory. */
+ inode_lock_nested(root_inode, I_MUTEX_PARENT);
+ orphanage_dentry = lookup_one_len(ORPHANAGE, root_dentry,
+ strlen(ORPHANAGE));
+ if (IS_ERR(orphanage_dentry)) {
+ error = PTR_ERR(orphanage_dentry);
+ goto out_unlock_root;
+ }
+
+ /* Nothing found? Call mkdir to create the orphanage. */
+ if (d_really_is_negative(orphanage_dentry)) {
+ error = vfs_mkdir(&init_user_ns, root_inode, orphanage_dentry,
+ 0755);
+ if (error)
+ goto out_dput_orphanage;
+ }
+
+ /* Not a directory? Bail out. */
+ if (!d_is_dir(orphanage_dentry)) {
+ error = -ENOTDIR;
+ goto out_dput_orphanage;
+ }
+
+ /*
+ * Grab a reference to the orphanage. This /should/ succeed since
+ * we hold the root directory locked and therefore nobody can delete
+ * the orphanage.
+ */
+ orphanage_inode = igrab(d_inode(orphanage_dentry));
+ if (!orphanage_inode) {
+ error = -ENOENT;
+ goto out_dput_orphanage;
+ }
+
+ /* Make sure the orphanage is owned by root. */
+ error = xrep_chown_orphanage(sc, XFS_I(orphanage_inode));
+ if (error)
+ goto out_dput_orphanage;
+
+ /* Stash the reference for later and bail out. */
+ sc->orphanage = XFS_I(orphanage_inode);
+ sc->orphanage_ilock_flags = 0;
+
+out_dput_orphanage:
+ dput(orphanage_dentry);
+out_unlock_root:
+ inode_unlock(VFS_I(sc->mp->m_rootip));
+out_dput_root:
+ dput(root_dentry);
+out:
+ return error;
+}
+
+void
+xrep_orphanage_ilock(
+ struct xfs_scrub *sc,
+ unsigned int ilock_flags)
+{
+ sc->orphanage_ilock_flags |= ilock_flags;
+ xfs_ilock(sc->orphanage, ilock_flags);
+}
+
+bool
+xrep_orphanage_ilock_nowait(
+ struct xfs_scrub *sc,
+ unsigned int ilock_flags)
+{
+ if (xfs_ilock_nowait(sc->orphanage, ilock_flags)) {
+ sc->orphanage_ilock_flags |= ilock_flags;
+ return true;
+ }
+
+ return false;
+}
+
+void
+xrep_orphanage_iunlock(
+ struct xfs_scrub *sc,
+ unsigned int ilock_flags)
+{
+ xfs_iunlock(sc->orphanage, ilock_flags);
+ sc->orphanage_ilock_flags &= ~ilock_flags;
+}
+
+/* Grab the IOLOCK of the orphanage and sc->ip. */
+int
+xrep_orphanage_iolock_two(
+ struct xfs_scrub *sc)
+{
+ int error = 0;
+
+ while (true) {
+ if (xchk_should_terminate(sc, &error))
+ return error;
+ xrep_orphanage_ilock(sc, XFS_IOLOCK_EXCL);
+ if (xchk_ilock_nowait(sc, XFS_IOLOCK_EXCL))
+ break;
+ xrep_orphanage_iunlock(sc, XFS_IOLOCK_EXCL);
+ }
+
+ return 0;
+}
+
+/* Compute block reservation needed to add sc->ip to the orphanage. */
+void
+xrep_orphanage_compute_blkres(
+ struct xfs_scrub *sc,
+ struct xrep_orphanage_req *orph)
+{
+ struct xfs_mount *mp = sc->mp;
+ bool isdir = S_ISDIR(VFS_I(sc->ip)->i_mode);
+
+ orph->sc = sc;
+ orph->orphanage_blkres = XFS_LINK_SPACE_RES(mp, MAXNAMELEN);
+ orph->child_blkres = isdir ? XFS_RENAME_SPACE_RES(mp, 2) : 0;
+}
+
+/*
+ * Compute the xfs_name for the directory entry that we're adding to the
+ * orphanage. Caller must have the IOLOCK of the orphanage and sc->ip.
+ */
+int
+xrep_orphanage_compute_name(
+ struct xrep_orphanage_req *orph,
+ unsigned char *namebuf)
+{
+ struct xfs_name *xname = &orph->xname;
+ struct xfs_scrub *sc = orph->sc;
+ xfs_ino_t ino;
+ unsigned int incr = 0;
+ int error = 0;
+
+ xname->name = namebuf;
+ xname->len = snprintf(namebuf, MAXNAMELEN, "%llu", sc->ip->i_ino);
+ xname->type = xfs_mode_to_ftype(VFS_I(sc->ip)->i_mode);
+
+ /* Make sure the filename is unique in the lost+found. */
+ error = xfs_dir_lookup(sc->tp, sc->orphanage, xname, &ino, NULL);
+ while (error == 0 && incr < 10000) {
+ xname->len = snprintf(namebuf, MAXNAMELEN, "%llu.%u",
+ sc->ip->i_ino, ++incr);
+ error = xfs_dir_lookup(sc->tp, sc->orphanage, xname, &ino,
+ NULL);
+ }
+ if (error == 0) {
+ /* We already have 10,000 entries in the orphanage? */
+ return -EFSCORRUPTED;
+ }
+
+ if (error != -ENOENT)
+ return error;
+ return 0;
+}
+
+/*
+ * Take the ILOCKs of the orphanage and sc->ip, join them to the transaction,
+ * and reserve quota to reparent the latter.
+ */
+int
+xrep_orphanage_ilock_resv_quota(
+ struct xrep_orphanage_req *orph)
+{
+ struct xfs_scrub *sc = orph->sc;
+ bool isdir = S_ISDIR(VFS_I(sc->ip)->i_mode);
+ int error;
+
+ xfs_lock_two_inodes(sc->orphanage, XFS_ILOCK_EXCL,
+ sc->ip, XFS_ILOCK_EXCL);
+ sc->ilock_flags |= XFS_ILOCK_EXCL;
+ sc->orphanage_ilock_flags |= XFS_ILOCK_EXCL;
+
+ xfs_trans_ijoin(sc->tp, sc->orphanage, 0);
+ xfs_trans_ijoin(sc->tp, sc->ip, 0);
+
+ /* Reserve enough quota in the orphan directory to add the new name. */
+ error = xfs_trans_reserve_quota_nblks(sc->tp, sc->orphanage,
+ orph->orphanage_blkres, 0, false);
+ if (error)
+ return error;
+
+ /* Reserve enough quota in the child directory to change dotdot. */
+ if (isdir) {
+ error = xfs_trans_reserve_quota_nblks(sc->tp, sc->ip,
+ orph->child_blkres, 0, false);
+ if (error)
+ return error;
+ }
+
+ return 0;
+}
+
+/*
+ * Move the current file to the orphanage.
+ *
+ * The caller must hold the IOLOCKs and the ILOCKs for both sc->ip and the
+ * orphanage. The directory entry name must have been computed, and quota
+ * reserved. The function returns with both inodes joined and ILOCKed to the
+ * transaction.
+ */
+int
+xrep_orphanage_adopt(
+ struct xrep_orphanage_req *orph)
+{
+ struct xfs_scrub *sc = orph->sc;
+ struct xfs_name *xname = &orph->xname;
+ bool isdir = S_ISDIR(VFS_I(sc->ip)->i_mode);
+ int error;
+
+ trace_xrep_orphanage_adopt(sc->orphanage, &orph->xname, sc->ip->i_ino);
+
+ /*
+ * Create the new name in the orphanage, and bump the link count of
+ * the orphanage if we just added a directory.
+ */
+ error = xfs_dir_createname(sc->tp, sc->orphanage, xname, sc->ip->i_ino,
+ orph->orphanage_blkres);
+ if (error)
+ return error;
+
+ xfs_trans_ichgtime(sc->tp, sc->orphanage,
+ XFS_ICHGTIME_MOD | XFS_ICHGTIME_CHG);
+ if (isdir)
+ xfs_bumplink(sc->tp, sc->orphanage);
+ xfs_trans_log_inode(sc->tp, sc->orphanage, XFS_ILOG_CORE);
+
+ if (!isdir)
+ return 0;
+
+ /* Replace the dotdot entry in the child directory. */
+ return xfs_dir_replace(sc->tp, sc->ip, &xfs_name_dotdot,
+ sc->orphanage->i_ino, orph->child_blkres);
+}
+
+/* Release the orphanage. */
+void
+xrep_orphanage_rele(
+ struct xfs_scrub *sc)
+{
+ if (!sc->orphanage)
+ return;
+
+ if (sc->orphanage_ilock_flags)
+ xfs_iunlock(sc->orphanage, sc->orphanage_ilock_flags);
+ xfs_irele(sc->orphanage);
+ sc->orphanage = NULL;
+}
diff --git a/fs/xfs/scrub/orphanage.h b/fs/xfs/scrub/orphanage.h
new file mode 100644
index 000000000000..9ae1082e1e53
--- /dev/null
+++ b/fs/xfs/scrub/orphanage.h
@@ -0,0 +1,78 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2021 Oracle. All Rights Reserved.
+ * Author: Darrick J. Wong <djwong@kernel.org>
+ */
+#ifndef __XFS_SCRUB_ORPHANAGE_H__
+#define __XFS_SCRUB_ORPHANAGE_H__
+
+#ifdef CONFIG_XFS_ONLINE_REPAIR
+int xrep_orphanage_create(struct xfs_scrub *sc);
+
+/*
+ * If we're doing a repair, ensure that the orphanage exists and attach it to
+ * the scrub context.
+ */
+static inline int
+xrep_orphanage_try_create(
+ struct xfs_scrub *sc)
+{
+ int error;
+
+ ASSERT(sc->sm->sm_flags & XFS_SCRUB_IFLAG_REPAIR);
+
+ error = xrep_orphanage_create(sc);
+ switch (error) {
+ case 0:
+ case -ENOENT:
+ case -ENOTDIR:
+ case -ENOSPC:
+ /*
+ * If the orphanage can't be found or isn't a directory, we'll
+ * keep going, but we won't be able to attach the file to the
+ * orphanage if we can't find the parent.
+ */
+ return 0;
+ }
+
+ return error;
+}
+
+int xrep_orphanage_iolock_two(struct xfs_scrub *sc);
+
+/* Information about a request to add a file to the orphanage. */
+struct xrep_orphanage_req {
+ /* Name structure; caller must provide a buffer separately. */
+ struct xfs_name xname;
+
+ struct xfs_scrub *sc;
+
+ /* Block reservations for orphanage and child (if directory). */
+ unsigned int orphanage_blkres;
+ unsigned int child_blkres;
+};
+
+static inline size_t
+xrep_orphanage_req_sizeof(void)
+{
+ return sizeof(struct xrep_orphanage_req) + MAXNAMELEN + 1;
+}
+
+void xrep_orphanage_compute_blkres(struct xfs_scrub *sc,
+ struct xrep_orphanage_req *orph);
+int xrep_orphanage_compute_name(struct xrep_orphanage_req *orph,
+ unsigned char *namebuf);
+int xrep_orphanage_ilock_resv_quota(struct xrep_orphanage_req *orph);
+int xrep_orphanage_adopt(struct xrep_orphanage_req *orph);
+
+void xrep_orphanage_ilock(struct xfs_scrub *sc, unsigned int ilock_flags);
+bool xrep_orphanage_ilock_nowait(struct xfs_scrub *sc,
+ unsigned int ilock_flags);
+void xrep_orphanage_iunlock(struct xfs_scrub *sc, unsigned int ilock_flags);
+
+void xrep_orphanage_rele(struct xfs_scrub *sc);
+#else
+# define xrep_orphanage_rele(sc)
+#endif /* CONFIG_XFS_ONLINE_REPAIR */
+
+#endif /* __XFS_SCRUB_ORPHANAGE_H__ */
diff --git a/fs/xfs/scrub/parent.c b/fs/xfs/scrub/parent.c
index 5e11d5141d19..ea169669e8c3 100644
--- a/fs/xfs/scrub/parent.c
+++ b/fs/xfs/scrub/parent.c
@@ -10,6 +10,7 @@
#include "xfs_trans_resv.h"
#include "xfs_mount.h"
#include "xfs_log_format.h"
+#include "xfs_trans.h"
#include "xfs_inode.h"
#include "xfs_icache.h"
#include "xfs_dir2.h"
@@ -17,12 +18,21 @@
#include "scrub/scrub.h"
#include "scrub/common.h"
#include "scrub/parent.h"
+#include "scrub/repair.h"
/* Set us up to scrub parents. */
int
xchk_setup_parent(
struct xfs_scrub *sc)
{
+ int error;
+
+ if (sc->sm->sm_flags & XFS_SCRUB_IFLAG_REPAIR) {
+ error = xrep_setup_parent(sc);
+ if (error)
+ return error;
+ }
+
return xchk_setup_inode_contents(sc, 0);
}
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. */
diff --git a/fs/xfs/scrub/repair.h b/fs/xfs/scrub/repair.h
index f9dd6a036998..df6f7c5f1ef1 100644
--- a/fs/xfs/scrub/repair.h
+++ b/fs/xfs/scrub/repair.h
@@ -75,6 +75,7 @@ int xrep_setup_ag_rmapbt(struct xfs_scrub *sc);
int xrep_setup_rtsummary(struct xfs_scrub *sc, unsigned int *resblks);
int xrep_setup_xattr(struct xfs_scrub *sc);
int xrep_setup_directory(struct xfs_scrub *sc);
+int xrep_setup_parent(struct xfs_scrub *sc);
int xrep_xattr_reset_fork(struct xfs_scrub *sc, struct xfs_inode *ip);
@@ -260,6 +261,7 @@ xrep_setup_xattr(
}
#define xrep_setup_directory xrep_setup_xattr
+#define xrep_setup_parent xrep_setup_xattr
#define xrep_revalidate_allocbt (NULL)
#define xrep_revalidate_iallocbt (NULL)
diff --git a/fs/xfs/scrub/scrub.c b/fs/xfs/scrub/scrub.c
index 14c76a55a9bb..a087d36e791e 100644
--- a/fs/xfs/scrub/scrub.c
+++ b/fs/xfs/scrub/scrub.c
@@ -27,6 +27,7 @@
#include "scrub/health.h"
#include "scrub/xfile.h"
#include "scrub/tempfile.h"
+#include "scrub/orphanage.h"
/*
* Online Scrub and Repair
@@ -196,6 +197,7 @@ xchk_teardown(
sc->buf = NULL;
}
xrep_tempfile_rele(sc);
+ xrep_orphanage_rele(sc);
return error;
}
diff --git a/fs/xfs/scrub/scrub.h b/fs/xfs/scrub/scrub.h
index 9592221edfe2..f42e1775f8b8 100644
--- a/fs/xfs/scrub/scrub.h
+++ b/fs/xfs/scrub/scrub.h
@@ -94,6 +94,10 @@ struct xfs_scrub {
/* Lock flags for @ip. */
uint ilock_flags;
+ /* The orphanage, for stashing files that have lost their parent. */
+ uint orphanage_ilock_flags;
+ struct xfs_inode *orphanage;
+
/* A temporary file on this filesystem, for staging new metadata. */
struct xfs_inode *tempip;
uint temp_ilock_flags;
diff --git a/fs/xfs/scrub/trace.h b/fs/xfs/scrub/trace.h
index afbcd9bf7001..02f58210343c 100644
--- a/fs/xfs/scrub/trace.h
+++ b/fs/xfs/scrub/trace.h
@@ -1717,6 +1717,7 @@ DEFINE_EVENT(xrep_dirent_class, name, \
TP_PROTO(struct xfs_inode *dp, struct xfs_name *name, xfs_ino_t ino), \
TP_ARGS(dp, name, ino))
DEFINE_XREP_DIRENT_CLASS(xrep_directory_insert_rec);
+DEFINE_XREP_DIRENT_CLASS(xrep_orphanage_adopt);
DECLARE_EVENT_CLASS(xrep_parent_salvage_class,
TP_PROTO(struct xfs_inode *dp, xfs_ino_t ino),
diff --git a/fs/xfs/xfs_inode.c b/fs/xfs/xfs_inode.c
index c132c4c28323..03db0f31827b 100644
--- a/fs/xfs/xfs_inode.c
+++ b/fs/xfs/xfs_inode.c
@@ -947,10 +947,10 @@ xfs_droplink(
/*
* Increment the link count on an inode & log the change.
*/
-static void
+void
xfs_bumplink(
- xfs_trans_t *tp,
- xfs_inode_t *ip)
+ struct xfs_trans *tp,
+ struct xfs_inode *ip)
{
xfs_trans_ichgtime(tp, ip, XFS_ICHGTIME_CHG);
diff --git a/fs/xfs/xfs_inode.h b/fs/xfs/xfs_inode.h
index a8eaf18b5310..adf3e905eec4 100644
--- a/fs/xfs/xfs_inode.h
+++ b/fs/xfs/xfs_inode.h
@@ -524,6 +524,7 @@ void xfs_end_io(struct work_struct *work);
int xfs_ilock2_io_mmap(struct xfs_inode *ip1, struct xfs_inode *ip2);
void xfs_iunlock2_io_mmap(struct xfs_inode *ip1, struct xfs_inode *ip2);
+void xfs_bumplink(struct xfs_trans *tp, struct xfs_inode *ip);
void xfs_inode_count_blocks(struct xfs_trans *tp, struct xfs_inode *ip,
xfs_filblks_t *dblocks, xfs_filblks_t *rblocks);