/*
 * fakefile.c - Programmed file system illusion
 *
 * Copyright 2012 by Werner Almesberger
 *
 * 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.
 */


#include <stdlib.h>
#include <stdio.h>
#include <poll.h>
#include <assert.h>
#include <sys/wait.h>

#include "util.h"
#include "internal.h"
#include "fakefile.h"


struct event {
	struct fakefile_event ev;
	struct fakefile *ff;
	void *tmp;
	void (*respond)(struct event *ev);
};

static struct fakefile *fakefiles = NULL;
static struct pollfd *fds = NULL;
static int nfds = 0;


static void map_slave_fd(struct fakefile *ff, int fd, void *handle)
{
	struct fd_map *map;

	map = alloc_type(struct fd_map);
	map->fd = fd;
	map->handle = handle;
	map->next = ff->map;
	ff->map = map;
}


static void *lookup_slave_fd(struct fakefile *ff, int fd)
{
	const struct fd_map *map;

	for (map = ff->map; map; map = map->next)
		if (map->fd == fd)
			return map->handle;
	abort();
}


static void unmap_slave_fd(struct fakefile *ff, int fd)
{
	struct fd_map **map, *next;

	for (map = &ff->map; (*map)->fd != fd; map = &(*map)->next);
	next = (*map)->next;
	free(*map);
	*map = next;
}


struct fakefile *fakefile_execv(const char *path, char *const argv[])
{
	struct fakefile *ff;

	ff = fakefile_launch(path, argv);
	ff->map = NULL;
	ff->next = fakefiles;
	fakefiles = ff;
	fds = realloc(fds, sizeof(struct pollfd)*(nfds+1));
	if (!fds) {
		perror("realloc");
		exit(1);
	}
	fds[nfds].fd = fakefile_peer_fd(ff->peer);
	fds[nfds].events = POLLIN | POLLHUP;
	nfds++;
	return ff;
}


static void respond_open(struct event *ev)
{
	const struct fakefile_open *prm = &ev->ev.u.open;
	struct fakefile *ff = ev->ff;
	struct fakefile_msg *msg;

	msg = fakefile_msg_new(ff->peer, sizeof(int));
	if (prm->res > 0)
		map_slave_fd(ff, prm->res, prm->handle);
	fakefile_msg_add_int(msg, prm->res);
	fakefile_msg_end(msg);
	free(prm->name);
}


static void respond_read(struct event *ev)
{
	const struct fakefile_read *prm = &ev->ev.u.read;
	struct fakefile *ff = ev->ff;
	struct fakefile_msg *msg;
	ssize_t size;

	size = prm->len < 0 ? 0 : prm->len;
	msg = fakefile_msg_new(ff->peer, sizeof(ssize_t)+size);
	fakefile_msg_add_ssize_t(msg, prm->len);
	if (size)
		fakefile_msg_add(msg, prm->buf, size);
	fakefile_msg_end(msg);
	free(ev->tmp);
}


static void respond_fstat(struct event *ev)
{
	const struct fakefile_fstat *prm = &ev->ev.u.fstat;
	struct fakefile *ff = ev->ff;
	struct fakefile_msg *msg;
	ssize_t size;

	size = prm->res < 0 ? 0 : sizeof(struct stat);
	msg = fakefile_msg_new(ff->peer, sizeof(int)+size);
	fakefile_msg_add_int(msg, prm->res);
	if (size)
		fakefile_msg_add(msg, &prm->st, sizeof(struct stat));
	fakefile_msg_end(msg);
}


static void respond_exit(struct event *ev)
{
}


static void respond_close(struct event *ev)
{
	const struct fakefile_close *prm = &ev->ev.u.close;
	struct fakefile *ff = ev->ff;
	struct fakefile_msg *msg;

	msg = fakefile_msg_new(ff->peer, sizeof(int));
	fakefile_msg_add_int(msg, prm->res);
	fakefile_msg_end(msg);
}


