// SPDX-License-Identifier: GPL-2.0
/*
 * Copyright (C) 2017 Oracle.  All Rights Reserved.
 * Author: Darrick J. Wong <darrick.wong@oracle.com>
 */

#include <sys/uio.h>
#include <xfs/xfs.h>
#include "command.h"
#include "input.h"
#include "init.h"
#include "libfrog/paths.h"
#include "libfrog/fsgeom.h"
#include "libfrog/scrub.h"
#include "libfrog/logging.h"
#include "io.h"
#include "list.h"

static struct cmdinfo scrub_cmd;
static struct cmdinfo repair_cmd;
static const struct cmdinfo scrubv_cmd;

static void
scrub_help(void)
{
	const struct xfrog_scrub_descr	*d;
	int				i;

	printf(_(
"\n"
" Scrubs a piece of XFS filesystem metadata.  The first argument is the type\n"
" of metadata to examine.  Allocation group metadata types take one AG number\n"
" as the second parameter.  Inode metadata types act on the currently open file\n"
" or (optionally) take an inode number and generation number to act upon as\n"
" the second and third parameters.\n"
"\n"
" Example:\n"
" 'scrub inobt 3' - scrub the inode btree in AG 3.\n"
" 'scrub bmapbtd 128 13525' - scrubs the extent map of inode 128 gen 13525.\n"
"\n"
" Known metadata scrub types are:"));
	for (i = 0, d = xfrog_scrubbers; i < XFS_SCRUB_TYPE_NR; i++, d++)
		printf(" %s", d->name);
	printf(_(
"\n"
"\n"
" Known metapath scrub arguments are:"));
	for (i = 0, d = xfrog_metapaths; i < XFS_SCRUB_METAPATH_NR; i++, d++)
		printf(" %s", d->name);
	printf("\n");
}

static bool
parse_inode(
	int		argc,
	char		**argv,
	int		optind,
	__u64		*ino,
	__u32		*gen)
{
	char		*p;
	unsigned long long control;
	unsigned long	control2;

	if (optind == argc) {
		*ino = 0;
		*gen = 0;
		return true;
	}

	if (optind != argc - 2) {
		fprintf(stderr,
 _("Must specify inode number and generation.\n"));
		return false;
	}

	control = strtoull(argv[optind], &p, 0);
	if (*p != '\0') {
		fprintf(stderr, _("Bad inode number '%s'.\n"),
				argv[optind]);
		return false;
	}
	control2 = strtoul(argv[optind + 1], &p, 0);
	if (*p != '\0') {
		fprintf(stderr, _("Bad generation number '%s'.\n"),
				argv[optind + 1]);
		return false;
	}

	*ino = control;
	*gen = control2;
	return true;
}

static bool
parse_agno(
	int		argc,
	char		**argv,
	int		optind,
	__u32		*agno)
{
	char		*p;
	unsigned long	control;

	if (optind != argc - 1) {
		fprintf(stderr, _("Must specify one AG number.\n"));
		return false;
	}

	control = strtoul(argv[optind], &p, 0);
	if (*p != '\0') {
		fprintf(stderr, _("Bad AG number '%s'.\n"), argv[optind]);
		return false;
	}

	*agno = control;
	return true;
}

static bool
parse_none(
	int		argc,
	int		optind)
{
	if (optind != argc) {
		fprintf(stderr, _("No parameters allowed.\n"));
		return false;
	}

	/* no control parameters */
	return true;
}

