xref: /openbmc/linux/fs/xfs/scrub/parent.c (revision 294012fb)
1739a2fe0SDarrick J. Wong // SPDX-License-Identifier: GPL-2.0-or-later
20f28b257SDarrick J. Wong /*
3ecc73f8aSDarrick J. Wong  * Copyright (C) 2017-2023 Oracle.  All Rights Reserved.
4739a2fe0SDarrick J. Wong  * Author: Darrick J. Wong <djwong@kernel.org>
50f28b257SDarrick J. Wong  */
60f28b257SDarrick J. Wong #include "xfs.h"
70f28b257SDarrick J. Wong #include "xfs_fs.h"
80f28b257SDarrick J. Wong #include "xfs_shared.h"
90f28b257SDarrick J. Wong #include "xfs_format.h"
100f28b257SDarrick J. Wong #include "xfs_trans_resv.h"
110f28b257SDarrick J. Wong #include "xfs_mount.h"
120f28b257SDarrick J. Wong #include "xfs_log_format.h"
130f28b257SDarrick J. Wong #include "xfs_inode.h"
140f28b257SDarrick J. Wong #include "xfs_icache.h"
150f28b257SDarrick J. Wong #include "xfs_dir2.h"
160f28b257SDarrick J. Wong #include "xfs_dir2_priv.h"
170f28b257SDarrick J. Wong #include "scrub/scrub.h"
180f28b257SDarrick J. Wong #include "scrub/common.h"
194c233b5cSDarrick J. Wong #include "scrub/readdir.h"
200f28b257SDarrick J. Wong 
210f28b257SDarrick J. Wong /* Set us up to scrub parents. */
220f28b257SDarrick J. Wong int
xchk_setup_parent(struct xfs_scrub * sc)23c517b3aaSDarrick J. Wong xchk_setup_parent(
24026f57ebSDarrick J. Wong 	struct xfs_scrub	*sc)
250f28b257SDarrick J. Wong {
26026f57ebSDarrick J. Wong 	return xchk_setup_inode_contents(sc, 0);
270f28b257SDarrick J. Wong }
280f28b257SDarrick J. Wong 
290f28b257SDarrick J. Wong /* Parent pointers */
300f28b257SDarrick J. Wong 
310f28b257SDarrick J. Wong /* Look for an entry in a parent pointing to this inode. */
320f28b257SDarrick J. Wong 
33c517b3aaSDarrick J. Wong struct xchk_parent_ctx {
348feb4732SDarrick J. Wong 	struct xfs_scrub	*sc;
350f28b257SDarrick J. Wong 	xfs_nlink_t		nlink;
360f28b257SDarrick J. Wong };
370f28b257SDarrick J. Wong 
380f28b257SDarrick J. Wong /* Look for a single entry in a directory pointing to an inode. */
394c233b5cSDarrick J. Wong STATIC int
xchk_parent_actor(struct xfs_scrub * sc,struct xfs_inode * dp,xfs_dir2_dataptr_t dapos,const struct xfs_name * name,xfs_ino_t ino,void * priv)40c517b3aaSDarrick J. Wong xchk_parent_actor(
414c233b5cSDarrick J. Wong 	struct xfs_scrub	*sc,
424c233b5cSDarrick J. Wong 	struct xfs_inode	*dp,
434c233b5cSDarrick J. Wong 	xfs_dir2_dataptr_t	dapos,
444c233b5cSDarrick J. Wong 	const struct xfs_name	*name,
454c233b5cSDarrick J. Wong 	xfs_ino_t		ino,
464c233b5cSDarrick J. Wong 	void			*priv)
470f28b257SDarrick J. Wong {
484c233b5cSDarrick J. Wong 	struct xchk_parent_ctx	*spc = priv;
498feb4732SDarrick J. Wong 	int			error = 0;
500f28b257SDarrick J. Wong 
514c233b5cSDarrick J. Wong 	/* Does this name make sense? */
524c233b5cSDarrick J. Wong 	if (!xfs_dir2_namecheck(name->name, name->len))
534c233b5cSDarrick J. Wong 		error = -EFSCORRUPTED;
544c233b5cSDarrick J. Wong 	if (!xchk_fblock_xref_process_error(sc, XFS_DATA_FORK, 0, &error))
554c233b5cSDarrick J. Wong 		return error;
564c233b5cSDarrick J. Wong 
574c233b5cSDarrick J. Wong 	if (sc->ip->i_ino == ino)
580f28b257SDarrick J. Wong 		spc->nlink++;
598feb4732SDarrick J. Wong 
608feb4732SDarrick J. Wong 	if (xchk_should_terminate(spc->sc, &error))
614c233b5cSDarrick J. Wong 		return error;
628feb4732SDarrick J. Wong 
634c233b5cSDarrick J. Wong 	return 0;
640f28b257SDarrick J. Wong }
650f28b257SDarrick J. Wong 
660f28b257SDarrick J. Wong /*
670916056eSDarrick J. Wong  * Try to lock a parent directory for checking dirents.  Returns the inode
680916056eSDarrick J. Wong  * flags for the locks we now hold, or zero if we failed.
690916056eSDarrick J. Wong  */
700916056eSDarrick J. Wong STATIC unsigned int
xchk_parent_ilock_dir(struct xfs_inode * dp)710916056eSDarrick J. Wong xchk_parent_ilock_dir(
720916056eSDarrick J. Wong 	struct xfs_inode	*dp)
730916056eSDarrick J. Wong {
740916056eSDarrick J. Wong 	if (!xfs_ilock_nowait(dp, XFS_ILOCK_SHARED))
750916056eSDarrick J. Wong 		return 0;
760916056eSDarrick J. Wong 
770916056eSDarrick J. Wong 	if (!xfs_need_iread_extents(&dp->i_df))
780916056eSDarrick J. Wong 		return XFS_ILOCK_SHARED;
790916056eSDarrick J. Wong 
800916056eSDarrick J. Wong 	xfs_iunlock(dp, XFS_ILOCK_SHARED);
810916056eSDarrick J. Wong 
820916056eSDarrick J. Wong 	if (!xfs_ilock_nowait(dp, XFS_ILOCK_EXCL))
830916056eSDarrick J. Wong 		return 0;
840916056eSDarrick J. Wong 
850916056eSDarrick J. Wong 	return XFS_ILOCK_EXCL;
860916056eSDarrick J. Wong }
870916056eSDarrick J. Wong 
880916056eSDarrick J. Wong /*
890916056eSDarrick J. Wong  * Given the inode number of the alleged parent of the inode being scrubbed,
900916056eSDarrick J. Wong  * try to validate that the parent has exactly one directory entry pointing
910916056eSDarrick J. Wong  * back to the inode being scrubbed.  Returns -EAGAIN if we need to revalidate
920916056eSDarrick J. Wong  * the dotdot entry.
930f28b257SDarrick J. Wong  */
940f28b257SDarrick J. Wong STATIC int
xchk_parent_validate(struct xfs_scrub * sc,xfs_ino_t parent_ino)95c517b3aaSDarrick J. Wong xchk_parent_validate(
961d8a748aSDarrick J. Wong 	struct xfs_scrub	*sc,
970916056eSDarrick J. Wong 	xfs_ino_t		parent_ino)
980f28b257SDarrick J. Wong {
99cbab28f4SDarrick J. Wong 	struct xchk_parent_ctx	spc = {
100cbab28f4SDarrick J. Wong 		.sc		= sc,
101cbab28f4SDarrick J. Wong 		.nlink		= 0,
102cbab28f4SDarrick J. Wong 	};
1030f28b257SDarrick J. Wong 	struct xfs_mount	*mp = sc->mp;
1040f28b257SDarrick J. Wong 	struct xfs_inode	*dp = NULL;
1050f28b257SDarrick J. Wong 	xfs_nlink_t		expected_nlink;
1060916056eSDarrick J. Wong 	unsigned int		lock_mode;
10772f76f73SDarrick J. Wong 	int			error = 0;
1080f28b257SDarrick J. Wong 
109b049962cSDarrick J. Wong 	/* Is this the root dir?  Then '..' must point to itself. */
110b049962cSDarrick J. Wong 	if (sc->ip == mp->m_rootip) {
111b049962cSDarrick J. Wong 		if (sc->ip->i_ino != mp->m_sb.sb_rootino ||
112b049962cSDarrick J. Wong 		    sc->ip->i_ino != parent_ino)
113b049962cSDarrick J. Wong 			xchk_fblock_set_corrupt(sc, XFS_DATA_FORK, 0);
1140916056eSDarrick J. Wong 		return 0;
115b049962cSDarrick J. Wong 	}
1168bc763c2SDarrick J. Wong 
1170f28b257SDarrick J. Wong 	/* '..' must not point to ourselves. */
118b049962cSDarrick J. Wong 	if (sc->ip->i_ino == parent_ino) {
119c517b3aaSDarrick J. Wong 		xchk_fblock_set_corrupt(sc, XFS_DATA_FORK, 0);
1200916056eSDarrick J. Wong 		return 0;
1210f28b257SDarrick J. Wong 	}
1220f28b257SDarrick J. Wong 
1230f28b257SDarrick J. Wong 	/*
1240f28b257SDarrick J. Wong 	 * If we're an unlinked directory, the parent /won't/ have a link
1250f28b257SDarrick J. Wong 	 * to us.  Otherwise, it should have one link.
1260f28b257SDarrick J. Wong 	 */
1270f28b257SDarrick J. Wong 	expected_nlink = VFS_I(sc->ip)->i_nlink == 0 ? 0 : 1;
1280f28b257SDarrick J. Wong 
1290f28b257SDarrick J. Wong 	/*
130a03297a0SDarrick J. Wong 	 * Grab the parent directory inode.  This must be released before we
131a03297a0SDarrick J. Wong 	 * cancel the scrub transaction.
1325927268fSDarrick J. Wong 	 *
133da531cc4SDarrick J. Wong 	 * If _iget returns -EINVAL or -ENOENT then the parent inode number is
134da531cc4SDarrick J. Wong 	 * garbage and the directory is corrupt.  If the _iget returns
135da531cc4SDarrick J. Wong 	 * -EFSCORRUPTED or -EFSBADCRC then the parent is corrupt which is a
136da531cc4SDarrick J. Wong 	 *  cross referencing error.  Any other error is an operational error.
1370f28b257SDarrick J. Wong 	 */
138a03297a0SDarrick J. Wong 	error = xchk_iget(sc, parent_ino, &dp);
139da531cc4SDarrick J. Wong 	if (error == -EINVAL || error == -ENOENT) {
1405927268fSDarrick J. Wong 		error = -EFSCORRUPTED;
141c517b3aaSDarrick J. Wong 		xchk_fblock_process_error(sc, XFS_DATA_FORK, 0, &error);
1420916056eSDarrick J. Wong 		return error;
1435927268fSDarrick J. Wong 	}
144c517b3aaSDarrick J. Wong 	if (!xchk_fblock_xref_process_error(sc, XFS_DATA_FORK, 0, &error))
1450916056eSDarrick J. Wong 		return error;
14646c59736SDarrick J. Wong 	if (dp == sc->ip || !S_ISDIR(VFS_I(dp)->i_mode)) {
147c517b3aaSDarrick J. Wong 		xchk_fblock_set_corrupt(sc, XFS_DATA_FORK, 0);
1480f28b257SDarrick J. Wong 		goto out_rele;
1490f28b257SDarrick J. Wong 	}
1500f28b257SDarrick J. Wong 
1510916056eSDarrick J. Wong 	lock_mode = xchk_parent_ilock_dir(dp);
1520916056eSDarrick J. Wong 	if (!lock_mode) {
153*294012fbSDarrick J. Wong 		xchk_iunlock(sc, XFS_ILOCK_EXCL);
154*294012fbSDarrick J. Wong 		xchk_ilock(sc, XFS_ILOCK_EXCL);
1550916056eSDarrick J. Wong 		error = -EAGAIN;
156b049962cSDarrick J. Wong 		goto out_rele;
1570f28b257SDarrick J. Wong 	}
1580f28b257SDarrick J. Wong 
1590916056eSDarrick J. Wong 	/* Look for a directory entry in the parent pointing to the child. */
160cbab28f4SDarrick J. Wong 	error = xchk_dir_walk(sc, dp, xchk_parent_actor, &spc);
161c517b3aaSDarrick J. Wong 	if (!xchk_fblock_xref_process_error(sc, XFS_DATA_FORK, 0, &error))
1620f28b257SDarrick J. Wong 		goto out_unlock;
1630f28b257SDarrick J. Wong 
1640916056eSDarrick J. Wong 	/*
1650916056eSDarrick J. Wong 	 * Ensure that the parent has as many links to the child as the child
1660916056eSDarrick J. Wong 	 * thinks it has to the parent.
1670916056eSDarrick J. Wong 	 */
168cbab28f4SDarrick J. Wong 	if (spc.nlink != expected_nlink)
169c517b3aaSDarrick J. Wong 		xchk_fblock_set_corrupt(sc, XFS_DATA_FORK, 0);
1700f28b257SDarrick J. Wong 
1710f28b257SDarrick J. Wong out_unlock:
1720916056eSDarrick J. Wong 	xfs_iunlock(dp, lock_mode);
1730f28b257SDarrick J. Wong out_rele:
174a03297a0SDarrick J. Wong 	xchk_irele(sc, dp);
1750f28b257SDarrick J. Wong 	return error;
1760f28b257SDarrick J. Wong }
1770f28b257SDarrick J. Wong 
1780f28b257SDarrick J. Wong /* Scrub a parent pointer. */
1790f28b257SDarrick J. Wong int
xchk_parent(struct xfs_scrub * sc)180c517b3aaSDarrick J. Wong xchk_parent(
1811d8a748aSDarrick J. Wong 	struct xfs_scrub	*sc)
1820f28b257SDarrick J. Wong {
1830f28b257SDarrick J. Wong 	struct xfs_mount	*mp = sc->mp;
184b049962cSDarrick J. Wong 	xfs_ino_t		parent_ino;
18572f76f73SDarrick J. Wong 	int			error = 0;
1860f28b257SDarrick J. Wong 
1870f28b257SDarrick J. Wong 	/*
1880f28b257SDarrick J. Wong 	 * If we're a directory, check that the '..' link points up to
1890f28b257SDarrick J. Wong 	 * a directory that has one entry pointing to us.
1900f28b257SDarrick J. Wong 	 */
1910f28b257SDarrick J. Wong 	if (!S_ISDIR(VFS_I(sc->ip)->i_mode))
1920f28b257SDarrick J. Wong 		return -ENOENT;
1930f28b257SDarrick J. Wong 
1940f28b257SDarrick J. Wong 	/* We're not a special inode, are we? */
1950f28b257SDarrick J. Wong 	if (!xfs_verify_dir_ino(mp, sc->ip->i_ino)) {
196c517b3aaSDarrick J. Wong 		xchk_fblock_set_corrupt(sc, XFS_DATA_FORK, 0);
1970916056eSDarrick J. Wong 		return 0;
1980f28b257SDarrick J. Wong 	}
1990f28b257SDarrick J. Wong 
200b049962cSDarrick J. Wong 	do {
2010916056eSDarrick J. Wong 		if (xchk_should_terminate(sc, &error))
2020916056eSDarrick J. Wong 			break;
2030916056eSDarrick J. Wong 
2040f28b257SDarrick J. Wong 		/* Look up '..' */
2050916056eSDarrick J. Wong 		error = xchk_dir_lookup(sc, sc->ip, &xfs_name_dotdot,
2060916056eSDarrick J. Wong 				&parent_ino);
207c517b3aaSDarrick J. Wong 		if (!xchk_fblock_process_error(sc, XFS_DATA_FORK, 0, &error))
2080916056eSDarrick J. Wong 			return error;
209b049962cSDarrick J. Wong 		if (!xfs_verify_dir_ino(mp, parent_ino)) {
210c517b3aaSDarrick J. Wong 			xchk_fblock_set_corrupt(sc, XFS_DATA_FORK, 0);
2110916056eSDarrick J. Wong 			return 0;
2120f28b257SDarrick J. Wong 		}
2130f28b257SDarrick J. Wong 
2140916056eSDarrick J. Wong 		/*
2150916056eSDarrick J. Wong 		 * Check that the dotdot entry points to a parent directory
2160916056eSDarrick J. Wong 		 * containing a dirent pointing to this subdirectory.
2170916056eSDarrick J. Wong 		 */
2180916056eSDarrick J. Wong 		error = xchk_parent_validate(sc, parent_ino);
2190916056eSDarrick J. Wong 	} while (error == -EAGAIN);
2200f28b257SDarrick J. Wong 
2210f28b257SDarrick J. Wong 	return error;
2220f28b257SDarrick J. Wong }
223