| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627 |
- // SPDX-License-Identifier: GPL-2.0-or-later
- /*
- * Copyright (c) 2021-2024 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 "xfs_parent.h"
- #include "xfs_attr_sf.h"
- #include "scrub/scrub.h"
- #include "scrub/common.h"
- #include "scrub/repair.h"
- #include "scrub/trace.h"
- #include "scrub/orphanage.h"
- #include "scrub/readdir.h"
- #include <linux/namei.h>
- /*
- * The Orphanage
- * =============
- *
- * If the directory tree is damaged, children of that directory become
- * inaccessible via that file path. If a child has no other parents, the file
- * is said to be orphaned. xfs_repair fixes this situation by creating a
- * orphanage directory (specifically, /lost+found) and creating a directory
- * entry pointing to the orphaned file.
- *
- * Online repair follows this tactic by creating a root-owned /lost+found
- * directory if one does not exist. If an orphan is found, it will move that
- * files into orphanage.
- */
- /* 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;
- /*
- * Always clear setuid/setgid/sticky on the orphanage since we don't
- * normally want that functionality on this directory and xfs_repair
- * doesn't create it this way either. Leave the other access bits
- * unchanged.
- */
- inode->i_mode &= ~(S_ISUID | S_ISGID | S_ISVTX);
- /*
- * 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;
- }
- dp->i_diflags &= ~(XFS_DIFLAG_REALTIME | XFS_DIFLAG_RTINHERIT);
- 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. Create the
- * directory without other-user access because we're live and someone
- * could have been relying partly on minimal access to a parent
- * directory to control access to a file we put in here.
- */
- if (d_really_is_negative(orphanage_dentry)) {
- error = vfs_mkdir(&nop_mnt_idmap, root_inode, orphanage_dentry,
- 0750);
- 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;
- /*
- * Normal XFS takes the IOLOCK before grabbing a transaction.
- * Scrub holds a transaction, which means that we can't block
- * on either IOLOCK.
- */
- if (xrep_orphanage_ilock_nowait(sc, XFS_IOLOCK_EXCL)) {
- if (xchk_ilock_nowait(sc, XFS_IOLOCK_EXCL))
- break;
- xrep_orphanage_iunlock(sc, XFS_IOLOCK_EXCL);
- }
- delay(1);
- }
- return 0;
- }
- /* 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);
- xchk_irele(sc, sc->orphanage);
- sc->orphanage = NULL;
- }
- /* Adoption moves a file into /lost+found */
- /* Can the orphanage adopt @sc->ip? */
- bool
- xrep_orphanage_can_adopt(
- struct xfs_scrub *sc)
- {
- ASSERT(sc->ip != NULL);
- if (!sc->orphanage)
- return false;
- if (sc->ip == sc->orphanage)
- return false;
- if (xfs_internal_inum(sc->mp, sc->ip->i_ino))
- return false;
- return true;
- }
- /*
- * Create a new transaction to send a child to the orphanage.
- *
- * Allocate a new transaction with sufficient disk space to handle the
- * adoption, take ILOCK_EXCL of the orphanage and sc->ip, joins them to the
- * transaction, and reserve quota to reparent the latter. Caller must hold the
- * IOLOCK of the orphanage and sc->ip.
- */
- int
- xrep_adoption_trans_alloc(
- struct xfs_scrub *sc,
- struct xrep_adoption *adopt)
- {
- struct xfs_mount *mp = sc->mp;
- unsigned int child_blkres = 0;
- int error;
- ASSERT(sc->tp == NULL);
- ASSERT(sc->ip != NULL);
- ASSERT(sc->orphanage != NULL);
- ASSERT(sc->ilock_flags & XFS_IOLOCK_EXCL);
- ASSERT(sc->orphanage_ilock_flags & XFS_IOLOCK_EXCL);
- ASSERT(!(sc->ilock_flags & (XFS_ILOCK_SHARED | XFS_ILOCK_EXCL)));
- ASSERT(!(sc->orphanage_ilock_flags &
- (XFS_ILOCK_SHARED | XFS_ILOCK_EXCL)));
- /* Compute the worst case space reservation that we need. */
- adopt->sc = sc;
- adopt->orphanage_blkres = xfs_link_space_res(mp, MAXNAMELEN);
- if (S_ISDIR(VFS_I(sc->ip)->i_mode))
- child_blkres = xfs_rename_space_res(mp, 0, false,
- xfs_name_dotdot.len, false);
- if (xfs_has_parent(mp))
- child_blkres += XFS_ADDAFORK_SPACE_RES(mp);
- adopt->child_blkres = child_blkres;
- /*
- * Allocate a transaction to link the child into the parent, along with
- * enough disk space to handle expansion of both the orphanage and the
- * dotdot entry of a child directory.
- */
- error = xfs_trans_alloc(mp, &M_RES(mp)->tr_link,
- adopt->orphanage_blkres + adopt->child_blkres, 0, 0,
- &sc->tp);
- if (error)
- return 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.
- * Normally the orphanage should have user/group/project ids of zero
- * and hence is not subject to quota enforcement, but we're allowed to
- * exceed quota to reattach disconnected parts of the directory tree.
- */
- error = xfs_trans_reserve_quota_nblks(sc->tp, sc->orphanage,
- adopt->orphanage_blkres, 0, true);
- if (error)
- goto out_cancel;
- /*
- * Reserve enough quota in the child directory to change dotdot.
- * Here we're also allowed to exceed file quota to repair inconsistent
- * metadata.
- */
- if (adopt->child_blkres) {
- error = xfs_trans_reserve_quota_nblks(sc->tp, sc->ip,
- adopt->child_blkres, 0, true);
- if (error)
- goto out_cancel;
- }
- return 0;
- out_cancel:
- xchk_trans_cancel(sc);
- xrep_orphanage_iunlock(sc, XFS_ILOCK_EXCL);
- xchk_iunlock(sc, XFS_ILOCK_EXCL);
- return error;
- }
- /*
- * Compute the xfs_name for the directory entry that we're adding to the
- * orphanage. Caller must hold ILOCKs of sc->ip and the orphanage and must not
- * reuse namebuf until the adoption completes or is dissolved.
- */
- int
- xrep_adoption_compute_name(
- struct xrep_adoption *adopt,
- struct xfs_name *xname)
- {
- struct xfs_scrub *sc = adopt->sc;
- char *namebuf = (void *)xname->name;
- xfs_ino_t ino;
- unsigned int incr = 0;
- int error = 0;
- adopt->xname = xname;
- 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 = xchk_dir_lookup(sc, sc->orphanage, xname, &ino);
- while (error == 0 && incr < 10000) {
- xname->len = snprintf(namebuf, MAXNAMELEN, "%llu.%u",
- sc->ip->i_ino, ++incr);
- error = xchk_dir_lookup(sc, sc->orphanage, xname, &ino);
- }
- if (error == 0) {
- /* We already have 10,000 entries in the orphanage? */
- return -EFSCORRUPTED;
- }
- if (error != -ENOENT)
- return error;
- return 0;
- }
- /*
- * Make sure the dcache does not have a positive dentry for the name we've
- * chosen. The caller should have checked with the ondisk directory, so any
- * discrepancy is a sign that something is seriously wrong.
- */
- static int
- xrep_adoption_check_dcache(
- struct xrep_adoption *adopt)
- {
- struct qstr qname = QSTR_INIT(adopt->xname->name,
- adopt->xname->len);
- struct xfs_scrub *sc = adopt->sc;
- struct dentry *d_orphanage, *d_child;
- int error = 0;
- d_orphanage = d_find_alias(VFS_I(sc->orphanage));
- if (!d_orphanage)
- return 0;
- d_child = d_hash_and_lookup(d_orphanage, &qname);
- if (d_child) {
- trace_xrep_adoption_check_child(sc->mp, d_child);
- if (d_is_positive(d_child)) {
- ASSERT(d_is_negative(d_child));
- error = -EFSCORRUPTED;
- }
- dput(d_child);
- }
- dput(d_orphanage);
- return error;
- }
- /*
- * Invalidate all dentries for the name that was added to the orphanage
- * directory, and all dentries pointing to the child inode that was moved.
- *
- * There should not be any positive entries for the name, since we've
- * maintained our lock on the orphanage directory.
- */
- static void
- xrep_adoption_zap_dcache(
- struct xrep_adoption *adopt)
- {
- struct qstr qname = QSTR_INIT(adopt->xname->name,
- adopt->xname->len);
- struct xfs_scrub *sc = adopt->sc;
- struct dentry *d_orphanage, *d_child;
- /* Invalidate all dentries for the adoption name */
- d_orphanage = d_find_alias(VFS_I(sc->orphanage));
- if (!d_orphanage)
- return;
- d_child = d_hash_and_lookup(d_orphanage, &qname);
- while (d_child != NULL) {
- trace_xrep_adoption_invalidate_child(sc->mp, d_child);
- ASSERT(d_is_negative(d_child));
- d_invalidate(d_child);
- dput(d_child);
- d_child = d_lookup(d_orphanage, &qname);
- }
- dput(d_orphanage);
- /* Invalidate all the dentries pointing down to this file. */
- while ((d_child = d_find_alias(VFS_I(sc->ip))) != NULL) {
- trace_xrep_adoption_invalidate_child(sc->mp, d_child);
- d_invalidate(d_child);
- dput(d_child);
- }
- }
- /*
- * If we have to add an attr fork ahead of a parent pointer update, how much
- * space should we ask for?
- */
- static inline int
- xrep_adoption_attr_sizeof(
- const struct xrep_adoption *adopt)
- {
- return sizeof(struct xfs_attr_sf_hdr) +
- xfs_attr_sf_entsize_byname(sizeof(struct xfs_parent_rec),
- adopt->xname->len);
- }
- /*
- * Move the current file to the orphanage under the computed name.
- *
- * Returns with a dirty transaction so that the caller can handle any other
- * work, such as fixing up unlinked lists or resetting link counts.
- */
- int
- xrep_adoption_move(
- struct xrep_adoption *adopt)
- {
- struct xfs_scrub *sc = adopt->sc;
- bool isdir = S_ISDIR(VFS_I(sc->ip)->i_mode);
- int error;
- trace_xrep_adoption_reparent(sc->orphanage, adopt->xname,
- sc->ip->i_ino);
- error = xrep_adoption_check_dcache(adopt);
- if (error)
- return error;
- /*
- * If this filesystem has parent pointers, ensure that the file being
- * moved to the orphanage has an attribute fork. This is required
- * because the parent pointer code does not itself add attr forks.
- */
- if (!xfs_inode_has_attr_fork(sc->ip) && xfs_has_parent(sc->mp)) {
- int sf_size = xrep_adoption_attr_sizeof(adopt);
- error = xfs_bmap_add_attrfork(sc->tp, sc->ip, sf_size, true);
- if (error)
- return error;
- }
- /* Create the new name in the orphanage. */
- error = xfs_dir_createname(sc->tp, sc->orphanage, adopt->xname,
- sc->ip->i_ino, adopt->orphanage_blkres);
- if (error)
- return error;
- /*
- * Bump the link count of the orphanage if we just added a
- * subdirectory, and update its timestamps.
- */
- 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);
- /* Bump the link count of the child. */
- if (adopt->bump_child_nlink) {
- xfs_bumplink(sc->tp, sc->ip);
- xfs_trans_log_inode(sc->tp, sc->ip, XFS_ILOG_CORE);
- }
- /* Replace the dotdot entry if the child is a subdirectory. */
- if (isdir) {
- error = xfs_dir_replace(sc->tp, sc->ip, &xfs_name_dotdot,
- sc->orphanage->i_ino, adopt->child_blkres);
- if (error)
- return error;
- }
- /* Add a parent pointer from the file back to the lost+found. */
- if (xfs_has_parent(sc->mp)) {
- error = xfs_parent_addname(sc->tp, &adopt->ppargs,
- sc->orphanage, adopt->xname, sc->ip);
- if (error)
- return error;
- }
- /*
- * Notify dirent hooks that we moved the file to /lost+found, and
- * finish all the deferred work so that we know the adoption is fully
- * recorded in the log.
- */
- xfs_dir_update_hook(sc->orphanage, sc->ip, 1, adopt->xname);
- /* Remove negative dentries from the lost+found's dcache */
- xrep_adoption_zap_dcache(adopt);
- return 0;
- }
- /*
- * Roll to a clean scrub transaction so that we can release the orphanage,
- * even if xrep_adoption_move was not called.
- *
- * Commits all the work and deferred ops attached to an adoption request and
- * rolls to a clean scrub transaction. On success, returns 0 with the scrub
- * context holding a clean transaction with no inodes joined. On failure,
- * returns negative errno with no scrub transaction. All inode locks are
- * still held after this function returns.
- */
- int
- xrep_adoption_trans_roll(
- struct xrep_adoption *adopt)
- {
- struct xfs_scrub *sc = adopt->sc;
- int error;
- trace_xrep_adoption_trans_roll(sc->orphanage, sc->ip,
- !!(sc->tp->t_flags & XFS_TRANS_DIRTY));
- /* Finish all the deferred ops to commit all repairs. */
- error = xrep_defer_finish(sc);
- if (error)
- return error;
- /* Roll the transaction once more to detach the inodes. */
- return xfs_trans_roll(&sc->tp);
- }
|