xfs: mark metadir repair tempfiles with IRECOVERY

Once in a long while, xfs/566 and xfs/801 report directory corruption in
one of the metadata subdirectories while it's forcibly rebuilding all
filesystem metadata.  I observed the following sequence of events:

1. Initiate a repair of the parent pointers for the /quota/user file.
   This is the secret file containing user quota data.

2. The pptr repair thread creates a temporary file and begins staging
   parent pointers in the ondisk metadata in preparation for an
   exchange-range to commit the new pptr data.

3. At the same time, initiate a repair of the /quota directory itself.

4. The dir repair thread finds the temporary file from (2), scans it for
   parent pointers, and stages a dirent in its own temporary dir in
   preparation to commit the fixed directory.

5. The parent pointer repair completes and frees the temporary file.

6. The dir repair commits the new directory and scans it again.  It
   finds the dirent that points to the old temporary file in (2) and
   marks the directory corrupt.

Oops!  Repair code must never scan the temporary files that other repair
functions create to stage new metadata.  They're not supposed to do
that, but the predicate function xrep_is_tempfile is incorrect because
it assumes that any XFS_DIFLAG2_METADATA file cannot ever be a temporary
file, but xrep_tempfile_adjust_directory_tree creates exactly that.

Fix this by setting the IRECOVERY flag on temporary metadata directory
inodes and using that to correct the predicate.  Repair code is supposed
to erase all the data in temporary files before releasing them, so it's
ok if a thread scans the temporary file after we drop IRECOVERY.

Cc: <stable@vger.kernel.org> # v6.13-rc1
Fixes: bb6cdd5529 ("xfs: hide metadata inodes from everyone because they are special")
Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
Reviewed-by: Christoph Hellwig <hch@lst.de>
This commit is contained in:
Darrick J. Wong 2024-12-02 10:57:29 -08:00
parent 6f4669708a
commit dc5a052739
2 changed files with 9 additions and 3 deletions

View File

@ -223,6 +223,7 @@ xrep_tempfile_adjust_directory_tree(
if (error)
goto out_ilock;
xfs_iflags_set(sc->tempip, XFS_IRECOVERY);
xfs_qm_dqdetach(sc->tempip);
out_ilock:
xrep_tempfile_iunlock(sc);
@ -246,6 +247,8 @@ xrep_tempfile_remove_metadir(
ASSERT(sc->tp == NULL);
xfs_iflags_clear(sc->tempip, XFS_IRECOVERY);
xfs_ilock(sc->tempip, XFS_IOLOCK_EXCL);
sc->temp_ilock_flags |= XFS_IOLOCK_EXCL;
@ -945,10 +948,13 @@ xrep_is_tempfile(
/*
* Files in the metadata directory tree also have S_PRIVATE set and
* IOP_XATTR unset, so we must distinguish them separately.
* IOP_XATTR unset, so we must distinguish them separately. We (ab)use
* the IRECOVERY flag to mark temporary metadir inodes knowing that the
* end of log recovery clears IRECOVERY, so the only ones that can
* exist during online repair are the ones we create.
*/
if (xfs_has_metadir(mp) && (ip->i_diflags2 & XFS_DIFLAG2_METADATA))
return false;
return __xfs_iflags_test(ip, XFS_IRECOVERY);
if (IS_PRIVATE(inode) && !(inode->i_opflags & IOP_XATTR))
return true;

View File

@ -231,7 +231,7 @@ xfs_iflags_clear(xfs_inode_t *ip, unsigned long flags)
}
static inline int
__xfs_iflags_test(xfs_inode_t *ip, unsigned long flags)
__xfs_iflags_test(const struct xfs_inode *ip, unsigned long flags)
{
return (ip->i_flags & flags);
}