#include <u.h>
#include <lib9.h>
#include <thread.h>
#include <9P2000.h>
#include "dat.h"
#include "fns.h"

Chan *
chanattach(Fs *fs, int flags)
{
	Chan *ch;

	ch = emalloc(sizeof(*ch));
	ch->fs = fs;
	ch->flags = flags;
	ch->loc = cloneloc(fs, (flags & CHFDUMP) != 0 ? fs->dumprootloc : fs->rootloc);
	return ch;
}

Chan *
chanclone(Chan *ch)
{
	Chan *d;

	chbegin(ch);
	d = emalloc(sizeof(*d));
	d->fs = ch->fs;
	d->flags = ch->flags;
	d->uid = ch->uid;
	d->loc = cloneloc(ch->fs, ch->loc);
	chend(ch);
	return d;
}

int
chanwalk(Chan *ch, char *name)
{
	Buf *b;
	Dentry *d;
	Loc *l;
	FLoc f;

	if(name == nil || name[0] == 0 || name[0] == '.' && name[1] == 0)
		return 1;
	chbegin(ch);
	if(ch->open != 0){
		werrstr(Einval);
		chend(ch);
		return -1;
	}
	b = getbuf(ch->fs->d, ch->loc->blk, TDENTRY, 0);
	if(b == nil){
		chend(ch);
		return -1;
	}
	d = getdent(ch->loc, b);
	if(d == nil)
		goto error;
	if((d->type & QTDIR) == 0){
		werrstr(Enotadir);
		goto error;
	}
	if(!permcheck(ch->fs, d, ch->uid, OEXEC)){
		werrstr(Eperm);
		goto error;
	}
	if(strcmp(name, "..") == 0){
		l = ch->loc->next;
		if(l == nil)
			goto done;
		putloc(ch->fs, ch->loc, 0);
		ch->loc = l;
		goto done;
	}
	if(findentry(ch->fs, ch->loc, b, name, &f, ch->flags & CHFDUMP) <= 0)
		goto error;
	ch->loc = getloc(ch->fs, f, ch->loc);
done:
	putbuf(b);
	chend(ch);
	return 1;
error:
	putbuf(b);
	chend(ch);
	return -1;
}

int
namevalid(char *name)
{
	char *p;

	if(name == nil || name[0] == 0)
		return 0;
	if(name[0] == '.' && (name[1] == 0 || name[1] == '.' && name[2] == 0))
		return 0;
	for(p = name; *p; p++)
		if((uint8_t) *p < ' ' || *p == '/')
			return 0;
	return p - name < NAMELEN;
}

