xfs: check dirents have parent pointers
If the fs has parent pointers, we need to check that each child dirent points to a file that has a parent pointer pointing back at us. Signed-off-by: Darrick J. Wong <djwong@kernel.org> Reviewed-by: Christoph Hellwig <hch@lst.de>
This commit is contained in:
parent
2a009397eb
commit
61b3f0df5c
@ -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__ */
|
||||
|
@ -16,6 +16,8 @@
|
||||
#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"
|
||||
@ -41,6 +43,14 @@ xchk_setup_directory(
|
||||
|
||||
/* Directories */
|
||||
|
||||
struct xchk_dir {
|
||||
struct xfs_scrub *sc;
|
||||
|
||||
/* information for parent pointer validation. */
|
||||
struct xfs_parent_rec pptr_rec;
|
||||
struct xfs_da_args pptr_args;
|
||||
};
|
||||
|
||||
/* Scrub a directory entry. */
|
||||
|
||||
/* Check that an inode's mode matches a given XFS_DIR3_FT_* type. */
|
||||
@ -63,6 +73,90 @@ 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) {
|
||||
xchk_set_incomplete(sc);
|
||||
return -ECANCELED;
|
||||
}
|
||||
|
||||
error = xchk_dir_parent_pointer(sd, name, ip);
|
||||
xfs_iunlock(ip, lockmode);
|
||||
return error;
|
||||
}
|
||||
|
||||
/*
|
||||
* Scrub a single directory entry.
|
||||
*
|
||||
@ -80,6 +174,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 +241,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)
|
||||
@ -767,6 +870,7 @@ int
|
||||
xchk_directory(
|
||||
struct xfs_scrub *sc)
|
||||
{
|
||||
struct xchk_dir *sd;
|
||||
int error;
|
||||
|
||||
if (!S_ISDIR(VFS_I(sc->ip)->i_mode))
|
||||
@ -799,8 +903,14 @@ 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;
|
||||
|
||||
/* Look up every name in this directory by hash. */
|
||||
error = xchk_dir_walk(sc, sc->ip, xchk_dir_actor, NULL);
|
||||
error = xchk_dir_walk(sc, sc->ip, xchk_dir_actor, sd);
|
||||
kvfree(sd);
|
||||
if (error && error != -ECANCELED)
|
||||
return error;
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user