blob: d83c5ccd90d5dc2322e7bce8038e474e3f96b6cd [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright (c) 2019 Oracle.
* All Rights Reserved.
*/
#include "platform_defs.h"
#include "libxfs.h"
#include "command.h"
#include "init.h"
#include "input.h"
#include "libfrog/logging.h"
#include "libfrog/paths.h"
#include "libfrog/fsgeom.h"
#include "libfrog/bulkstat.h"
#include "space.h"
static cmdinfo_t health_cmd;
static unsigned long long reported;
static bool comprehensive;
static bool quiet;
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 bad;
for (f = maps; f->mask != 0; f++) {
if (f->has_fn && !f->has_fn(&file->xfd.fsgeom))
continue;
bad = sick & f->mask;
if (!bad && !(checked & f->mask))
continue;
reported++;
if (!bad && quiet)
continue;
printf("%s %s: %s\n", descr, _(f->descr),
bad ? _("unhealthy") : _("ok"));
}
}
/* Report on an AG's health. */
static int
report_ag_sick(
xfs_agnumber_t agno)
{
struct xfs_ag_geometry ageo = { 0 };
char descr[256];
int ret;
ret = -xfrog_ag_geometry(file->xfd.fd, agno, &ageo);
if (ret) {
xfrog_perror(ret, "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_bulkstat bs;
char d[256];
int ret;
if (!descr) {
snprintf(d, sizeof(d) - 1, _("inode %llu"), ino);
descr = d;
}
ret = -xfrog_bulkstat_single(&file->xfd, ino, 0, &bs);
if (ret) {
xfrog_perror(ret, 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->xfd.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);
}
#define BULKSTAT_NR (128)
/*
* Report on all files' health for a given @agno. If @agno is NULLAGNUMBER,
* report on all files in the filesystem.
*/
static int
report_bulkstat_health(
xfs_agnumber_t agno)
{
struct xfs_bulkstat_req *breq;
char descr[256];
uint32_t i;
int error;
error = -xfrog_bulkstat_alloc_req(BULKSTAT_NR, 0, &breq);
if (error) {
xfrog_perror(error, "bulk alloc req");
exitcode = 1;
return 1;
}
if (agno != NULLAGNUMBER)
xfrog_bulkstat_set_ag(breq, agno);
do {
error = -xfrog_bulkstat(&file->xfd, breq);
if (error)
break;
for (i = 0; i < breq->hdr.ocount; i++) {
snprintf(descr, sizeof(descr) - 1, _("inode %"PRIu64),
breq->bulkstat[i].bs_ino);
report_sick(descr, inode_flags,
breq->bulkstat[i].bs_sick,
breq->bulkstat[i].bs_checked);
}
} while (breq->hdr.ocount > 0);
if (error)
xfrog_perror(error, "bulkstat");
free(breq);
return error;
}
#define OPT_STRING ("a:cfi:q")
/* 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;
if (file->xfd.fsgeom.version != XFS_FSOP_GEOM_VERSION_V5) {
perror("health");
return 1;
}
/* Set our reporting options appropriately in the first pass. */
while ((c = getopt(argc, argv, OPT_STRING)) != 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;
}
break;
case 'c':
comprehensive = true;
break;
case 'f':
default_report = false;
break;
case 'i':
default_report = false;
errno = 0;
x = strtoll(optarg, NULL, 10);
if (errno) {
perror("inode health");
return 1;
}
break;
case 'q':
quiet = true;
break;
default:
return command_usage(&health_cmd);
}
}
if (optind < argc)
default_report = false;
/* Reparse arguments, this time for reporting actions. */
optind = 1;
while ((c = getopt(argc, argv, OPT_STRING)) != EOF) {
switch (c) {
case 'a':
agno = strtoll(optarg, NULL, 10);
ret = report_ag_sick(agno);
if (!ret && comprehensive)
ret = report_bulkstat_health(agno);
if (ret)
return 1;
break;
case 'f':
report_sick(_("filesystem"), fs_flags,
file->xfd.fsgeom.sick,
file->xfd.fsgeom.checked);
if (comprehensive) {
ret = report_bulkstat_health(NULLAGNUMBER);
if (ret)
return 1;
}
break;
case 'i':
x = strtoll(optarg, NULL, 10);
ret = report_inode_health(x, NULL);
if (ret)
return 1;
break;
default:
break;
}
}
for (c = optind; c < argc; c++) {
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, file->xfd.fsgeom.sick,
file->xfd.fsgeom.checked);
for (agno = 0; agno < file->xfd.fsgeom.agcount; agno++) {
ret = report_ag_sick(agno);
if (ret)
return 1;
}
if (comprehensive) {
ret = report_bulkstat_health(NULLAGNUMBER);
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"
" -c -- Report on the health of all inodes.\n"
" -f -- Report health of the overall filesystem.\n"
" -i inum -- Report health of a given inode number.\n"
" -q -- Only report unhealthy metadata.\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] [-c] [-f] [-i inum] [-q] [paths]",
.flags = CMD_FLAG_ONESHOT,
.help = health_help,
};
void
health_init(void)
{
health_cmd.oneline = _("Report observed XFS health problems."),
add_command(&health_cmd);
}