diff options
author | Darrick J. Wong <darrick.wong@oracle.com> | 2018-08-10 01:43:02 -0400 |
---|---|---|
committer | Darrick J. Wong <darrick.wong@oracle.com> | 2018-08-10 14:44:31 -0400 |
commit | 0e93d3f43ec7d3308bff25ce1be81d46330168c9 (patch) | |
tree | cbbaff0933ffd871850e18032eb6015281c65c0c | |
parent | f9ed6debca45dd9bcc02d77c98822d50aba342f4 (diff) |
xfs: repair the AGFL
Repair the AGFL from the rmap data.
Signed-off-by: Darrick J. Wong <darrick.wong@oracle.com>
Reviewed-by: Brian Foster <bfoster@redhat.com>
-rw-r--r-- | fs/xfs/scrub/agheader_repair.c | 281 | ||||
-rw-r--r-- | fs/xfs/scrub/bitmap.c | 92 | ||||
-rw-r--r-- | fs/xfs/scrub/bitmap.h | 4 | ||||
-rw-r--r-- | fs/xfs/scrub/repair.h | 2 | ||||
-rw-r--r-- | fs/xfs/scrub/scrub.c | 2 |
5 files changed, 380 insertions, 1 deletions
diff --git a/fs/xfs/scrub/agheader_repair.c b/fs/xfs/scrub/agheader_repair.c index aa180492a4a5..9ce302360bbb 100644 --- a/fs/xfs/scrub/agheader_repair.c +++ b/fs/xfs/scrub/agheader_repair.c | |||
@@ -433,3 +433,284 @@ out_revert: | |||
433 | memcpy(agf, &old_agf, sizeof(old_agf)); | 433 | memcpy(agf, &old_agf, sizeof(old_agf)); |
434 | return error; | 434 | return error; |
435 | } | 435 | } |
436 | |||
437 | /* AGFL */ | ||
438 | |||
439 | struct xrep_agfl { | ||
440 | /* Bitmap of other OWN_AG metadata blocks. */ | ||
441 | struct xfs_bitmap agmetablocks; | ||
442 | |||
443 | /* Bitmap of free space. */ | ||
444 | struct xfs_bitmap *freesp; | ||
445 | |||
446 | struct xfs_scrub *sc; | ||
447 | }; | ||
448 | |||
449 | /* Record all OWN_AG (free space btree) information from the rmap data. */ | ||
450 | STATIC int | ||
451 | xrep_agfl_walk_rmap( | ||
452 | struct xfs_btree_cur *cur, | ||
453 | struct xfs_rmap_irec *rec, | ||
454 | void *priv) | ||
455 | { | ||
456 | struct xrep_agfl *ra = priv; | ||
457 | xfs_fsblock_t fsb; | ||
458 | int error = 0; | ||
459 | |||
460 | if (xchk_should_terminate(ra->sc, &error)) | ||
461 | return error; | ||
462 | |||
463 | /* Record all the OWN_AG blocks. */ | ||
464 | if (rec->rm_owner == XFS_RMAP_OWN_AG) { | ||
465 | fsb = XFS_AGB_TO_FSB(cur->bc_mp, cur->bc_private.a.agno, | ||
466 | rec->rm_startblock); | ||
467 | error = xfs_bitmap_set(ra->freesp, fsb, rec->rm_blockcount); | ||
468 | if (error) | ||
469 | return error; | ||
470 | } | ||
471 | |||
472 | return xfs_bitmap_set_btcur_path(&ra->agmetablocks, cur); | ||
473 | } | ||
474 | |||
475 | /* | ||
476 | * Map out all the non-AGFL OWN_AG space in this AG so that we can deduce | ||
477 | * which blocks belong to the AGFL. | ||
478 | * | ||
479 | * Compute the set of old AGFL blocks by subtracting from the list of OWN_AG | ||
480 | * blocks the list of blocks owned by all other OWN_AG metadata (bnobt, cntbt, | ||
481 | * rmapbt). These are the old AGFL blocks, so return that list and the number | ||
482 | * of blocks we're actually going to put back on the AGFL. | ||
483 | */ | ||
484 | STATIC int | ||
485 | xrep_agfl_collect_blocks( | ||
486 | struct xfs_scrub *sc, | ||
487 | struct xfs_buf *agf_bp, | ||
488 | struct xfs_bitmap *agfl_extents, | ||
489 | xfs_agblock_t *flcount) | ||
490 | { | ||
491 | struct xrep_agfl ra; | ||
492 | struct xfs_mount *mp = sc->mp; | ||
493 | struct xfs_btree_cur *cur; | ||
494 | struct xfs_bitmap_range *br; | ||
495 | struct xfs_bitmap_range *n; | ||
496 | int error; | ||
497 | |||
498 | ra.sc = sc; | ||
499 | ra.freesp = agfl_extents; | ||
500 | xfs_bitmap_init(&ra.agmetablocks); | ||
501 | |||
502 | /* Find all space used by the free space btrees & rmapbt. */ | ||
503 | cur = xfs_rmapbt_init_cursor(mp, sc->tp, agf_bp, sc->sa.agno); | ||
504 | error = xfs_rmap_query_all(cur, xrep_agfl_walk_rmap, &ra); | ||
505 | if (error) | ||
506 | goto err; | ||
507 | xfs_btree_del_cursor(cur, error); | ||
508 | |||
509 | /* Find all blocks currently being used by the bnobt. */ | ||
510 | cur = xfs_allocbt_init_cursor(mp, sc->tp, agf_bp, sc->sa.agno, | ||
511 | XFS_BTNUM_BNO); | ||
512 | error = xfs_bitmap_set_btblocks(&ra.agmetablocks, cur); | ||
513 | if (error) | ||
514 | goto err; | ||
515 | xfs_btree_del_cursor(cur, error); | ||
516 | |||
517 | /* Find all blocks currently being used by the cntbt. */ | ||
518 | cur = xfs_allocbt_init_cursor(mp, sc->tp, agf_bp, sc->sa.agno, | ||
519 | XFS_BTNUM_CNT); | ||
520 | error = xfs_bitmap_set_btblocks(&ra.agmetablocks, cur); | ||
521 | if (error) | ||
522 | goto err; | ||
523 | |||
524 | xfs_btree_del_cursor(cur, error); | ||
525 | |||
526 | /* | ||
527 | * Drop the freesp meta blocks that are in use by btrees. | ||
528 | * The remaining blocks /should/ be AGFL blocks. | ||
529 | */ | ||
530 | error = xfs_bitmap_disunion(agfl_extents, &ra.agmetablocks); | ||
531 | xfs_bitmap_destroy(&ra.agmetablocks); | ||
532 | if (error) | ||
533 | return error; | ||
534 | |||
535 | /* | ||
536 | * Calculate the new AGFL size. If we found more blocks than fit in | ||
537 | * the AGFL we'll free them later. | ||
538 | */ | ||
539 | *flcount = 0; | ||
540 | for_each_xfs_bitmap_extent(br, n, agfl_extents) { | ||
541 | *flcount += br->len; | ||
542 | if (*flcount > xfs_agfl_size(mp)) | ||
543 | break; | ||
544 | } | ||
545 | if (*flcount > xfs_agfl_size(mp)) | ||
546 | *flcount = xfs_agfl_size(mp); | ||
547 | return 0; | ||
548 | |||
549 | err: | ||
550 | xfs_bitmap_destroy(&ra.agmetablocks); | ||
551 | xfs_btree_del_cursor(cur, error); | ||
552 | return error; | ||
553 | } | ||
554 | |||
555 | /* Update the AGF and reset the in-core state. */ | ||
556 | STATIC void | ||
557 | xrep_agfl_update_agf( | ||
558 | struct xfs_scrub *sc, | ||
559 | struct xfs_buf *agf_bp, | ||
560 | xfs_agblock_t flcount) | ||
561 | { | ||
562 | struct xfs_agf *agf = XFS_BUF_TO_AGF(agf_bp); | ||
563 | |||
564 | ASSERT(flcount <= xfs_agfl_size(sc->mp)); | ||
565 | |||
566 | /* Trigger fdblocks recalculation */ | ||
567 | xfs_force_summary_recalc(sc->mp); | ||
568 | |||
569 | /* Update the AGF counters. */ | ||
570 | if (sc->sa.pag->pagf_init) | ||
571 | sc->sa.pag->pagf_flcount = flcount; | ||
572 | agf->agf_flfirst = cpu_to_be32(0); | ||
573 | agf->agf_flcount = cpu_to_be32(flcount); | ||
574 | agf->agf_fllast = cpu_to_be32(flcount - 1); | ||
575 | |||
576 | xfs_alloc_log_agf(sc->tp, agf_bp, | ||
577 | XFS_AGF_FLFIRST | XFS_AGF_FLLAST | XFS_AGF_FLCOUNT); | ||
578 | } | ||
579 | |||
580 | /* Write out a totally new AGFL. */ | ||
581 | STATIC void | ||
582 | xrep_agfl_init_header( | ||
583 | struct xfs_scrub *sc, | ||
584 | struct xfs_buf *agfl_bp, | ||
585 | struct xfs_bitmap *agfl_extents, | ||
586 | xfs_agblock_t flcount) | ||
587 | { | ||
588 | struct xfs_mount *mp = sc->mp; | ||
589 | __be32 *agfl_bno; | ||
590 | struct xfs_bitmap_range *br; | ||
591 | struct xfs_bitmap_range *n; | ||
592 | struct xfs_agfl *agfl; | ||
593 | xfs_agblock_t agbno; | ||
594 | unsigned int fl_off; | ||
595 | |||
596 | ASSERT(flcount <= xfs_agfl_size(mp)); | ||
597 | |||
598 | /* | ||
599 | * Start rewriting the header by setting the bno[] array to | ||
600 | * NULLAGBLOCK, then setting AGFL header fields. | ||
601 | */ | ||
602 | agfl = XFS_BUF_TO_AGFL(agfl_bp); | ||
603 | memset(agfl, 0xFF, BBTOB(agfl_bp->b_length)); | ||
604 | agfl->agfl_magicnum = cpu_to_be32(XFS_AGFL_MAGIC); | ||
605 | agfl->agfl_seqno = cpu_to_be32(sc->sa.agno); | ||
606 | uuid_copy(&agfl->agfl_uuid, &mp->m_sb.sb_meta_uuid); | ||
607 | |||
608 | /* | ||
609 | * Fill the AGFL with the remaining blocks. If agfl_extents has more | ||
610 | * blocks than fit in the AGFL, they will be freed in a subsequent | ||
611 | * step. | ||
612 | */ | ||
613 | fl_off = 0; | ||
614 | agfl_bno = XFS_BUF_TO_AGFL_BNO(mp, agfl_bp); | ||
615 | for_each_xfs_bitmap_extent(br, n, agfl_extents) { | ||
616 | agbno = XFS_FSB_TO_AGBNO(mp, br->start); | ||
617 | |||
618 | trace_xrep_agfl_insert(mp, sc->sa.agno, agbno, br->len); | ||
619 | |||
620 | while (br->len > 0 && fl_off < flcount) { | ||
621 | agfl_bno[fl_off] = cpu_to_be32(agbno); | ||
622 | fl_off++; | ||
623 | agbno++; | ||
624 | |||
625 | /* | ||
626 | * We've now used br->start by putting it in the AGFL, | ||
627 | * so bump br so that we don't reap the block later. | ||
628 | */ | ||
629 | br->start++; | ||
630 | br->len--; | ||
631 | } | ||
632 | |||
633 | if (br->len) | ||
634 | break; | ||
635 | list_del(&br->list); | ||
636 | kmem_free(br); | ||
637 | } | ||
638 | |||
639 | /* Write new AGFL to disk. */ | ||
640 | xfs_trans_buf_set_type(sc->tp, agfl_bp, XFS_BLFT_AGFL_BUF); | ||
641 | xfs_trans_log_buf(sc->tp, agfl_bp, 0, BBTOB(agfl_bp->b_length) - 1); | ||
642 | } | ||
643 | |||
644 | /* Repair the AGFL. */ | ||
645 | int | ||
646 | xrep_agfl( | ||
647 | struct xfs_scrub *sc) | ||
648 | { | ||
649 | struct xfs_owner_info oinfo; | ||
650 | struct xfs_bitmap agfl_extents; | ||
651 | struct xfs_mount *mp = sc->mp; | ||
652 | struct xfs_buf *agf_bp; | ||
653 | struct xfs_buf *agfl_bp; | ||
654 | xfs_agblock_t flcount; | ||
655 | int error; | ||
656 | |||
657 | /* We require the rmapbt to rebuild anything. */ | ||
658 | if (!xfs_sb_version_hasrmapbt(&mp->m_sb)) | ||
659 | return -EOPNOTSUPP; | ||
660 | |||
661 | xchk_perag_get(sc->mp, &sc->sa); | ||
662 | xfs_bitmap_init(&agfl_extents); | ||
663 | |||
664 | /* | ||
665 | * Read the AGF so that we can query the rmapbt. We hope that there's | ||
666 | * nothing wrong with the AGF, but all the AG header repair functions | ||
667 | * have this chicken-and-egg problem. | ||
668 | */ | ||
669 | error = xfs_alloc_read_agf(mp, sc->tp, sc->sa.agno, 0, &agf_bp); | ||
670 | if (error) | ||
671 | return error; | ||
672 | if (!agf_bp) | ||
673 | return -ENOMEM; | ||
674 | |||
675 | /* | ||
676 | * Make sure we have the AGFL buffer, as scrub might have decided it | ||
677 | * was corrupt after xfs_alloc_read_agfl failed with -EFSCORRUPTED. | ||
678 | */ | ||
679 | error = xfs_trans_read_buf(mp, sc->tp, mp->m_ddev_targp, | ||
680 | XFS_AG_DADDR(mp, sc->sa.agno, XFS_AGFL_DADDR(mp)), | ||
681 | XFS_FSS_TO_BB(mp, 1), 0, &agfl_bp, NULL); | ||
682 | if (error) | ||
683 | return error; | ||
684 | agfl_bp->b_ops = &xfs_agfl_buf_ops; | ||
685 | |||
686 | /* Gather all the extents we're going to put on the new AGFL. */ | ||
687 | error = xrep_agfl_collect_blocks(sc, agf_bp, &agfl_extents, &flcount); | ||
688 | if (error) | ||
689 | goto err; | ||
690 | |||
691 | /* | ||
692 | * Update AGF and AGFL. We reset the global free block counter when | ||
693 | * we adjust the AGF flcount (which can fail) so avoid updating any | ||
694 | * buffers until we know that part works. | ||
695 | */ | ||
696 | xrep_agfl_update_agf(sc, agf_bp, flcount); | ||
697 | xrep_agfl_init_header(sc, agfl_bp, &agfl_extents, flcount); | ||
698 | |||
699 | /* | ||
700 | * Ok, the AGFL should be ready to go now. Roll the transaction to | ||
701 | * make the new AGFL permanent before we start using it to return | ||
702 | * freespace overflow to the freespace btrees. | ||
703 | */ | ||
704 | sc->sa.agf_bp = agf_bp; | ||
705 | sc->sa.agfl_bp = agfl_bp; | ||
706 | error = xrep_roll_ag_trans(sc); | ||
707 | if (error) | ||
708 | goto err; | ||
709 | |||
710 | /* Dump any AGFL overflow. */ | ||
711 | xfs_rmap_ag_owner(&oinfo, XFS_RMAP_OWN_AG); | ||
712 | return xrep_reap_extents(sc, &agfl_extents, &oinfo, XFS_AG_RESV_AGFL); | ||
713 | err: | ||
714 | xfs_bitmap_destroy(&agfl_extents); | ||
715 | return error; | ||
716 | } | ||
diff --git a/fs/xfs/scrub/bitmap.c b/fs/xfs/scrub/bitmap.c index c770e2d0b6aa..fdadc9e1dc49 100644 --- a/fs/xfs/scrub/bitmap.c +++ b/fs/xfs/scrub/bitmap.c | |||
@@ -9,6 +9,7 @@ | |||
9 | #include "xfs_format.h" | 9 | #include "xfs_format.h" |
10 | #include "xfs_trans_resv.h" | 10 | #include "xfs_trans_resv.h" |
11 | #include "xfs_mount.h" | 11 | #include "xfs_mount.h" |
12 | #include "xfs_btree.h" | ||
12 | #include "scrub/xfs_scrub.h" | 13 | #include "scrub/xfs_scrub.h" |
13 | #include "scrub/scrub.h" | 14 | #include "scrub/scrub.h" |
14 | #include "scrub/common.h" | 15 | #include "scrub/common.h" |
@@ -209,3 +210,94 @@ out: | |||
209 | } | 210 | } |
210 | #undef LEFT_ALIGNED | 211 | #undef LEFT_ALIGNED |
211 | #undef RIGHT_ALIGNED | 212 | #undef RIGHT_ALIGNED |
213 | |||
214 | /* | ||
215 | * Record all btree blocks seen while iterating all records of a btree. | ||
216 | * | ||
217 | * We know that the btree query_all function starts at the left edge and walks | ||
218 | * towards the right edge of the tree. Therefore, we know that we can walk up | ||
219 | * the btree cursor towards the root; if the pointer for a given level points | ||
220 | * to the first record/key in that block, we haven't seen this block before; | ||
221 | * and therefore we need to remember that we saw this block in the btree. | ||
222 | * | ||
223 | * So if our btree is: | ||
224 | * | ||
225 | * 4 | ||
226 | * / | \ | ||
227 | * 1 2 3 | ||
228 | * | ||
229 | * Pretend for this example that each leaf block has 100 btree records. For | ||
230 | * the first btree record, we'll observe that bc_ptrs[0] == 1, so we record | ||
231 | * that we saw block 1. Then we observe that bc_ptrs[1] == 1, so we record | ||
232 | * block 4. The list is [1, 4]. | ||
233 | * | ||
234 | * For the second btree record, we see that bc_ptrs[0] == 2, so we exit the | ||
235 | * loop. The list remains [1, 4]. | ||
236 | * | ||
237 | * For the 101st btree record, we've moved onto leaf block 2. Now | ||
238 | * bc_ptrs[0] == 1 again, so we record that we saw block 2. We see that | ||
239 | * bc_ptrs[1] == 2, so we exit the loop. The list is now [1, 4, 2]. | ||
240 | * | ||
241 | * For the 102nd record, bc_ptrs[0] == 2, so we continue. | ||
242 | * | ||
243 | * For the 201st record, we've moved on to leaf block 3. bc_ptrs[0] == 1, so | ||
244 | * we add 3 to the list. Now it is [1, 4, 2, 3]. | ||
245 | * | ||
246 | * For the 300th record we just exit, with the list being [1, 4, 2, 3]. | ||
247 | */ | ||
248 | |||
249 | /* | ||
250 | * Record all the buffers pointed to by the btree cursor. Callers already | ||
251 | * engaged in a btree walk should call this function to capture the list of | ||
252 | * blocks going from the leaf towards the root. | ||
253 | */ | ||
254 | int | ||
255 | xfs_bitmap_set_btcur_path( | ||
256 | struct xfs_bitmap *bitmap, | ||
257 | struct xfs_btree_cur *cur) | ||
258 | { | ||
259 | struct xfs_buf *bp; | ||
260 | xfs_fsblock_t fsb; | ||
261 | int i; | ||
262 | int error; | ||
263 | |||
264 | for (i = 0; i < cur->bc_nlevels && cur->bc_ptrs[i] == 1; i++) { | ||
265 | xfs_btree_get_block(cur, i, &bp); | ||
266 | if (!bp) | ||
267 | continue; | ||
268 | fsb = XFS_DADDR_TO_FSB(cur->bc_mp, bp->b_bn); | ||
269 | error = xfs_bitmap_set(bitmap, fsb, 1); | ||
270 | if (error) | ||
271 | return error; | ||
272 | } | ||
273 | |||
274 | return 0; | ||
275 | } | ||
276 | |||
277 | /* Collect a btree's block in the bitmap. */ | ||
278 | STATIC int | ||
279 | xfs_bitmap_collect_btblock( | ||
280 | struct xfs_btree_cur *cur, | ||
281 | int level, | ||
282 | void *priv) | ||
283 | { | ||
284 | struct xfs_bitmap *bitmap = priv; | ||
285 | struct xfs_buf *bp; | ||
286 | xfs_fsblock_t fsbno; | ||
287 | |||
288 | xfs_btree_get_block(cur, level, &bp); | ||
289 | if (!bp) | ||
290 | return 0; | ||
291 | |||
292 | fsbno = XFS_DADDR_TO_FSB(cur->bc_mp, bp->b_bn); | ||
293 | return xfs_bitmap_set(bitmap, fsbno, 1); | ||
294 | } | ||
295 | |||
296 | /* Walk the btree and mark the bitmap wherever a btree block is found. */ | ||
297 | int | ||
298 | xfs_bitmap_set_btblocks( | ||
299 | struct xfs_bitmap *bitmap, | ||
300 | struct xfs_btree_cur *cur) | ||
301 | { | ||
302 | return xfs_btree_visit_blocks(cur, xfs_bitmap_collect_btblock, bitmap); | ||
303 | } | ||
diff --git a/fs/xfs/scrub/bitmap.h b/fs/xfs/scrub/bitmap.h index dad652ee9177..ae8ecbce6fa6 100644 --- a/fs/xfs/scrub/bitmap.h +++ b/fs/xfs/scrub/bitmap.h | |||
@@ -28,5 +28,9 @@ void xfs_bitmap_destroy(struct xfs_bitmap *bitmap); | |||
28 | 28 | ||
29 | int xfs_bitmap_set(struct xfs_bitmap *bitmap, uint64_t start, uint64_t len); | 29 | int xfs_bitmap_set(struct xfs_bitmap *bitmap, uint64_t start, uint64_t len); |
30 | int xfs_bitmap_disunion(struct xfs_bitmap *bitmap, struct xfs_bitmap *sub); | 30 | int xfs_bitmap_disunion(struct xfs_bitmap *bitmap, struct xfs_bitmap *sub); |
31 | int xfs_bitmap_set_btcur_path(struct xfs_bitmap *bitmap, | ||
32 | struct xfs_btree_cur *cur); | ||
33 | int xfs_bitmap_set_btblocks(struct xfs_bitmap *bitmap, | ||
34 | struct xfs_btree_cur *cur); | ||
31 | 35 | ||
32 | #endif /* __XFS_SCRUB_BITMAP_H__ */ | 36 | #endif /* __XFS_SCRUB_BITMAP_H__ */ |
diff --git a/fs/xfs/scrub/repair.h b/fs/xfs/scrub/repair.h index 6f0903c51a47..1d283360b5ab 100644 --- a/fs/xfs/scrub/repair.h +++ b/fs/xfs/scrub/repair.h | |||
@@ -59,6 +59,7 @@ int xrep_ino_dqattach(struct xfs_scrub *sc); | |||
59 | int xrep_probe(struct xfs_scrub *sc); | 59 | int xrep_probe(struct xfs_scrub *sc); |
60 | int xrep_superblock(struct xfs_scrub *sc); | 60 | int xrep_superblock(struct xfs_scrub *sc); |
61 | int xrep_agf(struct xfs_scrub *sc); | 61 | int xrep_agf(struct xfs_scrub *sc); |
62 | int xrep_agfl(struct xfs_scrub *sc); | ||
62 | 63 | ||
63 | #else | 64 | #else |
64 | 65 | ||
@@ -83,6 +84,7 @@ xrep_calc_ag_resblks( | |||
83 | #define xrep_probe xrep_notsupported | 84 | #define xrep_probe xrep_notsupported |
84 | #define xrep_superblock xrep_notsupported | 85 | #define xrep_superblock xrep_notsupported |
85 | #define xrep_agf xrep_notsupported | 86 | #define xrep_agf xrep_notsupported |
87 | #define xrep_agfl xrep_notsupported | ||
86 | 88 | ||
87 | #endif /* CONFIG_XFS_ONLINE_REPAIR */ | 89 | #endif /* CONFIG_XFS_ONLINE_REPAIR */ |
88 | 90 | ||
diff --git a/fs/xfs/scrub/scrub.c b/fs/xfs/scrub/scrub.c index 1e8a17c8e2b9..2670f4cf62f4 100644 --- a/fs/xfs/scrub/scrub.c +++ b/fs/xfs/scrub/scrub.c | |||
@@ -220,7 +220,7 @@ static const struct xchk_meta_ops meta_scrub_ops[] = { | |||
220 | .type = ST_PERAG, | 220 | .type = ST_PERAG, |
221 | .setup = xchk_setup_fs, | 221 | .setup = xchk_setup_fs, |
222 | .scrub = xchk_agfl, | 222 | .scrub = xchk_agfl, |
223 | .repair = xrep_notsupported, | 223 | .repair = xrep_agfl, |
224 | }, | 224 | }, |
225 | [XFS_SCRUB_TYPE_AGI] = { /* agi */ | 225 | [XFS_SCRUB_TYPE_AGI] = { /* agi */ |
226 | .type = ST_PERAG, | 226 | .type = ST_PERAG, |