#include	"dat.h"
#include	"fns.h"
#include	"error.h"

typedef struct Srv Srv;
struct Srv
{
	Ref;
	int	fd;	/* fd for opened /srv or /srv/X, or -1 */
	int	sfd;	/* fd for created /srv entry or -1 */
	uvlong	path;
	Srv	*next;
};

static QLock	srv9lk;
static Srv	*srv9;
static Srv	*srvroot;

static char*
srvname(Chan *c)
{
	char *p;

	p = strrchr(c->name->s, '/');
	if(p == nil)
		return "";
	return p+1;
}

static Srv*
srvget(uvlong path)
{
	Srv *sv;

	qlock(&srv9lk);
	for(sv = srv9; sv != nil; sv = sv->next)
		if(sv->path == path){
			incref(sv);
			qunlock(&srv9lk);
			return sv;
		}
	sv = smalloc(sizeof(*sv));
	sv->path = path;
	sv->fd = -1;
	sv->sfd = -1;
	sv->ref = 1;
	sv->next = srv9;
	srv9 = sv;
	qunlock(&srv9lk);
	return sv;
}

static void
srvput(Srv *sv)
{
	Srv **l;
	int fd, sfd;

	if(sv != nil && decref(sv) == 0){
		qlock(&srv9lk);
		for(l = &srv9; *l != nil; l = &(*l)->next)
			if(*l == sv){
				*l = sv->next;
				break;
			}
		qunlock(&srv9lk);
		fd = sv->fd;
		sfd = sv->sfd;
		free(sv);
		if(sfd >= 0){
			osenter();
			close(sfd);
			osleave();
		}
		if(fd >= 0){
			osenter();
			close(fd);
			osleave();
		}
	}
}

static void
srv9init(void)
{
	Srv *sv;

	sv = mallocz(sizeof(*srvroot), 1);
	sv->path = 0;
	sv->fd = -1;
	sv->ref = 1;	/* subsequently never reaches zero */
	srvroot = srv9 = sv;
}

static Chan*
srv9attach(char *spec)
{
	Chan *c;

	if(*spec)
		error(Ebadspec);
	c = devattach(L'₪', spec);
	if(c != nil){
		incref(srvroot);
		c->aux = srvroot;
	}
	return c;
}

static Walkqid*
srv9walk(Chan *c, Chan *nc, char **name, int nname)
{
	int j, alloc;
	Walkqid *wq;
	char *n;
	Dir *d;

	if(nname > 0)
		isdir(c);

	alloc = 0;
	wq = smalloc(sizeof(Walkqid)+(nname-1)*sizeof(Qid));
	if(waserror()){
		if(alloc)
			cclose(wq->clone);
		free(wq);
		return nil;
	}
	if(nc == nil){
		nc = devclone(c);
		nc->type = 0;	/* device doesn't know about this channel yet */
		alloc = 1;
	}
	wq->clone = nc;

	for(j=0; j<nname; j++){
		if(!(nc->qid.type&QTDIR)){
			if(j==0)
				error(Enotdir);
			break;
		}
		n = name[j];
		if(strcmp(n, ".") != 0 && strcmp(n, "..") != 0){
			snprint(up->genbuf, sizeof(up->genbuf), "/srv/%s", n);
			d = dirstat(up->genbuf);
			if(d == nil){
				if(j == 0)
					error(Enonexist);
				kstrcpy(up->env->errstr, Enonexist, ERRMAX);
				break;
			}
			nc->qid = d->qid;
			free(d);
		}
		wq->qid[wq->nqid++] = nc->qid;
	}
	poperror();
	if(wq->nqid < nname){
		if(alloc)
			cclose(wq->clone);
		wq->clone = nil;
	}else{
		/* attach cloned channel to device */
		wq->clone->type = c->type;
		if(wq->clone != c)
			nc->aux = srvget(nc->qid.path);
	}
	return wq;
}

static int
srv9stat(Chan *c, uchar *db, int n)
{
	Srv *sv;
	Dir d;

	if(c->qid.type & QTDIR){
		devdir(c, c->qid, "#₪", 0, eve, 0775, &d);
		n = convD2M(&d, db, n);
		if(n == 0)
			error(Eshortstat);
		return n;
	}
	sv = c->aux;
	if(sv->fd >= 0){
		osenter();
		n = fstat(sv->fd, db, n);
		osleave();
	}else{
		osenter();
		n = stat(srvname(c), db, n);
		osleave();
	}
	return n;
}

