xfs: scrubbing for parent pointers [v13.4 4/9]

Teach online fsck to use parent pointers to assist in checking
 directories, parent pointers, extended attributes, and link counts.
 
 This has been running on the djcloud for months with no problems.  Enjoy!
 
 Signed-off-by: Darrick J. Wong <djwong@kernel.org>
 -----BEGIN PGP SIGNATURE-----
 
 iHUEABYKAB0WIQQ2qTKExjcn+O1o2YRKO3ySh0YRpgUCZih1NgAKCRBKO3ySh0YR
 pkk7AQDd/xKCVQDq14c1A9MjzhpTUn9dWeHoU6KUV1xWvtDaygD6A1KBzirREi9O
 ieKg3OGPcg41sl4bnz7lsvRGxgp/mgY=
 =XU5l
 -----END PGP SIGNATURE-----

Merge tag 'scrub-pptrs-6.10_2024-04-23' of https://git.kernel.org/pub/scm/linux/kernel/git/djwong/xfs-linux into xfs-6.10-mergeC

xfs: scrubbing for parent pointers

Teach online fsck to use parent pointers to assist in checking
directories, parent pointers, extended attributes, and link counts.

Signed-off-by: Darrick J. Wong <djwong@kernel.org>
Signed-off-by: Chandan Babu R <chandanbabu@kernel.org>

* tag 'scrub-pptrs-6.10_2024-04-23' of https://git.kernel.org/pub/scm/linux/kernel/git/djwong/xfs-linux:
  xfs: check parent pointer xattrs when scrubbing
  xfs: walk directory parent pointers to determine backref count
  xfs: deferred scrub of parent pointers
  xfs: scrub parent pointers
  xfs: deferred scrub of dirents
  xfs: check dirents have parent pointers
  xfs: revert commit 44af6c7e59
This commit is contained in:
Chandan Babu R 2024-04-24 12:06:51 +05:30
commit 0d2dd382a7
13 changed files with 1307 additions and 11 deletions

View File

