]> Git Repo - linux.git/blob - fs/xfs/scrub/parent.c
x86/config: Fix warning for 'make ARCH=x86_64 tinyconfig'
[linux.git] / fs / xfs / scrub / parent.c
1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3  * Copyright (C) 2017-2023 Oracle.  All Rights Reserved.
4  * Author: Darrick J. Wong <[email protected]>
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 }
This page took 0.046494 seconds and 4 git commands to generate.