// SPDX-License-Identifier: GPL-2.0+
/*
 * Copyright (C) 2018 Oracle.  All Rights Reserved.
 * Author: Darrick J. Wong <darrick.wong@oracle.com>
 */
#include "libxfs.h"
#include "command.h"
#include "init.h"
#include "output.h"
#include "libfrog/fsgeom.h"
#include "libfrog/logging.h"

static void
info_help(void)
{
	dbprintf(_(
"\n"
" Pretty-prints the filesystem geometry as derived from the superblock.\n"
" The output has the same format as mkfs.xfs, xfs_info, and other utilities.\n"
"\n"
));

}

static int
info_f(
	int			argc,
	char			**argv)
{
	struct xfs_fsop_geom	geo;

	libxfs_fs_geometry(mp, &geo, XFS_FS_GEOM_MAX_STRUCT_VER);
	xfs_report_geom(&geo, x.data.name, x.log.name, x.rt.name);
	return 0;
}

static const struct cmdinfo info_cmd = {
	.name =		"info",
	.altname =	"i",
	.cfunc =	info_f,
	.argmin =	0,
	.argmax =	0,
	.canpush =	0,
	.args =		NULL,
	.oneline =	N_("pretty-print superblock info"),
	.help =		info_help,
};

static void
agresv_help(void)
{
	dbprintf(_(
"\n"
" Print the size and per-AG reservation information some allocation groups.\n"
"\n"
" Specific allocation group numbers can be provided as command line arguments.\n"
" If no arguments are provided, all allocation groups are iterated.\n"
"\n"
));

}

static void
print_agresv_info(
	struct xfs_perag *pag)
{
	struct xfs_buf	*bp;
	struct xfs_agf	*agf;
	xfs_agnumber_t	agno = pag_agno(pag);
	xfs_extlen_t	ask = 0;
	xfs_extlen_t	used = 0;
	xfs_extlen_t	free = 0;
	xfs_extlen_t	length = 0;
	int		error;

	error = -libxfs_refcountbt_calc_reserves(mp, NULL, pag, &ask, &used);
	if (error)
		xfrog_perror(error, "refcountbt");
	error = -libxfs_finobt_calc_reserves(pag, NULL, &ask, &used);
	if (error)
		xfrog_perror(error, "finobt");
	error = -libxfs_rmapbt_calc_reserves(mp, NULL, pag, &ask, &used);
	if (error)
		xfrog_perror(error, "rmapbt");

	error = -libxfs_read_agf(pag, NULL, 0, &bp);
	if (error)
		xfrog_perror(error, "AGF");
	agf = bp->b_addr;
	length = be32_to_cpu(agf->agf_length);
	free = be32_to_cpu(agf->agf_freeblks) +
	       be32_to_cpu(agf->agf_flcount);
	libxfs_buf_relse(bp);

	printf("AG %d: length: %u free: %u reserved: %u used: %u",
			agno, length, free, ask, used);
	if (ask - used > free)
		printf(" <not enough space>");
	printf("\n");
}

static int
agresv_f(
	int			argc,
	char			**argv)
{
	struct xfs_perag	*pag = NULL;
	int			i;

	if (argc > 1) {
		for (i = 1; i < argc; i++) {
			long	a;
			char	*p;

			errno = 0;
			a = strtol(argv[i], &p, 0);
			if (p == argv[i])
				errno = ERANGE;
			if (errno) {
				perror(argv[i]);
				continue;
			}

			if (a < 0 || a >= mp->m_sb.sb_agcount) {
				fprintf(stderr, "%ld: Not a AG.\n", a);
				continue;
			}

			pag = libxfs_perag_get(mp, a);
			print_agresv_info(pag);
			libxfs_perag_put(pag);
		}
		return 0;
	}

	while ((pag = xfs_perag_next(mp, pag)))
		print_agresv_info(pag);

	return 0;
}

static const struct cmdinfo agresv_cmd = {
	.name =		"agresv",
	.altname =	NULL,
	.cfunc =	agresv_f,
	.argmin =	0,
	.argmax =	-1,
	.canpush =	0,
	.args =		NULL,
	.oneline =	N_("print AG reservation stats"),
	.help =		agresv_help,
};

