blob: 193738332dbd71ef0230f8a9a08a0004b97af7c1 [file]
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (C) 2025-2026 Oracle. All Rights Reserved.
* Author: Darrick J. Wong <djwong@kernel.org>
*/
#include "xfs.h"
#include "platform_defs.h"
#include "libfrog/healthevent.h"
#include "libfrog/flagmap.h"
/*
* The healthmon log string format is as follows:
*
* WHICH OBJECT: STATUS
*
* /mnt: 32 events lost
* /mnt agno 0x5 bnobt, rmapbt: sick
* /mnt rgno 0x5 bitmap: sick
* /mnt ino 13 gen 0x3 bmbtd: sick
* /mnt/a bmbtd: sick
* /mnt ino 13 gen 0x3 pos 4096 len 4096: directio_write failed
* /mnt/a pos 4096 len 4096: directio_read failed
* /mnt datadev daddr 0x13 bbcount 0x5: media error
* /mnt: filesystem shut down due to shenanigans, badness
*/
static const struct flag_map device_domains[] = {
{ XFS_HEALTH_MONITOR_DOMAIN_DATADEV, N_("datadev") },
{ XFS_HEALTH_MONITOR_DOMAIN_RTDEV, N_("rtdev") },
{ XFS_HEALTH_MONITOR_DOMAIN_LOGDEV, N_("logdev") },
{0, NULL},
};
static inline const char *
device_domain_string(
uint32_t domain)
{
return value_to_string(device_domains, domain);
}
static const struct flag_map fileio_types[] = {
{ XFS_HEALTH_MONITOR_TYPE_BUFREAD, N_("buffered_read") },
{ XFS_HEALTH_MONITOR_TYPE_BUFWRITE, N_("buffered_write") },
{ XFS_HEALTH_MONITOR_TYPE_DIOREAD, N_("directio_read") },
{ XFS_HEALTH_MONITOR_TYPE_DIOWRITE, N_("directio_write") },
{ XFS_HEALTH_MONITOR_TYPE_DATALOST, N_("media") },
{0, NULL},
};
static inline const char *
fileio_type_string(
uint32_t type)
{
return value_to_string(fileio_types, type);
}
static const struct flag_map health_types[] = {
{ XFS_HEALTH_MONITOR_TYPE_SICK, N_("sick") },
{ XFS_HEALTH_MONITOR_TYPE_CORRUPT, N_("corrupt") },
{ XFS_HEALTH_MONITOR_TYPE_HEALTHY, N_("healthy") },
{0, NULL},
};
static inline const char *
health_type_string(
uint32_t type)
{
return value_to_string(health_types, type);
}
/* Report that the kernel lost events. */
static void
report_lost(
const struct hme_prefix *pfx,
const struct xfs_health_monitor_event *hme)
{
printf("%s: %llu %s\n", pfx->mountpoint,
(unsigned long long)hme->e.lost.count,
_("events lost"));
fflush(stdout);
}
/* Report that the monitor is running. */
static void
report_running(
const struct hme_prefix *pfx,
const struct xfs_health_monitor_event *hme)
{
printf("%s: %s\n", pfx->mountpoint, _("monitoring started"));
fflush(stdout);
}
/* Report that the filesystem was unmounted. */
static void
report_unmounted(
const struct hme_prefix *pfx,
const struct xfs_health_monitor_event *hme)
{
printf("%s: %s\n", pfx->mountpoint, _("filesystem unmounted"));
fflush(stdout);
}
static const struct flag_map shutdown_reasons[] = {
{ XFS_HEALTH_SHUTDOWN_META_IO_ERROR, N_("metadata I/O error") },
{ XFS_HEALTH_SHUTDOWN_LOG_IO_ERROR, N_("log I/O error") },
{ XFS_HEALTH_SHUTDOWN_FORCE_UMOUNT, N_("forced unmount") },
{ XFS_HEALTH_SHUTDOWN_CORRUPT_INCORE, N_("in-memory state corruption") },
{ XFS_HEALTH_SHUTDOWN_CORRUPT_ONDISK, N_("ondisk metadata corruption") },
{ XFS_HEALTH_SHUTDOWN_DEVICE_REMOVED, N_("device removed") },
{0, NULL},
};
/* Report an abortive shutdown of the filesystem. */
static void
report_shutdown(
const struct hme_prefix *pfx,
const struct xfs_health_monitor_event *hme)
{
char buf[512];
mask_to_string(shutdown_reasons, hme->e.shutdown.reasons, ", ", buf,
sizeof(buf));
printf("%s: %s %s\n", pfx->mountpoint,
_("filesystem shut down due to"), buf);
fflush(stdout);
}
static const struct flag_map inode_structs[] = {
{ XFS_BS_SICK_INODE, N_("core") },
{ XFS_BS_SICK_BMBTD, N_("datafork") },
{ XFS_BS_SICK_BMBTA, N_("attrfork") },
{ XFS_BS_SICK_BMBTC, N_("cowfork") },
{ XFS_BS_SICK_DIR, N_("directory") },
{ XFS_BS_SICK_XATTR, N_("xattr") },
{ XFS_BS_SICK_SYMLINK, N_("symlink") },
{ XFS_BS_SICK_PARENT, N_("parent") },
{ XFS_BS_SICK_DIRTREE, N_("dirtree") },
{0, NULL},
};
/* Report inode metadata corruption */
static void
report_inode(
const struct hme_prefix *pfx,
const struct xfs_health_monitor_event *hme)
{
char buf[512];
mask_to_string(inode_structs, hme->e.inode.mask, ", ", buf,
sizeof(buf));
if (hme_prefix_has_path(pfx))
printf("%s %s: %s\n",
pfx->path,
buf,
health_type_string(hme->type));
else
printf("%s %s %llu %s 0x%x %s: %s\n",
pfx->mountpoint,
_("ino"),
(unsigned long long)hme->e.inode.ino,
_("gen"),
hme->e.inode.gen,
buf,
health_type_string(hme->type));
fflush(stdout);
}
static const struct flag_map ag_structs[] = {
{ XFS_AG_GEOM_SICK_SB, N_("super") },
{ XFS_AG_GEOM_SICK_AGF, N_("agf") },
{ XFS_AG_GEOM_SICK_AGFL, N_("agfl") },
{ XFS_AG_GEOM_SICK_AGI, N_("agi") },
{ XFS_AG_GEOM_SICK_BNOBT, N_("bnobt") },
{ XFS_AG_GEOM_SICK_CNTBT, N_("cntbt") },
{ XFS_AG_GEOM_SICK_INOBT, N_("inobt") },
{ XFS_AG_GEOM_SICK_FINOBT, N_("finobt") },
{ XFS_AG_GEOM_SICK_RMAPBT, N_("rmapbt") },
{ XFS_AG_GEOM_SICK_REFCNTBT, N_("refcountbt") },
{ XFS_AG_GEOM_SICK_INODES, N_("inodes") },
{0, NULL},
};
/* Report AG metadata corruption */
static void
report_ag(
const struct hme_prefix *pfx,
const struct xfs_health_monitor_event *hme)
{
char buf[512];
mask_to_string(ag_structs, hme->e.group.mask, ", ", buf,
sizeof(buf));
printf("%s %s 0x%x %s: %s\n",
pfx->mountpoint,
_("agno"),
hme->e.group.gno,
buf,
health_type_string(hme->type));
fflush(stdout);
}
static const struct flag_map rtgroup_structs[] = {
{ XFS_RTGROUP_GEOM_SICK_SUPER, N_("super") },
{ XFS_RTGROUP_GEOM_SICK_BITMAP, N_("bitmap") },
{ XFS_RTGROUP_GEOM_SICK_SUMMARY, N_("summary") },
{ XFS_RTGROUP_GEOM_SICK_RMAPBT, N_("rmapbt") },
{ XFS_RTGROUP_GEOM_SICK_REFCNTBT, N_("refcountbt") },
{0, NULL},
};
/* Report rtgroup metadata corruption */
static void
report_rtgroup(
const struct hme_prefix *pfx,
const struct xfs_health_monitor_event *hme)
{
char buf[512];
mask_to_string(rtgroup_structs, hme->e.group.mask, ", ", buf,
sizeof(buf));
printf("%s %s 0x%x %s: %s\n",
pfx->mountpoint,
_("rgno"),
hme->e.group.gno,
buf, health_type_string(hme->type));
fflush(stdout);
}
static const struct flag_map fs_structs[] = {
{ XFS_FSOP_GEOM_SICK_COUNTERS, N_("fscounters") },
{ XFS_FSOP_GEOM_SICK_UQUOTA, N_("usrquota") },
{ XFS_FSOP_GEOM_SICK_GQUOTA, N_("grpquota") },
{ XFS_FSOP_GEOM_SICK_PQUOTA, N_("prjquota") },
{ XFS_FSOP_GEOM_SICK_RT_BITMAP, N_("bitmap") },
{ XFS_FSOP_GEOM_SICK_RT_SUMMARY, N_("summary") },
{ XFS_FSOP_GEOM_SICK_QUOTACHECK, N_("quotacheck") },
{ XFS_FSOP_GEOM_SICK_NLINKS, N_("nlinks") },
{ XFS_FSOP_GEOM_SICK_METADIR, N_("metadir") },
{ XFS_FSOP_GEOM_SICK_METAPATH, N_("metapath") },
{0, NULL},
};
/* Report fs-wide metadata corruption */
static void
report_fs(
const struct hme_prefix *pfx,
const struct xfs_health_monitor_event *hme)
{
char buf[512];
mask_to_string(fs_structs, hme->e.fs.mask, ", ", buf, sizeof(buf));
printf("%s %s: %s\n",
pfx->mountpoint,
buf,
health_type_string(hme->type));
fflush(stdout);
}
/* Report device media corruption */
static void
report_device_error(
const struct hme_prefix *pfx,
const struct xfs_health_monitor_event *hme)
{
printf("%s %s %s 0x%llx %s 0x%llx: %s\n", pfx->mountpoint,
device_domain_string(hme->domain),
_("daddr"),
(unsigned long long)hme->e.media.daddr,
_("bbcount"),
(unsigned long long)hme->e.media.bbcount,
_("media error"));
fflush(stdout);
}
/* Report file range errors */
static void
report_file_range(
const struct hme_prefix *pfx,
const struct xfs_health_monitor_event *hme)
{
if (hme_prefix_has_path(pfx))
printf("%s ", pfx->path);
else
printf("%s %s %llu %s 0x%x ",
pfx->mountpoint,
_("ino"),
(unsigned long long)hme->e.filerange.ino,
_("gen"),
hme->e.filerange.gen);
if (hme->type != XFS_HEALTH_MONITOR_TYPE_DATALOST &&
hme->e.filerange.error)
printf("%s %llu %s %llu: %s: %s\n",
_("pos"),
(unsigned long long)hme->e.filerange.pos,
_("len"),
(unsigned long long)hme->e.filerange.len,
fileio_type_string(hme->type),
strerror(hme->e.filerange.error));
else
printf("%s %llu %s %llu: %s %s\n",
_("pos"),
(unsigned long long)hme->e.filerange.pos,
_("len"),
(unsigned long long)hme->e.filerange.len,
fileio_type_string(hme->type),
_("failed"));
fflush(stdout);
}
/* Log a health monitoring event to stdout. */
void
hme_report_event(
const struct hme_prefix *pfx,
const struct xfs_health_monitor_event *hme)
{
switch (hme->domain) {
case XFS_HEALTH_MONITOR_DOMAIN_MOUNT:
switch (hme->type) {
case XFS_HEALTH_MONITOR_TYPE_LOST:
report_lost(pfx, hme);
return;
case XFS_HEALTH_MONITOR_TYPE_RUNNING:
report_running(pfx, hme);
return;
case XFS_HEALTH_MONITOR_TYPE_UNMOUNT:
report_unmounted(pfx, hme);
return;
case XFS_HEALTH_MONITOR_TYPE_SHUTDOWN:
report_shutdown(pfx, hme);
return;
}
break;
case XFS_HEALTH_MONITOR_DOMAIN_INODE:
report_inode(pfx, hme);
break;
case XFS_HEALTH_MONITOR_DOMAIN_AG:
report_ag(pfx, hme);
break;
case XFS_HEALTH_MONITOR_DOMAIN_RTGROUP:
report_rtgroup(pfx, hme);
break;
case XFS_HEALTH_MONITOR_DOMAIN_FS:
report_fs(pfx, hme);
break;
case XFS_HEALTH_MONITOR_DOMAIN_DATADEV:
case XFS_HEALTH_MONITOR_DOMAIN_RTDEV:
case XFS_HEALTH_MONITOR_DOMAIN_LOGDEV:
report_device_error(pfx, hme);
break;
case XFS_HEALTH_MONITOR_DOMAIN_FILERANGE:
report_file_range(pfx, hme);
break;
}
}
static const char *
repair_outcome_string(
enum repair_outcome o)
{
switch (o) {
case REPAIR_FAILED:
return _("Repair unsuccessful; offline repair required.");
case REPAIR_PROBABLY_OK:
return _("Seems correct but cross-referencing failed; offline repair recommended.");
case REPAIR_UNNECESSARY:
return _("No modification needed.");
case REPAIR_SUCCESS:
return _("Repairs successful.");
}
return NULL;
}
/* Report inode metadata repair */
static void
report_inode_repair(
const struct hme_prefix *pfx,
const struct xfs_health_monitor_event *hme,
uint32_t domain_mask,
enum repair_outcome outcome)
{
if (hme_prefix_has_path(pfx))
printf("%s %s: %s\n",
pfx->path,
lowest_set_mask_string(inode_structs,
domain_mask),
repair_outcome_string(outcome));
else
printf("%s %s %llu %s 0x%x %s: %s\n",
pfx->mountpoint,
_("ino"),
(unsigned long long)hme->e.inode.ino,
_("gen"),
hme->e.inode.gen,
lowest_set_mask_string(inode_structs,
domain_mask),
repair_outcome_string(outcome));
fflush(stdout);
}
/* Report AG metadata repair */
static void
report_ag_repair(
const struct hme_prefix *pfx,
const struct xfs_health_monitor_event *hme,
uint32_t domain_mask,
enum repair_outcome outcome)
{
printf("%s %s 0x%x %s: %s\n", pfx->mountpoint,
_("agno"),
hme->e.group.gno,
lowest_set_mask_string(ag_structs, domain_mask),
repair_outcome_string(outcome));
fflush(stdout);
}
/* Report rtgroup metadata repair */
static void
report_rtgroup_repair(
const struct hme_prefix *pfx,
const struct xfs_health_monitor_event *hme,
uint32_t domain_mask,
enum repair_outcome outcome)
{
printf("%s %s 0x%x %s: %s\n", pfx->mountpoint,
_("rgno"),
hme->e.group.gno,
lowest_set_mask_string(rtgroup_structs, domain_mask),
repair_outcome_string(outcome));
fflush(stdout);
}
/* Report fs-wide metadata repair */
static void
report_fs_repair(
const struct hme_prefix *pfx,
const struct xfs_health_monitor_event *hme,
uint32_t domain_mask,
enum repair_outcome outcome)
{
printf("%s %s: %s\n", pfx->mountpoint,
lowest_set_mask_string(fs_structs, domain_mask),
repair_outcome_string(outcome));
fflush(stdout);
}
/* Log a repair event to stdout. */
void
report_health_repair(
const struct hme_prefix *pfx,
const struct xfs_health_monitor_event *hme,
uint32_t domain_mask,
enum repair_outcome outcome)
{
switch (hme->domain) {
case XFS_HEALTH_MONITOR_DOMAIN_INODE:
report_inode_repair(pfx, hme, domain_mask, outcome);
break;
case XFS_HEALTH_MONITOR_DOMAIN_AG:
report_ag_repair(pfx, hme, domain_mask, outcome);
break;
case XFS_HEALTH_MONITOR_DOMAIN_RTGROUP:
report_rtgroup_repair(pfx, hme, domain_mask, outcome);
break;
case XFS_HEALTH_MONITOR_DOMAIN_FS:
report_fs_repair(pfx, hme, domain_mask, outcome);
break;
default:
break;
}
}