int
chancreat(Chan *ch, char *name, int perm, int mode)
{
	Buf *b;
	Dentry *d;
	int isdir;
	Loc *l;
	FLoc f;

	b = nil;
	l = nil;
	chbegin(ch);
	if(!namevalid(name) || ch->open != 0)
		goto inval;
	if((ch->flags & CHFRO) != 0)
		goto inval;
	if(willmodify(ch->fs, ch->loc, ch->flags & CHFNOLOCK) < 0)
		goto error;
	if(isdir = ((perm & DMDIR) != 0))
		if((mode & (NP_OWRITE | NP_OEXEC | NP_ORCLOSE | NP_OTRUNC)) != 0)
			goto inval;
	b = getbuf(ch->fs->d, ch->loc->blk, TDENTRY, 0);
	if(b == nil)
		goto error;
	d = getdent(ch->loc, b);
	if(d == nil)
		goto error;
	if((d->type & QTDIR) == 0){
		werrstr(Enotadir);
		goto error;
	}
	if((ch->flags & CHFNOPERM) == 0){
		if(!permcheck(ch->fs, d, ch->uid, NP_OWRITE)){
			werrstr(Eperm);
			goto error;
		}
		if(isdir)
			perm &= ~0777 | d->mode & 0777;
		else
			perm &= ~0666 | d->mode & 0666;
	}
	if(newentry(ch->fs, ch->loc, b, name, &f, 0) <= 0)
		goto error;

	f.type = perm >> 24;
	if(newqid(ch->fs, &f.path) < 0)
		goto error;
	l = getloc(ch->fs, f, ch->loc);
	modified(ch, d);
	b->op |= BDELWRI;
	putbuf(b);
	b = nil;
	if(willmodify(ch->fs, l, ch->flags & CHFNOLOCK) < 0)
		goto error;
	b = getbuf(ch->fs->d, l->blk, TDENTRY, 0);
	if(b == nil)
		goto error;
	ch->loc = l;
	d = &b->de[l->deind];
	memset(d, 0, sizeof(*d));
	d->Qid = l->Qid;
	strcpy(d->name, name);
	d->mtime = time(0);
	d->atime = d->mtime;
	d->gid = d->uid = d->muid = ch->uid;
	d->mode = DALLOC | perm & 0777;
	if((d->type & QTEXCL) != 0){
		qlock(&ch->loc->ex);
		ch->loc->exlock = ch;
		ch->loc->lwrite = d->atime;
		qunlock(&ch->loc->ex);
	}
	b->op |= BDELWRI;
	putbuf(b);
	switch(mode & NP_OEXEC){
	case NP_ORDWR:
		ch->open |= CHREAD;
	case NP_OWRITE:
		ch->open |= CHWRITE;
		break;
	case NP_OEXEC:
	case NP_OREAD:
		ch->open |= CHREAD;
		break;
	}
	if((mode & NP_ORCLOSE) != 0)
		ch->open |= CHRCLOSE;
	chend(ch);
	return 1;
inval:
	werrstr(Einval);
error:
	if(l != nil)
		putloc(ch->fs, l, 0);
	if(b != nil)
		putbuf(b);
	chend(ch);
	return -1;
}

int
chanopen(Chan *ch, int mode)
{
	Buf *b;
	Dentry *d;

	b = nil;
	chbegin(ch);
	if(ch->open != 0)
		goto inval;
	if((ch->flags & CHFRO) != 0 && (mode & (NP_ORCLOSE | NP_OTRUNC | NP_OWRITE | NP_ORDWR)) != 0)
		goto inval;
	if((mode & NP_OTRUNC) != 0)
		if(willmodify(ch->fs, ch->loc, ch->flags & CHFNOLOCK) < 0)
			goto error;
	if((mode & NP_ORCLOSE) != 0){
		if(ch->loc->next == nil)
			goto inval;
		b = getbuf(ch->fs->d, ch->loc->next->blk, TDENTRY, 0);
		if(b == nil)
			goto error;
		d = getdent(ch->loc->next, b);
		if(d == nil)
			goto error;
		if((ch->flags & CHFNOPERM) == 0)
			if(!permcheck(ch->fs, d, ch->uid, NP_OWRITE))
				goto perm;
		putbuf(b);
	}
	b = getbuf(ch->fs->d, ch->loc->blk, TDENTRY, 0);
	if(b == nil)
		goto error;
	d = getdent(ch->loc, b);
	if(d == nil)
		goto error;
	if((d->type & QTAPPEND) != 0)
		mode &= ~NP_OTRUNC;
	if((d->type & QTDIR) != 0 && (mode & (NP_ORCLOSE | NP_OTRUNC | NP_OWRITE | NP_ORDWR)) != 0)
		goto inval;
	if((ch->flags & CHFNOPERM) == 0){
		if(!permcheck(ch->fs, d, ch->uid, mode & NP_OEXEC))
			goto perm;
		if((mode & NP_OTRUNC) != 0 && !permcheck(ch->fs, d, ch->uid, NP_OWRITE))
			goto perm;
	}
	if((ch->loc->type & QTEXCL) != 0){
		qlock(&ch->loc->ex);
		if(ch->loc->exlock == nil || ch->loc->lwrite < time(0) - EXCLDUR){
			ch->loc->exlock = ch;
			ch->loc->lwrite = time(0);
			qunlock(&ch->loc->ex);
		}else{
			qunlock(&ch->loc->ex);
			werrstr(Elocked);
			goto error;
		}
	}
	switch(mode & NP_OEXEC){
	case NP_ORDWR:
		ch->open |= CHREAD;
	case NP_OWRITE:
		ch->open |= CHWRITE;
		break;
	case NP_OEXEC:
	case NP_OREAD:
		ch->open |= CHREAD;
		break;
	}
	if((mode & NP_OTRUNC) != 0){
		trunc(ch->fs, ch->loc, b, 0);
		modified(ch, d);
		b->op |= BDELWRI;
	}
	if((mode & NP_ORCLOSE) != 0)
		ch->open |= CHRCLOSE;
	putbuf(b);
	chend(ch);
	return 1;
inval:
	werrstr(Einval);
	goto error;
perm:
	werrstr(Eperm);
error:
	if(b != nil)
		putbuf(b);
	chend(ch);
	return -1;
}

