#include <u.h>
#include <libc.h>
#include <auth.h>
#include <fcall.h>
#include "dat.h"
#include "fns.h"

enum
{
	Maxfdata	= 8192,
	Maxiosize	= IOHDRSZ+Maxfdata,
};

void io(int);
void rversion(void);
void	rattach(void);
void	rauth(void);
void	rclunk(void);
void	rcreate(void);
void	rflush(void);
void	ropen(void);
void	rread(void);
void	rremove(void);
void	rsession(void);
void	rstat(void);
void	rwalk(void);
void	rwrite(void);
void	rwstat(void);

static int	openflags(int);
static void	rmservice(void);
static void	usage(void);

#define Reqsize (sizeof(Fcall)+Maxfdata)

Fcall *req;
Fcall *rep;

uchar mdata[Maxiosize];
char fdata[Maxfdata];
uchar statbuf[STATMAX];
int errno;

static char	srvfile[64];

extern Xfsub	*xsublist[];
extern int	nclust;

jmp_buf	err_lab[16];
int	nerr_lab;
char	err_msg[ERRMAX];

int	chatty;
int	nojoliet;
int	noplan9;
int norock;

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

void
main(int argc, char **argv)
{
	int srvfd, pipefd[2], stdio;
	Xfsub **xs;

	stdio = 0;
	ARGBEGIN {
	case '9':
		noplan9 = 1;
		break;
	case 'c':
		nclust = atoi(EARGF(usage()));
		if (nclust <= 0)
			sysfatal("nclust %d non-positive", nclust);
		break;
	case 'f':
		deffile = EARGF(usage());
		break;
	case 'r':
		norock = 1;
		break;
	case 's':
		stdio = 1;
		break;
	case 'v':
		chatty = 1;
		break;
	case 'J':
		nojoliet = 1;
		break;
	default:
		usage();
	} ARGEND

	switch(argc) {
	case 0:
		break;
	case 1:
		srvname = argv[0];
		break;
	default:
		usage();
	}

	iobuf_init();
	for(xs=xsublist; *xs; xs++)
		(*(*xs)->reset)();

	if(stdio) {
		pipefd[0] = 0;
		pipefd[1] = 1;
	} else {
		close(0);
		close(1);
		open("/dev/null", OREAD);
		open("/dev/null", OWRITE);
		if(pipe(pipefd) < 0)
			panic(1, "pipe");
		sprint(srvfile, "/srv/%s", srvname);
		srvfd = create(srvfile, OWRITE|ORCLOSE, 0600);
		if(srvfd < 0)
			panic(1, srvfile);
		fprint(srvfd, "%d", pipefd[0]);
		close(pipefd[0]);
		fprint(2, "%s %d: serving %s\n", argv0, getpid(), srvfile);
	}
	srvfd = pipefd[1];

	switch(rfork(RFNOWAIT|RFNOTEG|RFFDG|RFPROC)){
	case -1:
		panic(1, "fork");
	default:
		_exits(0);
	case 0:
		break;
	}

	io(srvfd);
	exits(0);
}

void
io(int srvfd)
{
	int n, pid;
	Fcall xreq, xrep;

	req = &xreq;
	rep = &xrep;
	pid = getpid();
	fmtinstall('F', fcallfmt);

	for(;;){
		/*
		 * reading from a pipe or a network device
		 * will give an error after a few eof reads.
		 * however, we cannot tell the difference
		 * between a zero-length read and an interrupt
		 * on the processes writing to us,
		 * so we wait for the error.
		 */
		n = read9pmsg(srvfd, mdata, sizeof mdata);
		if(n < 0)
			break;
		if(n == 0)
			continue;
		if(convM2S(mdata, n, req) == 0)
			continue;

		if(chatty)
			fprint(2, "9660srv %d:<-%F\n", pid, req);

		errno = 0;
		if(!waserror()){
			err_msg[0] = 0;
			if(req->type >= nelem(fcalls) || !fcalls[req->type])
				error("bad fcall type");
			(*fcalls[req->type])();
			poperror();
		}

		if(err_msg[0]){
			rep->type = Rerror;
			rep->ename = err_msg;
		}else{
			rep->type = req->type + 1;
			rep->fid = req->fid;
		}
		rep->tag = req->tag;

		if(chatty)
			fprint(2, "9660srv %d:->%F\n", pid, rep);
		n = convS2M(rep, mdata, sizeof mdata);
		if(n == 0)
			panic(1, "convS2M error on write");
		if(write(srvfd, mdata, n) != n)
			panic(1, "mount write");
		if(nerr_lab != 0)
			panic(0, "err stack %d: %lux %lux %lux %lux %lux %lux", nerr_lab,
			err_lab[0][JMPBUFPC], err_lab[1][JMPBUFPC],
			err_lab[2][JMPBUFPC], err_lab[3][JMPBUFPC],
			err_lab[4][JMPBUFPC], err_lab[5][JMPBUFPC]);
	}
	chat("server shut down");
}

