// SPDX-License-Identifier: GPL-2.0
/*
 * Copyright (c) 2000-2001,2005 Silicon Graphics, Inc.
 * All Rights Reserved.
 */

#include "libxfs.h"
#include "block.h"
#include "bmap.h"
#include "command.h"
#include "type.h"
#include "faddr.h"
#include "fprint.h"
#include "field.h"
#include "inode.h"
#include "io.h"
#include "output.h"
#include "init.h"

static int	ablock_f(int argc, char **argv);
static void     ablock_help(void);
static int	daddr_f(int argc, char **argv);
static void     daddr_help(void);
static int	dblock_f(int argc, char **argv);
static void     dblock_help(void);
static int	fsblock_f(int argc, char **argv);
static void     fsblock_help(void);
static int	rtblock_f(int argc, char **argv);
static void	rtblock_help(void);
static int	rtextent_f(int argc, char **argv);
static void	rtextent_help(void);
static int	logblock_f(int argc, char **argv);
static void	logblock_help(void);
static void	print_rawdata(void *data, int len);

static const cmdinfo_t	ablock_cmd =
	{ "ablock", NULL, ablock_f, 1, 1, 1, N_("filoff"),
	  N_("set address to file offset (attr fork)"), ablock_help };
static const cmdinfo_t	daddr_cmd =
	{ "daddr", NULL, daddr_f, 0, -1, 1, N_("[d]"),
	  N_("set address to daddr value"), daddr_help };
static const cmdinfo_t	dblock_cmd =
	{ "dblock", NULL, dblock_f, 1, 1, 1, N_("filoff"),
	  N_("set address to file offset (data fork)"), dblock_help };
static const cmdinfo_t	fsblock_cmd =
	{ "fsblock", "fsb", fsblock_f, 0, 1, 1, N_("[fsb]"),
	  N_("set address to fsblock value"), fsblock_help };
static const cmdinfo_t	rtblock_cmd =
	{ "rtblock", "rtbno", rtblock_f, 0, 1, 1, N_("[rtbno]"),
	  N_("set address to rtblock value"), rtblock_help };
static const cmdinfo_t	rtextent_cmd =
	{ "rtextent", "rtx", rtextent_f, 0, 1, 1, N_("[rtxno]"),
	  N_("set address to rtextent value"), rtextent_help };
static const cmdinfo_t	logblock_cmd =
	{ "logblock", "lsb", logblock_f, 0, 1, 1, N_("[logbno]"),
	  N_("set address to logblock value"), logblock_help };

static void
ablock_help(void)
{
	dbprintf(_(
"\n Example:\n"
"\n"
" 'ablock 23' - sets the file position to the 23rd filesystem block in\n"
" the inode's attribute fork.  The filesystem block size is specified in\n"
" the superblock.\n\n"
));
}

/*ARGSUSED*/
static int
ablock_f(
	int			argc,
	char			**argv)
{
	bmap_ext_t		bm;
	xfs_fileoff_t		bno;
	xfs_fsblock_t		dfsbno;
	int			haveattr;
	xfs_extnum_t		nex;
	char			*p;
	struct xfs_dinode	*dip = iocur_top->data;

	bno = (xfs_fileoff_t)strtoull(argv[1], &p, 0);
	if (*p != '\0') {
		dbprintf(_("bad block number %s\n"), argv[1]);
		return 0;
	}
	push_cur();
	set_cur_inode(iocur_top->ino);
	if (!dip) {
		pop_cur();
		dbprintf(_("no current inode\n"));
		return 0;
	}
	haveattr = dip->di_forkoff;
	pop_cur();
	if (!haveattr) {
		dbprintf(_("no attribute data for file\n"));
		return 0;
	}
	nex = 1;
	bmap(bno, 1, XFS_ATTR_FORK, &nex, &bm);
	if (nex == 0) {
		dbprintf(_("file attr block is unmapped\n"));
		return 0;
	}
	dfsbno = bm.startblock + (bno - bm.startoff);
	ASSERT(typtab[TYP_ATTR].typnm == TYP_ATTR);
	set_cur(&typtab[TYP_ATTR], (int64_t)XFS_FSB_TO_DADDR(mp, dfsbno),
		blkbb, DB_RING_ADD, NULL);
	return 0;
}

void
block_init(void)
{
	add_command(&ablock_cmd);
	add_command(&daddr_cmd);
	add_command(&dblock_cmd);
	add_command(&fsblock_cmd);
	add_command(&rtblock_cmd);
	add_command(&rtextent_cmd);
	add_command(&logblock_cmd);
}

