// SPDX-License-Identifier: GPL-2.0+
/*
 * Copyright (C) 2017 Oracle.  All Rights Reserved.
 * Author: Darrick J. Wong <darrick.wong@oracle.com>
 */
#include "platform_defs.h"
#include <linux/fsmap.h>
#include "command.h"
#include "init.h"
#include "libfrog/paths.h"
#include "io.h"
#include "input.h"
#include "libfrog/fsgeom.h"

static cmdinfo_t	fsmap_cmd;
static dev_t		xfs_data_dev;
static dev_t		xfs_log_dev;
static dev_t		xfs_rt_dev;

static void
fsmap_help(void)
{
	printf(_(
"\n"
" Prints the block mapping for the filesystem hosting the current file"
"\n"
" fsmap prints the map of disk blocks used on a given filesystem.\n"
" By default, the blocks for all devices are printed.\n"
" When possible, owner and offset information will be included in the\n"
" space report.\n"
"\n"
" By default, each line of the listing takes the following form:\n"
"     extent: major:minor [startblock..endblock]: owner startoffset..endoffset length\n"
" The owner field is either an inode number or a special value.\n"
" All the file offsets and disk blocks are in units of 512-byte blocks.\n"
" -d -- query only the data device.\n"
" -l -- query only the log device.\n"
" -r -- query only the realtime device.\n"
" -n -- query n extents at a time.\n"
" -m -- output machine-readable format.\n"
" -v -- Verbose information, show AG and offsets.  Show flags legend on 2nd -v\n"
"\n"
"The optional start and end arguments require one of -d, -l, or -r to be set.\n"
"\n"));
}

#define OWNER_BUF_SZ	32
static const char *
special_owner(
	int64_t		owner,
	char		*buf)
{
	switch (owner) {
	case XFS_FMR_OWN_FREE:
		return _("free space");
	case XFS_FMR_OWN_UNKNOWN:
		return _("unknown");
	case XFS_FMR_OWN_FS:
		return _("static fs metadata");
	case XFS_FMR_OWN_LOG:
		return _("journalling log");
	case XFS_FMR_OWN_AG:
		return _("per-AG metadata");
	case XFS_FMR_OWN_INOBT:
		return _("inode btree");
	case XFS_FMR_OWN_INODES:
		return _("inodes");
	case XFS_FMR_OWN_REFC:
		return _("refcount btree");
	case XFS_FMR_OWN_COW:
		return _("cow reservation");
	case XFS_FMR_OWN_DEFECTIVE:
		return _("defective");
	default:
		snprintf(buf, OWNER_BUF_SZ, _("special %u:%u"),
				FMR_OWNER_TYPE(owner), FMR_OWNER_CODE(owner));
		return buf;
	}
}

static void
dump_map(
	unsigned long long	*nr,
	struct fsmap_head	*head)
{
	unsigned long long	i;
	struct fsmap		*p;
	char			owner[OWNER_BUF_SZ];
	char			*fork;

	for (i = 0, p = head->fmh_recs; i < head->fmh_entries; i++, p++) {
		printf("\t%llu: %u:%u [%lld..%lld]: ", i + (*nr),
			major(p->fmr_device), minor(p->fmr_device),
			(long long)BTOBBT(p->fmr_physical),
			(long long)BTOBBT(p->fmr_physical + p->fmr_length - 1));
		fork = (p->fmr_flags & FMR_OF_ATTR_FORK) ? _("attr") : _("data");
		if (p->fmr_flags & FMR_OF_SPECIAL_OWNER)
			printf("%s", special_owner(p->fmr_owner, owner));
		else if (p->fmr_flags & FMR_OF_EXTENT_MAP)
			printf(_("inode %lld %s extent map"),
				(long long) p->fmr_owner, fork);
		else
			printf(_("inode %lld %s %lld..%lld"),
				(long long)p->fmr_owner, fork,
				(long long)BTOBBT(p->fmr_offset),
				(long long)BTOBBT(p->fmr_offset + p->fmr_length - 1));
		printf(_(" %lld\n"),
			(long long)BTOBBT(p->fmr_length));
	}

