Skip to content

Commit 0d29a20

Browse files
author
Darrick J. Wong
committed
xfs: scrub parent pointers
Actually check parent pointers now. Signed-off-by: Darrick J. Wong <djwong@kernel.org> Reviewed-by: Christoph Hellwig <hch@lst.de>
1 parent b961c8b commit 0d29a20

File tree

1 file changed

+371
-0
lines changed

1 file changed

+371
-0
lines changed

fs/xfs/scrub/parent.c

Lines changed: 371 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,15 @@
1515
#include "xfs_icache.h"
1616
#include "xfs_dir2.h"
1717
#include "xfs_dir2_priv.h"
18+
#include "xfs_attr.h"
19+
#include "xfs_parent.h"
1820
#include "scrub/scrub.h"
1921
#include "scrub/common.h"
2022
#include "scrub/readdir.h"
2123
#include "scrub/tempfile.h"
2224
#include "scrub/repair.h"
25+
#include "scrub/listxattr.h"
26+
#include "scrub/trace.h"
2327

2428
/* Set us up to scrub parents. */
2529
int
@@ -197,6 +201,370 @@ xchk_parent_validate(
197201
return error;
198202
}
199203

204+
/*
205+
* Checking of Parent Pointers
206+
* ===========================
207+
*
208+
* On filesystems with directory parent pointers, we check the referential
209+
* integrity by visiting each parent pointer of a child file and checking that
210+
* the directory referenced by the pointer actually has a dirent pointing
211+
* forward to the child file.
212+
*/
213+
214+
struct xchk_pptrs {
215+
struct xfs_scrub *sc;
216+
217+
/* How many parent pointers did we find at the end? */
218+
unsigned long long pptrs_found;
219+
220+
/* Parent of this directory. */
221+
xfs_ino_t parent_ino;
222+
};
223+
224+
/* Does this parent pointer match the dotdot entry? */
225+
STATIC int
226+
xchk_parent_scan_dotdot(
227+
struct xfs_scrub *sc,
228+
struct xfs_inode *ip,
229+
unsigned int attr_flags,
230+
const unsigned char *name,
231+
unsigned int namelen,
232+
const void *value,
233+
unsigned int valuelen,
234+
void *priv)
235+
{
236+
struct xchk_pptrs *pp = priv;
237+
xfs_ino_t parent_ino;
238+
int error;
239+
240+
if (!(attr_flags & XFS_ATTR_PARENT))
241+
return 0;
242+
243+
error = xfs_parent_from_attr(sc->mp, attr_flags, name, namelen, value,
244+
valuelen, &parent_ino, NULL);
245+
if (error)
246+
return error;
247+
248+
if (pp->parent_ino == parent_ino)
249+
return -ECANCELED;
250+
251+
return 0;
252+
}
253+
254+
/* Look up the dotdot entry so that we can check it as we walk the pptrs. */
255+
STATIC int
256+
xchk_parent_pptr_and_dotdot(
257+
struct xchk_pptrs *pp)
258+
{
259+
struct xfs_scrub *sc = pp->sc;
260+
int error;
261+
262+
/* Look up '..' */
263+
error = xchk_dir_lookup(sc, sc->ip, &xfs_name_dotdot, &pp->parent_ino);
264+
if (!xchk_fblock_process_error(sc, XFS_DATA_FORK, 0, &error))
265+
return error;
266+
if (!xfs_verify_dir_ino(sc->mp, pp->parent_ino)) {
267+
xchk_fblock_set_corrupt(sc, XFS_DATA_FORK, 0);
268+
return 0;
269+
}
270+
271+
/* Is this the root dir? Then '..' must point to itself. */
272+
if (sc->ip == sc->mp->m_rootip) {
273+
if (sc->ip->i_ino != pp->parent_ino)
274+
xchk_fblock_set_corrupt(sc, XFS_DATA_FORK, 0);
275+
return 0;
276+
}
277+
278+
/*
279+
* If this is now an unlinked directory, the dotdot value is
280+
* meaningless as long as it points to a valid inode.
281+
*/
282+
if (VFS_I(sc->ip)->i_nlink == 0)
283+
return 0;
284+
285+
if (pp->sc->sm->sm_flags & XFS_SCRUB_OFLAG_CORRUPT)
286+
return 0;
287+
288+
/* Otherwise, walk the pptrs again, and check. */
289+
error = xchk_xattr_walk(sc, sc->ip, xchk_parent_scan_dotdot, pp);
290+
if (error == -ECANCELED) {
291+
/* Found a parent pointer that matches dotdot. */
292+
return 0;
293+
}
294+
if (!error || error == -EFSCORRUPTED) {
295+
/* Found a broken parent pointer or no match. */
296+
xchk_fblock_set_corrupt(sc, XFS_ATTR_FORK, 0);
297+
return 0;
298+
}
299+
return error;
300+
}
301+
302+
/*
303+
* Try to lock a parent directory for checking dirents. Returns the inode
304+
* flags for the locks we now hold, or zero if we failed.
305+
*/
306+
STATIC unsigned int
307+
xchk_parent_lock_dir(
308+
struct xfs_scrub *sc,
309+
struct xfs_inode *dp)
310+
{
311+
if (!xfs_ilock_nowait(dp, XFS_IOLOCK_SHARED))
312+
return 0;
313+
314+
if (!xfs_ilock_nowait(dp, XFS_ILOCK_SHARED)) {
315+
xfs_iunlock(dp, XFS_IOLOCK_SHARED);
316+
return 0;
317+
}
318+
319+
if (!xfs_need_iread_extents(&dp->i_df))
320+
return XFS_IOLOCK_SHARED | XFS_ILOCK_SHARED;
321+
322+
xfs_iunlock(dp, XFS_ILOCK_SHARED);
323+
324+
if (!xfs_ilock_nowait(dp, XFS_ILOCK_EXCL)) {
325+
xfs_iunlock(dp, XFS_IOLOCK_SHARED);
326+
return 0;
327+
}
328+
329+
return XFS_IOLOCK_SHARED | XFS_ILOCK_EXCL;
330+
}
331+
332+
/* Check the forward link (dirent) associated with this parent pointer. */
333+
STATIC int
334+
xchk_parent_dirent(
335+
struct xchk_pptrs *pp,
336+
const struct xfs_name *xname,
337+
struct xfs_inode *dp)
338+
{
339+
struct xfs_scrub *sc = pp->sc;
340+
xfs_ino_t child_ino;
341+
int error;
342+
343+
/*
344+
* Use the name attached to this parent pointer to look up the
345+
* directory entry in the alleged parent.
346+
*/
347+
error = xchk_dir_lookup(sc, dp, xname, &child_ino);
348+
if (error == -ENOENT) {
349+
xchk_fblock_xref_set_corrupt(sc, XFS_ATTR_FORK, 0);
350+
return 0;
351+
}
352+
if (!xchk_fblock_xref_process_error(sc, XFS_ATTR_FORK, 0, &error))
353+
return error;
354+
355+
/* Does the inode number match? */
356+
if (child_ino != sc->ip->i_ino) {
357+
xchk_fblock_xref_set_corrupt(sc, XFS_ATTR_FORK, 0);
358+
return 0;
359+
}
360+
361+
return 0;
362+
}
363+
364+
/* Try to grab a parent directory. */
365+
STATIC int
366+
xchk_parent_iget(
367+
struct xchk_pptrs *pp,
368+
const struct xfs_parent_rec *pptr,
369+
struct xfs_inode **dpp)
370+
{
371+
struct xfs_scrub *sc = pp->sc;
372+
struct xfs_inode *ip;
373+
xfs_ino_t parent_ino = be64_to_cpu(pptr->p_ino);
374+
int error;
375+
376+
/* Validate inode number. */
377+
error = xfs_dir_ino_validate(sc->mp, parent_ino);
378+
if (error) {
379+
xchk_fblock_set_corrupt(sc, XFS_ATTR_FORK, 0);
380+
return -ECANCELED;
381+
}
382+
383+
error = xchk_iget(sc, parent_ino, &ip);
384+
if (error == -EINVAL || error == -ENOENT) {
385+
xchk_fblock_set_corrupt(sc, XFS_ATTR_FORK, 0);
386+
return -ECANCELED;
387+
}
388+
if (!xchk_fblock_xref_process_error(sc, XFS_ATTR_FORK, 0, &error))
389+
return error;
390+
391+
/* The parent must be a directory. */
392+
if (!S_ISDIR(VFS_I(ip)->i_mode)) {
393+
xchk_fblock_xref_set_corrupt(sc, XFS_ATTR_FORK, 0);
394+
goto out_rele;
395+
}
396+
397+
/* Validate generation number. */
398+
if (VFS_I(ip)->i_generation != be32_to_cpu(pptr->p_gen)) {
399+
xchk_fblock_xref_set_corrupt(sc, XFS_ATTR_FORK, 0);
400+
goto out_rele;
401+
}
402+
403+
*dpp = ip;
404+
return 0;
405+
out_rele:
406+
xchk_irele(sc, ip);
407+
return 0;
408+
}
409+
410+
/*
411+
* Walk an xattr of a file. If this xattr is a parent pointer, follow it up
412+
* to a parent directory and check that the parent has a dirent pointing back
413+
* to us.
414+
*/
415+
STATIC int
416+
xchk_parent_scan_attr(
417+
struct xfs_scrub *sc,
418+
struct xfs_inode *ip,
419+
unsigned int attr_flags,
420+
const unsigned char *name,
421+
unsigned int namelen,
422+
const void *value,
423+
unsigned int valuelen,
424+
void *priv)
425+
{
426+
struct xfs_name xname = {
427+
.name = name,
428+
.len = namelen,
429+
};
430+
struct xchk_pptrs *pp = priv;
431+
struct xfs_inode *dp = NULL;
432+
const struct xfs_parent_rec *pptr_rec = value;
433+
xfs_ino_t parent_ino;
434+
unsigned int lockmode;
435+
int error;
436+
437+
if (!(attr_flags & XFS_ATTR_PARENT))
438+
return 0;
439+
440+
error = xfs_parent_from_attr(sc->mp, attr_flags, name, namelen, value,
441+
valuelen, &parent_ino, NULL);
442+
if (error) {
443+
xchk_fblock_set_corrupt(sc, XFS_ATTR_FORK, 0);
444+
return error;
445+
}
446+
447+
/* No self-referential parent pointers. */
448+
if (parent_ino == sc->ip->i_ino) {
449+
xchk_fblock_set_corrupt(sc, XFS_ATTR_FORK, 0);
450+
return -ECANCELED;
451+
}
452+
453+
pp->pptrs_found++;
454+
455+
error = xchk_parent_iget(pp, pptr_rec, &dp);
456+
if (error)
457+
return error;
458+
if (!dp)
459+
return 0;
460+
461+
/* Try to lock the inode. */
462+
lockmode = xchk_parent_lock_dir(sc, dp);
463+
if (!lockmode) {
464+
xchk_set_incomplete(sc);
465+
error = -ECANCELED;
466+
goto out_rele;
467+
}
468+
469+
error = xchk_parent_dirent(pp, &xname, dp);
470+
if (error)
471+
goto out_unlock;
472+
473+
out_unlock:
474+
xfs_iunlock(dp, lockmode);
475+
out_rele:
476+
xchk_irele(sc, dp);
477+
return error;
478+
}
479+
480+
/*
481+
* Compare the number of parent pointers to the link count. For
482+
* non-directories these should be the same. For unlinked directories the
483+
* count should be zero; for linked directories, it should be nonzero.
484+
*/
485+
STATIC int
486+
xchk_parent_count_pptrs(
487+
struct xchk_pptrs *pp)
488+
{
489+
struct xfs_scrub *sc = pp->sc;
490+
491+
if (S_ISDIR(VFS_I(sc->ip)->i_mode)) {
492+
if (sc->ip == sc->mp->m_rootip)
493+
pp->pptrs_found++;
494+
495+
if (VFS_I(sc->ip)->i_nlink == 0 && pp->pptrs_found > 0)
496+
xchk_ino_set_corrupt(sc, sc->ip->i_ino);
497+
else if (VFS_I(sc->ip)->i_nlink > 0 &&
498+
pp->pptrs_found == 0)
499+
xchk_ino_set_corrupt(sc, sc->ip->i_ino);
500+
} else {
501+
if (VFS_I(sc->ip)->i_nlink != pp->pptrs_found)
502+
xchk_ino_set_corrupt(sc, sc->ip->i_ino);
503+
}
504+
505+
return 0;
506+
}
507+
508+
/* Check parent pointers of a file. */
509+
STATIC int
510+
xchk_parent_pptr(
511+
struct xfs_scrub *sc)
512+
{
513+
struct xchk_pptrs *pp;
514+
int error;
515+
516+
pp = kvzalloc(sizeof(struct xchk_pptrs), XCHK_GFP_FLAGS);
517+
if (!pp)
518+
return -ENOMEM;
519+
pp->sc = sc;
520+
521+
error = xchk_xattr_walk(sc, sc->ip, xchk_parent_scan_attr, pp);
522+
if (error == -ECANCELED) {
523+
error = 0;
524+
goto out_pp;
525+
}
526+
if (error)
527+
goto out_pp;
528+
529+
if (pp->sc->sm->sm_flags & XFS_SCRUB_OFLAG_CORRUPT)
530+
goto out_pp;
531+
532+
/*
533+
* For subdirectories, make sure the dotdot entry references the same
534+
* inode as the parent pointers.
535+
*
536+
* If we're scanning a /consistent/ directory, there should only be
537+
* one parent pointer, and it should point to the same directory as
538+
* the dotdot entry.
539+
*
540+
* However, a corrupt directory tree might feature a subdirectory with
541+
* multiple parents. The directory loop scanner is responsible for
542+
* correcting that kind of problem, so for now we only validate that
543+
* the dotdot entry matches /one/ of the parents.
544+
*/
545+
if (S_ISDIR(VFS_I(sc->ip)->i_mode)) {
546+
error = xchk_parent_pptr_and_dotdot(pp);
547+
if (error)
548+
goto out_pp;
549+
}
550+
551+
if (pp->sc->sm->sm_flags & XFS_SCRUB_OFLAG_CORRUPT)
552+
goto out_pp;
553+
554+
/*
555+
* Complain if the number of parent pointers doesn't match the link
556+
* count. This could be a sign of missing parent pointers (or an
557+
* incorrect link count).
558+
*/
559+
error = xchk_parent_count_pptrs(pp);
560+
if (error)
561+
goto out_pp;
562+
563+
out_pp:
564+
kvfree(pp);
565+
return error;
566+
}
567+
200568
/* Scrub a parent pointer. */
201569
int
202570
xchk_parent(
@@ -206,6 +574,9 @@ xchk_parent(
206574
xfs_ino_t parent_ino;
207575
int error = 0;
208576

577+
if (xfs_has_parent(mp))
578+
return xchk_parent_pptr(sc);
579+
209580
/*
210581
* If we're a directory, check that the '..' link points up to
211582
* a directory that has one entry pointing to us.

0 commit comments

Comments
 (0)