mirror of
https://github.com/torvalds/linux.git
synced 2024-11-28 23:21:31 +00:00
128a055291
Enable online fsck for quota file metadata directory paths. Signed-off-by: Darrick J. Wong <djwong@kernel.org> Reviewed-by: Christoph Hellwig <hch@lst.de>
690 lines
16 KiB
C
690 lines
16 KiB
C
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
/*
|
|
* Copyright (c) 2023-2024 Oracle. All Rights Reserved.
|
|
* Author: Darrick J. Wong <djwong@kernel.org>
|
|
*/
|
|
#include "xfs.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 "scrub/scrub.h"
|
|
#include "scrub/common.h"
|
|
#include "scrub/trace.h"
|
|
#include "scrub/readdir.h"
|
|
#include "scrub/repair.h"
|
|
|
|
/*
|
|
* Metadata Directory Tree Paths
|
|
* =============================
|
|
*
|
|
* A filesystem with metadir enabled expects to find metadata structures
|
|
* attached to files that are accessible by walking a path down the metadata
|
|
* directory tree. Given the metadir path and the incore inode storing the
|
|
* metadata, this scrubber ensures that the ondisk metadir path points to the
|
|
* ondisk inode represented by the incore inode.
|
|
*/
|
|
|
|
struct xchk_metapath {
|
|
struct xfs_scrub *sc;
|
|
|
|
/* Name for lookup */
|
|
struct xfs_name xname;
|
|
|
|
/* Directory update for repairs */
|
|
struct xfs_dir_update du;
|
|
|
|
/* Path down to this metadata file from the parent directory */
|
|
const char *path;
|
|
|
|
/* Directory parent of the metadata file. */
|
|
struct xfs_inode *dp;
|
|
|
|
/* Locks held on dp */
|
|
unsigned int dp_ilock_flags;
|
|
|
|
/* Transaction block reservations */
|
|
unsigned int link_resblks;
|
|
unsigned int unlink_resblks;
|
|
|
|
/* Parent pointer updates */
|
|
struct xfs_parent_args link_ppargs;
|
|
struct xfs_parent_args unlink_ppargs;
|
|
|
|
/* Scratchpads for removing links */
|
|
struct xfs_da_args pptr_args;
|
|
};
|
|
|
|
/* Release resources tracked in the buffer. */
|
|
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(mpath->path);
|
|
}
|
|
|
|
/* Set up a metadir path scan. @path must be dynamically allocated. */
|
|
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(path);
|
|
return error;
|
|
}
|
|
|
|
mpath = kzalloc(sizeof(struct xchk_metapath), XCHK_GFP_FLAGS);
|
|
if (!mpath) {
|
|
kfree(path);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
mpath->sc = sc;
|
|
sc->buf = mpath;
|
|
sc->buf_cleanup = xchk_metapath_cleanup;
|
|
|
|
mpath->dp = dp;
|
|
mpath->path = path; /* path is now owned by mpath */
|
|
|
|
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
|
|
/* Scan the /rtgroups directory itself. */
|
|
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,
|
|
kasprintf(GFP_KERNEL, "rtgroups"), sc->mp->m_rtdirip);
|
|
}
|
|
|
|
/* Scan a rtgroup inode under the /rtgroups directory. */
|
|
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 /* CONFIG_XFS_RT */
|
|
|
|
#ifdef CONFIG_XFS_QUOTA
|
|
/* Scan the /quota directory itself. */
|
|
static int
|
|
xchk_setup_metapath_quotadir(
|
|
struct xfs_scrub *sc)
|
|
{
|
|
struct xfs_trans *tp;
|
|
struct xfs_inode *dp = NULL;
|
|
int error;
|
|
|
|
error = xfs_trans_alloc_empty(sc->mp, &tp);
|
|
if (error)
|
|
return error;
|
|
|
|
error = xfs_dqinode_load_parent(tp, &dp);
|
|
xfs_trans_cancel(tp);
|
|
if (error)
|
|
return error;
|
|
|
|
error = xchk_setup_metapath_scan(sc, sc->mp->m_metadirip,
|
|
kasprintf(GFP_KERNEL, "quota"), dp);
|
|
xfs_irele(dp);
|
|
return error;
|
|
}
|
|
|
|
/* Scan a quota inode under the /quota directory. */
|
|
static int
|
|
xchk_setup_metapath_dqinode(
|
|
struct xfs_scrub *sc,
|
|
xfs_dqtype_t type)
|
|
{
|
|
struct xfs_trans *tp = NULL;
|
|
struct xfs_inode *dp = NULL;
|
|
struct xfs_inode *ip = NULL;
|
|
const char *path;
|
|
int error;
|
|
|
|
error = xfs_trans_alloc_empty(sc->mp, &tp);
|
|
if (error)
|
|
return error;
|
|
|
|
error = xfs_dqinode_load_parent(tp, &dp);
|
|
if (error)
|
|
goto out_cancel;
|
|
|
|
error = xfs_dqinode_load(tp, dp, type, &ip);
|
|
if (error)
|
|
goto out_dp;
|
|
|
|
xfs_trans_cancel(tp);
|
|
tp = NULL;
|
|
|
|
path = kasprintf(GFP_KERNEL, "%s", xfs_dqinode_path(type));
|
|
error = xchk_setup_metapath_scan(sc, dp, path, ip);
|
|
|
|
xfs_irele(ip);
|
|
out_dp:
|
|
xfs_irele(dp);
|
|
out_cancel:
|
|
if (tp)
|
|
xfs_trans_cancel(tp);
|
|
return error;
|
|
}
|
|
#else
|
|
# define xchk_setup_metapath_quotadir(...) (-ENOENT)
|
|
# define xchk_setup_metapath_dqinode(...) (-ENOENT)
|
|
#endif /* CONFIG_XFS_QUOTA */
|
|
|
|
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:
|
|
/* Just probing, nothing else to do. */
|
|
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);
|
|
default:
|
|
return -ENOENT;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Take the ILOCK on the metadata directory parent and child. We do not know
|
|
* that the metadata directory is not corrupt, so we lock the parent and try
|
|
* to lock the child. Returns 0 if successful, or -EINTR to abort the scrub.
|
|
*/
|
|
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;
|
|
}
|
|
|
|
/* Unlock parent and child inodes. */
|
|
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;
|
|
|
|
/* Just probing, nothing else to do. */
|
|
if (sc->sm->sm_ino == XFS_SCRUB_METAPATH_PROBE)
|
|
return 0;
|
|
|
|
/* Parent required to do anything else. */
|
|
if (mpath->dp == NULL) {
|
|
xchk_ino_set_corrupt(sc, sc->ip->i_ino);
|
|
return 0;
|
|
}
|
|
|
|
error = xchk_trans_alloc_empty(sc);
|
|
if (error)
|
|
return error;
|
|
|
|
error = xchk_metapath_ilock_both(mpath);
|
|
if (error)
|
|
goto out_cancel;
|
|
|
|
/* Make sure the parent dir has a dirent pointing to this file. */
|
|
error = xchk_dir_lookup(sc, mpath->dp, &mpath->xname, &ino);
|
|
trace_xchk_metapath_lookup(sc, mpath->path, mpath->dp, ino);
|
|
if (error == -ENOENT) {
|
|
/* No directory entry at all */
|
|
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) {
|
|
/* Pointing to wrong inode */
|
|
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
|
|
/* Create the dirent represented by the final component of the path. */
|
|
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);
|
|
}
|
|
|
|
/* Remove the dirent at the final component of the path. */
|
|
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) {
|
|
/* The child inode isn't allocated. Junk the dirent. */
|
|
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;
|
|
|
|
/* Figure out if we're removing a parent pointer too. */
|
|
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);
|
|
}
|
|
|
|
/*
|
|
* Try to create a dirent in @mpath->dp with the name @mpath->xname that points
|
|
* to @sc->ip. Returns:
|
|
*
|
|
* -EEXIST and an @alleged_child if the dirent that points to the wrong inode;
|
|
* 0 if there is now a dirent pointing to @sc->ip; or
|
|
* A negative errno on error.
|
|
*/
|
|
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;
|
|
|
|
/* Allocate transaction, lock inodes, join to transaction. */
|
|
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) {
|
|
/*
|
|
* There is no dirent in the directory. Create an entry
|
|
* pointing to @sc->ip.
|
|
*/
|
|
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) {
|
|
/* The dirent already points to @sc->ip; we're done. */
|
|
error = 0;
|
|
goto out_cancel;
|
|
}
|
|
|
|
/*
|
|
* The dirent points elsewhere; pass that back so that the caller
|
|
* can try to remove the dirent.
|
|
*/
|
|
*alleged_child = ino;
|
|
error = -EEXIST;
|
|
|
|
out_cancel:
|
|
xchk_trans_cancel(sc);
|
|
xchk_metapath_iunlock(mpath);
|
|
return error;
|
|
}
|
|
|
|
/*
|
|
* Take the ILOCK on the metadata directory parent and a bad child, if one is
|
|
* supplied. We do not know that the metadata directory is not corrupt, so we
|
|
* lock the parent and try to lock the child. Returns 0 if successful, or
|
|
* -EINTR to abort the repair. The lock state of @dp is not recorded in @mpath.
|
|
*/
|
|
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;
|
|
}
|
|
|
|
/*
|
|
* Try to remove a dirent in @mpath->dp with the name @mpath->xname that points
|
|
* to @alleged_child. Returns:
|
|
*
|
|
* 0 if there is no longer a dirent;
|
|
* -EEXIST if the dirent points to @sc->ip;
|
|
* -EAGAIN and an updated @alleged_child if the dirent points elsewhere; or
|
|
* A negative errno for any other error.
|
|
*/
|
|
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);
|
|
|
|
/*
|
|
* Allocate transaction, grab the alleged child inode, lock inodes,
|
|
* join to transaction.
|
|
*/
|
|
error = xchk_trans_alloc(sc, mpath->unlink_resblks);
|
|
if (error)
|
|
return error;
|
|
|
|
error = xchk_iget(sc, *alleged_child, &ip);
|
|
if (error == -EINVAL || error == -ENOENT) {
|
|
/* inode number is bogus, junk the dirent */
|
|
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) {
|
|
/*
|
|
* There is no dirent in the directory anymore. We're ready to
|
|
* try the link operation again.
|
|
*/
|
|
error = 0;
|
|
goto out_cancel;
|
|
}
|
|
if (error)
|
|
goto out_cancel;
|
|
|
|
if (ino == sc->ip->i_ino) {
|
|
/* The dirent already points to @sc->ip; we're done. */
|
|
error = -EEXIST;
|
|
goto out_cancel;
|
|
}
|
|
|
|
/*
|
|
* The dirent does not point to the alleged child. Update the caller
|
|
* and signal that we want to be called again.
|
|
*/
|
|
if (ino != *alleged_child) {
|
|
*alleged_child = ino;
|
|
error = -EAGAIN;
|
|
goto out_cancel;
|
|
}
|
|
|
|
/* Remove the link to the child. */
|
|
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;
|
|
}
|
|
|
|
/*
|
|
* Make sure the metadata directory path points to the child being examined.
|
|
*
|
|
* Repair needs to be able to create a directory structure, create its own
|
|
* transactions, and take ILOCKs. This function /must/ be called after all
|
|
* other repairs have completed.
|
|
*/
|
|
int
|
|
xrep_metapath(
|
|
struct xfs_scrub *sc)
|
|
{
|
|
struct xchk_metapath *mpath = sc->buf;
|
|
struct xfs_mount *mp = sc->mp;
|
|
int error = 0;
|
|
|
|
/* Just probing, nothing to repair. */
|
|
if (sc->sm->sm_ino == XFS_SCRUB_METAPATH_PROBE)
|
|
return 0;
|
|
|
|
/* Parent required to do anything else. */
|
|
if (mpath->dp == NULL)
|
|
return -EFSCORRUPTED;
|
|
|
|
/*
|
|
* Make sure the child file actually has an attr fork to receive a new
|
|
* parent pointer if the fs has parent pointers.
|
|
*/
|
|
if (xfs_has_parent(mp)) {
|
|
error = xfs_attr_add_fork(sc->ip,
|
|
sizeof(struct xfs_attr_sf_hdr), 1);
|
|
if (error)
|
|
return error;
|
|
}
|
|
|
|
/* Compute block reservation required to unlink and link a file. */
|
|
mpath->unlink_resblks = xfs_remove_space_res(mp, MAXNAMELEN);
|
|
mpath->link_resblks = xfs_link_space_res(mp, MAXNAMELEN);
|
|
|
|
do {
|
|
xfs_ino_t alleged_child;
|
|
|
|
/* Re-establish the link, or tell us which inode to remove. */
|
|
error = xrep_metapath_try_link(mpath, &alleged_child);
|
|
if (!error)
|
|
return 0;
|
|
if (error != -EEXIST)
|
|
return error;
|
|
|
|
/*
|
|
* Remove an incorrect link to an alleged child, or tell us
|
|
* which inode to remove.
|
|
*/
|
|
do {
|
|
error = xrep_metapath_try_unlink(mpath, &alleged_child);
|
|
} while (error == -EAGAIN);
|
|
if (error == -EEXIST) {
|
|
/* Link established; we're done. */
|
|
error = 0;
|
|
break;
|
|
}
|
|
} while (!error);
|
|
|
|
return error;
|
|
}
|
|
#endif /* CONFIG_XFS_ONLINE_REPAIR */
|