@@ -15,6 +15,8 @@
#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/dabtree.h"
@@ -39,6 +41,20 @@ xchk_setup_directory(
/* Directories */
+struct xchk_dir {
+ struct xfs_scrub *sc;
+
+ /* Scratch buffer for scanning pptr xattrs */
+ struct xfs_parent_name_irec pptr;
+
+ /* xattr key and da args for parent pointer validation. */
+ struct xfs_parent_scratch pptr_scratch;
+
+ /* Name buffer for pptr validation and dirent revalidation. */
+ uint8_t namebuf[MAXNAMELEN];
+
+};
+
/* Scrub a directory entry. */
/* Check that an inode's mode matches a given XFS_DIR3_FT_* type. */
@@ -61,6 +77,105 @@ 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,
+ xfs_dir2_dataptr_t dapos,
+ const struct xfs_name *name,
+ struct xfs_inode *ip)
+{
+ struct xfs_scrub *sc = sd->sc;
+ int pptr_namelen;
+
+ sd->pptr.p_ino = sc->ip->i_ino;
+ sd->pptr.p_gen = VFS_I(sc->ip)->i_generation;
+ sd->pptr.p_diroffset = dapos;
+
+ pptr_namelen = xfs_parent_lookup(sc->tp, ip, &sd->pptr, sd->namebuf,
+ MAXNAMELEN, &sd->pptr_scratch);
+ if (pptr_namelen == -ENOATTR) {
+ xchk_fblock_xref_set_corrupt(sc, XFS_DATA_FORK, 0);
+ return 0;
+ }
+ if (pptr_namelen < 0) {
+ xchk_fblock_xref_process_error(sc, XFS_DATA_FORK, 0,
+ &pptr_namelen);
+ return pptr_namelen;
+ }
+
+ if (pptr_namelen != name->len) {
+ xchk_fblock_xref_set_corrupt(sc, XFS_DATA_FORK, 0);
+ return 0;
+ }
+
+ if (memcmp(sd->namebuf, name->name, name->len)) {
+ xchk_fblock_xref_set_corrupt(sc, XFS_DATA_FORK, 0);
+ return 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 (!strncmp(".", name->name, name->len) ||
+ !strncmp("..", name->name, name->len))
+ return 0;
+
+ /* 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, dapos, name, ip);
+ xfs_iunlock(ip, lockmode);
+ return error;
+}
+
/*
* Scrub a single directory entry.
*
@@ -78,6 +193,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;
@@ -144,6 +260,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)
@@ -759,6 +883,7 @@ int
xchk_directory(
struct xfs_scrub *sc)
{
+ struct xchk_dir *sd;
int error;
if (!S_ISDIR(VFS_I(sc->ip)->i_mode))
@@ -786,9 +911,16 @@ 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);
if (error == -ECANCELED)
error = 0;
+
+ kvfree(sd);
return error;
}