	(*nr) += head->fmh_entries;
}

static void
dump_map_machine(
	unsigned long long	*nr,
	struct fsmap_head	*head)
{
	unsigned long long	i;
	struct fsmap		*p;
	char			*fork;

	if (*nr == 0)
		printf(_("EXT,MAJOR,MINOR,PSTART,PEND,OWNER,OSTART,OEND,LENGTH\n"));
	for (i = 0, p = head->fmh_recs; i < head->fmh_entries; i++, p++) {
		printf("%llu,%u,%u,%lld,%lld,", i + (*nr),
			major(p->fmr_device), minor(p->fmr_device),
			(long long)BTOBBT(p->fmr_physical),
			(long long)BTOBBT(p->fmr_physical + p->fmr_length - 1));
		fork = (p->fmr_flags & FMR_OF_ATTR_FORK) ? "attr" : "data";
		if (p->fmr_flags & FMR_OF_SPECIAL_OWNER)
			printf("special_%u:%u,,,", FMR_OWNER_TYPE(p->fmr_owner),
				FMR_OWNER_CODE(p->fmr_owner));
		else if (p->fmr_flags & FMR_OF_EXTENT_MAP)
			printf(_("inode_%lld_%s_bmbt,,,"),
				(long long) p->fmr_owner, fork);
		else
			printf(_("inode_%lld_%s,%lld,%lld,"),
				(long long)p->fmr_owner, fork,
				(long long)BTOBBT(p->fmr_offset),
				(long long)BTOBBT(p->fmr_offset + p->fmr_length - 1));
		printf("%lld\n",
			(long long)BTOBBT(p->fmr_length));
	}

	(*nr) += head->fmh_entries;
}

/*
 * Verbose mode displays:
 *   extent: major:minor [startblock..endblock]: startoffset..endoffset \
 *	ag# (agoffset..agendoffset) totalbbs flags
 */
#define MINRANGE_WIDTH	16
#define MINAG_WIDTH	2
#define MINTOT_WIDTH	5
#define NFLG		7		/* count of flags */
#define	FLG_NULL	00000000	/* Null flag */
#define	FLG_ATTR_FORK	01000000	/* attribute fork */
#define	FLG_SHARED	00100000	/* shared extent */
#define	FLG_PRE		00010000	/* Unwritten extent */
#define	FLG_BSU		00001000	/* Not on begin of stripe unit  */
#define	FLG_ESU		00000100	/* Not on end   of stripe unit  */
#define	FLG_BSW		00000010	/* Not on begin of stripe width */
#define	FLG_ESW		00000001	/* Not on end   of stripe width */
static void
dump_map_verbose(
	unsigned long long	*nr,
	struct fsmap_head	*head,
	bool			*dumped_flags,
	struct xfs_fsop_geom	*fsgeo)
{
	unsigned long long	i;
	struct fsmap		*p;
	int			agno;
	off_t			agoff, bperag, bperrtg;
	int			foff_w, boff_w, aoff_w, tot_w, agno_w, own_w;
	int			nr_w, dev_w;
	char			rbuf[40], bbuf[40], abuf[40], obuf[40];
	char			nbuf[40], dbuf[40], gbuf[40];
	char			owner[OWNER_BUF_SZ];
	int			sunit, swidth;
	int			flg = 0;

	foff_w = boff_w = aoff_w = own_w = MINRANGE_WIDTH;
	dev_w = 3;
	nr_w = 4;
	tot_w = MINTOT_WIDTH;
	bperag = (off_t)fsgeo->agblocks *
		  (off_t)fsgeo->blocksize;
	bperrtg = bytes_per_rtgroup(fsgeo);
	sunit = (fsgeo->sunit * fsgeo->blocksize);
	swidth = (fsgeo->swidth * fsgeo->blocksize);

