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:
parent
8d81082a8c
commit
b1991ee3e7
@ -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 \
|
||||
|
@ -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
1349
fs/xfs/scrub/dir_repair.c
Normal file
File diff suppressed because it is too large
Load Diff
@ -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.
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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"
|
||||
|
@ -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 */
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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 */
|
||||
|
||||
|
@ -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 */
|
||||
|
@ -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__ */
|
||||
|
Loading…
Reference in New Issue
Block a user