/*
 * Framework for USB devices that provide a file tree.
 * The main process (usbd or the driver's main proc)
 * calls fsinit() to start FS operation.
 *
 * One or more drivers call fsstart/fsend to register
 * or unregister their operations for their subtrees.
 *
 * root dir has qids with 0 in high 32 bits.
 * for other files we keep the device id in there.
 * The low 32 bits for directories at / must be 0.
 */
#include <u.h>
#include <libc.h>
#include <thread.h>
#include <fcall.h>
#include "usb.h"
#include "usbfs.h"

#undef dprint
#define dprint if(usbfsdebug)fprint

typedef struct Rpc Rpc;

enum
{
	Nproc = 3,		/* max nb. of cached FS procs */

	Nofid = ~0,		/* null value for fid number */
	Notag = ~0,		/* null value for tags */
	Dietag = 0xdead,		/* fake tag to ask outproc to die */

	Stack = 16 * 1024,

	/* Fsproc requests */
	Run = 0,		/* call f(r) */
	Exit,			/* terminate */

};

struct Rpc
{
	Fcall	t;
	Fcall	r;
	Fid*	fid;
	int	flushed;
	Rpc*	next;
	char	data[Bufsize];
};

int usbfsdebug;

char Enotfound[] = "file not found";
char Etoosmall[] = "parameter too small";
char Eio[] = "i/o error";
char Eperm[] = "permission denied";
char Ebadcall[] = "unknown fs call";
char Ebadfid[] = "fid not found";
char Einuse[] = "fid already in use";
char Eisopen[] = "it is already open";
char Ebadctl[] = "unknown control request";

static char *user;
static ulong epoch;
static ulong msgsize = Msgsize;
static int fsfd = -1;
static Channel *outc;	/* of Rpc* */

static QLock rpclck;	/* protect vars in this block */
static Fid *freefids;
static Fid *fids;
static Rpc *freerpcs;
static Rpc *rpcs;

static Channel*procc;
static Channel*endc;

static Usbfs* fsops;

static void fsioproc(void*);

static void
schedproc(void*)
{
	Channel *proc[Nproc];
	int nproc;
	Channel *p;

	Alt a[] =
	{
		{procc, &proc[0], CHANSND},
		{endc, &p, CHANRCV},
		{nil, nil, CHANEND}
	};
	memset(proc, 0, sizeof(proc));
	nproc = 0;
	for(;;){
		if(nproc == 0){
			proc[0] = chancreate(sizeof(Rpc*), 0);
			proccreate(fsioproc, proc[0], Stack);
			nproc++;
		}
		switch(alt(a)){
		case 0:
			proc[0] = nil;
			if(nproc > 1){
				proc[0] = proc[nproc-1];
				proc[nproc-1] = nil;
			}
			nproc--;
			break;
		case 1:
			if(nproc < nelem(proc))
				proc[nproc++] = p;
			else
				sendp(p, nil);
			break;
		default:
			sysfatal("alt");
		}
	}
}

static void
dump(void)
{
	Rpc *rpc;
	Fid *fid;

	qlock(&rpclck);
	fprint(2, "dump:\n");
	for(rpc = rpcs; rpc != nil; rpc = rpc->next)
		fprint(2, "rpc %#p %F next %#p\n", rpc, &rpc->t, rpc->next);
	for(fid = fids; fid != nil; fid = fid->next)
		fprint(2, "fid %d qid %#llux omode %d aux %#p\n",
			fid->fid, fid->qid.path, fid->omode, fid->aux);
	fprint(2, "\n");
	qunlock(&rpclck);
}

static Rpc*
newrpc(void)
{
	Rpc *r;

	qlock(&rpclck);
	r = freerpcs;
	if(r != nil)
		freerpcs = r->next;
	else
		r = emallocz(sizeof(Rpc), 0);
	r->next = rpcs;
	rpcs = r;
	r->t.tag = r->r.tag = Notag;
	r->t.fid = r->r.fid = Nofid;
	r->t.type = r->r.type = 0;
	r->flushed = 0;
	r->fid = nil;
	r->r.data = (char*)r->data;
	qunlock(&rpclck);
	return r;
}

static void
freerpc(Rpc *r)
{
	Rpc **l;
	if(r == nil)
		return;
	qlock(&rpclck);
	for(l = &rpcs; *l != nil && *l != r; l = &(*l)->next)
		;
	assert(*l == r);
	*l = r->next;
	r->next = freerpcs;
	freerpcs = r;
	r->t.type = 0;
	r->t.tag = 0x77777777;
	qunlock(&rpclck);
}