	/*
	 * Go through the extents and figure out the width
	 * needed for all columns.
	 */
	for (i = 0, p = head->fmh_recs; i < head->fmh_entries; i++, p++) {
		if (p->fmr_flags & FMR_OF_PREALLOC ||
		    p->fmr_flags & FMR_OF_ATTR_FORK ||
		    p->fmr_flags & FMR_OF_SHARED)
			flg = 1;
		if (sunit && p->fmr_device == xfs_data_dev &&
		    (p->fmr_physical  % sunit != 0 ||
		     ((p->fmr_physical + p->fmr_length) % sunit) != 0 ||
		     p->fmr_physical % swidth != 0 ||
		     ((p->fmr_physical + p->fmr_length) % swidth) != 0))
			flg = 1;
		if (flg)
			*dumped_flags = true;
		snprintf(nbuf, sizeof(nbuf), "%llu", (*nr) + i);
		nr_w = max(nr_w, strlen(nbuf));
		if (head->fmh_oflags & FMH_OF_DEV_T)
			snprintf(dbuf, sizeof(dbuf), "%u:%u",
				major(p->fmr_device),
				minor(p->fmr_device));
		else
			snprintf(dbuf, sizeof(dbuf), "0x%x", p->fmr_device);
		dev_w = max(dev_w, strlen(dbuf));
		snprintf(bbuf, sizeof(bbuf), "[%lld..%lld]:",
			(long long)BTOBBT(p->fmr_physical),
			(long long)BTOBBT(p->fmr_physical + p->fmr_length - 1));
		boff_w = max(boff_w, strlen(bbuf));
		if (p->fmr_flags & FMR_OF_SPECIAL_OWNER)
			own_w = max(own_w, strlen(
					special_owner(p->fmr_owner, owner)));
		else {
			snprintf(obuf, sizeof(obuf), "%lld",
				(long long)p->fmr_owner);
			own_w = max(own_w, strlen(obuf));
		}
		if (p->fmr_flags & FMR_OF_EXTENT_MAP)
			foff_w = max(foff_w, strlen(_("extent_map")));
		else if (p->fmr_flags & FMR_OF_SPECIAL_OWNER)
			;
		else {
			snprintf(rbuf, sizeof(rbuf), "%lld..%lld",
				(long long)BTOBBT(p->fmr_offset),
				(long long)BTOBBT(p->fmr_offset + p->fmr_length - 1));
			foff_w = max(foff_w, strlen(rbuf));
		}
		if (p->fmr_device == xfs_data_dev) {
			agno = p->fmr_physical / bperag;
			agoff = p->fmr_physical - (agno * bperag);
			snprintf(abuf, sizeof(abuf),
				"(%lld..%lld)",
				(long long)BTOBBT(agoff),
				(long long)BTOBBT(agoff + p->fmr_length - 1));
		} else if (p->fmr_device == xfs_rt_dev && fsgeo->rgcount > 0) {
			uint64_t start = p->fmr_physical -
				fsgeo->rtstart * fsgeo->blocksize;

			agno = start / bperrtg;
			if (agno < 0)
				agno = -1;
			agoff = start % bperrtg;
			snprintf(abuf, sizeof(abuf),
				"(%lld..%lld)",
				(long long)BTOBBT(agoff),
				(long long)BTOBBT(agoff + p->fmr_length - 1));
		} else
			abuf[0] = 0;
		aoff_w = max(aoff_w, strlen(abuf));
		tot_w = max(tot_w,
			numlen(BTOBBT(p->fmr_length), 10));
	}
	agno_w = max(MINAG_WIDTH, numlen(fsgeo->agcount, 10));
	if (*nr == 0)
		printf("%*s: %-*s %-*s %-*s %-*s %*s %-*s %*s%s\n",
			nr_w, _("EXT"),
			dev_w, _("DEV"),
			boff_w, _("BLOCK-RANGE"),
			own_w, _("OWNER"),
			foff_w, _("FILE-OFFSET"),
			agno_w, _("AG"),
			aoff_w, _("AG-OFFSET"),
			tot_w, _("TOTAL"),
			flg ? _(" FLAGS") : "");
	for (i = 0, p = head->fmh_recs; i < head->fmh_entries; i++, p++) {
		flg = FLG_NULL;
		if (p->fmr_flags & FMR_OF_PREALLOC)
			flg |= FLG_PRE;
		if (p->fmr_flags & FMR_OF_ATTR_FORK)
			flg |= FLG_ATTR_FORK;
		if (p->fmr_flags & FMR_OF_SHARED)
			flg |= FLG_SHARED;
		/*
		 * If striping enabled, determine if extent starts/ends
		 * on a stripe unit boundary.
		 */
		if (sunit && p->fmr_device == xfs_data_dev) {
			if (p->fmr_physical  % sunit != 0)
				flg |= FLG_BSU;
			if (((p->fmr_physical +
			      p->fmr_length ) % sunit ) != 0)
				flg |= FLG_ESU;
			if (p->fmr_physical % swidth != 0)
				flg |= FLG_BSW;
			if (((p->fmr_physical +
			      p->fmr_length ) % swidth ) != 0)
				flg |= FLG_ESW;
		}
		if (head->fmh_oflags & FMH_OF_DEV_T)
			snprintf(dbuf, sizeof(dbuf), "%u:%u",
				major(p->fmr_device),
				minor(p->fmr_device));
		else
			snprintf(dbuf, sizeof(dbuf), "0x%x", p->fmr_device);
		snprintf(bbuf, sizeof(bbuf), "[%lld..%lld]:",
			(long long)BTOBBT(p->fmr_physical),
			(long long)BTOBBT(p->fmr_physical + p->fmr_length - 1));
		if (p->fmr_flags & FMR_OF_SPECIAL_OWNER) {
			snprintf(obuf, sizeof(obuf), "%s",
				special_owner(p->fmr_owner, owner));
			snprintf(rbuf, sizeof(rbuf), " ");
		} else {
			snprintf(obuf, sizeof(obuf), "%lld",
				(long long)p->fmr_owner);
			snprintf(rbuf, sizeof(rbuf), "%lld..%lld",
				(long long)BTOBBT(p->fmr_offset),
				(long long)BTOBBT(p->fmr_offset + p->fmr_length - 1));
		}
		if (p->fmr_device == xfs_data_dev) {
			agno = p->fmr_physical / bperag;
			agoff = p->fmr_physical - (agno * bperag);
			snprintf(abuf, sizeof(abuf),
				"(%lld..%lld)",
				(long long)BTOBBT(agoff),
				(long long)BTOBBT(agoff + p->fmr_length - 1));
			snprintf(gbuf, sizeof(gbuf),
				"%lld",
				(long long)agno);
		} else if (p->fmr_device == xfs_rt_dev && fsgeo->rgcount > 0) {
			uint64_t start = p->fmr_physical -
				fsgeo->rtstart * fsgeo->blocksize;

			agno = start / bperrtg;
			if (agno < 0)
				agno = -1;
			agoff = start % bperrtg;
			snprintf(abuf, sizeof(abuf),
				"(%lld..%lld)",
				(long long)BTOBBT(agoff),
				(long long)BTOBBT(agoff + p->fmr_length - 1));
			snprintf(gbuf, sizeof(gbuf),
				"%lld",
				(long long)agno);
		} else {
			abuf[0] = 0;
			gbuf[0] = 0;
		}
		if (p->fmr_flags & FMR_OF_EXTENT_MAP)
			printf("%*llu: %-*s %-*s %-*s %-*s %-*s %-*s %*lld\n",
				nr_w, (*nr) + i,
				dev_w, dbuf,
				boff_w, bbuf,
				own_w, obuf,
				foff_w, _("extent map"),
				agno_w, gbuf,
				aoff_w, abuf,
				tot_w, (long long)BTOBBT(p->fmr_length));
		else {
			printf("%*llu: %-*s %-*s %-*s %-*s", nr_w, (*nr) + i,
				dev_w, dbuf, boff_w, bbuf, own_w, obuf,
				foff_w, rbuf);
			printf(" %-*s %-*s", agno_w, gbuf,
				aoff_w, abuf);
			printf(" %*lld", tot_w,
				(long long)BTOBBT(p->fmr_length));
			if (flg == FLG_NULL)
				printf("\n");
			else
				printf(" %-*.*o\n", NFLG, NFLG, flg);
		}
	}

