#include <sys/mman.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <ctype.h>
#include <dirent.h>
#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <libqrtr.h>
#include <limits.h>
#include <signal.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include "qmi_rmtfs.h"
#include "util.h"
#include "rmtfs.h"

#define RMTFS_QMI_SERVICE	14
#define RMTFS_QMI_VERSION	1
#define RMTFS_QMI_INSTANCE	0

static struct rmtfs_mem *rmem;
static sig_atomic_t sig_int_count;

static bool dbgprintf_enabled;
static void dbgprintf(const char *fmt, ...)
{
	va_list ap;

	if (!dbgprintf_enabled)
		return;

	va_start(ap, fmt);
	vprintf(fmt, ap);
	va_end(ap);
}

static void qmi_result_error(struct rmtfs_qmi_result *result, unsigned error)
{
	/* Only propagate initial error */
	if (result->result == QMI_RMTFS_RESULT_FAILURE)
		return;

	result->result = QMI_RMTFS_RESULT_FAILURE;
	result->error = error;
}

static void rmtfs_open(int sock, const struct qrtr_packet *pkt)
{
	struct rmtfs_open_resp resp = {};
	struct rmtfs_open_req req = {};
	DEFINE_QRTR_PACKET(resp_buf, 256);
	struct rmtfd *rmtfd;
	unsigned int txn;
	ssize_t len;
	int caller_id = -1;
	int ret;

	ret = qmi_decode_message(&req, &txn, pkt, QMI_REQUEST,
				 QMI_RMTFS_OPEN, rmtfs_open_req_ei);
	if (ret < 0) {
		qmi_result_error(&resp.result, QMI_RMTFS_ERR_MALFORMED_MSG);
		goto respond;
	}

	rmtfd = storage_open(pkt->node, req.path);
	if (!rmtfd) {
		qmi_result_error(&resp.result, QMI_RMTFS_ERR_INTERNAL);
		goto respond;
	}

	caller_id = storage_get_caller_id(rmtfd);
	resp.caller_id = caller_id;
	resp.caller_id_valid = true;

respond:
	dbgprintf("[RMTFS] open %s => %d (%d:%d)\n",
		  req.path, caller_id, resp.result.result, resp.result.error);

	len = qmi_encode_message(&resp_buf,
				 QMI_RESPONSE, QMI_RMTFS_OPEN, txn, &resp,
				 rmtfs_open_resp_ei);
	if (len < 0) {
		fprintf(stderr, "[RMTFS] failed to encode open-response: %s\n",
			strerror(-len));
		return;
	}

	ret = qrtr_sendto(sock, pkt->node, pkt->port,
			  resp_buf.data, resp_buf.data_len);
	if (ret < 0)
		fprintf(stderr, "[RMTFS] failed to send open-response: %s\n",
			strerror(-ret));
}

static void rmtfs_close(int sock, const struct qrtr_packet *pkt)
{
	struct rmtfs_close_resp resp = {};
	struct rmtfs_close_req req = {};
	DEFINE_QRTR_PACKET(resp_buf, 256);
	struct rmtfd *rmtfd;
	unsigned int txn;
	ssize_t len;
	int ret;

	ret = qmi_decode_message(&req, &txn, pkt, QMI_REQUEST,
				 QMI_RMTFS_CLOSE, rmtfs_close_req_ei);
	if (ret < 0) {
		qmi_result_error(&resp.result, QMI_RMTFS_ERR_MALFORMED_MSG);
		goto respond;
	}

	rmtfd = storage_get(pkt->node, req.caller_id);
	if (!rmtfd) {
		qmi_result_error(&resp.result, QMI_RMTFS_ERR_INTERNAL);
		goto respond;
	}

	storage_close(rmtfd);
	rmtfs_mem_free(rmem);

respond:
	dbgprintf("[RMTFS] close %s => %d (%d:%d)\n",
		  req.caller_id, resp.result.result, resp.result.error);

	len = qmi_encode_message(&resp_buf,
				 QMI_RESPONSE, QMI_RMTFS_CLOSE, txn, &resp,
				 rmtfs_close_resp_ei);
	if (len < 0) {
		fprintf(stderr, "[RMTFS] failed to encode close-response: %s\n",
			strerror(-len));
		return;
	}

	ret = qrtr_sendto(sock, pkt->node, pkt->port,
			  resp_buf.data, resp_buf.data_len);
	if (ret < 0)
		fprintf(stderr, "[RMTFS] failed to send close-response: %s\n",
			strerror(-ret));
}

