162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Copyright (C) 2017-2023 Oracle.  All Rights Reserved.
462306a36Sopenharmony_ci * Author: Darrick J. Wong <djwong@kernel.org>
562306a36Sopenharmony_ci */
662306a36Sopenharmony_ci#include "xfs.h"
762306a36Sopenharmony_ci#include "xfs_fs.h"
862306a36Sopenharmony_ci#include "xfs_shared.h"
962306a36Sopenharmony_ci#include "xfs_format.h"
1062306a36Sopenharmony_ci#include "xfs_trans_resv.h"
1162306a36Sopenharmony_ci#include "xfs_mount.h"
1262306a36Sopenharmony_ci#include "xfs_log_format.h"
1362306a36Sopenharmony_ci#include "xfs_inode.h"
1462306a36Sopenharmony_ci#include "xfs_icache.h"
1562306a36Sopenharmony_ci#include "xfs_dir2.h"
1662306a36Sopenharmony_ci#include "xfs_dir2_priv.h"
1762306a36Sopenharmony_ci#include "scrub/scrub.h"
1862306a36Sopenharmony_ci#include "scrub/common.h"
1962306a36Sopenharmony_ci#include "scrub/readdir.h"
2062306a36Sopenharmony_ci
2162306a36Sopenharmony_ci/* Set us up to scrub parents. */
2262306a36Sopenharmony_ciint
2362306a36Sopenharmony_cixchk_setup_parent(
2462306a36Sopenharmony_ci	struct xfs_scrub	*sc)
2562306a36Sopenharmony_ci{
2662306a36Sopenharmony_ci	return xchk_setup_inode_contents(sc, 0);
2762306a36Sopenharmony_ci}
2862306a36Sopenharmony_ci
2962306a36Sopenharmony_ci/* Parent pointers */
3062306a36Sopenharmony_ci
3162306a36Sopenharmony_ci/* Look for an entry in a parent pointing to this inode. */
3262306a36Sopenharmony_ci
3362306a36Sopenharmony_cistruct xchk_parent_ctx {
3462306a36Sopenharmony_ci	struct xfs_scrub	*sc;
3562306a36Sopenharmony_ci	xfs_nlink_t		nlink;
3662306a36Sopenharmony_ci};
3762306a36Sopenharmony_ci
3862306a36Sopenharmony_ci/* Look for a single entry in a directory pointing to an inode. */
3962306a36Sopenharmony_ciSTATIC int
4062306a36Sopenharmony_cixchk_parent_actor(
4162306a36Sopenharmony_ci	struct xfs_scrub	*sc,
4262306a36Sopenharmony_ci	struct xfs_inode	*dp,
4362306a36Sopenharmony_ci	xfs_dir2_dataptr_t	dapos,
4462306a36Sopenharmony_ci	const struct xfs_name	*name,
4562306a36Sopenharmony_ci	xfs_ino_t		ino,
4662306a36Sopenharmony_ci	void			*priv)
4762306a36Sopenharmony_ci{
4862306a36Sopenharmony_ci	struct xchk_parent_ctx	*spc = priv;
4962306a36Sopenharmony_ci	int			error = 0;
5062306a36Sopenharmony_ci
5162306a36Sopenharmony_ci	/* Does this name make sense? */
5262306a36Sopenharmony_ci	if (!xfs_dir2_namecheck(name->name, name->len))
5362306a36Sopenharmony_ci		error = -EFSCORRUPTED;
5462306a36Sopenharmony_ci	if (!xchk_fblock_xref_process_error(sc, XFS_DATA_FORK, 0, &error))
5562306a36Sopenharmony_ci		return error;
5662306a36Sopenharmony_ci
5762306a36Sopenharmony_ci	if (sc->ip->i_ino == ino)
5862306a36Sopenharmony_ci		spc->nlink++;
5962306a36Sopenharmony_ci
6062306a36Sopenharmony_ci	if (xchk_should_terminate(spc->sc, &error))
6162306a36Sopenharmony_ci		return error;
6262306a36Sopenharmony_ci
6362306a36Sopenharmony_ci	return 0;
6462306a36Sopenharmony_ci}
6562306a36Sopenharmony_ci
6662306a36Sopenharmony_ci/*
6762306a36Sopenharmony_ci * Try to lock a parent directory for checking dirents.  Returns the inode
6862306a36Sopenharmony_ci * flags for the locks we now hold, or zero if we failed.
6962306a36Sopenharmony_ci */
7062306a36Sopenharmony_ciSTATIC unsigned int
7162306a36Sopenharmony_cixchk_parent_ilock_dir(
7262306a36Sopenharmony_ci	struct xfs_inode	*dp)
7362306a36Sopenharmony_ci{
7462306a36Sopenharmony_ci	if (!xfs_ilock_nowait(dp, XFS_ILOCK_SHARED))
7562306a36Sopenharmony_ci		return 0;
7662306a36Sopenharmony_ci
7762306a36Sopenharmony_ci	if (!xfs_need_iread_extents(&dp->i_df))
7862306a36Sopenharmony_ci		return XFS_ILOCK_SHARED;
7962306a36Sopenharmony_ci
8062306a36Sopenharmony_ci	xfs_iunlock(dp, XFS_ILOCK_SHARED);
8162306a36Sopenharmony_ci
8262306a36Sopenharmony_ci	if (!xfs_ilock_nowait(dp, XFS_ILOCK_EXCL))
8362306a36Sopenharmony_ci		return 0;
8462306a36Sopenharmony_ci
8562306a36Sopenharmony_ci	return XFS_ILOCK_EXCL;
8662306a36Sopenharmony_ci}
8762306a36Sopenharmony_ci
8862306a36Sopenharmony_ci/*
8962306a36Sopenharmony_ci * Given the inode number of the alleged parent of the inode being scrubbed,
9062306a36Sopenharmony_ci * try to validate that the parent has exactly one directory entry pointing
9162306a36Sopenharmony_ci * back to the inode being scrubbed.  Returns -EAGAIN if we need to revalidate
9262306a36Sopenharmony_ci * the dotdot entry.
9362306a36Sopenharmony_ci */
9462306a36Sopenharmony_ciSTATIC int
9562306a36Sopenharmony_cixchk_parent_validate(
9662306a36Sopenharmony_ci	struct xfs_scrub	*sc,
9762306a36Sopenharmony_ci	xfs_ino_t		parent_ino)
9862306a36Sopenharmony_ci{
9962306a36Sopenharmony_ci	struct xchk_parent_ctx	spc = {
10062306a36Sopenharmony_ci		.sc		= sc,
10162306a36Sopenharmony_ci		.nlink		= 0,
10262306a36Sopenharmony_ci	};
10362306a36Sopenharmony_ci	struct xfs_mount	*mp = sc->mp;
10462306a36Sopenharmony_ci	struct xfs_inode	*dp = NULL;
10562306a36Sopenharmony_ci	xfs_nlink_t		expected_nlink;
10662306a36Sopenharmony_ci	unsigned int		lock_mode;
10762306a36Sopenharmony_ci	int			error = 0;
10862306a36Sopenharmony_ci
10962306a36Sopenharmony_ci	/* Is this the root dir?  Then '..' must point to itself. */
11062306a36Sopenharmony_ci	if (sc->ip == mp->m_rootip) {
11162306a36Sopenharmony_ci		if (sc->ip->i_ino != mp->m_sb.sb_rootino ||
11262306a36Sopenharmony_ci		    sc->ip->i_ino != parent_ino)
11362306a36Sopenharmony_ci			xchk_fblock_set_corrupt(sc, XFS_DATA_FORK, 0);
11462306a36Sopenharmony_ci		return 0;
11562306a36Sopenharmony_ci	}
11662306a36Sopenharmony_ci
11762306a36Sopenharmony_ci	/* '..' must not point to ourselves. */
11862306a36Sopenharmony_ci	if (sc->ip->i_ino == parent_ino) {
11962306a36Sopenharmony_ci		xchk_fblock_set_corrupt(sc, XFS_DATA_FORK, 0);
12062306a36Sopenharmony_ci		return 0;
12162306a36Sopenharmony_ci	}
12262306a36Sopenharmony_ci
12362306a36Sopenharmony_ci	/*
12462306a36Sopenharmony_ci	 * If we're an unlinked directory, the parent /won't/ have a link
12562306a36Sopenharmony_ci	 * to us.  Otherwise, it should have one link.
12662306a36Sopenharmony_ci	 */
12762306a36Sopenharmony_ci	expected_nlink = VFS_I(sc->ip)->i_nlink == 0 ? 0 : 1;
12862306a36Sopenharmony_ci
12962306a36Sopenharmony_ci	/*
13062306a36Sopenharmony_ci	 * Grab the parent directory inode.  This must be released before we
13162306a36Sopenharmony_ci	 * cancel the scrub transaction.
13262306a36Sopenharmony_ci	 *
13362306a36Sopenharmony_ci	 * If _iget returns -EINVAL or -ENOENT then the parent inode number is
13462306a36Sopenharmony_ci	 * garbage and the directory is corrupt.  If the _iget returns
13562306a36Sopenharmony_ci	 * -EFSCORRUPTED or -EFSBADCRC then the parent is corrupt which is a
13662306a36Sopenharmony_ci	 *  cross referencing error.  Any other error is an operational error.
13762306a36Sopenharmony_ci	 */
13862306a36Sopenharmony_ci	error = xchk_iget(sc, parent_ino, &dp);
13962306a36Sopenharmony_ci	if (error == -EINVAL || error == -ENOENT) {
14062306a36Sopenharmony_ci		error = -EFSCORRUPTED;
14162306a36Sopenharmony_ci		xchk_fblock_process_error(sc, XFS_DATA_FORK, 0, &error);
14262306a36Sopenharmony_ci		return error;
14362306a36Sopenharmony_ci	}
14462306a36Sopenharmony_ci	if (!xchk_fblock_xref_process_error(sc, XFS_DATA_FORK, 0, &error))
14562306a36Sopenharmony_ci		return error;
14662306a36Sopenharmony_ci	if (dp == sc->ip || !S_ISDIR(VFS_I(dp)->i_mode)) {
14762306a36Sopenharmony_ci		xchk_fblock_set_corrupt(sc, XFS_DATA_FORK, 0);
14862306a36Sopenharmony_ci		goto out_rele;
14962306a36Sopenharmony_ci	}
15062306a36Sopenharmony_ci
15162306a36Sopenharmony_ci	lock_mode = xchk_parent_ilock_dir(dp);
15262306a36Sopenharmony_ci	if (!lock_mode) {
15362306a36Sopenharmony_ci		xchk_iunlock(sc, XFS_ILOCK_EXCL);
15462306a36Sopenharmony_ci		xchk_ilock(sc, XFS_ILOCK_EXCL);
15562306a36Sopenharmony_ci		error = -EAGAIN;
15662306a36Sopenharmony_ci		goto out_rele;
15762306a36Sopenharmony_ci	}
15862306a36Sopenharmony_ci
15962306a36Sopenharmony_ci	/* Look for a directory entry in the parent pointing to the child. */
16062306a36Sopenharmony_ci	error = xchk_dir_walk(sc, dp, xchk_parent_actor, &spc);
16162306a36Sopenharmony_ci	if (!xchk_fblock_xref_process_error(sc, XFS_DATA_FORK, 0, &error))
16262306a36Sopenharmony_ci		goto out_unlock;
16362306a36Sopenharmony_ci
16462306a36Sopenharmony_ci	/*
16562306a36Sopenharmony_ci	 * Ensure that the parent has as many links to the child as the child
16662306a36Sopenharmony_ci	 * thinks it has to the parent.
16762306a36Sopenharmony_ci	 */
16862306a36Sopenharmony_ci	if (spc.nlink != expected_nlink)
16962306a36Sopenharmony_ci		xchk_fblock_set_corrupt(sc, XFS_DATA_FORK, 0);
17062306a36Sopenharmony_ci
17162306a36Sopenharmony_ciout_unlock:
17262306a36Sopenharmony_ci	xfs_iunlock(dp, lock_mode);
17362306a36Sopenharmony_ciout_rele:
17462306a36Sopenharmony_ci	xchk_irele(sc, dp);
17562306a36Sopenharmony_ci	return error;
17662306a36Sopenharmony_ci}
17762306a36Sopenharmony_ci
17862306a36Sopenharmony_ci/* Scrub a parent pointer. */
17962306a36Sopenharmony_ciint
18062306a36Sopenharmony_cixchk_parent(
18162306a36Sopenharmony_ci	struct xfs_scrub	*sc)
18262306a36Sopenharmony_ci{
18362306a36Sopenharmony_ci	struct xfs_mount	*mp = sc->mp;
18462306a36Sopenharmony_ci	xfs_ino_t		parent_ino;
18562306a36Sopenharmony_ci	int			error = 0;
18662306a36Sopenharmony_ci
18762306a36Sopenharmony_ci	/*
18862306a36Sopenharmony_ci	 * If we're a directory, check that the '..' link points up to
18962306a36Sopenharmony_ci	 * a directory that has one entry pointing to us.
19062306a36Sopenharmony_ci	 */
19162306a36Sopenharmony_ci	if (!S_ISDIR(VFS_I(sc->ip)->i_mode))
19262306a36Sopenharmony_ci		return -ENOENT;
19362306a36Sopenharmony_ci
19462306a36Sopenharmony_ci	/* We're not a special inode, are we? */
19562306a36Sopenharmony_ci	if (!xfs_verify_dir_ino(mp, sc->ip->i_ino)) {
19662306a36Sopenharmony_ci		xchk_fblock_set_corrupt(sc, XFS_DATA_FORK, 0);
19762306a36Sopenharmony_ci		return 0;
19862306a36Sopenharmony_ci	}
19962306a36Sopenharmony_ci
20062306a36Sopenharmony_ci	do {
20162306a36Sopenharmony_ci		if (xchk_should_terminate(sc, &error))
20262306a36Sopenharmony_ci			break;
20362306a36Sopenharmony_ci
20462306a36Sopenharmony_ci		/* Look up '..' */
20562306a36Sopenharmony_ci		error = xchk_dir_lookup(sc, sc->ip, &xfs_name_dotdot,
20662306a36Sopenharmony_ci				&parent_ino);
20762306a36Sopenharmony_ci		if (!xchk_fblock_process_error(sc, XFS_DATA_FORK, 0, &error))
20862306a36Sopenharmony_ci			return error;
20962306a36Sopenharmony_ci		if (!xfs_verify_dir_ino(mp, parent_ino)) {
21062306a36Sopenharmony_ci			xchk_fblock_set_corrupt(sc, XFS_DATA_FORK, 0);
21162306a36Sopenharmony_ci			return 0;
21262306a36Sopenharmony_ci		}
21362306a36Sopenharmony_ci
21462306a36Sopenharmony_ci		/*
21562306a36Sopenharmony_ci		 * Check that the dotdot entry points to a parent directory
21662306a36Sopenharmony_ci		 * containing a dirent pointing to this subdirectory.
21762306a36Sopenharmony_ci		 */
21862306a36Sopenharmony_ci		error = xchk_parent_validate(sc, parent_ino);
21962306a36Sopenharmony_ci	} while (error == -EAGAIN);
22062306a36Sopenharmony_ci
22162306a36Sopenharmony_ci	return error;
22262306a36Sopenharmony_ci}
223