static int
checklock(Chan *ch)
{
	int rc;

	qlock(&ch->loc->ex);
	rc = 1;
	if(ch->loc->exlock == ch){
		if(ch->loc->lwrite < time(0) - EXCLDUR){
			ch->loc->exlock = nil;
			werrstr("lock broken");
			rc = -1;
		}else
			ch->loc->lwrite = time(0);
	}else{
		werrstr(Elocked);
		rc = -1;
	}
	qunlock(&ch->loc->ex);
	return rc;
}

int
chanwrite(Chan *ch, void *buf, uint32_t n, uint64_t off)
{
	uint64_t i, e, bl;
	int r, rn, rc;
	Buf *b, *c;
	Dentry *d;
	uint8_t *p;

	if(n == 0)
		return 0;
	if((ch->flags & CHFRO) != 0){
		werrstr(Einval);
		return -1;
	}
	if((ch->open & CHWRITE) == 0){
		werrstr(Einval);
		return -1;
	}
	chbegin(ch);
	if((ch->loc->type & QTEXCL) != 0 && checklock(ch) < 0){
		chend(ch);
		return -1;
	}
	if(willmodify(ch->fs, ch->loc, ch->flags & CHFNOLOCK) < 0){
		chend(ch);
		return -1;
	}
	b = getbuf(ch->fs->d, ch->loc->blk, TDENTRY, 0);
	if(b == nil){
		chend(ch);
		return -1;
	}
	d = getdent(ch->loc, b);
	if(d == nil){
		putbuf(b);
		chend(ch);
		return -1;
	}
	if((d->type & QTAPPEND) != 0)
		off = d->size;
	e = off + n;
	i = off;
	p = buf;
	while(i < e){
		bl = i / RBLOCK;
		r = i % RBLOCK;
		rn = RBLOCK - r;
		if(i + rn > e)
			rn = e - i;
		rc = getblk(ch->fs, ch->loc, b, bl, &bl, rn == RBLOCK ? GBOVERWR : GBCREATE);
		if(rc < 0)
			goto done;
		c = getbuf(ch->fs->d, bl, TRAW, rc == 0 || rn == RBLOCK);
		if(c == nil)
			goto done;
		if(rc == 0 && rn != RBLOCK)
			memset(c->data, 0, sizeof(c->data));
		memcpy(c->data + r, p, rn);
		i += rn;
		c->op |= i != e ? BWRIM : BDELWRI;
		putbuf(c);
		p += rn;
	}
done:
	modified(ch, d);
	e = off + (p - (uint8_t *) buf);
	if(e > d->size)
		d->size = e;
	b->op |= BDELWRI;
	putbuf(b);
	chend(ch);
	if(p == buf)
		return -1;
	return p - (uint8_t *) buf;
}

