#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_log_format.h"
#include "xfs_trans.h"
#include "xfs_inode.h"
#include "xfs_ialloc.h"
#include "xfs_quota.h"
#include "xfs_bmap.h"
#include "xfs_bmap_btree.h"
#include "xfs_trans_space.h"
#include "xfs_dir2.h"
#include "xfs_exchrange.h"
#include "xfs_exchmaps.h"
#include "xfs_defer.h"
#include "xfs_symlink_remote.h"
#include "xfs_metafile.h"
#include "scrub/scrub.h"
#include "scrub/common.h"
#include "scrub/repair.h"
#include "scrub/trace.h"
#include "scrub/tempfile.h"
#include "scrub/tempexch.h"
#include "scrub/xfile.h"
int
xrep_tempfile_create(
struct xfs_scrub *sc,
uint16_t mode)
{
struct xfs_icreate_args args = {
.pip = sc->mp->m_rootip,
.mode = mode,
.flags = XFS_ICREATE_TMPFILE | XFS_ICREATE_UNLINKABLE,
};
struct xfs_mount *mp = sc->mp;
struct xfs_trans *tp = NULL;
struct xfs_dquot *udqp;
struct xfs_dquot *gdqp;
struct xfs_dquot *pdqp;
struct xfs_trans_res *tres;
struct xfs_inode *dp = mp->m_rootip;
xfs_ino_t ino;
unsigned int resblks;
bool is_dir = S_ISDIR(mode);
int error;
if (xfs_is_shutdown(mp))
return -EIO;
if (xfs_is_readonly(mp))
return -EROFS;
ASSERT(sc->tp == NULL);
ASSERT(sc->tempip == NULL);
error = xfs_icreate_dqalloc(&args, &udqp, &gdqp, &pdqp);
if (error)
return error;
if (is_dir) {
resblks = xfs_mkdir_space_res(mp, 0);
tres = &M_RES(mp)->tr_mkdir;
} else {
resblks = XFS_IALLOC_SPACE_RES(mp);
tres = &M_RES(mp)->tr_create_tmpfile;
}
error = xfs_trans_alloc_icreate(mp, tres, udqp, gdqp, pdqp, resblks,
&tp);
if (error)
goto out_release_dquots;
error = xfs_dialloc(&tp, &args, &ino);
if (error)
goto out_trans_cancel;
error = xfs_icreate(tp, ino, &args, &sc->tempip);
if (error)
goto out_trans_cancel;
sc->tempip->i_diflags &= ~(XFS_DIFLAG_REALTIME | XFS_DIFLAG_RTINHERIT);
xfs_trans_log_inode(tp, sc->tempip, XFS_ILOG_CORE);
VFS_I(sc->tempip)->i_flags |= S_PRIVATE;
VFS_I(sc->tempip)->i_opflags &= ~IOP_XATTR;
if (is_dir) {
error = xfs_dir_init(tp, sc->tempip, dp);
if (error)
goto out_trans_cancel;
} else if (S_ISLNK(VFS_I(sc->tempip)->i_mode)) {
error = xfs_symlink_write_target(tp, sc->tempip,
sc->tempip->i_ino, ".", 1, 0, 0);
if (error)
goto out_trans_cancel;
}
xfs_qm_vop_create_dqattach(tp, sc->tempip, udqp, gdqp, pdqp);
error = xfs_iunlink(tp, sc->tempip);
if (error)
goto out_trans_cancel;
error = xfs_trans_commit(tp);
if (error)
goto out_release_inode;
trace_xrep_tempfile_create(sc);
xfs_qm_dqrele(udqp);
xfs_qm_dqrele(gdqp);
xfs_qm_dqrele(pdqp);
xfs_iunlock(sc->tempip, XFS_ILOCK_EXCL);
xfs_setup_iops(sc->tempip);
xfs_finish_inode_setup(sc->tempip);
sc->temp_ilock_flags = 0;
return error;
out_trans_cancel:
xfs_trans_cancel(tp);
out_release_inode:
if (sc->tempip) {
xfs_iunlock(sc->tempip, XFS_ILOCK_EXCL);
xfs_finish_inode_setup(sc->tempip);
xchk_irele(sc, sc->tempip);
}
out_release_dquots:
xfs_qm_dqrele(udqp);
xfs_qm_dqrele(gdqp);
xfs_qm_dqrele(pdqp);
return error;
}
int
xrep_tempfile_adjust_directory_tree(
struct xfs_scrub *sc)
{
int error;
if (!sc->tempip)
return 0;
ASSERT(sc->tp == NULL);
ASSERT(!xfs_is_metadir_inode(sc->tempip));
if (!sc->ip || !xfs_is_metadir_inode(sc->ip))
return 0;
if (!S_ISDIR(VFS_I(sc->tempip)->i_mode) &&
!S_ISREG(VFS_I(sc->tempip)->i_mode))
return 0;
xfs_ilock(sc->tempip, XFS_IOLOCK_EXCL);
sc->temp_ilock_flags |= XFS_IOLOCK_EXCL;
error = xchk_trans_alloc(sc, 0);
if (error)
goto out_iolock;
xrep_tempfile_ilock(sc);
xfs_trans_ijoin(sc->tp, sc->tempip, 0);
xfs_trans_mod_dquot_byino(sc->tp, sc->tempip, XFS_TRANS_DQ_ICOUNT, -1L);
xfs_metafile_set_iflag(sc->tp, sc->tempip, XFS_METAFILE_UNKNOWN);
error = xrep_trans_commit(sc);
if (error)
goto out_ilock;
xfs_iflags_set(sc->tempip, XFS_IRECOVERY);
xfs_qm_dqdetach(sc->tempip);
out_ilock:
xrep_tempfile_iunlock(sc);
out_iolock:
xrep_tempfile_iounlock(sc);
return error;
}
STATIC int
xrep_tempfile_remove_metadir(
struct xfs_scrub *sc)
{
int error;
if (!sc->tempip || !xfs_is_metadir_inode(sc->tempip))
return 0;
ASSERT(sc->tp == NULL);
xfs_iflags_clear(sc->tempip, XFS_IRECOVERY);
xfs_ilock(sc->tempip, XFS_IOLOCK_EXCL);
sc->temp_ilock_flags |= XFS_IOLOCK_EXCL;
error = xchk_trans_alloc(sc, 0);
if (error)
goto out_iolock;
xrep_tempfile_ilock(sc);
xfs_trans_ijoin(sc->tp, sc->tempip, 0);
xfs_metafile_clear_iflag(sc->tp, sc->tempip);
error = xfs_qm_dqattach_locked(sc->tempip, false);
if (error)
goto out_cancel;
xfs_trans_mod_dquot_byino(sc->tp, sc->tempip, XFS_TRANS_DQ_ICOUNT, 1L);
xfs_trans_mod_dquot_byino(sc->tp, sc->tempip, XFS_TRANS_DQ_BCOUNT,
sc->tempip->i_nblocks);
error = xrep_trans_commit(sc);
goto out_ilock;
out_cancel:
xchk_trans_cancel(sc);
out_ilock:
xrep_tempfile_iunlock(sc);
out_iolock:
xrep_tempfile_iounlock(sc);
return error;
}
bool
xrep_tempfile_iolock_nowait(
struct xfs_scrub *sc)
{
if (xfs_ilock_nowait(sc->tempip, XFS_IOLOCK_EXCL)) {
sc->temp_ilock_flags |= XFS_IOLOCK_EXCL;
return true;
}
return false;
}
int
xrep_tempfile_iolock_polled(
struct xfs_scrub *sc)
{
int error = 0;
while (!xrep_tempfile_iolock_nowait(sc)) {
if (xchk_should_terminate(sc, &error))
return error;
delay(1);
}
return 0;
}
void
xrep_tempfile_iounlock(
struct xfs_scrub *sc)
{
xfs_iunlock(sc->tempip, XFS_IOLOCK_EXCL);
sc->temp_ilock_flags &= ~XFS_IOLOCK_EXCL;
}
void
xrep_tempfile_ilock(
struct xfs_scrub *sc)
{
sc->temp_ilock_flags |= XFS_ILOCK_EXCL;
xfs_ilock(sc->tempip, XFS_ILOCK_EXCL);
}
bool
xrep_tempfile_ilock_nowait(
struct xfs_scrub *sc)
{
if (xfs_ilock_nowait(sc->tempip, XFS_ILOCK_EXCL)) {
sc->temp_ilock_flags |= XFS_ILOCK_EXCL;
return true;
}
return false;
}
void
xrep_tempfile_iunlock(
struct xfs_scrub *sc)
{
xfs_iunlock(sc->tempip, XFS_ILOCK_EXCL);
sc->temp_ilock_flags &= ~XFS_ILOCK_EXCL;
}
void
xrep_tempfile_ilock_both(
struct xfs_scrub *sc)
{
xfs_lock_two_inodes(sc->ip, XFS_ILOCK_EXCL, sc->tempip, XFS_ILOCK_EXCL);
sc->ilock_flags |= XFS_ILOCK_EXCL;
sc->temp_ilock_flags |= XFS_ILOCK_EXCL;
}
void
xrep_tempfile_iunlock_both(
struct xfs_scrub *sc)
{
xrep_tempfile_iunlock(sc);
xchk_iunlock(sc, XFS_ILOCK_EXCL);
}
void
xrep_tempfile_rele(
struct xfs_scrub *sc)
{
if (!sc->tempip)
return;
if (sc->temp_ilock_flags) {
xfs_iunlock(sc->tempip, sc->temp_ilock_flags);
sc->temp_ilock_flags = 0;
}
xrep_tempfile_remove_metadir(sc);
xchk_irele(sc, sc->tempip);
sc->tempip = NULL;
}
int
xrep_tempfile_prealloc(
struct xfs_scrub *sc,
xfs_fileoff_t off,
xfs_filblks_t len)
{
struct xfs_bmbt_irec map;
xfs_fileoff_t end = off + len;
int error;
ASSERT(sc->tempip != NULL);
ASSERT(!XFS_NOT_DQATTACHED(sc->mp, sc->tempip));
for (; off < end; off = map.br_startoff + map.br_blockcount) {
int nmaps = 1;
error = xfs_bmapi_read(sc->tempip, off, end - off, &map, &nmaps,
XFS_DATA_FORK);
if (error)
return error;
if (nmaps == 0) {
ASSERT(nmaps != 0);
return -EFSCORRUPTED;
}
if (xfs_bmap_is_written_extent(&map))
continue;
if (map.br_startblock == DELAYSTARTBLOCK)
return -EFSCORRUPTED;
nmaps = 1;
error = xfs_bmapi_write(sc->tp, sc->tempip, off, end - off,
XFS_BMAPI_CONVERT | XFS_BMAPI_ZERO, 0, &map,
&nmaps);
if (error)
return error;
if (nmaps != 1)
return -EFSCORRUPTED;
trace_xrep_tempfile_prealloc(sc, XFS_DATA_FORK, &map);
error = xfs_defer_finish(&sc->tp);
if (error)
return error;
}
return 0;
}
int
xrep_tempfile_copyin(
struct xfs_scrub *sc,
xfs_fileoff_t off,
xfs_filblks_t len,
xrep_tempfile_copyin_fn prep_fn,
void *data)
{
LIST_HEAD(buffers_list);
struct xfs_mount *mp = sc->mp;
struct xfs_buf *bp;
xfs_fileoff_t flush_mask;
xfs_fileoff_t end = off + len;
loff_t pos = XFS_FSB_TO_B(mp, off);
int error = 0;
ASSERT(S_ISREG(VFS_I(sc->tempip)->i_mode));
flush_mask = XFS_B_TO_FSBT(mp, (1U << 19)) - 1;
for (; off < end; off++, pos += mp->m_sb.sb_blocksize) {
struct xfs_bmbt_irec map;
int nmaps = 1;
error = xfs_bmapi_read(sc->tempip, off, 1, &map, &nmaps, 0);
if (error)
goto out_err;
if (nmaps == 0 || !xfs_bmap_is_written_extent(&map)) {
error = -EFSCORRUPTED;
goto out_err;
}
error = xfs_trans_get_buf(sc->tp, mp->m_ddev_targp,
XFS_FSB_TO_DADDR(mp, map.br_startblock),
mp->m_bsize, 0, &bp);
if (error)
goto out_err;
trace_xrep_tempfile_copyin(sc, XFS_DATA_FORK, &map);
error = prep_fn(sc, bp, data);
if (error) {
xfs_trans_brelse(sc->tp, bp);
goto out_err;
}
xfs_buf_delwri_queue_here(bp, &buffers_list);
xfs_trans_brelse(sc->tp, bp);
if (!(off & flush_mask)) {
error = xfs_buf_delwri_submit(&buffers_list);
if (error)
goto out_err;
}
}
error = xfs_buf_delwri_submit(&buffers_list);
if (error)
goto out_err;
if (!list_empty(&buffers_list)) {
ASSERT(list_empty(&buffers_list));
error = -EIO;
goto out_err;
}
return 0;
out_err:
xfs_buf_delwri_cancel(&buffers_list);
return error;
}
int
xrep_tempfile_set_isize(
struct xfs_scrub *sc,
unsigned long long isize)
{
if (sc->tempip->i_disk_size == isize)
return 0;
sc->tempip->i_disk_size = isize;
i_size_write(VFS_I(sc->tempip), isize);
return xrep_tempfile_roll_trans(sc);
}
int
xrep_tempfile_roll_trans(
struct xfs_scrub *sc)
{
int error;
xfs_trans_log_inode(sc->tp, sc->tempip, XFS_ILOG_CORE);
error = xrep_roll_trans(sc);
if (error)
return error;
xfs_trans_ijoin(sc->tp, sc->tempip, 0);
return 0;
}
STATIC int
xrep_tempexch_prep_request(
struct xfs_scrub *sc,
int whichfork,
xfs_fileoff_t off,
xfs_filblks_t len,
struct xrep_tempexch *tx)
{
struct xfs_exchmaps_req *req = &tx->req;
memset(tx, 0, sizeof(struct xrep_tempexch));
if (whichfork == XFS_COW_FORK) {
ASSERT(0);
return -EINVAL;
}
if (!xfs_ifork_ptr(sc->ip, whichfork) ||
!xfs_ifork_ptr(sc->tempip, whichfork)) {
ASSERT(xfs_ifork_ptr(sc->ip, whichfork) != NULL);
ASSERT(xfs_ifork_ptr(sc->tempip, whichfork) != NULL);
return -EINVAL;
}
req->ip1 = sc->tempip;
req->ip2 = sc->ip;
req->startoff1 = off;
req->startoff2 = off;
switch (whichfork) {
case XFS_ATTR_FORK:
req->flags |= XFS_EXCHMAPS_ATTR_FORK;
break;
case XFS_DATA_FORK:
if (off == 0 && len == XFS_MAX_FILEOFF)
req->flags |= XFS_EXCHMAPS_SET_SIZES;
break;
}
req->blockcount = len;
return 0;
}
STATIC int
xrep_tempexch_estimate(
struct xfs_scrub *sc,
struct xrep_tempexch *tx)
{
struct xfs_exchmaps_req *req = &tx->req;
struct xfs_ifork *ifp;
struct xfs_ifork *tifp;
int whichfork = xfs_exchmaps_reqfork(req);
int state = 0;
ifp = xfs_ifork_ptr(sc->ip, whichfork);
if (ifp->if_format == XFS_DINODE_FMT_LOCAL)
state |= 1;
tifp = xfs_ifork_ptr(sc->tempip, whichfork);
if (tifp->if_format == XFS_DINODE_FMT_LOCAL)
state |= 2;
switch (state) {
case 0:
return xfs_exchrange_estimate(req);
case 1:
req->ip1_bcount = sc->tempip->i_nblocks;
req->ip2_bcount = 1;
req->nr_exchanges = 1 + tifp->if_nextents;
req->resblks = 1;
break;
case 2:
req->ip1_bcount = 1;
req->ip2_bcount = sc->ip->i_nblocks;
req->nr_exchanges = 1 + ifp->if_nextents;
req->resblks = 1;
break;
case 3:
req->ip1_bcount = 1;
req->ip2_bcount = 1;
req->nr_exchanges = 1;
req->resblks = 2;
break;
}
return xfs_exchmaps_estimate_overhead(req);
}
STATIC int
xrep_tempexch_reserve_quota(
struct xfs_scrub *sc,
const struct xrep_tempexch *tx)
{
struct xfs_trans *tp = sc->tp;
const struct xfs_exchmaps_req *req = &tx->req;
int64_t ddelta, rdelta;
int error;
if (!XFS_IS_QUOTA_ON(tp->t_mountp) || req->ip1 == req->ip2 ||
xfs_is_metadir_inode(req->ip1) ||
(req->ip1->i_udquot == req->ip2->i_udquot &&
req->ip1->i_gdquot == req->ip2->i_gdquot &&
req->ip1->i_pdquot == req->ip2->i_pdquot))
return 0;
ddelta = max_t(int64_t, 0, req->ip2_bcount - req->ip1_bcount);
rdelta = max_t(int64_t, 0, req->ip2_rtbcount - req->ip1_rtbcount);
error = xfs_trans_reserve_quota_nblks(tp, req->ip1,
ddelta + req->ip1_bcount, rdelta + req->ip1_rtbcount,
true);
if (error)
return error;
ddelta = max_t(int64_t, 0, req->ip1_bcount - req->ip2_bcount);
rdelta = max_t(int64_t, 0, req->ip1_rtbcount - req->ip2_rtbcount);
return xfs_trans_reserve_quota_nblks(tp, req->ip2,
ddelta + req->ip2_bcount, rdelta + req->ip2_rtbcount,
true);
}
int
xrep_tempexch_trans_reserve(
struct xfs_scrub *sc,
int whichfork,
xfs_fileoff_t off,
xfs_filblks_t len,
struct xrep_tempexch *tx)
{
int error;
ASSERT(sc->tp != NULL);
xfs_assert_ilocked(sc->ip, XFS_ILOCK_EXCL);
xfs_assert_ilocked(sc->tempip, XFS_ILOCK_EXCL);
error = xrep_tempexch_prep_request(sc, whichfork, off, len, tx);
if (error)
return error;
error = xfs_exchmaps_estimate(&tx->req);
if (error)
return error;
error = xfs_trans_reserve_more(sc->tp, tx->req.resblks, 0);
if (error)
return error;
return xrep_tempexch_reserve_quota(sc, tx);
}
int
xrep_tempexch_trans_alloc(
struct xfs_scrub *sc,
int whichfork,
struct xrep_tempexch *tx)
{
unsigned int flags = 0;
int error;
ASSERT(sc->tp == NULL);
ASSERT(xfs_has_exchange_range(sc->mp));
error = xrep_tempexch_prep_request(sc, whichfork, 0, XFS_MAX_FILEOFF,
tx);
if (error)
return error;
error = xrep_tempexch_estimate(sc, tx);
if (error)
return error;
if (xfs_has_lazysbcount(sc->mp))
flags |= XFS_TRANS_RES_FDBLKS;
error = xfs_trans_alloc(sc->mp, &M_RES(sc->mp)->tr_itruncate,
tx->req.resblks, 0, flags, &sc->tp);
if (error)
return error;
sc->temp_ilock_flags |= XFS_ILOCK_EXCL;
sc->ilock_flags |= XFS_ILOCK_EXCL;
xfs_exchrange_ilock(sc->tp, sc->ip, sc->tempip);
return xrep_tempexch_reserve_quota(sc, tx);
}
int
xrep_tempexch_contents(
struct xfs_scrub *sc,
struct xrep_tempexch *tx)
{
int error;
ASSERT(xfs_has_exchange_range(sc->mp));
xfs_exchange_mappings(sc->tp, &tx->req);
error = xfs_defer_finish(&sc->tp);
if (error)
return error;
if (tx->req.flags & XFS_EXCHMAPS_SET_SIZES) {
loff_t temp;
temp = i_size_read(VFS_I(sc->ip));
i_size_write(VFS_I(sc->ip), i_size_read(VFS_I(sc->tempip)));
i_size_write(VFS_I(sc->tempip), temp);
}
return 0;
}
void
xrep_tempfile_copyout_local(
struct xfs_scrub *sc,
int whichfork)
{
struct xfs_ifork *temp_ifp;
struct xfs_ifork *ifp;
unsigned int ilog_flags = XFS_ILOG_CORE;
temp_ifp = xfs_ifork_ptr(sc->tempip, whichfork);
ifp = xfs_ifork_ptr(sc->ip, whichfork);
ASSERT(temp_ifp != NULL);
ASSERT(ifp != NULL);
ASSERT(temp_ifp->if_format == XFS_DINODE_FMT_LOCAL);
ASSERT(ifp->if_format == XFS_DINODE_FMT_LOCAL);
switch (whichfork) {
case XFS_DATA_FORK:
ASSERT(sc->tempip->i_disk_size <=
xfs_inode_data_fork_size(sc->ip));
break;
case XFS_ATTR_FORK:
ASSERT(sc->tempip->i_forkoff >= sc->ip->i_forkoff);
break;
default:
ASSERT(0);
return;
}
xfs_idestroy_fork(ifp);
xfs_init_local_fork(sc->ip, whichfork, temp_ifp->if_data,
temp_ifp->if_bytes);
if (whichfork == XFS_DATA_FORK) {
i_size_write(VFS_I(sc->ip), i_size_read(VFS_I(sc->tempip)));
sc->ip->i_disk_size = sc->tempip->i_disk_size;
}
ilog_flags |= xfs_ilog_fdata(whichfork);
xfs_trans_log_inode(sc->tp, sc->ip, ilog_flags);
}
bool
xrep_is_tempfile(
const struct xfs_inode *ip)
{
const struct inode *inode = &ip->i_vnode;
struct xfs_mount *mp = ip->i_mount;
if (xfs_has_metadir(mp) && (ip->i_diflags2 & XFS_DIFLAG2_METADATA))
return __xfs_iflags_test(ip, XFS_IRECOVERY);
if (IS_PRIVATE(inode) && !(inode->i_opflags & IOP_XATTR))
return true;
return false;
}