/*
 * This file is part of Jehanne.
 *
 * Copyright (C) 2015 Giacomo Tesio <giacomo@tesio.it>
 *
 * Jehanne 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 3 of the License.
 *
 * Jehanne 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 Jehanne.  If not, see <http://www.gnu.org/licenses/>.
 */
#include <u.h>
#include <lib9.h>
#include <9P2000.h>

#include "console.h"

enum
{
	Maxfdata	= IODATASZ,
	Miniosize	= IOHDRSZ+ScreenBufferSize,
	Maxiosize	= IOHDRSZ+Maxfdata,
};

typedef enum
{
	Initializing,
	Mounted,
	Unmounted,		/* fsserve() loop while status < Unmounted */
} Status;

int systemwide;

static Status status;
static int rawmode;

static int fspid;
static void *data;

enum {
	Qroot,
	Qcons,
	Qconsctl,
	Nqid,
	Qinput,
	Qoutput,
	Nhqid,
};
static struct Qtab {
	char *name;
	int mode;
	int type;
	int length;
} qtab[Nhqid] = {
	"/",
		DMDIR|0555,
		QTDIR,
		0,

	"cons",
		0666,
		0,
		0,

	"consctl",
		0222,
		0,
		0,

	"",
		0,
		0,
		0,

	"gconsin",		/* data from input will be written here */
		DMAPPEND|DMEXCL|0222,
		0,
		0,

	"gconsout",		/* data to output will be read here */
		DMEXCL|0444,
		0,
		0,
};

/* message size for the exported name space
 *
 * between Miniosize and Maxiosize
 */
static int messagesize = Maxiosize;

/* Initialized on Qinput open */
static Buffer *input;	/* written on Twrite(Qinput), read on Tread(Qcons) */

/* Initialized on Qoutput open */
static Buffer *output;	/* written on Twrite(Qcons), read on Tread(Qoutput) */

static uint32_t nextoutputcol;

/* linked list of known fids
 *
 * NOTE: we don't free() Fids, because there's no appropriate point
 *	in 9P2000 to do that, except the Tclunk of the attach fid,
 *	that in our case corresponds to shutdown
 *	(the kernel is our single client, we are doomed to trust it)
 */
typedef struct Fid Fid;
struct Fid
{
	int32_t fd;
	Qid qid;
	int16_t opened;	/* -1 when not open */
	Fid *next;
};
#define ISCLOSED(f) (f != nil && f->opened == -1)

static Fid *fids;
static Fid **ftail;
static Fid *external;	/* attach fid of the last mount() in gconsole.c */
static Fid *inputfid;	/* from Topen(Qinput) */
static Fid *outputfid;	/* from Topen(Qoutput) */

static Fid*
createFid(int32_t fd, Qid qid)
{
	Fid *fid;

	fid = (Fid*)malloc(sizeof(Fid));
	if(fid){
		fid->fd = fd;
		fid->qid = qid;
		fid->opened = -1;
		fid->next = nil;
		*ftail = fid;
		ftail = &fid->next;
	}
	return fid;
}
static Fid*
findFid(int32_t fd)
{
	Fid *fid;

	fid = fids;
	while(fid != nil && fid->fd != fd)
		fid = fid->next;
	return fid;
}

/* utilities */
static int
readmessage(int fd, Fcall *req)
{
	int n;

	n = read9pmsg(fd, data, Maxiosize);
	if(n > 0)
		if(convM2S(data, n, req) == 0){
			debug("readmessage: convM2S returns 0\n");
			return -1;
		} else {
			debug("serve9p %d: <-%F\n", fspid, req);
			return 1;
		}
	if(n < 0){
		debug("readmessage: read9pmsg: %r\n");
		return -1;
	}
	return 0;
}
static int
sendmessage(int fd, Fcall *rep)
{
	int n;
	static uint8_t repdata[Maxiosize];

	n = convS2M(rep, repdata, Maxiosize);
	if(n == 0) {
		debug("sendmessage: convS2M error\n");
		return 0;
	}
	if(write(fd, repdata, n) != n) {
		debug("sendmessage: write\n");
		return 0;
	}
	debug("serve9p %d: ->%F\n", fspid, rep);
	return 1;
}