static Chan*
srv9open(Chan *c, int omode)
{
	Srv *sv;
	char *args[10];
	int fd[2], i, ifd, is9p;
	Dir *d;

	sv = c->aux;
	if(c->qid.type == QTDIR){
		osenter();
		sv->fd = open("/srv", omode);
		osleave();
		if(sv->fd < 0)
			oserror();
		c->mode = omode;
		c->flag |= COPEN;
		c->offset = 0;
		return c;
	}

	if(omode&OTRUNC || openmode(omode) != ORDWR)
		error(Eperm);
	if(sv->fd < 0){
		snprint(up->genbuf, sizeof(up->genbuf), "/srv/%s", srvname(c));

		/* check permission */
		osenter();
		ifd = open(up->genbuf, omode);
		osleave();
		if(ifd < 0)
			oserror();
		osenter();
		d = dirfstat(ifd);
		is9p = d != nil && d->qid.type & QTMOUNT;
		free(d);
		osleave();

		if(is9p){
			close(ifd);

			/* spawn exportfs */
			args[0] = "exportfs";
			args[1] = "-S";
			args[2] = up->genbuf;
			args[3] = nil;
			if(pipe(fd) < 0)
				oserror();
			/* TO DO: without RFMEM there's a copy made of each page touched by any kproc until the exec */
			switch(rfork(RFPROC|RFNOWAIT|RFREND|RFFDG|RFNAMEG|RFENVG)){	/* no sharing except NOTEG */
			case -1:
				oserrstr(up->genbuf, sizeof(up->genbuf));
				close(fd[0]);
				close(fd[1]);
				error(up->genbuf);
			case 0:
				for(i=3; i<MAXNFD; i++)
					if(i != fd[1])
						close(i);
				dup(fd[1], 0);
				if(fd[0] != 0)
					close(fd[0]);
				dup(0, 1);
				exec("/bin/exportfs", args);
				exits("exportfs failed");
			default:
				sv->fd = fd[0];
				close(fd[1]);
				break;
			}
		}else
			sv->fd = ifd;
	}

	c->mode = ORDWR;
	c->offset = 0;
	c->flag |= COPEN;
	return c;
}

static void
srv9close(Chan *c)
{
	srvput(c->aux);
}

static long
srv9read(Chan *c, void *va, long n, vlong off)
{
	Srv *sv;

	sv = c->aux;
	osenter();
	n = pread(sv->fd, va, n, off);
	osleave();
	if(n < 0)
		oserror();
	return n;
}

static long
srv9write(Chan *c, void *va, long n, vlong off)
{
	Srv *sv;

	sv = c->aux;
	osenter();
	n = pwrite(sv->fd, va, n, off);
	osleave();
	if(n == 0)
		error(Ehungup);
	if(n < 0)
		oserror();
	return n;
}

static void
srv9create(Chan *c, char *name, int omode, ulong perm)
{
	Srv *sv;
	int sfd, fd[2];
	vlong path;
	Dir *d;

	if(openmode(omode) != ORDWR)
		error(Eperm);

	if(pipe(fd) < 0)
		oserror();
	if(waserror()){
		close(fd[0]);
		close(fd[1]);
		nexterror();
	}

	snprint(up->genbuf, sizeof(up->genbuf), "/srv/%s", name);
	osenter();
	sfd = create(up->genbuf, OWRITE|ORCLOSE, perm);
	osleave();
	if(sfd < 0)
		oserror();
	if(waserror()){
		close(sfd);
		nexterror();
	}
	osenter();
	if(fprint(sfd, "%d", fd[1]) < 0){
		osleave();
		oserror();
	}
	d = dirfstat(sfd);
	osleave();
	if(d != nil){
		path = d->qid.path;
		free(d);
	}else
		oserror();

	poperror();
	poperror();
	close(fd[1]);

	if(waserror()){
		close(sfd);
		close(fd[0]);
		nexterror();
	}
	sv = srvget(path);
	sv->fd = fd[0];
	sv->sfd = sfd;
	poperror();

	srvput((Srv*)c->aux);
	c->qid.type = QTFILE;
	c->qid.path = path;
	c->aux = sv;
	c->flag |= COPEN;
	c->mode = ORDWR;
	c->offset = 0;
}

Dev srv9devtab = {
	L'₪',
	"srv9",

	srv9init,	
	srv9attach,
	srv9walk,
	srv9stat,
	srv9open,
	srv9create,	/* TO DO */
	srv9close,
	srv9read,
	devbread,
	srv9write,
	devbwrite,
	devremove,	/* TO DO */
	devwstat,	/* TO DO */
};
