a26dc21309
Once we've assembled all the parent pointers for a file, we need to commit the new dataset atomically to that file. Parent pointer records are embedded in the xattr structure, which means that we must write a new extended attribute structure, again, atomically. Therefore, we must copy the non-parent-pointer attributes from the file being repaired into the temporary file's extended attributes and then call the atomic extent swap mechanism to exchange the blocks. Signed-off-by: Darrick J. Wong <djwong@kernel.org> Reviewed-by: Christoph Hellwig <hch@lst.de>
455 lines
11 KiB
C
455 lines
11 KiB
C
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
/*
|
|
* Copyright (c) 2020-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_defer.h"
|
|
#include "xfs_bit.h"
|
|
#include "xfs_log_format.h"
|
|
#include "xfs_trans.h"
|
|
#include "xfs_sb.h"
|
|
#include "xfs_inode.h"
|
|
#include "xfs_icache.h"
|
|
#include "xfs_da_format.h"
|
|
#include "xfs_da_btree.h"
|
|
#include "xfs_dir2.h"
|
|
#include "xfs_bmap_btree.h"
|
|
#include "xfs_dir2_priv.h"
|
|
#include "xfs_trans_space.h"
|
|
#include "xfs_health.h"
|
|
#include "xfs_exchmaps.h"
|
|
#include "xfs_parent.h"
|
|
#include "scrub/xfs_scrub.h"
|
|
#include "scrub/scrub.h"
|
|
#include "scrub/common.h"
|
|
#include "scrub/trace.h"
|
|
#include "scrub/repair.h"
|
|
#include "scrub/iscan.h"
|
|
#include "scrub/findparent.h"
|
|
#include "scrub/readdir.h"
|
|
#include "scrub/tempfile.h"
|
|
#include "scrub/listxattr.h"
|
|
|
|
/*
|
|
* Finding the Parent of a Directory
|
|
* =================================
|
|
*
|
|
* Directories have parent pointers, in the sense that each directory contains
|
|
* a dotdot entry that points to the single allowed parent. The brute force
|
|
* way to find the parent of a given directory is to scan every directory in
|
|
* the filesystem looking for a child dirent that references this directory.
|
|
*
|
|
* This module wraps the process of scanning the directory tree. It requires
|
|
* that @sc->ip is the directory whose parent we want to find, and that the
|
|
* caller hold only the IOLOCK on that directory. The scan itself needs to
|
|
* take the ILOCK of each directory visited.
|
|
*
|
|
* Because we cannot hold @sc->ip's ILOCK during a scan of the whole fs, it is
|
|
* necessary to use dirent hook to update the parent scan results. Callers
|
|
* must not read the scan results without re-taking @sc->ip's ILOCK.
|
|
*
|
|
* There are a few shortcuts that we can take to avoid scanning the entire
|
|
* filesystem, such as noticing directory tree roots and querying the dentry
|
|
* cache for parent information.
|
|
*/
|
|
|
|
struct xrep_findparent_info {
|
|
/* The directory currently being scanned. */
|
|
struct xfs_inode *dp;
|
|
|
|
/*
|
|
* Scrub context. We're looking for a @dp containing a directory
|
|
* entry pointing to sc->ip->i_ino.
|
|
*/
|
|
struct xfs_scrub *sc;
|
|
|
|
/* Optional scan information for a xrep_findparent_scan call. */
|
|
struct xrep_parent_scan_info *parent_scan;
|
|
|
|
/*
|
|
* Parent that we've found for sc->ip. If we're scanning the entire
|
|
* directory tree, we need this to ensure that we only find /one/
|
|
* parent directory.
|
|
*/
|
|
xfs_ino_t found_parent;
|
|
|
|
/*
|
|
* This is set to true if @found_parent was not observed directly from
|
|
* the directory scan but by noticing a change in dotdot entries after
|
|
* cycling the sc->ip IOLOCK.
|
|
*/
|
|
bool parent_tentative;
|
|
};
|
|
|
|
/*
|
|
* If this directory entry points to the scrub target inode, then the directory
|
|
* we're scanning is the parent of the scrub target inode.
|
|
*/
|
|
STATIC int
|
|
xrep_findparent_dirent(
|
|
struct xfs_scrub *sc,
|
|
struct xfs_inode *dp,
|
|
xfs_dir2_dataptr_t dapos,
|
|
const struct xfs_name *name,
|
|
xfs_ino_t ino,
|
|
void *priv)
|
|
{
|
|
struct xrep_findparent_info *fpi = priv;
|
|
int error = 0;
|
|
|
|
if (xchk_should_terminate(fpi->sc, &error))
|
|
return error;
|
|
|
|
if (ino != fpi->sc->ip->i_ino)
|
|
return 0;
|
|
|
|
/* Ignore garbage directory entry names. */
|
|
if (name->len == 0 || !xfs_dir2_namecheck(name->name, name->len))
|
|
return -EFSCORRUPTED;
|
|
|
|
/*
|
|
* Ignore dotdot and dot entries -- we're looking for parent -> child
|
|
* links only.
|
|
*/
|
|
if (name->name[0] == '.' && (name->len == 1 ||
|
|
(name->len == 2 && name->name[1] == '.')))
|
|
return 0;
|
|
|
|
/* Uhoh, more than one parent for a dir? */
|
|
if (fpi->found_parent != NULLFSINO &&
|
|
!(fpi->parent_tentative && fpi->found_parent == fpi->dp->i_ino)) {
|
|
trace_xrep_findparent_dirent(fpi->sc->ip, 0);
|
|
return -EFSCORRUPTED;
|
|
}
|
|
|
|
/* We found a potential parent; remember this. */
|
|
trace_xrep_findparent_dirent(fpi->sc->ip, fpi->dp->i_ino);
|
|
fpi->found_parent = fpi->dp->i_ino;
|
|
fpi->parent_tentative = false;
|
|
|
|
if (fpi->parent_scan)
|
|
xrep_findparent_scan_found(fpi->parent_scan, fpi->dp->i_ino);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* If this is a directory, walk the dirents looking for any that point to the
|
|
* scrub target inode.
|
|
*/
|
|
STATIC int
|
|
xrep_findparent_walk_directory(
|
|
struct xrep_findparent_info *fpi)
|
|
{
|
|
struct xfs_scrub *sc = fpi->sc;
|
|
struct xfs_inode *dp = fpi->dp;
|
|
unsigned int lock_mode;
|
|
int error = 0;
|
|
|
|
/*
|
|
* The inode being scanned cannot be its own parent, nor can any
|
|
* temporary directory we created to stage this repair.
|
|
*/
|
|
if (dp == sc->ip || dp == sc->tempip)
|
|
return 0;
|
|
|
|
/*
|
|
* Similarly, temporary files created to stage a repair cannot be the
|
|
* parent of this inode.
|
|
*/
|
|
if (xrep_is_tempfile(dp))
|
|
return 0;
|
|
|
|
/*
|
|
* Scan the directory to see if there it contains an entry pointing to
|
|
* the directory that we are repairing.
|
|
*/
|
|
lock_mode = xfs_ilock_data_map_shared(dp);
|
|
|
|
/*
|
|
* If this directory is known to be sick, we cannot scan it reliably
|
|
* and must abort.
|
|
*/
|
|
if (xfs_inode_has_sickness(dp, XFS_SICK_INO_CORE |
|
|
XFS_SICK_INO_BMBTD |
|
|
XFS_SICK_INO_DIR)) {
|
|
error = -EFSCORRUPTED;
|
|
goto out_unlock;
|
|
}
|
|
|
|
/*
|
|
* We cannot complete our parent pointer scan if a directory looks as
|
|
* though it has been zapped by the inode record repair code.
|
|
*/
|
|
if (xchk_dir_looks_zapped(dp)) {
|
|
error = -EBUSY;
|
|
goto out_unlock;
|
|
}
|
|
|
|
error = xchk_dir_walk(sc, dp, xrep_findparent_dirent, fpi);
|
|
if (error)
|
|
goto out_unlock;
|
|
|
|
out_unlock:
|
|
xfs_iunlock(dp, lock_mode);
|
|
return error;
|
|
}
|
|
|
|
/*
|
|
* Update this directory's dotdot pointer based on ongoing dirent updates.
|
|
*/
|
|
STATIC int
|
|
xrep_findparent_live_update(
|
|
struct notifier_block *nb,
|
|
unsigned long action,
|
|
void *data)
|
|
{
|
|
struct xfs_dir_update_params *p = data;
|
|
struct xrep_parent_scan_info *pscan;
|
|
struct xfs_scrub *sc;
|
|
|
|
pscan = container_of(nb, struct xrep_parent_scan_info,
|
|
dhook.dirent_hook.nb);
|
|
sc = pscan->sc;
|
|
|
|
/*
|
|
* If @p->ip is the subdirectory that we're interested in and we've
|
|
* already scanned @p->dp, update the dotdot target inumber to the
|
|
* parent inode.
|
|
*/
|
|
if (p->ip->i_ino == sc->ip->i_ino &&
|
|
xchk_iscan_want_live_update(&pscan->iscan, p->dp->i_ino)) {
|
|
if (p->delta > 0) {
|
|
xrep_findparent_scan_found(pscan, p->dp->i_ino);
|
|
} else {
|
|
xrep_findparent_scan_found(pscan, NULLFSINO);
|
|
}
|
|
}
|
|
|
|
return NOTIFY_DONE;
|
|
}
|
|
|
|
/*
|
|
* Set up a scan to find the parent of a directory. The provided dirent hook
|
|
* will be called when there is a dotdot update for the inode being repaired.
|
|
*/
|
|
int
|
|
__xrep_findparent_scan_start(
|
|
struct xfs_scrub *sc,
|
|
struct xrep_parent_scan_info *pscan,
|
|
notifier_fn_t custom_fn)
|
|
{
|
|
int error;
|
|
|
|
if (!(sc->flags & XCHK_FSGATES_DIRENTS)) {
|
|
ASSERT(sc->flags & XCHK_FSGATES_DIRENTS);
|
|
return -EINVAL;
|
|
}
|
|
|
|
pscan->sc = sc;
|
|
pscan->parent_ino = NULLFSINO;
|
|
|
|
mutex_init(&pscan->lock);
|
|
|
|
xchk_iscan_start(sc, 30000, 100, &pscan->iscan);
|
|
|
|
/*
|
|
* Hook into the dirent update code. The hook only operates on inodes
|
|
* that were already scanned, and the scanner thread takes each inode's
|
|
* ILOCK, which means that any in-progress inode updates will finish
|
|
* before we can scan the inode.
|
|
*/
|
|
if (custom_fn)
|
|
xfs_dir_hook_setup(&pscan->dhook, custom_fn);
|
|
else
|
|
xfs_dir_hook_setup(&pscan->dhook, xrep_findparent_live_update);
|
|
error = xfs_dir_hook_add(sc->mp, &pscan->dhook);
|
|
if (error)
|
|
goto out_iscan;
|
|
|
|
return 0;
|
|
out_iscan:
|
|
xchk_iscan_teardown(&pscan->iscan);
|
|
mutex_destroy(&pscan->lock);
|
|
return error;
|
|
}
|
|
|
|
/*
|
|
* Scan the entire filesystem looking for a parent inode for the inode being
|
|
* scrubbed. @sc->ip must not be the root of a directory tree. Callers must
|
|
* not hold a dirty transaction or any lock that would interfere with taking
|
|
* an ILOCK.
|
|
*
|
|
* Returns 0 with @pscan->parent_ino set to the parent that we found.
|
|
* Returns 0 with @pscan->parent_ino set to NULLFSINO if we found no parents.
|
|
* Returns the usual negative errno if something else happened.
|
|
*/
|
|
int
|
|
xrep_findparent_scan(
|
|
struct xrep_parent_scan_info *pscan)
|
|
{
|
|
struct xrep_findparent_info fpi = {
|
|
.sc = pscan->sc,
|
|
.found_parent = NULLFSINO,
|
|
.parent_scan = pscan,
|
|
};
|
|
struct xfs_scrub *sc = pscan->sc;
|
|
int ret;
|
|
|
|
ASSERT(S_ISDIR(VFS_IC(sc->ip)->i_mode));
|
|
|
|
while ((ret = xchk_iscan_iter(&pscan->iscan, &fpi.dp)) == 1) {
|
|
if (S_ISDIR(VFS_I(fpi.dp)->i_mode))
|
|
ret = xrep_findparent_walk_directory(&fpi);
|
|
else
|
|
ret = 0;
|
|
xchk_iscan_mark_visited(&pscan->iscan, fpi.dp);
|
|
xchk_irele(sc, fpi.dp);
|
|
if (ret)
|
|
break;
|
|
|
|
if (xchk_should_terminate(sc, &ret))
|
|
break;
|
|
}
|
|
xchk_iscan_iter_finish(&pscan->iscan);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* Tear down a parent scan. */
|
|
void
|
|
xrep_findparent_scan_teardown(
|
|
struct xrep_parent_scan_info *pscan)
|
|
{
|
|
xfs_dir_hook_del(pscan->sc->mp, &pscan->dhook);
|
|
xchk_iscan_teardown(&pscan->iscan);
|
|
mutex_destroy(&pscan->lock);
|
|
}
|
|
|
|
/* Finish a parent scan early. */
|
|
void
|
|
xrep_findparent_scan_finish_early(
|
|
struct xrep_parent_scan_info *pscan,
|
|
xfs_ino_t ino)
|
|
{
|
|
xrep_findparent_scan_found(pscan, ino);
|
|
xchk_iscan_finish_early(&pscan->iscan);
|
|
}
|
|
|
|
/*
|
|
* Confirm that the directory @parent_ino actually contains a directory entry
|
|
* pointing to the child @sc->ip->ino. This function returns one of several
|
|
* ways:
|
|
*
|
|
* Returns 0 with @parent_ino unchanged if the parent was confirmed.
|
|
* Returns 0 with @parent_ino set to NULLFSINO if the parent was not valid.
|
|
* Returns the usual negative errno if something else happened.
|
|
*/
|
|
int
|
|
xrep_findparent_confirm(
|
|
struct xfs_scrub *sc,
|
|
xfs_ino_t *parent_ino)
|
|
{
|
|
struct xrep_findparent_info fpi = {
|
|
.sc = sc,
|
|
.found_parent = NULLFSINO,
|
|
};
|
|
int error;
|
|
|
|
/*
|
|
* The root directory always points to itself. Unlinked dirs can point
|
|
* anywhere, so we point them at the root dir too.
|
|
*/
|
|
if (sc->ip == sc->mp->m_rootip || VFS_I(sc->ip)->i_nlink == 0) {
|
|
*parent_ino = sc->mp->m_sb.sb_rootino;
|
|
return 0;
|
|
}
|
|
|
|
/* Reject garbage parent inode numbers and self-referential parents. */
|
|
if (*parent_ino == NULLFSINO)
|
|
return 0;
|
|
if (!xfs_verify_dir_ino(sc->mp, *parent_ino) ||
|
|
*parent_ino == sc->ip->i_ino) {
|
|
*parent_ino = NULLFSINO;
|
|
return 0;
|
|
}
|
|
|
|
error = xchk_iget(sc, *parent_ino, &fpi.dp);
|
|
if (error)
|
|
return error;
|
|
|
|
if (!S_ISDIR(VFS_I(fpi.dp)->i_mode)) {
|
|
*parent_ino = NULLFSINO;
|
|
goto out_rele;
|
|
}
|
|
|
|
error = xrep_findparent_walk_directory(&fpi);
|
|
if (error)
|
|
goto out_rele;
|
|
|
|
*parent_ino = fpi.found_parent;
|
|
out_rele:
|
|
xchk_irele(sc, fpi.dp);
|
|
return error;
|
|
}
|
|
|
|
/*
|
|
* If we're the root of a directory tree, we are our own parent. If we're an
|
|
* unlinked directory, the parent /won't/ have a link to us. Set the parent
|
|
* directory to the root for both cases. Returns NULLFSINO if we don't know
|
|
* what to do.
|
|
*/
|
|
xfs_ino_t
|
|
xrep_findparent_self_reference(
|
|
struct xfs_scrub *sc)
|
|
{
|
|
if (sc->ip->i_ino == sc->mp->m_sb.sb_rootino)
|
|
return sc->mp->m_sb.sb_rootino;
|
|
|
|
if (VFS_I(sc->ip)->i_nlink == 0)
|
|
return sc->mp->m_sb.sb_rootino;
|
|
|
|
return NULLFSINO;
|
|
}
|
|
|
|
/* Check the dentry cache to see if knows of a parent for the scrub target. */
|
|
xfs_ino_t
|
|
xrep_findparent_from_dcache(
|
|
struct xfs_scrub *sc)
|
|
{
|
|
struct inode *pip = NULL;
|
|
struct dentry *dentry, *parent;
|
|
xfs_ino_t ret = NULLFSINO;
|
|
|
|
dentry = d_find_alias(VFS_I(sc->ip));
|
|
if (!dentry)
|
|
goto out;
|
|
|
|
parent = dget_parent(dentry);
|
|
if (!parent)
|
|
goto out_dput;
|
|
|
|
ASSERT(parent->d_sb == sc->ip->i_mount->m_super);
|
|
|
|
pip = igrab(d_inode(parent));
|
|
dput(parent);
|
|
|
|
if (S_ISDIR(pip->i_mode)) {
|
|
trace_xrep_findparent_from_dcache(sc->ip, XFS_I(pip)->i_ino);
|
|
ret = XFS_I(pip)->i_ino;
|
|
}
|
|
|
|
xchk_irele(sc, XFS_I(pip));
|
|
|
|
out_dput:
|
|
dput(dentry);
|
|
out:
|
|
return ret;
|
|
}
|