/* queue of pending reads */
typedef struct AsyncOp AsyncOp;
struct AsyncOp
{
	uint32_t tag;
	uint32_t count;
	AsyncOp *next;
};
typedef struct OpQueue OpQueue;
struct OpQueue
{
	AsyncOp *head;
	AsyncOp **tail;
};
#define qempty(q) (q->tail == &q->head)

static OpQueue consreads;
static OpQueue conswrites;
static AsyncOp *outputread;		/* only one process can access Qoutput */

static void
qinit(OpQueue *q)
{
	q->tail = &q->head;
}
static AsyncOp *
opalloc(uint32_t tag, uint32_t count)
{
	AsyncOp *read;

	read = (AsyncOp*)malloc(sizeof(AsyncOp));
	if(!read)
		 return nil;
	read->tag = tag;
	read->count = count;
	read->next = nil;
	return read;
}
static int
enqueue(OpQueue *queue, uint32_t tag, uint32_t count)
{
	AsyncOp *read;

	debug("enqueue(%#p, %d, %d)\n", queue, tag, count);
	if((read = opalloc(tag, count)) == nil)
		return 0;
	*queue->tail = read;
	queue->tail = &read->next;
	return 1;
}
static void
dequeue(OpQueue *queue, uint32_t tag)
{
	AsyncOp *op, *prev;

	if(qempty(queue))
		return;

	prev = nil;
	op = queue->head;
	while(op && op->tag != tag){
		prev = op;
		op = op->next;
	}
	if(op == nil)
		return;
	if(prev == nil)
		queue->head = op->next;
	else
		prev->next = op->next;
	if(queue->tail == &op->next)
		queue->tail = &prev->next;
	else
		queue->tail = &queue->head;
	free(op);
}
static int
syncOutput(int connection, Fcall *rep)
{
	uint32_t l; //, i;
	int w;
	char *d;
	AsyncOp *aw;
	OpQueue *queue;

	if(outputread == nil || bempty(output))
		return 1;	/* continue */

	queue = &conswrites;
	l = outputread->count;
	d = bread(output, &l);

	rep->type = Rread;
	rep->tag = outputread->tag;
	rep->count = l;
	rep->data = d;

	w = sendmessage(connection, rep);
	if(w <= 0){
		/* we had an error on the connection: stop to fsserve() */
		debug("serve9p %d: syncOutput: %d bytes ready, but sendmessage returns %d\n", fspid, rep->count, w);
		return w;
	}

	free(outputread);
	outputread = nil;

	if(bempty(output)){
		w = 0;
		rep->type = Rwrite;
		while(!qempty(queue)){
			aw = queue->head;
			rep->tag = aw->tag;
			rep->count = aw->count;

			w = sendmessage(connection, rep);
			if(w <= 0){
				/* we had an error on the connection: stop to fsserve() */
				debug("serve9p %d: syncOutput: %d bytes written, but sendmessage returns %d\n", fspid, rep->count, w);
				return w;
			}

			queue->head = aw->next;
			if(queue->head == nil){
				queue->tail = &queue->head;
			}
			free(aw);
		}
	}

	return 1;
}
/* distribute available data to pending Tread(cons) */
static int
syncCons(int connection, Fcall *rep)
{
	AsyncOp *ar;
	OpQueue *queue;
	uint32_t l;
	int w;
	char *d;

	queue = &consreads;
	if(qempty(queue) || bempty(input))
		return 1;	/* continue */

	/* here we have a pending queue and either (checked by fscons)
	 *
	 * - a full line verified by blineready, or
	 * - a partial line with either rawmode active or linecontrol inactive
	 *
	 * in both case we have something to send and somebody ready to receive
	 */
	debug("syncCons(queue = %#p)\n", queue);
	rep->type = Rread;

	w = 0;
	while(!qempty(queue) && !bempty(input)){
		ar = queue->head;
		l = ar->count;
		if(rawmode)
			d = bread(input, &l);
		else
			d = breadln(input, &l);

		rep->tag = ar->tag;
		rep->count = l;
		rep->data = d;

		w = sendmessage(connection, rep);
		if(w <= 0){
			/* we had an error on the connection: stop to fsserve() */
			debug("serve9p %d: syncCons: %d bytes ready, but sendmessage returns %d\n", fspid, rep->count, w);
			return w;
		}

		queue->head = ar->next;
		if(queue->head == nil){
			queue->tail = &queue->head;
		}
		free(ar);
	}
	return 1;
}
static int
closeOutput(int connection, Fcall *rep)
{
	int w;

	if(!outputread)
		return 1;

	rep->type = Rread;
	rep->count = 0;
	rep->data = nil;
	rep->tag = outputread->tag;

	w = sendmessage(connection, rep);
	if(w <= 0){
		/* we had an error on the connection: stop to fsserve() */
		debug("serve9p %d: qclose: sendmessage returns %d\n", fspid, w);
		return w;
	}

	free(outputread);
	outputread = nil;

	return 1;
}
static int
closeConsReaders(int connection, Fcall *rep)
{
	OpQueue *queue;
	AsyncOp *op;
	int w;

	queue = &consreads;
	if(qempty(queue))
		return 1;

	debug("qclose(queue = %#p)\n", queue, data);
	rep->type = Rread;
	rep->count = 0;
	rep->data = nil;

	op = queue->head;
	while(op != nil){
		queue->head = op->next;
		rep->tag = op->tag;

		w = sendmessage(connection, rep);
		if(w <= 0){
			/* we had an error on the connection: stop to fsserve() */
			debug("serve9p %d: qclose: sendmessage returns %d\n", fspid, w);
			return w;
		}

		free(op);
		op = queue->head;
	}
	queue->tail = &queue->head;
	return 1;
}

