xfs: walk directory parent pointers to determine backref count

If the filesystem has parent pointers enabled, walk the parent pointers
of subdirectories to determine the true backref count.  In theory each
subdir should have a single parent reachable via dotdot, but in the case
of (corrupt) subdirs with multiple parents, we need to keep the link
counts high enough that the directory loop detector will be able to
correct the multiple parents problems.

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-04-22 09:48:05 -07:00
parent 8ad345306d
commit 77ede5f44b
6 changed files with 177 additions and 1 deletions

View File

@ -212,6 +212,7 @@ static inline bool xchk_skip_xref(struct xfs_scrub_metadata *sm)
} }
bool xchk_dir_looks_zapped(struct xfs_inode *dp); bool xchk_dir_looks_zapped(struct xfs_inode *dp);
bool xchk_pptr_looks_zapped(struct xfs_inode *ip);
#ifdef CONFIG_XFS_ONLINE_REPAIR #ifdef CONFIG_XFS_ONLINE_REPAIR
/* Decide if a repair is required. */ /* Decide if a repair is required. */

View File

@ -18,6 +18,7 @@
#include "xfs_dir2.h" #include "xfs_dir2.h"
#include "xfs_dir2_priv.h" #include "xfs_dir2_priv.h"
#include "xfs_ag.h" #include "xfs_ag.h"
#include "xfs_parent.h"
#include "scrub/scrub.h" #include "scrub/scrub.h"
#include "scrub/common.h" #include "scrub/common.h"
#include "scrub/repair.h" #include "scrub/repair.h"
@ -29,6 +30,7 @@
#include "scrub/trace.h" #include "scrub/trace.h"
#include "scrub/readdir.h" #include "scrub/readdir.h"
#include "scrub/tempfile.h" #include "scrub/tempfile.h"
#include "scrub/listxattr.h"
/* /*
* Live Inode Link Count Checking * Live Inode Link Count Checking
@ -272,12 +274,17 @@ xchk_nlinks_collect_dirent(
* number of parents of the root directory. * number of parents of the root directory.
* *
* Otherwise, increment the number of backrefs pointing back to ino. * Otherwise, increment the number of backrefs pointing back to ino.
*
* If the filesystem has parent pointers, we walk the pptrs to
* determine the backref count.
*/ */
if (dotdot) { if (dotdot) {
if (dp == sc->mp->m_rootip) if (dp == sc->mp->m_rootip)
error = xchk_nlinks_update_incore(xnc, ino, 1, 0, 0); error = xchk_nlinks_update_incore(xnc, ino, 1, 0, 0);
else else if (!xfs_has_parent(sc->mp))
error = xchk_nlinks_update_incore(xnc, ino, 0, 1, 0); error = xchk_nlinks_update_incore(xnc, ino, 0, 1, 0);
else
error = 0;
if (error) if (error)
goto out_unlock; goto out_unlock;
} }
@ -314,6 +321,61 @@ out_incomplete:
return error; return error;
} }
/* Bump the backref count for the inode referenced by this parent pointer. */
STATIC int
xchk_nlinks_collect_pptr(
struct xfs_scrub *sc,
struct xfs_inode *ip,
unsigned int attr_flags,
const unsigned char *name,
unsigned int namelen,
const void *value,
unsigned int valuelen,
void *priv)
{
struct xfs_name xname = {
.name = name,
.len = namelen,
};
struct xchk_nlink_ctrs *xnc = priv;
const struct xfs_parent_rec *pptr_rec = value;
xfs_ino_t parent_ino;
int error;
/* Update the shadow link counts if we haven't already failed. */
if (xchk_iscan_aborted(&xnc->collect_iscan)) {
error = -ECANCELED;
goto out_incomplete;
}
if (!(attr_flags & XFS_ATTR_PARENT))
return 0;
error = xfs_parent_from_attr(sc->mp, attr_flags, name, namelen, value,
valuelen, &parent_ino, NULL);
if (error)
return error;
trace_xchk_nlinks_collect_pptr(sc->mp, ip, &xname, pptr_rec);
mutex_lock(&xnc->lock);
error = xchk_nlinks_update_incore(xnc, parent_ino, 0, 1, 0);
if (error)
goto out_unlock;
mutex_unlock(&xnc->lock);
return 0;
out_unlock:
mutex_unlock(&xnc->lock);
xchk_iscan_abort(&xnc->collect_iscan);
out_incomplete:
xchk_set_incomplete(sc);
return error;
}
/* Walk a directory to bump the observed link counts of the children. */ /* Walk a directory to bump the observed link counts of the children. */
STATIC int STATIC int
xchk_nlinks_collect_dir( xchk_nlinks_collect_dir(
@ -360,6 +422,27 @@ xchk_nlinks_collect_dir(
if (error) if (error)
goto out_abort; goto out_abort;
/* Walk the parent pointers to get real backref counts. */
if (xfs_has_parent(sc->mp)) {
/*
* If the extended attributes look as though they has been
* zapped by the inode record repair code, we cannot scan for
* parent pointers.
*/
if (xchk_pptr_looks_zapped(dp)) {
error = -EBUSY;
goto out_unlock;
}
error = xchk_xattr_walk(sc, dp, xchk_nlinks_collect_pptr, xnc);
if (error == -ECANCELED) {
error = 0;
goto out_unlock;
}
if (error)
goto out_abort;
}
xchk_iscan_mark_visited(&xnc->collect_iscan, dp); xchk_iscan_mark_visited(&xnc->collect_iscan, dp);
goto out_unlock; goto out_unlock;

View File

@ -18,6 +18,8 @@
#include "xfs_ialloc.h" #include "xfs_ialloc.h"
#include "xfs_sb.h" #include "xfs_sb.h"
#include "xfs_ag.h" #include "xfs_ag.h"
#include "xfs_dir2.h"
#include "xfs_parent.h"
#include "scrub/scrub.h" #include "scrub/scrub.h"
#include "scrub/common.h" #include "scrub/common.h"
#include "scrub/repair.h" #include "scrub/repair.h"

View File

@ -873,3 +873,64 @@ xchk_parent(
return error; return error;
} }
/*
* Decide if this file's extended attributes (and therefore its parent
* pointers) have been zapped to satisfy the inode and ifork verifiers.
* Checking and repairing should be postponed until the extended attribute
* structure is fixed.
*/
bool
xchk_pptr_looks_zapped(
struct xfs_inode *ip)
{
struct xfs_mount *mp = ip->i_mount;
struct inode *inode = VFS_I(ip);
ASSERT(xfs_has_parent(mp));
/*
* Temporary files that cannot be linked into the directory tree do not
* have attr forks because they cannot ever have parents.
*/
if (inode->i_nlink == 0 && !(inode->i_state & I_LINKABLE))
return false;
/*
* Directory tree roots do not have parents, so the expected outcome
* of a parent pointer scan is always the empty set. It's safe to scan
* them even if the attr fork was zapped.
*/
if (ip == mp->m_rootip)
return false;
/*
* Metadata inodes are all rooted in the superblock and do not have
* any parents. Hence the attr fork will not be initialized, but
* there are no parent pointers that might have been zapped.
*/
if (xfs_is_metadata_inode(ip))
return false;
/*
* Linked and linkable non-rootdir files should always have an
* attribute fork because that is where parent pointers are
* stored. If the fork is absent, something is amiss.
*/
if (!xfs_inode_has_attr_fork(ip))
return true;
/* Repair zapped this file's attr fork a short time ago */
if (xfs_ifork_zapped(ip, XFS_ATTR_FORK))
return true;
/*
* If the dinode repair found a bad attr fork, it will reset the fork
* to extents format with zero records and wait for the bmapbta
* scrubber to reconstruct the block mappings. The extended attribute
* structure always contain some content when parent pointers are
* enabled, so this is a clear sign of a zapped attr fork.
*/
return ip->i_af.if_format == XFS_DINODE_FMT_EXTENTS &&
ip->i_af.if_nextents == 0;
}

View File

@ -19,6 +19,7 @@
#include "xfs_da_format.h" #include "xfs_da_format.h"
#include "xfs_dir2.h" #include "xfs_dir2.h"
#include "xfs_rmap.h" #include "xfs_rmap.h"
#include "xfs_parent.h"
#include "scrub/scrub.h" #include "scrub/scrub.h"
#include "scrub/xfile.h" #include "scrub/xfile.h"
#include "scrub/xfarray.h" #include "scrub/xfarray.h"

View File

@ -26,6 +26,7 @@ struct xchk_iscan;
struct xchk_nlink; struct xchk_nlink;
struct xchk_fscounters; struct xchk_fscounters;
struct xfs_rmap_update_params; struct xfs_rmap_update_params;
struct xfs_parent_rec;
/* /*
* ftrace's __print_symbolic requires that all enum values be wrapped in the * ftrace's __print_symbolic requires that all enum values be wrapped in the
@ -1363,6 +1364,33 @@ TRACE_EVENT(xchk_nlinks_collect_dirent,
__get_str(name)) __get_str(name))
); );
TRACE_EVENT(xchk_nlinks_collect_pptr,
TP_PROTO(struct xfs_mount *mp, struct xfs_inode *dp,
const struct xfs_name *name,
const struct xfs_parent_rec *pptr),
TP_ARGS(mp, dp, name, pptr),
TP_STRUCT__entry(
__field(dev_t, dev)
__field(xfs_ino_t, dir)
__field(xfs_ino_t, ino)
__field(unsigned int, namelen)
__dynamic_array(char, name, name->len)
),
TP_fast_assign(
__entry->dev = mp->m_super->s_dev;
__entry->dir = dp->i_ino;
__entry->ino = be64_to_cpu(pptr->p_ino);
__entry->namelen = name->len;
memcpy(__get_str(name), name->name, name->len);
),
TP_printk("dev %d:%d dir 0x%llx -> ino 0x%llx name '%.*s'",
MAJOR(__entry->dev), MINOR(__entry->dev),
__entry->dir,
__entry->ino,
__entry->namelen,
__get_str(name))
);
TRACE_EVENT(xchk_nlinks_collect_metafile, TRACE_EVENT(xchk_nlinks_collect_metafile,
TP_PROTO(struct xfs_mount *mp, xfs_ino_t ino), TP_PROTO(struct xfs_mount *mp, xfs_ino_t ino),
TP_ARGS(mp, ino), TP_ARGS(mp, ino),