static int chandirread(Chan *, void *, uint32_t, uint64_t);

int
chanread(Chan *ch, void *buf, uint32_t n, uint64_t off)
{
	uint64_t i, e, bl;
	int r, rn, rc;
	uint8_t *p;
	Buf *b, *c;
	Dentry *d;

	if((ch->open & CHREAD) == 0){
		werrstr(Einval);
		return -1;
	}
	chbegin(ch);
	if((ch->loc->type & QTEXCL) != 0 && checklock(ch) < 0){
		chend(ch);
		return -1;
	}
	if((ch->loc->Qid.type & QTDIR) != 0)
		return chandirread(ch, buf, n, off);
	b = getbuf(ch->fs->d, ch->loc->blk, TDENTRY, 0);
	if(b == nil){
		chend(ch);
		return -1;
	}
	d = getdent(ch->loc, b);
	if(d == nil)
		goto error;
	if(off >= d->size)
		n = 0;
	else if(off + n > d->size)
		n = d->size - off;
	if(n == 0){
		putbuf(b);
		chend(ch);
		return 0;
	}
	e = off + n;
	i = off;
	p = buf;
	while(i < e){
		bl = i / RBLOCK;
		r = i % RBLOCK;
		rn = RBLOCK - r;
		if(i + rn > e)
			rn = e - i;
		rc = getblk(ch->fs, ch->loc, b, bl, &bl, GBREAD);
		if(rc < 0)
			goto error;
		if(rc == 0)
			memset(p, 0, rn);
		else{
			c = getbuf(ch->fs->d, bl, TRAW, 0);
			if(c == nil)
				goto error;
			memcpy(p, c->data + r, rn);
			putbuf(c);
		}
		i += rn;
		p += rn;
	}
	putbuf(b);
	chend(ch);
	return n;
error:
	putbuf(b);
	chend(ch);
	return -1;
}

static void
statbuf(Fs *fs, Dentry *d, Dir *di, char *buf)
{
	di->qid = d->Qid;
	di->mode = (d->mode & 0777) | (d->Qid.type << 24);
	di->mtime = d->mtime;
	di->atime = d->atime;
	di->length = d->size;
	if(d->type & QTDIR)
		di->length = 0;
	if(buf == nil){
		di->name = estrdup(d->name);
		di->uid = uid2name(fs, d->uid, nil);
		di->gid = uid2name(fs, d->gid, nil);
		di->muid = uid2name(fs, d->muid, nil);
	}else{
		memset(buf, 0, NAMELEN + 3 * USERLEN);
		strncpy(buf, d->name, NAMELEN - 1);
		di->name = buf;
		di->uid = uid2name(fs, d->uid, buf + NAMELEN);
		di->gid = uid2name(fs, d->gid, buf + NAMELEN + USERLEN);
		di->muid = uid2name(fs, d->muid, buf + NAMELEN + 2 * USERLEN);
	}
}

int
chanstat(Chan *ch, Dir *di)
{
	Buf *b;
	Dentry *d;

	chbegin(ch);
	b = getbuf(ch->fs->d, ch->loc->blk, TDENTRY, 0);
	if(b == nil){
		chend(ch);
		return -1;
	}
	d = getdent(ch->loc, b);
	if(d == nil){
		putbuf(b);
		chend(ch);
		return -1;
	}
	statbuf(ch->fs, d, di, nil);
	putbuf(b);
	chend(ch);
	return 0;
}

