/*
 * Copyright (c) 2011-2012 - Mauro Carvalho Chehab
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation version 2
 * of the License.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 * Or, point your browser to http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
 *
 */

#define _XOPEN_SOURCE 600

#define _FILE_OFFSET_BITS 64
#define _LARGEFILE_SOURCE 1
#define _LARGEFILE64_SOURCE 1

#ifdef HAVE_BACKTRACE
#include <execinfo.h>
#endif

#include <argp.h>
#include <endian.h>
#include <netinet/in.h>
#include <poll.h>
#include <pthread.h>
#include <search.h>
#include <signal.h>
#include <stdarg.h>
#include <stdlib.h>
#include <stdio.h>
#include <signal.h>
#include <syslog.h>
#include <sys/socket.h>
#include <unistd.h>

#include <netdb.h>
#include <netinet/in.h>
#include <netinet/tcp.h>

#include "../../lib/libdvbv5/dvb-fe-priv.h"
#include "../../lib/libdvbv5/dvb-dev-priv.h"
#include "libdvbv5/dvb-file.h"
#include "libdvbv5/dvb-dev.h"

#ifdef ENABLE_NLS
# define _(string) gettext(string)
# include "gettext.h"
# include <locale.h>
# include <langinfo.h>
# include <iconv.h>
#else
# define _(string) string
#endif

# define N_(string) string

/* Max number of open files */
#define NUM_FOPEN	1024

/*
 * Argument processing data and logic
 */

#define PROGRAM_NAME	"dvbv5-daemon"

const char *argp_program_version = PROGRAM_NAME " version " V4L_UTILS_VERSION;
const char *argp_program_bug_address = "Mauro Carvalho Chehab <mchehab@kernel.org>";

#define CMD_SIZE	80

static const char doc[] = N_(
	"\nA DVB remote daemon using API version 5\n");

static const struct argp_option options[] = {
	{"verbose",	'v',	0,		0,	N_("enables debug messages"), 0},
	{"port",	'p',	"5555",		0,	N_("port to listen"), 0},
	{"help",        '?',	0,		0,	N_("Give this help list"), -1},
	{"usage",	-3,	0,		0,	N_("Give a short usage message")},
	{"version",	'V',	0,		0,	N_("Print program version"), -1},
	{ 0, 0, 0, 0, 0, 0 }
};

static int port = 0;
static int verbose = 0;

static error_t parse_opt(int k, char *arg, struct argp_state *state)
{
	switch (k) {
	case 'p':
		port = atoi(arg);
		break;
	case 'v':
		verbose	++;
		break;
	case '?':
		argp_state_help(state, state->out_stream,
				ARGP_HELP_SHORT_USAGE | ARGP_HELP_LONG
				| ARGP_HELP_DOC);
		fprintf(state->out_stream, _("\nReport bugs to %s.\n"), argp_program_bug_address);
		exit(0);
	case 'V':
		fprintf (state->out_stream, "%s\n", argp_program_version);
		exit(0);
	case -3:
		argp_state_help(state, state->out_stream, ARGP_HELP_USAGE);
		exit(0);
	default:
		return ARGP_ERR_UNKNOWN;
	}
	return 0;
}

static struct argp argp = {
	.options = options,
	.parser = parse_opt,
	.doc = doc,
};

/*
 * Local log
 *
 * Error messages should ideally be sent to the remote end, but, if
 * an error happens during the processing of a packet, we need to
 * report locally, as reporting remotely may not work.
 */
static const struct loglevel {
	const char *name;
	const char *color;
	int fd;
} loglevels[9] = {
	{N_("EMERG    "), "\033[31m", STDERR_FILENO },
	{N_("ALERT    "), "\033[31m", STDERR_FILENO },
	{N_("CRITICAL "), "\033[31m", STDERR_FILENO },
	{N_("ERROR    "), "\033[31m", STDERR_FILENO },
	{N_("WARNING  "), "\033[33m", STDOUT_FILENO },
	{NULL,            "\033[36m", STDOUT_FILENO }, /* NOTICE */
	{NULL,            NULL,       STDOUT_FILENO }, /* INFO */
	{N_("DEBUG    "), "\033[32m", STDOUT_FILENO },
	{NULL,            "\033[0m",  STDOUT_FILENO }, /* reset*/
};
#define LOG_COLOROFF 8

void local_log(int level, const char *fmt, ...)
{
	if(level > sizeof(loglevels) / sizeof(struct loglevel) - 2) // ignore LOG_COLOROFF as well
		level = LOG_INFO;
	va_list ap;
	va_start(ap, fmt);
	FILE *out = stdout;
	if (STDERR_FILENO == loglevels[level].fd)
		out = stderr;
	if (loglevels[level].color && isatty(loglevels[level].fd))
		fputs(loglevels[level].color, out);
	if (loglevels[level].name)
		fprintf(out, "%s", _(loglevels[level].name));
	vfprintf(out, fmt, ap);
	fprintf(out, "\n");
	if(loglevels[level].color && isatty(loglevels[level].fd))
		fputs(loglevels[LOG_COLOROFF].color, out);
	va_end(ap);
}

#define info(fmt, arg...) do {\
	local_log(LOG_INFO, fmt, ##arg); \
} while (0)

#define warn(fmt, arg...) do {\
	local_log(LOG_WARNING, fmt, ##arg); \
} while (0)

#define err(fmt, arg...) do {\
	local_log(LOG_ERR, "%s: " fmt, __FUNCTION__, ##arg); \
} while (0)

