// 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 "path.h"
#include "io.h"

static struct cmdinfo scrub_cmd;
static struct cmdinfo repair_cmd;

/* Type info and names for the scrub types. */
enum scrub_type {
	ST_NONE,	/* disabled */
	ST_PERAG,	/* per-AG metadata */
	ST_FS,		/* per-FS metadata */
	ST_INODE,	/* per-inode metadata */
};

struct scrub_descr {
	const char	*name;
	enum scrub_type	type;
};

static const struct scrub_descr scrubbers[XFS_SCRUB_TYPE_NR] = {
	[XFS_SCRUB_TYPE_PROBE]		= {"probe",		ST_NONE},
	[XFS_SCRUB_TYPE_SB]		= {"sb",		ST_PERAG},
	[XFS_SCRUB_TYPE_AGF]		= {"agf",		ST_PERAG},
	[XFS_SCRUB_TYPE_AGFL]		= {"agfl",		ST_PERAG},
	[XFS_SCRUB_TYPE_AGI]		= {"agi",		ST_PERAG},
	[XFS_SCRUB_TYPE_BNOBT]		= {"bnobt",		ST_PERAG},
	[XFS_SCRUB_TYPE_CNTBT]		= {"cntbt",		ST_PERAG},
	[XFS_SCRUB_TYPE_INOBT]		= {"inobt",		ST_PERAG},
	[XFS_SCRUB_TYPE_FINOBT]		= {"finobt",		ST_PERAG},
	[XFS_SCRUB_TYPE_RMAPBT]		= {"rmapbt",		ST_PERAG},
	[XFS_SCRUB_TYPE_REFCNTBT]	= {"refcountbt",	ST_PERAG},
	[XFS_SCRUB_TYPE_INODE]		= {"inode",		ST_INODE},
	[XFS_SCRUB_TYPE_BMBTD]		= {"bmapbtd",		ST_INODE},
	[XFS_SCRUB_TYPE_BMBTA]		= {"bmapbta",		ST_INODE},
	[XFS_SCRUB_TYPE_BMBTC]		= {"bmapbtc",		ST_INODE},
	[XFS_SCRUB_TYPE_DIR]		= {"directory",		ST_INODE},
	[XFS_SCRUB_TYPE_XATTR]		= {"xattr",		ST_INODE},
	[XFS_SCRUB_TYPE_SYMLINK]	= {"symlink",		ST_INODE},
	[XFS_SCRUB_TYPE_PARENT]		= {"parent",		ST_INODE},
	[XFS_SCRUB_TYPE_RTBITMAP]	= {"rtbitmap",		ST_FS},
	[XFS_SCRUB_TYPE_RTSUM]		= {"rtsummary",		ST_FS},
	[XFS_SCRUB_TYPE_UQUOTA]		= {"usrquota",		ST_FS},
	[XFS_SCRUB_TYPE_GQUOTA]		= {"grpquota",		ST_FS},
	[XFS_SCRUB_TYPE_PQUOTA]		= {"prjquota",		ST_FS},
};

static void
scrub_help(void)
{
	const struct 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 = scrubbers; i < XFS_SCRUB_TYPE_NR; i++, d++)
		printf(" %s", d->name);
	printf("\n");
}

static void
scrub_ioctl(
	int				fd,
	int				type,
	uint64_t			control,
	uint32_t			control2)
{
	struct xfs_scrub_metadata	meta;
	const struct scrub_descr	*sc;
	int				error;

	sc = &scrubbers[type];
	memset(&meta, 0, sizeof(meta));
	meta.sm_type = type;
	switch (sc->type) {
	case ST_PERAG:
		meta.sm_agno = control;
		break;
	case ST_INODE:
		meta.sm_ino = control;
		meta.sm_gen = control2;
		break;
	case ST_NONE:
	case ST_FS:
		/* no control parameters */
		break;
	}
	meta.sm_flags = 0;

	error = ioctl(fd, XFS_IOC_SCRUB_METADATA, &meta);
	if (error)
		perror("scrub");
	if (meta.sm_flags & XFS_SCRUB_OFLAG_CORRUPT)
		printf(_("Corruption detected.\n"));
	if (meta.sm_flags & XFS_SCRUB_OFLAG_PREEN)
		printf(_("Optimization possible.\n"));
	if (meta.sm_flags & XFS_SCRUB_OFLAG_XFAIL)
		printf(_("Cross-referencing failed.\n"));
	if (meta.sm_flags & XFS_SCRUB_OFLAG_XCORRUPT)
		printf(_("Corruption detected during cross-referencing.\n"));
	if (meta.sm_flags & XFS_SCRUB_OFLAG_INCOMPLETE)
		printf(_("Scan was not complete.\n"));
}

