/* This device has been ported from 9front.
 */
#include	"u.h"
#include	"../port/lib.h"
#include	"mem.h"
#include	"dat.h"
#include	"fns.h"
#include	"../port/error.h"

extern Dev* devtab[];

enum {
	Qroot,
	Qcroot,
	Qshr,
	Qcshr,
	Qcmpt,
};

typedef struct Ent Ent;
typedef struct Shr Shr;
typedef struct Mpt Mpt;
typedef struct Sch Sch;

struct Ent
{
	Ref	ref;
	int	id;
	char	*name;
	char	*owner;
	uint32_t	perm;
};

struct Shr
{
	Ent;
	Mhead	umh;
	Shr	*next;
};

struct Mpt
{
	Ent;
	Mount	m;
};

struct Sch
{
	int	level;

	Shr	*shr;
	Mpt	*mpt;

	Chan	*chan;
};

static QLock	shrslk;
static Shr	*shrs;

static int	shrid;
static int	mptid;

static Mpt*
tompt(Mount *m)
{
	return (Mpt*)((char*)m - (char*)&((Mpt*)0)->m);
}

static Sch*
tosch(Chan *c)
{
	Sch *sch;

	if(c == nil)
		error("nil chan");
	sch = c->aux;
	if(sch == nil)
		error("nil chan aux");
	if(sch->chan != c)
		error("bad chan");
	return sch;
}

static void
shrinit(void)
{
	shrid = 1;
	mptid = 1;
}

static void
putmpt(Mpt *mpt)
{
	if(decref(&mpt->ref))
		return;
	if(mpt->m.to != nil)
		cclose(mpt->m.to);
	jehanne_free(mpt->name);
	jehanne_free(mpt->owner);
	jehanne_free(mpt);
}

static void
putshr(Shr *shr)
{
	if(decref(&shr->ref))
		return;
	jehanne_free(shr->name);
	jehanne_free(shr->owner);
	jehanne_free(shr);
}

static Qid
shrqid(int level, int id)
{
	Qid q;

	q.type = (level == Qcmpt) ? QTFILE : QTDIR;
	q.path = (uint64_t)id<<4 | level;
	q.vers = 0;
	return q;
}

static Chan*
shrattach(Chan *c, Chan *ac, char *spec, int flags)
{
	Sch *sch;

	if(!(spec[0] == 'c' && spec[1] == 0 || spec[0] == 0))
		error(Enoattach);
	c = devattach(L'σ', spec);

	sch = smalloc(sizeof(*sch));
	sch->level = spec[0] == 'c' ? Qcroot : Qroot;
	sch->mpt = nil;
	sch->shr = nil;
	sch->chan = c;
	c->aux = sch;
	c->qid = shrqid(sch->level, 0);

	return c;
}

static Chan*
shrclone(Chan *c)
{
	Chan *nc;
	Sch *sch, *och;

	och = tosch(c);
	nc = devclone(c);
	nc->dev = c->dev; 	// devclone does not fill ->dev
				// this is a plan9-9k specific behaviour, but why?
				// many related places are marked with XDYNX
	sch = smalloc(sizeof(*sch));
	jehanne_memmove(sch, och, sizeof(*sch));
	if(sch->shr != nil)
		incref(&sch->shr->ref);
	if(sch->mpt != nil)
		incref(&sch->mpt->ref);
	sch->chan = nc;
	nc->aux = sch;
	return nc;
}

static void
shrclunk(Chan *c)
{
	Sch *sch;

	sch = tosch(c);
	c->aux = nil;
	sch->chan = nil;
	if(sch->mpt != nil)
		putmpt(sch->mpt);
	if(sch->shr != nil)
		putshr(sch->shr);
	jehanne_free(sch);
}

static Walkqid*
shrwalk(Chan *c, Chan *nc, char **name, int nname)
{
	Walkqid *wq, *wq2;
	int alloc, j;
	char *nam;
	Sch *sch;
	Shr *shr;
	Mpt *mpt;
	Mount *m;
	Mhead *h;

	alloc = 0;
	if(nc == nil){
		nc = shrclone(c);
		alloc = 1;
	}
	wq = smalloc(sizeof(Walkqid) + (nname - 1) * sizeof(Qid));
	wq->nqid = 0;
	wq->clone = nc;
	if(waserror()){
		if(alloc)
			cclose(wq->clone);
		if(wq->nqid > 0)
			wq->clone = nil;
		else {
			jehanne_free(wq);
			wq = nil;
		}
		return wq;
	}
	sch = tosch(nc);
	for(j = 0; j < nname; j++){
		if(nc->qid.type != QTDIR)
			error(Enotdir);

		nam = name[j];
		if(nam[0] == '.' && nam[1] == 0) {
			/* nop */
		} else if(nam[0] == '.' && nam[1] == '.' && nam[2] == 0) {
			switch(sch->level){
			default:
				error(Egreg);
			case Qshr:
				nc->qid = shrqid(sch->level = Qroot, 0);
				break;
			case Qcshr:
				nc->qid = shrqid(sch->level = Qcroot, 0);
				break;
			}
			putshr(sch->shr);
			sch->shr = nil;
		} else if(sch->level == Qcroot || sch->level == Qroot) {
			qlock(&shrslk);
			for(shr = shrs; shr != nil; shr = shr->next)
				if(jehanne_strcmp(nam, shr->name) == 0){
					incref(&shr->ref);
					break;
				}
			qunlock(&shrslk);
			if(shr == nil)
				error(Enonexist);
			sch->level = sch->level == Qcroot ? Qcshr : Qshr;
			sch->shr = shr;
			nc->qid = shrqid(sch->level, shr->id);
		} else if(sch->level == Qcshr) {
			mpt = nil;
			shr = sch->shr;
			h = &shr->umh;
			rlock(&h->lock);
			for(m = h->mount; m != nil; m = m->next){
				mpt = tompt(m);
				if(jehanne_strcmp(nam, mpt->name) == 0){
					incref(&mpt->ref);
					break;
				}
			}
			runlock(&h->lock);
			if(m == nil)
				error(Enonexist);
			sch->mpt = mpt;
			nc->qid = shrqid(sch->level = Qcmpt, mpt->id);
		} else if(sch->level == Qshr) {
			shr = sch->shr;
			h = &shr->umh;
			wq2 = nil;
			rlock(&h->lock);
			for(m = h->mount; m != nil && wq2 == nil; m = m->next){
				if(m->to == nil)
					continue;
				if(waserror())
					continue;
				wq2 = m->to->dev->walk(m->to, nil, name + j, nname - j);
				poperror();
			}
			runlock(&h->lock);
			if(wq2 == nil)
				error(Enonexist);
			jehanne_memmove(wq->qid + wq->nqid, wq2->qid, wq2->nqid);
			wq->nqid += wq2->nqid;
			if(alloc)
				cclose(wq->clone);
			wq->clone = wq2->clone;
			jehanne_free(wq2);
			poperror();
			return wq;
		} else
			error(Egreg);
		wq->qid[wq->nqid++] = nc->qid;
	}
	poperror();
	return wq;
}

static int
shrgen(Chan *c, char* _, Dirtab* __, int ___, int s, Dir *dp)
{
	Mpt *mpt;
	Sch *sch;
	Shr *shr;
	Mhead *h;
	Mount *m;

	sch = tosch(c);
	switch(sch->level){
	default:
		return -1;
	case Qroot:
	case Qcroot:
		qlock(&shrslk);
		for(shr = shrs; shr != nil && s > 0; shr = shr->next)
			s--;
		if(shr == nil){
			qunlock(&shrslk);
			return -1;
		}
		kstrcpy(up->genbuf, shr->name, sizeof up->genbuf);
		if(sch->level == Qroot)
			devdir(c, shrqid(Qshr, shr->id), up->genbuf, 0, shr->owner,
				shr->perm & ~0222, dp);
		else
			devdir(c, shrqid(Qcshr, shr->id), up->genbuf, 0, shr->owner,
				shr->perm, dp);
		qunlock(&shrslk);
		return 1;
	case Qcshr:
		shr = sch->shr;
		h = &shr->umh;
		rlock(&h->lock);
		for(m = h->mount; m != nil && s > 0; m = m->next)
			s--;
		if(m == nil){
			runlock(&h->lock);
			return -1;
		}
		mpt = tompt(m);
		kstrcpy(up->genbuf, mpt->name, sizeof up->genbuf);
		devdir(c, shrqid(Qcmpt, mpt->id), up->genbuf, 0, mpt->owner, mpt->perm, dp);
		runlock(&h->lock);
		return 1;
	}
}

static long
shrstat(Chan *c, uint8_t *db, long n)
{
	Sch *sch;
	Dir dir;
	int rc;

	sch = tosch(c);
	switch(sch->level){
	default:
		error(Egreg);
	case Qroot:
		devdir(c, c->qid, "#σ", 0, eve, 0555, &dir);
		break;
	case Qcroot:
		devdir(c, c->qid, "#σc", 0, eve, 0777, &dir);
		break;
	case Qshr:
		devdir(c, c->qid, sch->shr->name, 0, sch->shr->owner, sch->shr->perm & ~0222, &dir);
		break;
	case Qcshr:
		devdir(c, c->qid, sch->shr->name, 0, sch->shr->owner, sch->shr->perm, &dir);
		break;
	case Qcmpt:
		devdir(c, c->qid, sch->mpt->name, 0, sch->mpt->owner, sch->mpt->perm, &dir);
		break;
	}
	rc = jehanne_convD2M(&dir, db, n);
	if(rc == 0)
		error(Ebadarg);
	return rc;
}

static Chan*
shropen(Chan *c, unsigned long omode)
{
	Chan *nc;
	Sch *sch;
	Shr *shr;
	Mpt *mpt;
	int mode;

	if(c->qid.type == QTDIR && omode != OREAD)
		error(Eisdir);

	mode = openmode(omode);
	sch = tosch(c);
	switch(sch->level){
	default:
		error(Egreg);
	case Qroot:
	case Qcroot:
		break;
	case Qshr:
	case Qcshr:
		shr = sch->shr;
		devpermcheck(shr->owner, shr->perm, mode);
		break;
	case Qcmpt:
		if(omode&OTRUNC)
			error(Eexist);
		shr = sch->shr;
		mpt = sch->mpt;
		devpermcheck(mpt->owner, mpt->perm, mode);
		rlock(&shr->umh.lock);
		if(mpt->m.to == nil || mpt->m.to->mchan == nil){
			runlock(&shr->umh.lock);
			error(Eshutdown);
		}
		nc = mpt->m.to->mchan;
		incref(&nc->r);
		runlock(&shr->umh.lock);
		if(mode != nc->mode){
			cclose(nc);
			error(Eperm);
		}
		cclose(c);
		return nc;
	}
	c->mode = mode;
	c->flag |= COPEN;
	c->offset = 0;
	return c;
}

/* chan.c */
Chan* createdir(Chan *c, Mhead *m);