static bool
parse_metapath(
	int		argc,
	char		**argv,
	int		optind,
	__u64		*ino,
	__u32		*group)
{
	char		*p;
	unsigned long long control;
	unsigned long	control2 = 0;
	int		i;

	if (optind != argc - 1 && optind != argc - 2) {
		fprintf(stderr, _("Must specify metapath number.\n"));
		return false;
	}

	for (i = 0; i < XFS_SCRUB_METAPATH_NR; i++) {
		if (!strcmp(argv[optind], xfrog_metapaths[i].name)) {
			control = i;
			goto find_group;
		}
	}

	control = strtoll(argv[optind], &p, 0);
	if (*p != '\0') {
		fprintf(stderr, _("Bad metapath number '%s'.\n"),
				argv[optind]);
		return false;
	}

find_group:
	if (xfrog_metapaths[control].group == XFROG_SCRUB_GROUP_RTGROUP) {
		if (optind == argc - 1) {
			fprintf(stderr,
_("%s: Metapath requires a group number.\n"),
					xfrog_metapaths[*ino].name);
			return false;
		}
		control2 = strtoul(argv[optind + 1], &p, 0);
		if (*p != '\0') {
			fprintf(stderr,
 _("Bad group number '%s'.\n"),
				argv[optind + 1]);
			return false;
		}
	} else {
		if (optind == argc - 2) {
			fprintf(stderr,
_("%s: Metapath does not take a second argument.\n"),
					xfrog_metapaths[*ino].name);
			return false;
		}
	}

	*ino = control;
	*group = control2;
	return true;
}

static bool
parse_rtgroup(
	int		argc,
	char		**argv,
	int		optind,
	__u32		*rgno)
{
	char		*p;
	unsigned long	control;

	if (optind != argc - 1) {
		fprintf(stderr, _("Must specify one rtgroup number.\n"));
		return false;
	}

	control = strtoul(argv[optind], &p, 0);
	if (*p != '\0') {
		fprintf(stderr, _("Bad rtgroup number '%s'.\n"),
				argv[optind]);
		return false;
	}

	*rgno = control;
	return true;
}

static int
parse_args(
	int				argc,
	char				**argv,
	const struct cmdinfo		*cmdinfo,
	struct xfs_scrub_metadata	*meta)
{
	int				type = -1;
	int				i, c;
	uint32_t			flags = 0;
	const struct xfrog_scrub_descr	*d = NULL;

	memset(meta, 0, sizeof(struct xfs_scrub_metadata));
	while ((c = getopt(argc, argv, "R")) != EOF) {
		switch (c) {
		case 'R':
			flags |= XFS_SCRUB_IFLAG_FORCE_REBUILD;
			break;
		default:
			exitcode = 1;
			return command_usage(cmdinfo);
		}
	}
	if (optind > argc - 1) {
		exitcode = 1;
		return command_usage(cmdinfo);
	}

	for (i = 0, d = xfrog_scrubbers; i < XFS_SCRUB_TYPE_NR; i++, d++) {
		if (strcmp(d->name, argv[optind]) == 0) {
			type = i;
			break;
		}
	}
	if (type < 0) {
		printf(_("Unknown type '%s'.\n"), argv[optind]);
		exitcode = 1;
		return command_usage(cmdinfo);
	}
	optind++;

	meta->sm_type = type;
	meta->sm_flags = flags;

	switch (d->group) {
	case XFROG_SCRUB_GROUP_METAPATH:
		if (!parse_metapath(argc, argv, optind, &meta->sm_ino,
							&meta->sm_agno)) {
			exitcode = 1;
			return command_usage(cmdinfo);
		}
		break;
	case XFROG_SCRUB_GROUP_INODE:
		if (!parse_inode(argc, argv, optind, &meta->sm_ino,
						     &meta->sm_gen)) {
			exitcode = 1;
			return command_usage(cmdinfo);
		}
		break;
	case XFROG_SCRUB_GROUP_AGHEADER:
	case XFROG_SCRUB_GROUP_PERAG:
		if (!parse_agno(argc, argv, optind, &meta->sm_agno)) {
			exitcode = 1;
			return command_usage(cmdinfo);
		}
		break;
	case XFROG_SCRUB_GROUP_RTGROUP:
		if (!parse_rtgroup(argc, argv, optind, &meta->sm_agno)) {
			exitcode = 1;
			return command_usage(cmdinfo);
		}
		break;
	case XFROG_SCRUB_GROUP_FS:
	case XFROG_SCRUB_GROUP_NONE:
	case XFROG_SCRUB_GROUP_SUMMARY:
	case XFROG_SCRUB_GROUP_ISCAN:
		if (!parse_none(argc, optind)) {
			exitcode = 1;
			return command_usage(cmdinfo);
		}
		break;
	default:
		ASSERT(0);
		break;
	}
	return 0;
}