static void
daddr_help(void)
{
	dbprintf(_(
"\n Example:\n"
"\n"
" 'daddr 102' - sets position to the 102nd absolute disk block\n"
" (512 byte block).\n"
));
}

enum daddr_target {
	DT_DATA,
	DT_RT,
	DT_LOG,
};

static int
daddr_f(
	int		argc,
	char		**argv)
{
	xfs_rfsblock_t	max_daddrs = mp->m_sb.sb_dblocks;
	int64_t		d;
	char		*p;
	int		bb_count = BTOBB(mp->m_sb.sb_sectsize);
	int		c;
	enum daddr_target tgt = DT_DATA;

	while ((c = getopt(argc, argv, "rl")) != -1) {
		switch (c) {
		case 'r':
			tgt = DT_RT;
			max_daddrs = mp->m_sb.sb_rblocks;
			break;
		case 'l':
			tgt = DT_LOG;
			max_daddrs = mp->m_sb.sb_logblocks;
			break;
		default:
			daddr_help();
			return 0;
		}
	}

	if (tgt == DT_LOG && mp->m_sb.sb_logstart > 0) {
		dbprintf(_("filesystem has internal log\n"));
		return 0;
	}

	if (optind == argc) {
		xfs_daddr_t	daddr = iocur_top->off >> BBSHIFT;

		if (iocur_is_ddev(iocur_top))
			dbprintf(_("datadev daddr is %lld\n"), daddr);
		else if (iocur_is_extlogdev(iocur_top))
			dbprintf(_("logdev daddr is %lld\n"), daddr);
		else if (iocur_is_rtdev(iocur_top))
			dbprintf(_("rtdev daddr is %lld\n"), daddr);
		else
			dbprintf(_("current daddr is %lld\n"), daddr);

		return 0;
	}

	if (optind != argc - 1) {
		daddr_help();
		return 0;
	}

	d = (int64_t)strtoull(argv[optind], &p, 0);
	if (*p != '\0' ||
	    d >= max_daddrs << (mp->m_sb.sb_blocklog - BBSHIFT)) {
		dbprintf(_("bad daddr %s\n"), argv[1]);
		return 0;
	}
	ASSERT(typtab[TYP_DATA].typnm == TYP_DATA);
	switch (tgt) {
	case DT_DATA:
		set_cur(&typtab[TYP_DATA], d, bb_count, DB_RING_ADD, NULL);
		break;
	case DT_RT:
		set_rt_cur(&typtab[TYP_DATA], d, bb_count, DB_RING_ADD, NULL);
		break;
	case DT_LOG:
		set_log_cur(&typtab[TYP_DATA], d, bb_count, DB_RING_ADD, NULL);
		break;
	}
	return 0;
}

static void
dblock_help(void)
{
	dbprintf(_(
"\n Example:\n"
"\n"
" 'dblock 23' - sets the file position to the 23rd filesystem block in\n"
" the inode's data fork.  The filesystem block size is specified in the\n"
" superblock.\n\n"
));
}

static inline bool
is_rtfile(
	struct xfs_dinode	*dip)
{
	return dip->di_flags & cpu_to_be16(XFS_DIFLAG_REALTIME);
}

static int
dblock_f(
	int		argc,
	char		**argv)
{
	bbmap_t		bbmap;
	bmap_ext_t	*bmp;
	xfs_fileoff_t	bno;
	xfs_fsblock_t	dfsbno;
	int		nb;
	xfs_extnum_t	nex;
	char		*p;
	bool		isrt;
	typnm_t		type;

	bno = (xfs_fileoff_t)strtoull(argv[1], &p, 0);
	if (*p != '\0') {
		dbprintf(_("bad block number %s\n"), argv[1]);
		return 0;
	}
	push_cur();
	set_cur_inode(iocur_top->ino);
	isrt = is_rtfile(iocur_top->data);
	type = inode_next_type();
	pop_cur();
	if (type == TYP_NONE) {
		dbprintf(_("no type for file data\n"));
		return 0;
	}
	nex = nb = type == TYP_DIR2 ? mp->m_dir_geo->fsbcount : 1;
	bmp = malloc(nb * sizeof(*bmp));
	bmap(bno, nb, XFS_DATA_FORK, &nex, bmp);
	if (nex == 0) {
		dbprintf(_("file data block is unmapped\n"));
		free(bmp);
		return 0;
	}
	dfsbno = bmp->startblock + (bno - bmp->startoff);
	ASSERT(typtab[type].typnm == type);
	if (nex > 1)
		make_bbmap(&bbmap, nex, bmp);
	if (isrt)
		set_rt_cur(&typtab[type], xfs_rtb_to_daddr(mp, dfsbno),
				nb * blkbb, DB_RING_ADD,
				nex > 1 ? &bbmap : NULL);
	else
		set_cur(&typtab[type], (int64_t)XFS_FSB_TO_DADDR(mp, dfsbno),
				nb * blkbb, DB_RING_ADD,
				nex > 1 ? &bbmap : NULL);
	free(bmp);
	return 0;
}