static void
flushrpc(int tag)
{
	Rpc *r;

	qlock(&rpclck);
	for(r = rpcs; r != nil; r = r->next)
		if(r->t.tag == tag){
			r->flushed = 1;
			break;
		}
	qunlock(&rpclck);
}

static Fid*
getfid(int fid, int alloc)
{
	Fid *f;

	qlock(&rpclck);
	for(f = fids; f != nil && f->fid != fid; f = f->next)
		;
	if(f != nil && alloc != 0){	/* fid in use */
		qunlock(&rpclck);
		return nil;
	}
	if(f == nil && alloc != 0){
		if(freefids != nil){
			f = freefids;
			freefids = freefids->next;
		}else
			f = emallocz(sizeof(Fid), 1);
		f->fid = fid;
		f->aux = nil;
		f->omode = ONONE;
		f->next = fids;
		fids = f;
	}
	qunlock(&rpclck);
	return f;
}

static void
freefid(Fid *f)
{
	Fid **l;

	if(f == nil)
		return;
	if(fsops->clunk != nil)
		fsops->clunk(fsops, f);
	qlock(&rpclck);
	for(l = &fids; *l != nil && *l != f; l = &(*l)->next)
		;
	assert(*l == f);
	*l = f->next;
	f->next = freefids;
	freefids = f;
	qunlock(&rpclck);
}

static Rpc*
fserror(Rpc *rpc, char* fmt, ...)
{
	va_list arg;
	char *c;

	va_start(arg, fmt);
	c = (char*)rpc->data;
	vseprint(c, c+sizeof(rpc->data), fmt, arg);
	va_end(arg);
	rpc->r.type = Rerror;
	rpc->r.ename = (char*)rpc->data;
	return rpc;
}

static Rpc*
fsversion(Rpc *r)
{
	if(r->t.msize < 256)
		return fserror(r, Etoosmall);
	if(strncmp(r->t.version, "9P2000", 6) != 0)
		return fserror(r, "wrong version");
	if(r->t.msize < msgsize)
		msgsize = r->t.msize;
	r->r.msize = msgsize;
	r->r.version = "9P2000";
	return r;
}

static Rpc*
fsattach(Rpc *r)
{
	static int already;

	/* Reload user because at boot it could be still none */
	user=getuser();
	if(already++ > 0 && strcmp(r->t.uname, user) != 0)
		return fserror(r, Eperm);
	if(r->fid == nil)
		return fserror(r, Einuse);

	r->r.qid.type = QTDIR;
	r->r.qid.path = fsops->qid;
	r->r.qid.vers = 0;
	r->fid->qid = r->r.qid;
	return r;
}

static Rpc*
fswalk(Rpc *r)
{
	int i;
	Fid *nfid, *ofid;

	if(r->fid->omode != ONONE)
		return fserror(r, Eisopen);

	nfid = nil;
	ofid = r->fid;
	if(r->t.newfid != r->t.fid){
		nfid = getfid(r->t.newfid, 1);
		if(nfid == nil)
			return fserror(r, Einuse);
		nfid->qid = r->fid->qid;
		if(fsops->clone != nil)
			fsops->clone(fsops, ofid, nfid);
		else
			nfid->aux = r->fid->aux;
		r->fid = nfid;
	}
	r->r.nwqid = 0;
	for(i = 0; i < r->t.nwname; i++)
		if(fsops->walk(fsops, r->fid, r->t.wname[i]) < 0)
			break;
		else
			r->r.wqid[i] = r->fid->qid;
	r->r.nwqid = i;
	if(i != r->t.nwname && r->t.nwname > 0){
		if(nfid != nil)
			freefid(nfid);
		r->fid = ofid;
	}
	if(i == 0 && r->t.nwname > 0)
		return fserror(r, "%r");
	return r;
}

static void
fsioproc(void* a)
{
	long rc;
	Channel *p = a;
	Rpc *rpc;
	Fcall *t, *r;
	Fid *fid;

	threadsetname("fsioproc");
	dprint(2, "%s: fsioproc pid %d\n", argv0, getpid());
	while((rpc = recvp(p)) != nil){
		t = &rpc->t;
		r = &rpc->r;
		fid = rpc->fid;
		rc = -1;
		dprint(2, "%s: fsioproc pid %d: req %d\n", argv0, getpid(), t->type);
		switch(t->type){
		case Topen:
			rc = fsops->open(fsops, fid, t->mode);
			if(rc >= 0){
				r->iounit = 0;
				r->qid = fid->qid;
				fid->omode = t->mode & 3;
			}
			break;
		case Tread:
			rc = fsops->read(fsops, fid, r->data, t->count, t->offset);
			if(rc >= 0){
				if(rc > t->count)
					print("%s: bug: read %ld bytes > %ud wanted\n",
						argv0, rc, t->count);
				r->count = rc;
			}
			/*
			 * TODO: if we encounter a long run of continuous read
			 * errors, we should do something more drastic so that
			 * our caller doesn't just spin its wheels forever.
			 */
			break;
		case Twrite:
			rc = fsops->write(fsops, fid, t->data, t->count, t->offset);
			r->count = rc;
			break;
		default:
			sysfatal("fsioproc: bad type");
		}
		if(rc < 0)
			sendp(outc, fserror(rpc, "%r"));
		else
			sendp(outc, rpc);
		sendp(endc, p);
	}
	chanfree(p);
	dprint(2, "%s: fsioproc %d exiting\n", argv0, getpid());
	threadexits(nil);
}