#define dbg(fmt, arg...) do {\
	local_log(LOG_DEBUG, "%s: " fmt, __FUNCTION__, ##arg); \
} while (0)

#define local_perror(msg) do {\
	local_log(LOG_ERR, "%s: %s: %m (%d)",  __FUNCTION__, msg, errno); \
} while (0)

/*
 * Static data used by the code
 */

static pthread_mutex_t msg_mutex;
static pthread_mutex_t dvb_read_mutex;
static pthread_t read_id = 0;

struct dvb_descriptors {
	int uid;
	struct dvb_open_descriptor *open_dev;
};

static struct dvb_device *dvb = NULL;
static void *desc_root = NULL;
static int dvb_fd = -1;

static struct pollfd fds[NUM_FOPEN];
static nfds_t numfds = 0;

static char output_charset[256] = "utf-8";
static char default_charset[256] = "iso-8859-1";

static void stack_dump(void)
{
#ifdef HAVE_BACKTRACE
	int i, nptrs = 0;
	void *buffer[10];
	char **strings = NULL;

	nptrs = backtrace(buffer, sizeof(buffer));

	if (nptrs) {
		strings = backtrace_symbols(buffer, nptrs);
		dbg("Stack:");
	}

	for (i = 0; i < nptrs; i++)
		dbg("   %s", strings[i]);

	free(strings);
#endif
}

/*
 * Open dev descriptor handling
 */

static int dvb_desc_compare(const void *__a, const void *__b)
{
	const struct dvb_descriptors *a = __a, *b = __b;

	return (b->uid - a->uid);
}

static struct dvb_open_descriptor *get_open_dev(int uid)
{
	struct dvb_descriptors desc, **p;

	if (!desc_root)
		return NULL;

	desc.uid = uid;
	p = tfind(&desc, &desc_root, dvb_desc_compare);

	if (!p) {
		err("open element not retrieved!");
		return NULL;
	}

	return (*p)->open_dev;
}

static void destroy_open_dev(int uid)
{
	struct dvb_descriptors desc, **p;

	desc.uid = uid;
	p = tdelete(&desc, &desc_root, dvb_desc_compare);
	if (!p)
		err("can't destroy opened element");
}

static void free_opendevs(void *node)
{
	struct dvb_descriptors *desc = node;

	if (verbose)
		dbg("closing dev %p", desc, desc->open_dev);

	dvb_dev_close(desc->open_dev);
	free (desc);
}

static void close_all_devs(void)
{
	dvb_fd = -1;
	numfds = 0;
	tdestroy(desc_root, free_opendevs);

	desc_root = NULL;
}

/*
 * Signal handling logic
 */

static void sigterm_handler(int const sig_class)
{
	info(PROGRAM_NAME" interrupted.");

	pthread_exit(NULL);

	close_all_devs();
	dvb_dev_free(dvb);
}

static void start_signal_handler(void)
{
	struct sigaction action;

	sigemptyset(&action.sa_mask);
	action.sa_flags = 0;
	action.sa_handler = sigterm_handler;
	sigaction(SIGTERM, &action, NULL);
}

static void stop_signal_handler(void)
{
	struct sigaction action;

	sigemptyset(&action.sa_mask);
	action.sa_flags = 0;
	action.sa_handler = SIG_DFL;
	sigaction(SIGTERM, &action, NULL);
}

/*
 * Functions to send/receive messages to the client
 */

static ssize_t __prepare_data(char *buf, const size_t size,
			      const char *fmt, va_list ap)
{
	char *p = buf, *endp = &buf[size], *s;
	int len;
	int32_t i32;
	uint64_t u64;

	while (*fmt && *fmt != '%') fmt++;
	if (*fmt == '%') fmt++;
	while (*fmt) {
		switch (*fmt++) {
		case 's':              /* string */
			s = va_arg(ap, char *);
			if (s)
				len = strlen(s);
			else
				len = 0;
			if (p + len + 4 > endp) {
				dbg("buffer to short for string");
				stack_dump();
				return -1;
			}
			i32 = htobe32(len);
			memcpy(p, &i32, 4);
			p += 4;
			if (s)
				memcpy(p, s, len);
			p += len;
			break;
		case 'p':              /* binary data with specified length */
			s = va_arg(ap, char *);
			len = va_arg(ap, ssize_t);
			if (p + len + 4 > endp) {
				dbg("buffer to short for string");
				stack_dump();
				return -1;
			}
			i32 = htobe32(len);
			memcpy(p, &i32, 4);
			p += 4;
			memcpy(p, s, len);
			p += len;
			break;
		case 'i':              /* 32-bit int */
			if (p + 4 > endp) {
				dbg("buffer to short for int32_t");
				stack_dump();
				return -1;
			}

			i32 = htobe32(va_arg(ap, int32_t));
			memcpy(p, &i32, 4);
			p += 4;
			break;
		case 'l':              /* 64-bit unsigned int */
			if (*fmt++ != 'u') {
				dbg("invalid long format character: '%c'", *fmt);
				stack_dump();
				break;
			}
			if (p + 8 > endp) {
				dbg("buffer to short for uint64_t");
				stack_dump();
				return -1;
			}
			u64 = htobe64(va_arg(ap, uint64_t));
			memcpy(p, &u64, 8);
			p += 8;
			break;
		default:
			dbg("invalid format character: '%c'", *fmt);
			stack_dump();
		}
		while (*fmt && *fmt != '%') fmt++;
		if (*fmt == '%') fmt++;
	}
	return p - buf;
}