	(*nr) += head->fmh_entries;
}

static void
dump_verbose_key(void)
{
	printf(_(" FLAG Values:\n"));
	printf(_("    %*.*o Attribute fork\n"),
		NFLG+1, NFLG+1, FLG_ATTR_FORK);
	printf(_("    %*.*o Shared extent\n"),
		NFLG+1, NFLG+1, FLG_SHARED);
	printf(_("    %*.*o Unwritten preallocated extent\n"),
		NFLG+1, NFLG+1, FLG_PRE);
	printf(_("    %*.*o Doesn't begin on stripe unit\n"),
		NFLG+1, NFLG+1, FLG_BSU);
	printf(_("    %*.*o Doesn't end   on stripe unit\n"),
		NFLG+1, NFLG+1, FLG_ESU);
	printf(_("    %*.*o Doesn't begin on stripe width\n"),
		NFLG+1, NFLG+1, FLG_BSW);
	printf(_("    %*.*o Doesn't end   on stripe width\n"),
		NFLG+1, NFLG+1, FLG_ESW);
}

static int
fsmap_f(
	int			argc,
	char			**argv)
{
	struct fsmap		*p;
	struct fsmap_head	*head;
	struct fsmap		*l, *h;
	struct xfs_fsop_geom	fsgeo;
	long long		start = 0;
	long long		end = -1;
	int			map_size;
	int			nflag = 0;
	int			vflag = 0;
	int			mflag = 0;
	int			i = 0;
	int			c;
	unsigned long long	nr = 0;
	size_t			fsblocksize, fssectsize;
	bool			dumped_flags = false;
	int			dflag, lflag, rflag;

	init_cvtnum(&fsblocksize, &fssectsize);

	dflag = lflag = rflag = 0;
	while ((c = getopt(argc, argv, "dlmn:rv")) != EOF) {
		switch (c) {
		case 'd':	/* data device */
			dflag = 1;
			break;
		case 'l':	/* log device */
			lflag = 1;
			break;
		case 'm':	/* machine readable format */
			mflag++;
			break;
		case 'n':	/* number of extents specified */
			nflag = cvt_u32(optarg, 10);
			if (errno)
				return command_usage(&fsmap_cmd);
			break;
		case 'r':	/* rt device */
			rflag = 1;
			break;
		case 'v':	/* Verbose output */
			vflag++;
			break;
		default:
			exitcode = 1;
			return command_usage(&fsmap_cmd);
		}
	}

	if ((dflag + lflag + rflag > 1) || (mflag > 0 && vflag > 0) ||
	    (argc > optind && dflag + lflag + rflag == 0)) {
		exitcode = 1;
		return command_usage(&fsmap_cmd);
	}

	if (argc > optind) {
		start = cvtnum(fsblocksize, fssectsize, argv[optind]);
		if (start < 0) {
			fprintf(stderr,
				_("Bad rmap start_bblock %s.\n"),
				argv[optind]);
			exitcode = 1;
			return 0;
		}
		start <<= BBSHIFT;
	}

	if (argc > optind + 1) {
		end = cvtnum(fsblocksize, fssectsize, argv[optind + 1]);
		if (end < 0) {
			fprintf(stderr,
				_("Bad rmap end_bblock %s.\n"),
				argv[optind + 1]);
			exitcode = 1;
			return 0;
		}
		end <<= BBSHIFT;
	}

	if (vflag) {
		c = -xfrog_geometry(file->fd, &fsgeo);
		if (c) {
			fprintf(stderr,
				_("%s: can't get geometry [\"%s\"]: %s\n"),
				progname, file->name, strerror(c));
			exitcode = 1;
			return 0;
		}
	}

	map_size = nflag ? nflag : 131072 / sizeof(struct fsmap);
	head = malloc(fsmap_sizeof(map_size));
	if (head == NULL) {
		fprintf(stderr, _("%s: malloc of %zu bytes failed.\n"),
			progname, fsmap_sizeof(map_size));
		exitcode = 1;
		return 0;
	}

	/*
	 * File systems with internal rt device use synthetic device values.
	 */
	if (file->geom.rtstart) {
		xfs_data_dev = XFS_DEV_DATA;
		xfs_log_dev = XFS_DEV_LOG;
		xfs_rt_dev = XFS_DEV_RT;
	} else {
		xfs_data_dev = file->fs_path.fs_datadev;
		xfs_log_dev = file->fs_path.fs_logdev;
		xfs_rt_dev = file->fs_path.fs_rtdev;
	}

	memset(head, 0, sizeof(*head));
	l = head->fmh_keys;
	h = head->fmh_keys + 1;
	if (dflag) {
		l->fmr_device = h->fmr_device = xfs_data_dev;
	} else if (lflag) {
		l->fmr_device = h->fmr_device = xfs_log_dev;
	} else if (rflag) {
		l->fmr_device = h->fmr_device = xfs_rt_dev;
	} else {
		l->fmr_device = 0;
		h->fmr_device = UINT_MAX;
	}
	l->fmr_physical = start;
	h->fmr_physical = end;
	h->fmr_owner = ULLONG_MAX;
	h->fmr_flags = UINT_MAX;
	h->fmr_offset = ULLONG_MAX;

	head->fmh_count = map_size;
	do {
		/* Get some extents */
		i = ioctl(file->fd, FS_IOC_GETFSMAP, head);
		if (i < 0) {
			fprintf(stderr, _("%s: xfsctl(XFS_IOC_GETFSMAP)"
				" iflags=0x%x [\"%s\"]: %s\n"),
				progname, head->fmh_iflags, file->name,
				strerror(errno));
			free(head);
			exitcode = 1;
			return 0;
		}

		if (head->fmh_entries == 0)
			break;

		if (vflag)
			dump_map_verbose(&nr, head, &dumped_flags, &fsgeo);
		else if (mflag)
			dump_map_machine(&nr, head);
		else
			dump_map(&nr, head);

		p = &head->fmh_recs[head->fmh_entries - 1];
		if (p->fmr_flags & FMR_OF_LAST)
			break;
		fsmap_advance(head);
	} while (true);

	if (dumped_flags)
		dump_verbose_key();

	free(head);
	return 0;
}

void
fsmap_init(void)
{
	fsmap_cmd.name = "fsmap";
	fsmap_cmd.cfunc = fsmap_f;
	fsmap_cmd.argmin = 0;
	fsmap_cmd.argmax = -1;
	fsmap_cmd.flags = CMD_NOMAP_OK | CMD_FLAG_FOREIGN_OK;
	fsmap_cmd.args = _("[-d|-l|-r] [-m|-v] [-n nx] [start] [end]");
	fsmap_cmd.oneline = _("print filesystem mapping for a range of blocks");
	fsmap_cmd.help = fsmap_help;

	add_command(&fsmap_cmd);
}