static void
usage(void)
{
	fprint(2, "usage: %s [-v] [-9Jr] [-s] [-f devicefile] [srvname]\n", argv0);
	exits("usage");
}

void
error(char *p)
{
	strecpy(err_msg, err_msg+sizeof err_msg, p);
	nexterror();
}

void
nexterror(void)
{
	longjmp(err_lab[--nerr_lab], 1);
}

void*
ealloc(long n)
{
	void *p;

	p = malloc(n);
	if(p == 0)
		error("no memory");
	return p;
}

void
setnames(Dir *d, char *n)
{
	d->name = n;
	d->uid = n+Maxname;
	d->gid = n+Maxname*2;
	d->muid = n+Maxname*3;

	d->name[0] = '\0';
	d->uid[0] = '\0';
	d->gid[0] = '\0';
	d->muid[0] = '\0';
}

void
rversion(void)
{
	if(req->msize > Maxiosize)
		rep->msize = Maxiosize;
	else
		rep->msize = req->msize;
	rep->version = "9P2000";
}

void
rauth(void)
{
	error("9660srv: authentication not required");
}

void
rflush(void)
{
}

void
rattach(void)
{
	Xfs *xf;
	Xfile *root;
	Xfsub **xs;

	chat("attach(fid=%d,uname=\"%s\",aname=\"%s\")...",
		req->fid, req->uname, req->aname);

	if(waserror()){
		xfile(req->fid, Clunk);
		nexterror();
	}
	root = xfile(req->fid, Clean);
	root->qid = (Qid){0, 0, QTDIR};
	root->xf = xf = ealloc(sizeof(Xfs));
	memset(xf, 0, sizeof(Xfs));
	xf->ref = 1;
	xf->d = getxdata(req->aname);

	for(xs=xsublist; *xs; xs++)
		if((*(*xs)->attach)(root) >= 0){
			poperror();
			xf->s = *xs;
			xf->rootqid = root->qid;
			rep->qid = root->qid;
			return;
		}
	error("unknown format");
}

Xfile*
doclone(Xfile *of, int newfid)
{
	Xfile *nf, *next;

	nf = xfile(newfid, Clean);
	if(waserror()){
		xfile(newfid, Clunk);
		nexterror();
	}
	next = nf->next;
	*nf = *of;
	nf->next = next;
	nf->fid = newfid;
	refxfs(nf->xf, 1);
	if(nf->len){
		nf->ptr = ealloc(nf->len);
		memmove(nf->ptr, of->ptr, nf->len);
	}else
		nf->ptr = of->ptr;
	(*of->xf->s->clone)(of, nf);
	poperror();
	return nf;
}

void
rwalk(void)
{
	Xfile *f, *nf;
	Isofile *oldptr;
	int oldlen;
	Qid oldqid;

	rep->nwqid = 0;
	nf = nil;
	f = xfile(req->fid, Asis);
	if(req->fid != req->newfid)
		f = nf = doclone(f, req->newfid);

	/* save old state in case of error */
	oldqid = f->qid;
	oldlen = f->len;
	oldptr = f->ptr;
	if(oldlen){
		oldptr = ealloc(oldlen);
		memmove(oldptr, f->ptr, oldlen);
	}

	if(waserror()){
		/*
		 * if nf != nil, nf == f, which is derived from req->newfid,
		 * so we can't clunk req->newfid with xfile, which would put
		 * f back on the free list, until we're done with f below.
		 */
		if(rep->nwqid == req->nwname){
			if(oldlen)
				free(oldptr);
		}else{
			/* restore previous state */
			f->qid = oldqid;
			if(f->len)
				free(f->ptr);
			f->ptr = oldptr;
			f->len = oldlen;
		}
		if(nf != nil)
			xfile(req->newfid, Clunk);
		if(rep->nwqid==req->nwname || rep->nwqid > 0){
			err_msg[0] = '\0';
			return;
		}
		nexterror();
	}

	for(rep->nwqid=0; rep->nwqid < req->nwname && rep->nwqid < MAXWELEM; rep->nwqid++){
		chat("\twalking %s\n", req->wname[rep->nwqid]);
		if(!(f->qid.type & QTDIR)){
			chat("\tnot dir: type=%#x\n", f->qid.type);
			error("walk in non-directory");
		}

		if(strcmp(req->wname[rep->nwqid], "..")==0){
			if(f->qid.path != f->xf->rootqid.path)
				(*f->xf->s->walkup)(f);
		}else
			(*f->xf->s->walk)(f, req->wname[rep->nwqid]);
		rep->wqid[rep->nwqid] = f->qid;
	}
	poperror();
	if(oldlen)
		free(oldptr);
}