static ssize_t prepare_data(char *buf, const size_t size,
			      const char *fmt, ...)
	__attribute__ (( format( printf, 3, 4 )));

static ssize_t prepare_data(char *buf, const size_t size,
			      const char *fmt, ...)
{
	va_list ap;
	int ret;

	va_start(ap, fmt);
	ret = __prepare_data(buf, size, fmt, ap);
	va_end(ap);

	return ret;
}

static int send_buf(int fd, const char *buf, size_t size)
{
	int ret;
	int32_t i32;

	if (fd < 0)
		return ECONNRESET;

	pthread_mutex_lock(&msg_mutex);
	i32 = htobe32(size);
	ret = send(fd, (void *)&i32, 4, MSG_MORE);
	if (ret >= 0)
		ret = send(fd, buf, size, 0);
	pthread_mutex_unlock(&msg_mutex);
	if (ret < 0) {
		local_perror("write");
		if (ret == ECONNRESET)
			close_all_devs();

		return errno;
	}

	return ret;
}

static ssize_t send_data(int fd, const char *fmt, ...)
	__attribute__ (( format( printf, 2, 3 )));

static ssize_t send_data(int fd, const char *fmt, ...)
{
	char buf[REMOTE_BUF_SIZE];
	va_list ap;
	int ret;

	if (verbose)
		dbg("called %s(fd, \"%s\", ...)", __FUNCTION__, fmt);

	va_start(ap, fmt);
	ret = __prepare_data(buf, sizeof(buf), fmt, ap);
	va_end(ap);
	if (ret < 0)
		return ret;

	return send_buf(fd, buf, ret);
}

static ssize_t scan_data(char *buf, int buf_size, const char *fmt, ...)
	__attribute__ (( format( scanf, 3, 4 )));

static ssize_t scan_data(char *buf, int buf_size, const char *fmt, ...)
{
	char *p = buf, *endp = &buf[buf_size], *s;
	int len;
	int32_t *i32;
	uint64_t *u64;
	va_list ap;

	va_start(ap, fmt);
	while (*fmt && *fmt != '%') fmt++;
	if (*fmt == '%') fmt++;
	while (*fmt) {
		switch (*fmt++) {
		case 's':              /* string */
			s = va_arg(ap, char *);
			if (p + 4 > endp) {
				dbg("buffer to short for string length");
				stack_dump();
				return -1;
			}
			len = be32toh(*(int32_t *)p);
			p += 4;
			if (p + len > endp) {
				dbg("buffer to short for string");
				stack_dump();
				return -1;
			}

			memcpy(s, p, len);
			s[len] = '\0';
			p += len;
			break;
		case 'i':              /* 32-bit int */
			if (p + 4 > endp) {
				dbg("buffer to short for int32_t");
				stack_dump();
				return -1;
			}
			i32 = va_arg(ap, int32_t *);

			*i32 = be32toh(*(int32_t *)p);
			p += 4;
			break;
		case 'l':              /* 64-bit unsigned int */
			if (*fmt++ != 'u') {
				dbg("invalid long format character: '%c'", *fmt);
				stack_dump();
				break;
			}
			if (p + 8 > endp) {
				dbg("buffer to short for uint64_t");
				stack_dump();
				return -1;
			}
			u64 = va_arg(ap, uint64_t *);

			*u64 = be32toh(*(uint64_t *)p);
			p += 8;
			break;
		default:
			dbg("invalid format character: '%c'", *fmt);
			stack_dump();
		}
		while (*fmt && *fmt != '%') fmt++;
		if (*fmt == '%') fmt++;
	}
	va_end(ap);

	return p - buf;
}

/*
 * Remote log
 */
void dvb_remote_log(void *priv, int level, const char *fmt, ...)
{
	int ret;
	char *buf;

	va_list ap;
	int fd = *(int*)priv;

	va_start(ap, fmt);
	ret = vasprintf(&buf, fmt, ap);
	if (ret <= 0) {
		local_perror("vasprintf");
		return;
	}

	va_end(ap);

	if (fd >= 0)
		send_data(fd, "%i%s%i%s", 0, "log", level, buf);
	else
		local_log(level, buf);

	free(buf);
}

static int dev_change_monitor(char *sysname,
			      enum dvb_dev_change_type type, void *user_priv)
{
	send_data(dvb_fd, "%i%s%i%s", 0, "dev_change", type, sysname);

	return 0;
}


/*
 * command handler methods
 */
static int daemon_get_version(uint32_t seq, char *cmd, int fd,
			      char *buf, ssize_t size)
{
	int ret = 0;

	return send_data(fd, "%i%s%i%s", seq, cmd, ret, argp_program_version);
}

static int dev_find(uint32_t seq, char *cmd, int fd, char *buf, ssize_t size)
{
	int enable_monitor = 0, ret;
	dvb_dev_change_t handler = NULL;

	ret = scan_data(buf, size, "%i", &enable_monitor);
	if (ret < 0)
		goto error;

	if (enable_monitor)
		handler = &dev_change_monitor;

	ret = dvb_dev_find(dvb, handler, NULL);

error:
	return send_data(fd, "%i%s%i", seq, cmd, ret);
}

static int dev_stop_monitor(uint32_t seq, char *cmd, int fd,
			    char *buf, ssize_t size)
{
	dvb_dev_stop_monitor(dvb);

	return send_data(fd, "%i%s%i", seq, cmd, 0);
}

