Commit f1184081 authored by Darrick J. Wong's avatar Darrick J. Wong

xfs: teach scrub to check file nlinks

Create the necessary scrub code to walk the filesystem's directory tree
so that we can compute file link counts.  Similar to quotacheck, we
create an incore shadow array of link count information and then we walk
the filesystem a second time to compare the link counts.  We need live
updates to keep the information up to date during the lengthy scan, so
this scrubber remains disabled until the next patch.
Signed-off-by: default avatarDarrick J. Wong <djwong@kernel.org>
Reviewed-by: default avatarChristoph Hellwig <hch@lst.de>
parent 93687ee2
......@@ -160,6 +160,7 @@ xfs-y += $(addprefix scrub/, \
ialloc.o \
inode.o \
iscan.o \
nlinks.o \
parent.o \
readdir.o \
refcount.o \
......
......@@ -712,9 +712,10 @@ struct xfs_scrub_metadata {
#define XFS_SCRUB_TYPE_PQUOTA 23 /* project quotas */
#define XFS_SCRUB_TYPE_FSCOUNTERS 24 /* fs summary counters */
#define XFS_SCRUB_TYPE_QUOTACHECK 25 /* quota counters */
#define XFS_SCRUB_TYPE_NLINKS 26 /* inode link counts */
/* Number of scrub subcommands. */
#define XFS_SCRUB_TYPE_NR 26
#define XFS_SCRUB_TYPE_NR 27
/* i: Repair this metadata. */
#define XFS_SCRUB_IFLAG_REPAIR (1u << 0)
......
......@@ -129,6 +129,7 @@ xchk_setup_quotacheck(struct xfs_scrub *sc)
}
#endif
int xchk_setup_fscounters(struct xfs_scrub *sc);
int xchk_setup_nlinks(struct xfs_scrub *sc);
void xchk_ag_free(struct xfs_scrub *sc, struct xchk_ag *sa);
int xchk_ag_init(struct xfs_scrub *sc, xfs_agnumber_t agno,
......
......@@ -106,6 +106,7 @@ static const struct xchk_health_map type_to_health_flag[XFS_SCRUB_TYPE_NR] = {
[XFS_SCRUB_TYPE_PQUOTA] = { XHG_FS, XFS_SICK_FS_PQUOTA },
[XFS_SCRUB_TYPE_FSCOUNTERS] = { XHG_FS, XFS_SICK_FS_COUNTERS },
[XFS_SCRUB_TYPE_QUOTACHECK] = { XHG_FS, XFS_SICK_FS_QUOTACHECK },
[XFS_SCRUB_TYPE_NLINKS] = { XHG_FS, XFS_SICK_FS_NLINKS },
};
/* Return the health status mask for this scrub type. */
......
This diff is collapsed.
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Copyright (c) 2021-2024 Oracle. All Rights Reserved.
* Author: Darrick J. Wong <djwong@kernel.org>
*/
#ifndef __XFS_SCRUB_NLINKS_H__
#define __XFS_SCRUB_NLINKS_H__
/* Live link count control structure. */
struct xchk_nlink_ctrs {
struct xfs_scrub *sc;
/* Shadow link count data and its mutex. */
struct xfarray *nlinks;
struct mutex lock;
/*
* The collection step uses a separate iscan context from the compare
* step because the collection iscan coordinates live updates to the
* observation data while this scanner is running. The compare iscan
* is secondary and can be reinitialized as needed.
*/
struct xchk_iscan collect_iscan;
struct xchk_iscan compare_iscan;
};
/*
* In-core link counts for a given inode in the filesystem.
*
* For an empty rootdir, the directory entries and the field to which they are
* accounted are as follows:
*
* Root directory:
*
* . points to self (root.child)
* .. points to self (root.parent)
* f1 points to a child file (f1.parent)
* d1 points to a child dir (d1.parent, root.child)
*
* Subdirectory d1:
*
* . points to self (d1.child)
* .. points to root dir (root.backref)
* f2 points to child file (f2.parent)
* f3 points to root.f1 (f1.parent)
*
* root.nlink == 3 (root.dot, root.dotdot, root.d1)
* d1.nlink == 2 (root.d1, d1.dot)
* f1.nlink == 2 (root.f1, d1.f3)
* f2.nlink == 1 (d1.f2)
*/
struct xchk_nlink {
/* Count of forward links from parent directories to this file. */
xfs_nlink_t parents;
/*
* Count of back links to this parent directory from child
* subdirectories.
*/
xfs_nlink_t backrefs;
/*
* Count of forward links from this directory to all child files and
* the number of dot entries. Should be zero for non-directories.
*/
xfs_nlink_t children;
/* Record state flags */
unsigned int flags;
};
/*
* This incore link count has been written at least once. We never want to
* store an xchk_nlink that looks uninitialized.
*/
#define XCHK_NLINK_WRITTEN (1U << 0)
/* This data item was seen by the check-time compare function. */
#define XCHK_NLINK_COMPARE_SCANNED (1U << 1)
/* Compute total link count, using large enough variables to detect overflow. */
static inline uint64_t
xchk_nlink_total(struct xfs_inode *ip, const struct xchk_nlink *live)
{
uint64_t ret = live->parents;
/* Add one link count for the dot entry of any linked directory. */
if (ip && S_ISDIR(VFS_I(ip)->i_mode) && VFS_I(ip)->i_nlink)
ret++;
return ret + live->children;
}
#endif /* __XFS_SCRUB_NLINKS_H__ */
......@@ -369,6 +369,12 @@ static const struct xchk_meta_ops meta_scrub_ops[] = {
.scrub = xchk_quotacheck,
.repair = xrep_quotacheck,
},
[XFS_SCRUB_TYPE_NLINKS] = { /* inode link counts */
.type = ST_FS,
.setup = xchk_setup_nlinks,
.scrub = xchk_nlinks,
.repair = xrep_notsupported,
},
};
static int
......
......@@ -183,6 +183,7 @@ xchk_quotacheck(struct xfs_scrub *sc)
}
#endif
int xchk_fscounters(struct xfs_scrub *sc);
int xchk_nlinks(struct xfs_scrub *sc);
/* cross-referencing helpers */
void xchk_xref_is_used_space(struct xfs_scrub *sc, xfs_agblock_t agbno,
......
......@@ -78,6 +78,7 @@ static const char *name_map[XFS_SCRUB_TYPE_NR] = {
[XFS_SCRUB_TYPE_PQUOTA] = "prjquota",
[XFS_SCRUB_TYPE_FSCOUNTERS] = "fscounters",
[XFS_SCRUB_TYPE_QUOTACHECK] = "quotacheck",
[XFS_SCRUB_TYPE_NLINKS] = "nlinks",
};
/* Format the scrub stats into a text buffer, similar to pcp style. */
......
......@@ -17,11 +17,13 @@
#include "xfs_quota.h"
#include "xfs_quota_defs.h"
#include "xfs_da_format.h"
#include "xfs_dir2.h"
#include "scrub/scrub.h"
#include "scrub/xfile.h"
#include "scrub/xfarray.h"
#include "scrub/quota.h"
#include "scrub/iscan.h"
#include "scrub/nlinks.h"
/* Figure out which block the btree cursor was pointing to. */
static inline xfs_fsblock_t
......
......@@ -23,6 +23,7 @@ struct xfarray;
struct xfarray_sortinfo;
struct xchk_dqiter;
struct xchk_iscan;
struct xchk_nlink;
/*
* ftrace's __print_symbolic requires that all enum values be wrapped in the
......@@ -67,6 +68,7 @@ TRACE_DEFINE_ENUM(XFS_SCRUB_TYPE_GQUOTA);
TRACE_DEFINE_ENUM(XFS_SCRUB_TYPE_PQUOTA);
TRACE_DEFINE_ENUM(XFS_SCRUB_TYPE_FSCOUNTERS);
TRACE_DEFINE_ENUM(XFS_SCRUB_TYPE_QUOTACHECK);
TRACE_DEFINE_ENUM(XFS_SCRUB_TYPE_NLINKS);
#define XFS_SCRUB_TYPE_STRINGS \
{ XFS_SCRUB_TYPE_PROBE, "probe" }, \
......@@ -94,7 +96,8 @@ TRACE_DEFINE_ENUM(XFS_SCRUB_TYPE_QUOTACHECK);
{ XFS_SCRUB_TYPE_GQUOTA, "grpquota" }, \
{ XFS_SCRUB_TYPE_PQUOTA, "prjquota" }, \
{ XFS_SCRUB_TYPE_FSCOUNTERS, "fscounters" }, \
{ XFS_SCRUB_TYPE_QUOTACHECK, "quotacheck" }
{ XFS_SCRUB_TYPE_QUOTACHECK, "quotacheck" }, \
{ XFS_SCRUB_TYPE_NLINKS, "nlinks" }
#define XFS_SCRUB_FLAG_STRINGS \
{ XFS_SCRUB_IFLAG_REPAIR, "repair" }, \
......@@ -1318,6 +1321,148 @@ TRACE_EVENT(xchk_iscan_iget_retry_wait,
__entry->retry_delay)
);
TRACE_EVENT(xchk_nlinks_collect_dirent,
TP_PROTO(struct xfs_mount *mp, struct xfs_inode *dp,
xfs_ino_t ino, const struct xfs_name *name),
TP_ARGS(mp, dp, ino, name),
TP_STRUCT__entry(
__field(dev_t, dev)
__field(xfs_ino_t, dir)
__field(xfs_ino_t, ino)
__field(unsigned int, namelen)
__dynamic_array(char, name, name->len)
),
TP_fast_assign(
__entry->dev = mp->m_super->s_dev;
__entry->dir = dp->i_ino;
__entry->ino = ino;
__entry->namelen = name->len;
memcpy(__get_str(name), name->name, name->len);
),
TP_printk("dev %d:%d dir 0x%llx -> ino 0x%llx name '%.*s'",
MAJOR(__entry->dev), MINOR(__entry->dev),
__entry->dir,
__entry->ino,
__entry->namelen,
__get_str(name))
);
TRACE_EVENT(xchk_nlinks_collect_metafile,
TP_PROTO(struct xfs_mount *mp, xfs_ino_t ino),
TP_ARGS(mp, ino),
TP_STRUCT__entry(
__field(dev_t, dev)
__field(xfs_ino_t, ino)
),
TP_fast_assign(
__entry->dev = mp->m_super->s_dev;
__entry->ino = ino;
),
TP_printk("dev %d:%d ino 0x%llx",
MAJOR(__entry->dev), MINOR(__entry->dev),
__entry->ino)
);
TRACE_EVENT(xchk_nlinks_check_zero,
TP_PROTO(struct xfs_mount *mp, xfs_ino_t ino,
const struct xchk_nlink *live),
TP_ARGS(mp, ino, live),
TP_STRUCT__entry(
__field(dev_t, dev)
__field(xfs_ino_t, ino)
__field(xfs_nlink_t, parents)
__field(xfs_nlink_t, backrefs)
__field(xfs_nlink_t, children)
),
TP_fast_assign(
__entry->dev = mp->m_super->s_dev;
__entry->ino = ino;
__entry->parents = live->parents;
__entry->backrefs = live->backrefs;
__entry->children = live->children;
),
TP_printk("dev %d:%d ino 0x%llx parents %u backrefs %u children %u",
MAJOR(__entry->dev), MINOR(__entry->dev),
__entry->ino,
__entry->parents,
__entry->backrefs,
__entry->children)
);
TRACE_EVENT(xchk_nlinks_update_incore,
TP_PROTO(struct xfs_mount *mp, xfs_ino_t ino,
const struct xchk_nlink *live, int parents_delta,
int backrefs_delta, int children_delta),
TP_ARGS(mp, ino, live, parents_delta, backrefs_delta, children_delta),
TP_STRUCT__entry(
__field(dev_t, dev)
__field(xfs_ino_t, ino)
__field(xfs_nlink_t, parents)
__field(xfs_nlink_t, backrefs)
__field(xfs_nlink_t, children)
__field(int, parents_delta)
__field(int, backrefs_delta)
__field(int, children_delta)
),
TP_fast_assign(
__entry->dev = mp->m_super->s_dev;
__entry->ino = ino;
__entry->parents = live->parents;
__entry->backrefs = live->backrefs;
__entry->children = live->children;
__entry->parents_delta = parents_delta;
__entry->backrefs_delta = backrefs_delta;
__entry->children_delta = children_delta;
),
TP_printk("dev %d:%d ino 0x%llx parents %d:%u backrefs %d:%u children %d:%u",
MAJOR(__entry->dev), MINOR(__entry->dev),
__entry->ino,
__entry->parents_delta,
__entry->parents,
__entry->backrefs_delta,
__entry->backrefs,
__entry->children_delta,
__entry->children)
);
DECLARE_EVENT_CLASS(xchk_nlinks_diff_class,
TP_PROTO(struct xfs_mount *mp, struct xfs_inode *ip,
const struct xchk_nlink *live),
TP_ARGS(mp, ip, live),
TP_STRUCT__entry(
__field(dev_t, dev)
__field(xfs_ino_t, ino)
__field(uint8_t, ftype)
__field(xfs_nlink_t, nlink)
__field(xfs_nlink_t, parents)
__field(xfs_nlink_t, backrefs)
__field(xfs_nlink_t, children)
),
TP_fast_assign(
__entry->dev = mp->m_super->s_dev;
__entry->ino = ip->i_ino;
__entry->ftype = xfs_mode_to_ftype(VFS_I(ip)->i_mode);
__entry->nlink = VFS_I(ip)->i_nlink;
__entry->parents = live->parents;
__entry->backrefs = live->backrefs;
__entry->children = live->children;
),
TP_printk("dev %d:%d ino 0x%llx ftype %s nlink %u parents %u backrefs %u children %u",
MAJOR(__entry->dev), MINOR(__entry->dev),
__entry->ino,
__print_symbolic(__entry->ftype, XFS_DIR3_FTYPE_STR),
__entry->nlink,
__entry->parents,
__entry->backrefs,
__entry->children)
);
#define DEFINE_SCRUB_NLINKS_DIFF_EVENT(name) \
DEFINE_EVENT(xchk_nlinks_diff_class, name, \
TP_PROTO(struct xfs_mount *mp, struct xfs_inode *ip, \
const struct xchk_nlink *live), \
TP_ARGS(mp, ip, live))
DEFINE_SCRUB_NLINKS_DIFF_EVENT(xchk_nlinks_compare_inode);
/* repair tracepoints */
#if IS_ENABLED(CONFIG_XFS_ONLINE_REPAIR)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment