// 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 "platform_defs.h"
#include "command.h"
#include "init.h"
#include "libfrog/logging.h"
#include "libfrog/fsgeom.h"
#include "libfrog/bulkstat.h"
#include "libfrog/paths.h"
#include "io.h"
#include "input.h"

static void
dump_bulkstat_time(
	const char		*tag,
	uint64_t		sec,
	uint32_t		nsec)
{
	printf("\t%s = %"PRIu64".%"PRIu32"\n", tag, sec, nsec);
}

static void
dump_bulkstat(
	struct xfs_bulkstat	*bstat)
{
	printf("bs_ino = %"PRIu64"\n", bstat->bs_ino);
	printf("\tbs_size = %"PRIu64"\n", bstat->bs_size);

	printf("\tbs_blocks = %"PRIu64"\n", bstat->bs_blocks);
	printf("\tbs_xflags = 0x%"PRIx64"\n", bstat->bs_xflags);

	dump_bulkstat_time("bs_atime", bstat->bs_atime, bstat->bs_atime_nsec);
	dump_bulkstat_time("bs_ctime", bstat->bs_ctime, bstat->bs_ctime_nsec);
	dump_bulkstat_time("bs_mtime", bstat->bs_mtime, bstat->bs_mtime_nsec);
	dump_bulkstat_time("bs_btime", bstat->bs_btime, bstat->bs_btime_nsec);

	printf("\tbs_gen = 0x%"PRIx32"\n", bstat->bs_gen);
	printf("\tbs_uid = %"PRIu32"\n", bstat->bs_uid);
	printf("\tbs_gid = %"PRIu32"\n", bstat->bs_gid);
	printf("\tbs_projectid = %"PRIu32"\n", bstat->bs_projectid);

	printf("\tbs_blksize = %"PRIu32"\n", bstat->bs_blksize);
	printf("\tbs_rdev = %"PRIu32"\n", bstat->bs_rdev);
	printf("\tbs_cowextsize_blks = %"PRIu32"\n", bstat->bs_cowextsize_blks);
	printf("\tbs_extsize_blks = %"PRIu32"\n", bstat->bs_extsize_blks);

	printf("\tbs_nlink = %"PRIu32"\n", bstat->bs_nlink);
	printf("\tbs_extents = %"PRIu32"\n", bstat->bs_extents);
	printf("\tbs_aextents = %"PRIu32"\n", bstat->bs_aextents);
	printf("\tbs_version = %"PRIu16"\n", bstat->bs_version);
	printf("\tbs_forkoff = %"PRIu16"\n", bstat->bs_forkoff);

	printf("\tbs_sick = 0x%"PRIx16"\n", bstat->bs_sick);
	printf("\tbs_checked = 0x%"PRIx16"\n", bstat->bs_checked);
	printf("\tbs_mode = 0%"PRIo16"\n", bstat->bs_mode);
	printf("\tbs_extents64 = %"PRIu64"\n", bstat->bs_extents64);
};

static void
bulkstat_help(void)
{
	printf(_(
"Bulk-queries the filesystem for inode stat information and prints it.\n"
"\n"
"   -a <agno>  Only iterate this AG.\n"
"   -d         Print debugging output.\n"
"   -q         Be quiet, no output.\n"
"   -e <ino>   Stop after this inode.\n"
"   -m         Include metadata directories.\n"
"   -n <nr>    Ask for this many results at once.\n"
"   -s <ino>   Inode to start with.\n"
"   -v <ver>   Use this version of the ioctl (1 or 5).\n"));
}

static void
set_xfd_flags(
	struct xfs_fd	*xfd,
	uint32_t	ver)
{
	switch (ver) {
	case 1:
		xfd->flags |= XFROG_FLAG_BULKSTAT_FORCE_V1;
		break;
	case 5:
		xfd->flags |= XFROG_FLAG_BULKSTAT_FORCE_V5;
		break;
	default:
		break;
	}
}