static Chan*
shrcreate(Chan *c, char *name, unsigned long omode, unsigned long perm)
{
	Sch *sch;
	Shr *shr;
	Mpt *mpt;
	Mhead *h;
	Mount *m;
	Chan *nc;
	int mode;

	mode = openmode(omode);
	sch = tosch(c);
	switch(sch->level){
	case Qcroot:
	case Qcshr:
		if(jehanne_strcmp(up->user, "none") == 0)
			error(Eperm);
	}
	switch(sch->level){
	default:
		error(Eperm);
	case Qshr:
		incref(&c->r);
		if(waserror()){
			cclose(c);
			nexterror();
		}
		nc = createdir(c, &sch->shr->umh);
		poperror();
		if(waserror()){
			cclose(nc);
			nexterror();
		}
		nc->dev->create(nc, name, omode, perm);
		poperror();
		cclose(c);
		return nc;
	case Qcroot:
		if(up->pgrp->noattach)
			error(Enoattach);
		if((perm & DMDIR) == 0 || mode != OREAD)
			error(Eperm);
		if(jehanne_strlen(name) >= sizeof(up->genbuf))
			error(Etoolong);
		qlock(&shrslk);
		if(waserror()){
			qunlock(&shrslk);
			nexterror();
		}
		for(shr = shrs; shr != nil; shr = shr->next)
			if(jehanne_strcmp(name, shr->name) == 0)
				error(Eexist);

		shr = smalloc(sizeof(*shr));
		incref(&shr->ref);
		shr->id = shrid++;

		kstrdup(&shr->name, name);
		kstrdup(&shr->owner, up->user);
		shr->perm = perm;

		incref(&shr->ref);
		shr->next = shrs;
		shrs = shr;

		poperror();
		qunlock(&shrslk);

		c->qid = shrqid(sch->level = Qcshr, shr->id);
		sch->shr = shr;
		break;
	case Qcshr:
		if(up->pgrp->noattach)
			error(Enoattach);
		if((perm & DMDIR) != 0 || mode != OWRITE)
			error(Eperm);

		shr = sch->shr;
		if(jehanne_strcmp(shr->owner, eve) == 0 && !iseve())
			error(Eperm);
		devpermcheck(shr->owner, shr->perm, ORDWR);

		if(jehanne_strlen(name) >= sizeof(up->genbuf))
			error(Etoolong);

		h = &shr->umh;
		wlock(&h->lock);
		if(waserror()){
			wunlock(&h->lock);
			nexterror();
		}
		for(m = h->mount; m != nil; m = m->next){
			mpt = tompt(m);
			if(jehanne_strcmp(name, mpt->name) == 0)
				error(Eexist);
		}

		mpt = smalloc(sizeof(*mpt));
		incref(&mpt->ref);
		mpt->id = mptid++;

		kstrdup(&mpt->name, name);
		kstrdup(&mpt->owner, up->user);
		mpt->perm = perm;

		incref(&mpt->ref);
		mpt->m.mflag = (h->mount == nil) ? MCREATE : 0;
		mpt->m.next = h->mount;
		h->mount = &mpt->m;

		poperror();
		wunlock(&h->lock);

		c->qid = shrqid(sch->level = Qcmpt, mpt->id);
		sch->mpt = mpt;
		break;
	}
	c->flag |= COPEN;
	c->mode = mode;
	return c;
}

static void
shrremove(Chan *c)
{
	Mount *m, **ml;
	Shr *shr, **sl;
	Sch *sch;
	Mpt *mpt;
	Mhead *h;

	sch = tosch(c);
	if(waserror()){
		shrclunk(c);
		nexterror();
	}
	switch(sch->level){
	default:
		error(Eperm);
	case Qcshr:
	case Qcmpt:
		shr = sch->shr;
		if(!iseve()){
			if(jehanne_strcmp(shr->owner, eve) == 0)
				error(Eperm);
			devpermcheck(shr->owner, shr->perm, ORDWR);
		}
	}
	switch(sch->level){
	case Qcshr:
		h = &shr->umh;
		qlock(&shrslk);
		rlock(&h->lock);
		if(h->mount != nil){
			runlock(&h->lock);
			qunlock(&shrslk);
			error("directory not empty");
		}
		runlock(&h->lock);
		for(sl = &shrs; *sl != nil; sl = &((*sl)->next))
			if(*sl == shr){
				*sl = shr->next;
				shr->next = nil;
				putshr(shr);
				break;
			}
		qunlock(&shrslk);
		break;
	case Qcmpt:
		mpt = sch->mpt;
		m = &mpt->m;
		h = &shr->umh;
		wlock(&h->lock);
		for(ml = &h->mount; *ml != nil; ml = &((*ml)->next))
			if(*ml == m){
				*ml = m->next;
				m->next = nil;
				putmpt(mpt);
				break;
			}
		wunlock(&h->lock);
		break;
	}
	poperror();
	shrclunk(c);
}