static void
rgresv_help(void)
{
	dbprintf(_(
"\n"
" Print the size and per-rtgroup reservation information for some realtime allocation groups.\n"
"\n"
" Specific realtime allocation group numbers can be provided as command line\n"
" arguments.  If no arguments are provided, all allocation groups are iterated.\n"
"\n"
));

}

static void
print_rgresv_info(
	struct xfs_rtgroup	*rtg)
{
	struct xfs_trans	*tp;
	xfs_filblks_t		ask = 0;
	xfs_filblks_t		used = 0;
	int			error;

	error = -libxfs_trans_alloc_empty(mp, &tp);
	if (error) {
		dbprintf(
 _("Cannot alloc transaction to look up rtgroup %u rmap inode\n"),
				rtg_rgno(rtg));
		return;
	}

	error = -libxfs_rtginode_load_parent(tp);
	if (error) {
		dbprintf(_("Cannot load realtime metadir, error %d\n"),
			error);
		goto out_trans;
	}

	/* rtrmapbt */
	error = -libxfs_rtginode_load(rtg, XFS_RTGI_RMAP, tp);
	if (error) {
		dbprintf(_("Cannot load rtgroup %u rmap inode, error %d\n"),
			rtg_rgno(rtg), error);
		goto out_rele_dp;
	}
	if (rtg_rmap(rtg))
		used += rtg_rmap(rtg)->i_nblocks;
	libxfs_rtginode_irele(&rtg->rtg_inodes[XFS_RTGI_RMAP]);

	ask += libxfs_rtrmapbt_calc_reserves(mp);

	/* rtrefcount */
	error = -libxfs_rtginode_load(rtg, XFS_RTGI_REFCOUNT, tp);
	if (error) {
		dbprintf(_("Cannot load rtgroup %u refcount inode, error %d\n"),
			rtg_rgno(rtg), error);
		goto out_rele_dp;
	}
	if (rtg_refcount(rtg))
		used += rtg_refcount(rtg)->i_nblocks;
	libxfs_rtginode_irele(&rtg->rtg_inodes[XFS_RTGI_REFCOUNT]);

	ask += libxfs_rtrefcountbt_calc_reserves(mp);

	printf(_("rtg %d: dblocks: %llu fdblocks: %llu reserved: %llu used: %llu"),
			rtg_rgno(rtg),
			(unsigned long long)mp->m_sb.sb_dblocks,
			(unsigned long long)mp->m_sb.sb_fdblocks,
			(unsigned long long)ask,
			(unsigned long long)used);
	if (ask - used > mp->m_sb.sb_fdblocks)
		printf(_(" <not enough space>"));
	printf("\n");
out_rele_dp:
	libxfs_rtginode_irele(&mp->m_rtdirip);
out_trans:
	libxfs_trans_cancel(tp);
}

static int
rgresv_f(
	int			argc,
	char			**argv)
{
	struct xfs_rtgroup	*rtg = NULL;
	int			i;

	if (argc > 1) {
		for (i = 1; i < argc; i++) {
			long	a;
			char	*p;

			errno = 0;
			a = strtol(argv[i], &p, 0);
			if (p == argv[i])
				errno = ERANGE;
			if (errno) {
				perror(argv[i]);
				continue;
			}

			if (a < 0 || a >= mp->m_sb.sb_rgcount) {
				fprintf(stderr, "%ld: Not a rtgroup.\n", a);
				continue;
			}

			rtg = libxfs_rtgroup_get(mp, a);
			print_rgresv_info(rtg);
			libxfs_rtgroup_put(rtg);
		}
		return 0;
	}

	while ((rtg = xfs_rtgroup_next(mp, rtg)))
		print_rgresv_info(rtg);

	return 0;
}

static const struct cmdinfo rgresv_cmd = {
	.name =		"rgresv",
	.altname =	NULL,
	.cfunc =	rgresv_f,
	.argmin =	0,
	.argmax =	-1,
	.canpush =	0,
	.args =		NULL,
	.oneline =	N_("print rtgroup reservation stats"),
	.help =		rgresv_help,
};

void
info_init(void)
{
	add_command(&info_cmd);
	add_command(&agresv_cmd);
	add_command(&rgresv_cmd);
}