static struct fakefile_event *get_event(int fd)
{
	struct fakefile *ff;
	struct event *ev;
	struct fakefile_msg *msg;

	for (ff = fakefiles; ff; ff = ff->next)
		if (fakefile_peer_fd(ff->peer) == fd)
			break;
	assert(ff);

	ev = alloc_type(struct event);
	ev->ff = ff;

	msg = fakefile_msg_recv(ff->peer);
	ev->ev.type = fakefile_msg_get_int(msg);
	switch (ev->ev.type) {
	case ff_et_exit: {
		struct fakefile_exit *prm = &ev->ev.u.exit;

		ev->respond = respond_exit;
		prm->status = fakefile_msg_get_int(msg);
		break;
	}
	case ff_et_open: {
		struct fakefile_open *prm = &ev->ev.u.open;
		int len;

		ev->respond = respond_open;
		prm->flags = fakefile_msg_get_int(msg);
		prm->mode = fakefile_msg_get_int(msg);
		prm->res = fakefile_msg_get_int(msg);
		len = fakefile_msg_get_int(msg);
		prm->name = alloc_size(len+1);
		fakefile_msg_get(msg, prm->name, len);
		prm->name[len] = 0;
		prm->handle = NULL;
		break;
	}
	case ff_et_read: {
		struct fakefile_read *prm = &ev->ev.u.read;
		int fd;

		ev->respond = respond_read;
		fd = fakefile_msg_get_int(msg);
		prm->handle = lookup_slave_fd(ff, fd);
		prm->len = fakefile_msg_get_size_t(msg);
		prm->buf = ev->tmp = alloc_size(prm->len);
		break;
	}
	case ff_et_write: {
		abort();
	}
	case ff_et_close: {
		struct fakefile_close *prm = &ev->ev.u.close;
		int fd;

		ev->respond = respond_close;
		fd = fakefile_msg_get_int(msg);
		prm->handle = lookup_slave_fd(ff, fd);
		prm->res = 0;
		break;
	}
	case ff_et_unlink:
		abort();
	case ff_et_rename:
		abort();
	case ff_et_fstat: {
		struct fakefile_fstat *prm = &ev->ev.u.fstat;
		int fd;

		ev->respond = respond_fstat;
		fd = fakefile_msg_get_int(msg);
		prm->handle = lookup_slave_fd(ff, fd);
		prm->res = fstat(fd, &prm->st);
		break;
	}
	default:
		abort();
	}
	fakefile_msg_end(msg);
	if (fakefile_internal_event(ev))
		return NULL;
	return &ev->ev;
}


static struct fakefile_event *handle_exit(int fd)
{
	struct fakefile *ff;
	struct fakefile_event *event;

	for (ff = fakefiles; ff; ff = ff->next)
		if (fakefile_peer_fd(ff->peer) == fd)
			break;
	assert(ff);
	event = alloc_type(struct fakefile_event);
	event->type = ff_et_exit;
	if (waitpid(ff->pid, &event->u.exit.status, 0) < 0) {
		perror("waitpid");
		exit(1);
	}
//@@@ destroy
	return event;
}


struct fakefile_event *fakefile_poll(void)
{
	struct fakefile_event *event;
	struct pollfd *p;
	int n;

	while (1) {
		n = poll(fds, nfds, -1);
		if (n < 0) {
			perror("poll");
			exit(1);
		}
		if (!n) {
			fprintf(stderr, "poll() returned 0\n");
			exit(1);
		}
		for (p = fds; n && p != fds+nfds; p++) {
			if (p->revents & POLLIN) {
				event = get_event(p->fd);
				if (event)
					return event;
			}
			if (p->revents & POLLHUP)
				return handle_exit(p->fd);
		}
	}
}


void fakefile_respond(struct fakefile_event *event)
{
	struct event *ev = (struct event *) event;

	ev->respond(ev);
	free(ev);
}


void fakefile_end(struct fakefile *ff)
{
}