diff options
author | Darrick J. Wong <djwong@kernel.org> | 2021-09-01 10:59:08 -0700 |
---|---|---|
committer | Darrick J. Wong <djwong@kernel.org> | 2021-10-22 16:40:46 -0700 |
commit | 83afbcfdb3ddd64b827c2197da6743effb5a9a3f (patch) | |
tree | 34dbcf4ebbffd084fc48138001bc81c305202469 | |
parent | 6780caa2619855a1afc3751453a6ea1d9424c67e (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/Makefile | 1 | ||||
-rw-r--r-- | fs/xfs/scrub/dir_repair.c | 123 | ||||
-rw-r--r-- | fs/xfs/scrub/orphanage.c | 381 | ||||
-rw-r--r-- | fs/xfs/scrub/orphanage.h | 78 | ||||
-rw-r--r-- | fs/xfs/scrub/parent.c | 10 | ||||
-rw-r--r-- | fs/xfs/scrub/parent_repair.c | 97 | ||||
-rw-r--r-- | fs/xfs/scrub/repair.h | 2 | ||||
-rw-r--r-- | fs/xfs/scrub/scrub.c | 2 | ||||
-rw-r--r-- | fs/xfs/scrub/scrub.h | 4 | ||||
-rw-r--r-- | fs/xfs/scrub/trace.h | 1 | ||||
-rw-r--r-- | fs/xfs/xfs_inode.c | 6 | ||||
-rw-r--r-- | fs/xfs/xfs_inode.h | 1 |
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); |