static void rmtfs_iovec(int sock, struct qrtr_packet *pkt)
{
	struct rmtfs_iovec_entry *entries;
	struct rmtfs_iovec_resp resp = {};
	struct rmtfs_iovec_req req = {};
	DEFINE_QRTR_PACKET(resp_buf, 256);
	struct rmtfd *rmtfd;
	uint32_t caller_id = 0;
	size_t num_entries = 0;
	off_t sector_base;
	uint8_t is_write;
	off_t phys_base;
	uint8_t force = 0;
	unsigned txn;
	off_t offset;
	ssize_t len;
	ssize_t n;
	char buf[SECTOR_SIZE];
	int ret;
	int i;
	int j;

	ret = qmi_decode_message(&req, &txn, pkt, QMI_REQUEST,
				 QMI_RMTFS_RW_IOVEC, rmtfs_iovec_req_ei);
	if (ret < 0) {
		qmi_result_error(&resp.result, QMI_RMTFS_ERR_MALFORMED_MSG);
		goto respond;
	}

	caller_id = req.caller_id;
	is_write = req.direction;
	entries = req.iovec;
	num_entries = req.iovec_len;
	force = req.is_force_sync;

	rmtfd = storage_get(pkt->node, caller_id);
	if (!rmtfd) {
		fprintf(stderr, "[RMTFS] iovec request for non-existing caller\n");
		qmi_result_error(&resp.result, QMI_RMTFS_ERR_INTERNAL);
		goto respond;
	}

	for (i = 0; i < num_entries; i++) {
		phys_base = entries[i].phys_offset;
		sector_base = entries[i].sector_addr * SECTOR_SIZE;
		offset = 0;

		for (j = 0; j < entries[i].num_sector; j++) {
			if (is_write) {
				n = rmtfs_mem_read(rmem, phys_base + offset, buf, SECTOR_SIZE);
				if (n == SECTOR_SIZE)
					n = storage_pwrite(rmtfd, buf, n, sector_base + offset);
			} else {
				n = storage_pread(rmtfd, buf, SECTOR_SIZE, sector_base + offset);
				if (n >= 0) {
					if (n < SECTOR_SIZE)
						memset(buf + n, 0, SECTOR_SIZE - n);
					n = rmtfs_mem_write(rmem, phys_base + offset, buf, SECTOR_SIZE);
				}
			}

			if (n != SECTOR_SIZE) {
				fprintf(stderr, "[RMTFS] failed to %s sector %d\n",
					is_write ? "write" : "read", entries[i].sector_addr + j);
				qmi_result_error(&resp.result, QMI_RMTFS_ERR_INTERNAL);
				goto respond;
			}

			offset += SECTOR_SIZE;
		}
	}

respond:
	dbgprintf("[RMTFS] iovec %d, %sforced => (%d:%d)\n", caller_id, force ? "" : "not ",
							     resp.result.result, resp.result.error);

	if (is_write)
		storage_sync(rmtfd);

	for (i = 0; i < num_entries; i++) {
		dbgprintf("[RMTFS]       %s %d:%d 0x%x\n", is_write ? "write" : "read",
							   entries[i].sector_addr,
							   entries[i].num_sector,
							   entries[i].phys_offset);
	}

	len = qmi_encode_message(&resp_buf,
				 QMI_RESPONSE, QMI_RMTFS_RW_IOVEC, txn, &resp,
				 rmtfs_iovec_resp_ei);
	if (len < 0) {
		fprintf(stderr, "[RMTFS] failed to encode iovec-response: %s\n",
			strerror(-len));
		return;
	}

	ret = qrtr_sendto(sock, pkt->node, pkt->port,
			  resp_buf.data, resp_buf.data_len);
	if (ret < 0)
		fprintf(stderr, "[RMTFS] failed to send iovec-response: %s\n",
			strerror(-ret));
}