static int
bulkstat_f(
	int			argc,
	char			**argv)
{
	struct xfs_fd		xfd = XFS_FD_INIT(file->fd);
	struct xfs_bulkstat_req	*breq;
	uint64_t		startino = 0;
	uint64_t		endino = -1ULL;
	uint32_t		batch_size = 4096;
	uint32_t		agno = 0;
	uint32_t		ver = 0;
	bool			has_agno = false;
	bool			debug = false;
	bool			quiet = false;
	bool			metadir = false;
	unsigned int		i;
	int			c;
	int			ret;

	while ((c = getopt(argc, argv, "a:de:mn:qs:v:")) != -1) {
		switch (c) {
		case 'a':
			agno = cvt_u32(optarg, 10);
			if (errno) {
				perror(optarg);
				return 1;
			}
			has_agno = true;
			break;
		case 'd':
			debug = true;
			break;
		case 'e':
			endino = cvt_u64(optarg, 10);
			if (errno) {
				perror(optarg);
				return 1;
			}
			break;
		case 'm':
			metadir = true;
			break;
		case 'n':
			batch_size = cvt_u32(optarg, 10);
			if (errno) {
				perror(optarg);
				return 1;
			}
			break;
		case 'q':
			quiet = true;
			break;
		case 's':
			startino = cvt_u64(optarg, 10);
			if (errno) {
				perror(optarg);
				return 1;
			}
			break;
		case 'v':
			ver = cvt_u32(optarg, 10);
			if (errno) {
				perror(optarg);
				return 1;
			}
			if (ver != 1 && ver != 5) {
				fprintf(stderr, "version must be 1 or 5.\n");
				return 1;
			}
			break;
		default:
			bulkstat_help();
			return 0;
		}
	}
	if (optind != argc) {
		bulkstat_help();
		return 0;
	}

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

	ret = -xfrog_bulkstat_alloc_req(batch_size, startino, &breq);
	if (ret) {
		xfrog_perror(ret, "alloc bulkreq");
		exitcode = 1;
		return 0;
	}

	if (has_agno)
		xfrog_bulkstat_set_ag(breq, agno);
	if (metadir)
		breq->hdr.flags |= XFS_BULK_IREQ_METADIR;

	set_xfd_flags(&xfd, ver);

	while ((ret = -xfrog_bulkstat(&xfd, breq)) == 0) {
		if (debug)
			printf(
_("bulkstat: startino=%lld flags=0x%x agno=%u ret=%d icount=%u ocount=%u\n"),
				(long long)breq->hdr.ino,
				(unsigned int)breq->hdr.flags,
				(unsigned int)breq->hdr.agno,
				ret,
				(unsigned int)breq->hdr.icount,
				(unsigned int)breq->hdr.ocount);
		if (breq->hdr.ocount == 0)
			break;

		for (i = 0; i < breq->hdr.ocount; i++) {
			if (breq->bulkstat[i].bs_ino > endino)
				break;
			if (quiet)
				continue;
			dump_bulkstat(&breq->bulkstat[i]);
		}
	}
	if (ret) {
		xfrog_perror(ret, "xfrog_bulkstat");
		exitcode = 1;
	}

	free(breq);
	return 0;
}

static void
bulkstat_single_help(void)
{
	printf(_(
"Queries the filesystem for a single inode's stat information and prints it.\n"
"If a given inode is not allocated, information about the next allocated \n"
"inode will be printed instead.\n"
"\n"
"   -v (ver)  Use this version of the ioctl (1 or 5).\n"
"   -d        Print debugging information.\n"
"\n"
"Pass in inode numbers or a special inode name:\n"
"    root     Root directory.\n"));
}

struct single_map {
	const char		*tag;
	uint64_t		code;
};

struct single_map tags[] = {
	{"root", XFS_BULK_IREQ_SPECIAL_ROOT},
	{NULL, 0},
};

