diff options
author | Darrick J. Wong <djwong@kernel.org> | 2021-09-01 10:59:08 -0700 |
---|---|---|
committer | Darrick J. Wong <djwong@kernel.org> | 2021-09-17 18:55:01 -0700 |
commit | de1d5d9c3c194f4d8330fff5e6526c43a80a3d0d (patch) | |
tree | 786ec362b4c2ba50202e039374ef39476444840c | |
parent | ef1221bcd0249d035074b8f39faab255697eba5b (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.c | 21 | ||||
-rw-r--r-- | fs/xfs/scrub/dir_repair.c | 28 | ||||
-rw-r--r-- | fs/xfs/scrub/parent.c | 48 | ||||
-rw-r--r-- | fs/xfs/scrub/parent_repair.c | 4 | ||||
-rw-r--r-- | fs/xfs/scrub/repair.c | 259 | ||||
-rw-r--r-- | fs/xfs/scrub/repair.h | 2 | ||||
-rw-r--r-- | fs/xfs/scrub/scrub.c | 6 | ||||
-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 |
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); |