static void rmtfs_alloc_buf(int sock, struct qrtr_packet *pkt)
{
	struct rmtfs_alloc_buf_resp resp = {};
	struct rmtfs_alloc_buf_req req = {};
	DEFINE_QRTR_PACKET(resp_buf, 256);
	uint32_t alloc_size = 0;
	uint32_t caller_id = 0;
	int64_t address = 0;
	unsigned txn;
	ssize_t len;
	int ret;

	ret = qmi_decode_message(&req, &txn, pkt, QMI_REQUEST,
				 QMI_RMTFS_ALLOC_BUFF, rmtfs_alloc_buf_req_ei);
	if (ret < 0) {
		qmi_result_error(&resp.result, QMI_RMTFS_ERR_MALFORMED_MSG);
		goto respond;
	}

	caller_id = req.caller_id;
	alloc_size = req.buff_size;

	address = rmtfs_mem_alloc(rmem, alloc_size);
	if (address < 0) {
		qmi_result_error(&resp.result, QMI_RMTFS_ERR_INTERNAL);
		goto respond;
	}

	resp.buff_address = address;
	resp.buff_address_valid = true;
respond:
	dbgprintf("[RMTFS] alloc %d, %d => 0x%lx (%d:%d)\n", caller_id, alloc_size, address, resp.result.result, resp.result.error);

	len = qmi_encode_message(&resp_buf,
				 QMI_RESPONSE, QMI_RMTFS_ALLOC_BUFF, txn, &resp,
				 rmtfs_alloc_buf_resp_ei);
	if (len < 0) {
		fprintf(stderr, "[RMTFS] failed to encode alloc-buf-response: %s\n",
			strerror(-len));
		return;
	}

	ret = qrtr_sendto(sock, pkt->node, pkt->port,
			  resp_buf.data, resp_buf.data_len);
	if (ret < 0)
		fprintf(stderr, "[RMTFS] failed to send alloc-buf-response: %s\n",
			strerror(-ret));
}

static void rmtfs_get_dev_error(int sock, struct qrtr_packet *pkt)
{
	struct rmtfs_dev_error_resp resp = {};
	struct rmtfs_dev_error_req req = {};
	DEFINE_QRTR_PACKET(resp_buf, 256);
	struct rmtfd *rmtfd;
	unsigned txn;
	ssize_t len;
	int ret;

	ret = qmi_decode_message(&req, &txn, pkt, QMI_REQUEST,
				 QMI_RMTFS_GET_DEV_ERROR,
				 rmtfs_dev_error_req_ei);
	if (ret < 0) {
		qmi_result_error(&resp.result, QMI_RMTFS_ERR_MALFORMED_MSG);
		goto respond;
	}

	rmtfd = storage_get(pkt->node, req.caller_id);
	if (rmtfd) {
		qmi_result_error(&resp.result, QMI_RMTFS_ERR_INTERNAL);
		goto respond;
	}

	resp.status = storage_get_error(rmtfd);
	resp.status_valid = true;

respond:
	dbgprintf("[RMTFS] dev_error %d => %d (%d:%d)\n", req.caller_id, resp.status, resp.result.result, resp.result.error);

	len = qmi_encode_message(&resp_buf,
				 QMI_RESPONSE, QMI_RMTFS_GET_DEV_ERROR, txn,
				 &resp, rmtfs_dev_error_resp_ei);
	if (len < 0) {
		fprintf(stderr, "[RMTFS] failed to encode dev-error-response: %s\n",
			strerror(-len));
		return;
	}

	ret = qrtr_sendto(sock, pkt->node, pkt->port,
			  resp_buf.data, resp_buf.data_len);
	if (ret < 0)
		fprintf(stderr, "[RMTFS] failed to send dev-error-response: %s\n",
			strerror(-ret));
}

static int rmtfs_bye(uint32_t node)
{
	dbgprintf("[RMTFS] bye from %d\n", node);

	return 0;
}

static int rmtfs_del_client(uint32_t node, uint32_t port)
{
	dbgprintf("[RMTFS] del_client %d:%d\n", node, port);

	return 0;
}

static int handle_rmtfs(int sock)
{
	struct sockaddr_qrtr sq;
	struct qrtr_packet pkt;
	unsigned int msg_id;
	socklen_t sl;
	char buf[4096];
	int ret;

	sl = sizeof(sq);
	ret = recvfrom(sock, buf, sizeof(buf), 0, (void *)&sq, &sl);
	if (ret < 0) {
		ret = -errno;
		if (ret != -ENETRESET)
			fprintf(stderr, "[RMTFS] recvfrom failed: %d\n", ret);
		return ret;
	}

	dbgprintf("[RMTFS] packet; from: %d:%d\n", sq.sq_node, sq.sq_port);

	ret = qrtr_decode(&pkt, buf, ret, &sq);
	if (ret < 0) {
		fprintf(stderr, "[RMTFS] unable to decode qrtr packet\n");
		return ret;
	}

	switch (pkt.type) {
	case QRTR_TYPE_BYE:
		return rmtfs_bye(pkt.node);
	case QRTR_TYPE_DEL_CLIENT:
		return rmtfs_del_client(pkt.node, pkt.port);
	case QRTR_TYPE_DATA:
		ret = qmi_decode_header(&pkt, &msg_id);
		if (ret < 0)
			return ret;

		switch (msg_id) {
		case QMI_RMTFS_OPEN:
			rmtfs_open(sock, &pkt);
			break;
		case QMI_RMTFS_CLOSE:
			rmtfs_close(sock, &pkt);
			break;
		case QMI_RMTFS_RW_IOVEC:
			rmtfs_iovec(sock, &pkt);
			break;
		case QMI_RMTFS_ALLOC_BUFF:
			rmtfs_alloc_buf(sock, &pkt);
			break;
		case QMI_RMTFS_GET_DEV_ERROR:
			rmtfs_get_dev_error(sock, &pkt);
			break;
		default:
			fprintf(stderr, "[RMTFS] Unknown request: %d\n", msg_id);
			break;
		}

		return 0;
	}

	return ret;
}