static int
bulkstat_single_f(
	int			argc,
	char			**argv)
{
	struct xfs_fd		xfd = XFS_FD_INIT(file->fd);
	struct xfs_bulkstat	bulkstat;
	unsigned long		ver = 0;
	unsigned int		i;
	bool			debug = false;
	bool			metadir = false;
	int			c;
	int			ret;

	while ((c = getopt(argc, argv, "dv:")) != -1) {
		switch (c) {
		case 'd':
			debug = true;
			break;
		case 'm':
			metadir = true;
			break;
		case 'v':
			errno = 0;
			ver = strtoull(optarg, NULL, 10);
			if (errno) {
				perror(optarg);
				return 1;
			}
			if (ver != 1 && ver != 5) {
				fprintf(stderr, "version must be 1 or 5.\n");
				return 1;
			}
			break;
		default:
			bulkstat_single_help();
			return 0;
		}
	}

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

	set_xfd_flags(&xfd, ver);

	for (i = optind; i < argc; i++) {
		struct single_map	*sm = tags;
		uint64_t		ino = NULLFSINO;
		unsigned int		flags = 0;

		/* Try to look up our tag... */
		for (sm = tags; sm->tag; sm++) {
			if (!strcmp(argv[i], sm->tag)) {
				ino = sm->code;
				flags |= XFS_BULK_IREQ_SPECIAL;
				break;
			}
		}

		/* ...or else it's an inode number. */
		if (ino == NULLFSINO) {
			errno = 0;
			ino = strtoull(argv[i], NULL, 10);
			if (errno) {
				perror(argv[i]);
				exitcode = 1;
				return 0;
			}
		}

		if (metadir)
			flags |= XFS_BULK_IREQ_METADIR;

		ret = -xfrog_bulkstat_single(&xfd, ino, flags, &bulkstat);
		if (ret) {
			xfrog_perror(ret, "xfrog_bulkstat_single");
			continue;
		}

		if (debug)
			printf(
_("bulkstat_single: startino=%"PRIu64" flags=0x%"PRIx32" ret=%d\n"),
				ino, flags, ret);

		dump_bulkstat(&bulkstat);
	}

	return 0;
}

static void
dump_inumbers(
	struct xfs_inumbers	*inumbers)
{
	printf("xi_startino = %"PRIu64"\n", inumbers->xi_startino);
	printf("\txi_allocmask = 0x%"PRIx64"\n", inumbers->xi_allocmask);
	printf("\txi_alloccount = %"PRIu8"\n", inumbers->xi_alloccount);
	printf("\txi_version = %"PRIu8"\n", inumbers->xi_version);
}

static void
inumbers_help(void)
{
	printf(_(
"Queries the filesystem for inode group information and prints it.\n"
"\n"
"   -a <agno>  Only iterate this AG.\n"
"   -d         Print debugging output.\n"
"   -e <ino>   Stop after this inode.\n"
"   -n <nr>    Ask for this many results at once.\n"
"   -s <ino>   Inode to start with.\n"
"   -v <ver>   Use this version of the ioctl (1 or 5).\n"));
}

