// SPDX-License-Identifier: GPL-2.0+
/*
 * Copyright (C) 2018 Oracle.  All Rights Reserved.
 * Author: Darrick J. Wong <darrick.wong@oracle.com>
 */
#include "xfs.h"
#include <stdint.h>
#include <string.h>
#include <pthread.h>
#include <sys/statvfs.h>
#include "workqueue.h"
#include "path.h"
#include "xfs_scrub.h"
#include "common.h"
#include "spacemap.h"

/*
 * Filesystem space map iterators.
 *
 * Logically, we call GETFSMAP to fetch a set of space map records and
 * call a function to iterate over the records.  However, that's not
 * what actually happens -- the work is split into separate items, with
 * each AG, the realtime device, and the log device getting their own
 * work items.  For an XFS with a realtime device and an external log,
 * this means that we can have up to ($agcount + 2) threads running at
 * once.
 *
 * This comes into play if we want to have per-workitem memory.  Maybe.
 * XXX: do we really need all that ?
 */

#define FSMAP_NR	65536

/* Iterate all the fs block mappings between the two keys. */
bool
xfs_iterate_fsmap(
	struct scrub_ctx	*ctx,
	const char		*descr,
	struct fsmap		*keys,
	xfs_fsmap_iter_fn	fn,
	void			*arg)
{
	struct fsmap_head	*head;
	struct fsmap		*p;
	bool			moveon = true;
	int			i;
	int			error;

	head = malloc(fsmap_sizeof(FSMAP_NR));
	if (!head) {
		str_errno(ctx, descr);
		return false;
	}

	memset(head, 0, sizeof(*head));
	memcpy(head->fmh_keys, keys, sizeof(struct fsmap) * 2);
	head->fmh_count = FSMAP_NR;

	while ((error = ioctl(ctx->mnt_fd, FS_IOC_GETFSMAP, head)) == 0) {
		for (i = 0, p = head->fmh_recs;
		     i < head->fmh_entries;
		     i++, p++) {
			moveon = fn(ctx, descr, p, arg);
			if (!moveon)
				goto out;
			if (xfs_scrub_excessive_errors(ctx)) {
				moveon = false;
				goto out;
			}
		}

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

	if (error) {
		str_errno(ctx, descr);
		moveon = false;
	}
out:
	free(head);
	return moveon;
}

/* GETFSMAP wrappers routines. */
struct xfs_scan_blocks {
	xfs_fsmap_iter_fn	fn;
	void			*arg;
	bool			moveon;
};

/* Iterate all the reverse mappings of an AG. */
static void
xfs_scan_ag_blocks(
	struct workqueue	*wq,
	xfs_agnumber_t		agno,
	void			*arg)
{
	struct scrub_ctx	*ctx = (struct scrub_ctx *)wq->wq_ctx;
	struct xfs_scan_blocks	*sbx = arg;
	char			descr[DESCR_BUFSZ];
	struct fsmap		keys[2];
	off64_t			bperag;
	bool			moveon;

	bperag = (off64_t)ctx->geo.agblocks *
		 (off64_t)ctx->geo.blocksize;

	snprintf(descr, DESCR_BUFSZ, _("dev %d:%d AG %u fsmap"),
				major(ctx->fsinfo.fs_datadev),
				minor(ctx->fsinfo.fs_datadev),
				agno);

	memset(keys, 0, sizeof(struct fsmap) * 2);
	keys->fmr_device = ctx->fsinfo.fs_datadev;
	keys->fmr_physical = agno * bperag;
	(keys + 1)->fmr_device = ctx->fsinfo.fs_datadev;
	(keys + 1)->fmr_physical = ((agno + 1) * bperag) - 1;
	(keys + 1)->fmr_owner = ULLONG_MAX;
	(keys + 1)->fmr_offset = ULLONG_MAX;
	(keys + 1)->fmr_flags = UINT_MAX;

	moveon = xfs_iterate_fsmap(ctx, descr, keys, sbx->fn, sbx->arg);
	if (!moveon)
		sbx->moveon = false;
}

/* Iterate all the reverse mappings of a standalone device. */
static void
xfs_scan_dev_blocks(
	struct scrub_ctx	*ctx,
	int			idx,
	dev_t			dev,
	struct xfs_scan_blocks	*sbx)
{
	struct fsmap		keys[2];
	char			descr[DESCR_BUFSZ];
	bool			moveon;

	snprintf(descr, DESCR_BUFSZ, _("dev %d:%d fsmap"),
			major(dev), minor(dev));

	memset(keys, 0, sizeof(struct fsmap) * 2);
	keys->fmr_device = dev;
	(keys + 1)->fmr_device = dev;
	(keys + 1)->fmr_physical = ULLONG_MAX;
	(keys + 1)->fmr_owner = ULLONG_MAX;
	(keys + 1)->fmr_offset = ULLONG_MAX;
	(keys + 1)->fmr_flags = UINT_MAX;

	moveon = xfs_iterate_fsmap(ctx, descr, keys, sbx->fn, sbx->arg);
	if (!moveon)
		sbx->moveon = false;
}

/* Iterate all the reverse mappings of the realtime device. */
static void
xfs_scan_rt_blocks(
	struct workqueue	*wq,
	xfs_agnumber_t		agno,
	void			*arg)
{
	struct scrub_ctx	*ctx = (struct scrub_ctx *)wq->wq_ctx;

	xfs_scan_dev_blocks(ctx, agno, ctx->fsinfo.fs_rtdev, arg);
}

/* Iterate all the reverse mappings of the log device. */
static void
xfs_scan_log_blocks(
	struct workqueue	*wq,
	xfs_agnumber_t		agno,
	void			*arg)
{
	struct scrub_ctx	*ctx = (struct scrub_ctx *)wq->wq_ctx;

	xfs_scan_dev_blocks(ctx, agno, ctx->fsinfo.fs_logdev, arg);
}

/* Scan all the blocks in a filesystem. */
bool
xfs_scan_all_spacemaps(
	struct scrub_ctx	*ctx,
	xfs_fsmap_iter_fn	fn,
	void			*arg)
{
	struct workqueue	wq;
	struct xfs_scan_blocks	sbx;
	xfs_agnumber_t		agno;
	int			ret;

	sbx.moveon = true;
	sbx.fn = fn;
	sbx.arg = arg;

	ret = workqueue_create(&wq, (struct xfs_mount *)ctx,
			scrub_nproc_workqueue(ctx));
	if (ret) {
		str_info(ctx, ctx->mntpoint, _("Could not create workqueue."));
		return false;
	}
	if (ctx->fsinfo.fs_rt) {
		ret = workqueue_add(&wq, xfs_scan_rt_blocks,
				ctx->geo.agcount + 1, &sbx);
		if (ret) {
			sbx.moveon = false;
			str_info(ctx, ctx->mntpoint,
_("Could not queue rtdev fsmap work."));
			goto out;
		}
	}
	if (ctx->fsinfo.fs_log) {
		ret = workqueue_add(&wq, xfs_scan_log_blocks,
				ctx->geo.agcount + 2, &sbx);
		if (ret) {
			sbx.moveon = false;
			str_info(ctx, ctx->mntpoint,
_("Could not queue logdev fsmap work."));
			goto out;
		}
	}
	for (agno = 0; agno < ctx->geo.agcount; agno++) {
		ret = workqueue_add(&wq, xfs_scan_ag_blocks, agno, &sbx);
		if (ret) {
			sbx.moveon = false;
			str_info(ctx, ctx->mntpoint,
_("Could not queue AG %u fsmap work."), agno);
			break;
		}
	}
out:
	workqueue_destroy(&wq);

	return sbx.moveon;
}