static void
report_scrub_outcome(
	uint32_t	flags)
{
	if (flags & XFS_SCRUB_OFLAG_CORRUPT)
		printf(_("Corruption detected.\n"));
	if (flags & XFS_SCRUB_OFLAG_PREEN)
		printf(_("Optimization possible.\n"));
	if (flags & XFS_SCRUB_OFLAG_XFAIL)
		printf(_("Cross-referencing failed.\n"));
	if (flags & XFS_SCRUB_OFLAG_XCORRUPT)
		printf(_("Corruption detected during cross-referencing.\n"));
	if (flags & XFS_SCRUB_OFLAG_INCOMPLETE)
		printf(_("Scan was not complete.\n"));
}

static int
scrub_f(
	int				argc,
	char				**argv)
{
	struct xfs_scrub_metadata	meta;
	int				error;

	error = parse_args(argc, argv, &scrub_cmd, &meta);
	if (error)
		return error;

	error = ioctl(file->fd, XFS_IOC_SCRUB_METADATA, &meta);
	if (error)
		perror("scrub");
	report_scrub_outcome(meta.sm_flags);
	return 0;
}

void
scrub_init(void)
{
	scrub_cmd.name = "scrub";
	scrub_cmd.altname = "sc";
	scrub_cmd.cfunc = scrub_f;
	scrub_cmd.argmin = 1;
	scrub_cmd.argmax = -1;
	scrub_cmd.flags = CMD_NOMAP_OK;
	scrub_cmd.args = _("type [agno|ino gen|metapath]");
	scrub_cmd.oneline = _("scrubs filesystem metadata");
	scrub_cmd.help = scrub_help;

	add_command(&scrub_cmd);
	add_command(&scrubv_cmd);
}

static void
repair_help(void)
{
	const struct xfrog_scrub_descr	*d;
	int				i;

	printf(_(
"\n"
" Repairs a piece of XFS filesystem metadata.  The first argument is the type\n"
" of metadata to examine.  Allocation group metadata types take one AG number\n"
" as the second parameter.  Inode metadata types act on the currently open file\n"
" or (optionally) take an inode number and generation number to act upon as\n"
" the second and third parameters.\n"
"\n"
" Flags are -R to force rebuilding metadata.\n"
"\n"
" Example:\n"
" 'repair inobt 3' - repairs the inode btree in AG 3.\n"
" 'repair bmapbtd 128 13525' - repairs the extent map of inode 128 gen 13525.\n"
"\n"
" Known metadata repair types are:"));
	for (i = 0, d = xfrog_scrubbers; i < XFS_SCRUB_TYPE_NR; i++, d++)
		printf(" %s", d->name);
	printf(_(
"\n"
"\n"
" Known metapath repair arguments are:"));
	for (i = 0, d = xfrog_metapaths; i < XFS_SCRUB_METAPATH_NR; i++, d++)
		printf(" %s", d->name);
	printf("\n");
}

