| // SPDX-License-Identifier: GPL-2.0+ | 
 | /* | 
 |  * Copyright (C) 2019 Oracle.  All Rights Reserved. | 
 |  * Author: Darrick J. Wong <darrick.wong@oracle.com> | 
 |  */ | 
 | #include "xfs.h" | 
 | #include "xfs_fs.h" | 
 | #include "xfs_shared.h" | 
 | #include "xfs_format.h" | 
 | #include "xfs_log_format.h" | 
 | #include "xfs_trans_resv.h" | 
 | #include "xfs_mount.h" | 
 | #include "xfs_inode.h" | 
 | #include "xfs_trace.h" | 
 | #include "xfs_health.h" | 
 | #include "xfs_ag.h" | 
 |  | 
 | /* | 
 |  * Warn about metadata corruption that we detected but haven't fixed, and | 
 |  * make sure we're not sitting on anything that would get in the way of | 
 |  * recovery. | 
 |  */ | 
 | void | 
 | xfs_health_unmount( | 
 | 	struct xfs_mount	*mp) | 
 | { | 
 | 	struct xfs_perag	*pag; | 
 | 	xfs_agnumber_t		agno; | 
 | 	unsigned int		sick = 0; | 
 | 	unsigned int		checked = 0; | 
 | 	bool			warn = false; | 
 |  | 
 | 	if (xfs_is_shutdown(mp)) | 
 | 		return; | 
 |  | 
 | 	/* Measure AG corruption levels. */ | 
 | 	for_each_perag(mp, agno, pag) { | 
 | 		xfs_ag_measure_sickness(pag, &sick, &checked); | 
 | 		if (sick) { | 
 | 			trace_xfs_ag_unfixed_corruption(mp, agno, sick); | 
 | 			warn = true; | 
 | 		} | 
 | 	} | 
 |  | 
 | 	/* Measure realtime volume corruption levels. */ | 
 | 	xfs_rt_measure_sickness(mp, &sick, &checked); | 
 | 	if (sick) { | 
 | 		trace_xfs_rt_unfixed_corruption(mp, sick); | 
 | 		warn = true; | 
 | 	} | 
 |  | 
 | 	/* | 
 | 	 * Measure fs corruption and keep the sample around for the warning. | 
 | 	 * See the note below for why we exempt FS_COUNTERS. | 
 | 	 */ | 
 | 	xfs_fs_measure_sickness(mp, &sick, &checked); | 
 | 	if (sick & ~XFS_SICK_FS_COUNTERS) { | 
 | 		trace_xfs_fs_unfixed_corruption(mp, sick); | 
 | 		warn = true; | 
 | 	} | 
 |  | 
 | 	if (warn) { | 
 | 		xfs_warn(mp, | 
 | "Uncorrected metadata errors detected; please run xfs_repair."); | 
 |  | 
 | 		/* | 
 | 		 * We discovered uncorrected metadata problems at some point | 
 | 		 * during this filesystem mount and have advised the | 
 | 		 * administrator to run repair once the unmount completes. | 
 | 		 * | 
 | 		 * However, we must be careful -- when FSCOUNTERS are flagged | 
 | 		 * unhealthy, the unmount procedure omits writing the clean | 
 | 		 * unmount record to the log so that the next mount will run | 
 | 		 * recovery and recompute the summary counters.  In other | 
 | 		 * words, we leave a dirty log to get the counters fixed. | 
 | 		 * | 
 | 		 * Unfortunately, xfs_repair cannot recover dirty logs, so if | 
 | 		 * there were filesystem problems, FSCOUNTERS was flagged, and | 
 | 		 * the administrator takes our advice to run xfs_repair, | 
 | 		 * they'll have to zap the log before repairing structures. | 
 | 		 * We don't really want to encourage this, so we mark the | 
 | 		 * FSCOUNTERS healthy so that a subsequent repair run won't see | 
 | 		 * a dirty log. | 
 | 		 */ | 
 | 		if (sick & XFS_SICK_FS_COUNTERS) | 
 | 			xfs_fs_mark_healthy(mp, XFS_SICK_FS_COUNTERS); | 
 | 	} | 
 | } | 
 |  | 
 | /* Mark unhealthy per-fs metadata. */ | 
 | void | 
 | xfs_fs_mark_sick( | 
 | 	struct xfs_mount	*mp, | 
 | 	unsigned int		mask) | 
 | { | 
 | 	ASSERT(!(mask & ~XFS_SICK_FS_PRIMARY)); | 
 | 	trace_xfs_fs_mark_sick(mp, mask); | 
 |  | 
 | 	spin_lock(&mp->m_sb_lock); | 
 | 	mp->m_fs_sick |= mask; | 
 | 	mp->m_fs_checked |= mask; | 
 | 	spin_unlock(&mp->m_sb_lock); | 
 | } | 
 |  | 
 | /* Mark a per-fs metadata healed. */ | 
 | void | 
 | xfs_fs_mark_healthy( | 
 | 	struct xfs_mount	*mp, | 
 | 	unsigned int		mask) | 
 | { | 
 | 	ASSERT(!(mask & ~XFS_SICK_FS_PRIMARY)); | 
 | 	trace_xfs_fs_mark_healthy(mp, mask); | 
 |  | 
 | 	spin_lock(&mp->m_sb_lock); | 
 | 	mp->m_fs_sick &= ~mask; | 
 | 	mp->m_fs_checked |= mask; | 
 | 	spin_unlock(&mp->m_sb_lock); | 
 | } | 
 |  | 
 | /* Sample which per-fs metadata are unhealthy. */ | 
 | void | 
 | xfs_fs_measure_sickness( | 
 | 	struct xfs_mount	*mp, | 
 | 	unsigned int		*sick, | 
 | 	unsigned int		*checked) | 
 | { | 
 | 	spin_lock(&mp->m_sb_lock); | 
 | 	*sick = mp->m_fs_sick; | 
 | 	*checked = mp->m_fs_checked; | 
 | 	spin_unlock(&mp->m_sb_lock); | 
 | } | 
 |  | 
 | /* Mark unhealthy realtime metadata. */ | 
 | void | 
 | xfs_rt_mark_sick( | 
 | 	struct xfs_mount	*mp, | 
 | 	unsigned int		mask) | 
 | { | 
 | 	ASSERT(!(mask & ~XFS_SICK_RT_PRIMARY)); | 
 | 	trace_xfs_rt_mark_sick(mp, mask); | 
 |  | 
 | 	spin_lock(&mp->m_sb_lock); | 
 | 	mp->m_rt_sick |= mask; | 
 | 	mp->m_rt_checked |= mask; | 
 | 	spin_unlock(&mp->m_sb_lock); | 
 | } | 
 |  | 
 | /* Mark a realtime metadata healed. */ | 
 | void | 
 | xfs_rt_mark_healthy( | 
 | 	struct xfs_mount	*mp, | 
 | 	unsigned int		mask) | 
 | { | 
 | 	ASSERT(!(mask & ~XFS_SICK_RT_PRIMARY)); | 
 | 	trace_xfs_rt_mark_healthy(mp, mask); | 
 |  | 
 | 	spin_lock(&mp->m_sb_lock); | 
 | 	mp->m_rt_sick &= ~mask; | 
 | 	mp->m_rt_checked |= mask; | 
 | 	spin_unlock(&mp->m_sb_lock); | 
 | } | 
 |  | 
 | /* Sample which realtime metadata are unhealthy. */ | 
 | void | 
 | xfs_rt_measure_sickness( | 
 | 	struct xfs_mount	*mp, | 
 | 	unsigned int		*sick, | 
 | 	unsigned int		*checked) | 
 | { | 
 | 	spin_lock(&mp->m_sb_lock); | 
 | 	*sick = mp->m_rt_sick; | 
 | 	*checked = mp->m_rt_checked; | 
 | 	spin_unlock(&mp->m_sb_lock); | 
 | } | 
 |  | 
 | /* Mark unhealthy per-ag metadata. */ | 
 | void | 
 | xfs_ag_mark_sick( | 
 | 	struct xfs_perag	*pag, | 
 | 	unsigned int		mask) | 
 | { | 
 | 	ASSERT(!(mask & ~XFS_SICK_AG_PRIMARY)); | 
 | 	trace_xfs_ag_mark_sick(pag->pag_mount, pag->pag_agno, mask); | 
 |  | 
 | 	spin_lock(&pag->pag_state_lock); | 
 | 	pag->pag_sick |= mask; | 
 | 	pag->pag_checked |= mask; | 
 | 	spin_unlock(&pag->pag_state_lock); | 
 | } | 
 |  | 
 | /* Mark per-ag metadata ok. */ | 
 | void | 
 | xfs_ag_mark_healthy( | 
 | 	struct xfs_perag	*pag, | 
 | 	unsigned int		mask) | 
 | { | 
 | 	ASSERT(!(mask & ~XFS_SICK_AG_PRIMARY)); | 
 | 	trace_xfs_ag_mark_healthy(pag->pag_mount, pag->pag_agno, mask); | 
 |  | 
 | 	spin_lock(&pag->pag_state_lock); | 
 | 	pag->pag_sick &= ~mask; | 
 | 	pag->pag_checked |= mask; | 
 | 	spin_unlock(&pag->pag_state_lock); | 
 | } | 
 |  | 
 | /* Sample which per-ag metadata are unhealthy. */ | 
 | void | 
 | xfs_ag_measure_sickness( | 
 | 	struct xfs_perag	*pag, | 
 | 	unsigned int		*sick, | 
 | 	unsigned int		*checked) | 
 | { | 
 | 	spin_lock(&pag->pag_state_lock); | 
 | 	*sick = pag->pag_sick; | 
 | 	*checked = pag->pag_checked; | 
 | 	spin_unlock(&pag->pag_state_lock); | 
 | } | 
 |  | 
 | /* Mark the unhealthy parts of an inode. */ | 
 | void | 
 | xfs_inode_mark_sick( | 
 | 	struct xfs_inode	*ip, | 
 | 	unsigned int		mask) | 
 | { | 
 | 	ASSERT(!(mask & ~XFS_SICK_INO_PRIMARY)); | 
 | 	trace_xfs_inode_mark_sick(ip, mask); | 
 |  | 
 | 	spin_lock(&ip->i_flags_lock); | 
 | 	ip->i_sick |= mask; | 
 | 	ip->i_checked |= mask; | 
 | 	spin_unlock(&ip->i_flags_lock); | 
 |  | 
 | 	/* | 
 | 	 * Keep this inode around so we don't lose the sickness report.  Scrub | 
 | 	 * grabs inodes with DONTCACHE assuming that most inode are ok, which | 
 | 	 * is not the case here. | 
 | 	 */ | 
 | 	spin_lock(&VFS_I(ip)->i_lock); | 
 | 	VFS_I(ip)->i_state &= ~I_DONTCACHE; | 
 | 	spin_unlock(&VFS_I(ip)->i_lock); | 
 | } | 
 |  | 
 | /* Mark parts of an inode healed. */ | 
 | void | 
 | xfs_inode_mark_healthy( | 
 | 	struct xfs_inode	*ip, | 
 | 	unsigned int		mask) | 
 | { | 
 | 	ASSERT(!(mask & ~XFS_SICK_INO_PRIMARY)); | 
 | 	trace_xfs_inode_mark_healthy(ip, mask); | 
 |  | 
 | 	spin_lock(&ip->i_flags_lock); | 
 | 	ip->i_sick &= ~mask; | 
 | 	ip->i_checked |= mask; | 
 | 	spin_unlock(&ip->i_flags_lock); | 
 | } | 
 |  | 
 | /* Sample which parts of an inode are unhealthy. */ | 
 | void | 
 | xfs_inode_measure_sickness( | 
 | 	struct xfs_inode	*ip, | 
 | 	unsigned int		*sick, | 
 | 	unsigned int		*checked) | 
 | { | 
 | 	spin_lock(&ip->i_flags_lock); | 
 | 	*sick = ip->i_sick; | 
 | 	*checked = ip->i_checked; | 
 | 	spin_unlock(&ip->i_flags_lock); | 
 | } | 
 |  | 
 | /* Mappings between internal sick masks and ioctl sick masks. */ | 
 |  | 
 | struct ioctl_sick_map { | 
 | 	unsigned int		sick_mask; | 
 | 	unsigned int		ioctl_mask; | 
 | }; | 
 |  | 
 | static const struct ioctl_sick_map fs_map[] = { | 
 | 	{ XFS_SICK_FS_COUNTERS,	XFS_FSOP_GEOM_SICK_COUNTERS}, | 
 | 	{ XFS_SICK_FS_UQUOTA,	XFS_FSOP_GEOM_SICK_UQUOTA }, | 
 | 	{ XFS_SICK_FS_GQUOTA,	XFS_FSOP_GEOM_SICK_GQUOTA }, | 
 | 	{ XFS_SICK_FS_PQUOTA,	XFS_FSOP_GEOM_SICK_PQUOTA }, | 
 | 	{ 0, 0 }, | 
 | }; | 
 |  | 
 | static const struct ioctl_sick_map rt_map[] = { | 
 | 	{ XFS_SICK_RT_BITMAP,	XFS_FSOP_GEOM_SICK_RT_BITMAP }, | 
 | 	{ XFS_SICK_RT_SUMMARY,	XFS_FSOP_GEOM_SICK_RT_SUMMARY }, | 
 | 	{ 0, 0 }, | 
 | }; | 
 |  | 
 | static inline void | 
 | xfgeo_health_tick( | 
 | 	struct xfs_fsop_geom		*geo, | 
 | 	unsigned int			sick, | 
 | 	unsigned int			checked, | 
 | 	const struct ioctl_sick_map	*m) | 
 | { | 
 | 	if (checked & m->sick_mask) | 
 | 		geo->checked |= m->ioctl_mask; | 
 | 	if (sick & m->sick_mask) | 
 | 		geo->sick |= m->ioctl_mask; | 
 | } | 
 |  | 
 | /* Fill out fs geometry health info. */ | 
 | void | 
 | xfs_fsop_geom_health( | 
 | 	struct xfs_mount		*mp, | 
 | 	struct xfs_fsop_geom		*geo) | 
 | { | 
 | 	const struct ioctl_sick_map	*m; | 
 | 	unsigned int			sick; | 
 | 	unsigned int			checked; | 
 |  | 
 | 	geo->sick = 0; | 
 | 	geo->checked = 0; | 
 |  | 
 | 	xfs_fs_measure_sickness(mp, &sick, &checked); | 
 | 	for (m = fs_map; m->sick_mask; m++) | 
 | 		xfgeo_health_tick(geo, sick, checked, m); | 
 |  | 
 | 	xfs_rt_measure_sickness(mp, &sick, &checked); | 
 | 	for (m = rt_map; m->sick_mask; m++) | 
 | 		xfgeo_health_tick(geo, sick, checked, m); | 
 | } | 
 |  | 
 | static const struct ioctl_sick_map ag_map[] = { | 
 | 	{ XFS_SICK_AG_SB,	XFS_AG_GEOM_SICK_SB }, | 
 | 	{ XFS_SICK_AG_AGF,	XFS_AG_GEOM_SICK_AGF }, | 
 | 	{ XFS_SICK_AG_AGFL,	XFS_AG_GEOM_SICK_AGFL }, | 
 | 	{ XFS_SICK_AG_AGI,	XFS_AG_GEOM_SICK_AGI }, | 
 | 	{ XFS_SICK_AG_BNOBT,	XFS_AG_GEOM_SICK_BNOBT }, | 
 | 	{ XFS_SICK_AG_CNTBT,	XFS_AG_GEOM_SICK_CNTBT }, | 
 | 	{ XFS_SICK_AG_INOBT,	XFS_AG_GEOM_SICK_INOBT }, | 
 | 	{ XFS_SICK_AG_FINOBT,	XFS_AG_GEOM_SICK_FINOBT }, | 
 | 	{ XFS_SICK_AG_RMAPBT,	XFS_AG_GEOM_SICK_RMAPBT }, | 
 | 	{ XFS_SICK_AG_REFCNTBT,	XFS_AG_GEOM_SICK_REFCNTBT }, | 
 | 	{ 0, 0 }, | 
 | }; | 
 |  | 
 | /* Fill out ag geometry health info. */ | 
 | void | 
 | xfs_ag_geom_health( | 
 | 	struct xfs_perag		*pag, | 
 | 	struct xfs_ag_geometry		*ageo) | 
 | { | 
 | 	const struct ioctl_sick_map	*m; | 
 | 	unsigned int			sick; | 
 | 	unsigned int			checked; | 
 |  | 
 | 	ageo->ag_sick = 0; | 
 | 	ageo->ag_checked = 0; | 
 |  | 
 | 	xfs_ag_measure_sickness(pag, &sick, &checked); | 
 | 	for (m = ag_map; m->sick_mask; m++) { | 
 | 		if (checked & m->sick_mask) | 
 | 			ageo->ag_checked |= m->ioctl_mask; | 
 | 		if (sick & m->sick_mask) | 
 | 			ageo->ag_sick |= m->ioctl_mask; | 
 | 	} | 
 | } | 
 |  | 
 | static const struct ioctl_sick_map ino_map[] = { | 
 | 	{ XFS_SICK_INO_CORE,	XFS_BS_SICK_INODE }, | 
 | 	{ XFS_SICK_INO_BMBTD,	XFS_BS_SICK_BMBTD }, | 
 | 	{ XFS_SICK_INO_BMBTA,	XFS_BS_SICK_BMBTA }, | 
 | 	{ XFS_SICK_INO_BMBTC,	XFS_BS_SICK_BMBTC }, | 
 | 	{ XFS_SICK_INO_DIR,	XFS_BS_SICK_DIR }, | 
 | 	{ XFS_SICK_INO_XATTR,	XFS_BS_SICK_XATTR }, | 
 | 	{ XFS_SICK_INO_SYMLINK,	XFS_BS_SICK_SYMLINK }, | 
 | 	{ XFS_SICK_INO_PARENT,	XFS_BS_SICK_PARENT }, | 
 | 	{ 0, 0 }, | 
 | }; | 
 |  | 
 | /* Fill out bulkstat health info. */ | 
 | void | 
 | xfs_bulkstat_health( | 
 | 	struct xfs_inode		*ip, | 
 | 	struct xfs_bulkstat		*bs) | 
 | { | 
 | 	const struct ioctl_sick_map	*m; | 
 | 	unsigned int			sick; | 
 | 	unsigned int			checked; | 
 |  | 
 | 	bs->bs_sick = 0; | 
 | 	bs->bs_checked = 0; | 
 |  | 
 | 	xfs_inode_measure_sickness(ip, &sick, &checked); | 
 | 	for (m = ino_map; m->sick_mask; m++) { | 
 | 		if (checked & m->sick_mask) | 
 | 			bs->bs_checked |= m->ioctl_mask; | 
 | 		if (sick & m->sick_mask) | 
 | 			bs->bs_sick |= m->ioctl_mask; | 
 | 	} | 
 | } |