void
ropen(void)
{
	Xfile *f;

	f = xfile(req->fid, Asis);
	if(f->flags&Omodes)
		error("open on open file");
	if(req->mode&ORCLOSE)
		error("no removes");
	(*f->xf->s->open)(f, req->mode);
	f->flags = openflags(req->mode);
	rep->qid = f->qid;
	rep->iounit = 0;
}

void
rcreate(void)
{
	error("no creates");
/*
	Xfile *f;

	if(strcmp(req->name, ".") == 0 || strcmp(req->name, "..") == 0)
		error("create . or ..");
	f = xfile(req->fid, Asis);
	if(f->flags&Omodes)
		error("create on open file");
	if(!(f->qid.path&CHDIR))
		error("create in non-directory");
	(*f->xf->s->create)(f, req->name, req->perm, req->mode);
	chat("f->qid=0x%8.8lux...", f->qid.path);
	f->flags = openflags(req->mode);
	rep->qid = f->qid;
*/
}

void
rread(void)
{
	Xfile *f;

	f=xfile(req->fid, Asis);
	if (!(f->flags&Oread))
		error("file not opened for reading");
	if(f->qid.type & QTDIR)
		rep->count = (*f->xf->s->readdir)(f, (uchar*)fdata, req->offset, req->count);
	else
		rep->count = (*f->xf->s->read)(f, fdata, req->offset, req->count);
	rep->data = fdata;
}

void
rwrite(void)
{
	Xfile *f;

	f=xfile(req->fid, Asis);
	if(!(f->flags&Owrite))
		error("file not opened for writing");
	rep->count = (*f->xf->s->write)(f, req->data, req->offset, req->count);
}

void
rclunk(void)
{
	Xfile *f;

	if(!waserror()){
		f = xfile(req->fid, Asis);
		(*f->xf->s->clunk)(f);
		poperror();
	}
	xfile(req->fid, Clunk);
}

void
rremove(void)
{
	error("no removes");
}

void
rstat(void)
{
	Xfile *f;
	Dir dir;

	chat("stat(fid=%d)...", req->fid);
	f=xfile(req->fid, Asis);
	setnames(&dir, fdata);
	(*f->xf->s->stat)(f, &dir);
	if(chatty)
		showdir(2, &dir);
	rep->nstat = convD2M(&dir, statbuf, sizeof statbuf);
	rep->stat = statbuf;
}

void
rwstat(void)
{
	error("no wstat");
}

static int
openflags(int mode)
{
	int flags = 0;

	switch(mode & ~(OTRUNC|OCEXEC|ORCLOSE)){
	case OREAD:
	case OEXEC:
		flags = Oread; break;
	case OWRITE:
		flags = Owrite; break;
	case ORDWR:
		flags = Oread|Owrite; break;
	}
	if(mode & ORCLOSE)
		flags |= Orclose;
	return flags;
}

void
showdir(int fd, Dir *s)
{
	char a_time[32], m_time[32];
	char *p;

	strcpy(a_time, ctime(s->atime));
	if(p=strchr(a_time, '\n'))	/* assign = */
		*p = 0;
	strcpy(m_time, ctime(s->mtime));
	if(p=strchr(m_time, '\n'))	/* assign = */
		*p = 0;
	fprint(fd, "name=\"%s\" qid=(0x%llux,%lud) type=%d dev=%d \
mode=0x%8.8lux=0%luo atime=%s mtime=%s length=%lld uid=\"%s\" gid=\"%s\"...",
		s->name, s->qid.path, s->qid.vers, s->type, s->dev,
		s->mode, s->mode,
		a_time, m_time, s->length, s->uid, s->gid);
}

#define	SIZE	1024

void
chat(char *fmt, ...)
{
	va_list arg;

	if(chatty){
		va_start(arg, fmt);
		vfprint(2, fmt, arg);
		va_end(arg);
	}
}

void
panic(int rflag, char *fmt, ...)
{
	va_list arg;
	char buf[SIZE]; int n;

	n = sprint(buf, "%s %d: ", argv0, getpid());
	va_start(arg, fmt);
	vseprint(buf+n, buf+SIZE, fmt, arg);
	va_end(arg);
	fprint(2, (rflag ? "%s: %r\n" : "%s\n"), buf);
	if(chatty){
		fprint(2, "abort\n");
		abort();
	}
	exits("panic");
}
