/*
 * The Qubes OS Project, http://www.qubes-os.org
 *
 * Copyright (C) 2010  Rafal Wojtczuk  <rafal@invisiblethingslab.com>
 *
 * 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; either version 2
 * of the License, or (at your option) any later version.
 *
 * 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.
 *
 */

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <libvchan.h>
#ifdef USE_XENSTORE_H
#include <xenstore.h>
#else
#include <xs.h>
#endif
#include <xenctrl.h>

static struct libvchan *ctrl;
static int is_server;
int write_all_vchan_ext(const void *buf, int size)
{
	int written = 0;
	int ret;

	while (written < size) {
		ret =
		    libvchan_write(ctrl, (const char *) buf + written,
				   size - written);
		if (ret <= 0) {
			perror("write");
			exit(1);
		}
		written += ret;
	}
//      fprintf(stderr, "sent %d bytes\n", size);
	return size;
}


int read_all_vchan_ext(void *buf, int size)
{
	int written = 0;
	int ret;
	while (written < size) {
		ret =
		    libvchan_read(ctrl, (char *) buf + written,
				  size - written);
		if (ret == 0) {
			fprintf(stderr, "EOF\n");
			exit(1);
		}
		if (ret < 0) {
			perror("read");
			exit(1);
		}
		written += ret;
	}
//      fprintf(stderr, "read %d bytes\n", size);
	return size;
}

unsigned int read_ready_vchan_ext()
{
	int ready = libvchan_data_ready(ctrl);
	if (ready < 0) {
		fprintf(stderr, "libvchan_data_ready returned invalid size\n");
		exit(1);
	}
	return ready;
}

unsigned int buffer_space_vchan_ext()
{
	int space = libvchan_buffer_space(ctrl);
	if (space < 0) {
		fprintf(stderr, "libvchan_buffer_space returned invalid size\n");
		exit(1);
	}
	return space;
}

// if the remote domain is destroyed, we get no notification
// thus, we check for the status periodically

#ifdef XENCTRL_HAS_XC_INTERFACE
static xc_interface *xc_handle = NULL;
#else
static int xc_handle = -1;
#endif
void slow_check_for_libvchan_is_eof(struct libvchan *ctrl)
{
	struct evtchn_status evst;
	evst.port = ctrl->evport;
	evst.dom = DOMID_SELF;
	if (xc_evtchn_status(xc_handle, &evst)) {
		perror("xc_evtchn_status");
		exit(1);
	}
	if (evst.status != EVTCHNSTAT_interdomain) {
		fprintf(stderr, "event channel disconnected\n");
		exit(0);
	}
}


int wait_for_vchan_or_argfd_once(int max, fd_set * rdset, fd_set * wrset)
{
	int vfd, ret;
	struct timespec tv = { 1, 100000000 };
	sigset_t empty_set;

	sigemptyset(&empty_set);

	vfd = libvchan_fd_for_select(ctrl);
	FD_SET(vfd, rdset);
	if (vfd > max)
		max = vfd;
	max++;
	ret = pselect(max, rdset, wrset, NULL, &tv, &empty_set);
	if (ret < 0) {
		if (errno != EINTR) {
			perror("select");
			exit(1);
		} else {
			FD_ZERO(rdset);
			FD_ZERO(wrset);
			fprintf(stderr, "eintr\n");
			return 1;
		}

	}
	if (libvchan_is_eof(ctrl)) {
		fprintf(stderr, "libvchan_is_eof\n");
		exit(0);
	}
	if (!is_server && ret == 0)
		slow_check_for_libvchan_is_eof(ctrl);
	if (FD_ISSET(vfd, rdset))
		// the following will never block; we need to do this to
		// clear libvchan_fd pending state 
		libvchan_wait(ctrl);
	return ret;
}

void wait_for_vchan_or_argfd(int max, fd_set * rdset, fd_set * wrset)
{
	fd_set r = *rdset, w = *wrset;
	do {
		*rdset = r;
		*wrset = w;
	}
	while (wait_for_vchan_or_argfd_once(max, rdset, wrset) == 0);
}

int peer_server_init(int port)
{
	is_server = 1;
	ctrl = libvchan_server_init(port);
	if (!ctrl) {
		perror("libvchan_server_init");
		exit(1);
	}
	return 0;
}

char *peer_client_init(int dom, int port)
{
	struct xs_handle *xs;
	char buf[64];
	char *name;
	char *dummy, *dummy2;
	unsigned int len = 0;
	char devbuf[128];
	char dombuf[128];
	unsigned int count;
	char **vec;

//      double_buffered = 1; // writes to vchan are buffered, nonblocking
//      double_buffer_init();
	xs = xs_daemon_open();
	if (!xs) {
		perror("xs_daemon_open");
		exit(1);
	}
	snprintf(buf, sizeof(buf), "/local/domain/%d/name", dom);
	name = xs_read(xs, 0, buf, &len);
	if (!name) {
		perror("xs_read domainname");
		exit(1);
	}
	snprintf(devbuf, sizeof(devbuf),
		 "/local/domain/%d/device/vchan/%d/event-channel", dom,
		 port);
	snprintf(dombuf, sizeof(dombuf), "/local/domain/%d", dom);
	xs_watch(xs, devbuf, devbuf);
	do {
		vec = xs_read_watch(xs, &count);
		if (vec)
			free(vec);
		len = 0;
		dummy = xs_read(xs, 0, devbuf, &len);
		if (dummy)
			free(dummy);
		else {
			/* check if domain still alive */
			dummy2 = xs_read(xs, 0, dombuf, &len);
			if (!dummy2) {
				fprintf(stderr, "domain dead\n");
				exit(1);
			}
			free(dummy2);
		}
	}
	while (!dummy || !len);	// wait for the server to create xenstore entries
	xs_daemon_close(xs);

	// now client init should succeed; "while" is redundant
	while (!(ctrl = libvchan_client_init(dom, port)));

#ifdef XENCTRL_HAS_XC_INTERFACE
	xc_handle = xc_interface_open(NULL, 0, 0);
	if (!xc_handle) {
#else
	xc_handle = xc_interface_open();
	if (xc_handle < 0) {
#endif
		perror("xc_interface_open");
		exit(1);
	}
	return name;
}
