xfs: online repair of directories

If a directory looks like it's in bad shape, try to sift through the
rubble to find whatever directory entries we can, scan the directory
tree for the parent (if needed), stage the new directory contents in a
temporary file and use the atomic extent swapping mechanism to commit
the results in bulk.

Signed-off-by: Darrick J. Wong <djwong@kernel.org>
Reviewed-by: Christoph Hellwig <hch@lst.de>
This commit is contained in:
Darrick J. Wong 2024-04-15 14:54:51 -07:00
parent 8d81082a8c
commit b1991ee3e7
15 changed files with 1563 additions and 2 deletions

View File

@ -198,6 +198,7 @@ xfs-y += $(addprefix scrub/, \
attr_repair.o \
bmap_repair.o \
cow_repair.o \
dir_repair.o \
fscounters_repair.o \
ialloc_repair.o \
inode_repair.o \

View File

@ -21,12 +21,21 @@
#include "scrub/dabtree.h"
#include "scrub/readdir.h"
#include "scrub/health.h"
#include "scrub/repair.h"
/* Set us up to scrub directories. */
int
xchk_setup_directory(
struct xfs_scrub *sc)
{
int error;
if (xchk_could_repair(sc)) {
error = xrep_setup_directory(sc);
if (error)
return error;
}
return xchk_setup_inode_contents(sc, 0);
}

1349
fs/xfs/scrub/dir_repair.c Normal file

File diff suppressed because it is too large Load Diff

View File

@ -46,6 +46,7 @@
#include "scrub/repair.h"
#include "scrub/iscan.h"
#include "scrub/readdir.h"
#include "scrub/tempfile.h"
/*
* Inode Record Repair
@ -340,6 +341,10 @@ xrep_dinode_findmode_walk_directory(
unsigned int lock_mode;
int error = 0;
/* Ignore temporary repair directories. */
if (xrep_is_tempfile(dp))
return 0;
/*
* Scan the directory to see if there it contains an entry pointing to
* the directory that we are repairing.

View File

@ -27,6 +27,7 @@
#include "scrub/nlinks.h"
#include "scrub/trace.h"
#include "scrub/readdir.h"
#include "scrub/tempfile.h"
/*
* Live Inode Link Count Checking
@ -152,6 +153,13 @@ xchk_nlinks_live_update(
xnc = container_of(nb, struct xchk_nlink_ctrs, dhook.dirent_hook.nb);
/*
* Ignore temporary directories being used to stage dir repairs, since
* we don't bump the link counts of the children.
*/
if (xrep_is_tempfile(p->dp))
return NOTIFY_DONE;
trace_xchk_nlinks_live_update(xnc->sc->mp, p->dp, action, p->ip->i_ino,
p->delta, p->name->name, p->name->len);
@ -303,6 +311,13 @@ xchk_nlinks_collect_dir(
unsigned int lock_mode;
int error = 0;
/*
* Ignore temporary directories being used to stage dir repairs, since
* we don't bump the link counts of the children.
*/
if (xrep_is_tempfile(dp))
return 0;
/* Prevent anyone from changing this directory while we walk it. */
xfs_ilock(dp, XFS_IOLOCK_SHARED);
lock_mode = xfs_ilock_data_map_shared(dp);
@ -537,6 +552,14 @@ xchk_nlinks_compare_inode(
unsigned int actual_nlink;
int error;
/*
* Ignore temporary files being used to stage repairs, since we assume
* they're correct for non-directories, and the directory repair code
* doesn't bump the link counts for the children.
*/
if (xrep_is_tempfile(ip))
return 0;
xfs_ilock(ip, XFS_ILOCK_SHARED);
mutex_lock(&xnc->lock);

View File

@ -26,6 +26,7 @@
#include "scrub/iscan.h"
#include "scrub/nlinks.h"
#include "scrub/trace.h"
#include "scrub/tempfile.h"
/*
* Live Inode Link Count Repair
@ -68,6 +69,14 @@ xrep_nlinks_repair_inode(
bool dirty = false;
int error;
/*
* Ignore temporary files being used to stage repairs, since we assume
* they're correct for non-directories, and the directory repair code
* doesn't bump the link counts for the children.
*/
if (xrep_is_tempfile(ip))
return 0;
xchk_ilock(sc, XFS_IOLOCK_EXCL);
error = xfs_trans_alloc(mp, &M_RES(mp)->tr_link, 0, 0, 0, &sc->tp);

View File

@ -17,6 +17,7 @@
#include "scrub/scrub.h"
#include "scrub/common.h"
#include "scrub/readdir.h"
#include "scrub/tempfile.h"
/* Set us up to scrub parents. */
int
@ -143,7 +144,8 @@ xchk_parent_validate(
}
if (!xchk_fblock_xref_process_error(sc, XFS_DATA_FORK, 0, &error))
return error;
if (dp == sc->ip || dp == sc->tempip || !S_ISDIR(VFS_I(dp)->i_mode)) {
if (dp == sc->ip || xrep_is_tempfile(dp) ||
!S_ISDIR(VFS_I(dp)->i_mode)) {
xchk_fblock_set_corrupt(sc, XFS_DATA_FORK, 0);
goto out_rele;
}

View File

@ -333,6 +333,13 @@ xchk_dir_lookup(
if (xfs_is_shutdown(dp->i_mount))
return -EIO;
/*
* A temporary directory's block headers are written with the owner
* set to sc->ip, so we must switch the owner here for the lookup.
*/
if (dp == sc->tempip)
args.owner = sc->ip->i_ino;
ASSERT(S_ISDIR(VFS_I(dp)->i_mode));
xfs_assert_ilocked(dp, XFS_ILOCK_SHARED | XFS_ILOCK_EXCL);

View File

@ -35,6 +35,7 @@
#include "xfs_da_format.h"
#include "xfs_da_btree.h"
#include "xfs_attr.h"
#include "xfs_dir2.h"
#include "scrub/scrub.h"
#include "scrub/common.h"
#include "scrub/trace.h"

View File

@ -91,6 +91,7 @@ int xrep_metadata_inode_forks(struct xfs_scrub *sc);
int xrep_setup_ag_rmapbt(struct xfs_scrub *sc);
int xrep_setup_ag_refcountbt(struct xfs_scrub *sc);
int xrep_setup_xattr(struct xfs_scrub *sc);
int xrep_setup_directory(struct xfs_scrub *sc);
/* Repair setup functions */
int xrep_setup_ag_allocbt(struct xfs_scrub *sc);
@ -125,6 +126,7 @@ int xrep_bmap_cow(struct xfs_scrub *sc);
int xrep_nlinks(struct xfs_scrub *sc);
int xrep_fscounters(struct xfs_scrub *sc);
int xrep_xattr(struct xfs_scrub *sc);
int xrep_directory(struct xfs_scrub *sc);
#ifdef CONFIG_XFS_RT
int xrep_rtbitmap(struct xfs_scrub *sc);
@ -195,6 +197,7 @@ xrep_setup_nothing(
#define xrep_setup_ag_rmapbt xrep_setup_nothing
#define xrep_setup_ag_refcountbt xrep_setup_nothing
#define xrep_setup_xattr xrep_setup_nothing
#define xrep_setup_directory xrep_setup_nothing
#define xrep_setup_inode(sc, imap) ((void)0)
@ -221,6 +224,7 @@ xrep_setup_nothing(
#define xrep_fscounters xrep_notsupported
#define xrep_rtsummary xrep_notsupported
#define xrep_xattr xrep_notsupported
#define xrep_directory xrep_notsupported
#endif /* CONFIG_XFS_ONLINE_REPAIR */

View File

@ -325,7 +325,7 @@ static const struct xchk_meta_ops meta_scrub_ops[] = {
.type = ST_INODE,
.setup = xchk_setup_directory,
.scrub = xchk_directory,
.repair = xrep_notsupported,
.repair = xrep_directory,
},
[XFS_SCRUB_TYPE_XATTR] = { /* extended attributes */
.type = ST_INODE,

View File

@ -841,3 +841,16 @@ xrep_tempfile_copyout_local(
ilog_flags |= xfs_ilog_fdata(whichfork);
xfs_trans_log_inode(sc->tp, sc->ip, ilog_flags);
}
/* Decide if a given XFS inode is a temporary file for a repair. */
bool
xrep_is_tempfile(
const struct xfs_inode *ip)
{
const struct inode *inode = &ip->i_vnode;
if (IS_PRIVATE(inode) && !(inode->i_opflags & IOP_XATTR))
return true;
return false;
}

View File

@ -35,11 +35,13 @@ int xrep_tempfile_set_isize(struct xfs_scrub *sc, unsigned long long isize);
int xrep_tempfile_roll_trans(struct xfs_scrub *sc);
void xrep_tempfile_copyout_local(struct xfs_scrub *sc, int whichfork);
bool xrep_is_tempfile(const struct xfs_inode *ip);
#else
static inline void xrep_tempfile_iolock_both(struct xfs_scrub *sc)
{
xchk_ilock(sc, XFS_IOLOCK_EXCL);
}
# define xrep_is_tempfile(ip) (false)
# define xrep_tempfile_rele(sc)
#endif /* CONFIG_XFS_ONLINE_REPAIR */

View File

@ -2500,6 +2500,118 @@ DEFINE_EVENT(xrep_xattr_class, name, \
DEFINE_XREP_XATTR_EVENT(xrep_xattr_rebuild_tree);
DEFINE_XREP_XATTR_EVENT(xrep_xattr_reset_fork);
TRACE_EVENT(xrep_dir_recover_dirblock,
TP_PROTO(struct xfs_inode *dp, xfs_dablk_t dabno, uint32_t magic,
uint32_t magic_guess),
TP_ARGS(dp, dabno, magic, magic_guess),
TP_STRUCT__entry(
__field(dev_t, dev)
__field(xfs_ino_t, dir_ino)
__field(xfs_dablk_t, dabno)
__field(uint32_t, magic)
__field(uint32_t, magic_guess)
),
TP_fast_assign(
__entry->dev = dp->i_mount->m_super->s_dev;
__entry->dir_ino = dp->i_ino;
__entry->dabno = dabno;
__entry->magic = magic;
__entry->magic_guess = magic_guess;
),
TP_printk("dev %d:%d dir 0x%llx dablk 0x%x magic 0x%x magic_guess 0x%x",
MAJOR(__entry->dev), MINOR(__entry->dev),
__entry->dir_ino,
__entry->dabno,
__entry->magic,
__entry->magic_guess)
);
DECLARE_EVENT_CLASS(xrep_dir_class,
TP_PROTO(struct xfs_inode *dp, xfs_ino_t parent_ino),
TP_ARGS(dp, parent_ino),
TP_STRUCT__entry(
__field(dev_t, dev)
__field(xfs_ino_t, dir_ino)
__field(xfs_ino_t, parent_ino)
),
TP_fast_assign(
__entry->dev = dp->i_mount->m_super->s_dev;
__entry->dir_ino = dp->i_ino;
__entry->parent_ino = parent_ino;
),
TP_printk("dev %d:%d dir 0x%llx parent 0x%llx",
MAJOR(__entry->dev), MINOR(__entry->dev),
__entry->dir_ino,
__entry->parent_ino)
)
#define DEFINE_XREP_DIR_EVENT(name) \
DEFINE_EVENT(xrep_dir_class, name, \
TP_PROTO(struct xfs_inode *dp, xfs_ino_t parent_ino), \
TP_ARGS(dp, parent_ino))
DEFINE_XREP_DIR_EVENT(xrep_dir_rebuild_tree);
DEFINE_XREP_DIR_EVENT(xrep_dir_reset_fork);
DECLARE_EVENT_CLASS(xrep_dirent_class,
TP_PROTO(struct xfs_inode *dp, const struct xfs_name *name,
xfs_ino_t ino),
TP_ARGS(dp, name, ino),
TP_STRUCT__entry(
__field(dev_t, dev)
__field(xfs_ino_t, dir_ino)
__field(unsigned int, namelen)
__dynamic_array(char, name, name->len)
__field(xfs_ino_t, ino)
__field(uint8_t, ftype)
),
TP_fast_assign(
__entry->dev = dp->i_mount->m_super->s_dev;
__entry->dir_ino = dp->i_ino;
__entry->namelen = name->len;
memcpy(__get_str(name), name->name, name->len);
__entry->ino = ino;
__entry->ftype = name->type;
),
TP_printk("dev %d:%d dir 0x%llx ftype %s name '%.*s' ino 0x%llx",
MAJOR(__entry->dev), MINOR(__entry->dev),
__entry->dir_ino,
__print_symbolic(__entry->ftype, XFS_DIR3_FTYPE_STR),
__entry->namelen,
__get_str(name),
__entry->ino)
)
#define DEFINE_XREP_DIRENT_EVENT(name) \
DEFINE_EVENT(xrep_dirent_class, name, \
TP_PROTO(struct xfs_inode *dp, const struct xfs_name *name, \
xfs_ino_t ino), \
TP_ARGS(dp, name, ino))
DEFINE_XREP_DIRENT_EVENT(xrep_dir_salvage_entry);
DEFINE_XREP_DIRENT_EVENT(xrep_dir_stash_createname);
DEFINE_XREP_DIRENT_EVENT(xrep_dir_replay_createname);
DECLARE_EVENT_CLASS(xrep_parent_salvage_class,
TP_PROTO(struct xfs_inode *dp, xfs_ino_t ino),
TP_ARGS(dp, ino),
TP_STRUCT__entry(
__field(dev_t, dev)
__field(xfs_ino_t, dir_ino)
__field(xfs_ino_t, ino)
),
TP_fast_assign(
__entry->dev = dp->i_mount->m_super->s_dev;
__entry->dir_ino = dp->i_ino;
__entry->ino = ino;
),
TP_printk("dev %d:%d dir 0x%llx parent 0x%llx",
MAJOR(__entry->dev), MINOR(__entry->dev),
__entry->dir_ino,
__entry->ino)
)
#define DEFINE_XREP_PARENT_SALVAGE_EVENT(name) \
DEFINE_EVENT(xrep_parent_salvage_class, name, \
TP_PROTO(struct xfs_inode *dp, xfs_ino_t ino), \
TP_ARGS(dp, ino))
DEFINE_XREP_PARENT_SALVAGE_EVENT(xrep_dir_salvaged_parent);
#endif /* IS_ENABLED(CONFIG_XFS_ONLINE_REPAIR) */
#endif /* _TRACE_XFS_SCRUB_TRACE_H */

View File

@ -23,4 +23,28 @@ int xfblob_free(struct xfblob *blob, xfblob_cookie cookie);
unsigned long long xfblob_bytes(struct xfblob *blob);
void xfblob_truncate(struct xfblob *blob);
static inline int
xfblob_storename(
struct xfblob *blob,
xfblob_cookie *cookie,
const struct xfs_name *xname)
{
return xfblob_store(blob, cookie, xname->name, xname->len);
}
static inline int
xfblob_loadname(
struct xfblob *blob,
xfblob_cookie cookie,
struct xfs_name *xname,
uint32_t size)
{
int ret = xfblob_load(blob, cookie, (void *)xname->name, size);
if (ret)
return ret;
xname->len = size;
return 0;
}
#endif /* __XFS_SCRUB_XFBLOB_H__ */