Skip to content

Commit 4c233b5

Browse files
author
Darrick J. Wong
committed
xfs: streamline the directory iteration code for scrub
Currently, online scrub reuses the xfs_readdir code to walk every entry in a directory. This isn't awesome for performance, since we end up cycling the directory ILOCK needlessly and coding around the particular quirks of the VFS dir_context interface. Create a streamlined version of readdir that keeps the ILOCK (since the walk function isn't going to copy stuff to userspace), skips a whole lot of directory walk cursor checks (since we start at 0 and walk to the end) and has a sane way to return error codes. Note: Porting the dotdot checking code is left for a subsequent patch. Signed-off-by: Darrick J. Wong <djwong@kernel.org> Reviewed-by: Dave Chinner <dchinner@redhat.com>
1 parent 9dceccc commit 4c233b5

5 files changed

Lines changed: 473 additions & 183 deletions

File tree

fs/xfs/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,7 @@ xfs-y += $(addprefix scrub/, \
158158
ialloc.o \
159159
inode.o \
160160
parent.o \
161+
readdir.o \
161162
refcount.o \
162163
rmap.o \
163164
scrub.o \

fs/xfs/scrub/dir.c

Lines changed: 58 additions & 130 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
#include "scrub/scrub.h"
1919
#include "scrub/common.h"
2020
#include "scrub/dabtree.h"
21+
#include "scrub/readdir.h"
2122

2223
/* Set us up to scrub directories. */
2324
int
@@ -31,30 +32,21 @@ xchk_setup_directory(
3132

3233
/* Scrub a directory entry. */
3334

34-
struct xchk_dir_ctx {
35-
/* VFS fill-directory iterator */
36-
struct dir_context dir_iter;
37-
38-
struct xfs_scrub *sc;
39-
};
40-
41-
/* Check that an inode's mode matches a given DT_ type. */
35+
/* Check that an inode's mode matches a given XFS_DIR3_FT_* type. */
4236
STATIC int
4337
xchk_dir_check_ftype(
44-
struct xchk_dir_ctx *sdc,
38+
struct xfs_scrub *sc,
4539
xfs_fileoff_t offset,
4640
xfs_ino_t inum,
47-
int dtype)
41+
int ftype)
4842
{
49-
struct xfs_mount *mp = sdc->sc->mp;
43+
struct xfs_mount *mp = sc->mp;
5044
struct xfs_inode *ip;
51-
int ino_dtype;
5245
int error = 0;
5346

5447
if (!xfs_has_ftype(mp)) {
55-
if (dtype != DT_UNKNOWN && dtype != DT_DIR)
56-
xchk_fblock_set_corrupt(sdc->sc, XFS_DATA_FORK,
57-
offset);
48+
if (ftype != XFS_DIR3_FT_UNKNOWN && ftype != XFS_DIR3_FT_DIR)
49+
xchk_fblock_set_corrupt(sc, XFS_DATA_FORK, offset);
5850
goto out;
5951
}
6052

@@ -71,21 +63,17 @@ xchk_dir_check_ftype(
7163
* -EFSCORRUPTED or -EFSBADCRC then the child is corrupt which is a
7264
* cross referencing error. Any other error is an operational error.
7365
*/
74-
error = xfs_iget(mp, sdc->sc->tp, inum, 0, 0, &ip);
66+
error = xfs_iget(mp, sc->tp, inum, 0, 0, &ip);
7567
if (error == -EINVAL || error == -ENOENT) {
7668
error = -EFSCORRUPTED;
77-
xchk_fblock_process_error(sdc->sc, XFS_DATA_FORK, 0, &error);
69+
xchk_fblock_process_error(sc, XFS_DATA_FORK, 0, &error);
7870
goto out;
7971
}
80-
if (!xchk_fblock_xref_process_error(sdc->sc, XFS_DATA_FORK, offset,
81-
&error))
72+
if (!xchk_fblock_xref_process_error(sc, XFS_DATA_FORK, offset, &error))
8273
goto out;
8374

84-
/* Convert mode to the DT_* values that dir_emit uses. */
85-
ino_dtype = xfs_dir3_get_dtype(mp,
86-
xfs_mode_to_ftype(VFS_I(ip)->i_mode));
87-
if (ino_dtype != dtype)
88-
xchk_fblock_set_corrupt(sdc->sc, XFS_DATA_FORK, offset);
75+
if (xfs_mode_to_ftype(VFS_I(ip)->i_mode) != ftype)
76+
xchk_fblock_set_corrupt(sc, XFS_DATA_FORK, offset);
8977
xfs_irele(ip);
9078
out:
9179
return error;
@@ -94,105 +82,85 @@ xchk_dir_check_ftype(
9482
/*
9583
* Scrub a single directory entry.
9684
*
97-
* We use the VFS directory iterator (i.e. readdir) to call this
98-
* function for every directory entry in a directory. Once we're here,
99-
* we check the inode number to make sure it's sane, then we check that
100-
* we can look up this filename. Finally, we check the ftype.
85+
* Check the inode number to make sure it's sane, then we check that we can
86+
* look up this filename. Finally, we check the ftype.
10187
*/
102-
STATIC bool
88+
STATIC int
10389
xchk_dir_actor(
104-
struct dir_context *dir_iter,
105-
const char *name,
106-
int namelen,
107-
loff_t pos,
108-
u64 ino,
109-
unsigned type)
90+
struct xfs_scrub *sc,
91+
struct xfs_inode *dp,
92+
xfs_dir2_dataptr_t dapos,
93+
const struct xfs_name *name,
94+
xfs_ino_t ino,
95+
void *priv)
11096
{
111-
struct xfs_mount *mp;
112-
struct xfs_inode *ip;
113-
struct xchk_dir_ctx *sdc;
114-
struct xfs_name xname;
97+
struct xfs_mount *mp = dp->i_mount;
11598
xfs_ino_t lookup_ino;
11699
xfs_dablk_t offset;
117100
bool checked_ftype = false;
118101
int error = 0;
119102

120-
sdc = container_of(dir_iter, struct xchk_dir_ctx, dir_iter);
121-
ip = sdc->sc->ip;
122-
mp = ip->i_mount;
123103
offset = xfs_dir2_db_to_da(mp->m_dir_geo,
124-
xfs_dir2_dataptr_to_db(mp->m_dir_geo, pos));
104+
xfs_dir2_dataptr_to_db(mp->m_dir_geo, dapos));
125105

126-
if (xchk_should_terminate(sdc->sc, &error))
127-
return !error;
106+
if (xchk_should_terminate(sc, &error))
107+
return error;
128108

129109
/* Does this inode number make sense? */
130110
if (!xfs_verify_dir_ino(mp, ino)) {
131-
xchk_fblock_set_corrupt(sdc->sc, XFS_DATA_FORK, offset);
132-
goto out;
111+
xchk_fblock_set_corrupt(sc, XFS_DATA_FORK, offset);
112+
return -ECANCELED;
133113
}
134114

135115
/* Does this name make sense? */
136-
if (!xfs_dir2_namecheck(name, namelen)) {
137-
xchk_fblock_set_corrupt(sdc->sc, XFS_DATA_FORK, offset);
138-
goto out;
116+
if (!xfs_dir2_namecheck(name->name, name->len)) {
117+
xchk_fblock_set_corrupt(sc, XFS_DATA_FORK, offset);
118+
return -ECANCELED;
139119
}
140120

141-
if (!strncmp(".", name, namelen)) {
121+
if (!strncmp(".", name->name, name->len)) {
142122
/* If this is "." then check that the inum matches the dir. */
143-
if (xfs_has_ftype(mp) && type != DT_DIR)
144-
xchk_fblock_set_corrupt(sdc->sc, XFS_DATA_FORK,
145-
offset);
123+
if (xfs_has_ftype(mp) && name->type != XFS_DIR3_FT_DIR)
124+
xchk_fblock_set_corrupt(sc, XFS_DATA_FORK, offset);
146125
checked_ftype = true;
147-
if (ino != ip->i_ino)
148-
xchk_fblock_set_corrupt(sdc->sc, XFS_DATA_FORK,
149-
offset);
150-
} else if (!strncmp("..", name, namelen)) {
126+
if (ino != dp->i_ino)
127+
xchk_fblock_set_corrupt(sc, XFS_DATA_FORK, offset);
128+
} else if (!strncmp("..", name->name, name->len)) {
151129
/*
152130
* If this is ".." in the root inode, check that the inum
153131
* matches this dir.
154132
*/
155-
if (xfs_has_ftype(mp) && type != DT_DIR)
156-
xchk_fblock_set_corrupt(sdc->sc, XFS_DATA_FORK,
157-
offset);
133+
if (xfs_has_ftype(mp) && name->type != XFS_DIR3_FT_DIR)
134+
xchk_fblock_set_corrupt(sc, XFS_DATA_FORK, offset);
158135
checked_ftype = true;
159-
if (ip->i_ino == mp->m_sb.sb_rootino && ino != ip->i_ino)
160-
xchk_fblock_set_corrupt(sdc->sc, XFS_DATA_FORK,
161-
offset);
136+
if (dp->i_ino == mp->m_sb.sb_rootino && ino != dp->i_ino)
137+
xchk_fblock_set_corrupt(sc, XFS_DATA_FORK, offset);
162138
}
163139

164140
/* Verify that we can look up this name by hash. */
165-
xname.name = name;
166-
xname.len = namelen;
167-
xname.type = XFS_DIR3_FT_UNKNOWN;
168-
169-
error = xfs_dir_lookup(sdc->sc->tp, ip, &xname, &lookup_ino, NULL);
141+
error = xchk_dir_lookup(sc, dp, name, &lookup_ino);
170142
/* ENOENT means the hash lookup failed and the dir is corrupt */
171143
if (error == -ENOENT)
172144
error = -EFSCORRUPTED;
173-
if (!xchk_fblock_process_error(sdc->sc, XFS_DATA_FORK, offset,
174-
&error))
145+
if (!xchk_fblock_process_error(sc, XFS_DATA_FORK, offset, &error))
175146
goto out;
176147
if (lookup_ino != ino) {
177-
xchk_fblock_set_corrupt(sdc->sc, XFS_DATA_FORK, offset);
178-
goto out;
148+
xchk_fblock_set_corrupt(sc, XFS_DATA_FORK, offset);
149+
return -ECANCELED;
179150
}
180151

181152
/* Verify the file type. This function absorbs error codes. */
182153
if (!checked_ftype) {
183-
error = xchk_dir_check_ftype(sdc, offset, lookup_ino, type);
154+
error = xchk_dir_check_ftype(sc, offset, lookup_ino,
155+
name->type);
184156
if (error)
185157
goto out;
186158
}
159+
187160
out:
188-
/*
189-
* A negative error code returned here is supposed to cause the
190-
* dir_emit caller (xfs_readdir) to abort the directory iteration
191-
* and return zero to xchk_directory.
192-
*/
193-
if (error == 0 && sdc->sc->sm->sm_flags & XFS_SCRUB_OFLAG_CORRUPT)
194-
return false;
195-
return !error;
161+
if (sc->sm->sm_flags & XFS_SCRUB_OFLAG_CORRUPT)
162+
return -ECANCELED;
163+
return error;
196164
}
197165

198166
/* Scrub a directory btree record. */
@@ -808,22 +776,15 @@ int
808776
xchk_directory(
809777
struct xfs_scrub *sc)
810778
{
811-
struct xchk_dir_ctx sdc = {
812-
.dir_iter.actor = xchk_dir_actor,
813-
.dir_iter.pos = 0,
814-
.sc = sc,
815-
};
816-
size_t bufsize;
817-
loff_t oldpos;
818-
int error = 0;
779+
int error;
819780

820781
if (!S_ISDIR(VFS_I(sc->ip)->i_mode))
821782
return -ENOENT;
822783

823784
/* Plausible size? */
824785
if (sc->ip->i_disk_size < xfs_dir2_sf_hdr_size(0)) {
825786
xchk_ino_set_corrupt(sc, sc->ip->i_ino);
826-
goto out;
787+
return 0;
827788
}
828789

829790
/* Check directory tree structure */
@@ -832,52 +793,19 @@ xchk_directory(
832793
return error;
833794

834795
if (sc->sm->sm_flags & XFS_SCRUB_OFLAG_CORRUPT)
835-
return error;
796+
return 0;
836797

837798
/* Check the freespace. */
838799
error = xchk_directory_blocks(sc);
839800
if (error)
840801
return error;
841802

842803
if (sc->sm->sm_flags & XFS_SCRUB_OFLAG_CORRUPT)
843-
return error;
844-
845-
/*
846-
* Check that every dirent we see can also be looked up by hash.
847-
* Userspace usually asks for a 32k buffer, so we will too.
848-
*/
849-
bufsize = (size_t)min_t(loff_t, XFS_READDIR_BUFSIZE,
850-
sc->ip->i_disk_size);
851-
852-
/*
853-
* Look up every name in this directory by hash.
854-
*
855-
* Use the xfs_readdir function to call xchk_dir_actor on
856-
* every directory entry in this directory. In _actor, we check
857-
* the name, inode number, and ftype (if applicable) of the
858-
* entry. xfs_readdir uses the VFS filldir functions to provide
859-
* iteration context.
860-
*
861-
* The VFS grabs a read or write lock via i_rwsem before it reads
862-
* or writes to a directory. If we've gotten this far we've
863-
* already obtained IOLOCK_EXCL, which (since 4.10) is the same as
864-
* getting a write lock on i_rwsem. Therefore, it is safe for us
865-
* to drop the ILOCK here in order to reuse the _readdir and
866-
* _dir_lookup routines, which do their own ILOCK locking.
867-
*/
868-
oldpos = 0;
869-
sc->ilock_flags &= ~XFS_ILOCK_EXCL;
870-
xfs_iunlock(sc->ip, XFS_ILOCK_EXCL);
871-
while (true) {
872-
error = xfs_readdir(sc->tp, sc->ip, &sdc.dir_iter, bufsize);
873-
if (!xchk_fblock_process_error(sc, XFS_DATA_FORK, 0,
874-
&error))
875-
goto out;
876-
if (oldpos == sdc.dir_iter.pos)
877-
break;
878-
oldpos = sdc.dir_iter.pos;
879-
}
804+
return 0;
880805

881-
out:
806+
/* Look up every name in this directory by hash. */
807+
error = xchk_dir_walk(sc, sc->ip, xchk_dir_actor, NULL);
808+
if (error == -ECANCELED)
809+
error = 0;
882810
return error;
883811
}

0 commit comments

Comments
 (0)