#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_metafile.h"
#include "xfs_quota.h"
#include "xfs_qm.h"
#include "xfs_dir2.h"
#include "xfs_parent.h"
#include "xfs_bmap_btree.h"
#include "xfs_trans_space.h"
#include "xfs_attr.h"
#include "xfs_rtgroup.h"
#include "xfs_rtrmap_btree.h"
#include "xfs_rtrefcount_btree.h"
#include "scrub/scrub.h"
#include "scrub/common.h"
#include "scrub/trace.h"
#include "scrub/readdir.h"
#include "scrub/repair.h"
struct xchk_metapath {
struct xfs_scrub *sc;
struct xfs_name xname;
struct xfs_dir_update du;
const char *path;
struct xfs_inode *dp;
unsigned int dp_ilock_flags;
unsigned int link_resblks;
unsigned int unlink_resblks;
struct xfs_parent_args link_ppargs;
struct xfs_parent_args unlink_ppargs;
struct xfs_da_args pptr_args;
};
static inline void
xchk_metapath_cleanup(
void *buf)
{
struct xchk_metapath *mpath = buf;
if (mpath->dp_ilock_flags)
xfs_iunlock(mpath->dp, mpath->dp_ilock_flags);
kfree_const(mpath->path);
}
static inline int
xchk_setup_metapath_scan(
struct xfs_scrub *sc,
struct xfs_inode *dp,
const char *path,
struct xfs_inode *ip)
{
struct xchk_metapath *mpath;
int error;
if (!path)
return -ENOMEM;
error = xchk_install_live_inode(sc, ip);
if (error) {
kfree_const(path);
return error;
}
mpath = kzalloc_obj(struct xchk_metapath, XCHK_GFP_FLAGS);
if (!mpath) {
kfree_const(path);
return -ENOMEM;
}
mpath->sc = sc;
sc->buf = mpath;
sc->buf_cleanup = xchk_metapath_cleanup;
mpath->dp = dp;
mpath->path = path;
mpath->xname.name = mpath->path;
mpath->xname.len = strlen(mpath->path);
mpath->xname.type = xfs_mode_to_ftype(VFS_I(ip)->i_mode);
return 0;
}
#ifdef CONFIG_XFS_RT
static int
xchk_setup_metapath_rtdir(
struct xfs_scrub *sc)
{
if (!sc->mp->m_rtdirip)
return -ENOENT;
return xchk_setup_metapath_scan(sc, sc->mp->m_metadirip,
kstrdup_const("rtgroups", GFP_KERNEL), sc->mp->m_rtdirip);
}
static int
xchk_setup_metapath_rtginode(
struct xfs_scrub *sc,
enum xfs_rtg_inodes type)
{
struct xfs_rtgroup *rtg;
struct xfs_inode *ip;
int error;
rtg = xfs_rtgroup_get(sc->mp, sc->sm->sm_agno);
if (!rtg)
return -ENOENT;
ip = rtg->rtg_inodes[type];
if (!ip) {
error = -ENOENT;
goto out_put_rtg;
}
error = xchk_setup_metapath_scan(sc, sc->mp->m_rtdirip,
xfs_rtginode_path(rtg_rgno(rtg), type), ip);
out_put_rtg:
xfs_rtgroup_put(rtg);
return error;
}
#else
# define xchk_setup_metapath_rtdir(...) (-ENOENT)
# define xchk_setup_metapath_rtginode(...) (-ENOENT)
#endif
#ifdef CONFIG_XFS_QUOTA
static int
xchk_setup_metapath_quotadir(
struct xfs_scrub *sc)
{
struct xfs_quotainfo *qi = sc->mp->m_quotainfo;
if (!qi || !qi->qi_dirip)
return -ENOENT;
return xchk_setup_metapath_scan(sc, sc->mp->m_metadirip,
kstrdup_const("quota", GFP_KERNEL), qi->qi_dirip);
}
static int
xchk_setup_metapath_dqinode(
struct xfs_scrub *sc,
xfs_dqtype_t type)
{
struct xfs_quotainfo *qi = sc->mp->m_quotainfo;
struct xfs_inode *ip = NULL;
if (!qi)
return -ENOENT;
switch (type) {
case XFS_DQTYPE_USER:
ip = qi->qi_uquotaip;
break;
case XFS_DQTYPE_GROUP:
ip = qi->qi_gquotaip;
break;
case XFS_DQTYPE_PROJ:
ip = qi->qi_pquotaip;
break;
default:
ASSERT(0);
return -EINVAL;
}
if (!ip)
return -ENOENT;
return xchk_setup_metapath_scan(sc, qi->qi_dirip,
kstrdup_const(xfs_dqinode_path(type), GFP_KERNEL), ip);
}
#else
# define xchk_setup_metapath_quotadir(...) (-ENOENT)
# define xchk_setup_metapath_dqinode(...) (-ENOENT)
#endif
int
xchk_setup_metapath(
struct xfs_scrub *sc)
{
if (!xfs_has_metadir(sc->mp))
return -ENOENT;
if (sc->sm->sm_gen)
return -EINVAL;
switch (sc->sm->sm_ino) {
case XFS_SCRUB_METAPATH_PROBE:
if (sc->sm->sm_agno)
return -EINVAL;
return 0;
case XFS_SCRUB_METAPATH_RTDIR:
return xchk_setup_metapath_rtdir(sc);
case XFS_SCRUB_METAPATH_RTBITMAP:
return xchk_setup_metapath_rtginode(sc, XFS_RTGI_BITMAP);
case XFS_SCRUB_METAPATH_RTSUMMARY:
return xchk_setup_metapath_rtginode(sc, XFS_RTGI_SUMMARY);
case XFS_SCRUB_METAPATH_QUOTADIR:
return xchk_setup_metapath_quotadir(sc);
case XFS_SCRUB_METAPATH_USRQUOTA:
return xchk_setup_metapath_dqinode(sc, XFS_DQTYPE_USER);
case XFS_SCRUB_METAPATH_GRPQUOTA:
return xchk_setup_metapath_dqinode(sc, XFS_DQTYPE_GROUP);
case XFS_SCRUB_METAPATH_PRJQUOTA:
return xchk_setup_metapath_dqinode(sc, XFS_DQTYPE_PROJ);
case XFS_SCRUB_METAPATH_RTRMAPBT:
return xchk_setup_metapath_rtginode(sc, XFS_RTGI_RMAP);
case XFS_SCRUB_METAPATH_RTREFCOUNTBT:
return xchk_setup_metapath_rtginode(sc, XFS_RTGI_REFCOUNT);
default:
return -ENOENT;
}
}
STATIC int
xchk_metapath_ilock_both(
struct xchk_metapath *mpath)
{
struct xfs_scrub *sc = mpath->sc;
int error = 0;
while (true) {
xfs_ilock(mpath->dp, XFS_ILOCK_EXCL);
if (xchk_ilock_nowait(sc, XFS_ILOCK_EXCL)) {
mpath->dp_ilock_flags |= XFS_ILOCK_EXCL;
return 0;
}
xfs_iunlock(mpath->dp, XFS_ILOCK_EXCL);
if (xchk_should_terminate(sc, &error))
return error;
delay(1);
}
ASSERT(0);
return -EINTR;
}
static inline void
xchk_metapath_iunlock(
struct xchk_metapath *mpath)
{
struct xfs_scrub *sc = mpath->sc;
xchk_iunlock(sc, XFS_ILOCK_EXCL);
mpath->dp_ilock_flags &= ~XFS_ILOCK_EXCL;
xfs_iunlock(mpath->dp, XFS_ILOCK_EXCL);
}
int
xchk_metapath(
struct xfs_scrub *sc)
{
struct xchk_metapath *mpath = sc->buf;
xfs_ino_t ino = NULLFSINO;
int error;
if (sc->sm->sm_ino == XFS_SCRUB_METAPATH_PROBE)
return 0;
if (mpath->dp == NULL) {
xchk_ino_set_corrupt(sc, sc->ip->i_ino);
return 0;
}
xchk_trans_alloc_empty(sc);
error = xchk_metapath_ilock_both(mpath);
if (error)
goto out_cancel;
error = xchk_dir_lookup(sc, mpath->dp, &mpath->xname, &ino);
trace_xchk_metapath_lookup(sc, mpath->path, mpath->dp, ino);
if (error == -ENOENT) {
xchk_ino_set_corrupt(sc, sc->ip->i_ino);
error = 0;
goto out_ilock;
}
if (!xchk_fblock_xref_process_error(sc, XFS_DATA_FORK, 0, &error))
goto out_ilock;
if (ino != sc->ip->i_ino) {
xchk_ino_set_corrupt(sc, sc->ip->i_ino);
}
out_ilock:
xchk_metapath_iunlock(mpath);
out_cancel:
xchk_trans_cancel(sc);
return error;
}
#ifdef CONFIG_XFS_ONLINE_REPAIR
STATIC int
xrep_metapath_link(
struct xchk_metapath *mpath)
{
struct xfs_scrub *sc = mpath->sc;
mpath->du.dp = mpath->dp;
mpath->du.name = &mpath->xname;
mpath->du.ip = sc->ip;
if (xfs_has_parent(sc->mp))
mpath->du.ppargs = &mpath->link_ppargs;
else
mpath->du.ppargs = NULL;
trace_xrep_metapath_link(sc, mpath->path, mpath->dp, sc->ip->i_ino);
return xfs_dir_add_child(sc->tp, mpath->link_resblks, &mpath->du);
}
STATIC int
xrep_metapath_unlink(
struct xchk_metapath *mpath,
xfs_ino_t ino,
struct xfs_inode *ip)
{
struct xfs_parent_rec rec;
struct xfs_scrub *sc = mpath->sc;
struct xfs_mount *mp = sc->mp;
int error;
trace_xrep_metapath_unlink(sc, mpath->path, mpath->dp, ino);
if (!ip) {
xfs_trans_log_inode(sc->tp, mpath->dp, XFS_ILOG_CORE);
return xfs_dir_removename(sc->tp, mpath->dp, &mpath->xname,
ino, mpath->unlink_resblks);
}
mpath->du.dp = mpath->dp;
mpath->du.name = &mpath->xname;
mpath->du.ip = ip;
mpath->du.ppargs = NULL;
if (xfs_has_parent(mp)) {
xfs_inode_to_parent_rec(&rec, ip);
error = xfs_parent_lookup(sc->tp, ip, &mpath->xname, &rec,
&mpath->pptr_args);
switch (error) {
case -ENOATTR:
break;
case 0:
mpath->du.ppargs = &mpath->unlink_ppargs;
break;
default:
return error;
}
}
return xfs_dir_remove_child(sc->tp, mpath->unlink_resblks, &mpath->du);
}
STATIC int
xrep_metapath_try_link(
struct xchk_metapath *mpath,
xfs_ino_t *alleged_child)
{
struct xfs_scrub *sc = mpath->sc;
xfs_ino_t ino;
int error;
error = xchk_trans_alloc(sc, mpath->link_resblks);
if (error)
return error;
error = xchk_metapath_ilock_both(mpath);
if (error) {
xchk_trans_cancel(sc);
return error;
}
xfs_trans_ijoin(sc->tp, mpath->dp, 0);
xfs_trans_ijoin(sc->tp, sc->ip, 0);
error = xchk_dir_lookup(sc, mpath->dp, &mpath->xname, &ino);
trace_xrep_metapath_lookup(sc, mpath->path, mpath->dp, ino);
if (error == -ENOENT) {
error = xrep_metapath_link(mpath);
if (error)
goto out_cancel;
error = xrep_trans_commit(sc);
xchk_metapath_iunlock(mpath);
return error;
}
if (error)
goto out_cancel;
if (ino == sc->ip->i_ino) {
error = 0;
goto out_cancel;
}
*alleged_child = ino;
error = -EEXIST;
out_cancel:
xchk_trans_cancel(sc);
xchk_metapath_iunlock(mpath);
return error;
}
STATIC int
xchk_metapath_ilock_parent_and_child(
struct xchk_metapath *mpath,
struct xfs_inode *ip)
{
struct xfs_scrub *sc = mpath->sc;
int error = 0;
while (true) {
xfs_ilock(mpath->dp, XFS_ILOCK_EXCL);
if (!ip || xfs_ilock_nowait(ip, XFS_ILOCK_EXCL))
return 0;
xfs_iunlock(mpath->dp, XFS_ILOCK_EXCL);
if (xchk_should_terminate(sc, &error))
return error;
delay(1);
}
ASSERT(0);
return -EINTR;
}
STATIC int
xrep_metapath_try_unlink(
struct xchk_metapath *mpath,
xfs_ino_t *alleged_child)
{
struct xfs_scrub *sc = mpath->sc;
struct xfs_inode *ip = NULL;
xfs_ino_t ino;
int error;
ASSERT(*alleged_child != sc->ip->i_ino);
trace_xrep_metapath_try_unlink(sc, mpath->path, mpath->dp,
*alleged_child);
error = xchk_trans_alloc(sc, mpath->unlink_resblks);
if (error)
return error;
error = xchk_iget(sc, *alleged_child, &ip);
if (error == -EINVAL || error == -ENOENT) {
error = 0;
}
if (error) {
xchk_trans_cancel(sc);
return error;
}
error = xchk_metapath_ilock_parent_and_child(mpath, ip);
if (error) {
xchk_trans_cancel(sc);
return error;
}
xfs_trans_ijoin(sc->tp, mpath->dp, 0);
if (ip)
xfs_trans_ijoin(sc->tp, ip, 0);
error = xchk_dir_lookup(sc, mpath->dp, &mpath->xname, &ino);
trace_xrep_metapath_lookup(sc, mpath->path, mpath->dp, ino);
if (error == -ENOENT) {
error = 0;
goto out_cancel;
}
if (error)
goto out_cancel;
if (ino == sc->ip->i_ino) {
error = -EEXIST;
goto out_cancel;
}
if (ino != *alleged_child) {
*alleged_child = ino;
error = -EAGAIN;
goto out_cancel;
}
error = xrep_metapath_unlink(mpath, ino, ip);
if (error)
goto out_cancel;
error = xrep_trans_commit(sc);
goto out_unlock;
out_cancel:
xchk_trans_cancel(sc);
out_unlock:
xfs_iunlock(mpath->dp, XFS_ILOCK_EXCL);
if (ip) {
xfs_iunlock(ip, XFS_ILOCK_EXCL);
xchk_irele(sc, ip);
}
return error;
}
int
xrep_metapath(
struct xfs_scrub *sc)
{
struct xchk_metapath *mpath = sc->buf;
struct xfs_mount *mp = sc->mp;
int error = 0;
if (sc->sm->sm_ino == XFS_SCRUB_METAPATH_PROBE)
return 0;
if (mpath->dp == NULL)
return -EFSCORRUPTED;
if (xfs_has_parent(mp)) {
error = xfs_attr_add_fork(sc->ip,
sizeof(struct xfs_attr_sf_hdr), 1);
if (error)
return error;
}
mpath->unlink_resblks = xfs_remove_space_res(mp, MAXNAMELEN);
mpath->link_resblks = xfs_link_space_res(mp, MAXNAMELEN);
do {
xfs_ino_t alleged_child;
error = xrep_metapath_try_link(mpath, &alleged_child);
if (!error)
return 0;
if (error != -EEXIST)
return error;
do {
error = xrep_metapath_try_unlink(mpath, &alleged_child);
} while (error == -EAGAIN);
if (error == -EEXIST) {
error = 0;
break;
}
} while (!error);
return error;
}
#endif