xref: /linux/fs/xfs/scrub/parent.c (revision 45d8b572fac3aa8b49d53c946b3685eaf78a2824)
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. */
22 int
23 xchk_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 
33 struct 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. */
39 STATIC int
40 xchk_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  */
70 STATIC unsigned int
71 xchk_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  */
94 STATIC int
95 xchk_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 
181 out_unlock:
182 	xfs_iunlock(dp, lock_mode);
183 out_rele:
184 	xchk_irele(sc, dp);
185 	return error;
186 }
187 
188 /* Scrub a parent pointer. */
189 int
190 xchk_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 }
240