Skip to content

Commit ab9d5dc

Browse files
committed
xfs: scrub AGF and AGFL
Check the block references in the AGF and AGFL headers to make sure they make sense. Signed-off-by: Darrick J. Wong <darrick.wong@oracle.com> Reviewed-by: Dave Chinner <dchinner@redhat.com>
1 parent 21fb4cb commit ab9d5dc

File tree

6 files changed

+223
-7
lines changed

6 files changed

+223
-7
lines changed

fs/xfs/libxfs/xfs_fs.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -485,9 +485,11 @@ struct xfs_scrub_metadata {
485485
/* Scrub subcommands. */
486486
#define XFS_SCRUB_TYPE_PROBE 0 /* presence test ioctl */
487487
#define XFS_SCRUB_TYPE_SB 1 /* superblock */
488+
#define XFS_SCRUB_TYPE_AGF 2 /* AG free header */
489+
#define XFS_SCRUB_TYPE_AGFL 3 /* AG free list */
488490

489491
/* Number of scrub subcommands. */
490-
#define XFS_SCRUB_TYPE_NR 2
492+
#define XFS_SCRUB_TYPE_NR 4
491493

492494
/* i: Repair this metadata. */
493495
#define XFS_SCRUB_IFLAG_REPAIR (1 << 0)

fs/xfs/scrub/agheader.c

Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
#include "xfs_trans.h"
3131
#include "xfs_sb.h"
3232
#include "xfs_inode.h"
33+
#include "xfs_alloc.h"
3334
#include "scrub/xfs_scrub.h"
3435
#include "scrub/scrub.h"
3536
#include "scrub/common.h"
@@ -52,6 +53,65 @@ xfs_scrub_setup_ag_header(
5253
return xfs_scrub_setup_fs(sc, ip);
5354
}
5455

56+
/* Walk all the blocks in the AGFL. */
57+
int
58+
xfs_scrub_walk_agfl(
59+
struct xfs_scrub_context *sc,
60+
int (*fn)(struct xfs_scrub_context *,
61+
xfs_agblock_t bno, void *),
62+
void *priv)
63+
{
64+
struct xfs_agf *agf;
65+
__be32 *agfl_bno;
66+
struct xfs_mount *mp = sc->mp;
67+
unsigned int flfirst;
68+
unsigned int fllast;
69+
int i;
70+
int error;
71+
72+
agf = XFS_BUF_TO_AGF(sc->sa.agf_bp);
73+
agfl_bno = XFS_BUF_TO_AGFL_BNO(mp, sc->sa.agfl_bp);
74+
flfirst = be32_to_cpu(agf->agf_flfirst);
75+
fllast = be32_to_cpu(agf->agf_fllast);
76+
77+
/* Nothing to walk in an empty AGFL. */
78+
if (agf->agf_flcount == cpu_to_be32(0))
79+
return 0;
80+
81+
/* first to last is a consecutive list. */
82+
if (fllast >= flfirst) {
83+
for (i = flfirst; i <= fllast; i++) {
84+
error = fn(sc, be32_to_cpu(agfl_bno[i]), priv);
85+
if (error)
86+
return error;
87+
if (sc->sm->sm_flags & XFS_SCRUB_OFLAG_CORRUPT)
88+
return error;
89+
}
90+
91+
return 0;
92+
}
93+
94+
/* first to the end */
95+
for (i = flfirst; i < XFS_AGFL_SIZE(mp); i++) {
96+
error = fn(sc, be32_to_cpu(agfl_bno[i]), priv);
97+
if (error)
98+
return error;
99+
if (sc->sm->sm_flags & XFS_SCRUB_OFLAG_CORRUPT)
100+
return error;
101+
}
102+
103+
/* the start to last. */
104+
for (i = 0; i <= fllast; i++) {
105+
error = fn(sc, be32_to_cpu(agfl_bno[i]), priv);
106+
if (error)
107+
return error;
108+
if (sc->sm->sm_flags & XFS_SCRUB_OFLAG_CORRUPT)
109+
return error;
110+
}
111+
112+
return 0;
113+
}
114+
55115
/* Superblock */
56116

57117
/*
@@ -328,3 +388,127 @@ xfs_scrub_superblock(
328388

329389
return error;
330390
}
391+
392+
/* AGF */
393+
394+
/* Scrub the AGF. */
395+
int
396+
xfs_scrub_agf(
397+
struct xfs_scrub_context *sc)
398+
{
399+
struct xfs_mount *mp = sc->mp;
400+
struct xfs_agf *agf;
401+
xfs_agnumber_t agno;
402+
xfs_agblock_t agbno;
403+
xfs_agblock_t eoag;
404+
xfs_agblock_t agfl_first;
405+
xfs_agblock_t agfl_last;
406+
xfs_agblock_t agfl_count;
407+
xfs_agblock_t fl_count;
408+
int level;
409+
int error = 0;
410+
411+
agno = sc->sa.agno = sc->sm->sm_agno;
412+
error = xfs_scrub_ag_read_headers(sc, agno, &sc->sa.agi_bp,
413+
&sc->sa.agf_bp, &sc->sa.agfl_bp);
414+
if (!xfs_scrub_process_error(sc, agno, XFS_AGF_BLOCK(sc->mp), &error))
415+
goto out;
416+
417+
agf = XFS_BUF_TO_AGF(sc->sa.agf_bp);
418+
419+
/* Check the AG length */
420+
eoag = be32_to_cpu(agf->agf_length);
421+
if (eoag != xfs_ag_block_count(mp, agno))
422+
xfs_scrub_block_set_corrupt(sc, sc->sa.agf_bp);
423+
424+
/* Check the AGF btree roots and levels */
425+
agbno = be32_to_cpu(agf->agf_roots[XFS_BTNUM_BNO]);
426+
if (!xfs_verify_agbno(mp, agno, agbno))
427+
xfs_scrub_block_set_corrupt(sc, sc->sa.agf_bp);
428+
429+
agbno = be32_to_cpu(agf->agf_roots[XFS_BTNUM_CNT]);
430+
if (!xfs_verify_agbno(mp, agno, agbno))
431+
xfs_scrub_block_set_corrupt(sc, sc->sa.agf_bp);
432+
433+
level = be32_to_cpu(agf->agf_levels[XFS_BTNUM_BNO]);
434+
if (level <= 0 || level > XFS_BTREE_MAXLEVELS)
435+
xfs_scrub_block_set_corrupt(sc, sc->sa.agf_bp);
436+
437+
level = be32_to_cpu(agf->agf_levels[XFS_BTNUM_CNT]);
438+
if (level <= 0 || level > XFS_BTREE_MAXLEVELS)
439+
xfs_scrub_block_set_corrupt(sc, sc->sa.agf_bp);
440+
441+
if (xfs_sb_version_hasrmapbt(&mp->m_sb)) {
442+
agbno = be32_to_cpu(agf->agf_roots[XFS_BTNUM_RMAP]);
443+
if (!xfs_verify_agbno(mp, agno, agbno))
444+
xfs_scrub_block_set_corrupt(sc, sc->sa.agf_bp);
445+
446+
level = be32_to_cpu(agf->agf_levels[XFS_BTNUM_RMAP]);
447+
if (level <= 0 || level > XFS_BTREE_MAXLEVELS)
448+
xfs_scrub_block_set_corrupt(sc, sc->sa.agf_bp);
449+
}
450+
451+
if (xfs_sb_version_hasreflink(&mp->m_sb)) {
452+
agbno = be32_to_cpu(agf->agf_refcount_root);
453+
if (!xfs_verify_agbno(mp, agno, agbno))
454+
xfs_scrub_block_set_corrupt(sc, sc->sa.agf_bp);
455+
456+
level = be32_to_cpu(agf->agf_refcount_level);
457+
if (level <= 0 || level > XFS_BTREE_MAXLEVELS)
458+
xfs_scrub_block_set_corrupt(sc, sc->sa.agf_bp);
459+
}
460+
461+
/* Check the AGFL counters */
462+
agfl_first = be32_to_cpu(agf->agf_flfirst);
463+
agfl_last = be32_to_cpu(agf->agf_fllast);
464+
agfl_count = be32_to_cpu(agf->agf_flcount);
465+
if (agfl_last > agfl_first)
466+
fl_count = agfl_last - agfl_first + 1;
467+
else
468+
fl_count = XFS_AGFL_SIZE(mp) - agfl_first + agfl_last + 1;
469+
if (agfl_count != 0 && fl_count != agfl_count)
470+
xfs_scrub_block_set_corrupt(sc, sc->sa.agf_bp);
471+
472+
out:
473+
return error;
474+
}
475+
476+
/* AGFL */
477+
478+
/* Scrub an AGFL block. */
479+
STATIC int
480+
xfs_scrub_agfl_block(
481+
struct xfs_scrub_context *sc,
482+
xfs_agblock_t agbno,
483+
void *priv)
484+
{
485+
struct xfs_mount *mp = sc->mp;
486+
xfs_agnumber_t agno = sc->sa.agno;
487+
488+
if (!xfs_verify_agbno(mp, agno, agbno))
489+
xfs_scrub_block_set_corrupt(sc, sc->sa.agfl_bp);
490+
491+
return 0;
492+
}
493+
494+
/* Scrub the AGFL. */
495+
int
496+
xfs_scrub_agfl(
497+
struct xfs_scrub_context *sc)
498+
{
499+
xfs_agnumber_t agno;
500+
int error;
501+
502+
agno = sc->sa.agno = sc->sm->sm_agno;
503+
error = xfs_scrub_ag_read_headers(sc, agno, &sc->sa.agi_bp,
504+
&sc->sa.agf_bp, &sc->sa.agfl_bp);
505+
if (!xfs_scrub_process_error(sc, agno, XFS_AGFL_BLOCK(sc->mp), &error))
506+
goto out;
507+
if (!sc->sa.agf_bp)
508+
return -EFSCORRUPTED;
509+
510+
/* Check the blocks in the AGFL. */
511+
return xfs_scrub_walk_agfl(sc, xfs_scrub_agfl_block, NULL);
512+
out:
513+
return error;
514+
}

fs/xfs/scrub/common.c

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,26 @@ xfs_scrub_set_incomplete(
246246
* cleaning everything up once we're through.
247247
*/
248248

249+
/* Decide if we want to return an AG header read failure. */
250+
static inline bool
251+
want_ag_read_header_failure(
252+
struct xfs_scrub_context *sc,
253+
unsigned int type)
254+
{
255+
/* Return all AG header read failures when scanning btrees. */
256+
if (sc->sm->sm_type != XFS_SCRUB_TYPE_AGF &&
257+
sc->sm->sm_type != XFS_SCRUB_TYPE_AGFL)
258+
return true;
259+
/*
260+
* If we're scanning a given type of AG header, we only want to
261+
* see read failures from that specific header. We'd like the
262+
* other headers to cross-check them, but this isn't required.
263+
*/
264+
if (sc->sm->sm_type == type)
265+
return true;
266+
return false;
267+
}
268+
249269
/*
250270
* Grab all the headers for an AG.
251271
*
@@ -269,15 +289,11 @@ xfs_scrub_ag_read_headers(
269289
goto out;
270290

271291
error = xfs_alloc_read_agf(mp, sc->tp, agno, 0, agf);
272-
if (error)
273-
goto out;
274-
if (!*agf) {
275-
error = -ENOMEM;
292+
if (error && want_ag_read_header_failure(sc, XFS_SCRUB_TYPE_AGF))
276293
goto out;
277-
}
278294

279295
error = xfs_alloc_read_agfl(mp, sc->tp, agno, agfl);
280-
if (error)
296+
if (error && want_ag_read_header_failure(sc, XFS_SCRUB_TYPE_AGFL))
281297
goto out;
282298

283299
out:

fs/xfs/scrub/common.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,5 +88,9 @@ int xfs_scrub_ag_read_headers(struct xfs_scrub_context *sc, xfs_agnumber_t agno,
8888
void xfs_scrub_ag_btcur_free(struct xfs_scrub_ag *sa);
8989
int xfs_scrub_ag_btcur_init(struct xfs_scrub_context *sc,
9090
struct xfs_scrub_ag *sa);
91+
int xfs_scrub_walk_agfl(struct xfs_scrub_context *sc,
92+
int (*fn)(struct xfs_scrub_context *, xfs_agblock_t bno,
93+
void *),
94+
void *priv);
9195

9296
#endif /* __XFS_SCRUB_COMMON_H__ */

fs/xfs/scrub/scrub.c

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,14 @@ static const struct xfs_scrub_meta_ops meta_scrub_ops[] = {
162162
.setup = xfs_scrub_setup_ag_header,
163163
.scrub = xfs_scrub_superblock,
164164
},
165+
{ /* agf */
166+
.setup = xfs_scrub_setup_ag_header,
167+
.scrub = xfs_scrub_agf,
168+
},
169+
{ /* agfl */
170+
.setup = xfs_scrub_setup_ag_header,
171+
.scrub = xfs_scrub_agfl,
172+
},
165173
};
166174

167175
/* This isn't a stable feature, warn once per day. */

fs/xfs/scrub/scrub.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,5 +68,7 @@ struct xfs_scrub_context {
6868
/* Metadata scrubbers */
6969
int xfs_scrub_tester(struct xfs_scrub_context *sc);
7070
int xfs_scrub_superblock(struct xfs_scrub_context *sc);
71+
int xfs_scrub_agf(struct xfs_scrub_context *sc);
72+
int xfs_scrub_agfl(struct xfs_scrub_context *sc);
7173

7274
#endif /* __XFS_SCRUB_SCRUB_H__ */

0 commit comments

Comments
 (0)