static int
parse_args(
	int				argc,
	char				**argv,
	struct cmdinfo			*cmdinfo,
	void				(*fn)(int, int, uint64_t, uint32_t))
{
	char				*p;
	int				type = -1;
	int				i, c;
	uint64_t			control = 0;
	uint32_t			control2 = 0;
	const struct scrub_descr	*d = NULL;

	while ((c = getopt(argc, argv, "")) != EOF) {
		switch (c) {
		default:
			return command_usage(cmdinfo);
		}
	}
	if (optind > argc - 1)
		return command_usage(cmdinfo);

	for (i = 0, d = 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]);
		return command_usage(cmdinfo);
	}
	optind++;

	switch (d->type) {
	case ST_INODE:
		if (optind == argc) {
			control = 0;
			control2 = 0;
		} else if (optind == argc - 2) {
			control = strtoull(argv[optind], &p, 0);
			if (*p != '\0') {
				fprintf(stderr,
					_("Bad inode number '%s'.\n"),
					argv[optind]);
				return 0;
			}
			control2 = strtoul(argv[optind + 1], &p, 0);
			if (*p != '\0') {
				fprintf(stderr,
					_("Bad generation number '%s'.\n"),
					argv[optind + 1]);
				return 0;
			}
		} else {
			fprintf(stderr,
				_("Must specify inode number and generation.\n"));
			return 0;
		}
		break;
	case ST_PERAG:
		if (optind != argc - 1) {
			fprintf(stderr,
				_("Must specify one AG number.\n"));
			return 0;
		}
		control = strtoul(argv[optind], &p, 0);
		if (*p != '\0') {
			fprintf(stderr,
				_("Bad AG number '%s'.\n"), argv[optind]);
			return 0;
		}
		break;
	case ST_FS:
	case ST_NONE:
		if (optind != argc) {
			fprintf(stderr,
				_("No parameters allowed.\n"));
			return 0;
		}
		break;
	default:
		ASSERT(0);
		break;
	}
	fn(file->fd, type, control, control2);

	return 0;
}

static int
scrub_f(
	int				argc,
	char				**argv)
{
	return parse_args(argc, argv, &scrub_cmd, scrub_ioctl);
}

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]");
	scrub_cmd.oneline = _("scrubs filesystem metadata");
	scrub_cmd.help = scrub_help;

	add_command(&scrub_cmd);
}

static void
repair_help(void)
{
	const struct 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"
" 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 repairs types are:"));
	for (i = 0, d = scrubbers; i < XFS_SCRUB_TYPE_NR; i++, d++)
		printf(" %s", d->name);
	printf("\n");
}

static void
repair_ioctl(
	int				fd,
	int				type,
	uint64_t			control,
	uint32_t			control2)
{
	struct xfs_scrub_metadata	meta;
	const struct scrub_descr	*sc;
	int				error;

	sc = &scrubbers[type];
	memset(&meta, 0, sizeof(meta));
	meta.sm_type = type;
	switch (sc->type) {
	case ST_PERAG:
		meta.sm_agno = control;
		break;
	case ST_INODE:
		meta.sm_ino = control;
		meta.sm_gen = control2;
		break;
	case ST_NONE:
	case ST_FS:
		/* no control parameters */
		break;
	}
	meta.sm_flags = XFS_SCRUB_IFLAG_REPAIR;

	error = ioctl(fd, XFS_IOC_SCRUB_METADATA, &meta);
	if (error)
		perror("scrub");
	if (meta.sm_flags & XFS_SCRUB_OFLAG_CORRUPT)
		printf(_("Corruption remains.\n"));
	if (meta.sm_flags & XFS_SCRUB_OFLAG_PREEN)
		printf(_("Optimization possible.\n"));
	if (meta.sm_flags & XFS_SCRUB_OFLAG_XFAIL)
		printf(_("Cross-referencing failed.\n"));
	if (meta.sm_flags & XFS_SCRUB_OFLAG_XCORRUPT)
		printf(_("Corruption still detected during cross-referencing.\n"));
	if (meta.sm_flags & XFS_SCRUB_OFLAG_INCOMPLETE)
		printf(_("Repair was not complete.\n"));
	if (meta.sm_flags & XFS_SCRUB_OFLAG_NO_REPAIR_NEEDED)
		printf(_("Metadata did not need repair or optimization.\n"));
}

static int
repair_f(
	int				argc,
	char				**argv)
{
	return parse_args(argc, argv, &repair_cmd, repair_ioctl);
}

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]");
	repair_cmd.oneline = _("repairs filesystem metadata");
	repair_cmd.help = repair_help;

	add_command(&repair_cmd);
}
