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
xchk_setup_parent(struct xfs_scrub * sc)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
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)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
xchk_parent_ilock_dir(struct xfs_inode * dp)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
xchk_parent_validate(struct xfs_scrub * sc,xfs_ino_t parent_ino)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 /* Look for a directory entry in the parent pointing to the child. */
160 error = xchk_dir_walk(sc, dp, xchk_parent_actor, &spc);
161 if (!xchk_fblock_xref_process_error(sc, XFS_DATA_FORK, 0, &error))
162 goto out_unlock;
163
164 /*
165 * Ensure that the parent has as many links to the child as the child
166 * thinks it has to the parent.
167 */
168 if (spc.nlink != expected_nlink)
169 xchk_fblock_set_corrupt(sc, XFS_DATA_FORK, 0);
170
171 out_unlock:
172 xfs_iunlock(dp, lock_mode);
173 out_rele:
174 xchk_irele(sc, dp);
175 return error;
176 }
177
178 /* Scrub a parent pointer. */
179 int
xchk_parent(struct xfs_scrub * sc)180 xchk_parent(
181 struct xfs_scrub *sc)
182 {
183 struct xfs_mount *mp = sc->mp;
184 xfs_ino_t parent_ino;
185 int error = 0;
186
187 /*
188 * If we're a directory, check that the '..' link points up to
189 * a directory that has one entry pointing to us.
190 */
191 if (!S_ISDIR(VFS_I(sc->ip)->i_mode))
192 return -ENOENT;
193
194 /* We're not a special inode, are we? */
195 if (!xfs_verify_dir_ino(mp, sc->ip->i_ino)) {
196 xchk_fblock_set_corrupt(sc, XFS_DATA_FORK, 0);
197 return 0;
198 }
199
200 do {
201 if (xchk_should_terminate(sc, &error))
202 break;
203
204 /* Look up '..' */
205 error = xchk_dir_lookup(sc, sc->ip, &xfs_name_dotdot,
206 &parent_ino);
207 if (!xchk_fblock_process_error(sc, XFS_DATA_FORK, 0, &error))
208 return error;
209 if (!xfs_verify_dir_ino(mp, parent_ino)) {
210 xchk_fblock_set_corrupt(sc, XFS_DATA_FORK, 0);
211 return 0;
212 }
213
214 /*
215 * Check that the dotdot entry points to a parent directory
216 * containing a dirent pointing to this subdirectory.
217 */
218 error = xchk_parent_validate(sc, parent_ino);
219 } while (error == -EAGAIN);
220
221 return error;
222 }
223