@ -177,6 +177,7 @@ xfs-y += $(addprefix scrub/, \
scrub.o \
symlink.o \
xfarray.o \
xfblob.o \
xfile.o \
)
@ -218,7 +219,6 @@ xfs-y += $(addprefix scrub/, \
rmap_repair.o \
symlink_repair.o \
tempfile.o \
xfblob.o \
)
xfs-$(CONFIG_XFS_RT) += $(addprefix scrub/, \

View File

@ -291,3 +291,25 @@ xfs_parent_from_attr(
*parent_gen = be32_to_cpu(rec->p_gen);
return 0;
}
/*
* Look up a parent pointer record (@parent_name -> @pptr) of @ip.
*
* Caller must hold at least ILOCK_SHARED. The scratchpad need not be
* initialized.
*
* Returns 0 if the pointer is found, -ENOATTR if there is no match, or a
* negative errno.
*/
int
xfs_parent_lookup(
struct xfs_trans *tp,
struct xfs_inode *ip,
const struct xfs_name *parent_name,
struct xfs_parent_rec *pptr,
struct xfs_da_args *scratch)
{
memset(scratch, 0, sizeof(struct xfs_da_args));
xfs_parent_da_args_init(scratch, tp, pptr, ip, ip->i_ino, parent_name);
return xfs_attr_get_ilocked(scratch);
}

View File

@ -96,4 +96,9 @@ int xfs_parent_from_attr(struct xfs_mount *mp, unsigned int attr_flags,
const void *value, unsigned int valuelen,
xfs_ino_t *parent_ino, uint32_t *parent_gen);
/* Repair functions */
int xfs_parent_lookup(struct xfs_trans *tp, struct xfs_inode *ip,
const struct xfs_name *name, struct xfs_parent_rec *pptr,
struct xfs_da_args *scratch);
#endif /* __XFS_PARENT_H__ */

View File

@ -17,6 +17,7 @@
#include "xfs_attr.h"
#include "xfs_attr_leaf.h"
#include "xfs_attr_sf.h"
#include "xfs_parent.h"
#include "scrub/scrub.h"
#include "scrub/common.h"
#include "scrub/dabtree.h"
@ -208,13 +209,12 @@ xchk_xattr_actor(
return -ECANCELED;
}
/*
* Local and shortform xattr values are stored in the attr leaf block,
* so we don't need to retrieve the value from a remote block to detect
* corruption problems.
*/
if (value)
return 0;
/* Check parent pointer record. */
if ((attr_flags & XFS_ATTR_PARENT) &&
!xfs_parent_valuecheck(sc->mp, value, valuelen)) {
xchk_fblock_set_corrupt(sc, XFS_ATTR_FORK, args.blkno);
return -ECANCELED;
}
/*
* Try to allocate enough memory to extract the attr value. If that
@ -227,8 +227,21 @@ xchk_xattr_actor(
if (error)
return error;
/*
* Parent pointers are matched on attr name and value, so we must
* supply the xfs_parent_rec here when confirming that the dabtree
* indexing works correctly.
*/
if (attr_flags & XFS_ATTR_PARENT)
memcpy(ab->value, value, valuelen);
args.value = ab->value;
/*
* Get the attr value to ensure that lookup can find this attribute
* through the dabtree indexing and that remote value retrieval also
* works correctly.
*/
xfs_attr_sethash(&args);
error = xfs_attr_get_ilocked(&args);
/* ENODATA means the hash lookup failed and the attr is bad */

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_pptr_looks_zapped(struct xfs_inode *ip);
#ifdef CONFIG_XFS_ONLINE_REPAIR
/* Decide if a repair is required. */

View File

@ -16,12 +16,18 @@
#include "xfs_dir2.h"
#include "xfs_dir2_priv.h"
#include "xfs_health.h"
#include "xfs_attr.h"
#include "xfs_parent.h"
#include "scrub/scrub.h"
#include "scrub/common.h"
#include "scrub/dabtree.h"
#include "scrub/readdir.h"
#include "scrub/health.h"
#include "scrub/repair.h"
#include "scrub/trace.h"
#include "scrub/xfile.h"
#include "scrub/xfarray.h"
#include "scrub/xfblob.h"
/* Set us up to scrub directories. */
int
@ -41,6 +47,39 @@ xchk_setup_directory(
/* Directories */
/* Deferred directory entry that we saved for later. */
struct xchk_dirent {
/* Cookie for retrieval of the dirent name. */
xfblob_cookie name_cookie;
/* Child inode number. */
xfs_ino_t ino;
/* Length of the pptr name. */
uint8_t namelen;
};
struct xchk_dir {
struct xfs_scrub *sc;
/* information for parent pointer validation. */
struct xfs_parent_rec pptr_rec;
struct xfs_da_args pptr_args;
/* Fixed-size array of xchk_dirent structures. */
struct xfarray *dir_entries;
/* Blobs containing dirent names. */
struct xfblob *dir_names;
/* If we've cycled the ILOCK, we must revalidate deferred dirents. */
bool need_revalidate;
/* Name buffer for dirent revalidation. */
struct xfs_name xname;
uint8_t namebuf[MAXNAMELEN];
};
/* Scrub a directory entry. */
/* Check that an inode's mode matches a given XFS_DIR3_FT_* type. */
@ -63,6 +102,108 @@ xchk_dir_check_ftype(
xchk_fblock_set_corrupt(sc, XFS_DATA_FORK, offset);
}
/*
* Try to lock a child file for checking parent pointers. Returns the inode
* flags for the locks we now hold, or zero if we failed.
*/
STATIC unsigned int
xchk_dir_lock_child(
struct xfs_scrub *sc,
struct xfs_inode *ip)
{
if (!xfs_ilock_nowait(ip, XFS_IOLOCK_SHARED))
return 0;
if (!xfs_ilock_nowait(ip, XFS_ILOCK_SHARED)) {
xfs_iunlock(ip, XFS_IOLOCK_SHARED);
return 0;
}
if (!xfs_inode_has_attr_fork(ip) || !xfs_need_iread_extents(&ip->i_af))
return XFS_IOLOCK_SHARED | XFS_ILOCK_SHARED;
xfs_iunlock(ip, XFS_ILOCK_SHARED);
if (!xfs_ilock_nowait(ip, XFS_ILOCK_EXCL)) {
xfs_iunlock(ip, XFS_IOLOCK_SHARED);
return 0;
}
return XFS_IOLOCK_SHARED | XFS_ILOCK_EXCL;
}
/* Check the backwards link (parent pointer) associated with this dirent. */
STATIC int
xchk_dir_parent_pointer(
struct xchk_dir *sd,
const struct xfs_name *name,
struct xfs_inode *ip)
{
struct xfs_scrub *sc = sd->sc;
int error;
xfs_inode_to_parent_rec(&sd->pptr_rec, sc->ip);
error = xfs_parent_lookup(sc->tp, ip, name, &sd->pptr_rec,
&sd->pptr_args);
if (error == -ENOATTR)
xchk_fblock_xref_set_corrupt(sc, XFS_DATA_FORK, 0);
return 0;
}
/* Look for a parent pointer matching this dirent, if the child isn't busy. */
STATIC int
xchk_dir_check_pptr_fast(
struct xchk_dir *sd,
xfs_dir2_dataptr_t dapos,
const struct xfs_name *name,
struct xfs_inode *ip)
{
struct xfs_scrub *sc = sd->sc;
unsigned int lockmode;
int error;
/* dot and dotdot entries do not have parent pointers */
if (xfs_dir2_samename(name, &xfs_name_dot) ||
xfs_dir2_samename(name, &xfs_name_dotdot))
return 0;
/* No self-referential non-dot or dotdot dirents. */
if (ip == sc->ip) {
xchk_fblock_set_corrupt(sc, XFS_DATA_FORK, 0);
return -ECANCELED;
}
/* Try to lock the inode. */
lockmode = xchk_dir_lock_child(sc, ip);
if (!lockmode) {
struct xchk_dirent save_de = {
.namelen = name->len,
.ino = ip->i_ino,
};
/* Couldn't lock the inode, so save the dirent for later. */
trace_xchk_dir_defer(sc->ip, name, ip->i_ino);
error = xfblob_storename(sd->dir_names, &save_de.name_cookie,
name);
if (!xchk_fblock_xref_process_error(sc, XFS_DATA_FORK, 0,
&error))
return error;
error = xfarray_append(sd->dir_entries, &save_de);
if (!xchk_fblock_xref_process_error(sc, XFS_DATA_FORK, 0,
&error))
return error;
return 0;
}
error = xchk_dir_parent_pointer(sd, name, ip);
xfs_iunlock(ip, lockmode);
return error;
}
/*
* Scrub a single directory entry.
*
@ -80,6 +221,7 @@ xchk_dir_actor(
{
struct xfs_mount *mp = dp->i_mount;
struct xfs_inode *ip;
struct xchk_dir *sd = priv;
xfs_ino_t lookup_ino;
xfs_dablk_t offset;
int error = 0;
@ -146,6 +288,14 @@ xchk_dir_actor(
goto out;
xchk_dir_check_ftype(sc, offset, ip, name->type);
if (xfs_has_parent(mp)) {
error = xchk_dir_check_pptr_fast(sd, dapos, name, ip);
if (error)
goto out_rele;
}
out_rele:
xchk_irele(sc, ip);
out:
if (sc->sm->sm_flags & XFS_SCRUB_OFLAG_CORRUPT)
@ -762,11 +912,148 @@ xchk_directory_blocks(
return error;
}
/*
* Revalidate a dirent that we collected in the past but couldn't check because
* of lock contention. Returns 0 if the dirent is still valid, -ENOENT if it
* has gone away on us, or a negative errno.
*/
STATIC int
xchk_dir_revalidate_dirent(
struct xchk_dir *sd,
const struct xfs_name *xname,
xfs_ino_t ino)
{
struct xfs_scrub *sc = sd->sc;
xfs_ino_t child_ino;
int error;
/*
* Look up the directory entry. If we get -ENOENT, the directory entry
* went away and there's nothing to revalidate. Return any other
* error.
*/
error = xchk_dir_lookup(sc, sc->ip, xname, &child_ino);
if (error)
return error;
/* The inode number changed, nothing to revalidate. */
if (ino != child_ino)
return -ENOENT;
return 0;
}
/*
* Check a directory entry's parent pointers the slow way, which means we cycle
* locks a bunch and put up with revalidation until we get it done.
*/
STATIC int
xchk_dir_slow_dirent(
struct xchk_dir *sd,
struct xchk_dirent *dirent,
const struct xfs_name *xname)
{
struct xfs_scrub *sc = sd->sc;
struct xfs_inode *ip;
unsigned int lockmode;
int error;
/* Check that the deferred dirent still exists. */
if (sd->need_revalidate) {
error = xchk_dir_revalidate_dirent(sd, xname, dirent->ino);
if (error == -ENOENT)
return 0;
if (!xchk_fblock_xref_process_error(sc, XFS_DATA_FORK, 0,
&error))
return error;
}
error = xchk_iget(sc, dirent->ino, &ip);
if (error == -EINVAL || error == -ENOENT) {
xchk_fblock_set_corrupt(sc, XFS_DATA_FORK, 0);
return 0;
}
if (!xchk_fblock_xref_process_error(sc, XFS_DATA_FORK, 0, &error))
return error;
/*
* If we can grab both IOLOCK and ILOCK of the alleged child, we can
* proceed with the validation.
*/
lockmode = xchk_dir_lock_child(sc, ip);
if (lockmode) {
trace_xchk_dir_slowpath(sc->ip, xname, ip->i_ino);
goto check_pptr;
}
/*
* We couldn't lock the child file. Drop all the locks and try to
* get them again, one at a time.
*/
xchk_iunlock(sc, sc->ilock_flags);
sd->need_revalidate = true;
trace_xchk_dir_ultraslowpath(sc->ip, xname, ip->i_ino);
error = xchk_dir_trylock_for_pptrs(sc, ip, &lockmode);
if (error)
goto out_rele;
/* Revalidate, since we just cycled the locks. */
error = xchk_dir_revalidate_dirent(sd, xname, dirent->ino);
if (error == -ENOENT) {
error = 0;
goto out_unlock;
}
if (!xchk_fblock_xref_process_error(sc, XFS_DATA_FORK, 0, &error))
goto out_unlock;
check_pptr:
error = xchk_dir_parent_pointer(sd, xname, ip);
out_unlock:
xfs_iunlock(ip, lockmode);
out_rele:
xchk_irele(sc, ip);
return error;
}
/* Check all the dirents that we deferred the first time around. */
STATIC int
xchk_dir_finish_slow_dirents(
struct xchk_dir *sd)
{
xfarray_idx_t array_cur;
int error;
foreach_xfarray_idx(sd->dir_entries, array_cur) {
struct xchk_dirent dirent;
if (sd->sc->sm->sm_flags & XFS_SCRUB_OFLAG_CORRUPT)
return 0;
error = xfarray_load(sd->dir_entries, array_cur, &dirent);
if (error)
return error;
error = xfblob_loadname(sd->dir_names, dirent.name_cookie,
&sd->xname, dirent.namelen);
if (error)
return error;
error = xchk_dir_slow_dirent(sd, &dirent, &sd->xname);
if (error)
return error;
}
return 0;
}
/* Scrub a whole directory. */
int
xchk_directory(
struct xfs_scrub *sc)
{
struct xchk_dir *sd;
int error;
if (!S_ISDIR(VFS_I(sc->ip)->i_mode))
@ -799,9 +1086,60 @@ xchk_directory(
if (sc->sm->sm_flags & XFS_SCRUB_OFLAG_CORRUPT)
return 0;
sd = kvzalloc(sizeof(struct xchk_dir), XCHK_GFP_FLAGS);
if (!sd)
return -ENOMEM;
sd->sc = sc;
sd->xname.name = sd->namebuf;
if (xfs_has_parent(sc->mp)) {
char *descr;
/*
* Set up some staging memory for dirents that we can't check
* due to locking contention.
*/
descr = xchk_xfile_ino_descr(sc, "slow directory entries");
error = xfarray_create(descr, 0, sizeof(struct xchk_dirent),
&sd->dir_entries);
kfree(descr);
if (error)
goto out_sd;
descr = xchk_xfile_ino_descr(sc, "slow directory entry names");
error = xfblob_create(descr, &sd->dir_names);
kfree(descr);
if (error)
goto out_entries;
}
/* Look up every name in this directory by hash. */
error = xchk_dir_walk(sc, sc->ip, xchk_dir_actor, NULL);
if (error && error != -ECANCELED)
error = xchk_dir_walk(sc, sc->ip, xchk_dir_actor, sd);
if (error == -ECANCELED)
error = 0;
if (error)
goto out_names;
if (xfs_has_parent(sc->mp)) {
error = xchk_dir_finish_slow_dirents(sd);
if (error == -ETIMEDOUT) {
/* Couldn't grab a lock, scrub was marked incomplete */
error = 0;
goto out_names;
}
if (error)
goto out_names;
}
out_names:
if (sd->dir_names)
xfblob_destroy(sd->dir_names);
out_entries:
if (sd->dir_entries)
xfarray_destroy(sd->dir_entries);
out_sd:
kvfree(sd);
if (error)
return error;
/* If the dir is clean, it is clearly not zapped. */

View File

@ -18,6 +18,7 @@
#include "xfs_dir2.h"
#include "xfs_dir2_priv.h"
#include "xfs_ag.h"
#include "xfs_parent.h"
#include "scrub/scrub.h"
#include "scrub/common.h"
#include "scrub/repair.h"
@ -29,6 +30,7 @@
#include "scrub/trace.h"
#include "scrub/readdir.h"
#include "scrub/tempfile.h"
#include "scrub/listxattr.h"
/*
* Live Inode Link Count Checking
@ -272,12 +274,17 @@ xchk_nlinks_collect_dirent(
* number of parents of the root directory.
*
* 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 (dp == sc->mp->m_rootip)
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);
else
error = 0;
if (error)
goto out_unlock;
}
@ -314,6 +321,61 @@ xchk_nlinks_collect_dirent(
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. */
STATIC int
xchk_nlinks_collect_dir(
@ -360,6 +422,27 @@ xchk_nlinks_collect_dir(
if (error)
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);
goto out_unlock;

View File

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

View File

@ -15,11 +15,18 @@
#include "xfs_icache.h"
#include "xfs_dir2.h"
#include "xfs_dir2_priv.h"
#include "xfs_attr.h"
#include "xfs_parent.h"
#include "scrub/scrub.h"
#include "scrub/common.h"
#include "scrub/readdir.h"
#include "scrub/tempfile.h"
#include "scrub/repair.h"
#include "scrub/listxattr.h"
#include "scrub/xfile.h"
#include "scrub/xfarray.h"
#include "scrub/xfblob.h"
#include "scrub/trace.h"
/* Set us up to scrub parents. */
int
@ -197,6 +204,620 @@ xchk_parent_validate(
return error;
}
/*
* Checking of Parent Pointers
* ===========================
*
* On filesystems with directory parent pointers, we check the referential
* integrity by visiting each parent pointer of a child file and checking that
* the directory referenced by the pointer actually has a dirent pointing
* forward to the child file.
*/
/* Deferred parent pointer entry that we saved for later. */
struct xchk_pptr {
/* Cookie for retrieval of the pptr name. */
xfblob_cookie name_cookie;
/* Parent pointer record. */
struct xfs_parent_rec pptr_rec;
/* Length of the pptr name. */
uint8_t namelen;
};
struct xchk_pptrs {
struct xfs_scrub *sc;
/* How many parent pointers did we find at the end? */
unsigned long long pptrs_found;
/* Parent of this directory. */
xfs_ino_t parent_ino;
/* Fixed-size array of xchk_pptr structures. */
struct xfarray *pptr_entries;
/* Blobs containing parent pointer names. */
struct xfblob *pptr_names;
/* Scratch buffer for scanning pptr xattrs */
struct xfs_da_args pptr_args;
/* If we've cycled the ILOCK, we must revalidate all deferred pptrs. */
bool need_revalidate;
/* Name buffer */
struct xfs_name xname;
char namebuf[MAXNAMELEN];
};
/* Does this parent pointer match the dotdot entry? */
STATIC int
xchk_parent_scan_dotdot(
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 xchk_pptrs *pp = priv;
xfs_ino_t parent_ino;
int error;
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;
if (pp->parent_ino == parent_ino)
return -ECANCELED;
return 0;
}
/* Look up the dotdot entry so that we can check it as we walk the pptrs. */
STATIC int
xchk_parent_pptr_and_dotdot(
struct xchk_pptrs *pp)
{
struct xfs_scrub *sc = pp->sc;
int error;
/* Look up '..' */
error = xchk_dir_lookup(sc, sc->ip, &xfs_name_dotdot, &pp->parent_ino);
if (!xchk_fblock_process_error(sc, XFS_DATA_FORK, 0, &error))
return error;
if (!xfs_verify_dir_ino(sc->mp, pp->parent_ino)) {
xchk_fblock_set_corrupt(sc, XFS_DATA_FORK, 0);
return 0;
}
/* Is this the root dir? Then '..' must point to itself. */
if (sc->ip == sc->mp->m_rootip) {
if (sc->ip->i_ino != pp->parent_ino)
xchk_fblock_set_corrupt(sc, XFS_DATA_FORK, 0);
return 0;
}
/*
* If this is now an unlinked directory, the dotdot value is
* meaningless as long as it points to a valid inode.
*/
if (VFS_I(sc->ip)->i_nlink == 0)
return 0;
if (pp->sc->sm->sm_flags & XFS_SCRUB_OFLAG_CORRUPT)
return 0;
/* Otherwise, walk the pptrs again, and check. */
error = xchk_xattr_walk(sc, sc->ip, xchk_parent_scan_dotdot, pp);
if (error == -ECANCELED) {
/* Found a parent pointer that matches dotdot. */
return 0;
}
if (!error || error == -EFSCORRUPTED) {
/* Found a broken parent pointer or no match. */
xchk_fblock_set_corrupt(sc, XFS_ATTR_FORK, 0);
return 0;
}
return error;
}
/*
* Try to lock a parent directory for checking dirents. Returns the inode
* flags for the locks we now hold, or zero if we failed.
*/
STATIC unsigned int
xchk_parent_lock_dir(
struct xfs_scrub *sc,
struct xfs_inode *dp)
{
if (!xfs_ilock_nowait(dp, XFS_IOLOCK_SHARED))
return 0;
if (!xfs_ilock_nowait(dp, XFS_ILOCK_SHARED)) {
xfs_iunlock(dp, XFS_IOLOCK_SHARED);
return 0;
}
if (!xfs_need_iread_extents(&dp->i_df))
return XFS_IOLOCK_SHARED | XFS_ILOCK_SHARED;
xfs_iunlock(dp, XFS_ILOCK_SHARED);
if (!xfs_ilock_nowait(dp, XFS_ILOCK_EXCL)) {
xfs_iunlock(dp, XFS_IOLOCK_SHARED);
return 0;
}
return XFS_IOLOCK_SHARED | XFS_ILOCK_EXCL;
}
/* Check the forward link (dirent) associated with this parent pointer. */
STATIC int
xchk_parent_dirent(
struct xchk_pptrs *pp,
const struct xfs_name *xname,
struct xfs_inode *dp)
{
struct xfs_scrub *sc = pp->sc;
xfs_ino_t child_ino;
int error;
/*
* Use the name attached to this parent pointer to look up the
* directory entry in the alleged parent.
*/
error = xchk_dir_lookup(sc, dp, xname, &child_ino);
if (error == -ENOENT) {
xchk_fblock_xref_set_corrupt(sc, XFS_ATTR_FORK, 0);
return 0;
}
if (!xchk_fblock_xref_process_error(sc, XFS_ATTR_FORK, 0, &error))
return error;
/* Does the inode number match? */
if (child_ino != sc->ip->i_ino) {
xchk_fblock_xref_set_corrupt(sc, XFS_ATTR_FORK, 0);
return 0;
}
return 0;
}
/* Try to grab a parent directory. */
STATIC int
xchk_parent_iget(
struct xchk_pptrs *pp,
const struct xfs_parent_rec *pptr,
struct xfs_inode **dpp)
{
struct xfs_scrub *sc = pp->sc;
struct xfs_inode *ip;
xfs_ino_t parent_ino = be64_to_cpu(pptr->p_ino);
int error;
/* Validate inode number. */
error = xfs_dir_ino_validate(sc->mp, parent_ino);
if (error) {
xchk_fblock_set_corrupt(sc, XFS_ATTR_FORK, 0);
return -ECANCELED;
}
error = xchk_iget(sc, parent_ino, &ip);
if (error == -EINVAL || error == -ENOENT) {
xchk_fblock_set_corrupt(sc, XFS_ATTR_FORK, 0);
return -ECANCELED;
}
if (!xchk_fblock_xref_process_error(sc, XFS_ATTR_FORK, 0, &error))
return error;
/* The parent must be a directory. */
if (!S_ISDIR(VFS_I(ip)->i_mode)) {
xchk_fblock_xref_set_corrupt(sc, XFS_ATTR_FORK, 0);
goto out_rele;
}
/* Validate generation number. */
if (VFS_I(ip)->i_generation != be32_to_cpu(pptr->p_gen)) {
xchk_fblock_xref_set_corrupt(sc, XFS_ATTR_FORK, 0);
goto out_rele;
}
*dpp = ip;
return 0;
out_rele:
xchk_irele(sc, ip);
return 0;
}
/*
* Walk an xattr of a file. If this xattr is a parent pointer, follow it up
* to a parent directory and check that the parent has a dirent pointing back
* to us.
*/
STATIC int
xchk_parent_scan_attr(
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_pptrs *pp = priv;
struct xfs_inode *dp = NULL;
const struct xfs_parent_rec *pptr_rec = value;
xfs_ino_t parent_ino;
unsigned int lockmode;
int error;
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) {
xchk_fblock_set_corrupt(sc, XFS_ATTR_FORK, 0);
return error;
}
/* No self-referential parent pointers. */
if (parent_ino == sc->ip->i_ino) {
xchk_fblock_set_corrupt(sc, XFS_ATTR_FORK, 0);
return -ECANCELED;
}
pp->pptrs_found++;
error = xchk_parent_iget(pp, pptr_rec, &dp);
if (error)
return error;
if (!dp)
return 0;
/* Try to lock the inode. */
lockmode = xchk_parent_lock_dir(sc, dp);
if (!lockmode) {
struct xchk_pptr save_pp = {
.pptr_rec = *pptr_rec, /* struct copy */
.namelen = namelen,
};
/* Couldn't lock the inode, so save the pptr for later. */
trace_xchk_parent_defer(sc->ip, &xname, dp->i_ino);
error = xfblob_storename(pp->pptr_names, &save_pp.name_cookie,
&xname);
if (!xchk_fblock_xref_process_error(sc, XFS_ATTR_FORK, 0,
&error))
goto out_rele;
error = xfarray_append(pp->pptr_entries, &save_pp);
if (!xchk_fblock_xref_process_error(sc, XFS_ATTR_FORK, 0,
&error))
goto out_rele;
goto out_rele;
}
error = xchk_parent_dirent(pp, &xname, dp);
if (error)
goto out_unlock;
out_unlock:
xfs_iunlock(dp, lockmode);
out_rele:
xchk_irele(sc, dp);
return error;
}
/*
* Revalidate a parent pointer that we collected in the past but couldn't check
* because of lock contention. Returns 0 if the parent pointer is still valid,
* -ENOENT if it has gone away on us, or a negative errno.
*/
STATIC int
xchk_parent_revalidate_pptr(
struct xchk_pptrs *pp,
const struct xfs_name *xname,
struct xfs_parent_rec *pptr)
{
struct xfs_scrub *sc = pp->sc;
int error;
error = xfs_parent_lookup(sc->tp, sc->ip, xname, pptr, &pp->pptr_args);
if (error == -ENOATTR) {
/* Parent pointer went away, nothing to revalidate. */
return -ENOENT;
}
return error;
}
/*
* Check a parent pointer the slow way, which means we cycle locks a bunch
* and put up with revalidation until we get it done.
*/
STATIC int
xchk_parent_slow_pptr(
struct xchk_pptrs *pp,
const struct xfs_name *xname,
struct xfs_parent_rec *pptr)
{
struct xfs_scrub *sc = pp->sc;
struct xfs_inode *dp = NULL;
unsigned int lockmode;
int error;
/* Check that the deferred parent pointer still exists. */
if (pp->need_revalidate) {
error = xchk_parent_revalidate_pptr(pp, xname, pptr);
if (error == -ENOENT)
return 0;
if (!xchk_fblock_xref_process_error(sc, XFS_ATTR_FORK, 0,
&error))
return error;
}
error = xchk_parent_iget(pp, pptr, &dp);
if (error)
return error;
if (!dp)
return 0;
/*
* If we can grab both IOLOCK and ILOCK of the alleged parent, we
* can proceed with the validation.
*/
lockmode = xchk_parent_lock_dir(sc, dp);
if (lockmode) {
trace_xchk_parent_slowpath(sc->ip, xname, dp->i_ino);
goto check_dirent;
}
/*
* We couldn't lock the parent dir. Drop all the locks and try to
* get them again, one at a time.
*/
xchk_iunlock(sc, sc->ilock_flags);
pp->need_revalidate = true;
trace_xchk_parent_ultraslowpath(sc->ip, xname, dp->i_ino);
error = xchk_dir_trylock_for_pptrs(sc, dp, &lockmode);
if (error)
goto out_rele;
/* Revalidate the parent pointer now that we cycled locks. */
error = xchk_parent_revalidate_pptr(pp, xname, pptr);
if (error == -ENOENT) {
error = 0;
goto out_unlock;
}
if (!xchk_fblock_xref_process_error(sc, XFS_ATTR_FORK, 0, &error))
goto out_unlock;
check_dirent:
error = xchk_parent_dirent(pp, xname, dp);
out_unlock:
xfs_iunlock(dp, lockmode);
out_rele:
xchk_irele(sc, dp);
return error;
}
/* Check all the parent pointers that we deferred the first time around. */
STATIC int
xchk_parent_finish_slow_pptrs(
struct xchk_pptrs *pp)
{
xfarray_idx_t array_cur;
int error;
foreach_xfarray_idx(pp->pptr_entries, array_cur) {
struct xchk_pptr pptr;
if (pp->sc->sm->sm_flags & XFS_SCRUB_OFLAG_CORRUPT)
return 0;
error = xfarray_load(pp->pptr_entries, array_cur, &pptr);
if (error)
return error;
error = xfblob_loadname(pp->pptr_names, pptr.name_cookie,
&pp->xname, pptr.namelen);
if (error)
return error;
error = xchk_parent_slow_pptr(pp, &pp->xname, &pptr.pptr_rec);
if (error)
return error;
}
/* Empty out both xfiles now that we've checked everything. */
xfarray_truncate(pp->pptr_entries);
xfblob_truncate(pp->pptr_names);
return 0;
}
/* Count the number of parent pointers. */
STATIC int
xchk_parent_count_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 xchk_pptrs *pp = priv;
int error;
if (!(attr_flags & XFS_ATTR_PARENT))
return 0;
error = xfs_parent_from_attr(sc->mp, attr_flags, name, namelen, value,
valuelen, NULL, NULL);
if (error)
return error;
pp->pptrs_found++;
return 0;
}
/*
* Compare the number of parent pointers to the link count. For
* non-directories these should be the same. For unlinked directories the
* count should be zero; for linked directories, it should be nonzero.
*/
STATIC int
xchk_parent_count_pptrs(
struct xchk_pptrs *pp)
{
struct xfs_scrub *sc = pp->sc;
int error;
/*
* If we cycled the ILOCK while cross-checking parent pointers with
* dirents, then we need to recalculate the number of parent pointers.
*/
if (pp->need_revalidate) {
pp->pptrs_found = 0;
error = xchk_xattr_walk(sc, sc->ip, xchk_parent_count_pptr, pp);
if (error == -EFSCORRUPTED) {
/* Found a bad parent pointer */
xchk_fblock_set_corrupt(sc, XFS_ATTR_FORK, 0);
return 0;
}
if (error)
return error;
}
if (S_ISDIR(VFS_I(sc->ip)->i_mode)) {
if (sc->ip == sc->mp->m_rootip)
pp->pptrs_found++;
if (VFS_I(sc->ip)->i_nlink == 0 && pp->pptrs_found > 0)
xchk_ino_set_corrupt(sc, sc->ip->i_ino);
else if (VFS_I(sc->ip)->i_nlink > 0 &&
pp->pptrs_found == 0)
xchk_ino_set_corrupt(sc, sc->ip->i_ino);
} else {
if (VFS_I(sc->ip)->i_nlink != pp->pptrs_found)
xchk_ino_set_corrupt(sc, sc->ip->i_ino);
}
return 0;
}
/* Check parent pointers of a file. */
STATIC int
xchk_parent_pptr(
struct xfs_scrub *sc)
{
struct xchk_pptrs *pp;
char *descr;
int error;
pp = kvzalloc(sizeof(struct xchk_pptrs), XCHK_GFP_FLAGS);
if (!pp)
return -ENOMEM;
pp->sc = sc;
pp->xname.name = pp->namebuf;
/*
* Set up some staging memory for parent pointers that we can't check
* due to locking contention.
*/
descr = xchk_xfile_ino_descr(sc, "slow parent pointer entries");
error = xfarray_create(descr, 0, sizeof(struct xchk_pptr),
&pp->pptr_entries);
kfree(descr);
if (error)
goto out_pp;
descr = xchk_xfile_ino_descr(sc, "slow parent pointer names");
error = xfblob_create(descr, &pp->pptr_names);
kfree(descr);
if (error)
goto out_entries;
error = xchk_xattr_walk(sc, sc->ip, xchk_parent_scan_attr, pp);
if (error == -ECANCELED) {
error = 0;
goto out_names;
}
if (error)
goto out_names;
error = xchk_parent_finish_slow_pptrs(pp);
if (error == -ETIMEDOUT) {
/* Couldn't grab a lock, scrub was marked incomplete */
error = 0;
goto out_names;
}
if (error)
goto out_names;
if (pp->sc->sm->sm_flags & XFS_SCRUB_OFLAG_CORRUPT)
goto out_names;
/*
* For subdirectories, make sure the dotdot entry references the same
* inode as the parent pointers.
*
* If we're scanning a /consistent/ directory, there should only be
* one parent pointer, and it should point to the same directory as
* the dotdot entry.
*
* However, a corrupt directory tree might feature a subdirectory with
* multiple parents. The directory loop scanner is responsible for
* correcting that kind of problem, so for now we only validate that
* the dotdot entry matches /one/ of the parents.
*/
if (S_ISDIR(VFS_I(sc->ip)->i_mode)) {
error = xchk_parent_pptr_and_dotdot(pp);
if (error)
goto out_names;
}
if (pp->sc->sm->sm_flags & XFS_SCRUB_OFLAG_CORRUPT)
goto out_pp;
/*
* Complain if the number of parent pointers doesn't match the link
* count. This could be a sign of missing parent pointers (or an
* incorrect link count).
*/
error = xchk_parent_count_pptrs(pp);
if (error)
goto out_names;
out_names:
xfblob_destroy(pp->pptr_names);
out_entries:
xfarray_destroy(pp->pptr_entries);
out_pp:
kvfree(pp);
return error;
}
/* Scrub a parent pointer. */
int
xchk_parent(
@ -206,6 +827,9 @@ xchk_parent(
xfs_ino_t parent_ino;
int error = 0;
if (xfs_has_parent(mp))
return xchk_parent_pptr(sc);
/*
* If we're a directory, check that the '..' link points up to
* a directory that has one entry pointing to us.
@ -249,3 +873,64 @@ xchk_parent(
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

@ -18,6 +18,7 @@
#include "xfs_trans.h"
#include "xfs_error.h"
#include "scrub/scrub.h"
#include "scrub/common.h"
#include "scrub/readdir.h"
/* Call a function for every entry in a shortform directory. */
@ -380,3 +381,80 @@ xchk_dir_lookup(
*ino = args.inumber;
return error;
}
/*
* Try to grab the IOLOCK and ILOCK of sc->ip and ip, returning @ip's lock
* state. The caller may have a transaction, so we must use trylock for both
* IOLOCKs.
*/
static inline unsigned int
xchk_dir_trylock_both(
struct xfs_scrub *sc,
struct xfs_inode *ip)
{
if (!xchk_ilock_nowait(sc, XFS_IOLOCK_EXCL))
return 0;
if (!xfs_ilock_nowait(ip, XFS_IOLOCK_SHARED))
goto parent_iolock;
xchk_ilock(sc, XFS_ILOCK_EXCL);
if (!xfs_ilock_nowait(ip, XFS_ILOCK_EXCL))
goto parent_ilock;
return XFS_IOLOCK_SHARED | XFS_ILOCK_EXCL;
parent_ilock:
xchk_iunlock(sc, XFS_ILOCK_EXCL);
xfs_iunlock(ip, XFS_IOLOCK_SHARED);
parent_iolock:
xchk_iunlock(sc, XFS_IOLOCK_EXCL);
return 0;
}
/*
* Try for a limited time to grab the IOLOCK and ILOCK of both the scrub target
* (@sc->ip) and the inode at the other end (@ip) of a directory or parent
* pointer link so that we can check that link.
*
* We do not know ahead of time that the directory tree is /not/ corrupt, so we
* cannot use the "lock two inode" functions because we do not know that there
* is not a racing thread trying to take the locks in opposite order. First
* take IOLOCK_EXCL of the scrub target, and then try to take IOLOCK_SHARED
* of @ip to synchronize with the VFS. Next, take ILOCK_EXCL of the scrub
* target and @ip to synchronize with XFS.
*
* If the trylocks succeed, *lockmode will be set to the locks held for @ip;
* @sc->ilock_flags will be set for the locks held for @sc->ip; and zero will
* be returned. If not, returns -EDEADLOCK to try again; or -ETIMEDOUT if
* XCHK_TRY_HARDER was set. Returns -EINTR if the process has been killed.
*/
int
xchk_dir_trylock_for_pptrs(
struct xfs_scrub *sc,
struct xfs_inode *ip,
unsigned int *lockmode)
{
unsigned int nr;
int error = 0;
ASSERT(sc->ilock_flags == 0);
for (nr = 0; nr < HZ; nr++) {
*lockmode = xchk_dir_trylock_both(sc, ip);
if (*lockmode)
return 0;
if (xchk_should_terminate(sc, &error))
return error;
delay(1);
}
if (sc->flags & XCHK_TRY_HARDER) {
xchk_set_incomplete(sc);
return -ETIMEDOUT;
}
return -EDEADLOCK;
}

View File

@ -16,4 +16,7 @@ int xchk_dir_walk(struct xfs_scrub *sc, struct xfs_inode *dp,
int xchk_dir_lookup(struct xfs_scrub *sc, struct xfs_inode *dp,
const struct xfs_name *name, xfs_ino_t *ino);
int xchk_dir_trylock_for_pptrs(struct xfs_scrub *sc, struct xfs_inode *ip,
unsigned int *lockmode);
#endif /* __XFS_SCRUB_READDIR_H__ */

View File

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

View File

@ -26,6 +26,7 @@ struct xchk_iscan;
struct xchk_nlink;
struct xchk_fscounters;
struct xfs_rmap_update_params;
struct xfs_parent_rec;
/*
* 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))
);
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,
TP_PROTO(struct xfs_mount *mp, xfs_ino_t ino),
TP_ARGS(mp, ino),
@ -1511,6 +1539,43 @@ DEFINE_EVENT(xchk_nlinks_diff_class, name, \
TP_ARGS(mp, ip, live))
DEFINE_SCRUB_NLINKS_DIFF_EVENT(xchk_nlinks_compare_inode);
DECLARE_EVENT_CLASS(xchk_pptr_class,
TP_PROTO(struct xfs_inode *ip, const struct xfs_name *name,
xfs_ino_t far_ino),
TP_ARGS(ip, name, far_ino),
TP_STRUCT__entry(
__field(dev_t, dev)
__field(xfs_ino_t, ino)
__field(unsigned int, namelen)
__dynamic_array(char, name, name->len)
__field(xfs_ino_t, far_ino)
),
TP_fast_assign(
__entry->dev = ip->i_mount->m_super->s_dev;
__entry->ino = ip->i_ino;
__entry->namelen = name->len;
memcpy(__get_str(name), name, name->len);
__entry->far_ino = far_ino;
),
TP_printk("dev %d:%d ino 0x%llx name '%.*s' far_ino 0x%llx",
MAJOR(__entry->dev), MINOR(__entry->dev),
__entry->ino,
__entry->namelen,
__get_str(name),
__entry->far_ino)
)
#define DEFINE_XCHK_PPTR_EVENT(name) \
DEFINE_EVENT(xchk_pptr_class, name, \
TP_PROTO(struct xfs_inode *ip, const struct xfs_name *name, \
xfs_ino_t far_ino), \
TP_ARGS(ip, name, far_ino))
DEFINE_XCHK_PPTR_EVENT(xchk_dir_defer);
DEFINE_XCHK_PPTR_EVENT(xchk_dir_slowpath);
DEFINE_XCHK_PPTR_EVENT(xchk_dir_ultraslowpath);
DEFINE_XCHK_PPTR_EVENT(xchk_parent_defer);
DEFINE_XCHK_PPTR_EVENT(xchk_parent_slowpath);
DEFINE_XCHK_PPTR_EVENT(xchk_parent_ultraslowpath);
/* repair tracepoints */
#if IS_ENABLED(CONFIG_XFS_ONLINE_REPAIR)