blob: 1de6d3439ab3d3490d4933c1c587c8767ce6e195 [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (c) 2022-2024 Oracle. All Rights Reserved.
* Author: Darrick J. Wong <djwong@kernel.org>
*/
#include "libxfs.h"
#include "command.h"
#include "init.h"
#include "output.h"
#include "io.h"
#include "libfrog/convert.h"
static void
bmapinflate_help(void)
{
dbprintf(_(
"\n"
" Make the bmbt really big by cloning the first data fork mapping over and over.\n"
" -d Constrain dirty buffers to this many bytes.\n"
" -e Print the size and height of the btree and exit.\n"
" -n nr Create this many copies of the mapping.\n"
"\n"
));
}
static int
find_mapping(
struct xfs_trans *tp,
struct xfs_inode *ip,
struct xfs_bmbt_irec *irec)
{
struct xfs_iext_cursor icur;
int error;
if (!xfs_has_reflink(ip->i_mount)) {
dbprintf(_("filesystem does not support reflink\n"));
return 1;
}
if (ip->i_df.if_nextents != 1) {
dbprintf(_("inode must have only one data fork mapping\n"));
return 1;
}
error = -libxfs_iread_extents(tp, ip, XFS_DATA_FORK);
if (error) {
dbprintf(_("could not read data fork, err %d\n"), error);
return 1;
}
libxfs_iext_first(&ip->i_df, &icur);
if (!xfs_iext_get_extent(&ip->i_df, &icur, irec)) {
dbprintf(_("could not read data fork mapping\n"));
return 1;
}
if (irec->br_state != XFS_EXT_NORM) {
dbprintf(_("cannot duplicate unwritten extent\n"));
return 1;
}
return 0;
}
static int
set_nrext64(
struct xfs_trans *tp,
struct xfs_inode *ip,
xfs_extnum_t nextents)
{
xfs_extnum_t max_extents;
bool large_extcount;
large_extcount = xfs_inode_has_large_extent_counts(ip);
max_extents = xfs_iext_max_nextents(large_extcount, XFS_DATA_FORK);
if (nextents <= max_extents)
return 0;
if (large_extcount)
return EFSCORRUPTED;
if (!xfs_has_large_extent_counts(ip->i_mount))
return EFSCORRUPTED;
max_extents = xfs_iext_max_nextents(true, XFS_DATA_FORK);
if (nextents > max_extents)
return EFSCORRUPTED;
ip->i_diflags2 |= XFS_DIFLAG2_NREXT64;
libxfs_trans_log_inode(tp, ip, XFS_ILOG_CORE);
return 0;
}
static int
populate_extents(
struct xfs_trans *tp,
struct xfs_inode *ip,
struct xbtree_ifakeroot *ifake,
const struct xfs_bmbt_irec *template,
xfs_extnum_t nextents)
{
struct xfs_bmbt_irec irec = {
.br_startoff = 0,
.br_startblock = template->br_startblock,
.br_blockcount = template->br_blockcount,
.br_state = XFS_EXT_NORM,
};
struct xfs_iext_cursor icur;
struct xfs_ifork *ifp = ifake->if_fork;
unsigned long long i;
/* Add all the mappings to the incore extent tree. */
libxfs_iext_first(ifp, &icur);
for (i = 0; i < nextents; i++) {
libxfs_iext_insert_raw(ifp, &icur, &irec);
ifp->if_nextents++;
libxfs_iext_next(ifp, &icur);
irec.br_startoff += irec.br_blockcount;
}
ip->i_nblocks = template->br_blockcount * nextents;
libxfs_trans_log_inode(tp, ip, XFS_ILOG_CORE);
return 0;
}
struct bmbt_resv {
struct list_head list;
xfs_fsblock_t fsbno;
xfs_extlen_t len;
xfs_extlen_t used;
};
struct bmbt_data {
struct xfs_bmbt_irec irec;
struct list_head resv_list;
unsigned long long iblocks;
unsigned long long nr;
};
static int
alloc_bmbt_blocks(
struct xfs_trans **tpp,
struct xfs_inode *ip,
struct bmbt_data *bd,
uint64_t nr_blocks)
{
struct xfs_mount *mp = ip->i_mount;
struct list_head *resv_list = &bd->resv_list;
int error = 0;
while (nr_blocks > 0) {
struct xfs_alloc_arg args = {
.tp = *tpp,
.mp = mp,
.minlen = 1,
.maxlen = nr_blocks,
.prod = 1,
.resv = XFS_AG_RESV_NONE,
};
struct bmbt_resv *resv;
xfs_fsblock_t target = 0;
if (xfs_has_rmapbt(mp)) {
xfs_agnumber_t tgt_agno;
/*
* Try to allocate bmbt blocks in a different AG so
* that we don't blow up the rmapbt with the bmbt
* records.
*/
tgt_agno = 1 + XFS_FSB_TO_AGNO(mp,
bd->irec.br_startblock);
if (tgt_agno >= mp->m_sb.sb_agcount)
tgt_agno = 0;
target = XFS_AGB_TO_FSB(mp, tgt_agno, 0);
}
libxfs_rmap_ino_bmbt_owner(&args.oinfo, ip->i_ino,
XFS_DATA_FORK);
error = -libxfs_alloc_vextent_start_ag(&args, target);
if (error)
return error;
if (args.fsbno == NULLFSBLOCK)
return ENOSPC;
resv = kmalloc(sizeof(struct bmbt_resv), 0);
if (!resv)
return ENOMEM;
INIT_LIST_HEAD(&resv->list);
resv->fsbno = args.fsbno;
resv->len = args.len;
resv->used = 0;
list_add_tail(&resv->list, resv_list);
nr_blocks -= args.len;
error = -libxfs_trans_roll_inode(tpp, ip);
if (error)
return error;
}
return 0;
}
static int
get_bmbt_records(
struct xfs_btree_cur *cur,
unsigned int idx,
struct xfs_btree_block *block,
unsigned int nr_wanted,
void *priv)
{
struct xfs_bmbt_irec *irec = &cur->bc_rec.b;
struct bmbt_data *bd = priv;
union xfs_btree_rec *block_rec;
struct xfs_ifork *ifp = cur->bc_ino.ifake->if_fork;
unsigned int loaded;
for (loaded = 0; loaded < nr_wanted; loaded++, idx++) {
memcpy(irec, &bd->irec, sizeof(struct xfs_bmbt_irec));
block_rec = libxfs_btree_rec_addr(cur, idx, block);
cur->bc_ops->init_rec_from_cur(cur, block_rec);
ifp->if_nextents++;
bd->irec.br_startoff += bd->irec.br_blockcount;
}
return loaded;
}
static int
claim_block(
struct xfs_btree_cur *cur,
union xfs_btree_ptr *ptr,
void *priv)
{
struct bmbt_data *bd = priv;
struct bmbt_resv *resv;
xfs_fsblock_t fsb;
/*
* The first item in the list should always have a free block unless
* we're completely out.
*/
resv = list_first_entry(&bd->resv_list, struct bmbt_resv, list);
if (resv->used == resv->len)
return ENOSPC;
fsb = resv->fsbno + 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, &bd->resv_list);
ptr->l = cpu_to_be64(fsb);
bd->iblocks++;
return 0;
}
static size_t
iroot_size(
struct xfs_btree_cur *cur,
unsigned int level,
unsigned int nr_this_level,
void *priv)
{
return xfs_bmap_broot_space_calc(cur->bc_mp, nr_this_level);
}
static int
populate_btree(
struct xfs_trans **tpp,
struct xfs_inode *ip,
uint16_t dirty_blocks,
struct xbtree_ifakeroot *ifake,
struct xfs_btree_cur *bmap_cur,
const struct xfs_bmbt_irec *template,
xfs_extnum_t nextents)
{
struct xfs_btree_bload bmap_bload = {
.get_records = get_bmbt_records,
.claim_block = claim_block,
.iroot_size = iroot_size,
.max_dirty = dirty_blocks,
.leaf_slack = 1,
.node_slack = 1,
};
struct bmbt_data bd = {
.irec = {
.br_startoff = 0,
.br_startblock = template->br_startblock,
.br_blockcount = template->br_blockcount,
.br_state = XFS_EXT_NORM,
},
.iblocks = 0,
};
struct bmbt_resv *resv, *n;
int error;
error = -libxfs_btree_bload_compute_geometry(bmap_cur, &bmap_bload,
nextents);
if (error)
return error;
error = -libxfs_trans_reserve_more(*tpp, bmap_bload.nr_blocks, 0);
if (error)
return error;
INIT_LIST_HEAD(&bd.resv_list);
error = alloc_bmbt_blocks(tpp, ip, &bd, bmap_bload.nr_blocks);
if (error)
return error;
error = -libxfs_btree_bload(bmap_cur, &bmap_bload, &bd);
if (error)
goto out_resv_list;
ip->i_nblocks = bd.iblocks + (template->br_blockcount * nextents);
libxfs_trans_log_inode(*tpp, ip, XFS_ILOG_CORE);
out_resv_list:
/* Leak any unused blocks */
list_for_each_entry_safe(resv, n, &bd.resv_list, list) {
list_del(&resv->list);
kfree(resv);
}
return error;
}
static int
build_new_datafork(
struct xfs_trans **tpp,
struct xfs_inode *ip,
uint16_t dirty_blocks,
const struct xfs_bmbt_irec *irec,
xfs_extnum_t nextents)
{
struct xbtree_ifakeroot ifake = {};
struct xfs_btree_cur *bmap_cur;
int error;
error = set_nrext64(*tpp, ip, nextents);
if (error)
return error;
/* Set up staging for the new bmbt */
ifake.if_fork = kmem_cache_zalloc(xfs_ifork_cache, 0);
ifake.if_fork_size = xfs_inode_fork_size(ip, XFS_DATA_FORK);
bmap_cur = libxfs_bmbt_init_cursor(ip->i_mount, NULL, ip,
XFS_STAGING_FORK);
libxfs_btree_stage_ifakeroot(bmap_cur, &ifake);
/*
* Figure out the size and format of the new fork, then fill it with
* the bmap record we want.
*/
if (nextents <= XFS_IFORK_MAXEXT(ip, XFS_DATA_FORK)) {
ifake.if_fork->if_format = XFS_DINODE_FMT_EXTENTS;
error = populate_extents(*tpp, ip, &ifake, irec, nextents);
} else {
ifake.if_fork->if_format = XFS_DINODE_FMT_BTREE;
error = populate_btree(tpp, ip, dirty_blocks, &ifake, bmap_cur,
irec, nextents);
}
if (error) {
libxfs_btree_del_cursor(bmap_cur, 0);
goto err_ifork;
}
/* Install the new fork in the inode. */
libxfs_bmbt_commit_staged_btree(bmap_cur, *tpp, XFS_DATA_FORK);
libxfs_btree_del_cursor(bmap_cur, 0);
/* Mark filesystem as needsrepair */
dbprintf(_("filesystem is now inconsistent, xfs_repair required!\n"));
mp->m_sb.sb_features_incompat |= XFS_SB_FEAT_INCOMPAT_NEEDSREPAIR;
libxfs_log_sb(*tpp);
err_ifork:
kmem_cache_free(xfs_ifork_cache, ifake.if_fork);
return error;
}
static int
estimate_size(
struct xfs_inode *ip,
unsigned long long dirty_blocks,
xfs_extnum_t nextents)
{
struct xfs_btree_bload bmap_bload = {
.leaf_slack = 1,
.node_slack = 1,
};
struct xbtree_ifakeroot ifake = {};
struct xfs_btree_cur *bmap_cur;
int error;
/* FMT_EXTENTS means we report zero btblocks and zero height */
if (nextents <= XFS_IFORK_MAXEXT(ip, XFS_DATA_FORK))
goto report;
ifake.if_fork = kmem_cache_zalloc(xfs_ifork_cache, 0);
ifake.if_fork_size = xfs_inode_fork_size(ip, XFS_DATA_FORK);
bmap_cur = libxfs_bmbt_init_cursor(ip->i_mount, NULL, ip,
XFS_STAGING_FORK);
libxfs_btree_stage_ifakeroot(bmap_cur, &ifake);
error = -libxfs_btree_bload_compute_geometry(bmap_cur, &bmap_bload,
nextents);
libxfs_btree_del_cursor(bmap_cur, error);
kmem_cache_free(xfs_ifork_cache, ifake.if_fork);
if (error)
return error;
report:
dbprintf(_("ino 0x%llx nextents %llu btblocks %llu btheight %u dirty %u\n"),
ip->i_ino, nextents, bmap_bload.nr_blocks,
bmap_bload.btree_height, dirty_blocks);
return 0;
}
static int
bmapinflate_f(
int argc,
char **argv)
{
struct xfs_bmbt_irec irec;
struct xfs_inode *ip;
struct xfs_trans *tp;
char *p;
unsigned long long nextents = 0;
unsigned long long dirty_bytes = 60U << 20; /* 60MiB */
unsigned long long dirty_blocks;
unsigned int resblks;
bool estimate = false;
int c, error;
if (iocur_top->ino == NULLFSINO) {
dbprintf(_("no current inode\n"));
return 0;
}
optind = 0;
while ((c = getopt(argc, argv, "d:en:")) != EOF) {
switch (c) {
case 'e':
estimate = true;
break;
case 'n':
errno = 0;
nextents = strtoull(optarg, &p, 0);
if (errno) {
perror(optarg);
return 1;
}
break;
case 'd':
errno = 0;
dirty_bytes = cvtnum(mp->m_sb.sb_blocksize,
mp->m_sb.sb_sectsize, optarg);
if (errno) {
perror(optarg);
return 1;
}
break;
default:
dbprintf(_("bad option for bmap command\n"));
return 0;
}
}
dirty_blocks = XFS_B_TO_FSBT(mp, dirty_bytes);
if (dirty_blocks >= UINT16_MAX)
dirty_blocks = UINT16_MAX - 1;
error = -libxfs_iget(mp, NULL, iocur_top->ino, 0, &ip);
if (error) {
dbprintf(_("could not grab inode 0x%llx, err %d\n"),
iocur_top->ino, error);
return 1;
}
error = estimate_size(ip, dirty_blocks, nextents);
if (error)
goto out_irele;
if (estimate)
goto done;
resblks = libxfs_bmbt_calc_size(mp, nextents);
error = -libxfs_trans_alloc_inode(ip, &M_RES(mp)->tr_itruncate,
resblks, 0, false, &tp);
if (error) {
dbprintf(_("could not allocate transaction, err %d\n"),
error);
return 1;
}
error = find_mapping(tp, ip, &irec);
if (error)
goto out_cancel;
error = build_new_datafork(&tp, ip, dirty_blocks, &irec, nextents);
if (error) {
dbprintf(_("could not build new data fork, err %d\n"),
error);
exitcode = 1;
goto out_cancel;
}
error = -libxfs_trans_commit(tp);
if (error) {
dbprintf(_("could not commit transaction, err %d\n"),
error);
exitcode = 1;
return 1;
}
done:
libxfs_irele(ip);
return 0;
out_cancel:
libxfs_trans_cancel(tp);
out_irele:
libxfs_irele(ip);
return 1;
}
static const struct cmdinfo bmapinflate_cmd = {
.name = "bmapinflate",
.cfunc = bmapinflate_f,
.argmin = 0,
.argmax = -1,
.canpush = 0,
.args = N_("[-n copies] [-e] [-d maxdirty]"),
.oneline = N_("inflate bmbt by copying mappings"),
.help = bmapinflate_help,
};
void
bmapinflate_init(void)
{
if (!expert_mode)
return;
add_command(&bmapinflate_cmd);
}