static Rpc*
fsopen(Rpc *r)
{
	Channel *p;

	if(r->fid->omode != ONONE)
		return fserror(r, Eisopen);
	if((r->t.mode & 3) != OREAD && (r->fid->qid.type & QTDIR) != 0)
		return fserror(r, Eperm);
	p = recvp(procc);
	sendp(p, r);
	return nil;
}

int
usbdirread(Usbfs*f, Qid q, char *data, long cnt, vlong off, Dirgen gen, void *arg)
{
	int i, n, nd;
	char name[Namesz];
	Dir d;

	memset(&d, 0, sizeof(d));
	d.name = name;
	d.uid = d.gid = d.muid = user;
	d.atime = time(nil);
	d.mtime = epoch;
	d.length = 0;
	for(i = n = 0; gen(f, q, i, &d, arg) >= 0; i++){
		if(usbfsdebug > 1)
			fprint(2, "%s: dir %d q %#llux: %D\n", argv0, i, q.path, &d);
		nd = convD2M(&d, (uchar*)data+n, cnt-n);
		if(nd <= BIT16SZ)
			break;
		if(off > 0)
			off -= nd;
		else
			n += nd;
		d.name = name;
		d.uid = d.gid = d.muid = user;
		d.atime = time(nil);
		d.mtime = epoch;
		d.length = 0;
	}
	return n;
}

long
usbreadbuf(void *data, long count, vlong offset, void *buf, long n)
{
	if(offset >= n)
		return 0;
	if(offset + count > n)
		count = n - offset;
	memmove(data, (char*)buf + offset, count);
	return count;
}

static Rpc*
fsread(Rpc *r)
{
	Channel *p;

	if(r->fid->omode != OREAD && r->fid->omode != ORDWR)
		return fserror(r, Eperm);
	p = recvp(procc);
	sendp(p, r);
	return nil;
}

static Rpc*
fswrite(Rpc *r)
{
	Channel *p;

	if(r->fid->omode != OWRITE && r->fid->omode != ORDWR)
		return fserror(r, Eperm);
	p = recvp(procc);
	sendp(p, r);
	return nil;
}

static Rpc*
fsclunk(Rpc *r)
{
	freefid(r->fid);
	return r;
}

static Rpc*
fsno(Rpc *r)
{
	return fserror(r, Eperm);
}

static Rpc*
fsstat(Rpc *r)
{
	Dir d;
	char name[Namesz];

	memset(&d, 0, sizeof(d));
	d.name = name;
	d.uid = d.gid = d.muid = user;
	d.atime = time(nil);
	d.mtime = epoch;
	d.length = 0;
	if(fsops->stat(fsops, r->fid->qid, &d) < 0)
		return fserror(r, "%r");
	r->r.stat = (uchar*)r->data;
	r->r.nstat = convD2M(&d, (uchar*)r->data, msgsize);
	return r;
}

static Rpc*
fsflush(Rpc *r)
{
	/*
	 * Flag it as flushed and respond.
	 * Either outproc will reply to the flushed request
	 * before responding to flush, or it will never reply to it.
	 * Note that we do NOT abort the ongoing I/O.
	 * That might leave the affected endpoints in a failed
	 * state. Instead, we pretend the request is aborted.
	 *
	 * Only open, read, and write are processed
	 * by auxiliary processes and other requests wil never be
	 * flushed in practice.
	 */
	flushrpc(r->t.oldtag);
	return r;
}

Rpc* (*fscalls[])(Rpc*) = {
	[Tversion]	fsversion,
	[Tauth]		fsno,
	[Tattach]	fsattach,
	[Twalk]		fswalk,
	[Topen]		fsopen,
	[Tcreate]	fsno,
	[Tread]		fsread,
	[Twrite]	fswrite,
	[Tclunk]	fsclunk,
	[Tremove]	fsno,
	[Tstat]		fsstat,
	[Twstat]	fsno,
	[Tflush]	fsflush,
};