static int dev_seek_by_adapter(uint32_t seq, char *cmd, int fd,
			       char *buf, ssize_t size)
{
	struct dvb_dev_list *dev;
	int adapter, num, type, ret;

	ret = scan_data(buf, size, "%i%i%i", &adapter, &num, &type);
	if (ret < 0)
		goto error;

	dev = dvb_dev_seek_by_adapter(dvb, adapter, num, type);
	if (!dev)
		goto error;

	return send_data(fd, "%i%s%i%s%s%s%i%s%s%s%s%s", seq, cmd, ret,
			 dev->syspath, dev->path, dev->sysname, dev->dvb_type,
			 dev->bus_addr, dev->bus_id, dev->manufacturer,
			 dev->product, dev->serial);
error:
	return send_data(fd, "%i%s%i", seq, cmd, ret);
}

static int dev_get_dev_info(uint32_t seq, char *cmd, int fd,
			       char *buf, ssize_t size)
{
	struct dvb_dev_list *dev;
	char sysname[REMOTE_BUF_SIZE];
	int ret;

	ret = scan_data(buf, size, "%s", sysname);
	if (ret < 0)
		goto error;

	dev = dvb_get_dev_info(dvb, sysname);
	if (!dev)
		goto error;

	return send_data(fd, "%i%s%i%s%s%s%i%s%s%s%s%s", seq, cmd, ret,
			 dev->syspath, dev->path, dev->sysname, dev->dvb_type,
			 dev->bus_addr, dev->bus_id, dev->manufacturer,
			 dev->product, dev->serial);
error:
	return send_data(fd, "%i%s%i", seq, cmd, ret);
}

static void *read_data(void *privdata)
{
	struct dvb_open_descriptor *open_dev;
	int timeout;
	int ret, read_ret = -1, fd, i;
	char databuf[REMOTE_BUF_SIZE];
	char buf[REMOTE_BUF_SIZE + 32], *p;
	size_t size;
	size_t count;
	struct pollfd __fds[NUM_FOPEN];
	nfds_t __numfds;

	timeout = 10; /* ms */
	while (1) {
		pthread_mutex_lock(&dvb_read_mutex);
		if (!numfds) {
			pthread_mutex_unlock(&dvb_read_mutex);
			break;
		}
		__numfds = numfds;
		memcpy(__fds, fds, sizeof(fds));
		pthread_mutex_unlock(&dvb_read_mutex);

		ret = poll(__fds, __numfds, timeout);
		if (!ret)
			continue;
		if (ret < 0) {
			err("poll");
			continue;
		}

		for (i = 0; i < __numfds; i++) {
			if (__fds[i].revents & (POLLERR | POLLHUP | POLLNVAL))
				continue;
			if (__fds[i].revents)
				break;
		}

		/*
		 * it means that one error condition happened.
		 * Likely the file was closed.
		 */
		if (i == __numfds)
			continue;

		fd = __fds[i].fd;

		if (!desc_root)
			break;

		open_dev = get_open_dev(fd);
		if (!open_dev) {
			err("Couldn't find opened file %d", fd);
			continue;
		}

		count = REMOTE_BUF_SIZE;
		read_ret = dvb_dev_read(open_dev, databuf, count);
		if (verbose) {
			if (read_ret < 0)
				dbg("#%d: read error: %d on %p", fd, read_ret, open_dev);
			else
				dbg("#%d: read %ul bytes (count %d)", fd, read_ret, count);
		}

		/* Initialize to the start of the buffer */
		p = buf;
		size = sizeof(buf);

		ret = prepare_data(p, size, "%i%s%i%i", 0, "data_read",
				   read_ret, fd);
		if (ret < 0) {
			err("Failed to prepare answer to dvb_read()");
			break;
		}

		p += ret;
		size -= ret;

		if (read_ret > 0) {
			if (read_ret > size) {
				dbg("buffer to short to store read data!");
				read_ret = -EOVERFLOW;
			} else {
				memcpy(p, databuf, read_ret);
				p += read_ret;
			}
		}

		ret = send_buf(dvb_fd, buf, p - buf);
		if (ret < 0) {
			err("Error %d sending buffer\n", ret);
			if (ret == ECONNRESET) {
				close_all_devs();
				break;
			}
			continue;
		}
	}

	dbg("Finishing kthread");
	read_id = 0;
	return NULL;
}