/* line buffering and control */
static void
rawappender(char c, Buffer* b)
{
	b->data[b->written++] = c;
}
/* build the input line when linecontrol is on */
static void
lineprinter(char c, Buffer* b)
{
	switch(c){
		case '\n':
		case '\r':
			b->linewidth = 0;
			if(crnl){
				b->data[b->written++] = '\r';
				b->data[b->written++] = '\n';
				return;
			}
			break;
		case '\b':
			/* decrease line width a bit */
			if(b->linewidth)
				--b->linewidth;
			/* overwrite with a space */
			b->data[b->written++] = '\b';
			b->data[b->written++] = ' ';
			break;
		case '\t':
			/* turns \t to spaces */
			do
			{
				lineprinter(' ', b);
			}
			while(b->linewidth&7);
			return;
		default:
			++b->linewidth;
			break;
	}
	b->data[b->written++] = c;
}
/* build the input line when linecontrol is on
 *
 * note that this is only used for input, thus do not
 * compute linewidth, since \b would have variable width
 * according to the position (thanks \t!).
 */
static void
linebuilder(char c, Buffer* b)
{
	if (b->ctrld < b->size)
		return;	/* ignore everything after ^D, until it has been consumed */
	if(crnl && c == '\n' && b->written > 0 && b->data[b->written-1] == '\n')
		return; /* avoid duplications */

	switch(c){
		case '\004':	// ctrl-d
			b->ctrld = b->written;
			break;
		case 0x15:		// ctrl-u
			b->written = b->read;
			break;
		case 0x7f:		// backspace
		case '\b':
			if(!bempty(b))
				--b->written;
			break;
		case '\n':
			++b->newlines;	/* only newline count for us */
			b->data[b->written++] = c;
			break;
		case '\r':
			if(crnl){
				/* turn \r to \n */
				b->data[b->written++] = '\n';
				++b->newlines;
			} else {
				b->data[b->written++] = c;
			}
			break;
		default:
			b->data[b->written++] = c;
			break;
	}
}

