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-09-17 18:55:01 -0700
commitde1d5d9c3c194f4d8330fff5e6526c43a80a3d0d (patch)
tree786ec362b4c2ba50202e039374ef39476444840c
parentef1221bcd0249d035074b8f39faab255697eba5b (diff)
xfs: move orphan files to the orphanagerepair-dirs_2021-09-17
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/scrub/dir.c21
-rw-r--r--fs/xfs/scrub/dir_repair.c28
-rw-r--r--fs/xfs/scrub/parent.c48
-rw-r--r--fs/xfs/scrub/parent_repair.c4
-rw-r--r--fs/xfs/scrub/repair.c259
-rw-r--r--fs/xfs/scrub/repair.h2
-rw-r--r--fs/xfs/scrub/scrub.c6
-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
11 files changed, 358 insertions, 22 deletions
diff --git a/fs/xfs/scrub/dir.c b/fs/xfs/scrub/dir.c
index 2d627371de1b..3811f21b7697 100644
--- a/fs/xfs/scrub/dir.c
+++ b/fs/xfs/scrub/dir.c
@@ -28,6 +28,27 @@ xchk_setup_directory(
unsigned int sz;
int error;
+#if IS_ENABLED(CONFIG_XFS_ONLINE_REPAIR)
+ if (sc->sm->sm_flags & XFS_SCRUB_IFLAG_REPAIR) {
+ error = xrep_setup_orphanage(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.
+ */
+ break;
+ default:
+ return error;
+ }
+ }
+#endif
+
error = xrep_setup_tempfile(sc, S_IFDIR);
if (error)
return error;
diff --git a/fs/xfs/scrub/dir_repair.c b/fs/xfs/scrub/dir_repair.c
index 00922089a5e5..8f069636ae1a 100644
--- a/fs/xfs/scrub/dir_repair.c
+++ b/fs/xfs/scrub/dir_repair.c
@@ -1090,6 +1090,7 @@ xrep_dir(
.parent_ino = NULLFSINO,
.new_nlink = 2,
};
+ bool move_orphanage = false;
int error;
/* Set up some storage */
@@ -1147,17 +1148,36 @@ xrep_dir(
/*
* Validate the parent pointer that we observed while salvaging the
* directory or scan the filesystem to find one. If the scan fails
- * to find a single parent, we'll set the parent to the root dir and
- * let the parent pointer repair fix it.
+ * to find a single parent, we'll move the directory to the orphanage.
*/
error = xrep_findparent(rd.sc, &rd.parent_ino);
if (error)
return error;
- if (rd.parent_ino == NULLFSINO)
+ if (rd.parent_ino == NULLFSINO) {
rd.parent_ino = rd.sc->mp->m_sb.sb_rootino;
+ move_orphanage = true;
+ }
/* Now rebuild the directory information. */
- return xrep_dir_rebuild_tree(&rd);
+ error = xrep_dir_rebuild_tree(&rd);
+ if (error || !move_orphanage)
+ return error;
+
+ /*
+ * 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;
+ xfs_iunlock(sc->tempip, XFS_ILOCK_EXCL);
+ sc->temp_ilock_flags &= ~XFS_ILOCK_EXCL;
+ xfs_iunlock(sc->ip, XFS_ILOCK_EXCL);
+ sc->ilock_flags &= ~XFS_ILOCK_EXCL;
+
+ return xrep_move_to_orphanage(sc);
out_names:
xblob_destroy(rd.dir_names);
diff --git a/fs/xfs/scrub/parent.c b/fs/xfs/scrub/parent.c
index cac9f270e1af..02c682be29de 100644
--- a/fs/xfs/scrub/parent.c
+++ b/fs/xfs/scrub/parent.c
@@ -17,27 +17,49 @@
#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 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)
+#if IS_ENABLED(CONFIG_XFS_ONLINE_REPAIR)
+ if (sc->sm->sm_flags & XFS_SCRUB_IFLAG_REPAIR) {
+ int error;
+
+ error = xrep_setup_orphanage(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
+ * any parents.
+ */
+ break;
+ default:
return 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->flags & XCHK_TRY_HARDER) {
+ error = xchk_fs_freeze(sc);
+ if (error)
+ return error;
+ }
}
+#endif
return xchk_setup_inode_contents(sc, 0);
}
diff --git a/fs/xfs/scrub/parent_repair.c b/fs/xfs/scrub/parent_repair.c
index 511fd77d9b86..a527402a79b4 100644
--- a/fs/xfs/scrub/parent_repair.c
+++ b/fs/xfs/scrub/parent_repair.c
@@ -394,13 +394,13 @@ xrep_parent(
/*
* Try to find the parent of this directory. If we can't find it,
- * we'll just bail out for now.
+ * we'll move the directory to the orphanage.
*/
error = xrep_findparent(sc, &parent_ino);
if (error)
return error;
if (parent_ino == NULLFSINO)
- return -EFSCORRUPTED;
+ return xrep_move_to_orphanage(sc);
error = xrep_ino_dqattach(sc);
if (error)
diff --git a/fs/xfs/scrub/repair.c b/fs/xfs/scrub/repair.c
index b5d0f23b9b6d..d364d2f6e313 100644
--- a/fs/xfs/scrub/repair.c
+++ b/fs/xfs/scrub/repair.c
@@ -41,12 +41,14 @@
#include "xfs_trans_space.h"
#include "xfs_swapext.h"
#include "xfs_xchgrange.h"
+#include "xfs_icache.h"
#include "scrub/scrub.h"
#include "scrub/common.h"
#include "scrub/trace.h"
#include "scrub/repair.h"
#include "scrub/bitmap.h"
#include "scrub/xfile.h"
+#include <linux/namei.h>
/*
* Attempt to repair some metadata, if the metadata is corrupt and userspace
@@ -1879,6 +1881,263 @@ out_release_dquots:
return error;
}
+/* 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_setup_orphanage(
+ 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;
+
+ 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;
+}
+
+/*
+ * Move the current file to the orphanage. The caller must not hold any locks
+ * on the orphanage and must not hold the ILOCK on sc->ip. sc->ip and
+ * sc->orphanage must not be joined to the transaction. The function returns
+ * with both inodes joined and ILOCKed to the transaction.
+ */
+int
+xrep_move_to_orphanage(
+ struct xfs_scrub *sc)
+{
+ struct xfs_name xname;
+ unsigned char fname[MAXNAMELEN + 1];
+ struct xfs_inode *dp = sc->orphanage;
+ struct xfs_mount *mp = sc->mp;
+ xfs_ino_t ino;
+ unsigned int incr = 0;
+ unsigned int linkres, dotdotres;
+ bool isdir = S_ISDIR(VFS_I(sc->ip)->i_mode);
+ int error;
+
+ /* No orphanage? We can't fix this. */
+ if (!sc->orphanage)
+ return -EFSCORRUPTED;
+
+ /* Try to grab the IOLOCK on the orphanage. */
+ error = xchk_ilock_inverted(sc->orphanage, XFS_IOLOCK_EXCL);
+ if (error)
+ return error;
+ sc->orphanage_ilock_flags |= XFS_IOLOCK_EXCL;
+
+ xname.name = fname;
+ xname.len = snprintf(fname, sizeof(fname), "%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, dp, &xname, &ino, NULL);
+ while (error == 0 && incr < 10000) {
+ xname.len = snprintf(fname, sizeof(fname), "%llu.%u",
+ sc->ip->i_ino, ++incr);
+ error = xfs_dir_lookup(sc->tp, dp, &xname, &ino, NULL);
+ }
+ if (error == 0) {
+ /* We already have 10,000 entries in the orphanage? */
+ return -EFSCORRUPTED;
+ }
+ if (error != -ENOENT)
+ return error;
+
+ trace_xrep_move_orphanage(sc->ip, &xname, dp->i_ino);
+
+ xfs_lock_two_inodes(dp, 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, dp, 0);
+ xfs_trans_ijoin(sc->tp, sc->ip, 0);
+
+ /*
+ * Reserve enough space to add a directory entry to the orphanage and
+ * update the dotdot entry.
+ */
+ linkres = XFS_LINK_SPACE_RES(mp, xname.len);
+ dotdotres = isdir ? XFS_RENAME_SPACE_RES(mp, 2) : 0;
+ error = xfs_trans_reserve_more(sc->tp, linkres + dotdotres, 0);
+ if (error)
+ return error;
+
+ /* Reserve enough quota in the orphan directory to add the new name. */
+ error = xfs_trans_reserve_quota_nblks(sc->tp, dp, linkres, 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,
+ dotdotres, 0, false);
+ if (error)
+ return error;
+ }
+
+ /*
+ * 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, dp, &xname, sc->ip->i_ino,
+ linkres);
+ if (error)
+ return error;
+
+ xfs_trans_ichgtime(sc->tp, dp, XFS_ICHGTIME_MOD | XFS_ICHGTIME_CHG);
+ if (isdir)
+ xfs_bumplink(sc->tp, dp);
+ xfs_trans_log_inode(sc->tp, dp, 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, dp->i_ino,
+ dotdotres);
+}
+
/*
* Make sure that the given range of the data fork of the temporary file is
* mapped to written blocks. The caller must ensure that both inodes are
diff --git a/fs/xfs/scrub/repair.h b/fs/xfs/scrub/repair.h
index 690a3cfd7264..eb8749971104 100644
--- a/fs/xfs/scrub/repair.h
+++ b/fs/xfs/scrub/repair.h
@@ -41,6 +41,8 @@ int xrep_init_btblock(struct xfs_scrub *sc, xfs_fsblock_t fsb,
struct xfs_buf **bpp, xfs_btnum_t btnum,
const struct xfs_buf_ops *ops);
int xrep_setup_tempfile(struct xfs_scrub *sc, uint16_t mode);
+int xrep_setup_orphanage(struct xfs_scrub *sc);
+int xrep_move_to_orphanage(struct xfs_scrub *sc);
int xrep_fallocate(struct xfs_scrub *sc, xfs_fileoff_t off, xfs_filblks_t len);
typedef int (*xrep_setfile_getbuf_fn)(struct xfs_scrub *sc,
diff --git a/fs/xfs/scrub/scrub.c b/fs/xfs/scrub/scrub.c
index 51e047f6d70f..d7e05959f2d7 100644
--- a/fs/xfs/scrub/scrub.c
+++ b/fs/xfs/scrub/scrub.c
@@ -204,6 +204,12 @@ xchk_teardown(
xfs_irele(sc->tempip);
sc->tempip = NULL;
}
+ if (sc->orphanage) {
+ if (sc->orphanage_ilock_flags)
+ xfs_iunlock(sc->orphanage, sc->orphanage_ilock_flags);
+ xfs_irele(sc->orphanage);
+ sc->orphanage = NULL;
+ }
return error;
}
diff --git a/fs/xfs/scrub/scrub.h b/fs/xfs/scrub/scrub.h
index 3994fcdf5916..b092a1b67d7c 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 0b2c9bf57408..7e7e053721c6 100644
--- a/fs/xfs/scrub/trace.h
+++ b/fs/xfs/scrub/trace.h
@@ -1638,6 +1638,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_dir_insert_rec);
+DEFINE_XREP_DIRENT_CLASS(xrep_move_orphanage);
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 fc71a780476d..43e1098f25ad 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);