static void
fsblock_help(void)
{
	dbprintf(_(
"\n Example:\n"
"\n"
" 'fsblock 1023' - sets the file position to the 1023rd filesystem block.\n"
" The filesystem block size is specified in the superblock and set during\n"
" mkfs time.  Offset is absolute (not AG relative).\n\n"
));
}

static int
fsblock_f(
	int		argc,
	char		**argv)
{
	xfs_agblock_t	agbno;
	xfs_agnumber_t	agno;
	xfs_fsblock_t	d;
	char		*p;

	if (argc == 1) {
		if (!iocur_is_ddev(iocur_top)) {
			dbprintf(_("cursor does not point to data device\n"));
			return 0;
		}
		dbprintf(_("current fsblock is %lld\n"),
			XFS_DADDR_TO_FSB(mp, iocur_top->off >> BBSHIFT));
		return 0;
	}
	d = strtoull(argv[1], &p, 0);
	if (*p != '\0') {
		dbprintf(_("bad fsblock %s\n"), argv[1]);
		return 0;
	}
	agno = XFS_FSB_TO_AGNO(mp, d);
	agbno = XFS_FSB_TO_AGBNO(mp, d);
	if (agno >= mp->m_sb.sb_agcount || agbno >= mp->m_sb.sb_agblocks) {
		dbprintf(_("bad fsblock %s\n"), argv[1]);
		return 0;
	}
	ASSERT(typtab[TYP_DATA].typnm == TYP_DATA);
	set_cur(&typtab[TYP_DATA], XFS_AGB_TO_DADDR(mp, agno, agbno),
		blkbb, DB_RING_ADD, NULL);
	return 0;
}

static void
rtblock_help(void)
{
	dbprintf(_(
"\n Example:\n"
"\n"
" 'rtblock 1023' - sets the file position to the 1023rd block on the realtime\n"
" volume. The filesystem block size is specified in the superblock and set\n"
" during mkfs time.\n\n"
));
}

static int
rtblock_f(
	int		argc,
	char		**argv)
{
	xfs_rtblock_t	rtbno;
	char		*p;

	if (argc == 1) {
		if (!iocur_is_rtdev(iocur_top)) {
			dbprintf(_("cursor does not point to rt device\n"));
			return 0;
		}
		dbprintf(_("current rtblock is %lld\n"),
			xfs_daddr_to_rtb(mp, iocur_top->off >> BBSHIFT));
		return 0;
	}
	rtbno = strtoull(argv[1], &p, 0);
	if (*p != '\0') {
		dbprintf(_("bad rtblock %s\n"), argv[1]);
		return 0;
	}

	if (xfs_has_rtgroups(mp)) {
		xfs_rgnumber_t	rgno = xfs_rtb_to_rgno(mp, rtbno);
		xfs_rgblock_t	rgbno = xfs_rtb_to_rgbno(mp, rtbno);

		if (rgno >= mp->m_sb.sb_rgcount ||
		    rgbno >= mp->m_sb.sb_rgextents * mp->m_sb.sb_rextsize) {
			dbprintf(_("bad rtblock %s\n"), argv[1]);
			return 0;
		}
	} else {
		if (rtbno >= mp->m_sb.sb_rblocks) {
			dbprintf(_("bad rtblock %s\n"), argv[1]);
			return 0;
		}
	}

	ASSERT(typtab[TYP_DATA].typnm == TYP_DATA);
	set_rt_cur(&typtab[TYP_DATA], xfs_rtb_to_daddr(mp, rtbno), blkbb,
			DB_RING_ADD, NULL);
	return 0;
}

static void
rtextent_help(void)
{
	dbprintf(_(
"\n Example:\n"
"\n"
" 'rtextent 10' - sets the file position to the 10th extent on the realtime\n"
" volume. The realtime extent size is specified in the superblock and set\n"
" during mkfs or growfs time.\n\n"
));
}

/*
 * Move the cursor to a specific location on the realtime block device given
 * a linear address in units of realtime extents.
 *
 * NOTE: The user interface assumes a global RT extent number, while the
 * in-kernel rtx is per-RTG now, thus the odd conversions here.
 */
