mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2026-05-07 18:37:58 -04:00
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:
@@ -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/, \
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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__ */
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -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. */
|
||||
|
||||
@@ -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. */
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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__ */
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user