#include "xfs_platform.h"
#include "xfs_fs.h"
#include "xfs_shared.h"
#include "xfs_format.h"
#include "xfs_trans_resv.h"
#include "xfs_mount.h"
#include "xfs_defer.h"
#include "xfs_btree.h"
#include "xfs_btree_staging.h"
#include "xfs_bit.h"
#include "xfs_log_format.h"
#include "xfs_trans.h"
#include "xfs_sb.h"
#include "xfs_alloc.h"
#include "xfs_alloc_btree.h"
#include "xfs_rmap.h"
#include "xfs_rmap_btree.h"
#include "xfs_inode.h"
#include "xfs_refcount.h"
#include "xfs_extent_busy.h"
#include "xfs_health.h"
#include "xfs_bmap.h"
#include "xfs_ialloc.h"
#include "xfs_ag.h"
#include "scrub/xfs_scrub.h"
#include "scrub/scrub.h"
#include "scrub/common.h"
#include "scrub/btree.h"
#include "scrub/trace.h"
#include "scrub/repair.h"
#include "scrub/bitmap.h"
#include "scrub/agb_bitmap.h"
#include "scrub/xfile.h"
#include "scrub/xfarray.h"
#include "scrub/newbt.h"
#include "scrub/reap.h"
struct xrep_abt {
struct xagb_bitmap not_allocbt_blocks;
struct xagb_bitmap old_allocbt_blocks;
struct xrep_newbt new_bnobt;
struct xrep_newbt new_cntbt;
struct xfarray *free_records;
struct xfs_scrub *sc;
uint64_t nr_real_records;
xfarray_idx_t array_cur;
xfs_agblock_t next_agbno;
xfs_agblock_t nr_blocks;
xfs_agblock_t longest;
};
int
xrep_setup_ag_allocbt(
struct xfs_scrub *sc)
{
struct xfs_group *xg = pag_group(sc->sa.pag);
unsigned int busy_gen;
if (xfs_extent_busy_list_empty(xg, &busy_gen))
return 0;
return xfs_extent_busy_flush(sc->tp, xg, busy_gen, 0);
}
STATIC int
xrep_abt_check_free_ext(
struct xfs_scrub *sc,
const struct xfs_alloc_rec_incore *rec)
{
enum xbtree_recpacking outcome;
int error;
if (xfs_alloc_check_irec(sc->sa.pag, rec) != NULL)
return -EFSCORRUPTED;
error = xfs_ialloc_has_inodes_at_extent(sc->sa.ino_cur,
rec->ar_startblock, rec->ar_blockcount, &outcome);
if (error)
return error;
if (outcome != XBTREE_RECPACKING_EMPTY)
return -EFSCORRUPTED;
if (sc->sa.refc_cur) {
error = xfs_refcount_has_records(sc->sa.refc_cur,
XFS_REFC_DOMAIN_SHARED, rec->ar_startblock,
rec->ar_blockcount, &outcome);
if (error)
return error;
if (outcome != XBTREE_RECPACKING_EMPTY)
return -EFSCORRUPTED;
error = xfs_refcount_has_records(sc->sa.refc_cur,
XFS_REFC_DOMAIN_COW, rec->ar_startblock,
rec->ar_blockcount, &outcome);
if (error)
return error;
if (outcome != XBTREE_RECPACKING_EMPTY)
return -EFSCORRUPTED;
}
return 0;
}
static int
xrep_abt_stash(
struct xrep_abt *ra,
xfs_agblock_t end)
{
struct xfs_alloc_rec_incore arec = {
.ar_startblock = ra->next_agbno,
.ar_blockcount = end - ra->next_agbno,
};
struct xfs_scrub *sc = ra->sc;
int error = 0;
if (xchk_should_terminate(sc, &error))
return error;
error = xrep_abt_check_free_ext(ra->sc, &arec);
if (error)
return error;
trace_xrep_abt_found(sc->sa.pag, &arec);
error = xfarray_append(ra->free_records, &arec);
if (error)
return error;
ra->nr_blocks += arec.ar_blockcount;
return 0;
}
STATIC int
xrep_abt_walk_rmap(
struct xfs_btree_cur *cur,
const struct xfs_rmap_irec *rec,
void *priv)
{
struct xrep_abt *ra = priv;
int error;
if (rec->rm_owner == XFS_RMAP_OWN_AG) {
error = xagb_bitmap_set(&ra->old_allocbt_blocks,
rec->rm_startblock, rec->rm_blockcount);
if (error)
return error;
}
error = xagb_bitmap_set_btcur_path(&ra->not_allocbt_blocks, cur);
if (error)
return error;
if (rec->rm_startblock > ra->next_agbno) {
error = xrep_abt_stash(ra, rec->rm_startblock);
if (error)
return error;
}
ra->next_agbno = max_t(xfs_agblock_t, ra->next_agbno,
rec->rm_startblock + rec->rm_blockcount);
return 0;
}
static int
xrep_abt_walk_agfl(
struct xfs_mount *mp,
xfs_agblock_t agbno,
void *priv)
{
struct xrep_abt *ra = priv;
return xagb_bitmap_set(&ra->not_allocbt_blocks, agbno, 1);
}
static int
xrep_bnobt_extent_cmp(
const void *a,
const void *b)
{
const struct xfs_alloc_rec_incore *ap = a;
const struct xfs_alloc_rec_incore *bp = b;
if (ap->ar_startblock > bp->ar_startblock)
return 1;
else if (ap->ar_startblock < bp->ar_startblock)
return -1;
return 0;
}
STATIC int
xrep_bnobt_sort_records(
struct xrep_abt *ra)
{
struct xfs_alloc_rec_incore arec;
xfarray_idx_t cur = XFARRAY_CURSOR_INIT;
xfs_agblock_t next_agbno = 0;
int error;
error = xfarray_sort(ra->free_records, xrep_bnobt_extent_cmp, 0);
if (error)
return error;
while ((error = xfarray_iter(ra->free_records, &cur, &arec)) == 1) {
if (arec.ar_startblock < next_agbno)
return -EFSCORRUPTED;
next_agbno = arec.ar_startblock + arec.ar_blockcount;
}
return error;
}
static int
xrep_cntbt_extent_cmp(
const void *a,
const void *b)
{
const struct xfs_alloc_rec_incore *ap = a;
const struct xfs_alloc_rec_incore *bp = b;
if (ap->ar_blockcount > bp->ar_blockcount)
return 1;
else if (ap->ar_blockcount < bp->ar_blockcount)
return -1;
return xrep_bnobt_extent_cmp(a, b);
}
STATIC int
xrep_cntbt_sort_records(
struct xrep_abt *ra,
bool is_resort)
{
return xfarray_sort(ra->free_records, xrep_cntbt_extent_cmp,
is_resort ? 0 : XFARRAY_SORT_KILLABLE);
}
STATIC int
xrep_abt_find_freespace(
struct xrep_abt *ra)
{
struct xfs_scrub *sc = ra->sc;
struct xfs_mount *mp = sc->mp;
struct xfs_agf *agf = sc->sa.agf_bp->b_addr;
struct xfs_buf *agfl_bp;
xfs_agblock_t agend;
int error;
xagb_bitmap_init(&ra->not_allocbt_blocks);
xrep_ag_btcur_init(sc, &sc->sa);
error = xfs_rmap_query_all(sc->sa.rmap_cur, xrep_abt_walk_rmap, ra);
if (error)
goto err;
agend = be32_to_cpu(agf->agf_length);
if (ra->next_agbno < agend) {
error = xrep_abt_stash(ra, agend);
if (error)
goto err;
}
error = xfs_alloc_read_agfl(sc->sa.pag, sc->tp, &agfl_bp);
if (error)
goto err;
error = xfs_agfl_walk(mp, agf, agfl_bp, xrep_abt_walk_agfl, ra);
if (error)
goto err_agfl;
error = xagb_bitmap_disunion(&ra->old_allocbt_blocks,
&ra->not_allocbt_blocks);
if (error)
goto err_agfl;
ra->nr_real_records = xfarray_length(ra->free_records);
err_agfl:
xfs_trans_brelse(sc->tp, agfl_bp);
err:
xchk_ag_btcur_free(&sc->sa);
xagb_bitmap_destroy(&ra->not_allocbt_blocks);
return error;
}
STATIC int
xrep_abt_reserve_space(
struct xrep_abt *ra,
struct xfs_btree_cur *bno_cur,
struct xfs_btree_cur *cnt_cur,
bool *needs_resort)
{
struct xfs_scrub *sc = ra->sc;
xfarray_idx_t record_nr;
unsigned int allocated = 0;
int error = 0;
record_nr = xfarray_length(ra->free_records) - 1;
do {
struct xfs_alloc_rec_incore arec;
uint64_t required;
unsigned int desired;
unsigned int len;
error = xfs_btree_bload_compute_geometry(cnt_cur,
&ra->new_cntbt.bload, ra->nr_real_records);
if (error)
break;
error = xfs_btree_bload_compute_geometry(bno_cur,
&ra->new_bnobt.bload, ra->nr_real_records);
if (error)
break;
required = ra->new_bnobt.bload.nr_blocks +
ra->new_cntbt.bload.nr_blocks;
ASSERT(required < INT_MAX);
if (allocated >= required)
break;
desired = required - allocated;
if (ra->nr_real_records == 0) {
error = -ENOSPC;
break;
}
error = xfarray_load(ra->free_records, record_nr, &arec);
if (error)
break;
ASSERT(arec.ar_blockcount <= UINT_MAX);
len = min_t(unsigned int, arec.ar_blockcount, desired);
trace_xrep_newbt_alloc_ag_blocks(sc->sa.pag, arec.ar_startblock,
len, XFS_RMAP_OWN_AG);
error = xrep_newbt_add_extent(&ra->new_bnobt, sc->sa.pag,
arec.ar_startblock, len);
if (error)
break;
allocated += len;
ra->nr_blocks -= len;
if (arec.ar_blockcount > desired) {
arec.ar_startblock += desired;
arec.ar_blockcount -= desired;
error = xfarray_store(ra->free_records, record_nr,
&arec);
if (error)
break;
*needs_resort = true;
return 0;
}
error = xfarray_unset(ra->free_records, record_nr);
if (error)
break;
ra->nr_real_records--;
record_nr--;
} while (1);
return error;
}
STATIC int
xrep_abt_dispose_one(
struct xrep_abt *ra,
struct xrep_newbt_resv *resv)
{
struct xfs_scrub *sc = ra->sc;
struct xfs_perag *pag = sc->sa.pag;
xfs_agblock_t free_agbno = resv->agbno + resv->used;
xfs_extlen_t free_aglen = resv->len - resv->used;
int error;
ASSERT(pag == resv->pag);
if (resv->used > 0)
xfs_rmap_alloc_extent(sc->tp, false,
xfs_agbno_to_fsb(pag, resv->agbno), resv->used,
XFS_RMAP_OWN_AG);
if (free_aglen == 0)
return 0;
trace_xrep_newbt_free_blocks(resv->pag, free_agbno, free_aglen,
ra->new_bnobt.oinfo.oi_owner);
error = __xfs_free_extent(sc->tp, resv->pag, free_agbno, free_aglen,
&ra->new_bnobt.oinfo, XFS_AG_RESV_IGNORE, true);
if (error)
return error;
return xrep_defer_finish(sc);
}
STATIC void
xrep_abt_dispose_reservations(
struct xrep_abt *ra,
int error)
{
struct xrep_newbt_resv *resv, *n;
if (error)
goto junkit;
list_for_each_entry_safe(resv, n, &ra->new_bnobt.resv_list, list) {
error = xrep_abt_dispose_one(ra, resv);
if (error)
goto junkit;
}
junkit:
list_for_each_entry_safe(resv, n, &ra->new_bnobt.resv_list, list) {
xfs_perag_put(resv->pag);
list_del(&resv->list);
kfree(resv);
}
xrep_newbt_cancel(&ra->new_bnobt);
xrep_newbt_cancel(&ra->new_cntbt);
}
STATIC int
xrep_abt_get_records(
struct xfs_btree_cur *cur,
unsigned int idx,
struct xfs_btree_block *block,
unsigned int nr_wanted,
void *priv)
{
struct xfs_alloc_rec_incore *arec = &cur->bc_rec.a;
struct xrep_abt *ra = priv;
union xfs_btree_rec *block_rec;
unsigned int loaded;
int error;
for (loaded = 0; loaded < nr_wanted; loaded++, idx++) {
error = xfarray_load_next(ra->free_records, &ra->array_cur,
arec);
if (error)
return error;
ra->longest = max(ra->longest, arec->ar_blockcount);
block_rec = xfs_btree_rec_addr(cur, idx, block);
cur->bc_ops->init_rec_from_cur(cur, block_rec);
}
return loaded;
}
STATIC int
xrep_abt_claim_block(
struct xfs_btree_cur *cur,
union xfs_btree_ptr *ptr,
void *priv)
{
struct xrep_abt *ra = priv;
return xrep_newbt_claim_block(cur, &ra->new_bnobt, ptr);
}
STATIC int
xrep_abt_reset_counters(
struct xrep_abt *ra)
{
struct xfs_scrub *sc = ra->sc;
struct xfs_perag *pag = sc->sa.pag;
struct xfs_agf *agf = sc->sa.agf_bp->b_addr;
unsigned int freesp_btreeblks = 0;
freesp_btreeblks += ra->new_bnobt.bload.nr_blocks - 1;
freesp_btreeblks += ra->new_cntbt.bload.nr_blocks - 1;
freesp_btreeblks -= xrep_newbt_unused_blocks(&ra->new_bnobt);
freesp_btreeblks -= xrep_newbt_unused_blocks(&ra->new_cntbt);
agf->agf_btreeblks = cpu_to_be32(freesp_btreeblks +
(be32_to_cpu(agf->agf_rmap_blocks) - 1));
agf->agf_freeblks = cpu_to_be32(ra->nr_blocks);
agf->agf_longest = cpu_to_be32(ra->longest);
xfs_alloc_log_agf(sc->tp, sc->sa.agf_bp, XFS_AGF_BTREEBLKS |
XFS_AGF_LONGEST |
XFS_AGF_FREEBLKS);
pag->pagf_repair_bno_level = pag->pagf_bno_level;
pag->pagf_repair_cnt_level = pag->pagf_cnt_level;
return xrep_reinit_pagf(sc);
}
STATIC int
xrep_abt_build_new_trees(
struct xrep_abt *ra)
{
struct xfs_scrub *sc = ra->sc;
struct xfs_btree_cur *bno_cur;
struct xfs_btree_cur *cnt_cur;
struct xfs_perag *pag = sc->sa.pag;
bool needs_resort = false;
int error;
error = xrep_cntbt_sort_records(ra, false);
if (error)
return error;
xrep_newbt_init_bare(&ra->new_bnobt, sc);
xrep_newbt_init_bare(&ra->new_cntbt, sc);
ra->new_bnobt.bload.get_records = xrep_abt_get_records;
ra->new_cntbt.bload.get_records = xrep_abt_get_records;
ra->new_bnobt.bload.claim_block = xrep_abt_claim_block;
ra->new_cntbt.bload.claim_block = xrep_abt_claim_block;
bno_cur = xfs_bnobt_init_cursor(sc->mp, NULL, NULL, pag);
xfs_btree_stage_afakeroot(bno_cur, &ra->new_bnobt.afake);
cnt_cur = xfs_cntbt_init_cursor(sc->mp, NULL, NULL, pag);
xfs_btree_stage_afakeroot(cnt_cur, &ra->new_cntbt.afake);
if (xchk_should_terminate(sc, &error))
goto err_cur;
error = xrep_abt_reserve_space(ra, bno_cur, cnt_cur, &needs_resort);
if (error)
goto err_cur;
if (needs_resort) {
error = xrep_cntbt_sort_records(ra, needs_resort);
if (error)
goto err_cur;
}
pag->pagf_repair_bno_level = ra->new_bnobt.bload.btree_height;
pag->pagf_repair_cnt_level = ra->new_cntbt.bload.btree_height;
ra->array_cur = XFARRAY_CURSOR_INIT;
ra->longest = 0;
error = xfs_btree_bload(cnt_cur, &ra->new_cntbt.bload, ra);
if (error)
goto err_levels;
error = xrep_bnobt_sort_records(ra);
if (error)
goto err_levels;
ra->array_cur = XFARRAY_CURSOR_INIT;
error = xfs_btree_bload(bno_cur, &ra->new_bnobt.bload, ra);
if (error)
goto err_levels;
xfs_allocbt_commit_staged_btree(bno_cur, sc->tp, sc->sa.agf_bp);
xfs_btree_del_cursor(bno_cur, 0);
xfs_allocbt_commit_staged_btree(cnt_cur, sc->tp, sc->sa.agf_bp);
xfs_btree_del_cursor(cnt_cur, 0);
error = xrep_abt_reset_counters(ra);
if (error)
goto err_newbt;
xrep_abt_dispose_reservations(ra, error);
return xrep_roll_ag_trans(sc);
err_levels:
pag->pagf_repair_bno_level = 0;
pag->pagf_repair_cnt_level = 0;
err_cur:
xfs_btree_del_cursor(cnt_cur, error);
xfs_btree_del_cursor(bno_cur, error);
err_newbt:
xrep_abt_dispose_reservations(ra, error);
return error;
}
STATIC int
xrep_abt_remove_old_trees(
struct xrep_abt *ra)
{
struct xfs_perag *pag = ra->sc->sa.pag;
int error;
error = xrep_reap_agblocks(ra->sc, &ra->old_allocbt_blocks,
&XFS_RMAP_OINFO_AG, XFS_AG_RESV_IGNORE);
if (error)
return error;
pag->pagf_repair_bno_level = 0;
pag->pagf_repair_cnt_level = 0;
return 0;
}
int
xrep_allocbt(
struct xfs_scrub *sc)
{
struct xrep_abt *ra;
struct xfs_mount *mp = sc->mp;
unsigned int busy_gen;
int error;
if (!xfs_has_rmapbt(mp))
return -EOPNOTSUPP;
ra = kzalloc_obj(struct xrep_abt, XCHK_GFP_FLAGS);
if (!ra)
return -ENOMEM;
ra->sc = sc;
sc->sick_mask = XFS_SICK_AG_BNOBT | XFS_SICK_AG_CNTBT;
if (!xfs_extent_busy_list_empty(pag_group(sc->sa.pag), &busy_gen)) {
error = -EDEADLOCK;
goto out_ra;
}
error = xfarray_create("free space records", mp->m_sb.sb_agblocks / 2,
sizeof(struct xfs_alloc_rec_incore),
&ra->free_records);
if (error)
goto out_ra;
xagb_bitmap_init(&ra->old_allocbt_blocks);
error = xrep_abt_find_freespace(ra);
if (error)
goto out_bitmap;
error = xrep_abt_build_new_trees(ra);
if (error)
goto out_bitmap;
error = xrep_abt_remove_old_trees(ra);
if (error)
goto out_bitmap;
out_bitmap:
xagb_bitmap_destroy(&ra->old_allocbt_blocks);
xfarray_destroy(ra->free_records);
out_ra:
kfree(ra);
return error;
}
int
xrep_revalidate_allocbt(
struct xfs_scrub *sc)
{
__u32 old_type = sc->sm->sm_type;
int error;
sc->sm->sm_type = XFS_SCRUB_TYPE_BNOBT;
error = xchk_allocbt(sc);
if (error)
goto out;
if (sc->sm->sm_flags & XFS_SCRUB_OFLAG_CORRUPT)
goto out;
sc->sm->sm_type = XFS_SCRUB_TYPE_CNTBT;
if (!sc->sa.cnt_cur) {
xchk_set_incomplete(sc);
goto out;
}
error = xchk_allocbt(sc);
out:
sc->sm->sm_type = old_type;
return error;
}