static int
chandirread(Chan *ch, void *buf, uint32_t n, uint64_t off)
{
	Buf *b, *c;
	Dentry *d;
	uint64_t i, blk;
	int j;
	int rc;
	uint32_t wr;
	Dir di;
	char cbuf[NAMELEN + 3 * USERLEN];

	if(off == 0){
		ch->dwloff = 0;
		ch->dwblk = 0;
		ch->dwind = 0;
	}else if(ch->dwloff != off){
		werrstr(Einval);
		chend(ch);
		return -1;
	}
	b = getbuf(ch->fs->d, ch->loc->blk, TDENTRY, 0);
	if(b == nil){
		chend(ch);
		return -1;
	}
	d = getdent(ch->loc, b);
	if(d == nil){
		putbuf(b);
		chend(ch);
		return -1;
	}
	if(ch->dwblk >= d->size){
		putbuf(b);
		chend(ch);
		return 0;
	}
	c = nil;
	wr = 0;
	i = ch->dwblk;
	j = ch->dwind;
	for(;;){
		if(c == nil){
			rc = getblk(ch->fs, ch->loc, b, i, &blk, GBREAD);
			if(rc < 0)
				goto error;
			if(rc == 0){
				j = 0;
				if(++i >= d->size)
					break;
				continue;
			}
			c = getbuf(ch->fs->d, blk, TDENTRY, 0);
			if(c == nil)
				goto error;
		}
		if((c->de[j].mode & DALLOC) == 0)
			goto next;
		if((ch->flags & CHFDUMP) != 0 && (c->de[j].type & QTTMP) != 0)
			goto next;
		statbuf(ch->fs, &c->de[j], &di, cbuf);
		rc = convD2M(&di, (uint8_t *) buf + wr, n - wr);
		if(rc <= BIT16SZ)
			break;
		wr += rc;
	next:
		if(++j >= DEPERBLK){
			j = 0;
			if(c != nil)
				putbuf(c);
			c = nil;
			if(++i >= d->size)
				break;
		}
	}
	ch->dwblk = i;
	ch->dwind = j;
	ch->dwloff += wr;
	if(c != nil)
		putbuf(c);
	putbuf(b);
	chend(ch);
	return wr;
error:
	putbuf(b);
	chend(ch);
	return -1;
}

int
chanclunk(Chan *ch)
{
	Buf *b, *p;
	int rc;
	Dentry *d;

	rc = 1;
	b = p = nil;
	chbegin(ch);
	if(ch->open & CHRCLOSE){
		if((ch->flags & CHFRO) != 0)
			goto inval;
		if(willmodify(ch->fs, ch->loc, ch->flags & CHFNOLOCK) < 0)
			goto error;
		if(ch->loc->next == nil)
			goto inval;
		p = getbuf(ch->fs->d, ch->loc->next->blk, TDENTRY, 0);
		if(p == nil)
			goto error;
		b = getbuf(ch->fs->d, ch->loc->blk, TDENTRY, 0);
		if(b == nil)
			goto error;
		d = getdent(ch->loc->next, p);
		if(d == nil)
			goto error;
		if((ch->flags & CHFNOPERM) == 0)
			if(!permcheck(ch->fs, d, ch->uid, NP_OWRITE)){
				werrstr(Eperm);
				goto error;
			}
		d = getdent(ch->loc, b);
		if(d == nil)
			goto error;
		if((d->type & QTDIR) != 0 && findentry(ch->fs, ch->loc, b, nil, nil, ch->flags & CHFDUMP) != 0)
			goto inval;
		if((d->mode & DGONE) != 0)
			goto done;
		qlock(&ch->fs->loctree);
		if(ch->loc->ref > 1){
			d->mode &= ~DALLOC;
			d->mode |= DGONE; /* aaaaand it's gone */
			ch->loc->flags |= LGONE;
			qunlock(&ch->fs->loctree);
		}else{
			ch->loc->flags &= ~LGONE;
			qunlock(&ch->fs->loctree);
			rc = delete(ch->fs, ch->loc, b);
		}
		b->op |= BDELWRI;
	}
done:
	if(b != nil)
		putbuf(b);
	if(p != nil)
		putbuf(p);
	if((ch->loc->type & QTEXCL) != 0){
		qlock(&ch->loc->ex);
		if(ch->loc->exlock == ch)
			ch->loc->exlock = nil;
		qunlock(&ch->loc->ex);
	}
	putloc(ch->fs, ch->loc, 1);
	chend(ch);
	free(ch);
	return rc;
inval:
	werrstr(Einval);
error:
	rc = -1;
	goto done;
}