static int sig_int_count;

static int run_rmtfs(int rprocfd)
{
	bool sig_int_handled = false;
	int rmtfs_fd;
	fd_set rfds;
	char done;
	int nfds;
	int ret;

	rmtfs_fd = qrtr_open(RMTFS_QMI_SERVICE);
	if (rmtfs_fd < 0) {
		fprintf(stderr, "failed to create qrtr socket\n");
		return rmtfs_fd;
	}

	dbgprintf("registering services\n");

	ret = qrtr_publish(rmtfs_fd, RMTFS_QMI_SERVICE,
			   RMTFS_QMI_VERSION, RMTFS_QMI_INSTANCE);
	if (ret < 0) {
		fprintf(stderr, "failed to publish rmtfs service");
		return ret;
	}

	if (rprocfd >= 0)
		rproc_start();

	for (;;) {
		if (sig_int_count == 1 && !sig_int_handled) {
			if (rprocfd < 0)
				break;

			rproc_stop();
			sig_int_handled = true;
		} else if (sig_int_count > 1) {
			break;
		}

		FD_ZERO(&rfds);
		FD_SET(rmtfs_fd, &rfds);
		if (rprocfd >= 0)
			FD_SET(rprocfd, &rfds);
		nfds = MAX(rmtfs_fd, rprocfd) + 1;

		ret = select(nfds, &rfds, NULL, NULL, NULL);
		if (ret < 0 && errno != EINTR)
			break;
		else if (ret < 0 && errno == EINTR)
			continue;

		if (rprocfd >= 0 && FD_ISSET(rprocfd, &rfds)) {
			ret = read(rprocfd, &done, 1);
			if (!ret || done == 'Y')
				break;
		}

		if (FD_ISSET(rmtfs_fd, &rfds)) {
			ret = handle_rmtfs(rmtfs_fd);
			if (ret == -ENETRESET)
				break;
		}
	}

	close(rmtfs_fd);

	return ret;
}

static void sig_int_handler(int signo)
{
	sig_int_count++;
}

int main(int argc, char **argv)
{
	struct sigaction action;
	bool use_partitions = false;
	bool read_only = false;
	int rprocfd = -1;
	int ret;
	int option;
	const char *storage_root = NULL;

	while ((option = getopt(argc, argv, "o:Prsv")) != -1) {
		switch (option) {
		/*
		 * -o sets the directory where EFS images are stored,
		 * or sets the directory from where raw EFS partitions
		 * can be picked by-name when used with -P option.
		 */
		case 'o':
			storage_root = optarg;
			break;

		/* -P to find and use raw EFS partitions */
		case 'P':
			use_partitions = true;
			break;

		/* -r to avoid writing to storage */
		case 'r':
			read_only = true;
			break;

		/* enable sync for the mss rproc instance */
		case 's':
			rprocfd = rproc_init();
			if (rprocfd < 0) {
				fprintf(stderr, "Failed to get rprocfd\n");
				return 1;
			}

			break;

		/* -v is for verbose */
		case 'v':
			dbgprintf_enabled = 1;
			break;

		case '?':
			fprintf(stderr, "Unknown option: -%c\n", option);
			return 1;
		}
	}

	sigemptyset(&action.sa_mask);
	action.sa_handler = sig_int_handler;
	action.sa_flags = 0;

	sigaction(SIGINT, &action, NULL);
	sigaction(SIGTERM, &action, NULL);

	rmem = rmtfs_mem_open();
	if (!rmem)
		return 1;

	ret = storage_init(storage_root, read_only, use_partitions);
	if (ret) {
		fprintf(stderr, "failed to initialize storage system\n");
		goto close_rmtfs_mem;
	}

	do {
		ret = run_rmtfs(rprocfd);
	} while (ret == -ENETRESET);

	storage_exit();
close_rmtfs_mem:
	rmtfs_mem_close(rmem);

	return 0;
}