/* 9p message handlers */
static char *
invalidioreq(Fcall *req)
{
	if(req->count > messagesize || req->count < 0)
		return "bad read/write count";
	return nil;
}
static char*
gethostowner(void)
{
	int f, r;
	char *res;

	res = (char*)malloc(256);
	if(res == nil)
		sysfatal("out of memory");
	f = open("#c/hostowner", OREAD);
	if(f < 0)
		sysfatal("open(#c/hostowner) %r");
	r = read(f, res, 255);
	if(r < 0)
		sysfatal("read(#c/hostowner)");
	res[r] = '\0';
	close(f);
	return res;
}
static int
fillstat(uint64_t path, Dir *d)
{
	struct Qtab *t;
	static char *filesowner;

	if(filesowner == nil)
		filesowner = gethostowner();

	memset(d, 0, sizeof(Dir));
	d->uid = filesowner;
	d->gid = filesowner;
	d->muid = "";
	d->qid = (Qid){path, 0, 0};
	d->atime = time(0);
	t = qtab + path;
	d->name = t->name;
	d->qid.type = t->type;
	d->mode = t->mode;
	d->length = t->length;
	return 1;
}
static int32_t
rootread(Fid *fid, uint8_t *buf, int64_t off, int32_t cnt, int blen)
{
	int32_t m, n;
	int64_t i, pos;
	Dir d;

	n = 0;
	pos = 0;
	for (i = 1; i < Nqid; i++){
		fillstat(i, &d);
		m = convD2M(&d, &buf[n], blen-n);
		if(off <= pos){
			if(m <= BIT16SZ || m > cnt)
				break;
			n += m;
			cnt -= m;
		}
		pos += m;
	}
	return n;
}
static int
rerror(Fcall *rep, char *err)
{
	debug("rerror: %s\n", err);
	rep->type = Rerror;
	rep->ename = err;
	return 1;
}
static int
rpermission(Fcall *req, Fcall *rep)
{
	return rerror(rep, "permission denied");
}
static int
rattach(Fcall *req, Fcall *rep)
{
	char *spec;
	Fid *f;

	spec = req->aname;
	if(spec && spec[0])
		return rerror(rep, "bad attach specifier");

	if(external != nil && !systemwide){
		/* when not system wide (aka screenconsole), we expect 3 valid Tattach:
		 * 1 for the process that will send us the input, writing Qinput
		 * 1 for the process that will print our output, reading Qoutput
		 * 1 for the rest of the children
		 */
		return rerror(rep, "device busy");
	}
	f = findFid(req->fid);
	if(f == nil)
		f = createFid(req->fid, (Qid){Qroot, 0, QTDIR});
	if(f == nil)
		return rerror(rep, "out of memory");

	if(external == nil && input != nil && output != nil){
		external = f;
		status = Mounted;
	}

	rep->type = Rattach;
	rep->qid = f->qid;
	return 1;
}
static int
rauth(Fcall *req, Fcall *rep)
{
	return rerror(rep, "authentication not required");
}
static int
rversion(Fcall *req, Fcall *rep)
{
	if(req->msize < Miniosize)
		return rerror(rep, "message size too small");

	messagesize = req->msize;
	if(messagesize > Maxiosize)
		messagesize = Maxiosize;
	rep->type = Rversion;
	rep->msize = messagesize;
	if(*req->version == 0 || strncmp(req->version, "9P2000", 6) == 0)
		rep->version = "9P2000";
	else
		rep->version = "unknown";
	return 1;
}
static int
rflush(Fcall *req, Fcall *rep)
{
	if(outputread != nil && req->tag == outputread->tag){
		free(outputread);
		outputread = nil;
	}else
		dequeue(&consreads, req->oldtag);

	rep->type = Rflush;
	return 1;
}
static int
rwalk(Fcall *req, Fcall *rep)
{
	Fid *f, *n;
	Qid q;

	f = findFid(req->fid);
	if(f == nil)
		return rerror(rep, "bad fid");
	if(req->nwname > 1 || (req->nwname == 1 && f->qid.path != Qroot))
		return rerror(rep, "walk in non directory");
	if(f->opened != -1)
		return rerror(rep, "fid in use");

	if(req->nwname == 1){
		if (strcmp(qtab[Qcons].name, req->wname[0]) == 0) {
			q = (Qid){Qcons, 0, 0};
		} else if (strcmp(qtab[Qconsctl].name, req->wname[0]) == 0) {
			q = (Qid){Qconsctl, 0, 0};
		} else if (!input && strcmp(qtab[Qinput].name, req->wname[0]) == 0) {
			q = (Qid){Qinput, 0, 0};
		} else if (!output && strcmp(qtab[Qoutput].name, req->wname[0]) == 0) {
			q = (Qid){Qoutput, 0, 0};
		} else {
			return rerror(rep, "file does not exist");
		}
	} else {
		q = f->qid;
	}
	if(req->fid == req->newfid){
		n = f;
	} else {
		n = findFid(req->newfid);
		if(n == nil)
			n = createFid(req->newfid, q);
		else if(n->opened != -1)
			return rerror(rep, "newfid already in use");

		if(n == nil)
			return rerror(rep, "out of memory");
	}
	n->qid = q;
	rep->type = Rwalk;
	rep->nwqid = req->nwname;
	if(req->nwname)
		rep->wqid[0] = q;
	return 1;
}
static int
ropen(Fcall *req, Fcall *rep)
{
	static int need[4] = {
		4,	/* NP_OREAD */
		2,	/* NP_OWRITE */
		6,	/* NP_ORDWR */
		1	/* NP_OEXEC */
	};
	struct Qtab *t;
	Fid *f;
	int n;

	if(req->mode&NP_OZEROES)
		return rerror(rep, "invalid 9P2000 open mode");

	f = findFid(req->fid);
	if(f == nil)
		return rerror(rep, "bad fid");
	if(f->opened != -1)
		return rerror(rep, "already open");

	t = qtab + f->qid.path;
	n = need[req->mode & 3];
	if((n & t->mode) != n)
		return rpermission(req, rep);

	rep->iounit = 0;
	switch(f->qid.path)
	{
		case Qinput:
			inputfid = f;
			if(linecontrol){
				input = balloc(Maxfdata);
				input->add = linebuilder;
			} else {
				input = balloc(Maxfdata);
				input->add = rawappender;
			}
			break;
		case Qoutput:
			outputfid = f;
			output = balloc(Maxfdata*4);	/* space for multiple verbose writers */
			if(linecontrol){
				output->add = lineprinter;
			} else {
				output->add = rawappender;
			}
			break;
		case Qcons:
			if(ISCLOSED(inputfid) && (req->mode & NP_OREAD) == NP_OREAD)
				return rerror(rep, "input device closed");
			if(ISCLOSED(outputfid) && (req->mode & NP_OWRITE) == NP_OWRITE)
				return rerror(rep, "output device closed");
			if((req->mode & NP_OWRITE) == NP_OWRITE)
				rep->iounit = ScreenBufferSize;
			break;
		default:
			break;
	}
	f->opened = req->mode;
	rep->type = Ropen;
	rep->qid = f->qid;
	return 1;
}
static int
rread(Fcall *req, Fcall *rep)
{
	Fid *f;
	char *err;

	if(err = invalidioreq(req))
		return rerror(rep, err);

	f = findFid(req->fid);
	if(f == nil)
		return rerror(rep, "bad fid");
	if(ISCLOSED(f) || (f->opened & 3) % 2 != 0)
		return rerror(rep, "i/o error");

	rep->type = Rread;
	if(req->count == 0){
		rep->count = 0;
		rep->data = nil;
		return 1;
	}
	switch(f->qid.path){
		case Qroot:
			rep->count = rootread(f, (uint8_t*)data, req->offset, req->count, Maxfdata);
			rep->data = data + req->offset;
			return 1;
		case Qcons:
			if(ISCLOSED(inputfid)){
				rep->count = 0;
				rep->data = nil;
				return 1;
			}
			if(!enqueue(&consreads, req->tag, req->count))
				return rerror(rep, "out of memory");
			return 0;
		case Qoutput:
			assert(outputread == nil);
			outputread = opalloc(req->tag, req->count);
			if(!outputread)
				return rerror(rep, "out of memory");
			return 0;
		default:
			return rerror(rep, "i/o error");
	}
}
static int
rwrite(Fcall *req, Fcall *rep)
{
	static char backspace = '\b';
	int maxlength, old, new;
	Fid *f;
	char *s;

	if(s = invalidioreq(req))
		return rerror(rep, s);

	f = findFid(req->fid);
	if(f == nil)
		return rerror(rep, "bad fid");
	if(ISCLOSED(f) || (f->opened & NP_OWRITE) != NP_OWRITE)
		return rerror(rep, "i/o error");

	switch(f->qid.path){
		case Qcons:
			if(ISCLOSED(outputfid))
				rep->count = 0;
			else if(bspace(output) < req->count * 2)
				return rerror(rep, "device busy");
			else {
				while(output->linewidth > nextoutputcol)
					bwrite(output, &backspace, 1);
				if(req->count > ScreenBufferSize)
					req->count = ScreenBufferSize;
				rep->count = bwrite(output, req->data, req->count);
				nextoutputcol = output->linewidth;
				if(!bempty(input))
					bwrite(output, input->data + input->read, input->written - input->read);
				if(bpending(output) > ScreenBufferSize * 3){
					/* the output buffer contains too much data:
					 * slow down writers by deferring Rwrites
					 */
					if(!enqueue(&conswrites, req->tag, rep->count))
						return rerror(rep, "out of memory");
					return 0;
				}
			}
			break;
		case Qconsctl:
			if(blind)
				return rerror(rep, "no raw mode when blind");

			if(strncmp("rawon", req->data, 5) == 0){
				input->ctrld = input->size;
				input->add = rawappender;
				output->add = rawappender;
				rawmode = 1;
			} else if(strncmp("rawoff", req->data, 6) == 0) {
				input->ctrld = input->size;
				input->add = linebuilder;
				output->add = lineprinter;
				rawmode = 0;
			} else
				return rerror(rep, "unknown control message");
			rep->count = req->count;
			break;
		case Qinput:
			if(ISCLOSED(outputfid) || status == Unmounted){
				rep->count = 0;
			} else if(rawmode || blind){
				rep->count = bwrite(input, req->data, req->count);
			} else if(!linecontrol) {
				/* life is easy:
				 *
				 * we have to give a feedback to the user but we do
				 * not have to handle control characters
				 */
				rep->count = bwrite(input, req->data, req->count);
				bwrite(output, req->data, rep->count);
			} else {
				/* we have to send a feedback to the user but also
				 * handle control characters
				 *
				 * we need enough spaces in both input and output
				 */
				maxlength = req->count;
				if((new = bspace(input)) < maxlength)
					maxlength = new;
				if((new = bspace(output) / 2) < maxlength)
					maxlength = new;

				old = bpending(input);
				rep->count = bwrite(input, req->data, maxlength);
				new = bpending(input);
				if(new == old + rep->count){
					/* no control characters */
					maxlength = bwrite(output, req->data, maxlength);

					/* we knew we have enough space, abort if not */
					assert(maxlength == rep->count);
				} else {
					/* sync visible output and input buffer
					 *
					 * we have to delete previously pending input chars
					 * from output, and then write them again
					 */
					while(output->linewidth > nextoutputcol)
						bwrite(output, &backspace, 1);
					if(new > 0)
						bwrite(output, input->data, new);
				}
			}
			break;
		default:
			return rerror(rep, "i/o error");
	}
	rep->type = Rwrite;
	return 1;
}
static int
rclunk(Fcall *req, Fcall *rep)
{
	Fid *f;

	f = findFid(req->fid);
	if(f != nil){
		if(f == external){
			debug("serve9p: external clients gone\n");
			status = Unmounted;
		}
		f->opened = -1;
	}
	rep->type = Rclunk;
	return 1;
}
static int
rstat(Fcall *req, Fcall *rep)
{
	Dir d;
	Fid *f;
	static uint8_t mdata[Maxiosize];

	f = findFid(req->fid);
	if(f == nil || f->qid.path >= Nqid)
		return rerror(rep, "bad fid");

	fillstat(f->qid.path, &d);
	rep->type = Rstat;
	rep->nstat = convD2M(&d, mdata, Maxiosize);
	rep->stat = mdata;
	return 1;
}