int
chanwstat(Chan *ch, Dir *di)
{
	Buf *b, *pb;
	Dentry *d;
	int isdir, owner, rc;
	short nuid, ngid;

	b = pb = nil;
	chbegin(ch);
	if((ch->flags & CHFRO) != 0)
		goto inval;
	if(willmodify(ch->fs, ch->loc, ch->flags & CHFNOLOCK) < 0)
		goto error;
	if(*di->name){
		FLoc f;

		if(!namevalid(di->name) || ch->loc->next == nil)
			goto inval;
		pb = getbuf(ch->fs->d, ch->loc->next->blk, TDENTRY, 0);
		if(pb == nil)
			goto error;
		d = getdent(ch->loc->next, pb);
		if(d == nil)
			goto error;
		rc = findentry(ch->fs, ch->loc->next, pb, di->name, &f, ch->flags & CHFDUMP);
		if(rc < 0)
			goto error;
		else if(rc == 0){
			if((ch->flags & CHFNOPERM) == 0)
				if(!permcheck(ch->fs, d, ch->uid, NP_OWRITE))
					goto perm;
		} else if(f.blk != ch->loc->blk || f.deind != ch->loc->deind){
			werrstr(Eexists);
			goto error;
		}
	}
	b = getbuf(ch->fs->d, ch->loc->blk, TDENTRY, 0);
	if(b == nil)
		goto error;
	d = getdent(ch->loc, b);
	if(d == nil)
		goto error;
	isdir = (d->type & QTDIR) != 0;
	owner = ch->uid == d->uid ||
		ingroup(ch->fs, ch->uid, d->gid, 1) ||
		(ch->fs->flags & FSNOPERM) != 0 ||
		(ch->flags & CHFNOPERM) != 0;
	if(di->length != ~0){
		if(isdir && di->length != 0)
			goto inval;
		if((ch->flags & CHFNOPERM) == 0)
			if(di->length != d->size && !permcheck(ch->fs, d, ch->uid, NP_OWRITE))
				goto perm;
	}
	if(di->mtime != ~0 && !owner)
		goto perm;
	if(di->mode != ~0 && !owner)
		goto perm;
	nuid = d->uid;
	ngid = d->gid;
	if(*di->uid != 0 && name2uid(ch->fs, di->uid, &nuid) < 0)
		goto inval;
	if(*di->gid != 0 && name2uid(ch->fs, di->gid, &ngid) < 0)
		goto inval;
	if(nuid != d->uid && (ch->fs->flags & FSCHOWN) == 0)
		goto perm;
	if((nuid != d->uid || ngid != d->gid) && !owner)
		goto perm;
	d->uid = nuid;
	d->gid = ngid;
	if(di->length != ~0 && di->length != d->size && !isdir){
		trunc(ch->fs, ch->loc, b, di->length);
		modified(ch, d);
	}
	if(di->mtime != ~0)
		d->mtime = di->mtime;
	if(di->mode != ~0){
		d->mode = d->mode & ~0777 | di->mode & 0777;
		ch->loc->type = d->type = di->mode >> 24;
	}
	if(*di->name){
		memset(d->name, 0, NAMELEN);
		strcpy(d->name, di->name);
	}
	b->op |= BDELWRI;
	if(pb != nil)
		putbuf(pb);
	putbuf(b);
	chend(ch);
	return 1;
inval:
	werrstr(Einval);
	goto error;
perm:
	werrstr(Eperm);
error:
	if(pb != nil)
		putbuf(pb);
	if(b != nil)
		putbuf(b);
	chend(ch);
	return -1;
}

int
chanremove(Chan *ch)
{
	ch->open |= CHRCLOSE;
	return chanclunk(ch);
}
