// SPDX-License-Identifier: GPL-2.0
/*
 * User-space demo of mfs
 *
 * Example use:
 * ./mfsd [mfs_mountpoint]
 * mfsd.c demostrates how to poll mfs device, read the events,
 * parse the events, process the events according to user mode
 * and trigger the ioctls mfs supported.
 *
 * See Documentation/filesystems/mfs.rst
 */

#define _GNU_SOURCE
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
#include <stdio.h>
#include <poll.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/statfs.h>

#include "../../include/uapi/linux/mfs.h"
#include "../../include/uapi/linux/magic.h"

#define pr_err(fmt, ...) fprintf(stderr, fmt, ##__VA_ARGS__)

static int mfs_mode = -1;

static int process_close(struct mfs_msg *msg)
{
	int fd = msg->fd;
	int ret;

	if (fd < 0)
		return 0;
	ret = close(fd);
	if (ret)
		perror("close failed");

	return ret;
}

static int process_local_read(struct mfs_msg *msg)
{
	struct mfs_read *read = (struct mfs_read *)msg->data;
	struct mfs_ioc_ra ra;
	int fd = msg->fd;
	int ret;

	ra.off = read->off;
	ra.len = read->len;

	ret = ioctl(fd, MFS_IOC_RA, &ra);
	if (ret)
		perror("ioctl MFS_IOC_RA failed");

	return ret;
}

static int process_remote_read(struct mfs_msg *msg)
{
	struct mfs_ioc_rpath *rpath;
	struct mfs_ioc_done done;
	int fd = msg->fd;
	int ret;

	rpath = malloc(sizeof(struct mfs_ioc_rpath) + 1024);

	if (!rpath) {
		pr_err("malloc for path failed\n");
		return -1;
	}
	rpath->max = 1024;
	ret = ioctl(fd, MFS_IOC_RPATH, (unsigned long)rpath);
	if (ret) {
		free(rpath);
		perror("ioctl failed");
		return -1;
	}
	free(rpath);

	done.id = msg->id;
	done.ret = 0;
	ret = ioctl(fd, MFS_IOC_DONE, (unsigned long)&done);
	if (ret)
		perror("failed to ioctl MFS_IOC_DONE");

	return ret;
}

static int process_read(struct mfs_msg *msg)
{
	int ret;

	if (mfs_mode == MFS_MODE_REMOTE)
		ret = process_remote_read(msg);
	else if (mfs_mode == MFS_MODE_LOCAL)
		ret = process_local_read(msg);
	else
		ret = -EINVAL;
	return ret;
}

static int process_req(int fd)
{
	char buf[1024];
	struct mfs_msg *msg;
	int ret;

	memset(buf, 0, sizeof(buf));
	ret = read(fd, buf, sizeof(buf));
	if (ret <= 0) {
		if (ret < 0)
			pr_err("read failed, ret:%d\n", ret);
		return -1;
	}

	msg = (void *)buf;
	if (ret != msg->len) {
		pr_err("invalid message length, read:%d, need:%d\n", ret, msg->len);
		return -1;
	}
	if (msg->opcode == MFS_OP_READ || msg->opcode == MFS_OP_FAULT)
		return process_read(msg);
	if (msg->opcode == MFS_OP_CLOSE)
		return process_close(msg);

	pr_err("invalid opcode:%d\n", msg->opcode);
	return -1;
}

static void ioctl_mfs_mode(int fd)
{
	struct mfs_ioc_fsinfo fsinfo = {0};
	int ret;

	ret = ioctl(fd, MFS_IOC_FSINFO, (unsigned long)&fsinfo);
	if (ret < 0) {
		perror("failed to ioctl mfs_ioc_fsinfo");
		close(fd);
		exit(-1);
	}

	mfs_mode = fsinfo.mode;
}

int main(int argc, char *argv[])
{
	struct pollfd pfd;
	struct statfs buf;
	char *mountpoint;
	char devname[10];
	int fd, ret;

	if (argc != 2) {
		printf("./mfsd ${mfs_mountpoint}\n");
		return -1;
	}
	mountpoint = argv[1];

	ret = statfs(mountpoint, &buf);
	if (ret) {
		pr_err("statfs %s failed\n", mountpoint);
		return -1;
	}
	if (buf.f_type != MFS_SUPER_MAGIC) {
		pr_err("fstype(%lx) is invalid, please check the mountpoint\n", buf.f_type);
		return -1;
	}

	sprintf(devname, "/dev/mfs%ld", buf.f_spare[0]);
	fd = open(devname, O_RDWR);
	if (fd < 0) {
		pr_err("open %s failed\n", devname);
		return -1;
	}

	ioctl_mfs_mode(fd);
	pfd.fd = fd;
	pfd.events = POLLIN;

	while (1) {
		ret = poll(&pfd, 1, -1);
		if (ret < 0) {
			pr_err("poll failed\n");
			return -1;
		}

		if (ret == 0 || !(pfd.revents & POLLIN)) {
			pr_err("poll event error, ret:%d, revents:%x\n", ret, pfd.revents);
			break;
		}

		if (process_req(fd) == -1)
			pr_err("process req failed, errcode:%d\n", errno);
	}
	return 0;
}