blob: 9c5001c54dc8676bde0e3948e09299c2357cc5b5 [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright (c) 2019 Oracle.
* All Rights Reserved.
*/
#include "libxfs.h"
#include "command.h"
#include "init.h"
#include "path.h"
#include "space.h"
#include "input.h"
#include "fsgeom.h"
static struct xfs_fsop_geom fsgeo;
static cmdinfo_t health_cmd;
static int reported;
static bool has_realtime(const struct xfs_fsop_geom *g)
{
return g->rtblocks > 0;
}
static bool has_finobt(const struct xfs_fsop_geom *g)
{
return g->flags & XFS_FSOP_GEOM_FLAGS_FINOBT;
}
static bool has_rmapbt(const struct xfs_fsop_geom *g)
{
return g->flags & XFS_FSOP_GEOM_FLAGS_RMAPBT;
}
static bool has_reflink(const struct xfs_fsop_geom *g)
{
return g->flags & XFS_FSOP_GEOM_FLAGS_REFLINK;
}
struct flag_map {
unsigned int mask;
bool (*has_fn)(const struct xfs_fsop_geom *g);
const char *descr;
};
static const struct flag_map fs_flags[] = {
{
.mask = XFS_FSOP_GEOM_SICK_COUNTERS,
.descr = "summary counters",
},
{
.mask = XFS_FSOP_GEOM_SICK_UQUOTA,
.descr = "user quota",
},
{
.mask = XFS_FSOP_GEOM_SICK_GQUOTA,
.descr = "group quota",
},
{
.mask = XFS_FSOP_GEOM_SICK_PQUOTA,
.descr = "project quota",
},
{
.mask = XFS_FSOP_GEOM_SICK_RT_BITMAP,
.descr = "realtime bitmap",
.has_fn = has_realtime,
},
{
.mask = XFS_FSOP_GEOM_SICK_RT_SUMMARY,
.descr = "realtime summary",
.has_fn = has_realtime,
},
{0},
};
static const struct flag_map ag_flags[] = {
{
.mask = XFS_AG_GEOM_SICK_SB,
.descr = "superblock",
},
{
.mask = XFS_AG_GEOM_SICK_AGF,
.descr = "AGF header",
},
{
.mask = XFS_AG_GEOM_SICK_AGFL,
.descr = "AGFL header",
},
{
.mask = XFS_AG_GEOM_SICK_AGI,
.descr = "AGI header",
},
{
.mask = XFS_AG_GEOM_SICK_BNOBT,
.descr = "free space by block btree",
},
{
.mask = XFS_AG_GEOM_SICK_CNTBT,
.descr = "free space by length btree",
},
{
.mask = XFS_AG_GEOM_SICK_INOBT,
.descr = "inode btree",
},
{
.mask = XFS_AG_GEOM_SICK_FINOBT,
.descr = "free inode btree",
.has_fn = has_finobt,
},
{
.mask = XFS_AG_GEOM_SICK_RMAPBT,
.descr = "reverse mappings btree",
.has_fn = has_rmapbt,
},
{
.mask = XFS_AG_GEOM_SICK_REFCNTBT,
.descr = "reference count btree",
.has_fn = has_reflink,
},
{0},
};
static const struct flag_map inode_flags[] = {
{
.mask = XFS_BS_SICK_INODE,
.descr = "inode core",
},
{
.mask = XFS_BS_SICK_BMBTD,
.descr = "data fork",
},
{
.mask = XFS_BS_SICK_BMBTA,
.descr = "extended attribute fork",
},
{
.mask = XFS_BS_SICK_BMBTC,
.descr = "copy on write fork",
},
{
.mask = XFS_BS_SICK_DIR,
.descr = "directory",
},
{
.mask = XFS_BS_SICK_XATTR,
.descr = "extended attributes",
},
{
.mask = XFS_BS_SICK_SYMLINK,
.descr = "symbolic link target",
},
{
.mask = XFS_BS_SICK_PARENT,
.descr = "parent pointers",
},
{0},
};
/* Convert a flag mask to a report. */
static void
report_sick(
const char *descr,
const struct flag_map *maps,
unsigned int sick,
unsigned int checked)
{
const struct flag_map *f;
bool b;
for (f = maps; f->mask != 0; f++) {
if (f->has_fn && !f->has_fn(&fsgeo))
continue;
if (!(checked & f->mask))
continue;
reported++;
b = sick & f->mask;
printf("%s %s: %s\n", descr, _(f->descr),
b ? _("unhealthy") : _("ok"));
}
}
/* Report on an AG's health. */
static int
report_ag_sick(
xfs_agnumber_t agno)
{
struct xfs_ag_geometry ageo;
char descr[256];
int ret;
ageo.ag_number = agno;
ret = ioctl(file->fd, XFS_IOC_AG_GEOMETRY, &ageo);
if (ret) {
perror("ag_geometry");
return 1;
}
snprintf(descr, sizeof(descr) - 1, _("AG %u"), agno);
report_sick(descr, ag_flags, ageo.ag_sick, ageo.ag_checked);
return 0;
}
/* Report on an inode's health. */
static int
report_inode_health(
unsigned long long ino,
const char *descr)
{
struct xfs_bstat bs;
char d[256];
__u64 oneino = ino;
__s32 onelen = 0;
int ret;
struct xfs_fsop_bulkreq onereq = {
.lastip = &oneino,
.icount = 1,
.ocount = &onelen,
.ubuffer = &bs,
};
if (!descr) {
snprintf(d, sizeof(d) - 1, _("inode %llu"), ino);
descr = d;
}
ret = ioctl(file->fd, XFS_IOC_FSBULKSTAT_SINGLE, &onereq);
if (ret) {
perror(descr);
return 1;
}
report_sick(descr, inode_flags, bs.bs_sick, bs.bs_checked);
return 0;
}
/* Report on a file's health. */
static int
report_file_health(
const char *path)
{
struct stat stata, statb;
int ret;
ret = lstat(path, &statb);
if (ret) {
perror(path);
return 1;
}
ret = fstat(file->fd, &stata);
if (ret) {
perror(file->name);
return 1;
}
if (stata.st_dev != statb.st_dev) {
fprintf(stderr, _("%s: not on the open filesystem"), path);
return 1;
}
return report_inode_health(statb.st_ino, path);
}
/* Report on health problems in XFS filesystem. */
static int
health_f(
int argc,
char **argv)
{
unsigned long long x;
xfs_agnumber_t agno;
bool default_report = true;
int c;
int ret;
reported = 0;
ret = xfs_fsgeometry(file->fd, &fsgeo);
if (ret) {
perror("fsgeometry");
return 1;
}
if (fsgeo.version != XFS_FSOP_GEOM_VERSION_V5) {
perror("health");
return 1;
}
while ((c = getopt(argc, argv, "a:fi:")) != EOF) {
switch (c) {
case 'a':
default_report = false;
errno = 0;
x = strtoll(optarg, NULL, 10);
if (!errno && x >= NULLAGNUMBER)
errno = ERANGE;
if (errno) {
perror("ag health");
return 1;
}
agno = x;
ret = report_ag_sick(agno);
break;
case 'f':
default_report = false;
report_sick(_("filesystem"), fs_flags, fsgeo.sick,
fsgeo.checked);
break;
case 'i':
default_report = false;
errno = 0;
x = strtoll(optarg, NULL, 10);
if (errno) {
perror("inode health");
return 1;
}
ret = report_inode_health(x, NULL);
break;
case '?':
default:
return command_usage(&health_cmd);
}
}
for (c = optind; c < argc; c++) {
default_report = false;
ret = report_file_health(argv[c]);
if (ret)
return 1;
}
/* No arguments gets us a summary of fs state. */
if (default_report) {
report_sick(_("filesystem"), fs_flags, fsgeo.sick,
fsgeo.checked);
for (agno = 0; agno < fsgeo.agcount; agno++) {
ret = report_ag_sick(agno);
if (ret)
return 1;
}
}
if (!reported) {
fprintf(stderr,
_("Health status has not been collected for this filesystem.\n"));
fprintf(stderr,
_("Please run xfs_scrub(8) to remedy this situation.\n"));
}
return 0;
}
static void
health_help(void)
{
printf(_(
"\n"
"Report all observed filesystem health problems.\n"
"\n"
" -a agno -- Report health of the given allocation group.\n"
" -f -- Report health of the overall filesystem.\n"
" -i inum -- Report health of a given inode number.\n"
" paths -- Report health of the given file path.\n"
"\n"));
}
static cmdinfo_t health_cmd = {
.name = "health",
.cfunc = health_f,
.argmin = 0,
.argmax = -1,
.args = "[-a agno] [-f] [-i inum] [paths]",
.flags = CMD_FLAG_ONESHOT,
.help = health_help,
};
void
health_init(void)
{
health_cmd.oneline = _("Report observed XFS health problems."),
add_command(&health_cmd);
}