xfs: handle reconnecting metadir subdirectories

A longstanding weakness of the metapath repair code is that it can only
reattach non-directories to the metadata directory tree.  Let's fix
that by allowing reconnection of subdirectories.

Note that with the initial users of metadir (rtgroups and quota),
there's no way to mount a filesystem with broken /rtgroups or /quota
subdirectories, so this code won't be all that useful until something
adds deeper directory trees.  But we shouldn't leave a logic bomb for
those futures users wherein we get the link count wrong for a subdir.

Signed-off-by: Darrick J. Wong <djwong@kernel.org>
Reviewed-by: Christoph Hellwig <hch@lst.de>
Signed-off-by: Carlos Maiolino <cem@kernel.org>
This commit is contained in:
Darrick J. Wong 2026-08-26 22:32:12 -07:00 committed by Carlos Maiolino
parent cdc4a083ad
commit 9dd6cc92a7

View File

@ -23,6 +23,7 @@
#include "xfs_rtgroup.h"
#include "xfs_rtrmap_btree.h"
#include "xfs_rtrefcount_btree.h"
#include "xfs_ag.h"
#include "scrub/scrub.h"
#include "scrub/common.h"
#include "scrub/trace.h"
@ -348,12 +349,78 @@ xchk_metapath(
}
#ifdef CONFIG_XFS_ONLINE_REPAIR
/*
* Given a directory @dp, an existing inode @ip, and a @name, link @ip into @dp
* under the given @name.
*/
static int
xrep_metadir_add_child(
struct xchk_metapath *mpath,
xfs_ino_t old_dotdot)
{
struct xfs_trans *tp = mpath->sc->tp;
struct xfs_dir_update *du = &mpath->du;
struct xfs_inode *dp = du->dp;
const struct xfs_name *name = du->name;
struct xfs_inode *ip = du->ip;
struct xfs_mount *mp = tp->t_mountp;
const unsigned int resblks = mpath->link_resblks;
int error;
/*
* The metadata file shouldn't be on the unlinked list, but we'll fix
* it if that is the case.
*/
if (VFS_I(ip)->i_nlink == 0) {
struct xfs_perag *pag;
pag = xfs_perag_get(mp, XFS_INO_TO_AGNO(mp, I_INO(ip)));
error = xfs_iunlink_remove(tp, pag, ip);
xfs_perag_put(pag);
if (error)
return error;
}
error = xfs_dir_createname(tp, dp, name, I_INO(ip), resblks);
if (error)
return error;
xfs_trans_log_inode(tp, dp, XFS_ILOG_CORE);
xfs_bumplink(tp, ip);
/* update dotdot entry in child */
if (S_ISDIR(VFS_I(ip)->i_mode)) {
xfs_bumplink(tp, dp);
/* Replace the dotdot entry in the child */
if (old_dotdot != I_INO(dp)) {
error = xfs_dir_replace(tp, ip, &xfs_name_dotdot,
I_INO(dp), resblks);
if (error)
return error;
}
}
/* Update the child's parent pointer */
if (du->ppargs) {
error = xfs_parent_addname(tp, du->ppargs, dp, name, ip);
if (error)
return error;
}
xfs_dir_update_hook(dp, ip, 1, name);
return 0;
}
/* 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;
xfs_ino_t old_dotdot = NULLFSINO;
int error;
mpath->du.dp = mpath->dp;
mpath->du.name = &mpath->xname;
@ -366,7 +433,21 @@ xrep_metapath_link(
trace_xrep_metapath_link(sc, mpath->path, mpath->dp, I_INO(sc->ip));
return xfs_dir_add_child(sc->tp, mpath->link_resblks, &mpath->du);
if (S_ISDIR(VFS_I(sc->ip)->i_mode)) {
error = xchk_dir_lookup(sc, sc->ip, &xfs_name_dotdot,
&old_dotdot);
if (error && error != -ENOENT)
return error;
/*
* subdir didn't give us a dotdot entry, so we just give up
* and let the repair get marked as failed.
*/
if (old_dotdot == NULLFSINO)
return 0;
}
return xrep_metadir_add_child(mpath, old_dotdot);
}
/* Remove the dirent at the final component of the path. */