static int dev_open(uint32_t seq, char *cmd, int fd, char *buf, ssize_t size)
{
	struct dvb_open_descriptor *open_dev;
	struct dvb_dev_list *dev;
	struct dvb_descriptors *desc, **p;
	int ret, flags, uid;
	char sysname[REMOTE_BUF_SIZE];

	desc = calloc(1, sizeof(*desc));
	if (!desc) {
		local_perror("calloc");
		ret = -ENOMEM;
		goto error;
	}

	ret = scan_data(buf, size, "%s%i", sysname, &flags);
	if (ret < 0) {
		free(desc);
		goto error;
	}

	/*
	 * Discard requests for O_NONBLOCK, as the daemon will use threads
	 * to handle unblocked reads.
	 */
	flags &= ~O_NONBLOCK;

	open_dev = dvb_dev_open(dvb, sysname, flags);
	if (!open_dev) {
		ret = -errno;
		free(desc);
		goto error;
	}


	if (verbose)
		dbg("open dev handler for %s: %p with uid#%d", sysname, open_dev, open_dev->fd);

	dev = open_dev->dev;
	if (dev->dvb_type == DVB_DEVICE_DEMUX ||
	    dev->dvb_type == DVB_DEVICE_DVR) {
		pthread_mutex_lock(&dvb_read_mutex);
		fds[numfds].fd = open_dev->fd;
		fds[numfds].events = POLLIN | POLLPRI;
		numfds++;
		pthread_mutex_unlock(&dvb_read_mutex);
	}

	if (!read_id) {
		ret = pthread_create(&read_id, NULL, read_data, NULL);
		if (ret < 0) {
			local_perror("pthread_create");
			pthread_mutex_unlock(&msg_mutex);
			free(desc);
			return -1;
		}
	}

	pthread_mutex_unlock(&msg_mutex);
	uid = open_dev->fd;

	desc->uid = uid;
	desc->open_dev = open_dev;

	/* Add element to the desc_root tree */
	p = tsearch(desc, &desc_root, dvb_desc_compare);
	if (!p) {
		local_perror("tsearch");
		uid = 0;
	} else if (*p != desc) {
		err("uid %d was already opened!", uid);
	}

	pthread_mutex_unlock(&msg_mutex);

	ret = uid;
error:
	return send_data(fd, "%i%s%i", seq, cmd, ret);
}

static int dev_close(uint32_t seq, char *cmd, int fd, char *buf, ssize_t size)
{
	struct dvb_open_descriptor *open_dev;
	int uid, ret, i;

	ret = scan_data(buf, size, "%i",  &uid);
	if (ret < 0)
		goto error;

	open_dev = get_open_dev(uid);
	if (!open_dev) {
		err("Can't find uid to close");
		ret = -1;
		goto error;
	}

	/* Delete fd from the opened array */
	pthread_mutex_lock(&dvb_read_mutex);
	for (i = 0; i < numfds; i++) {
		if (fds[i].fd != open_dev->fd)
		    continue;
		if (i < numfds - 1)
			memmove(&fds[i], &fds[i + 1],
				sizeof(*fds)*(numfds - i - 1));
		numfds--;
		break;
	}
	pthread_mutex_unlock(&dvb_read_mutex);
	if (read_id && !numfds) {
		pthread_cancel(read_id);
		read_id = 0;
	}

	dvb_dev_close(open_dev);
	destroy_open_dev(uid);

error:
	return send_data(fd, "%i%s%i", seq, cmd, ret);
}

static int dev_dmx_stop(uint32_t seq, char *cmd, int fd,
			char *buf, ssize_t size)
{
	struct dvb_open_descriptor *open_dev;
	int uid, ret;

	ret = scan_data(buf, size, "%i",  &uid);
	if (ret < 0)
		goto error;

	open_dev = get_open_dev(uid);
	if (!open_dev) {
		ret = -1;
		err("Can't find uid to stop");
		goto error;
	}

	dvb_dev_dmx_stop(open_dev);

error:
	return send_data(fd, "%i%s%i", seq, cmd, ret);
}

static int dev_set_bufsize(uint32_t seq, char *cmd, int fd,
			   char *buf, ssize_t size)
{
	struct dvb_open_descriptor *open_dev;
	int uid, ret, bufsize;

	ret = scan_data(buf, size, "%i%i",  &uid, &bufsize);
	if (ret < 0)
		goto error;

	open_dev = get_open_dev(uid);
	if (!open_dev) {
		ret = -1;
		err("Can't find uid to stop");
		goto error;
	}

	dvb_dev_set_bufsize(open_dev, bufsize);

error:
	return send_data(fd, "%i%s%i", seq, cmd, ret);
}

static int dev_dmx_set_pesfilter(uint32_t seq, char *cmd, int fd,
				 char *buf, ssize_t size)
{
	struct dvb_open_descriptor *open_dev;
	int uid, ret, pid, type, output, bufsize;

	ret = scan_data(buf, size, "%i%i%i%i%i",
			&uid, &pid, &type, &output, &bufsize);
	if (ret < 0)
		goto error;

	open_dev = get_open_dev(uid);
	if (!open_dev) {
		ret = -1;
		err("Can't find uid to set pesfilter");
		goto error;
	}

	ret = dvb_dev_dmx_set_pesfilter(open_dev, pid, type, output, bufsize);

error:
	return send_data(fd, "%i%s%i", seq, cmd, ret);
}

static int dev_dmx_set_section_filter(uint32_t seq, char *cmd, int fd,
				      char *buf, ssize_t size)
{
	struct dvb_open_descriptor *open_dev;
	int uid, ret, pid, filtsize, flags;
	unsigned char filter[17], mask[17], mode[17];

	ret = scan_data(buf, size, "%i%i%i%s%s%s%i",
			&uid, &pid, &filtsize, filter, mask, mode, &flags);
	if (ret < 0)
		goto error;

	open_dev = get_open_dev(uid);
	if (!open_dev) {
		ret = -1;
		err("Can't find uid to set section filter");
		goto error;
	}

	ret = dvb_dev_dmx_set_section_filter(open_dev, pid, filtsize, filter,
					     mask, mode, flags);

error:
	return send_data(fd, "%i%s%i", seq, cmd, ret);
}

