summaryrefslogtreecommitdiff
path: root/fs/xfs/scrub/repair.c
diff options
context:
space:
mode:
Diffstat (limited to 'fs/xfs/scrub/repair.c')
-rw-r--r--fs/xfs/scrub/repair.c259
1 files changed, 259 insertions, 0 deletions
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