#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_defer.h"
#include "xfs_bit.h"
#include "xfs_log_format.h"
#include "xfs_trans.h"
#include "xfs_sb.h"
#include "xfs_inode.h"
#include "xfs_icache.h"
#include "xfs_da_format.h"
#include "xfs_da_btree.h"
#include "xfs_dir2.h"
#include "xfs_bmap_btree.h"
#include "xfs_dir2_priv.h"
#include "xfs_trans_space.h"
#include "xfs_health.h"
#include "xfs_exchmaps.h"
#include "xfs_parent.h"
#include "scrub/xfs_scrub.h"
#include "scrub/scrub.h"
#include "scrub/common.h"
#include "scrub/trace.h"
#include "scrub/repair.h"
#include "scrub/iscan.h"
#include "scrub/findparent.h"
#include "scrub/readdir.h"
#include "scrub/tempfile.h"
#include "scrub/listxattr.h"
struct xrep_findparent_info {
struct xfs_inode *dp;
struct xfs_scrub *sc;
struct xrep_parent_scan_info *parent_scan;
xfs_ino_t found_parent;
bool parent_tentative;
};
STATIC int
xrep_findparent_dirent(
struct xfs_scrub *sc,
struct xfs_inode *dp,
xfs_dir2_dataptr_t dapos,
const struct xfs_name *name,
xfs_ino_t ino,
void *priv)
{
struct xrep_findparent_info *fpi = priv;
int error = 0;
if (xchk_should_terminate(fpi->sc, &error))
return error;
if (ino != fpi->sc->ip->i_ino)
return 0;
if (name->len == 0 || !xfs_dir2_namecheck(name->name, name->len))
return -EFSCORRUPTED;
if (name->name[0] == '.' && (name->len == 1 ||
(name->len == 2 && name->name[1] == '.')))
return 0;
if (fpi->found_parent != NULLFSINO &&
!(fpi->parent_tentative && fpi->found_parent == fpi->dp->i_ino)) {
trace_xrep_findparent_dirent(fpi->sc->ip, 0);
return -EFSCORRUPTED;
}
trace_xrep_findparent_dirent(fpi->sc->ip, fpi->dp->i_ino);
fpi->found_parent = fpi->dp->i_ino;
fpi->parent_tentative = false;
if (fpi->parent_scan)
xrep_findparent_scan_found(fpi->parent_scan, fpi->dp->i_ino);
return 0;
}
STATIC int
xrep_findparent_walk_directory(
struct xrep_findparent_info *fpi)
{
struct xfs_scrub *sc = fpi->sc;
struct xfs_inode *dp = fpi->dp;
unsigned int lock_mode;
int error = 0;
if (dp == sc->ip || dp == sc->tempip)
return 0;
if (xrep_is_tempfile(dp))
return 0;
lock_mode = xfs_ilock_data_map_shared(dp);
if (xfs_is_metadir_inode(dp) != xfs_is_metadir_inode(sc->ip))
goto out_unlock;
if (xfs_inode_has_sickness(dp, XFS_SICK_INO_CORE |
XFS_SICK_INO_BMBTD |
XFS_SICK_INO_DIR)) {
error = -EFSCORRUPTED;
goto out_unlock;
}
if (xchk_dir_looks_zapped(dp)) {
error = -EBUSY;
goto out_unlock;
}
error = xchk_dir_walk(sc, dp, xrep_findparent_dirent, fpi);
if (error)
goto out_unlock;
out_unlock:
xfs_iunlock(dp, lock_mode);
return error;
}
STATIC int
xrep_findparent_live_update(
struct notifier_block *nb,
unsigned long action,
void *data)
{
struct xfs_dir_update_params *p = data;
struct xrep_parent_scan_info *pscan;
struct xfs_scrub *sc;
pscan = container_of(nb, struct xrep_parent_scan_info,
dhook.dirent_hook.nb);
sc = pscan->sc;
if (p->ip->i_ino == sc->ip->i_ino &&
xchk_iscan_want_live_update(&pscan->iscan, p->dp->i_ino)) {
if (p->delta > 0) {
xrep_findparent_scan_found(pscan, p->dp->i_ino);
} else {
xrep_findparent_scan_found(pscan, NULLFSINO);
}
}
return NOTIFY_DONE;
}
int
__xrep_findparent_scan_start(
struct xfs_scrub *sc,
struct xrep_parent_scan_info *pscan,
notifier_fn_t custom_fn)
{
int error;
if (!(sc->flags & XCHK_FSGATES_DIRENTS)) {
ASSERT(sc->flags & XCHK_FSGATES_DIRENTS);
return -EINVAL;
}
pscan->sc = sc;
pscan->parent_ino = NULLFSINO;
mutex_init(&pscan->lock);
xchk_iscan_start(sc, 30000, 100, &pscan->iscan);
if (custom_fn)
xfs_dir_hook_setup(&pscan->dhook, custom_fn);
else
xfs_dir_hook_setup(&pscan->dhook, xrep_findparent_live_update);
error = xfs_dir_hook_add(sc->mp, &pscan->dhook);
if (error)
goto out_iscan;
return 0;
out_iscan:
xchk_iscan_teardown(&pscan->iscan);
mutex_destroy(&pscan->lock);
return error;
}
int
xrep_findparent_scan(
struct xrep_parent_scan_info *pscan)
{
struct xrep_findparent_info fpi = {
.sc = pscan->sc,
.found_parent = NULLFSINO,
.parent_scan = pscan,
};
struct xfs_scrub *sc = pscan->sc;
int ret;
ASSERT(S_ISDIR(VFS_IC(sc->ip)->i_mode));
while ((ret = xchk_iscan_iter(&pscan->iscan, &fpi.dp)) == 1) {
if (S_ISDIR(VFS_I(fpi.dp)->i_mode))
ret = xrep_findparent_walk_directory(&fpi);
else
ret = 0;
xchk_iscan_mark_visited(&pscan->iscan, fpi.dp);
xchk_irele(sc, fpi.dp);
if (ret)
break;
if (xchk_should_terminate(sc, &ret))
break;
}
xchk_iscan_iter_finish(&pscan->iscan);
return ret;
}
void
xrep_findparent_scan_teardown(
struct xrep_parent_scan_info *pscan)
{
xfs_dir_hook_del(pscan->sc->mp, &pscan->dhook);
xchk_iscan_teardown(&pscan->iscan);
mutex_destroy(&pscan->lock);
}
void
xrep_findparent_scan_finish_early(
struct xrep_parent_scan_info *pscan,
xfs_ino_t ino)
{
xrep_findparent_scan_found(pscan, ino);
xchk_iscan_finish_early(&pscan->iscan);
}
int
xrep_findparent_confirm(
struct xfs_scrub *sc,
xfs_ino_t *parent_ino)
{
struct xrep_findparent_info fpi = {
.sc = sc,
.found_parent = NULLFSINO,
};
int error;
if (sc->ip == sc->mp->m_rootip) {
*parent_ino = sc->mp->m_sb.sb_rootino;
return 0;
}
if (sc->ip == sc->mp->m_metadirip) {
*parent_ino = sc->mp->m_sb.sb_metadirino;
return 0;
}
if (VFS_I(sc->ip)->i_nlink == 0) {
*parent_ino = xchk_inode_rootdir_inum(sc->ip);
return 0;
}
if (*parent_ino == NULLFSINO)
return 0;
if (!xfs_verify_dir_ino(sc->mp, *parent_ino) ||
*parent_ino == sc->ip->i_ino) {
*parent_ino = NULLFSINO;
return 0;
}
error = xchk_iget(sc, *parent_ino, &fpi.dp);
if (error)
return error;
if (!S_ISDIR(VFS_I(fpi.dp)->i_mode)) {
*parent_ino = NULLFSINO;
goto out_rele;
}
error = xrep_findparent_walk_directory(&fpi);
if (error)
goto out_rele;
*parent_ino = fpi.found_parent;
out_rele:
xchk_irele(sc, fpi.dp);
return error;
}
xfs_ino_t
xrep_findparent_self_reference(
struct xfs_scrub *sc)
{
if (sc->ip->i_ino == sc->mp->m_sb.sb_rootino)
return sc->mp->m_sb.sb_rootino;
if (sc->ip->i_ino == sc->mp->m_sb.sb_metadirino)
return sc->mp->m_sb.sb_metadirino;
if (VFS_I(sc->ip)->i_nlink == 0)
return xchk_inode_rootdir_inum(sc->ip);
return NULLFSINO;
}
xfs_ino_t
xrep_findparent_from_dcache(
struct xfs_scrub *sc)
{
struct inode *pip = NULL;
struct dentry *dentry, *parent;
xfs_ino_t ret = NULLFSINO;
dentry = d_find_alias(VFS_I(sc->ip));
if (!dentry)
goto out;
parent = dget_parent(dentry);
if (!parent)
goto out_dput;
ASSERT(parent->d_sb == sc->ip->i_mount->m_super);
pip = igrab(d_inode(parent));
dput(parent);
if (S_ISDIR(pip->i_mode)) {
trace_xrep_findparent_from_dcache(sc->ip, XFS_I(pip)->i_ino);
ret = XFS_I(pip)->i_ino;
}
xchk_irele(sc, XFS_I(pip));
out_dput:
dput(dentry);
out:
return ret;
}