static int dev_dmx_get_pmt_pid(uint32_t seq, char *cmd, int fd,
			       char *buf, ssize_t size)
{
	struct dvb_open_descriptor *open_dev;
	int uid, ret, sid;

	ret = scan_data(buf, size, "%i%i",  &uid, &sid);
	if (ret < 0)
		goto error;

	open_dev = get_open_dev(uid);
	if (!open_dev) {
		ret = -1;
		err("Can't find uid to get PMT PID");
		goto error;
	}

	ret = dvb_dev_dmx_get_pmt_pid(open_dev, sid);

error:
	return send_data(fd, "%i%s%i", seq, cmd, ret);
}

static int dev_scan(uint32_t seq, char *cmd, int fd, char *buf, ssize_t size)
{
	int ret = -1;

	/*
	 * FIXME: There are too many stuff here! Maybe we should
	 * split this into smaller per-table calls.
	 */

#if 0
	struct dvb_open_descriptor *open_dev;
	int uid;

	ret = scan_data(buf, size, "%i%i",  &uid);
	if (ret < 0)
		goto error;

	open_dev = get_open_dev(uid);
	if (!open_dev) {
		ret = -1;
		err("Can't find uid to scan");
		goto error;
	}

	ret = dvb_scan(foo);

error:
	return send_data(fd, "%i%s%i", seq, cmd, ret);
#else
	return send_data(fd, "%i%s%i", seq, cmd, ret);
#endif
}

static int dev_set_sys(uint32_t seq, char *cmd, int fd,
		       char *buf, ssize_t size)
{
	struct dvb_v5_fe_parms_priv *parms = (void *)dvb->fe_parms;
	struct dvb_v5_fe_parms *p = (void *)parms;
	int sys = 0, ret;

	ret = scan_data(buf, size, "%i", &sys);
	if (ret < 0)
		goto error;

	ret = __dvb_set_sys(p, sys);
error:
	return send_data(fd, "%i%s%i", seq, cmd, ret);
}

static int dev_get_parms(uint32_t seq, char *cmd, int fd,
			 char *inbuf, ssize_t insize)
{
	struct dvb_v5_fe_parms_priv *parms = (void *)dvb->fe_parms;
	struct dvb_v5_fe_parms *par = (void *)parms;
	struct dvb_frontend_info *info = &par->info;
	int ret, i;
	char buf[REMOTE_BUF_SIZE], lnb_name[80] = "", *p = buf;
	size_t size = sizeof(buf);

	if (verbose)
		dbg("dev_get_parms called");

	ret = __dvb_fe_get_parms(par);
	if (ret < 0)
		goto error;

	/* Send first the public params */

	ret = prepare_data(p, size, "%i%s%i%s%i%i%i%i%i%i%i", seq, cmd, ret,
			   info->name, info->frequency_min,
			   info->frequency_max, info->frequency_stepsize,
			   info->frequency_tolerance, info->symbol_rate_min,
			   info->symbol_rate_max, info->symbol_rate_tolerance);
	if (ret < 0)
		goto error;

	p += ret;
	size -= ret;

	if (par->lnb)
		strcpy(lnb_name, par->lnb->name);

	ret = prepare_data(p, size, "%i%i%i%i%i%i%i%s%i%i%i%i%s%s",
			   par->version, par->has_v5_stats, par->current_sys,
			   par->num_systems, par->legacy_fe, par->abort,
		           par->lna, lnb_name,
			   par->sat_number, par->freq_bpf, par->diseqc_wait,
			   par->verbose, par->default_charset,
			   par->output_charset);
	if (ret < 0)
		goto error;

	p += ret;
	size -= ret;

	for (i = 0; i < MAX_DELIVERY_SYSTEMS; i++) {
		ret = prepare_data(p, size, "%i", par->systems[i]);
		if (ret < 0)
			goto error;

		p += ret;
		size -= ret;
	}

	/* Now, send the private ones - except for stats */

	ret = prepare_data(p, size, "%i%i%i%i",
			   parms->n_props,
			   parms->country,
			   parms->high_band,
			   parms->freq_offset);
	if (ret < 0)
		goto error;

	p += ret;
	size -= ret;

	for (i = 0; i < parms->n_props; i++) {
		ret = prepare_data(p, size, "%i%i",
				   parms->dvb_prop[i].cmd,
				   parms->dvb_prop[i].u.data);
		if (ret < 0)
			goto error;

		p += ret;
		size -= ret;
	}

	strcpy(output_charset, par->output_charset);
	strcpy(default_charset, par->default_charset);

	return send_buf(fd, buf, p - buf);
error:
	return send_data(fd, "%i%s%i", seq, cmd, ret);
}

static int dev_set_parms(uint32_t seq, char *cmd, int fd,
			 char *buf, ssize_t size)
{
	struct dvb_v5_fe_parms_priv *parms = (void *)dvb->fe_parms;
	struct dvb_v5_fe_parms *par = (void *)parms;
	int ret, i;
	char *p = buf;
	const char *old_lnb = "";
	char new_lnb[256];

	if (verbose)
		dbg("dev_set_parms called");

	/* first the public params that aren't read only */

	/* Get current LNB name */
	if (par->lnb)
		old_lnb = par->lnb->name;

	ret = scan_data(p, size, "%i%i%s%i%i%i%i%s%s",
			&par->abort, &par->lna, new_lnb,
			&par->sat_number, &par->freq_bpf, &par->diseqc_wait,
			&par->verbose, default_charset, output_charset);

	if (ret < 0)
		goto error;

	p += ret;
	size -= ret;

	/* Now, the private ones */

	ret = scan_data(p, size, "%i", &i);
	if (ret < 0)
		goto error;
	parms->country = i;

	p += ret;
	size -= ret;

	for (i = 0; i < parms->n_props; i++) {
		ret = scan_data(p, size, "%i%i",
				&parms->dvb_prop[i].cmd,
				&parms->dvb_prop[i].u.data);
		if (ret < 0)
			goto error;

		p += ret;
		size -= ret;
	}

	if (!*new_lnb) {
		par->lnb = NULL;
	} else if (strcmp(old_lnb, new_lnb)) {
		int lnb = dvb_sat_search_lnb(new_lnb);

		if (lnb < 0) {
			dvb_logerr("Invalid lnb: %s", new_lnb);
			ret = -1;
			goto error;
		}

		par->lnb = dvb_sat_get_lnb(lnb);
	}

	par->output_charset = output_charset;
	par->default_charset = default_charset;

	ret = __dvb_fe_set_parms(par);

error:
	return send_data(fd, "%i%s%i", seq, cmd, ret);
}

