Linux Audio

Check our new training course

Loading...
Note: File does not exist in v3.5.6.
  1// SPDX-License-Identifier: GPL-2.0-or-later
  2/*
  3 * Copyright (C) 2017-2023 Oracle.  All Rights Reserved.
  4 * Author: Darrick J. Wong <djwong@kernel.org>
  5 */
  6#include "xfs.h"
  7#include "xfs_fs.h"
  8#include "xfs_shared.h"
  9#include "xfs_format.h"
 10#include "xfs_trans_resv.h"
 11#include "xfs_mount.h"
 12#include "xfs_log_format.h"
 13#include "xfs_inode.h"
 14#include "xfs_icache.h"
 15#include "xfs_dir2.h"
 16#include "xfs_dir2_priv.h"
 17#include "scrub/scrub.h"
 18#include "scrub/common.h"
 19#include "scrub/readdir.h"
 20
 21/* Set us up to scrub parents. */
 22int
 23xchk_setup_parent(
 24	struct xfs_scrub	*sc)
 25{
 26	return xchk_setup_inode_contents(sc, 0);
 27}
 28
 29/* Parent pointers */
 30
 31/* Look for an entry in a parent pointing to this inode. */
 32
 33struct xchk_parent_ctx {
 34	struct xfs_scrub	*sc;
 35	xfs_nlink_t		nlink;
 36};
 37
 38/* Look for a single entry in a directory pointing to an inode. */
 39STATIC int
 40xchk_parent_actor(
 41	struct xfs_scrub	*sc,
 42	struct xfs_inode	*dp,
 43	xfs_dir2_dataptr_t	dapos,
 44	const struct xfs_name	*name,
 45	xfs_ino_t		ino,
 46	void			*priv)
 47{
 48	struct xchk_parent_ctx	*spc = priv;
 49	int			error = 0;
 50
 51	/* Does this name make sense? */
 52	if (!xfs_dir2_namecheck(name->name, name->len))
 53		error = -EFSCORRUPTED;
 54	if (!xchk_fblock_xref_process_error(sc, XFS_DATA_FORK, 0, &error))
 55		return error;
 56
 57	if (sc->ip->i_ino == ino)
 58		spc->nlink++;
 59
 60	if (xchk_should_terminate(spc->sc, &error))
 61		return error;
 62
 63	return 0;
 64}
 65
 66/*
 67 * Try to lock a parent directory for checking dirents.  Returns the inode
 68 * flags for the locks we now hold, or zero if we failed.
 69 */
 70STATIC unsigned int
 71xchk_parent_ilock_dir(
 72	struct xfs_inode	*dp)
 73{
 74	if (!xfs_ilock_nowait(dp, XFS_ILOCK_SHARED))
 75		return 0;
 76
 77	if (!xfs_need_iread_extents(&dp->i_df))
 78		return XFS_ILOCK_SHARED;
 79
 80	xfs_iunlock(dp, XFS_ILOCK_SHARED);
 81
 82	if (!xfs_ilock_nowait(dp, XFS_ILOCK_EXCL))
 83		return 0;
 84
 85	return XFS_ILOCK_EXCL;
 86}
 87
 88/*
 89 * Given the inode number of the alleged parent of the inode being scrubbed,
 90 * try to validate that the parent has exactly one directory entry pointing
 91 * back to the inode being scrubbed.  Returns -EAGAIN if we need to revalidate
 92 * the dotdot entry.
 93 */
 94STATIC int
 95xchk_parent_validate(
 96	struct xfs_scrub	*sc,
 97	xfs_ino_t		parent_ino)
 98{
 99	struct xchk_parent_ctx	spc = {
100		.sc		= sc,
101		.nlink		= 0,
102	};
103	struct xfs_mount	*mp = sc->mp;
104	struct xfs_inode	*dp = NULL;
105	xfs_nlink_t		expected_nlink;
106	unsigned int		lock_mode;
107	int			error = 0;
108
109	/* Is this the root dir?  Then '..' must point to itself. */
110	if (sc->ip == mp->m_rootip) {
111		if (sc->ip->i_ino != mp->m_sb.sb_rootino ||
112		    sc->ip->i_ino != parent_ino)
113			xchk_fblock_set_corrupt(sc, XFS_DATA_FORK, 0);
114		return 0;
115	}
116
117	/* '..' must not point to ourselves. */
118	if (sc->ip->i_ino == parent_ino) {
119		xchk_fblock_set_corrupt(sc, XFS_DATA_FORK, 0);
120		return 0;
121	}
122
123	/*
124	 * If we're an unlinked directory, the parent /won't/ have a link
125	 * to us.  Otherwise, it should have one link.
126	 */
127	expected_nlink = VFS_I(sc->ip)->i_nlink == 0 ? 0 : 1;
128
129	/*
130	 * Grab the parent directory inode.  This must be released before we
131	 * cancel the scrub transaction.
132	 *
133	 * If _iget returns -EINVAL or -ENOENT then the parent inode number is
134	 * garbage and the directory is corrupt.  If the _iget returns
135	 * -EFSCORRUPTED or -EFSBADCRC then the parent is corrupt which is a
136	 *  cross referencing error.  Any other error is an operational error.
137	 */
138	error = xchk_iget(sc, parent_ino, &dp);
139	if (error == -EINVAL || error == -ENOENT) {
140		error = -EFSCORRUPTED;
141		xchk_fblock_process_error(sc, XFS_DATA_FORK, 0, &error);
142		return error;
143	}
144	if (!xchk_fblock_xref_process_error(sc, XFS_DATA_FORK, 0, &error))
145		return error;
146	if (dp == sc->ip || !S_ISDIR(VFS_I(dp)->i_mode)) {
147		xchk_fblock_set_corrupt(sc, XFS_DATA_FORK, 0);
148		goto out_rele;
149	}
150
151	lock_mode = xchk_parent_ilock_dir(dp);
152	if (!lock_mode) {
153		xchk_iunlock(sc, XFS_ILOCK_EXCL);
154		xchk_ilock(sc, XFS_ILOCK_EXCL);
155		error = -EAGAIN;
156		goto out_rele;
157	}
158
159	/*
160	 * We cannot yet validate this parent pointer if the directory looks as
161	 * though it has been zapped by the inode record repair code.
162	 */
163	if (xchk_dir_looks_zapped(dp)) {
164		error = -EBUSY;
165		xchk_set_incomplete(sc);
166		goto out_unlock;
167	}
168
169	/* Look for a directory entry in the parent pointing to the child. */
170	error = xchk_dir_walk(sc, dp, xchk_parent_actor, &spc);
171	if (!xchk_fblock_xref_process_error(sc, XFS_DATA_FORK, 0, &error))
172		goto out_unlock;
173
174	/*
175	 * Ensure that the parent has as many links to the child as the child
176	 * thinks it has to the parent.
177	 */
178	if (spc.nlink != expected_nlink)
179		xchk_fblock_set_corrupt(sc, XFS_DATA_FORK, 0);
180
181out_unlock:
182	xfs_iunlock(dp, lock_mode);
183out_rele:
184	xchk_irele(sc, dp);
185	return error;
186}
187
188/* Scrub a parent pointer. */
189int
190xchk_parent(
191	struct xfs_scrub	*sc)
192{
193	struct xfs_mount	*mp = sc->mp;
194	xfs_ino_t		parent_ino;
195	int			error = 0;
196
197	/*
198	 * If we're a directory, check that the '..' link points up to
199	 * a directory that has one entry pointing to us.
200	 */
201	if (!S_ISDIR(VFS_I(sc->ip)->i_mode))
202		return -ENOENT;
203
204	/* We're not a special inode, are we? */
205	if (!xfs_verify_dir_ino(mp, sc->ip->i_ino)) {
206		xchk_fblock_set_corrupt(sc, XFS_DATA_FORK, 0);
207		return 0;
208	}
209
210	do {
211		if (xchk_should_terminate(sc, &error))
212			break;
213
214		/* Look up '..' */
215		error = xchk_dir_lookup(sc, sc->ip, &xfs_name_dotdot,
216				&parent_ino);
217		if (!xchk_fblock_process_error(sc, XFS_DATA_FORK, 0, &error))
218			return error;
219		if (!xfs_verify_dir_ino(mp, parent_ino)) {
220			xchk_fblock_set_corrupt(sc, XFS_DATA_FORK, 0);
221			return 0;
222		}
223
224		/*
225		 * Check that the dotdot entry points to a parent directory
226		 * containing a dirent pointing to this subdirectory.
227		 */
228		error = xchk_parent_validate(sc, parent_ino);
229	} while (error == -EAGAIN);
230	if (error == -EBUSY) {
231		/*
232		 * We could not scan a directory, so we marked the check
233		 * incomplete.  No further error return is necessary.
234		 */
235		return 0;
236	}
237
238	return error;
239}