static int (*fcalls[])(Fcall *, Fcall *) = {
	[Tversion]	rversion,
	[Tauth]		rauth,
	[Tattach]	rattach,
	[Tflush]	rflush,
	[Twalk]		rwalk,
	[Topen]		ropen,
	[Tcreate]	rpermission,
	[Tread]		rread,
	[Twrite]	rwrite,
	[Tclunk]	rclunk,
	[Tremove]	rpermission,
	[Tstat]		rstat,
	[Twstat]	rpermission,
};

int
fsinit(int *mnt, int *mntdev)
{
	int tmp[2];

	pipe(tmp);
	*mnt = tmp[0];
	*mntdev = '9';

	return tmp[1];
}
/* fsserve is the main loop */
void
fsserve(int connection)
{
	int r, w, syncrep;
	Fcall	rep;
	Fcall	*req;

	fspid = getpid();
	req = malloc(sizeof(Fcall)+Maxfdata);
	if(req == nil)
		sysfatal("out of memory");
	data = malloc(messagesize);
	if(data == nil)
		sysfatal("out of memory");

	ftail = &fids;
	qinit(&consreads);
	qinit(&conswrites);

	status = Initializing;

	debug("serve9p %d: started\n", fspid);

	do
	{
		debug("serve9p %d: wait for a new request\n", fspid);
		if((r = readmessage(connection, req)) <= 0){
			debug("serve9p %d: readmessage returns %d\n", fspid, r);
			break;
		}

		rep.tag = req->tag;
		if(req->type < Tversion || req->type > Twstat)
			syncrep = rerror(&rep, "bad fcall type");
		else
			syncrep = (*fcalls[req->type])(req, &rep);

		if(syncrep && (w = sendmessage(connection, &rep)) <= 0){
			debug("serve9p %d: sendmessage returns %d\n", fspid, w);
			break;
		}

		/* first, display output to the user... */
		debug("serve9p %d: display output to the user... \n", fspid);
		if((status == Unmounted || ISCLOSED(outputfid)) && (w = closeOutput(connection, &rep)) <= 0){
			debug("serve9p %d: closeOutput returns %d\n", fspid, w);
			break;
		} else if((w = syncOutput(connection, &rep)) <= 0){
			debug("serve9p %d: syncOutput returns %d\n", fspid, w);
			break;
		}
		/* then distribute available input among cons readers... */
		debug("serve9p %d: distribute available input among cons readers... \n", fspid);
		if((status == Unmounted || ISCLOSED(inputfid)) && (w = closeConsReaders(connection, &rep)) <= 0){
			debug("serve9p %d: closeConsReaders returns %d\n", fspid, w);
			break;
		} else if(!linecontrol || rawmode || blineready(input)){
			if((w = syncCons(connection, &rep)) <= 0){
				debug("serve9p %d: syncCons(input) returns %d\n", fspid, w);
				break;
			}
		}

		/* We can exit (properly) only when the following conditions hold
		 *
		 * - the kernel decided that nobody need us anymore
		 *   (status == Unmounted, see rclunk and rattach)
		 * - the inputfid has been closed (so that the Qinput writer
		 *   exited, releasing the actual input device)
		 * - the outputfid has been closed (so that the Qoutput reader
		 *   exited, releasing the actual output device)
		 *
		 * Even if no processes in the namespace are currently using
		 * cons or consctl, as far as the namespace exists one of its
		 * processes could still open cons (or consctl).
		 *
		 * Thus we exit when the kernel decides that nobody will
		 * need our services (aka, all the children sharing the
		 * mountpoint that we serve have exited).
		 *
		 * (AND obviously if an unexpected error occurred)
		 */
	}
	while(systemwide || status < Unmounted || !ISCLOSED(inputfid) || !ISCLOSED(outputfid));


	if(r < 0)
		sysfatal("serve9p: readmessage");
	if(w < 0)
		sysfatal("serve9p: sendmessage");

	close(connection);
	debug("serve9p %d: close(%d)\n", fspid, connection);

	debug("serve9p %d: shut down\n", fspid);

	exits(nil);
}