static void
report_repair_outcome(
	uint32_t	flags)
{
	if (flags & XFS_SCRUB_OFLAG_CORRUPT)
		printf(_("Corruption remains.\n"));
	if (flags & XFS_SCRUB_OFLAG_PREEN)
		printf(_("Optimization possible.\n"));
	if (flags & XFS_SCRUB_OFLAG_XFAIL)
		printf(_("Cross-referencing failed.\n"));
	if (flags & XFS_SCRUB_OFLAG_XCORRUPT)
		printf(_("Corruption still detected during cross-referencing.\n"));
	if (flags & XFS_SCRUB_OFLAG_INCOMPLETE)
		printf(_("Repair was not complete.\n"));
	if (flags & XFS_SCRUB_OFLAG_NO_REPAIR_NEEDED)
		printf(_("Metadata did not need repair or optimization.\n"));
}

static int
repair_f(
	int				argc,
	char				**argv)
{
	struct xfs_scrub_metadata	meta;
	int				error;

	error = parse_args(argc, argv, &repair_cmd, &meta);
	if (error)
		return error;
	meta.sm_flags |= XFS_SCRUB_IFLAG_REPAIR;

	error = ioctl(file->fd, XFS_IOC_SCRUB_METADATA, &meta);
	if (error)
		perror("repair");
	report_repair_outcome(meta.sm_flags);
	return 0;
}

void
repair_init(void)
{
	if (!expert)
		return;
	repair_cmd.name = "repair";
	repair_cmd.altname = "fix";
	repair_cmd.cfunc = repair_f;
	repair_cmd.argmin = 1;
	repair_cmd.argmax = -1;
	repair_cmd.flags = CMD_NOMAP_OK;
	repair_cmd.args = _("type [agno|ino gen|metapath]");
	repair_cmd.oneline = _("repairs filesystem metadata");
	repair_cmd.help = repair_help;

	add_command(&repair_cmd);
}

static void
scrubv_help(void)
{
	printf(_(
"\n"
" Scrubs pieces of XFS filesystem metadata.  The first argument is the group\n"
" of metadata to examine.  If the group is 'ag', the second parameter should\n"
" be the AG number.  If the group is 'inode', the second and third parameters\n"
" should be the inode number and generation number to act upon; if these are\n"
" omitted, the scrub is performed on the open file.  If the group is 'fs',\n"
" 'summary', or 'probe', there are no other parameters.\n"
"\n"
" Flags are -d for debug, and -r to allow repairs.\n"
" -b NN will insert a scrub barrier after every NN scrubs, and -m sets the\n"
" desired corruption mask in all barriers. -w pauses for some microseconds\n"
" after each scrub call.\n"
"\n"
" Example:\n"
" 'scrubv ag 3' - scrub all metadata in AG 3.\n"
" 'scrubv ag 3 -b 2 -m 0x4' - scrub all metadata in AG 3, and use barriers\n"
"            every third scrub to exit early if there are optimizations.\n"
" 'scrubv fs' - scrub all non-AG non-file metadata.\n"
" 'scrubv inode' - scrub all metadata for the open file.\n"
" 'scrubv inode 128 13525' - scrub all metadata for inode 128 gen 13525.\n"
" 'scrubv probe' - check for presence of online scrub.\n"
" 'scrubv summary' - scrub all summary metadata.\n"));
}

/* Fill out the scrub vectors for a group of scrubber (ag, ino, fs, summary) */
static void
scrubv_fill_group(
	struct xfrog_scrubv		*scrubv,
	int				barrier_interval,
	__u32				barrier_mask,
	enum xfrog_scrub_group		group)
{
	const struct xfrog_scrub_descr	*d;
	struct xfs_scrub_vec		*v;
	unsigned int			i;

	for (i = 0, d = xfrog_scrubbers; i < XFS_SCRUB_TYPE_NR; i++, d++) {
		if (d->group != group)
			continue;

		v = xfrog_scrubv_next_vector(scrubv);
		v->sv_type = i;

		if (barrier_interval &&
		    scrubv->head.svh_nr % (barrier_interval + 1) == 0) {
			v = xfrog_scrubv_next_vector(scrubv);
			v->sv_flags = barrier_mask;
			v->sv_type = XFS_SCRUB_TYPE_BARRIER;
		}
	}
}