static void
outproc(void*)
{
	static uchar buf[Bufsize];
	Rpc *rpc;
	int nw;
	static int once = 0;

	if(once++ != 0)
		sysfatal("more than one outproc");
	threadsetname("outproc");
	for(;;){
		do
			rpc = recvp(outc);
		while(rpc == nil);		/* a delayed reply */
		if(rpc->t.tag == Dietag)
			break;
		if(rpc->flushed){
			dprint(2, "outproc: tag %d flushed\n", rpc->t.tag);
			freerpc(rpc);
			continue;
		}
		dprint(2, "-> %F\n", &rpc->r);
		nw = convS2M(&rpc->r, buf, sizeof(buf));
		if(nw == sizeof(buf))
			fprint(2, "%s: outproc: buffer is too small\n", argv0);
		if(nw <= BIT16SZ)
			fprint(2, "%s: conS2M failed\n", argv0);
		else if(write(fsfd, buf, nw) != nw){
			fprint(2, "%s: outproc: write: %r", argv0);
			/* continue and let the reader abort us */
		}
		if(usbfsdebug > 1)
			dump();
		freerpc(rpc);
	}
	dprint(2, "%s: outproc: exiting\n", argv0);
}

static void
usbfs(void*)
{
	Rpc *rpc;
	int nr;
	static int once = 0;

	if(once++ != 0)
		sysfatal("more than one usbfs proc");

	threadsetname("usbfs");
	outc = chancreate(sizeof(Rpc*), 1);
	procc = chancreate(sizeof(Channel*), 0);
	endc = chancreate(sizeof(Channel*), 0);
	if(outc == nil || procc == nil || endc == nil)
		sysfatal("chancreate: %r");
	threadcreate(schedproc, nil, Stack);
	proccreate(outproc, nil, Stack);
	for(;;){
		rpc = newrpc();
		do{
			nr = read9pmsg(fsfd, rpc->data, sizeof(rpc->data));
		}while(nr == 0);
		if(nr < 0){
			dprint(2, "%s: usbfs: read: '%r'", argv0);
			if(fsops->end != nil)
				fsops->end(fsops);
			else
				closedev(fsops->dev);
			rpc->t.tag = Dietag;
			sendp(outc, rpc);
			break;
		}
		if(convM2S((uchar*)rpc->data, nr, &rpc->t) <=0){
			dprint(2, "%s: convM2S failed\n", argv0);
			freerpc(rpc);
			continue;
		}
		dprint(2, "<- %F\n", &rpc->t);
		rpc->r.tag = rpc->t.tag;
		rpc->r.type = rpc->t.type + 1;
		rpc->r.fid = rpc->t.fid;
		if(fscalls[rpc->t.type] == nil){
			sendp(outc, fserror(rpc, Ebadcall));
			continue;
		}
		if(rpc->t.fid != Nofid){
			if(rpc->t.type == Tattach)
				rpc->fid = getfid(rpc->t.fid, 1);
			else
				rpc->fid = getfid(rpc->t.fid, 0);
			if(rpc->fid == nil){
				sendp(outc, fserror(rpc, Ebadfid));
				continue;
			}
		}
		sendp(outc, fscalls[rpc->t.type](rpc));
	}
	dprint(2, "%s: ubfs: eof: exiting\n", argv0);
}

void
usbfsinit(char* srv, char *mnt, Usbfs *f, int flag)
{
	int fd[2];
	int sfd;
	int afd;
	char sfile[40];

	fsops = f;
	if(pipe(fd) < 0)
		sysfatal("pipe: %r");
	user = getuser();
	epoch = time(nil);

	fmtinstall('D', dirfmt);
	fmtinstall('M', dirmodefmt);
	fmtinstall('F', fcallfmt);
	fsfd = fd[1];
	procrfork(usbfs, nil, Stack, RFNAMEG);	/* no RFFDG */
	if(srv != nil){
		snprint(sfile, sizeof(sfile), "#s/%s", srv);
		remove(sfile);
		sfd = create(sfile, OWRITE, 0660);
		if(sfd < 0)
			sysfatal("post: %r");
		snprint(sfile, sizeof(sfile), "%d", fd[0]);
		if(write(sfd, sfile, strlen(sfile)) != strlen(sfile))
			sysfatal("post: %r");
		close(sfd);
	}
	if(mnt != nil){
		sfd = dup(fd[0], -1);	/* debug */
		afd = fauth(sfd, "");
		if(afd >= 0)
			sysfatal("authentication required??");
		if(mount(sfd, -1, mnt, flag, "") < 0)
			sysfatal("mount: %r");
	}
	close(fd[0]);
}