static int dev_get_stats(uint32_t seq, char *cmd, int fd,
			 char *inbuf, ssize_t insize)
{
	struct dvb_v5_fe_parms_priv *parms = (void *)dvb->fe_parms;
	struct dvb_v5_stats *st = &parms->stats;
	struct dvb_v5_fe_parms *par = (void *)parms;
	int ret, i;
	char buf[REMOTE_BUF_SIZE], *p = buf;
	size_t size = sizeof(buf);

	if (verbose)
		dbg("dev_get_stats called");

	ret = __dvb_fe_get_stats(par);
	if (ret < 0)
		goto error;

	ret = prepare_data(p, size, "%i%s%i%i", seq, cmd, ret, st->prev_status);
	if (ret < 0)
		goto error;

	p += ret;
	size -= ret;

	for (i = 0; i < DTV_NUM_STATS_PROPS; i++) {
		ret = prepare_data(p, size, "%i%i",
				   st->prop[i].cmd,
				   st->prop[i].u.data);
		if (ret < 0)
			goto error;

		p += ret;
		size -= ret;
	}
	for (i = 0; i < MAX_DTV_STATS; i++) {
		struct dvb_v5_counters *prev = st->prev;
		struct dvb_v5_counters *cur = st->cur;

		ret = prepare_data(p, size, "%i%i%i",
				   st->has_post_ber[i],
				   st->has_pre_ber[i],
				   st->has_per[i]);
		if (ret < 0)
			goto error;

		p += ret;
		size -= ret;

#pragma GCC diagnostic ignored "-Wformat"
		ret = prepare_data(p, size,
				   "%lu%lu%lu%lu%lu%lu%lu%lu%lu%lu%lu%lu",
				   prev->pre_bit_count,
				   prev->pre_bit_error,
				   prev->post_bit_count,
				   prev->post_bit_error,
				   prev->block_count,
				   prev->block_error,
				   cur->pre_bit_count,
				   cur->pre_bit_error,
				   cur->post_bit_count,
				   cur->post_bit_error,
				   cur->block_count,
				   cur->block_error);
#pragma GCC diagnostic pop
		if (ret < 0)
			goto error;

		p += ret;
		size -= ret;
	}

	return send_buf(fd, buf, p - buf);
error:
	return send_data(fd, "%i%s%i", seq, cmd, ret);
}

/*
 * Structure with all methods with RPC calls
 */

typedef int (*method_handler) (uint32_t seq, char *cmd, int fd,
			       char *buf, ssize_t size);

struct method_types {
	char *name;
	method_handler handler;
	int locks_dvb;
};

static const struct method_types methods[] = {
	{"daemon_get_version", &daemon_get_version, 1},
	{"dev_find", &dev_find, 0},
	{"dev_stop_monitor", &dev_stop_monitor, 0},
	{"dev_seek_by_adapter", &dev_seek_by_adapter, 0},
	{"dev_get_dev_info", &dev_get_dev_info, 0},
	{"dev_open", &dev_open, 0},
	{"dev_close", &dev_close, 0},
	{"dev_dmx_stop", &dev_dmx_stop, 0},
	{"dev_set_bufsize", &dev_set_bufsize, 0},
	{"dev_dmx_set_pesfilter", &dev_dmx_set_pesfilter, 0},
	{"dev_dmx_set_section_filter", &dev_dmx_set_section_filter, 0},
	{"dev_dmx_get_pmt_pid", &dev_dmx_get_pmt_pid, 0},

	{"dev_scan", &dev_scan, 0},

	{"dev_set_sys", &dev_set_sys, 0},
	{"fe_get_parms", &dev_get_parms, 0},
	{"fe_set_parms", &dev_set_parms, 0},
	{"fe_get_stats", &dev_get_stats, 0},

	{}
};