static int
inumbers_f(
	int			argc,
	char			**argv)
{
	struct xfs_fd		xfd = XFS_FD_INIT(file->fd);
	struct xfs_inumbers_req	*ireq;
	uint64_t		startino = 0;
	uint64_t		endino = -1ULL;
	uint32_t		batch_size = 4096;
	uint32_t		agno = 0;
	uint32_t		ver = 0;
	bool			has_agno = false;
	bool			debug = false;
	unsigned int		i;
	int			c;
	int			ret;

	while ((c = getopt(argc, argv, "a:de:n:s:v:")) != -1) {
		switch (c) {
		case 'a':
			agno = cvt_u32(optarg, 10);
			if (errno) {
				perror(optarg);
				return 1;
			}
			has_agno = true;
			break;
		case 'd':
			debug = true;
			break;
		case 'e':
			endino = cvt_u64(optarg, 10);
			if (errno) {
				perror(optarg);
				return 1;
			}
			break;
		case 'n':
			batch_size = cvt_u32(optarg, 10);
			if (errno) {
				perror(optarg);
				return 1;
			}
			break;
		case 's':
			startino = cvt_u64(optarg, 10);
			if (errno) {
				perror(optarg);
				return 1;
			}
			break;
		case 'v':
			ver = cvt_u32(optarg, 10);
			if (errno) {
				perror(optarg);
				return 1;
			}
			if (ver != 1 && ver != 5) {
				fprintf(stderr, "version must be 1 or 5.\n");
				return 1;
			}
			break;
		default:
			bulkstat_help();
			return 0;
		}
	}
	if (optind != argc) {
		bulkstat_help();
		return 0;
	}

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

	ret = -xfrog_inumbers_alloc_req(batch_size, startino, &ireq);
	if (ret) {
		xfrog_perror(ret, "alloc inumbersreq");
		exitcode = 1;
		return 0;
	}

	if (has_agno)
		xfrog_inumbers_set_ag(ireq, agno);

	set_xfd_flags(&xfd, ver);

	while ((ret = -xfrog_inumbers(&xfd, ireq)) == 0) {
		if (debug)
			printf(
_("bulkstat: startino=%"PRIu64" flags=0x%"PRIx32" agno=%"PRIu32" ret=%d icount=%"PRIu32" ocount=%"PRIu32"\n"),
				ireq->hdr.ino,
				ireq->hdr.flags,
				ireq->hdr.agno,
				ret,
				ireq->hdr.icount,
				ireq->hdr.ocount);
		if (ireq->hdr.ocount == 0)
			break;

		for (i = 0; i < ireq->hdr.ocount; i++) {
			if (ireq->inumbers[i].xi_startino > endino)
				break;
			dump_inumbers(&ireq->inumbers[i]);
		}
	}
	if (ret) {
		xfrog_perror(ret, "xfrog_inumbers");
		exitcode = 1;
	}

	free(ireq);
	return 0;
}

static cmdinfo_t	bulkstat_cmd = {
	.name = "bulkstat",
	.cfunc = bulkstat_f,
	.argmin = 0,
	.argmax = -1,
	.flags = CMD_NOMAP_OK | CMD_FLAG_ONESHOT,
	.help = bulkstat_help,
};

static cmdinfo_t	bulkstat_single_cmd = {
	.name = "bulkstat_single",
	.cfunc = bulkstat_single_f,
	.argmin = 1,
	.argmax = -1,
	.flags = CMD_NOMAP_OK | CMD_FLAG_ONESHOT,
	.help = bulkstat_single_help,
};

static cmdinfo_t	inumbers_cmd = {
	.name = "inumbers",
	.cfunc = inumbers_f,
	.argmin = 0,
	.argmax = -1,
	.flags = CMD_NOMAP_OK | CMD_FLAG_ONESHOT,
	.help = inumbers_help,
};

void
bulkstat_init(void)
{
	bulkstat_cmd.args =
_("[-a agno] [-d] [-e endino] [-n batchsize] [-s startino] [-v version]");
	bulkstat_cmd.oneline = _("Bulk stat of inodes in a filesystem");

	bulkstat_single_cmd.args = _("[-d] [-v version] inum...");
	bulkstat_single_cmd.oneline = _("Stat one inode in a filesystem");

	inumbers_cmd.args =
_("[-a agno] [-d] [-e endino] [-n batchsize] [-s startino] [-v version]");
	inumbers_cmd.oneline = _("Query inode groups in a filesystem");

	add_command(&bulkstat_cmd);
	add_command(&bulkstat_single_cmd);
	add_command(&inumbers_cmd);
}
