1

xfs: ensure unlinked list state is consistent with nlink during scrub

Now that we have the means to tell if an inode is on an unlinked inode
list or not, we can check that an inode with zero link count is on the
unlinked list; and an inode that has nonzero link count is not on that
list.  Make repair clean things up too.

Signed-off-by: Darrick J. Wong <djwong@kernel.org>
Reviewed-by: Christoph Hellwig <hch@lst.de>
This commit is contained in:
Darrick J. Wong 2024-04-15 14:54:49 -07:00
parent 6c631e79e7
commit e921533ef1
4 changed files with 67 additions and 4 deletions

View File

@ -739,6 +739,23 @@ xchk_inode_check_reflink_iflag(
xchk_ino_set_corrupt(sc, ino); xchk_ino_set_corrupt(sc, ino);
} }
/*
* If this inode has zero link count, it must be on the unlinked list. If
* it has nonzero link count, it must not be on the unlinked list.
*/
STATIC void
xchk_inode_check_unlinked(
struct xfs_scrub *sc)
{
if (VFS_I(sc->ip)->i_nlink == 0) {
if (!xfs_inode_on_unlinked_list(sc->ip))
xchk_ino_set_corrupt(sc, sc->ip->i_ino);
} else {
if (xfs_inode_on_unlinked_list(sc->ip))
xchk_ino_set_corrupt(sc, sc->ip->i_ino);
}
}
/* Scrub an inode. */ /* Scrub an inode. */
int int
xchk_inode( xchk_inode(
@ -771,6 +788,8 @@ xchk_inode(
if (S_ISREG(VFS_I(sc->ip)->i_mode)) if (S_ISREG(VFS_I(sc->ip)->i_mode))
xchk_inode_check_reflink_iflag(sc, sc->ip->i_ino); xchk_inode_check_reflink_iflag(sc, sc->ip->i_ino);
xchk_inode_check_unlinked(sc);
xchk_inode_xref(sc, sc->ip->i_ino, &di); xchk_inode_xref(sc, sc->ip->i_ino, &di);
out: out:
return error; return error;

View File

@ -1745,6 +1745,46 @@ xrep_inode_problems(
return xrep_roll_trans(sc); return xrep_roll_trans(sc);
} }
/*
* Make sure this inode's unlinked list pointers are consistent with its
* link count.
*/
STATIC int
xrep_inode_unlinked(
struct xfs_scrub *sc)
{
unsigned int nlink = VFS_I(sc->ip)->i_nlink;
int error;
/*
* If this inode is linked from the directory tree and on the unlinked
* list, remove it from the unlinked list.
*/
if (nlink > 0 && xfs_inode_on_unlinked_list(sc->ip)) {
struct xfs_perag *pag;
int error;
pag = xfs_perag_get(sc->mp,
XFS_INO_TO_AGNO(sc->mp, sc->ip->i_ino));
error = xfs_iunlink_remove(sc->tp, pag, sc->ip);
xfs_perag_put(pag);
if (error)
return error;
}
/*
* If this inode is not linked from the directory tree yet not on the
* unlinked list, put it on the unlinked list.
*/
if (nlink == 0 && !xfs_inode_on_unlinked_list(sc->ip)) {
error = xfs_iunlink(sc->tp, sc->ip);
if (error)
return error;
}
return 0;
}
/* Repair an inode's fields. */ /* Repair an inode's fields. */
int int
xrep_inode( xrep_inode(
@ -1794,5 +1834,10 @@ xrep_inode(
return error; return error;
} }
/* Reconnect incore unlinked list */
error = xrep_inode_unlinked(sc);
if (error)
return error;
return xrep_defer_finish(sc); return xrep_defer_finish(sc);
} }

View File

@ -42,9 +42,6 @@
struct kmem_cache *xfs_inode_cache; struct kmem_cache *xfs_inode_cache;
STATIC int xfs_iunlink_remove(struct xfs_trans *tp, struct xfs_perag *pag,
struct xfs_inode *);
/* /*
* helper function to extract extent size hint from inode * helper function to extract extent size hint from inode
*/ */
@ -2252,7 +2249,7 @@ xfs_iunlink_remove_inode(
/* /*
* Pull the on-disk inode from the AGI unlinked list. * Pull the on-disk inode from the AGI unlinked list.
*/ */
STATIC int int
xfs_iunlink_remove( xfs_iunlink_remove(
struct xfs_trans *tp, struct xfs_trans *tp,
struct xfs_perag *pag, struct xfs_perag *pag,

View File

@ -617,6 +617,8 @@ extern struct kmem_cache *xfs_inode_cache;
bool xfs_inode_needs_inactive(struct xfs_inode *ip); bool xfs_inode_needs_inactive(struct xfs_inode *ip);
int xfs_iunlink(struct xfs_trans *tp, struct xfs_inode *ip); int xfs_iunlink(struct xfs_trans *tp, struct xfs_inode *ip);
int xfs_iunlink_remove(struct xfs_trans *tp, struct xfs_perag *pag,
struct xfs_inode *ip);
void xfs_end_io(struct work_struct *work); void xfs_end_io(struct work_struct *work);