static int
rtextent_f(
	int		argc,
	char		**argv)
{
	uint64_t	rfsbno;
	uint64_t	rtx;
	char		*p;

	if (argc == 1) {
		if (!iocur_is_rtdev(iocur_top)) {
			dbprintf(_("cursor does not point to rt device\n"));
			return 0;
		}

		rfsbno = XFS_BB_TO_FSB(mp, iocur_top->off >> BBSHIFT);
		dbprintf(_("current rtextent is %lld\n"),
				xfs_blen_to_rtbxlen(mp, rfsbno));
		return 0;
	}
	rtx = strtoull(argv[1], &p, 0);
	if (*p != '\0') {
		dbprintf(_("bad rtextent %s\n"), argv[1]);
		return 0;
	}
	if (rtx >= mp->m_sb.sb_rextents) {
		dbprintf(_("bad rtextent %s\n"), argv[1]);
		return 0;
	}

	rfsbno = xfs_rtbxlen_to_blen(mp, rtx);
	ASSERT(typtab[TYP_DATA].typnm == TYP_DATA);
	set_rt_cur(&typtab[TYP_DATA], XFS_FSB_TO_BB(mp, rfsbno),
			mp->m_sb.sb_rextsize * blkbb, DB_RING_ADD, NULL);
	return 0;
}

static void
logblock_help(void)
{
	dbprintf(_(
"\n Example:\n"
"\n"
" 'logblock 1023' - sets the file position to the 1023rd log block.\n"
" The external log device or the block offset within the internal log will be\n"
" chosen as appropriate.\n"
));
}

static int
logblock_f(
	int		argc,
	char		**argv)
{
	xfs_fsblock_t	logblock;
	char		*p;

	if (argc == 1) {
		if (mp->m_sb.sb_logstart > 0 && iocur_is_ddev(iocur_top)) {
			logblock = XFS_DADDR_TO_FSB(mp,
						iocur_top->off >> BBSHIFT);

			if (logblock < mp->m_sb.sb_logstart ||
			    logblock >= mp->m_sb.sb_logstart +
					mp->m_sb.sb_logblocks) {
				dbprintf(
 _("current address not within internal log\n"));
				return 0;
			}

			dbprintf(_("current logblock is %lld\n"),
					logblock - mp->m_sb.sb_logstart);
			return 0;
		}

		if (mp->m_sb.sb_logstart == 0 &&
		    iocur_is_extlogdev(iocur_top)) {
			logblock = XFS_BB_TO_FSB(mp,
						iocur_top->off >> BBSHIFT);

			if (logblock >= mp->m_sb.sb_logblocks) {
				dbprintf(
 _("current address not within external log\n"));
				return 0;
			}

			dbprintf(_("current logblock is %lld\n"), logblock);
			return 0;
		}

		dbprintf(_("current address does not point to log\n"));
		return 0;
	}

	logblock = strtoull(argv[1], &p, 0);
	if (*p != '\0') {
		dbprintf(_("bad logblock %s\n"), argv[1]);
		return 0;
	}

	if (logblock >= mp->m_sb.sb_logblocks) {
		dbprintf(_("bad logblock %s\n"), argv[1]);
		return 0;
	}

	ASSERT(typtab[TYP_DATA].typnm == TYP_DATA);

	if (mp->m_sb.sb_logstart) {
		logblock += mp->m_sb.sb_logstart;
		set_cur(&typtab[TYP_DATA], XFS_FSB_TO_DADDR(mp, logblock),
				blkbb, DB_RING_ADD, NULL);
	} else {
		set_log_cur(&typtab[TYP_DATA], XFS_FSB_TO_BB(mp, logblock),
				blkbb, DB_RING_ADD, NULL);
	}

	return 0;
}

void
print_block(
	const field_t	*fields,
	int		argc,
	char		**argv)
{
	print_rawdata(iocur_top->data, iocur_top->len);
}

static void
print_rawdata(
	void	*data,
	int	len)
{
	int	i;
	int	j;
	int	lastaddr;
	int	offchars;
	unsigned char	*p;

	lastaddr = (len - 1) & ~(32 - 1);
	if (lastaddr < 0x10)
		offchars = 1;
	else if (lastaddr < 0x100)
		offchars = 2;
	else if (lastaddr < 0x1000)
		offchars = 3;
	else
		offchars = 4;
	for (i = 0, p = data; i < len; i += 32) {
		dbprintf("%-0*.*x:", offchars, offchars, i);
		for (j = 0; j < 32 && i + j < len; j++, p++) {
			if ((j & 3) == 0)
				dbprintf(" ");
			dbprintf("%02x", *p);
		}
		dbprintf("\n");
	}
}
