| // 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" | 
 | #include "libfrog/getparents.h" | 
 |  | 
 | static cmdinfo_t health_cmd; | 
 | static unsigned long long reported; | 
 | static bool comprehensive; | 
 | static bool quiet; | 
 | static bool report_paths; | 
 |  | 
 | 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; | 
 | } | 
 |  | 
 | static bool has_rtrmapbt(const struct xfs_fsop_geom *g) | 
 | { | 
 | 	return g->rtblocks > 0 && (g->flags & XFS_FSOP_GEOM_FLAGS_RMAPBT); | 
 | } | 
 |  | 
 | static bool has_rtreflink(const struct xfs_fsop_geom *g) | 
 | { | 
 | 	return g->rtblocks > 0 && (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, | 
 | 	}, | 
 | 	{ | 
 | 		.mask = XFS_FSOP_GEOM_SICK_QUOTACHECK, | 
 | 		.descr = "quota counts", | 
 | 	}, | 
 | 	{ | 
 | 		.mask = XFS_FSOP_GEOM_SICK_NLINKS, | 
 | 		.descr = "inode link counts", | 
 | 	}, | 
 | 	{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 rtgroup_flags[] = { | 
 | 	{ | 
 | 		.mask = XFS_RTGROUP_GEOM_SICK_SUPER, | 
 | 		.descr = "superblock", | 
 | 	}, | 
 | 	{ | 
 | 		.mask = XFS_RTGROUP_GEOM_SICK_BITMAP, | 
 | 		.descr = "realtime bitmap", | 
 | 	}, | 
 | 	{ | 
 | 		.mask = XFS_RTGROUP_GEOM_SICK_SUMMARY, | 
 | 		.descr = "realtime summary", | 
 | 	}, | 
 | 	{ | 
 | 		.mask = XFS_RTGROUP_GEOM_SICK_RMAPBT, | 
 | 		.descr = "realtime reverse mappings btree", | 
 | 		.has_fn = has_rtrmapbt, | 
 | 	}, | 
 | 	{ | 
 | 		.mask = XFS_RTGROUP_GEOM_SICK_REFCNTBT, | 
 | 		.descr = "realtime reference count btree", | 
 | 		.has_fn = has_rtreflink, | 
 | 	}, | 
 | 	{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", | 
 | 	}, | 
 | 	{ | 
 | 		.mask = XFS_BS_SICK_DIRTREE, | 
 | 		.descr = "directory tree structure", | 
 | 	}, | 
 | 	{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 a rt group's health. */ | 
 | static int | 
 | report_rtgroup_sick( | 
 | 	xfs_rgnumber_t		rgno) | 
 | { | 
 | 	struct xfs_rtgroup_geometry rgeo = { 0 }; | 
 | 	char			descr[256]; | 
 | 	int			ret; | 
 |  | 
 | 	ret = -xfrog_rtgroup_geometry(file->xfd.fd, rgno, &rgeo); | 
 | 	if (ret) { | 
 | 		xfrog_perror(ret, "rtgroup_geometry"); | 
 | 		return 1; | 
 | 	} | 
 | 	snprintf(descr, sizeof(descr) - 1, _("rtgroup %u"), rgno); | 
 | 	report_sick(descr, rtgroup_flags, rgeo.rg_sick, rgeo.rg_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) | 
 |  | 
 | static void | 
 | report_inode( | 
 | 	const struct xfs_bulkstat	*bs) | 
 | { | 
 | 	char				descr[PATH_MAX]; | 
 | 	int				ret; | 
 |  | 
 | 	if (report_paths && file->fshandle && | 
 | 	    (file->xfd.fsgeom.flags & XFS_FSOP_GEOM_FLAGS_PARENT)) { | 
 | 		struct xfs_handle handle; | 
 |  | 
 | 		memcpy(&handle.ha_fsid, file->fshandle, sizeof(handle.ha_fsid)); | 
 | 		handle.ha_fid.fid_len = sizeof(xfs_fid_t) - | 
 | 				sizeof(handle.ha_fid.fid_len); | 
 | 		handle.ha_fid.fid_pad = 0; | 
 | 		handle.ha_fid.fid_ino = bs->bs_ino; | 
 | 		handle.ha_fid.fid_gen = bs->bs_gen; | 
 |  | 
 | 		ret = handle_to_path(&handle, sizeof(struct xfs_handle), 0, | 
 | 				descr, sizeof(descr) - 1); | 
 | 		if (ret) | 
 | 			goto report_inum; | 
 |  | 
 | 		goto report_status; | 
 | 	} | 
 |  | 
 | report_inum: | 
 | 	snprintf(descr, sizeof(descr) - 1, _("inode %"PRIu64), bs->bs_ino); | 
 | report_status: | 
 | 	report_sick(descr, inode_flags, bs->bs_sick, bs->bs_checked); | 
 | } | 
 |  | 
 | /* | 
 |  * 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; | 
 | 	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); | 
 | 	if (file->xfd.fsgeom.flags & XFS_FSOP_GEOM_FLAGS_METADIR) | 
 | 		breq->hdr.flags |= XFS_BULK_IREQ_METADIR; | 
 |  | 
 | 	do { | 
 | 		error = -xfrog_bulkstat(&file->xfd, breq); | 
 | 		if (error) | 
 | 			break; | 
 | 		for (i = 0; i < breq->hdr.ocount; i++) | 
 | 			report_inode(&breq->bulkstat[i]); | 
 | 	} while (breq->hdr.ocount > 0); | 
 |  | 
 | 	if (error) | 
 | 		xfrog_perror(error, "bulkstat"); | 
 |  | 
 | 	free(breq); | 
 | 	return error; | 
 | } | 
 |  | 
 | #define OPT_STRING ("a:cfi:nqr:") | 
 |  | 
 | /* Report on health problems in XFS filesystem. */ | 
 | static int | 
 | health_f( | 
 | 	int			argc, | 
 | 	char			**argv) | 
 | { | 
 | 	unsigned long long	x; | 
 | 	xfs_agnumber_t		agno; | 
 | 	xfs_rgnumber_t		rgno; | 
 | 	bool			default_report = true; | 
 | 	int			c; | 
 | 	int			ret; | 
 |  | 
 | 	reported = 0; | 
 | 	report_paths = false; | 
 |  | 
 | 	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 'n': | 
 | 			report_paths = true; | 
 | 			break; | 
 | 		case 'q': | 
 | 			quiet = true; | 
 | 			break; | 
 | 		case 'r': | 
 | 			default_report = false; | 
 | 			errno = 0; | 
 | 			x = strtoll(optarg, NULL, 10); | 
 | 			if (!errno && x >= NULLRGNUMBER) | 
 | 				errno = ERANGE; | 
 | 			if (errno) { | 
 | 				perror("rtgroup health"); | 
 | 				return 1; | 
 | 			} | 
 | 			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; | 
 | 		case 'r': | 
 | 			rgno = strtoll(optarg, NULL, 10); | 
 | 			ret = report_rtgroup_sick(rgno); | 
 | 			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; | 
 | 		} | 
 | 		for (rgno = 0; rgno < file->xfd.fsgeom.rgcount; rgno++) { | 
 | 			ret = report_rtgroup_sick(rgno); | 
 | 			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" | 
 | " -n       -- Try to report file names.\n" | 
 | " -q       -- Only report unhealthy metadata.\n" | 
 | " -r rgno  -- Report health of the given realtime group.\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] [-n] [-q] [-r rgno] [paths]", | 
 | 	.flags = CMD_FLAG_ONESHOT, | 
 | 	.help = health_help, | 
 | }; | 
 |  | 
 | void | 
 | health_init(void) | 
 | { | 
 | 	health_cmd.oneline = _("Report observed XFS health problems."), | 
 | 	add_command(&health_cmd); | 
 | } |