static long
shrwstat(Chan *c, uint8_t *dp, long n)
{
	char *strs;
	Mhead *h;
	Sch *sch;
	Ent *ent;
	Dir d;

	strs = smalloc(n);
	if(waserror()){
		jehanne_free(strs);
		nexterror();
	}
	n = jehanne_convM2D(dp, n, &d, strs);
	if(n == 0)
		error(Eshortstat);

	h = nil;
	sch = tosch(c);
	switch(sch->level){
	default:
		error(Eperm);
	case Qcshr:
		ent = sch->shr;
		qlock(&shrslk);
		if(waserror()){
			qunlock(&shrslk);
			nexterror();
		}
		break;
	case Qcmpt:
		ent = sch->mpt;
		h = &sch->shr->umh;
		wlock(&h->lock);
		if(waserror()){
			wunlock(&h->lock);
			nexterror();
		}
		break;
	}

	if(jehanne_strcmp(ent->owner, up->user) && !iseve())
		error(Eperm);

	if(d.name != nil && *d.name && jehanne_strcmp(ent->name, d.name) != 0) {
		if(jehanne_strchr(d.name, '/') != nil)
			error(Ebadchar);
		if(jehanne_strlen(d.name) >= sizeof(up->genbuf))
			error(Etoolong);
		kstrdup(&ent->name, d.name);
	}
	if(d.uid != nil && *d.uid)
		kstrdup(&ent->owner, d.uid);
	if(d.mode != ~0U)
		ent->perm = d.mode & 0777;

	switch(sch->level){
	case Qcshr:
		poperror();
		qunlock(&shrslk);
		break;
	case Qcmpt:
		poperror();
		wunlock(&h->lock);
		break;
	}

	poperror();
	jehanne_free(strs);

	return n;
}

static long
shrread(Chan *c, void *va, long n, int64_t _)
{
	Mhead *omh;
	Sch *sch;

	sch = tosch(c);
	switch(sch->level){
	default:
		error(Egreg);
	case Qroot:
	case Qcroot:
	case Qcshr:
		return devdirread(c, va, n, 0, 0, shrgen);
	case Qshr:
		omh = c->umh;
		c->umh = &sch->shr->umh;
		if(waserror()){
			c->umh = omh;
			nexterror();
		}
		n = unionread(c, va, n);
		poperror();
		c->umh = omh;
		return n;
	}
}

static long
shrwrite(Chan *c, void *va, long n, int64_t _)
{
	Sch *sch;
	char *buf, *p, *aname;
	int fd;
	Chan *bc, *c0;
	Mhead *h;
	Mount *m;

	if(up->pgrp->noattach)
		error(Enoattach);
	sch = tosch(c);
	if(sch->level != Qcmpt)
		error(Egreg);

	buf = smalloc(n+1);
	if(waserror()){
		jehanne_free(buf);
		nexterror();
	}
	jehanne_memmove(buf, va, n);
	buf[n] = 0;

	fd = jehanne_strtol(buf, &p, 10);
	if(p == buf || (*p != 0 && *p != '\n'))
		error(Ebadarg);
	if(*p == '\n' && *(p+1) != 0)
		aname = p + 1;
	else
		aname = nil;

	bc = fdtochan(fd, ORDWR, 0, 1);
	if(waserror()) {
		cclose(bc);
		nexterror();
	}
	c0 = mntattach(bc, nil, aname, 0);
	poperror();
	cclose(bc);
	poperror();
	jehanne_free(buf);

	if(c0 == nil)
		error(Egreg);

	m = &sch->mpt->m;
	h = &sch->shr->umh;
	wlock(&h->lock);
	bc = m->to;
	m->to = c0;
	wunlock(&h->lock);

	if(bc != nil)
		cclose(bc);

	return n;
}

static void
shrclose(Chan *c)
{
	if(c->flag & CRCLOSE)
		shrremove(c);
	else
		shrclunk(c);
}

Dev shrdevtab = {
	L'σ',
	"shr",

	devreset,
	shrinit,
	devshutdown,
	shrattach,
	shrwalk,
	shrstat,
	shropen,
	shrcreate,
	shrclose,
	shrread,
	devbread,
	shrwrite,
	devbwrite,
	shrremove,
	shrwstat,
};

static void
chowner(Ent *ent, char *old, char *new)
{
	if(ent->owner != nil && jehanne_strcmp(old, ent->owner) == 0)
		kstrdup(&ent->owner, new);
}

void
shrrenameuser(char *old, char *new)
{
	Shr *shr;
	Mount *m;

	qlock(&shrslk);
	for(shr = shrs; shr != nil; shr = shr->next){
		wlock(&shr->umh.lock);
		for(m = shr->umh.mount; m != nil; m = m->next)
			chowner(tompt(m), old, new);
		wunlock(&shr->umh.lock);
		chowner(shr, old, new);
	}
	qunlock(&shrslk);
}
