#include <linux/iversion.h>
#include "xfs_platform.h"
#include "xfs_fs.h"
#include "xfs_shared.h"
#include "xfs_format.h"
#include "xfs_log_format.h"
#include "xfs_trans_resv.h"
#include "xfs_mount.h"
#include "xfs_defer.h"
#include "xfs_inode.h"
#include "xfs_dir2.h"
#include "xfs_attr.h"
#include "xfs_bit.h"
#include "xfs_trans_space.h"
#include "xfs_trans.h"
#include "xfs_buf_item.h"
#include "xfs_inode_item.h"
#include "xfs_iunlink_item.h"
#include "xfs_ialloc.h"
#include "xfs_bmap.h"
#include "xfs_bmap_util.h"
#include "xfs_errortag.h"
#include "xfs_error.h"
#include "xfs_quota.h"
#include "xfs_filestream.h"
#include "xfs_trace.h"
#include "xfs_icache.h"
#include "xfs_symlink.h"
#include "xfs_trans_priv.h"
#include "xfs_log.h"
#include "xfs_bmap_btree.h"
#include "xfs_reflink.h"
#include "xfs_ag.h"
#include "xfs_log_priv.h"
#include "xfs_health.h"
#include "xfs_pnfs.h"
#include "xfs_parent.h"
#include "xfs_xattr.h"
#include "xfs_inode_util.h"
#include "xfs_metafile.h"
struct kmem_cache *xfs_inode_cache;
uint
xfs_ilock_data_map_shared(
struct xfs_inode *ip)
{
uint lock_mode = XFS_ILOCK_SHARED;
if (xfs_need_iread_extents(&ip->i_df))
lock_mode = XFS_ILOCK_EXCL;
xfs_ilock(ip, lock_mode);
return lock_mode;
}
uint
xfs_ilock_attr_map_shared(
struct xfs_inode *ip)
{
uint lock_mode = XFS_ILOCK_SHARED;
if (xfs_inode_has_attr_fork(ip) && xfs_need_iread_extents(&ip->i_af))
lock_mode = XFS_ILOCK_EXCL;
xfs_ilock(ip, lock_mode);
return lock_mode;
}
static inline void
xfs_lock_flags_assert(
uint lock_flags)
{
ASSERT((lock_flags & (XFS_IOLOCK_SHARED | XFS_IOLOCK_EXCL)) !=
(XFS_IOLOCK_SHARED | XFS_IOLOCK_EXCL));
ASSERT((lock_flags & (XFS_MMAPLOCK_SHARED | XFS_MMAPLOCK_EXCL)) !=
(XFS_MMAPLOCK_SHARED | XFS_MMAPLOCK_EXCL));
ASSERT((lock_flags & (XFS_ILOCK_SHARED | XFS_ILOCK_EXCL)) !=
(XFS_ILOCK_SHARED | XFS_ILOCK_EXCL));
ASSERT((lock_flags & ~(XFS_LOCK_MASK | XFS_LOCK_SUBCLASS_MASK)) == 0);
ASSERT(lock_flags != 0);
}
void
xfs_ilock(
xfs_inode_t *ip,
uint lock_flags)
{
trace_xfs_ilock(ip, lock_flags, _RET_IP_);
xfs_lock_flags_assert(lock_flags);
if (lock_flags & XFS_IOLOCK_EXCL) {
down_write_nested(&VFS_I(ip)->i_rwsem,
XFS_IOLOCK_DEP(lock_flags));
} else if (lock_flags & XFS_IOLOCK_SHARED) {
down_read_nested(&VFS_I(ip)->i_rwsem,
XFS_IOLOCK_DEP(lock_flags));
}
if (lock_flags & XFS_MMAPLOCK_EXCL) {
down_write_nested(&VFS_I(ip)->i_mapping->invalidate_lock,
XFS_MMAPLOCK_DEP(lock_flags));
} else if (lock_flags & XFS_MMAPLOCK_SHARED) {
down_read_nested(&VFS_I(ip)->i_mapping->invalidate_lock,
XFS_MMAPLOCK_DEP(lock_flags));
}
if (lock_flags & XFS_ILOCK_EXCL)
down_write_nested(&ip->i_lock, XFS_ILOCK_DEP(lock_flags));
else if (lock_flags & XFS_ILOCK_SHARED)
down_read_nested(&ip->i_lock, XFS_ILOCK_DEP(lock_flags));
}
int
xfs_ilock_nowait(
xfs_inode_t *ip,
uint lock_flags)
{
trace_xfs_ilock_nowait(ip, lock_flags, _RET_IP_);
xfs_lock_flags_assert(lock_flags);
if (lock_flags & XFS_IOLOCK_EXCL) {
if (!down_write_trylock(&VFS_I(ip)->i_rwsem))
goto out;
} else if (lock_flags & XFS_IOLOCK_SHARED) {
if (!down_read_trylock(&VFS_I(ip)->i_rwsem))
goto out;
}
if (lock_flags & XFS_MMAPLOCK_EXCL) {
if (!down_write_trylock(&VFS_I(ip)->i_mapping->invalidate_lock))
goto out_undo_iolock;
} else if (lock_flags & XFS_MMAPLOCK_SHARED) {
if (!down_read_trylock(&VFS_I(ip)->i_mapping->invalidate_lock))
goto out_undo_iolock;
}
if (lock_flags & XFS_ILOCK_EXCL) {
if (!down_write_trylock(&ip->i_lock))
goto out_undo_mmaplock;
} else if (lock_flags & XFS_ILOCK_SHARED) {
if (!down_read_trylock(&ip->i_lock))
goto out_undo_mmaplock;
}
return 1;
out_undo_mmaplock:
if (lock_flags & XFS_MMAPLOCK_EXCL)
up_write(&VFS_I(ip)->i_mapping->invalidate_lock);
else if (lock_flags & XFS_MMAPLOCK_SHARED)
up_read(&VFS_I(ip)->i_mapping->invalidate_lock);
out_undo_iolock:
if (lock_flags & XFS_IOLOCK_EXCL)
up_write(&VFS_I(ip)->i_rwsem);
else if (lock_flags & XFS_IOLOCK_SHARED)
up_read(&VFS_I(ip)->i_rwsem);
out:
return 0;
}
void
xfs_iunlock(
xfs_inode_t *ip,
uint lock_flags)
{
xfs_lock_flags_assert(lock_flags);
if (lock_flags & XFS_IOLOCK_EXCL)
up_write(&VFS_I(ip)->i_rwsem);
else if (lock_flags & XFS_IOLOCK_SHARED)
up_read(&VFS_I(ip)->i_rwsem);
if (lock_flags & XFS_MMAPLOCK_EXCL)
up_write(&VFS_I(ip)->i_mapping->invalidate_lock);
else if (lock_flags & XFS_MMAPLOCK_SHARED)
up_read(&VFS_I(ip)->i_mapping->invalidate_lock);
if (lock_flags & XFS_ILOCK_EXCL)
up_write(&ip->i_lock);
else if (lock_flags & XFS_ILOCK_SHARED)
up_read(&ip->i_lock);
trace_xfs_iunlock(ip, lock_flags, _RET_IP_);
}
void
xfs_ilock_demote(
xfs_inode_t *ip,
uint lock_flags)
{
ASSERT(lock_flags & (XFS_IOLOCK_EXCL|XFS_MMAPLOCK_EXCL|XFS_ILOCK_EXCL));
ASSERT((lock_flags &
~(XFS_IOLOCK_EXCL|XFS_MMAPLOCK_EXCL|XFS_ILOCK_EXCL)) == 0);
if (lock_flags & XFS_ILOCK_EXCL)
downgrade_write(&ip->i_lock);
if (lock_flags & XFS_MMAPLOCK_EXCL)
downgrade_write(&VFS_I(ip)->i_mapping->invalidate_lock);
if (lock_flags & XFS_IOLOCK_EXCL)
downgrade_write(&VFS_I(ip)->i_rwsem);
trace_xfs_ilock_demote(ip, lock_flags, _RET_IP_);
}
void
xfs_assert_ilocked(
struct xfs_inode *ip,
uint lock_flags)
{
if (lock_flags & XFS_ILOCK_SHARED)
rwsem_assert_held(&ip->i_lock);
else if (lock_flags & XFS_ILOCK_EXCL)
rwsem_assert_held_write_nolockdep(&ip->i_lock);
if (lock_flags & XFS_MMAPLOCK_SHARED)
rwsem_assert_held(&VFS_I(ip)->i_mapping->invalidate_lock);
else if (lock_flags & XFS_MMAPLOCK_EXCL)
rwsem_assert_held_write(&VFS_I(ip)->i_mapping->invalidate_lock);
if (lock_flags & XFS_IOLOCK_SHARED)
rwsem_assert_held(&VFS_I(ip)->i_rwsem);
else if (lock_flags & XFS_IOLOCK_EXCL)
rwsem_assert_held_write(&VFS_I(ip)->i_rwsem);
}
#if (defined(DEBUG) || defined(XFS_WARN)) && defined(CONFIG_LOCKDEP)
static bool
xfs_lockdep_subclass_ok(
int subclass)
{
return subclass < MAX_LOCKDEP_SUBCLASSES;
}
#else
#define xfs_lockdep_subclass_ok(subclass) (true)
#endif
static inline uint
xfs_lock_inumorder(
uint lock_mode,
uint subclass)
{
uint class = 0;
ASSERT(!(lock_mode & XFS_ILOCK_PARENT));
ASSERT(xfs_lockdep_subclass_ok(subclass));
if (lock_mode & (XFS_IOLOCK_SHARED|XFS_IOLOCK_EXCL)) {
ASSERT(subclass <= XFS_IOLOCK_MAX_SUBCLASS);
class += subclass << XFS_IOLOCK_SHIFT;
}
if (lock_mode & (XFS_MMAPLOCK_SHARED|XFS_MMAPLOCK_EXCL)) {
ASSERT(subclass <= XFS_MMAPLOCK_MAX_SUBCLASS);
class += subclass << XFS_MMAPLOCK_SHIFT;
}
if (lock_mode & (XFS_ILOCK_SHARED|XFS_ILOCK_EXCL)) {
ASSERT(subclass <= XFS_ILOCK_MAX_SUBCLASS);
class += subclass << XFS_ILOCK_SHIFT;
}
return (lock_mode & ~XFS_LOCK_SUBCLASS_MASK) | class;
}
void
xfs_lock_inodes(
struct xfs_inode **ips,
int inodes,
uint lock_mode)
{
int attempts = 0;
uint i;
int j;
bool try_lock;
struct xfs_log_item *lp;
ASSERT(ips && inodes >= 2 && inodes <= 5);
ASSERT(lock_mode & (XFS_IOLOCK_EXCL | XFS_MMAPLOCK_EXCL |
XFS_ILOCK_EXCL));
ASSERT(!(lock_mode & (XFS_IOLOCK_SHARED | XFS_MMAPLOCK_SHARED |
XFS_ILOCK_SHARED)));
ASSERT(!(lock_mode & XFS_MMAPLOCK_EXCL) ||
inodes <= XFS_MMAPLOCK_MAX_SUBCLASS + 1);
ASSERT(!(lock_mode & XFS_ILOCK_EXCL) ||
inodes <= XFS_ILOCK_MAX_SUBCLASS + 1);
if (lock_mode & XFS_IOLOCK_EXCL) {
ASSERT(!(lock_mode & (XFS_MMAPLOCK_EXCL | XFS_ILOCK_EXCL)));
} else if (lock_mode & XFS_MMAPLOCK_EXCL)
ASSERT(!(lock_mode & XFS_ILOCK_EXCL));
again:
try_lock = false;
i = 0;
for (; i < inodes; i++) {
ASSERT(ips[i]);
if (i && (ips[i] == ips[i - 1]))
continue;
if (!try_lock) {
for (j = (i - 1); j >= 0 && !try_lock; j--) {
lp = &ips[j]->i_itemp->ili_item;
if (lp && test_bit(XFS_LI_IN_AIL, &lp->li_flags))
try_lock = true;
}
}
if (!try_lock) {
xfs_ilock(ips[i], xfs_lock_inumorder(lock_mode, i));
continue;
}
ASSERT(i != 0);
if (xfs_ilock_nowait(ips[i], xfs_lock_inumorder(lock_mode, i)))
continue;
attempts++;
for (j = i - 1; j >= 0; j--) {
if (j != (i - 1) && ips[j] == ips[j + 1])
continue;
xfs_iunlock(ips[j], lock_mode);
}
if ((attempts % 5) == 0) {
delay(1);
}
goto again;
}
}
void
xfs_lock_two_inodes(
struct xfs_inode *ip0,
uint ip0_mode,
struct xfs_inode *ip1,
uint ip1_mode)
{
int attempts = 0;
struct xfs_log_item *lp;
ASSERT(hweight32(ip0_mode) == 1);
ASSERT(hweight32(ip1_mode) == 1);
ASSERT(!(ip0_mode & (XFS_IOLOCK_SHARED|XFS_IOLOCK_EXCL)));
ASSERT(!(ip1_mode & (XFS_IOLOCK_SHARED|XFS_IOLOCK_EXCL)));
ASSERT(!(ip0_mode & (XFS_MMAPLOCK_SHARED|XFS_MMAPLOCK_EXCL)));
ASSERT(!(ip1_mode & (XFS_MMAPLOCK_SHARED|XFS_MMAPLOCK_EXCL)));
ASSERT(ip0->i_ino != ip1->i_ino);
if (ip0->i_ino > ip1->i_ino) {
swap(ip0, ip1);
swap(ip0_mode, ip1_mode);
}
again:
xfs_ilock(ip0, xfs_lock_inumorder(ip0_mode, 0));
lp = &ip0->i_itemp->ili_item;
if (lp && test_bit(XFS_LI_IN_AIL, &lp->li_flags)) {
if (!xfs_ilock_nowait(ip1, xfs_lock_inumorder(ip1_mode, 1))) {
xfs_iunlock(ip0, ip0_mode);
if ((++attempts % 5) == 0)
delay(1);
goto again;
}
} else {
xfs_ilock(ip1, xfs_lock_inumorder(ip1_mode, 1));
}
}
int
xfs_lookup(
struct xfs_inode *dp,
const struct xfs_name *name,
struct xfs_inode **ipp,
struct xfs_name *ci_name)
{
xfs_ino_t inum;
int error;
trace_xfs_lookup(dp, name);
if (xfs_is_shutdown(dp->i_mount))
return -EIO;
if (xfs_ifork_zapped(dp, XFS_DATA_FORK))
return -EIO;
error = xfs_dir_lookup(NULL, dp, name, &inum, ci_name);
if (error)
goto out_unlock;
error = xfs_iget(dp->i_mount, NULL, inum, 0, 0, ipp);
if (error)
goto out_free_name;
if (XFS_IS_CORRUPT(dp->i_mount, xfs_is_metadir_inode(*ipp))) {
xfs_fs_mark_sick(dp->i_mount, XFS_SICK_FS_METADIR);
error = -EFSCORRUPTED;
goto out_irele;
}
return 0;
out_irele:
xfs_irele(*ipp);
out_free_name:
if (ci_name)
kfree(ci_name->name);
out_unlock:
*ipp = NULL;
return error;
}
int
xfs_icreate(
struct xfs_trans *tp,
xfs_ino_t ino,
const struct xfs_icreate_args *args,
struct xfs_inode **ipp)
{
struct xfs_mount *mp = tp->t_mountp;
struct xfs_inode *ip = NULL;
int error;
error = xfs_iget(mp, tp, ino, XFS_IGET_CREATE, XFS_ILOCK_EXCL, &ip);
if (error)
return error;
ASSERT(ip != NULL);
xfs_trans_ijoin(tp, ip, 0);
xfs_inode_init(tp, args, ip);
xfs_setup_inode(ip);
*ipp = ip;
return 0;
}
int
xfs_icreate_dqalloc(
const struct xfs_icreate_args *args,
struct xfs_dquot **udqpp,
struct xfs_dquot **gdqpp,
struct xfs_dquot **pdqpp)
{
struct inode *dir = VFS_I(args->pip);
kuid_t uid = GLOBAL_ROOT_UID;
kgid_t gid = GLOBAL_ROOT_GID;
prid_t prid = 0;
unsigned int flags = XFS_QMOPT_QUOTALL;
if (args->idmap) {
uid = mapped_fsuid(args->idmap, i_user_ns(dir));
gid = mapped_fsgid(args->idmap, i_user_ns(dir));
prid = xfs_get_initial_prid(args->pip);
flags |= XFS_QMOPT_INHERIT;
}
*udqpp = *gdqpp = *pdqpp = NULL;
return xfs_qm_vop_dqalloc(args->pip, uid, gid, prid, flags, udqpp,
gdqpp, pdqpp);
}
int
xfs_create(
const struct xfs_icreate_args *args,
struct xfs_name *name,
struct xfs_inode **ipp)
{
struct xfs_inode *dp = args->pip;
struct xfs_dir_update du = {
.dp = dp,
.name = name,
};
struct xfs_mount *mp = dp->i_mount;
struct xfs_trans *tp = NULL;
struct xfs_dquot *udqp;
struct xfs_dquot *gdqp;
struct xfs_dquot *pdqp;
struct xfs_trans_res *tres;
xfs_ino_t ino;
bool unlock_dp_on_error = false;
bool is_dir = S_ISDIR(args->mode);
uint resblks;
int error;
trace_xfs_create(dp, name);
if (xfs_is_shutdown(mp))
return -EIO;
if (xfs_ifork_zapped(dp, XFS_DATA_FORK))
return -EIO;
error = xfs_icreate_dqalloc(args, &udqp, &gdqp, &pdqp);
if (error)
return error;
if (is_dir) {
resblks = xfs_mkdir_space_res(mp, name->len);
tres = &M_RES(mp)->tr_mkdir;
} else {
resblks = xfs_create_space_res(mp, name->len);
tres = &M_RES(mp)->tr_create;
}
error = xfs_parent_start(mp, &du.ppargs);
if (error)
goto out_release_dquots;
error = xfs_trans_alloc_icreate(mp, tres, udqp, gdqp, pdqp, resblks,
&tp);
if (error == -ENOSPC) {
xfs_flush_inodes(mp);
error = xfs_trans_alloc_icreate(mp, tres, udqp, gdqp, pdqp,
resblks, &tp);
}
if (error)
goto out_parent;
xfs_ilock(dp, XFS_ILOCK_EXCL | XFS_ILOCK_PARENT);
unlock_dp_on_error = true;
error = xfs_dialloc(&tp, args, &ino);
if (!error)
error = xfs_icreate(tp, ino, args, &du.ip);
if (error)
goto out_trans_cancel;
xfs_trans_ijoin(tp, dp, 0);
error = xfs_dir_create_child(tp, resblks, &du);
if (error)
goto out_trans_cancel;
if (xfs_has_wsync(mp) || xfs_has_dirsync(mp))
xfs_trans_set_sync(tp);
xfs_qm_vop_create_dqattach(tp, du.ip, udqp, gdqp, pdqp);
error = xfs_trans_commit(tp);
if (error)
goto out_release_inode;
xfs_qm_dqrele(udqp);
xfs_qm_dqrele(gdqp);
xfs_qm_dqrele(pdqp);
*ipp = du.ip;
xfs_iunlock(du.ip, XFS_ILOCK_EXCL);
xfs_iunlock(dp, XFS_ILOCK_EXCL);
xfs_parent_finish(mp, du.ppargs);
return 0;
out_trans_cancel:
xfs_trans_cancel(tp);
out_release_inode:
if (du.ip) {
xfs_iunlock(du.ip, XFS_ILOCK_EXCL);
xfs_finish_inode_setup(du.ip);
xfs_irele(du.ip);
}
out_parent:
xfs_parent_finish(mp, du.ppargs);
out_release_dquots:
xfs_qm_dqrele(udqp);
xfs_qm_dqrele(gdqp);
xfs_qm_dqrele(pdqp);
if (unlock_dp_on_error)
xfs_iunlock(dp, XFS_ILOCK_EXCL);
return error;
}
int
xfs_create_tmpfile(
const struct xfs_icreate_args *args,
struct xfs_inode **ipp)
{
struct xfs_inode *dp = args->pip;
struct xfs_mount *mp = dp->i_mount;
struct xfs_inode *ip = NULL;
struct xfs_trans *tp = NULL;
struct xfs_dquot *udqp;
struct xfs_dquot *gdqp;
struct xfs_dquot *pdqp;
struct xfs_trans_res *tres;
xfs_ino_t ino;
uint resblks;
int error;
ASSERT(args->flags & XFS_ICREATE_TMPFILE);
if (xfs_is_shutdown(mp))
return -EIO;
error = xfs_icreate_dqalloc(args, &udqp, &gdqp, &pdqp);
if (error)
return error;
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)
error = xfs_icreate(tp, ino, args, &ip);
if (error)
goto out_trans_cancel;
if (xfs_has_wsync(mp))
xfs_trans_set_sync(tp);
xfs_qm_vop_create_dqattach(tp, ip, udqp, gdqp, pdqp);
error = xfs_iunlink(tp, ip);
if (error)
goto out_trans_cancel;
error = xfs_trans_commit(tp);
if (error)
goto out_release_inode;
xfs_qm_dqrele(udqp);
xfs_qm_dqrele(gdqp);
xfs_qm_dqrele(pdqp);
*ipp = ip;
xfs_iunlock(ip, XFS_ILOCK_EXCL);
return 0;
out_trans_cancel:
xfs_trans_cancel(tp);
out_release_inode:
if (ip) {
xfs_iunlock(ip, XFS_ILOCK_EXCL);
xfs_finish_inode_setup(ip);
xfs_irele(ip);
}
out_release_dquots:
xfs_qm_dqrele(udqp);
xfs_qm_dqrele(gdqp);
xfs_qm_dqrele(pdqp);
return error;
}
static inline int
xfs_projid_differ(
struct xfs_inode *tdp,
struct xfs_inode *sip)
{
if (unlikely((tdp->i_diflags & XFS_DIFLAG_PROJINHERIT) &&
tdp->i_projid != sip->i_projid)) {
if (!special_file(VFS_I(sip)->i_mode) ||
sip->i_projid != 0) {
return -EXDEV;
}
}
return 0;
}
int
xfs_link(
struct xfs_inode *tdp,
struct xfs_inode *sip,
struct xfs_name *target_name)
{
struct xfs_dir_update du = {
.dp = tdp,
.name = target_name,
.ip = sip,
};
struct xfs_mount *mp = tdp->i_mount;
struct xfs_trans *tp;
int error, nospace_error = 0;
int resblks;
trace_xfs_link(tdp, target_name);
ASSERT(!S_ISDIR(VFS_I(sip)->i_mode));
if (xfs_is_shutdown(mp))
return -EIO;
if (xfs_ifork_zapped(tdp, XFS_DATA_FORK))
return -EIO;
error = xfs_qm_dqattach(sip);
if (error)
goto std_return;
error = xfs_qm_dqattach(tdp);
if (error)
goto std_return;
error = xfs_parent_start(mp, &du.ppargs);
if (error)
goto std_return;
resblks = xfs_link_space_res(mp, target_name->len);
error = xfs_trans_alloc_dir(tdp, &M_RES(mp)->tr_link, sip, &resblks,
&tp, &nospace_error);
if (error)
goto out_parent;
if (du.ppargs && nospace_error) {
error = nospace_error;
goto error_return;
}
error = xfs_projid_differ(tdp, sip);
if (error)
goto error_return;
error = xfs_dir_add_child(tp, resblks, &du);
if (error)
goto error_return;
if (xfs_has_wsync(mp) || xfs_has_dirsync(mp))
xfs_trans_set_sync(tp);
error = xfs_trans_commit(tp);
xfs_iunlock(tdp, XFS_ILOCK_EXCL);
xfs_iunlock(sip, XFS_ILOCK_EXCL);
xfs_parent_finish(mp, du.ppargs);
return error;
error_return:
xfs_trans_cancel(tp);
xfs_iunlock(tdp, XFS_ILOCK_EXCL);
xfs_iunlock(sip, XFS_ILOCK_EXCL);
out_parent:
xfs_parent_finish(mp, du.ppargs);
std_return:
if (error == -ENOSPC && nospace_error)
error = nospace_error;
return error;
}
static void
xfs_itruncate_clear_reflink_flags(
struct xfs_inode *ip)
{
struct xfs_ifork *dfork;
struct xfs_ifork *cfork;
if (!xfs_is_reflink_inode(ip))
return;
dfork = xfs_ifork_ptr(ip, XFS_DATA_FORK);
cfork = xfs_ifork_ptr(ip, XFS_COW_FORK);
if (dfork->if_bytes == 0 && cfork->if_bytes == 0)
ip->i_diflags2 &= ~XFS_DIFLAG2_REFLINK;
if (cfork->if_bytes == 0)
xfs_inode_clear_cowblocks_tag(ip);
}
int
xfs_itruncate_extents_flags(
struct xfs_trans **tpp,
struct xfs_inode *ip,
int whichfork,
xfs_fsize_t new_size,
int flags)
{
struct xfs_mount *mp = ip->i_mount;
struct xfs_trans *tp = *tpp;
xfs_fileoff_t first_unmap_block;
int error = 0;
xfs_assert_ilocked(ip, XFS_ILOCK_EXCL);
if (icount_read(VFS_I(ip)))
xfs_assert_ilocked(ip, XFS_IOLOCK_EXCL);
if (whichfork == XFS_DATA_FORK)
ASSERT(new_size <= XFS_ISIZE(ip));
ASSERT(tp->t_flags & XFS_TRANS_PERM_LOG_RES);
ASSERT(ip->i_itemp != NULL);
ASSERT(ip->i_itemp->ili_lock_flags == 0);
ASSERT(!XFS_NOT_DQATTACHED(mp, ip));
trace_xfs_itruncate_extents_start(ip, new_size);
flags |= xfs_bmapi_aflag(whichfork);
first_unmap_block = XFS_B_TO_FSB(mp, (xfs_ufsize_t)new_size);
if (!xfs_verify_fileoff(mp, first_unmap_block)) {
WARN_ON_ONCE(first_unmap_block > XFS_MAX_FILEOFF);
return 0;
}
error = xfs_bunmapi_range(&tp, ip, flags, first_unmap_block,
XFS_MAX_FILEOFF);
if (error)
goto out;
if (whichfork == XFS_DATA_FORK) {
error = xfs_reflink_cancel_cow_blocks(ip, &tp,
first_unmap_block, XFS_MAX_FILEOFF, true);
if (error)
goto out;
xfs_itruncate_clear_reflink_flags(ip);
}
xfs_trans_log_inode(tp, ip, XFS_ILOG_CORE);
trace_xfs_itruncate_extents_end(ip, new_size);
out:
*tpp = tp;
return error;
}
STATIC void
xfs_inactive_dir(
struct xfs_inode *dp)
{
struct xfs_iext_cursor icur;
struct xfs_bmbt_irec got;
struct xfs_mount *mp = dp->i_mount;
struct xfs_da_geometry *geo = mp->m_dir_geo;
struct xfs_ifork *ifp = xfs_ifork_ptr(dp, XFS_DATA_FORK);
xfs_fileoff_t off;
for_each_xfs_iext(ifp, &icur, &got) {
for (off = round_up(got.br_startoff, geo->fsbcount);
off < got.br_startoff + got.br_blockcount;
off += geo->fsbcount) {
struct xfs_buf *bp = NULL;
xfs_fsblock_t fsbno;
int error;
fsbno = (off - got.br_startoff) + got.br_startblock;
error = xfs_buf_incore(mp->m_ddev_targp,
XFS_FSB_TO_DADDR(mp, fsbno),
XFS_FSB_TO_BB(mp, geo->fsbcount),
XBF_LIVESCAN, &bp);
if (error)
continue;
xfs_buf_stale(bp);
xfs_buf_relse(bp);
}
}
}
STATIC int
xfs_inactive_truncate(
struct xfs_inode *ip)
{
struct xfs_mount *mp = ip->i_mount;
struct xfs_trans *tp;
int error;
error = xfs_trans_alloc(mp, &M_RES(mp)->tr_itruncate, 0, 0, 0, &tp);
if (error) {
ASSERT(xfs_is_shutdown(mp));
return error;
}
xfs_ilock(ip, XFS_ILOCK_EXCL);
xfs_trans_ijoin(tp, ip, 0);
ip->i_disk_size = 0;
xfs_trans_log_inode(tp, ip, XFS_ILOG_CORE);
error = xfs_itruncate_extents(&tp, ip, XFS_DATA_FORK, 0);
if (error)
goto error_trans_cancel;
ASSERT(ip->i_df.if_nextents == 0);
error = xfs_trans_commit(tp);
if (error)
goto error_unlock;
xfs_iunlock(ip, XFS_ILOCK_EXCL);
return 0;
error_trans_cancel:
xfs_trans_cancel(tp);
error_unlock:
xfs_iunlock(ip, XFS_ILOCK_EXCL);
return error;
}
STATIC int
xfs_inactive_ifree(
struct xfs_inode *ip)
{
struct xfs_mount *mp = ip->i_mount;
struct xfs_trans *tp;
int error;
if (unlikely(mp->m_finobt_nores)) {
error = xfs_trans_alloc(mp, &M_RES(mp)->tr_ifree,
XFS_IFREE_SPACE_RES(mp), 0, XFS_TRANS_RESERVE,
&tp);
} else {
error = xfs_trans_alloc(mp, &M_RES(mp)->tr_ifree, 0, 0, 0, &tp);
}
if (error) {
if (error == -ENOSPC) {
xfs_warn_ratelimited(mp,
"Failed to remove inode(s) from unlinked list. "
"Please free space, unmount and run xfs_repair.");
} else {
ASSERT(xfs_is_shutdown(mp));
}
return error;
}
xfs_ilock(ip, XFS_ILOCK_EXCL);
xfs_trans_ijoin(tp, ip, XFS_ILOCK_EXCL);
error = xfs_ifree(tp, ip);
xfs_assert_ilocked(ip, XFS_ILOCK_EXCL);
if (error) {
if (!xfs_is_shutdown(mp)) {
xfs_notice(mp, "%s: xfs_ifree returned error %d",
__func__, error);
xfs_force_shutdown(mp, SHUTDOWN_META_IO_ERROR);
}
xfs_trans_cancel(tp);
return error;
}
xfs_trans_mod_dquot_byino(tp, ip, XFS_TRANS_DQ_ICOUNT, -1);
return xfs_trans_commit(tp);
}
bool
xfs_inode_needs_inactive(
struct xfs_inode *ip)
{
struct xfs_mount *mp = ip->i_mount;
struct xfs_ifork *cow_ifp = xfs_ifork_ptr(ip, XFS_COW_FORK);
if (VFS_I(ip)->i_mode == 0)
return false;
if (xfs_is_readonly(mp) && !xlog_recovery_needed(mp->m_log))
return false;
if (xfs_is_shutdown(mp) || xfs_has_norecovery(mp))
return false;
if (xfs_is_internal_inode(ip))
return false;
if (cow_ifp && cow_ifp->if_bytes > 0)
return true;
if (VFS_I(ip)->i_nlink == 0)
return true;
return xfs_can_free_eofblocks(ip);
}
static inline void
xfs_inactive_health(
struct xfs_inode *ip)
{
struct xfs_mount *mp = ip->i_mount;
struct xfs_perag *pag;
unsigned int sick;
unsigned int checked;
xfs_inode_measure_sickness(ip, &sick, &checked);
if (!sick)
return;
trace_xfs_inode_unfixed_corruption(ip, sick);
if (sick & XFS_SICK_INO_FORGET)
return;
pag = xfs_perag_get(mp, XFS_INO_TO_AGNO(mp, ip->i_ino));
if (!pag) {
ASSERT(0);
return;
}
xfs_ag_mark_sick(pag, XFS_SICK_AG_INODES);
xfs_perag_put(pag);
}
int
xfs_inactive(
xfs_inode_t *ip)
{
struct xfs_mount *mp;
int error = 0;
int truncate = 0;
if (VFS_I(ip)->i_mode == 0) {
ASSERT(ip->i_df.if_broot_bytes == 0);
goto out;
}
mp = ip->i_mount;
ASSERT(!xfs_iflags_test(ip, XFS_IRECOVERY));
xfs_inactive_health(ip);
if (xfs_is_readonly(mp) && !xlog_recovery_needed(mp->m_log))
goto out;
if (xfs_is_internal_inode(ip))
goto out;
if (xfs_inode_has_cow_data(ip)) {
error = xfs_reflink_cancel_cow_range(ip, 0, NULLFILEOFF, true);
if (error)
goto out;
}
if (VFS_I(ip)->i_nlink != 0) {
if (xfs_can_free_eofblocks(ip))
error = xfs_free_eofblocks(ip);
goto out;
}
if (S_ISREG(VFS_I(ip)->i_mode) &&
(ip->i_disk_size != 0 || XFS_ISIZE(ip) != 0 ||
xfs_inode_has_filedata(ip)))
truncate = 1;
if (xfs_iflags_test(ip, XFS_IQUOTAUNCHECKED)) {
xfs_qm_dqdetach(ip);
} else {
error = xfs_qm_dqattach(ip);
if (error)
goto out;
}
if (S_ISDIR(VFS_I(ip)->i_mode) && ip->i_df.if_nextents > 0) {
xfs_inactive_dir(ip);
truncate = 1;
}
if (S_ISLNK(VFS_I(ip)->i_mode))
error = xfs_inactive_symlink(ip);
else if (truncate)
error = xfs_inactive_truncate(ip);
if (error)
goto out;
if (xfs_inode_has_attr_fork(ip)) {
error = xfs_attr_inactive(ip);
if (error)
goto out;
}
ASSERT(ip->i_forkoff == 0);
error = xfs_inactive_ifree(ip);
out:
xfs_qm_dqdetach(ip);
return error;
}
struct xfs_inode *
xfs_iunlink_lookup(
struct xfs_perag *pag,
xfs_agino_t agino)
{
struct xfs_inode *ip;
rcu_read_lock();
ip = radix_tree_lookup(&pag->pag_ici_root, agino);
if (!ip) {
rcu_read_unlock();
return NULL;
}
if (WARN_ON_ONCE(!ip->i_ino)) {
rcu_read_unlock();
return NULL;
}
ASSERT(!xfs_iflags_test(ip, XFS_IRECLAIMABLE | XFS_IRECLAIM));
rcu_read_unlock();
return ip;
}
int
xfs_iunlink_reload_next(
struct xfs_trans *tp,
struct xfs_buf *agibp,
xfs_agino_t prev_agino,
xfs_agino_t next_agino)
{
struct xfs_perag *pag = agibp->b_pag;
struct xfs_mount *mp = pag_mount(pag);
struct xfs_inode *next_ip = NULL;
int error;
ASSERT(next_agino != NULLAGINO);
#ifdef DEBUG
rcu_read_lock();
next_ip = radix_tree_lookup(&pag->pag_ici_root, next_agino);
ASSERT(next_ip == NULL);
rcu_read_unlock();
#endif
xfs_info_ratelimited(mp,
"Found unrecovered unlinked inode 0x%x in AG 0x%x. Initiating recovery.",
next_agino, pag_agno(pag));
error = xfs_iget(mp, tp, xfs_agino_to_ino(pag, next_agino),
XFS_IGET_UNTRUSTED, 0, &next_ip);
if (error) {
xfs_ag_mark_sick(pag, XFS_SICK_AG_AGI);
return error;
}
if (VFS_I(next_ip)->i_nlink != 0) {
xfs_ag_mark_sick(pag, XFS_SICK_AG_AGI);
error = -EFSCORRUPTED;
goto rele;
}
next_ip->i_prev_unlinked = prev_agino;
trace_xfs_iunlink_reload_next(next_ip);
rele:
ASSERT(!(inode_state_read_once(VFS_I(next_ip)) & I_DONTCACHE));
if (xfs_is_quotacheck_running(mp) && next_ip)
xfs_iflags_set(next_ip, XFS_IQUOTAUNCHECKED);
xfs_irele(next_ip);
return error;
}
static void
xfs_ifree_mark_inode_stale(
struct xfs_perag *pag,
struct xfs_inode *free_ip,
xfs_ino_t inum)
{
struct xfs_mount *mp = pag_mount(pag);
struct xfs_inode_log_item *iip;
struct xfs_inode *ip;
retry:
rcu_read_lock();
ip = radix_tree_lookup(&pag->pag_ici_root, XFS_INO_TO_AGINO(mp, inum));
if (!ip) {
rcu_read_unlock();
return;
}
spin_lock(&ip->i_flags_lock);
if (ip->i_ino != inum || __xfs_iflags_test(ip, XFS_ISTALE))
goto out_iflags_unlock;
if (ip != free_ip) {
if (!xfs_ilock_nowait(ip, XFS_ILOCK_EXCL)) {
spin_unlock(&ip->i_flags_lock);
rcu_read_unlock();
delay(1);
goto retry;
}
}
ip->i_flags |= XFS_ISTALE;
iip = ip->i_itemp;
if (__xfs_iflags_test(ip, XFS_IFLUSHING)) {
ASSERT(!list_empty(&iip->ili_item.li_bio_list));
ASSERT(iip->ili_last_fields || xlog_is_shutdown(mp->m_log));
goto out_iunlock;
}
if (!iip || list_empty(&iip->ili_item.li_bio_list))
goto out_iunlock;
__xfs_iflags_set(ip, XFS_IFLUSHING);
spin_unlock(&ip->i_flags_lock);
rcu_read_unlock();
spin_lock(&iip->ili_lock);
iip->ili_last_fields = iip->ili_fields;
iip->ili_fields = 0;
spin_unlock(&iip->ili_lock);
ASSERT(iip->ili_last_fields);
if (ip != free_ip)
xfs_iunlock(ip, XFS_ILOCK_EXCL);
return;
out_iunlock:
if (ip != free_ip)
xfs_iunlock(ip, XFS_ILOCK_EXCL);
out_iflags_unlock:
spin_unlock(&ip->i_flags_lock);
rcu_read_unlock();
}
static int
xfs_ifree_cluster(
struct xfs_trans *tp,
struct xfs_perag *pag,
struct xfs_inode *free_ip,
struct xfs_icluster *xic)
{
struct xfs_mount *mp = free_ip->i_mount;
struct xfs_ino_geometry *igeo = M_IGEO(mp);
struct xfs_buf *bp;
xfs_daddr_t blkno;
xfs_ino_t inum = xic->first_ino;
int nbufs;
int i, j;
int ioffset;
int error;
nbufs = igeo->ialloc_blks / igeo->blocks_per_cluster;
for (j = 0; j < nbufs; j++, inum += igeo->inodes_per_cluster) {
ioffset = inum - xic->first_ino;
if ((xic->alloc & XFS_INOBT_MASK(ioffset)) == 0) {
ASSERT(ioffset % igeo->inodes_per_cluster == 0);
continue;
}
blkno = XFS_AGB_TO_DADDR(mp, XFS_INO_TO_AGNO(mp, inum),
XFS_INO_TO_AGBNO(mp, inum));
error = xfs_trans_get_buf(tp, mp->m_ddev_targp, blkno,
mp->m_bsize * igeo->blocks_per_cluster, 0, &bp);
if (error)
return error;
bp->b_flags |= XBF_DONE;
bp->b_ops = &xfs_inode_buf_ops;
for (i = 0; i < igeo->inodes_per_cluster; i++)
xfs_ifree_mark_inode_stale(pag, free_ip, inum + i);
xfs_trans_stale_inode_buf(tp, bp);
xfs_trans_binval(tp, bp);
}
return 0;
}
int
xfs_ifree(
struct xfs_trans *tp,
struct xfs_inode *ip)
{
struct xfs_mount *mp = ip->i_mount;
struct xfs_perag *pag;
struct xfs_icluster xic = { 0 };
struct xfs_inode_log_item *iip = ip->i_itemp;
int error;
xfs_assert_ilocked(ip, XFS_ILOCK_EXCL);
ASSERT(VFS_I(ip)->i_nlink == 0);
ASSERT(ip->i_df.if_nextents == 0);
ASSERT(ip->i_disk_size == 0 || !S_ISREG(VFS_I(ip)->i_mode));
ASSERT(ip->i_nblocks == 0);
pag = xfs_perag_get(mp, XFS_INO_TO_AGNO(mp, ip->i_ino));
error = xfs_inode_uninit(tp, pag, ip, &xic);
if (error)
goto out;
if (xfs_iflags_test(ip, XFS_IPRESERVE_DM_FIELDS))
xfs_iflags_clear(ip, XFS_IPRESERVE_DM_FIELDS);
spin_lock(&iip->ili_lock);
iip->ili_fields &= ~(XFS_ILOG_AOWNER | XFS_ILOG_DOWNER);
spin_unlock(&iip->ili_lock);
if (xic.deleted)
error = xfs_ifree_cluster(tp, pag, ip, &xic);
out:
xfs_perag_put(pag);
return error;
}
static void
xfs_iunpin(
struct xfs_inode *ip)
{
struct xfs_inode_log_item *iip = ip->i_itemp;
xfs_csn_t seq = 0;
trace_xfs_inode_unpin_nowait(ip, _RET_IP_);
xfs_assert_ilocked(ip, XFS_ILOCK_EXCL | XFS_ILOCK_SHARED);
spin_lock(&iip->ili_lock);
seq = iip->ili_commit_seq;
spin_unlock(&iip->ili_lock);
if (!seq)
return;
xfs_log_force_seq(ip->i_mount, seq, 0, NULL);
}
static void
__xfs_iunpin_wait(
struct xfs_inode *ip)
{
wait_queue_head_t *wq = bit_waitqueue(&ip->i_flags, __XFS_IPINNED_BIT);
DEFINE_WAIT_BIT(wait, &ip->i_flags, __XFS_IPINNED_BIT);
xfs_iunpin(ip);
do {
prepare_to_wait(wq, &wait.wq_entry, TASK_UNINTERRUPTIBLE);
if (xfs_ipincount(ip))
io_schedule();
} while (xfs_ipincount(ip));
finish_wait(wq, &wait.wq_entry);
}
void
xfs_iunpin_wait(
struct xfs_inode *ip)
{
if (xfs_ipincount(ip))
__xfs_iunpin_wait(ip);
}
int
xfs_remove(
struct xfs_inode *dp,
struct xfs_name *name,
struct xfs_inode *ip)
{
struct xfs_dir_update du = {
.dp = dp,
.name = name,
.ip = ip,
};
struct xfs_mount *mp = dp->i_mount;
struct xfs_trans *tp = NULL;
int is_dir = S_ISDIR(VFS_I(ip)->i_mode);
int dontcare;
int error = 0;
uint resblks;
trace_xfs_remove(dp, name);
if (xfs_is_shutdown(mp))
return -EIO;
if (xfs_ifork_zapped(dp, XFS_DATA_FORK))
return -EIO;
error = xfs_qm_dqattach(dp);
if (error)
goto std_return;
error = xfs_qm_dqattach(ip);
if (error)
goto std_return;
error = xfs_parent_start(mp, &du.ppargs);
if (error)
goto std_return;
resblks = xfs_remove_space_res(mp, name->len);
error = xfs_trans_alloc_dir(dp, &M_RES(mp)->tr_remove, ip, &resblks,
&tp, &dontcare);
if (error) {
ASSERT(error != -ENOSPC);
goto out_parent;
}
error = xfs_dir_remove_child(tp, resblks, &du);
if (error)
goto out_trans_cancel;
if (xfs_has_wsync(mp) || xfs_has_dirsync(mp))
xfs_trans_set_sync(tp);
error = xfs_trans_commit(tp);
if (error)
goto out_unlock;
if (is_dir && xfs_inode_is_filestream(ip))
xfs_filestream_deassociate(ip);
xfs_iunlock(ip, XFS_ILOCK_EXCL);
xfs_iunlock(dp, XFS_ILOCK_EXCL);
xfs_parent_finish(mp, du.ppargs);
return 0;
out_trans_cancel:
xfs_trans_cancel(tp);
out_unlock:
xfs_iunlock(ip, XFS_ILOCK_EXCL);
xfs_iunlock(dp, XFS_ILOCK_EXCL);
out_parent:
xfs_parent_finish(mp, du.ppargs);
std_return:
return error;
}
static inline void
xfs_iunlock_rename(
struct xfs_inode **i_tab,
int num_inodes)
{
int i;
for (i = num_inodes - 1; i >= 0; i--) {
if (!i_tab[i] || (i > 0 && i_tab[i] == i_tab[i - 1]))
continue;
xfs_iunlock(i_tab[i], XFS_ILOCK_EXCL);
}
}
#define __XFS_SORT_INODES 5
STATIC void
xfs_sort_for_rename(
struct xfs_inode *dp1,
struct xfs_inode *dp2,
struct xfs_inode *ip1,
struct xfs_inode *ip2,
struct xfs_inode *wip,
struct xfs_inode **i_tab,
int *num_inodes)
{
int i;
ASSERT(*num_inodes == __XFS_SORT_INODES);
memset(i_tab, 0, *num_inodes * sizeof(struct xfs_inode *));
i = 0;
i_tab[i++] = dp1;
i_tab[i++] = dp2;
i_tab[i++] = ip1;
if (ip2)
i_tab[i++] = ip2;
if (wip)
i_tab[i++] = wip;
*num_inodes = i;
xfs_sort_inodes(i_tab, *num_inodes);
}
void
xfs_sort_inodes(
struct xfs_inode **i_tab,
unsigned int num_inodes)
{
int i, j;
ASSERT(num_inodes <= __XFS_SORT_INODES);
for (i = 0; i < num_inodes; i++) {
for (j = 1; j < num_inodes; j++) {
if (i_tab[j]->i_ino < i_tab[j-1]->i_ino)
swap(i_tab[j], i_tab[j - 1]);
}
}
}
static int
xfs_rename_alloc_whiteout(
struct mnt_idmap *idmap,
struct xfs_name *src_name,
struct xfs_inode *dp,
struct xfs_inode **wip)
{
struct xfs_icreate_args args = {
.idmap = idmap,
.pip = dp,
.mode = S_IFCHR | WHITEOUT_MODE,
.flags = XFS_ICREATE_TMPFILE,
};
struct xfs_inode *tmpfile;
struct qstr name;
int error;
error = xfs_create_tmpfile(&args, &tmpfile);
if (error)
return error;
name.name = src_name->name;
name.len = src_name->len;
error = xfs_inode_init_security(VFS_I(tmpfile), VFS_I(dp), &name);
if (error) {
xfs_finish_inode_setup(tmpfile);
xfs_irele(tmpfile);
return error;
}
xfs_setup_iops(tmpfile);
xfs_finish_inode_setup(tmpfile);
inode_state_set_raw(VFS_I(tmpfile), I_LINKABLE);
*wip = tmpfile;
return 0;
}
int
xfs_rename(
struct mnt_idmap *idmap,
struct xfs_inode *src_dp,
struct xfs_name *src_name,
struct xfs_inode *src_ip,
struct xfs_inode *target_dp,
struct xfs_name *target_name,
struct xfs_inode *target_ip,
unsigned int flags)
{
struct xfs_dir_update du_src = {
.dp = src_dp,
.name = src_name,
.ip = src_ip,
};
struct xfs_dir_update du_tgt = {
.dp = target_dp,
.name = target_name,
.ip = target_ip,
};
struct xfs_dir_update du_wip = { };
struct xfs_mount *mp = src_dp->i_mount;
struct xfs_trans *tp;
struct xfs_inode *inodes[__XFS_SORT_INODES];
int i;
int num_inodes = __XFS_SORT_INODES;
bool new_parent = (src_dp != target_dp);
bool src_is_directory = S_ISDIR(VFS_I(src_ip)->i_mode);
int spaceres;
bool retried = false;
int error, nospace_error = 0;
trace_xfs_rename(src_dp, target_dp, src_name, target_name);
if ((flags & RENAME_EXCHANGE) && !target_ip)
return -EINVAL;
if (flags & RENAME_WHITEOUT) {
error = xfs_rename_alloc_whiteout(idmap, src_name, target_dp,
&du_wip.ip);
if (error)
return error;
src_name->type = XFS_DIR3_FT_CHRDEV;
}
xfs_sort_for_rename(src_dp, target_dp, src_ip, target_ip, du_wip.ip,
inodes, &num_inodes);
error = xfs_parent_start(mp, &du_src.ppargs);
if (error)
goto out_release_wip;
if (du_wip.ip) {
error = xfs_parent_start(mp, &du_wip.ppargs);
if (error)
goto out_src_ppargs;
}
if (target_ip) {
error = xfs_parent_start(mp, &du_tgt.ppargs);
if (error)
goto out_wip_ppargs;
}
retry:
nospace_error = 0;
spaceres = xfs_rename_space_res(mp, src_name->len, target_ip != NULL,
target_name->len, du_wip.ip != NULL);
error = xfs_trans_alloc(mp, &M_RES(mp)->tr_rename, spaceres, 0, 0, &tp);
if (error == -ENOSPC) {
nospace_error = error;
spaceres = 0;
error = xfs_trans_alloc(mp, &M_RES(mp)->tr_rename, 0, 0, 0,
&tp);
}
if (error)
goto out_tgt_ppargs;
if (du_src.ppargs && nospace_error) {
error = nospace_error;
xfs_trans_cancel(tp);
goto out_tgt_ppargs;
}
error = xfs_qm_vop_rename_dqattach(inodes);
if (error) {
xfs_trans_cancel(tp);
goto out_tgt_ppargs;
}
xfs_lock_inodes(inodes, num_inodes, XFS_ILOCK_EXCL);
xfs_trans_ijoin(tp, src_dp, 0);
if (new_parent)
xfs_trans_ijoin(tp, target_dp, 0);
xfs_trans_ijoin(tp, src_ip, 0);
if (target_ip)
xfs_trans_ijoin(tp, target_ip, 0);
if (du_wip.ip)
xfs_trans_ijoin(tp, du_wip.ip, 0);
error = xfs_projid_differ(target_dp, src_ip);
if (error)
goto out_trans_cancel;
if (flags & RENAME_EXCHANGE) {
error = xfs_dir_exchange_children(tp, &du_src, &du_tgt,
spaceres);
if (error)
goto out_trans_cancel;
goto out_commit;
}
if (spaceres != 0) {
error = xfs_trans_reserve_quota_nblks(tp, target_dp, spaceres,
0, false);
if (error == -EDQUOT || error == -ENOSPC) {
if (!retried) {
xfs_trans_cancel(tp);
xfs_iunlock_rename(inodes, num_inodes);
xfs_blockgc_free_quota(target_dp, 0);
retried = true;
goto retry;
}
nospace_error = error;
spaceres = 0;
error = 0;
}
if (error)
goto out_trans_cancel;
}
if (du_src.ppargs && nospace_error) {
error = nospace_error;
goto out_trans_cancel;
}
for (i = 0; i < num_inodes && inodes[i] != NULL; i++) {
if (inodes[i] == du_wip.ip ||
(inodes[i] == target_ip &&
(VFS_I(target_ip)->i_nlink == 1 || src_is_directory))) {
struct xfs_perag *pag;
struct xfs_buf *bp;
pag = xfs_perag_get(mp,
XFS_INO_TO_AGNO(mp, inodes[i]->i_ino));
error = xfs_read_agi(pag, tp, 0, &bp);
xfs_perag_put(pag);
if (error)
goto out_trans_cancel;
}
}
error = xfs_dir_rename_children(tp, &du_src, &du_tgt, spaceres,
&du_wip);
if (error)
goto out_trans_cancel;
if (du_wip.ip) {
inode_state_clear_raw(VFS_I(du_wip.ip), I_LINKABLE);
}
out_commit:
if (xfs_has_wsync(tp->t_mountp) || xfs_has_dirsync(tp->t_mountp))
xfs_trans_set_sync(tp);
error = xfs_trans_commit(tp);
nospace_error = 0;
goto out_unlock;
out_trans_cancel:
xfs_trans_cancel(tp);
out_unlock:
xfs_iunlock_rename(inodes, num_inodes);
out_tgt_ppargs:
xfs_parent_finish(mp, du_tgt.ppargs);
out_wip_ppargs:
xfs_parent_finish(mp, du_wip.ppargs);
out_src_ppargs:
xfs_parent_finish(mp, du_src.ppargs);
out_release_wip:
if (du_wip.ip)
xfs_irele(du_wip.ip);
if (error == -ENOSPC && nospace_error)
error = nospace_error;
return error;
}
static int
xfs_iflush(
struct xfs_inode *ip,
struct xfs_buf *bp)
{
struct xfs_inode_log_item *iip = ip->i_itemp;
struct xfs_dinode *dip;
struct xfs_mount *mp = ip->i_mount;
int error;
xfs_assert_ilocked(ip, XFS_ILOCK_EXCL | XFS_ILOCK_SHARED);
ASSERT(xfs_iflags_test(ip, XFS_IFLUSHING));
ASSERT(ip->i_df.if_format != XFS_DINODE_FMT_BTREE ||
ip->i_df.if_nextents > XFS_IFORK_MAXEXT(ip, XFS_DATA_FORK));
ASSERT(iip->ili_item.li_buf == bp);
dip = xfs_buf_offset(bp, ip->i_imap.im_boffset);
error = -EFSCORRUPTED;
if (dip->di_magic != cpu_to_be16(XFS_DINODE_MAGIC) ||
XFS_TEST_ERROR(mp, XFS_ERRTAG_IFLUSH_1)) {
xfs_alert_tag(mp, XFS_PTAG_IFLUSH,
"%s: Bad inode %llu magic number 0x%x, ptr "PTR_FMT,
__func__, ip->i_ino, be16_to_cpu(dip->di_magic), dip);
goto flush_out;
}
if (ip->i_df.if_format == XFS_DINODE_FMT_META_BTREE) {
if (!S_ISREG(VFS_I(ip)->i_mode) ||
!(ip->i_diflags2 & XFS_DIFLAG2_METADATA)) {
xfs_alert_tag(mp, XFS_PTAG_IFLUSH,
"%s: Bad %s meta btree inode %Lu, ptr "PTR_FMT,
__func__, xfs_metafile_type_str(ip->i_metatype),
ip->i_ino, ip);
goto flush_out;
}
} else if (S_ISREG(VFS_I(ip)->i_mode)) {
if ((ip->i_df.if_format != XFS_DINODE_FMT_EXTENTS &&
ip->i_df.if_format != XFS_DINODE_FMT_BTREE) ||
XFS_TEST_ERROR(mp, XFS_ERRTAG_IFLUSH_3)) {
xfs_alert_tag(mp, XFS_PTAG_IFLUSH,
"%s: Bad regular inode %llu, ptr "PTR_FMT,
__func__, ip->i_ino, ip);
goto flush_out;
}
} else if (S_ISDIR(VFS_I(ip)->i_mode)) {
if ((ip->i_df.if_format != XFS_DINODE_FMT_EXTENTS &&
ip->i_df.if_format != XFS_DINODE_FMT_BTREE &&
ip->i_df.if_format != XFS_DINODE_FMT_LOCAL) ||
XFS_TEST_ERROR(mp, XFS_ERRTAG_IFLUSH_4)) {
xfs_alert_tag(mp, XFS_PTAG_IFLUSH,
"%s: Bad directory inode %llu, ptr "PTR_FMT,
__func__, ip->i_ino, ip);
goto flush_out;
}
}
if (ip->i_df.if_nextents + xfs_ifork_nextents(&ip->i_af) >
ip->i_nblocks || XFS_TEST_ERROR(mp, XFS_ERRTAG_IFLUSH_5)) {
xfs_alert_tag(mp, XFS_PTAG_IFLUSH,
"%s: detected corrupt incore inode %llu, "
"total extents = %llu nblocks = %lld, ptr "PTR_FMT,
__func__, ip->i_ino,
ip->i_df.if_nextents + xfs_ifork_nextents(&ip->i_af),
ip->i_nblocks, ip);
goto flush_out;
}
if (ip->i_forkoff > mp->m_sb.sb_inodesize ||
XFS_TEST_ERROR(mp, XFS_ERRTAG_IFLUSH_6)) {
xfs_alert_tag(mp, XFS_PTAG_IFLUSH,
"%s: bad inode %llu, forkoff 0x%x, ptr "PTR_FMT,
__func__, ip->i_ino, ip->i_forkoff, ip);
goto flush_out;
}
if (xfs_inode_has_attr_fork(ip) &&
ip->i_af.if_format == XFS_DINODE_FMT_META_BTREE) {
xfs_alert_tag(mp, XFS_PTAG_IFLUSH,
"%s: meta btree in inode %Lu attr fork, ptr "PTR_FMT,
__func__, ip->i_ino, ip);
goto flush_out;
}
if (!xfs_has_v3inodes(mp))
ip->i_flushiter++;
if (ip->i_df.if_format == XFS_DINODE_FMT_LOCAL &&
xfs_ifork_verify_local_data(ip))
goto flush_out;
if (xfs_inode_has_attr_fork(ip) &&
ip->i_af.if_format == XFS_DINODE_FMT_LOCAL &&
xfs_ifork_verify_local_attr(ip))
goto flush_out;
xfs_inode_to_disk(ip, dip, iip->ili_item.li_lsn);
if (!xfs_has_v3inodes(mp)) {
if (ip->i_flushiter == DI_MAX_FLUSH)
ip->i_flushiter = 0;
}
xfs_iflush_fork(ip, dip, iip, XFS_DATA_FORK);
if (xfs_inode_has_attr_fork(ip))
xfs_iflush_fork(ip, dip, iip, XFS_ATTR_FORK);
error = 0;
flush_out:
spin_lock(&iip->ili_lock);
iip->ili_last_fields = iip->ili_fields;
iip->ili_fields = 0;
set_bit(XFS_LI_FLUSHING, &iip->ili_item.li_flags);
spin_unlock(&iip->ili_lock);
xfs_trans_ail_copy_lsn(mp->m_ail, &iip->ili_flush_lsn,
&iip->ili_item.li_lsn);
xfs_dinode_calc_crc(mp, dip);
if (error)
xfs_inode_mark_sick(ip, XFS_SICK_INO_CORE);
return error;
}
int
xfs_iflush_cluster(
struct xfs_buf *bp)
{
struct xfs_mount *mp = bp->b_mount;
struct xfs_log_item *lip, *n;
struct xfs_inode *ip;
struct xfs_inode_log_item *iip;
int clcount = 0;
int error = 0;
list_for_each_entry_safe(lip, n, &bp->b_li_list, li_bio_list) {
iip = (struct xfs_inode_log_item *)lip;
ip = iip->ili_inode;
if (__xfs_iflags_test(ip, XFS_IRECLAIM | XFS_IFLUSHING))
continue;
if (xfs_ipincount(ip))
continue;
spin_lock(&ip->i_flags_lock);
ASSERT(!__xfs_iflags_test(ip, XFS_ISTALE));
if (__xfs_iflags_test(ip, XFS_IRECLAIM | XFS_IFLUSHING)) {
spin_unlock(&ip->i_flags_lock);
continue;
}
if (!xfs_ilock_nowait(ip, XFS_ILOCK_SHARED)) {
spin_unlock(&ip->i_flags_lock);
continue;
}
__xfs_iflags_set(ip, XFS_IFLUSHING);
spin_unlock(&ip->i_flags_lock);
if (xlog_is_shutdown(mp->m_log)) {
xfs_iunpin_wait(ip);
xfs_iflush_abort(ip);
xfs_iunlock(ip, XFS_ILOCK_SHARED);
error = -EIO;
continue;
}
if (xfs_ipincount(ip)) {
xfs_iflags_clear(ip, XFS_IFLUSHING);
xfs_iunlock(ip, XFS_ILOCK_SHARED);
continue;
}
if (!xfs_inode_clean(ip))
error = xfs_iflush(ip, bp);
else
xfs_iflags_clear(ip, XFS_IFLUSHING);
xfs_iunlock(ip, XFS_ILOCK_SHARED);
if (error)
break;
clcount++;
}
if (error) {
xfs_force_shutdown(mp, SHUTDOWN_CORRUPT_INCORE);
bp->b_flags |= XBF_ASYNC;
xfs_buf_ioend_fail(bp);
return error;
}
if (!clcount)
return -EAGAIN;
XFS_STATS_INC(mp, xs_icluster_flushcnt);
XFS_STATS_ADD(mp, xs_icluster_flushinode, clcount);
return 0;
}
void
xfs_irele(
struct xfs_inode *ip)
{
trace_xfs_irele(ip, _RET_IP_);
iput(VFS_I(ip));
}
int
xfs_log_force_inode(
struct xfs_inode *ip)
{
struct xfs_inode_log_item *iip = ip->i_itemp;
xfs_csn_t seq = 0;
if (!iip)
return 0;
spin_lock(&iip->ili_lock);
seq = iip->ili_commit_seq;
spin_unlock(&iip->ili_lock);
if (!seq)
return 0;
return xfs_log_force_seq(ip->i_mount, seq, XFS_LOG_SYNC, NULL);
}
static int
xfs_iolock_two_inodes_and_break_layout(
struct inode *src,
struct inode *dest)
{
int error;
if (src > dest)
swap(src, dest);
retry:
error = break_layout(src, true);
if (error)
return error;
if (src != dest) {
error = break_layout(dest, true);
if (error)
return error;
}
inode_lock(src);
error = break_layout(src, false);
if (error) {
inode_unlock(src);
if (error == -EWOULDBLOCK)
goto retry;
return error;
}
if (src == dest)
return 0;
inode_lock_nested(dest, I_MUTEX_NONDIR2);
error = break_layout(dest, false);
if (error) {
inode_unlock(src);
inode_unlock(dest);
if (error == -EWOULDBLOCK)
goto retry;
return error;
}
return 0;
}
static int
xfs_mmaplock_two_inodes_and_break_dax_layout(
struct xfs_inode *ip1,
struct xfs_inode *ip2)
{
int error;
if (ip1->i_ino > ip2->i_ino)
swap(ip1, ip2);
again:
xfs_ilock(ip1, XFS_MMAPLOCK_EXCL);
error = xfs_break_dax_layouts(VFS_I(ip1));
if (error) {
xfs_iunlock(ip1, XFS_MMAPLOCK_EXCL);
return error;
}
if (ip1 == ip2)
return 0;
xfs_ilock(ip2, xfs_lock_inumorder(XFS_MMAPLOCK_EXCL, 1));
error = dax_break_layout(VFS_I(ip2), 0, -1, NULL);
if (error) {
xfs_iunlock(ip2, XFS_MMAPLOCK_EXCL);
xfs_iunlock(ip1, XFS_MMAPLOCK_EXCL);
goto again;
}
return 0;
}
int
xfs_ilock2_io_mmap(
struct xfs_inode *ip1,
struct xfs_inode *ip2)
{
int ret;
ret = xfs_iolock_two_inodes_and_break_layout(VFS_I(ip1), VFS_I(ip2));
if (ret)
return ret;
if (IS_DAX(VFS_I(ip1)) && IS_DAX(VFS_I(ip2))) {
ret = xfs_mmaplock_two_inodes_and_break_dax_layout(ip1, ip2);
if (ret) {
inode_unlock(VFS_I(ip2));
if (ip1 != ip2)
inode_unlock(VFS_I(ip1));
return ret;
}
} else
filemap_invalidate_lock_two(VFS_I(ip1)->i_mapping,
VFS_I(ip2)->i_mapping);
return 0;
}
void
xfs_iunlock2_io_mmap(
struct xfs_inode *ip1,
struct xfs_inode *ip2)
{
if (IS_DAX(VFS_I(ip1)) && IS_DAX(VFS_I(ip2))) {
xfs_iunlock(ip2, XFS_MMAPLOCK_EXCL);
if (ip1 != ip2)
xfs_iunlock(ip1, XFS_MMAPLOCK_EXCL);
} else
filemap_invalidate_unlock_two(VFS_I(ip1)->i_mapping,
VFS_I(ip2)->i_mapping);
inode_unlock(VFS_I(ip2));
if (ip1 != ip2)
inode_unlock(VFS_I(ip1));
}
void
xfs_iunlock2_remapping(
struct xfs_inode *ip1,
struct xfs_inode *ip2)
{
xfs_iflags_clear(ip1, XFS_IREMAPPING);
if (ip1 != ip2)
xfs_iunlock(ip1, XFS_MMAPLOCK_SHARED);
xfs_iunlock(ip2, XFS_MMAPLOCK_EXCL);
if (ip1 != ip2)
inode_unlock_shared(VFS_I(ip1));
inode_unlock(VFS_I(ip2));
}
int
xfs_inode_reload_unlinked_bucket(
struct xfs_trans *tp,
struct xfs_inode *ip)
{
struct xfs_mount *mp = tp->t_mountp;
struct xfs_buf *agibp;
struct xfs_agi *agi;
struct xfs_perag *pag;
xfs_agnumber_t agno = XFS_INO_TO_AGNO(mp, ip->i_ino);
xfs_agino_t agino = XFS_INO_TO_AGINO(mp, ip->i_ino);
xfs_agino_t prev_agino, next_agino;
unsigned int bucket;
bool foundit = false;
int error;
pag = xfs_perag_get(mp, agno);
error = xfs_ialloc_read_agi(pag, tp, 0, &agibp);
xfs_perag_put(pag);
if (error)
return error;
if (!xfs_inode_unlinked_incomplete(ip)) {
foundit = true;
goto out_agibp;
}
bucket = agino % XFS_AGI_UNLINKED_BUCKETS;
agi = agibp->b_addr;
trace_xfs_inode_reload_unlinked_bucket(ip);
xfs_info_ratelimited(mp,
"Found unrecovered unlinked inode 0x%x in AG 0x%x. Initiating list recovery.",
agino, agno);
prev_agino = NULLAGINO;
next_agino = be32_to_cpu(agi->agi_unlinked[bucket]);
while (next_agino != NULLAGINO) {
struct xfs_inode *next_ip = NULL;
if (next_agino == agino) {
next_ip = ip;
next_ip->i_prev_unlinked = prev_agino;
foundit = true;
goto next_inode;
}
next_ip = xfs_iunlink_lookup(pag, next_agino);
if (next_ip)
goto next_inode;
error = xfs_iunlink_reload_next(tp, agibp, prev_agino,
next_agino);
if (error)
break;
next_ip = xfs_iunlink_lookup(pag, next_agino);
if (!next_ip) {
ASSERT(next_ip != NULL);
error = -EFSCORRUPTED;
break;
}
next_inode:
prev_agino = next_agino;
next_agino = next_ip->i_next_unlinked;
}
out_agibp:
xfs_trans_brelse(tp, agibp);
if (!error && !foundit)
error = -EFSCORRUPTED;
return error;
}
int
xfs_inode_reload_unlinked(
struct xfs_inode *ip)
{
struct xfs_trans *tp;
int error = 0;
tp = xfs_trans_alloc_empty(ip->i_mount);
xfs_ilock(ip, XFS_ILOCK_SHARED);
if (xfs_inode_unlinked_incomplete(ip))
error = xfs_inode_reload_unlinked_bucket(tp, ip);
xfs_iunlock(ip, XFS_ILOCK_SHARED);
xfs_trans_cancel(tp);
return error;
}
bool
xfs_ifork_zapped(
const struct xfs_inode *ip,
int whichfork)
{
unsigned int datamask = 0;
switch (whichfork) {
case XFS_DATA_FORK:
switch (ip->i_vnode.i_mode & S_IFMT) {
case S_IFDIR:
datamask = XFS_SICK_INO_DIR_ZAPPED;
break;
case S_IFLNK:
datamask = XFS_SICK_INO_SYMLINK_ZAPPED;
break;
}
return ip->i_sick & (XFS_SICK_INO_BMBTD_ZAPPED | datamask);
case XFS_ATTR_FORK:
return ip->i_sick & XFS_SICK_INO_BMBTA_ZAPPED;
default:
return false;
}
}
void
xfs_inode_count_blocks(
struct xfs_trans *tp,
struct xfs_inode *ip,
xfs_filblks_t *dblocks,
xfs_filblks_t *rblocks)
{
struct xfs_ifork *ifp = xfs_ifork_ptr(ip, XFS_DATA_FORK);
*rblocks = 0;
if (XFS_IS_REALTIME_INODE(ip))
xfs_bmap_count_leaves(ifp, rblocks);
*dblocks = ip->i_nblocks - *rblocks;
}
static void
xfs_wait_dax_page(
struct inode *inode)
{
struct xfs_inode *ip = XFS_I(inode);
xfs_iunlock(ip, XFS_MMAPLOCK_EXCL);
schedule();
xfs_ilock(ip, XFS_MMAPLOCK_EXCL);
}
int
xfs_break_dax_layouts(
struct inode *inode)
{
xfs_assert_ilocked(XFS_I(inode), XFS_MMAPLOCK_EXCL);
return dax_break_layout_inode(inode, xfs_wait_dax_page);
}
int
xfs_break_layouts(
struct inode *inode,
uint *iolock,
enum layout_break_reason reason)
{
bool retry;
int error;
xfs_assert_ilocked(XFS_I(inode), XFS_IOLOCK_SHARED | XFS_IOLOCK_EXCL);
do {
retry = false;
switch (reason) {
case BREAK_UNMAP:
error = xfs_break_dax_layouts(inode);
if (error)
break;
fallthrough;
case BREAK_WRITE:
error = xfs_break_leased_layouts(inode, iolock, &retry);
break;
default:
WARN_ON_ONCE(1);
error = -EINVAL;
}
} while (error == 0 && retry);
return error;
}
unsigned int
xfs_inode_alloc_unitsize(
struct xfs_inode *ip)
{
unsigned int blocks = 1;
if (XFS_IS_REALTIME_INODE(ip))
blocks = ip->i_mount->m_sb.sb_rextsize;
return XFS_FSB_TO_B(ip->i_mount, blocks);
}
bool
xfs_is_always_cow_inode(
const struct xfs_inode *ip)
{
return xfs_is_zoned_inode(ip) ||
(ip->i_mount->m_always_cow && xfs_has_reflink(ip->i_mount));
}