static int
scrubv_f(
	int				argc,
	char				**argv)
{
	struct xfrog_scrubv		scrubv = { };
	struct xfs_fd			xfd = XFS_FD_INIT(file->fd);
	struct xfs_scrub_vec		*v;
	uint32_t			flags = 0;
	__u32				barrier_mask = XFS_SCRUB_OFLAG_CORRUPT;
	enum xfrog_scrub_group		group;
	bool				debug = false;
	int				version = -1;
	int				barrier_interval = 0;
	int				rest_us = 0;
	int				c;
	int				error;

	xfrog_scrubv_init(&scrubv);

	while ((c = getopt(argc, argv, "b:dm:rv:w:")) != EOF) {
		switch (c) {
		case 'b':
			barrier_interval = atoi(optarg);
			if (barrier_interval < 0) {
				fprintf(stderr,
 _("Negative barrier interval makes no sense.\n"));
				exitcode = 1;
				return command_usage(&scrubv_cmd);
			}
			break;
		case 'd':
			debug = true;
			break;
		case 'm':
			barrier_mask = strtoul(optarg, NULL, 0);
			break;
		case 'r':
			flags |= XFS_SCRUB_IFLAG_REPAIR;
			break;
		case 'v':
			if (!strcmp("single", optarg)) {
				version = 0;
			} else if (!strcmp("vector", optarg)) {
				version = 1;
			} else {
				fprintf(stderr,
 _("API version must be 'single' or 'vector'.\n"));
				exitcode = 1;
				return command_usage(&scrubv_cmd);
			}
			break;
		case 'w':
			rest_us = atoi(optarg);
			if (rest_us < 0) {
				fprintf(stderr,
 _("Rest time must be positive.\n"));
				exitcode = 1;
				return command_usage(&scrubv_cmd);
			}
			break;
		default:
			exitcode = 1;
			return command_usage(&scrubv_cmd);
		}
	}
	if (optind > argc - 1) {
		fprintf(stderr,
 _("Must have at least one positional argument.\n"));
		exitcode = 1;
		return command_usage(&scrubv_cmd);
	}

	if ((flags & XFS_SCRUB_IFLAG_REPAIR) && !expert) {
		printf(_("Repair flag requires expert mode.\n"));
		return 1;
	}

	scrubv.head.svh_rest_us = rest_us;
	foreach_xfrog_scrubv_vec(&scrubv, c, v)
		v->sv_flags = flags;

	/* Extract group and domain information from cmdline. */
	if (!strcmp(argv[optind], "probe"))
		group = XFROG_SCRUB_GROUP_NONE;
	else if (!strcmp(argv[optind], "agheader"))
		group = XFROG_SCRUB_GROUP_AGHEADER;
	else if (!strcmp(argv[optind], "ag"))
		group = XFROG_SCRUB_GROUP_PERAG;
	else if (!strcmp(argv[optind], "fs"))
		group = XFROG_SCRUB_GROUP_FS;
	else if (!strcmp(argv[optind], "inode"))
		group = XFROG_SCRUB_GROUP_INODE;
	else if (!strcmp(argv[optind], "iscan"))
		group = XFROG_SCRUB_GROUP_ISCAN;
	else if (!strcmp(argv[optind], "summary"))
		group = XFROG_SCRUB_GROUP_SUMMARY;
	else if (!strcmp(argv[optind], "rtgroup"))
		group = XFROG_SCRUB_GROUP_RTGROUP;
	else {
		printf(_("Unknown group '%s'.\n"), argv[optind]);
		exitcode = 1;
		return command_usage(&scrubv_cmd);
	}
	optind++;

	switch (group) {
	case XFROG_SCRUB_GROUP_METAPATH:
		if (!parse_metapath(argc, argv, optind, &scrubv.head.svh_ino,
				    &scrubv.head.svh_agno)) {
			exitcode = 1;
			return command_usage(&scrubv_cmd);
		}
		break;
	case XFROG_SCRUB_GROUP_INODE:
		if (!parse_inode(argc, argv, optind, &scrubv.head.svh_ino,
						     &scrubv.head.svh_gen)) {
			exitcode = 1;
			return command_usage(&scrubv_cmd);
		}
		break;
	case XFROG_SCRUB_GROUP_AGHEADER:
	case XFROG_SCRUB_GROUP_PERAG:
		if (!parse_agno(argc, argv, optind, &scrubv.head.svh_agno)) {
			exitcode = 1;
			return command_usage(&scrubv_cmd);
		}
		break;
	case XFROG_SCRUB_GROUP_FS:
	case XFROG_SCRUB_GROUP_SUMMARY:
	case XFROG_SCRUB_GROUP_ISCAN:
	case XFROG_SCRUB_GROUP_NONE:
		if (!parse_none(argc, optind)) {
			exitcode = 1;
			return command_usage(&scrubv_cmd);
		}
		break;
	case XFROG_SCRUB_GROUP_RTGROUP:
		if (!parse_rtgroup(argc, argv, optind, &scrubv.head.svh_agno)) {
			exitcode = 1;
			return command_usage(&scrubv_cmd);
		}
		break;
	default:
		ASSERT(0);
		break;
	}
	scrubv_fill_group(&scrubv, barrier_interval, barrier_mask, group);
	assert(scrubv.head.svh_nr <= XFROG_SCRUBV_MAX_VECTORS);

	error = -xfd_prepare_geometry(&xfd);
	if (error) {
		xfrog_perror(error, "xfd_prepare_geometry");
		exitcode = 1;
		return 0;
	}

	switch (version) {
	case 0:
		xfd.flags |= XFROG_FLAG_SCRUB_FORCE_SINGLE;
		break;
	case 1:
		xfd.flags |= XFROG_FLAG_SCRUB_FORCE_VECTOR;
		break;
	default:
		break;
	}

	error = -xfrog_scrubv_metadata(&xfd, &scrubv);
	if (error) {
		xfrog_perror(error, "xfrog_scrub_many");
		exitcode = 1;
		return 0;
	}

	/* Dump what happened. */
	if (debug) {
		foreach_xfrog_scrubv_vec(&scrubv, c, v) {
			const char	*type;

			if (v->sv_type == XFS_SCRUB_TYPE_BARRIER)
				type = _("barrier");
			else
				type = _(xfrog_scrubbers[v->sv_type].descr);
			printf(_("[%02u] %-25s: flags 0x%x ret %d\n"), c, type,
					v->sv_flags, v->sv_ret);
		}
	}

	/* Figure out what happened. */
	foreach_xfrog_scrubv_vec(&scrubv, c, v) {
		/* Report barrier failures. */
		if (v->sv_type == XFS_SCRUB_TYPE_BARRIER) {
			if (v->sv_ret) {
				printf(_("barrier: FAILED\n"));
				break;
			}
			continue;
		}

		printf("%s: ", _(xfrog_scrubbers[v->sv_type].descr));
		switch (v->sv_ret) {
		case 0:
			break;
		default:
			printf("%s\n", strerror(-v->sv_ret));
			continue;
		}
		if (!(v->sv_flags & XFS_SCRUB_FLAGS_OUT))
			printf(_("OK.\n"));
		else if (v->sv_flags & XFS_SCRUB_IFLAG_REPAIR)
			report_repair_outcome(v->sv_flags);
		else
			report_scrub_outcome(v->sv_flags);
	}

	return 0;
}

static const struct cmdinfo scrubv_cmd = {
	.name		= "scrubv",
	.cfunc		= scrubv_f,
	.argmin		= 1,
	.argmax		= -1,
	.flags		= CMD_NOMAP_OK,
	.oneline	= N_("vectored metadata scrub"),
	.help		= scrubv_help,
};
