#include "lib9.h"
#include "logfs.h"
#include "fcall.h"
#include "local.h"

char *
logfsserverwstat(LogfsServer *server, u32int fid, uchar *stat, ushort nstat)
{
	Fid *f;
	uchar *p;
	ushort len;
	uchar *mep;
	Qid qid;
	u32int perm, mtime;
	uvlong length;
	char *name, *uname, *gname, *muname;
	int qiddonttouch, permdonttouch, mtimedonttouch, lengthdonttouch;
	Entry *e, *parent;
	LogMessage s;
	char *cuid, *ngid;
	Group *eg, *ng;
	char *cname;
	char *errmsg;
	char *nuid;

	if(server->trace > 1)
		print("logfsserverwstat(%ud, %ud)\n", fid, nstat);
	if(nstat < 49)
		return Emsgsize;
	p  = stat;
	len = GBIT16(p); p += BIT16SZ;
	if(len + BIT16SZ  != nstat)
		return Emsgsize;
	mep = p + len;
	p += BIT16SZ + BIT32SZ;		/* skip type and dev */
	qid.type = *p++;
	qid.vers = GBIT32(p); p += BIT32SZ;
	qid.path = GBIT64(p); p += BIT64SZ;
	perm = GBIT32(p); p += BIT32SZ;
	p += BIT32SZ;				/* skip atime */
	mtime = GBIT32(p); p += BIT32SZ;
	length = GBIT64(p); p+= BIT64SZ;
	if(!logfsgn(&p, mep, &name) || !logfsgn(&p, mep, &uname)
		|| !logfsgn(&p, mep, &gname) || !logfsgn(&p, mep, &muname))
		return Emsgsize;
	if(p != mep)
		return Emsgsize;
	qiddonttouch = qid.type == (uchar)~0 && qid.vers == ~0 && qid.path == ~(uvlong)0;
	permdonttouch = perm == ~0;
	mtimedonttouch = mtime == ~0;
	lengthdonttouch = length == ~(uvlong)0;
	if(server->trace > 1) {
		int comma = 0;
		print("logfsserverwstat(");
		if(!qiddonttouch) {
			comma = 1;
			print("qid=0x%.2ux/%lud/%llud", qid.type, qid.vers, qid.path);
		}
		if(!permdonttouch) {
			if(comma)
				print(", ");
			print("perm=0%uo", perm);
			comma = 1;
		}
		if(!mtimedonttouch) {
			if(comma)
				print(", ");
			print("mtime=%ud", mtime);
			comma = 1;
		}
		if(!lengthdonttouch) {
			if(comma)
				print(", ");
			print("length=%llud", length);
			comma = 1;
		}
		if(name != nil) {
			if(comma)
				print(", ");
			print("name=%s", name);
			comma = 1;
		}
		if(uname != nil) {
			if(comma)
				print(", ");
			print("uid=%s", uname);
			comma = 1;
		}
		if(gname != nil) {
			if(comma)
				print(", ");
			print("gid=%s", gname);
			comma = 1;
		}
		if(muname != nil) {
			if(comma)
				print(", ");
			print("muname=%s", muname);
			comma = 1;
		}
		USED(comma);
		print(")\n");
	}
	f = logfsfidmapfindentry(server->fidmap, fid);
	if(f == nil)
		return logfsebadfid;
	e = f->entry;
	if(e->deadandgone)
		return Eio;
	parent = e->parent;
	if(name) {
		Entry *oe;
		if(parent == e)
			return Eperm;
		if(!logfsuserpermcheck(server, e->parent, f, DMWRITE))
			return Eperm;
		for(oe = parent->u.dir.list; oe; oe = oe->next) {
			if(oe == e)
				continue;
			if(strcmp(oe->name, name) == 0)
				return Eexist;
		}
	}
	if(!lengthdonttouch) {
		if(!logfsuserpermcheck(server, e, f, DMWRITE))
			return Eperm;
		if(e->qid.type & QTDIR) {
			if(length != 0)
				return Eperm;
		}else if(length != e->u.file.length){
			/*
			 * TODO - truncate directory
			 * TODO - truncate file
			 */
			return "wstat -- can't change length";
		}
	}
	cuid = logfsisfindidfromname(server->is, f->uname);
	/* TODO - change entries to have a group pointer */
	eg = logfsisfindgroupfromid(server->is, e->uid);
	if(gname) {
		gname = logfsisustadd(server->is, gname);
		if(gname == nil)
			return Enomem;
		ngid = logfsisfindidfromname(server->is, gname);
		if(ngid == nil)
			return Eunknown;
	}
	else
		ngid = nil;
	if(uname) {
		uname = logfsisustadd(server->is, uname);
		if(uname == nil)
			return Enomem;
		nuid = logfsisfindidfromname(server->is, uname);
		if(nuid == nil)
			return Eunknown;
	}
	else
		nuid = nil;
	if(!permdonttouch || !mtimedonttouch) {
		/*
		 * same permissions rules - change by owner, or by group leader
		 */
		if((server->openflags & LogfsOpenFlagWstatAllow) == 0 &&
			e->uid != cuid && (eg == nil || !logfsisgroupuidisleader(server->is, eg, cuid)))
			return Eperm;
	}
	if(!permdonttouch){
		if((perm^e->perm) & DMDIR)
			return "wstat -- attempt to change directory";
		if(perm & ~(DMDIR|DMAPPEND|DMEXCL|0777))
			return Eperm;
	}
	if(gname) {
		int ok;
		ng = logfsisfindgroupfromid(server->is, ngid);
		ok = 0;
		if(e->uid == cuid && logfsisgroupuidismember(server->is, ng, e->uid))
			ok = 1;
		if(!ok && eg && logfsisgroupuidisleader(server->is, eg, cuid)
			&& logfsisgroupuidisleader(server->is, ng, cuid))
			ok = 1;
		if(!ok && (server->openflags & LogfsOpenFlagWstatAllow) == 0)
			return Eperm;
	}
	if(!qiddonttouch)
		return Eperm;
	if(uname){
		if((server->openflags & LogfsOpenFlagWstatAllow) == 0)
			return Eperm;
	}
	if(muname)
		return Eperm;
	/*
	 * we can do this
	 */
	if(mtimedonttouch && permdonttouch && lengthdonttouch
		&& name == nil && uname == nil && gname == nil) {
		/*
		 * but we aren't doing anything - this is a wstat flush
		 */
		return logfsserverflush(server);
	}
	if(name) {
		cname = logfsstrdup(name);
		if(cname == nil)
			return Enomem;
	}
	else
		cname = nil;
	/*
	 * send the log message
	 */
	s.type = LogfsLogTwstat;
	s.path = e->qid.path;	
	s.u.wstat.name = cname;
	s.u.wstat.perm = perm;
	s.u.wstat.uid = nuid;
	s.u.wstat.gid = ngid;
	s.u.wstat.mtime = mtime;
	s.u.wstat.muid = cuid;
	errmsg = logfslog(server, 1, &s);
	if(errmsg) {
		logfsfreemem(cname);
		return errmsg;
	}
	if(!mtimedonttouch)
		e->mtime = mtime;
	if(!permdonttouch)
		e->perm = (e->perm & DMDIR) | perm;
	if(!lengthdonttouch) {
		/* TODO */
	}
	if(name) {
		logfsfreemem(e->name);
		e->name = cname;
	}
	if(uname)
		e->uid = nuid;
	if(ngid)
		e->gid = ngid;
	return nil;
}

