blob: 48b71d3ca72c8e8b10b78dad10875a88faa51d2d [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright (C) 2019 Oracle. All Rights Reserved.
* Author: Darrick J. Wong <darrick.wong@oracle.com>
*/
#include <libxfs.h>
#include "btree.h"
#include "err_protos.h"
#include "libxlog.h"
#include "incore.h"
#include "globals.h"
#include "dinode.h"
#include "slab.h"
#include "rmap.h"
#include "rebuild.h"
/* Borrowed routines from xfs_scrub.c */
struct xfs_repair_bmap_extent {
struct xfs_rmap_irec rmap;
xfs_agnumber_t agno;
};
struct xfs_repair_bmap {
struct xfs_slab *extslab;
xfs_ino_t ino;
xfs_rfsblock_t bmbt_blocks;
int whichfork;
};
/* Record extents that belong to this inode's fork. */
STATIC int
xfs_repair_bmap_extent_fn(
struct xfs_btree_cur *cur,
struct xfs_rmap_irec *rec,
void *priv)
{
struct xfs_repair_bmap *rb = priv;
struct xfs_repair_bmap_extent rbe;
/* Skip extents which are not owned by this inode and fork. */
if (rec->rm_owner != rb->ino)
return 0;
else if (rb->whichfork == XFS_DATA_FORK &&
(rec->rm_flags & XFS_RMAP_ATTR_FORK))
return 0;
else if (rb->whichfork == XFS_ATTR_FORK &&
!(rec->rm_flags & XFS_RMAP_ATTR_FORK))
return 0;
else if (rec->rm_flags & XFS_RMAP_BMBT_BLOCK) {
rb->bmbt_blocks += rec->rm_blockcount;
return 0;
}
rbe.rmap = *rec;
rbe.agno = cur->bc_private.a.agno;
return slab_add(rb->extslab, &rbe);
}
/* Compare two bmap extents. */
static int
xfs_repair_bmap_extent_cmp(
const void *a,
const void *b)
{
const struct xfs_repair_bmap_extent *ap = a;
const struct xfs_repair_bmap_extent *bp = b;
if (ap->rmap.rm_offset > bp->rmap.rm_offset)
return 1;
else if (ap->rmap.rm_offset < bp->rmap.rm_offset)
return -1;
return 0;
}
/* Repair an inode fork. */
STATIC int
xfs_repair_bmap(
struct xfs_inode *ip,
struct xfs_trans **tpp,
int whichfork)
{
struct xfs_repair_bmap rb = {0};
struct xfs_bmbt_irec bmap;
struct xfs_mount *mp = ip->i_mount;
struct xfs_buf *agf_bp = NULL;
struct xfs_repair_bmap_extent *rbe;
struct xfs_btree_cur *cur;
struct xfs_slab_cursor *scur = NULL;
xfs_agnumber_t agno;
xfs_extlen_t extlen;
int baseflags;
int flags;
int error = 0;
ASSERT(whichfork == XFS_DATA_FORK || whichfork == XFS_ATTR_FORK);
/* Don't know how to repair the other fork formats. */
if (XFS_IFORK_FORMAT(ip, whichfork) != XFS_DINODE_FMT_EXTENTS &&
XFS_IFORK_FORMAT(ip, whichfork) != XFS_DINODE_FMT_BTREE)
return ENOTTY;
/* Only files, symlinks, and directories get to have data forks. */
if (whichfork == XFS_DATA_FORK && !S_ISREG(VFS_I(ip)->i_mode) &&
!S_ISDIR(VFS_I(ip)->i_mode) && !S_ISLNK(VFS_I(ip)->i_mode))
return EINVAL;
/* If we somehow have delalloc extents, forget it. */
if (whichfork == XFS_DATA_FORK && ip->i_delayed_blks)
return EBUSY;
/* We require the rmapbt to rebuild anything. */
if (!xfs_sb_version_hasrmapbt(&mp->m_sb))
return EOPNOTSUPP;
/* Don't know how to rebuild realtime data forks. */
if (XFS_IS_REALTIME_INODE(ip) && whichfork == XFS_DATA_FORK)
return EOPNOTSUPP;
/* Collect all reverse mappings for this fork's extents. */
init_slab(&rb.extslab, sizeof(*rbe));
rb.ino = ip->i_ino;
rb.whichfork = whichfork;
for (agno = 0; agno < mp->m_sb.sb_agcount; agno++) {
error = -libxfs_alloc_read_agf(mp, *tpp, agno, 0, &agf_bp);
if (error)
goto out;
cur = libxfs_rmapbt_init_cursor(mp, *tpp, agf_bp, agno);
error = -libxfs_rmap_query_all(cur, xfs_repair_bmap_extent_fn, &rb);
libxfs_btree_del_cursor(cur, error ? XFS_BTREE_ERROR :
XFS_BTREE_NOERROR);
if (error)
goto out;
}
/* Blow out the in-core fork and zero the on-disk fork. */
libxfs_trans_ijoin(*tpp, ip, 0);
if (XFS_IFORK_PTR(ip, whichfork) != NULL)
libxfs_idestroy_fork(ip, whichfork);
XFS_IFORK_FMT_SET(ip, whichfork, XFS_DINODE_FMT_EXTENTS);
XFS_IFORK_NEXT_SET(ip, whichfork, 0);
/* Reinitialize the on-disk fork. */
if (whichfork == XFS_DATA_FORK) {
memset(&ip->i_df, 0, sizeof(struct xfs_ifork));
ip->i_df.if_flags |= XFS_IFEXTENTS;
} else if (whichfork == XFS_ATTR_FORK) {
if (slab_count(rb.extslab) == 0)
ip->i_afp = NULL;
else {
ip->i_afp = kmem_zone_zalloc(xfs_ifork_zone, KM_NOFS);
ip->i_afp->if_flags |= XFS_IFEXTENTS;
}
}
libxfs_trans_log_inode(*tpp, ip, XFS_ILOG_CORE);
error = -libxfs_trans_roll_inode(tpp, ip);
if (error)
goto out;
baseflags = XFS_BMAPI_NORMAP;
if (whichfork == XFS_ATTR_FORK)
baseflags |= XFS_BMAPI_ATTRFORK;
/* "Remap" the extents into the fork. */
init_slab_cursor(rb.extslab, xfs_repair_bmap_extent_cmp, &scur);
rbe = pop_slab_cursor(scur);
while (rbe != NULL) {
/* Form the "new" mapping... */
bmap.br_startblock = XFS_AGB_TO_FSB(mp, rbe->agno,
rbe->rmap.rm_startblock);
bmap.br_startoff = rbe->rmap.rm_offset;
flags = 0;
if (rbe->rmap.rm_flags & XFS_RMAP_UNWRITTEN)
flags = XFS_BMAPI_PREALLOC;
while (rbe->rmap.rm_blockcount > 0) {
extlen = min(rbe->rmap.rm_blockcount, MAXEXTLEN);
bmap.br_blockcount = extlen;
/* Drop the block counter... */
ip->i_d.di_nblocks -= extlen;
/* Re-add the extent to the fork. */
error = -libxfs_bmapi_remap(*tpp, ip,
bmap.br_startoff, extlen,
bmap.br_startblock,
baseflags | flags);
if (error)
goto out;
bmap.br_startblock += extlen;
bmap.br_startoff += extlen;
rbe->rmap.rm_blockcount -= extlen;
error = -libxfs_defer_finish(tpp);
if (error)
goto out;
/* Make sure we roll the transaction. */
error = -libxfs_trans_roll_inode(tpp, ip);
if (error)
goto out;
}
rbe = pop_slab_cursor(scur);
}
free_slab_cursor(&scur);
free_slab(&rb.extslab);
/* Decrease nblocks to reflect the freed bmbt blocks. */
if (rb.bmbt_blocks) {
ip->i_d.di_nblocks -= rb.bmbt_blocks;
libxfs_trans_log_inode(*tpp, ip, XFS_ILOG_CORE);
error = -libxfs_trans_roll_inode(tpp, ip);
if (error)
goto out;
}
return error;
out:
if (scur)
free_slab_cursor(&scur);
if (rb.extslab)
free_slab(&rb.extslab);
return error;
}
/* Rebuild some inode's bmap. */
int
rebuild_bmap(
struct xfs_mount *mp,
xfs_ino_t ino,
int whichfork,
unsigned long nr_extents,
struct xfs_buf **ino_bpp,
struct xfs_dinode **dinop,
int *dirty)
{
struct xfs_inode *ip;
struct xfs_trans *tp;
struct xfs_buf *bp;
unsigned long long resblks;
xfs_daddr_t bp_bn;
int bp_length;
int error;
bp_bn = (*ino_bpp)->b_bn;
bp_length = (*ino_bpp)->b_length;
/*
* Bail out if the inode didn't think it had extents. Otherwise, zap
* it back to a zero-extents fork so that we can rebuild it.
*/
switch (whichfork) {
case XFS_DATA_FORK:
if ((*dinop)->di_nextents == 0)
return 0;
(*dinop)->di_format = XFS_DINODE_FMT_EXTENTS;
(*dinop)->di_nextents = 0;
libxfs_dinode_calc_crc(mp, *dinop);
*dirty = 1;
break;
case XFS_ATTR_FORK:
if ((*dinop)->di_anextents == 0)
return 0;
(*dinop)->di_aformat = XFS_DINODE_FMT_EXTENTS;
(*dinop)->di_anextents = 0;
libxfs_dinode_calc_crc(mp, *dinop);
*dirty = 1;
break;
default:
return -EINVAL;
}
resblks = libxfs_bmbt_calc_size(mp, nr_extents);
error = -libxfs_trans_alloc(mp, &M_RES(mp)->tr_itruncate,
resblks, 0, 0, &tp);
if (error)
return error;
/*
* Repair magic: the caller thinks it owns the buffer that backs
* the inode. The _iget call will want to grab the buffer to
* load the inode, so the buffer must be attached to the
* transaction. Furthermore, the _iget call drops the buffer
* once the inode is loaded, so if we've made any changes we
* have to log those to the transaction so they get written...
*/
libxfs_trans_bjoin(tp, *ino_bpp);
if (*dirty) {
libxfs_trans_log_buf(tp, *ino_bpp, 0, XFS_BUF_SIZE(*ino_bpp));
*dirty = 0;
}
/* ...then rebuild the bmbt... */
error = -libxfs_iget(mp, tp, ino, 0, &ip, &xfs_default_ifork_ops);
if (error)
goto out_trans;
error = xfs_repair_bmap(ip, &tp, whichfork);
if (error)
goto out_trans;
/*
* ...and then regrab the same inode buffer so that we return to
* the caller with the inode buffer locked and the dino pointer
* up to date. We bhold the buffer so that it doesn't get
* released during the transaction commit.
*/
error = -libxfs_imap_to_bp(mp, tp, &ip->i_imap, dinop, ino_bpp, 0, 0);
if (error)
goto out_trans;
libxfs_trans_bhold(tp, *ino_bpp);
error = -libxfs_trans_commit(tp);
libxfs_irele(ip);
return error;
out_trans:
libxfs_trans_cancel(tp);
libxfs_irele(ip);
/* Try to regrab the old buffer so we don't lose it... */
if (!libxfs_trans_read_buf(mp, NULL, mp->m_ddev_targp, bp_bn, bp_length,
0, &bp, NULL))
*ino_bpp = bp;
return error;
}