static void *start_server(void *fd_pointer)
{
	const struct method_types *method;
	int fd = *(int *)fd_pointer, ret, flag = 1;
	char buf[REMOTE_BUF_SIZE + 8], cmd[CMD_SIZE], *p;
	ssize_t size;
	uint32_t seq;
	int bufsize;

	if (verbose)
		dbg("Opening socket %d", fd);

	/* Set a large buffer for read() to work better */
	bufsize = REMOTE_BUF_SIZE;
	if (setsockopt(fd, SOL_SOCKET, SO_SNDBUF,
		       (void *)&bufsize, (int)sizeof(bufsize))) {
		dbg("Failed to set a large buffer size");
	};

	/* Disable Naggle algorithm, as we want errors to be sent ASAP */
	if (setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, (char *) &flag, sizeof(int))) {
		dbg("Failed to avoid TCP delays");
	};

	/* Command dispatcher */
	do {
		size = recv(fd, buf, 4, MSG_WAITALL);
		if (size <= 0)
			break;
		size = (uint32_t)buf[0] << 24 | (uint32_t)buf[1] << 16 |
		       (uint32_t)buf[2] << 8 | (uint32_t)buf[3];
		size = recv(fd, buf, size, MSG_WAITALL);
		if (size <= 0)
			break;

		ret = scan_data(buf, size, "%i%s",  &seq, cmd);
		if (ret < 0) {
			if (verbose)
				dbg("message too short: %ld", size);
			send_data(fd, "%i%s%i%s", 0, "log", LOG_ERR,
				  "msg too short");
			continue;
		}

		p = buf + ret;
		size -= ret;

		if (verbose)
			dbg("received command: %i '%s'", seq, cmd);

		if (size > buf + sizeof(buf) - p) {
			if (verbose)
				dbg("data length too big: %d", size);
			send_data(fd, "%i%s%i%s", 0, "log", LOG_ERR,
				  "data length too big");
			continue;
		}

		method = methods;
		while (method->name) {
			if (!strcmp(cmd, method->name)) {
				if (dvb_fd > 0 || method->locks_dvb) {
					ret = method->handler(seq, cmd,
							      fd, p, size);
					if (ret < 0)
						break;
					if (method->locks_dvb)
						dvb_fd = fd;
					break;
				}
				send_data(fd, "%i%s%i%s", 0, "log", LOG_ERR,
					  "daemon busy");
				break;
			}
			method++;
		}
		if (!method->name) {
			if (verbose)
				dbg("invalid command: %s", cmd);
			send_data(fd, "%i%s%i%s", 0, "log", LOG_ERR,
				  "invalid command");
		}
	} while (1);

	if (verbose)
		dbg("Closing socket %d", fd);

	close(fd);
	if (read_id) {
		pthread_cancel(read_id);
		read_id = 0;
	}
	if (dvb_fd > 0)
		close_all_devs();

	return NULL;
}

/*
 * main program
 */

int main(int argc, char *argv[])
{
	int ret;
	int sockfd;
	socklen_t addrlen;
	struct sockaddr_in serv_addr, cli_addr;

#ifdef ENABLE_NLS
	setlocale (LC_ALL, "");
	bindtextdomain (PACKAGE, LOCALEDIR);
	textdomain (PACKAGE);
#endif

	if (argp_parse(&argp, argc, argv, ARGP_NO_HELP | ARGP_NO_EXIT, 0, 0)) {
		argp_help(&argp, stderr, ARGP_HELP_SHORT_USAGE, PROGRAM_NAME);
		return -1;
	}

	if (!port) {
		argp_help(&argp, stderr, ARGP_HELP_SHORT_USAGE, PROGRAM_NAME);
		return -1;
	}

	/* Allocate DVB structure and start seek for devices */
	dvb = dvb_dev_alloc();
	if (!dvb) {
		err("Can't allocate DVB data\n");
		return -1;
	}

	dvb_dev_find(dvb, 0, NULL);

	/* Create a socket */
	sockfd = socket(AF_INET, SOCK_STREAM, 0);
	if (sockfd < 0) {
		local_perror("socket");
		goto error;
	}

	/* Initialize listen address struct */
	memset((char *) &serv_addr, 0, sizeof(serv_addr));
	serv_addr.sin_family = AF_INET;
	serv_addr.sin_addr.s_addr = INADDR_ANY;
	serv_addr.sin_port = htons(port);

	/* Bind to the address */
	ret = bind(sockfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr));
	if (ret < 0) {
		local_perror("bind");
		goto error;
	}

	/* FIXME: should allow the caller to set the verbosity */
	dvb_dev_set_logpriv(dvb, 1, dvb_remote_log, &dvb_fd);

	/* Listen up to 5 connections */
	listen(sockfd, 5);
	addrlen = sizeof(cli_addr);

	start_signal_handler();
	pthread_mutex_init(&msg_mutex, NULL);
	pthread_mutex_init(&dvb_read_mutex, NULL);

	/* Accept actual connection from the client */

	warn("Support for Digital TV remote access is still highly experimental.\n"
	     "\nKnown issues:\n"
	     "  - Abort needed to be implemented in a proper way;\n"
	     "  - Need to make more stuff opaque, as touching on fields at the local\n"
	     "    end is not automatically reflected remotely;\n"
	     "  - The libdvbv5 API support is incomplete: it misses satellite, scan\n"
	     "    and other functions;\n\n");

	info(PROGRAM_NAME" started.");

	while (1) {
		int fd;
		pthread_t id;

		if (verbose)
			dbg("waiting for connections");
		fd = accept(sockfd, (struct sockaddr *)&cli_addr, &addrlen);
		if (fd < 0) {
			local_perror("accept");
			break;
		}

		if (verbose)
			dbg("accepted connection %d", fd);
		ret = pthread_create(&id, NULL, start_server, (void *)&fd);
		if (ret < 0) {
			local_perror("pthread_create");
			break;
		}
	}

	/* Just in case we add some way for the remote part to stop the daemon */
	stop_signal_handler();

error:
	info(PROGRAM_NAME" stopped.");

	pthread_exit(NULL);

	if (dvb)
		dvb_dev_free(dvb);

	return -1;
}
