| // SPDX-License-Identifier: GPL-2.0-or-later |
| /* |
| * Copyright (C) 2022-2023 Oracle. All Rights Reserved. |
| * Author: Darrick J. Wong <djwong@kernel.org> |
| */ |
| #include "xfs.h" |
| #include "xfs_fs.h" |
| #include "xfs_shared.h" |
| #include "xfs_format.h" |
| #include "xfs_trans_resv.h" |
| #include "xfs_mount.h" |
| #include "xfs_btree.h" |
| #include "xfs_btree_staging.h" |
| #include "xfs_log_format.h" |
| #include "xfs_trans.h" |
| #include "xfs_sb.h" |
| #include "xfs_inode.h" |
| #include "xfs_alloc.h" |
| #include "xfs_rmap.h" |
| #include "xfs_ag.h" |
| #include "xfs_defer.h" |
| #include "scrub/scrub.h" |
| #include "scrub/common.h" |
| #include "scrub/trace.h" |
| #include "scrub/repair.h" |
| #include "scrub/newbt.h" |
| |
| /* |
| * Estimate proper slack values for a btree that's being reloaded. |
| * |
| * Under most circumstances, we'll take whatever default loading value the |
| * btree bulk loading code calculates for us. However, there are some |
| * exceptions to this rule: |
| * |
| * (0) If someone turned one of the debug knobs. |
| * (1) If this is a per-AG btree and the AG has less than 10% space free. |
| * (2) If this is an inode btree and the FS has less than 10% space free. |
| |
| * In either case, format the new btree blocks almost completely full to |
| * minimize space usage. |
| */ |
| static void |
| xrep_newbt_estimate_slack( |
| struct xrep_newbt *xnr) |
| { |
| struct xfs_scrub *sc = xnr->sc; |
| struct xfs_btree_bload *bload = &xnr->bload; |
| uint64_t free; |
| uint64_t sz; |
| |
| /* |
| * The xfs_globals values are set to -1 (i.e. take the bload defaults) |
| * unless someone has set them otherwise, so we just pull the values |
| * here. |
| */ |
| bload->leaf_slack = xfs_globals.bload_leaf_slack; |
| bload->node_slack = xfs_globals.bload_node_slack; |
| |
| if (sc->ops->type == ST_PERAG) { |
| free = sc->sa.pag->pagf_freeblks; |
| sz = xfs_ag_block_count(sc->mp, sc->sa.pag->pag_agno); |
| } else { |
| free = percpu_counter_sum(&sc->mp->m_fdblocks); |
| sz = sc->mp->m_sb.sb_dblocks; |
| } |
| |
| /* No further changes if there's more than 10% free space left. */ |
| if (free >= div_u64(sz, 10)) |
| return; |
| |
| /* |
| * We're low on space; load the btrees as tightly as possible. Leave |
| * a couple of open slots in each btree block so that we don't end up |
| * splitting the btrees like crazy after a mount. |
| */ |
| if (bload->leaf_slack < 0) |
| bload->leaf_slack = 2; |
| if (bload->node_slack < 0) |
| bload->node_slack = 2; |
| } |
| |
| /* Initialize accounting resources for staging a new AG btree. */ |
| void |
| xrep_newbt_init_ag( |
| struct xrep_newbt *xnr, |
| struct xfs_scrub *sc, |
| const struct xfs_owner_info *oinfo, |
| xfs_fsblock_t alloc_hint, |
| enum xfs_ag_resv_type resv) |
| { |
| memset(xnr, 0, sizeof(struct xrep_newbt)); |
| xnr->sc = sc; |
| xnr->oinfo = *oinfo; /* structure copy */ |
| xnr->alloc_hint = alloc_hint; |
| xnr->resv = resv; |
| INIT_LIST_HEAD(&xnr->resv_list); |
| xnr->bload.max_dirty = XFS_B_TO_FSBT(sc->mp, 256U << 10); /* 256K */ |
| xrep_newbt_estimate_slack(xnr); |
| } |
| |
| /* Initialize accounting resources for staging a new inode fork btree. */ |
| int |
| xrep_newbt_init_inode( |
| struct xrep_newbt *xnr, |
| struct xfs_scrub *sc, |
| int whichfork, |
| const struct xfs_owner_info *oinfo) |
| { |
| struct xfs_ifork *ifp; |
| |
| ifp = kmem_cache_zalloc(xfs_ifork_cache, XCHK_GFP_FLAGS); |
| if (!ifp) |
| return -ENOMEM; |
| |
| xrep_newbt_init_ag(xnr, sc, oinfo, |
| XFS_INO_TO_FSB(sc->mp, sc->ip->i_ino), |
| XFS_AG_RESV_NONE); |
| xnr->ifake.if_fork = ifp; |
| xnr->ifake.if_fork_size = xfs_inode_fork_size(sc->ip, whichfork); |
| return 0; |
| } |
| |
| /* |
| * Initialize accounting resources for staging a new btree. Callers are |
| * expected to add their own reservations (and clean them up) manually. |
| */ |
| void |
| xrep_newbt_init_bare( |
| struct xrep_newbt *xnr, |
| struct xfs_scrub *sc) |
| { |
| xrep_newbt_init_ag(xnr, sc, &XFS_RMAP_OINFO_ANY_OWNER, NULLFSBLOCK, |
| XFS_AG_RESV_NONE); |
| } |
| |
| /* |
| * Designate specific blocks to be used to build our new btree. @pag must be |
| * a passive reference. |
| */ |
| STATIC int |
| xrep_newbt_add_blocks( |
| struct xrep_newbt *xnr, |
| struct xfs_perag *pag, |
| const struct xfs_alloc_arg *args) |
| { |
| struct xfs_mount *mp = xnr->sc->mp; |
| struct xrep_newbt_resv *resv; |
| int error; |
| |
| resv = kmalloc(sizeof(struct xrep_newbt_resv), XCHK_GFP_FLAGS); |
| if (!resv) |
| return -ENOMEM; |
| |
| INIT_LIST_HEAD(&resv->list); |
| resv->agbno = XFS_FSB_TO_AGBNO(mp, args->fsbno); |
| resv->len = args->len; |
| resv->used = 0; |
| resv->pag = xfs_perag_hold(pag); |
| |
| if (args->tp) { |
| ASSERT(xnr->oinfo.oi_offset == 0); |
| |
| error = xfs_alloc_schedule_autoreap(args, true, &resv->autoreap); |
| if (error) |
| goto out_pag; |
| } |
| |
| list_add_tail(&resv->list, &xnr->resv_list); |
| return 0; |
| out_pag: |
| xfs_perag_put(resv->pag); |
| kfree(resv); |
| return error; |
| } |
| |
| /* |
| * Add an extent to the new btree reservation pool. Callers are required to |
| * reap this reservation manually if the repair is cancelled. @pag must be a |
| * passive reference. |
| */ |
| int |
| xrep_newbt_add_extent( |
| struct xrep_newbt *xnr, |
| struct xfs_perag *pag, |
| xfs_agblock_t agbno, |
| xfs_extlen_t len) |
| { |
| struct xfs_mount *mp = xnr->sc->mp; |
| struct xfs_alloc_arg args = { |
| .tp = NULL, /* no autoreap */ |
| .oinfo = xnr->oinfo, |
| .fsbno = XFS_AGB_TO_FSB(mp, pag->pag_agno, agbno), |
| .len = len, |
| .resv = xnr->resv, |
| }; |
| |
| return xrep_newbt_add_blocks(xnr, pag, &args); |
| } |
| |
| /* Don't let our allocation hint take us beyond this AG */ |
| static inline void |
| xrep_newbt_validate_ag_alloc_hint( |
| struct xrep_newbt *xnr) |
| { |
| struct xfs_scrub *sc = xnr->sc; |
| xfs_agnumber_t agno = XFS_FSB_TO_AGNO(sc->mp, xnr->alloc_hint); |
| |
| if (agno == sc->sa.pag->pag_agno && |
| xfs_verify_fsbno(sc->mp, xnr->alloc_hint)) |
| return; |
| |
| xnr->alloc_hint = XFS_AGB_TO_FSB(sc->mp, sc->sa.pag->pag_agno, |
| XFS_AGFL_BLOCK(sc->mp) + 1); |
| } |
| |
| /* Allocate disk space for a new per-AG btree. */ |
| STATIC int |
| xrep_newbt_alloc_ag_blocks( |
| struct xrep_newbt *xnr, |
| uint64_t nr_blocks) |
| { |
| struct xfs_scrub *sc = xnr->sc; |
| struct xfs_mount *mp = sc->mp; |
| int error = 0; |
| |
| ASSERT(sc->sa.pag != NULL); |
| |
| while (nr_blocks > 0) { |
| struct xfs_alloc_arg args = { |
| .tp = sc->tp, |
| .mp = mp, |
| .oinfo = xnr->oinfo, |
| .minlen = 1, |
| .maxlen = nr_blocks, |
| .prod = 1, |
| .resv = xnr->resv, |
| }; |
| xfs_agnumber_t agno; |
| |
| xrep_newbt_validate_ag_alloc_hint(xnr); |
| |
| error = xfs_alloc_vextent_near_bno(&args, xnr->alloc_hint); |
| if (error) |
| return error; |
| if (args.fsbno == NULLFSBLOCK) |
| return -ENOSPC; |
| |
| agno = XFS_FSB_TO_AGNO(mp, args.fsbno); |
| |
| trace_xrep_newbt_alloc_ag_blocks(mp, agno, |
| XFS_FSB_TO_AGBNO(mp, args.fsbno), args.len, |
| xnr->oinfo.oi_owner); |
| |
| if (agno != sc->sa.pag->pag_agno) { |
| ASSERT(agno == sc->sa.pag->pag_agno); |
| return -EFSCORRUPTED; |
| } |
| |
| error = xrep_newbt_add_blocks(xnr, sc->sa.pag, &args); |
| if (error) |
| return error; |
| |
| nr_blocks -= args.len; |
| xnr->alloc_hint = args.fsbno + args.len; |
| |
| error = xrep_defer_finish(sc); |
| if (error) |
| return error; |
| } |
| |
| return 0; |
| } |
| |
| /* Don't let our allocation hint take us beyond EOFS */ |
| static inline void |
| xrep_newbt_validate_file_alloc_hint( |
| struct xrep_newbt *xnr) |
| { |
| struct xfs_scrub *sc = xnr->sc; |
| |
| if (xfs_verify_fsbno(sc->mp, xnr->alloc_hint)) |
| return; |
| |
| xnr->alloc_hint = XFS_AGB_TO_FSB(sc->mp, 0, XFS_AGFL_BLOCK(sc->mp) + 1); |
| } |
| |
| /* Allocate disk space for our new file-based btree. */ |
| STATIC int |
| xrep_newbt_alloc_file_blocks( |
| struct xrep_newbt *xnr, |
| uint64_t nr_blocks) |
| { |
| struct xfs_scrub *sc = xnr->sc; |
| struct xfs_mount *mp = sc->mp; |
| int error = 0; |
| |
| while (nr_blocks > 0) { |
| struct xfs_alloc_arg args = { |
| .tp = sc->tp, |
| .mp = mp, |
| .oinfo = xnr->oinfo, |
| .minlen = 1, |
| .maxlen = nr_blocks, |
| .prod = 1, |
| .resv = xnr->resv, |
| }; |
| struct xfs_perag *pag; |
| xfs_agnumber_t agno; |
| |
| xrep_newbt_validate_file_alloc_hint(xnr); |
| |
| error = xfs_alloc_vextent_start_ag(&args, xnr->alloc_hint); |
| if (error) |
| return error; |
| if (args.fsbno == NULLFSBLOCK) |
| return -ENOSPC; |
| |
| agno = XFS_FSB_TO_AGNO(mp, args.fsbno); |
| |
| trace_xrep_newbt_alloc_file_blocks(mp, agno, |
| XFS_FSB_TO_AGBNO(mp, args.fsbno), args.len, |
| xnr->oinfo.oi_owner); |
| |
| pag = xfs_perag_get(mp, agno); |
| if (!pag) { |
| ASSERT(0); |
| return -EFSCORRUPTED; |
| } |
| |
| error = xrep_newbt_add_blocks(xnr, pag, &args); |
| xfs_perag_put(pag); |
| if (error) |
| return error; |
| |
| nr_blocks -= args.len; |
| xnr->alloc_hint = args.fsbno + args.len; |
| |
| error = xrep_defer_finish(sc); |
| if (error) |
| return error; |
| } |
| |
| return 0; |
| } |
| |
| /* Allocate disk space for our new btree. */ |
| int |
| xrep_newbt_alloc_blocks( |
| struct xrep_newbt *xnr, |
| uint64_t nr_blocks) |
| { |
| if (xnr->sc->ip) |
| return xrep_newbt_alloc_file_blocks(xnr, nr_blocks); |
| return xrep_newbt_alloc_ag_blocks(xnr, nr_blocks); |
| } |
| |
| /* |
| * Free the unused part of a space extent that was reserved for a new ondisk |
| * structure. Returns the number of EFIs logged or a negative errno. |
| */ |
| STATIC int |
| xrep_newbt_free_extent( |
| struct xrep_newbt *xnr, |
| struct xrep_newbt_resv *resv, |
| bool btree_committed) |
| { |
| struct xfs_scrub *sc = xnr->sc; |
| xfs_agblock_t free_agbno = resv->agbno; |
| xfs_extlen_t free_aglen = resv->len; |
| xfs_fsblock_t fsbno; |
| int error; |
| |
| if (!btree_committed || resv->used == 0) { |
| /* |
| * If we're not committing a new btree or we didn't use the |
| * space reservation, let the existing EFI free the entire |
| * space extent. |
| */ |
| trace_xrep_newbt_free_blocks(sc->mp, resv->pag->pag_agno, |
| free_agbno, free_aglen, xnr->oinfo.oi_owner); |
| xfs_alloc_commit_autoreap(sc->tp, &resv->autoreap); |
| return 1; |
| } |
| |
| /* |
| * We used space and committed the btree. Cancel the autoreap, remove |
| * the written blocks from the reservation, and possibly log a new EFI |
| * to free any unused reservation space. |
| */ |
| xfs_alloc_cancel_autoreap(sc->tp, &resv->autoreap); |
| free_agbno += resv->used; |
| free_aglen -= resv->used; |
| |
| if (free_aglen == 0) |
| return 0; |
| |
| trace_xrep_newbt_free_blocks(sc->mp, resv->pag->pag_agno, free_agbno, |
| free_aglen, xnr->oinfo.oi_owner); |
| |
| ASSERT(xnr->resv != XFS_AG_RESV_AGFL); |
| ASSERT(xnr->resv != XFS_AG_RESV_IGNORE); |
| |
| /* |
| * Use EFIs to free the reservations. This reduces the chance |
| * that we leak blocks if the system goes down. |
| */ |
| fsbno = XFS_AGB_TO_FSB(sc->mp, resv->pag->pag_agno, free_agbno); |
| error = xfs_free_extent_later(sc->tp, fsbno, free_aglen, &xnr->oinfo, |
| xnr->resv, true); |
| if (error) |
| return error; |
| |
| return 1; |
| } |
| |
| /* Free all the accounting info and disk space we reserved for a new btree. */ |
| STATIC int |
| xrep_newbt_free( |
| struct xrep_newbt *xnr, |
| bool btree_committed) |
| { |
| struct xfs_scrub *sc = xnr->sc; |
| struct xrep_newbt_resv *resv, *n; |
| unsigned int freed = 0; |
| int error = 0; |
| |
| /* |
| * If the filesystem already went down, we can't free the blocks. Skip |
| * ahead to freeing the incore metadata because we can't fix anything. |
| */ |
| if (xfs_is_shutdown(sc->mp)) |
| goto junkit; |
| |
| list_for_each_entry_safe(resv, n, &xnr->resv_list, list) { |
| int ret; |
| |
| ret = xrep_newbt_free_extent(xnr, resv, btree_committed); |
| list_del(&resv->list); |
| xfs_perag_put(resv->pag); |
| kfree(resv); |
| if (ret < 0) { |
| error = ret; |
| goto junkit; |
| } |
| |
| freed += ret; |
| if (freed >= XREP_MAX_ITRUNCATE_EFIS) { |
| error = xrep_defer_finish(sc); |
| if (error) |
| goto junkit; |
| freed = 0; |
| } |
| } |
| |
| if (freed) |
| error = xrep_defer_finish(sc); |
| |
| junkit: |
| /* |
| * If we still have reservations attached to @newbt, cleanup must have |
| * failed and the filesystem is about to go down. Clean up the incore |
| * reservations and try to commit to freeing the space we used. |
| */ |
| list_for_each_entry_safe(resv, n, &xnr->resv_list, list) { |
| xfs_alloc_commit_autoreap(sc->tp, &resv->autoreap); |
| list_del(&resv->list); |
| xfs_perag_put(resv->pag); |
| kfree(resv); |
| } |
| |
| if (sc->ip) { |
| kmem_cache_free(xfs_ifork_cache, xnr->ifake.if_fork); |
| xnr->ifake.if_fork = NULL; |
| } |
| |
| return error; |
| } |
| |
| /* |
| * Free all the accounting info and unused disk space allocations after |
| * committing a new btree. |
| */ |
| int |
| xrep_newbt_commit( |
| struct xrep_newbt *xnr) |
| { |
| return xrep_newbt_free(xnr, true); |
| } |
| |
| /* |
| * Free all the accounting info and all of the disk space we reserved for a new |
| * btree that we're not going to commit. We want to try to roll things back |
| * cleanly for things like ENOSPC midway through allocation. |
| */ |
| void |
| xrep_newbt_cancel( |
| struct xrep_newbt *xnr) |
| { |
| xrep_newbt_free(xnr, false); |
| } |
| |
| /* Feed one of the reserved btree blocks to the bulk loader. */ |
| int |
| xrep_newbt_claim_block( |
| struct xfs_btree_cur *cur, |
| struct xrep_newbt *xnr, |
| union xfs_btree_ptr *ptr) |
| { |
| struct xrep_newbt_resv *resv; |
| struct xfs_mount *mp = cur->bc_mp; |
| xfs_agblock_t agbno; |
| |
| /* |
| * The first item in the list should always have a free block unless |
| * we're completely out. |
| */ |
| resv = list_first_entry(&xnr->resv_list, struct xrep_newbt_resv, list); |
| if (resv->used == resv->len) |
| return -ENOSPC; |
| |
| /* |
| * Peel off a block from the start of the reservation. We allocate |
| * blocks in order to place blocks on disk in increasing record or key |
| * order. The block reservations tend to end up on the list in |
| * decreasing order, which hopefully results in leaf blocks ending up |
| * together. |
| */ |
| agbno = resv->agbno + resv->used; |
| resv->used++; |
| |
| /* If we used all the blocks in this reservation, move it to the end. */ |
| if (resv->used == resv->len) |
| list_move_tail(&resv->list, &xnr->resv_list); |
| |
| trace_xrep_newbt_claim_block(mp, resv->pag->pag_agno, agbno, 1, |
| xnr->oinfo.oi_owner); |
| |
| if (cur->bc_flags & XFS_BTREE_LONG_PTRS) |
| ptr->l = cpu_to_be64(XFS_AGB_TO_FSB(mp, resv->pag->pag_agno, |
| agbno)); |
| else |
| ptr->s = cpu_to_be32(agbno); |
| |
| /* Relog all the EFIs. */ |
| return xrep_defer_finish(xnr->sc); |
| } |
| |
| /* How many reserved blocks are unused? */ |
| unsigned int |
| xrep_newbt_unused_blocks( |
| struct xrep_newbt *xnr) |
| { |
| struct xrep_newbt_resv *resv; |
| unsigned int unused = 0; |
| |
| list_for_each_entry(resv, &xnr->resv_list, list) |
| unused += resv->len - resv->used; |
| return unused; |
| } |