/*
 * This file is part of the UCB release of Plan 9. It is subject to the license
 * terms in the LICENSE file found in the top-level directory of this
 * distribution and at http://akaros.cs.berkeley.edu/files/Plan9License. No
 * part of the UCB release of Plan 9, including this file, may be copied,
 * modified, propagated, or distributed except according to the terms contained
 * in the LICENSE file.
 */

#include <u.h>
#include <libc.h>
#include <auth.h>
#include <9P2000.h>
#include "iotrack.h"
#include "dat.h"
#include "dosfs.h"
#include "fns.h"

extern int putlongname(Xfs *xf, Dosptr *ndp, char *name, char sname[13]);

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

void
rauth(void)
{
	errno = Enoauth;
}

void
rflush(void)
{
}

void
rattach(void)
{
	Xfs *xf;
	Xfile *root;
	Dosptr *dp;

	root = xfile(req->fid, Clean);
	if(!root){
		errno = Enomem;
		goto error;
	}
	root->xf = xf = getxfs(req->uname, req->aname);
	if(!xf)
		goto error;
	if(xf->fmt == 0 && dosfs(xf) < 0){
		errno = Eformat;
		goto error;
	}
	root->qid.type = QTDIR;
	root->qid.path = 0;
	root->qid.vers = 0;
	root->xf->rootqid = root->qid;
	dp = jehanne_malloc(sizeof(Dosptr));
	if(dp == nil){
		errno = Enomem;
		goto error;
	}
	root->ptr = dp;
	rootfile(root);
	rep->qid = root->qid;
	return;
error:
	if(root)
		xfile(req->fid, Clunk);
}

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

	nf = xfile(newfid, Clean);
	if(!nf){
		errno = Enomem;
		return nil;
	}
	dp = jehanne_malloc(sizeof(Dosptr));
	if(dp == nil){
		errno = Enomem;
		return nil;
	}
	next = nf->next;
	*nf = *of;
	nf->next = next;
	nf->fid = req->newfid;
	nf->ptr = dp;
	refxfs(nf->xf, 1);
	jehanne_memmove(dp, of->ptr, sizeof(Dosptr));
	dp->p = nil;
	dp->d = nil;
	return nf;
}

void
rwalk(void)
{
	Xfile *f, *nf;
	Dosptr dp[1], savedp[1];
	int r, longtype;
	Qid saveqid;

	rep->nwqid = 0;
	nf = nil;
	f = xfile(req->fid, Asis);
	if(f == nil){
		chat("\tno xfile\n");
		goto error2;
	}
	if(req->fid != req->newfid){
		nf = doclone(f, req->newfid);
		if(nf == nil){
			chat("\tclone failed\n");
			goto error2;
		}
		f = nf;
	}

	saveqid = f->qid;
	jehanne_memmove(savedp, f->ptr, sizeof(Dosptr));
	for(; 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);
			goto error;
		}
		if(jehanne_strcmp(req->wname[rep->nwqid], ".") == 0){
			;
		}else if(jehanne_strcmp(req->wname[rep->nwqid], "..") == 0){
			if(f->qid.path != f->xf->rootqid.path){
				r = walkup(f, dp);
				if(r < 0)
					goto error;
				jehanne_memmove(f->ptr, dp, sizeof(Dosptr));
				if(isroot(dp->addr))
					f->qid.path = f->xf->rootqid.path;
				else
					f->qid.path = QIDPATH(dp);
			}
		}else{
			fixname(req->wname[rep->nwqid]);
			longtype = classifyname(req->wname[rep->nwqid]);
			if(longtype==Invalid || getfile(f) < 0)
				goto error;

			/*
			 * always do a search for the long name,
			 * because it could be filed as such
			 */
			r = searchdir(f, req->wname[rep->nwqid], dp, 0, longtype);
			putfile(f);
			if(r < 0)
				goto error;
			jehanne_memmove(f->ptr, dp, sizeof(Dosptr));
			f->qid.path = QIDPATH(dp);
			f->qid.type = QTFILE;
			if(isroot(dp->addr))
				f->qid.path = f->xf->rootqid.path;
			else if(dp->d->attr & DDIR)
				f->qid.type = QTDIR;
			else if(dp->d->attr & DSYSTEM){
				f->qid.type |= QTEXCL;
				if(iscontig(f->xf, dp->d))
					f->qid.type |= QTAPPEND;
			}
//ZZZ maybe use other bits than qtexcl & qtapppend
			putfile(f);
		}
		rep->wqid[rep->nwqid] = f->qid;
	}
	return;
error:
	f->qid = saveqid;
	jehanne_memmove(f->ptr, savedp, sizeof(Dosptr));
	if(nf != nil)
		xfile(req->newfid, Clunk);
error2:
	if(!errno && !rep->nwqid)
		errno = Enonexist;
}

void
ropen(void)
{
	Xfile *f;
	Iosect *p;
	Dosptr *dp;
	int attr, omode;

	f = xfile(req->fid, Asis);
	if(!f || (f->flags&Omodes)){
		errno = Eio;
		return;
	}
	dp = f->ptr;
	omode = 0;
	if(!isroot(dp->paddr) && (req->mode & NP_ORCLOSE)){
		/*
		 * check on parent directory of file to be deleted
		 */
		p = getsect(f->xf, dp->paddr);
		if(p == nil){
			errno = Eio;
			return;
		}
		attr = ((Dosdir *)&p->iobuf[dp->poffset])->attr;
		putsect(p);
		if(attr & DRONLY){
			errno = Eperm;
			return;
		}
		omode |= Orclose;
	}else if(req->mode & NP_ORCLOSE)
		omode |= Orclose;
	if(getfile(f) < 0){
		errno = Enonexist;
		return;
	}
	if(!isroot(dp->addr))
		attr = dp->d->attr;
	else
		attr = DDIR;
	switch(req->mode & 7){
	case NP_OREAD:
	case NP_OEXEC:
		omode |= Oread;
		break;
	case NP_ORDWR:
		omode |= Oread;
		/* fall through */
	case NP_OWRITE:
		omode |= Owrite;
		if(attr & DRONLY){
			errno = Eperm;
			goto out;
		}
		break;
	default:
		errno = Eio;
		goto out;
	}
	if(req->mode & NP_OTRUNC){
		if(attr & DDIR || attr & DRONLY){
			errno = Eperm;
			goto out;
		}
		if(truncfile(f, 0) < 0){
			errno = Eio;
			goto out;
		}
	}
	f->flags |= omode;
	rep->qid = f->qid;
	rep->iounit = 0;
out:
	putfile(f);
}

static int
mk8dot3name(Xfile *f, Dosptr *ndp, char *name, char *sname)
{
	Dosptr tmpdp;
	int i, longtype;

	if(jehanne_strcmp(name, ".") == 0 || jehanne_strcmp(name, "..") == 0)
		return Invalid;

	/*
	 * always do a search for the long name,
	 * because it could be filed as such
	 */
	fixname(name);
	longtype = classifyname(name);
	if(longtype==Invalid || searchdir(f, name, ndp, 1, longtype) < 0)
		return Invalid;

	if(longtype==Short)
		return Short;

	if(longtype==ShortLower){
		/*
		 * alias is the upper-case version, which we
		 * already know does not exist.
		 */
		jehanne_strcpy(sname, name);
		for(i=0; sname[i]; i++)
			if('a' <= sname[i] && sname[i] <= 'z')
				sname[i] += 'A'-'a';
		return ShortLower;
	}

	/*
	 * find alias for the long name
	 */
	for(i=1;; i++){
		mkalias(name, sname, i);
		if(searchdir(f, sname, &tmpdp, 0, 0) < 0)
			return Long;
		putsect(tmpdp.p);
	}
}

/*
 * fill in a directory entry for a new file
 */
static int
mkdentry(Xfs *xf, Dosptr *ndp, char *name, char *sname, int longtype,
	 int nattr,
	 int32_t start, int32_t length)
{
	Dosdir *nd;

	/*
	 * fill in the entry
	 */
	ndp->p = getsect(xf, ndp->addr);
	if(ndp->p == nil
	|| longtype!=Short && putlongname(xf, ndp, name, sname) < 0){
		errno = Eio;
		return -1;
	}

	ndp->d = (Dosdir *)&ndp->p->iobuf[ndp->offset];
	nd = ndp->d;
	jehanne_memset(nd, 0, DOSDIRSIZE);

	if(longtype!=Short)
		name = sname;
	putname(name, nd);

	nd->attr = nattr;
	puttime(nd, 0);
	putstart(xf, nd, start);
	nd->length[0] = length;
	nd->length[1] = length>>8;
	nd->length[2] = length>>16;
	nd->length[3] = length>>24;

	ndp->p->flags |= BMOD;

	return 0;
}

void
rcreate(void)
{
	Dosbpb *bp;
	Xfile *f;
	Dosptr *pdp, *ndp;
	Iosect *xp;
	Dosdir *pd, *xd;
	char sname[13];
	int32_t start;
	int longtype, attr, omode, nattr;

	f = xfile(req->fid, Asis);
	if(!f || (f->flags&Omodes) || getfile(f)<0){
		errno = Eio;
		return;
	}
	pdp = f->ptr;
	pd = pdp->d;
	/*
	 * perm check
	 */
	if(isroot(pdp->addr) && pd != nil)
		panic("root pd != nil");
	attr = pd ? pd->attr : DDIR;
	if(!(attr & DDIR) || (attr & DRONLY)){
badperm:
		putfile(f);
		errno = Eperm;
		return;
	}
	omode = 0;
	if(req->mode & NP_ORCLOSE)
		omode |= Orclose;
	switch(req->mode & 7){
	case NP_OREAD:
	case NP_OEXEC:
		omode |= Oread;
		break;
	case NP_ORDWR:
		omode |= Oread;
		/* fall through */
	case NP_OWRITE:
		omode |= Owrite;
		if(req->perm & DMDIR)
			goto badperm;
		break;
	default:
		goto badperm;
	}

	/*
	 * check the name, find the slot for the dentry,
	 * and find a good alias for a long name
	 */
	ndp = jehanne_malloc(sizeof(Dosptr));
	if(ndp == nil){
		putfile(f);
		errno = Enomem;
		return;
	}
	longtype = mk8dot3name(f, ndp, req->name, sname);
	chat("rcreate %s longtype %d...\n", req->name, longtype);
	if(longtype == Invalid){
		jehanne_free(ndp);
		goto badperm;
	}

	/*
	 * allocate first cluster, if making directory
	 */
	start = 0;
	bp = nil;
	if(req->perm & DMDIR){
		bp = f->xf->ptr;
		mlock(bp);
		start = falloc(f->xf);
		unmlock(bp);
		if(start <= 0){
			jehanne_free(ndp);
			putfile(f);
			errno = Eio;
			return;
		}
	}

	/*
	 * make the entry
	 */
	nattr = 0;
	if((req->perm & 0222) == 0)
		nattr |= DRONLY;
	if(req->perm & DMDIR)
		nattr |= DDIR;

	if(mkdentry(f->xf, ndp, req->name, sname, longtype, nattr, start, 0) < 0){
		if(ndp->p != nil)
			putsect(ndp->p);
		jehanne_free(ndp);
		if(start > 0)
			ffree(f->xf, start);
		putfile(f);
		return;
	}

	if(pd != nil){
		puttime(pd, 0);
		pdp->p->flags |= BMOD;
	}

	/*
	 * fix up the fid
	 */
	f->ptr = ndp;
	f->qid.type = QTFILE;
	f->qid.path = QIDPATH(ndp);

//ZZZ set type for excl, append?
	if(req->perm & DMDIR){
		f->qid.type = QTDIR;
		xp = getsect(f->xf, clust2sect(bp, start));
		if(xp == nil){
			errno = Eio;
			goto badio;
		}
		xd = (Dosdir *)&xp->iobuf[0];
		jehanne_memmove(xd, ndp->d, DOSDIRSIZE);
		jehanne_memset(xd->name, ' ', sizeof xd->name+sizeof xd->ext);
		xd->name[0] = '.';
		xd = (Dosdir *)&xp->iobuf[DOSDIRSIZE];
		if(pd)
			jehanne_memmove(xd, pd, DOSDIRSIZE);
		else{
			jehanne_memset(xd, 0, DOSDIRSIZE);
			puttime(xd, 0);
			xd->attr = DDIR;
		}
		jehanne_memset(xd->name, ' ', sizeof xd->name+sizeof xd->ext);
		xd->name[0] = '.';
		xd->name[1] = '.';
		xp->flags |= BMOD;
		putsect(xp);
	}

	f->flags |= omode;
	rep->qid = f->qid;
	rep->iounit = 0;

badio:
	putfile(f);
	putsect(pdp->p);
	jehanne_free(pdp);
}

void
rread(void)
{
	Xfile *f;
	int r;

	if (!(f=xfile(req->fid, Asis)) || !(f->flags&Oread))
		goto error;
	if(req->count > sizeof repdata)
		req->count = sizeof repdata;
	if(f->qid.type & QTDIR){
		if(getfile(f) < 0)
			goto error;
		r = readdir(f, repdata, req->offset, req->count);
	}else{
		if(getfile(f) < 0)
			goto error;
		r = readfile(f, repdata, req->offset, req->count);
	}
	putfile(f);
	if(r < 0){
error:
		errno = Eio;
	}else{
		rep->count = r;
		rep->data = (char*)repdata;
	}
}

void
rwrite(void)
{
	Xfile *f;
	int r;

	if (!(f=xfile(req->fid, Asis)) || !(f->flags&Owrite))
		goto error;
	if(getfile(f) < 0)
		goto error;
	r = writefile(f, req->data, req->offset, req->count);
	putfile(f);
	if(r < 0){
error:
		errno = Eio;
	}else{
		rep->count = r;
	}
}

void
rclunk(void)
{
	xfile(req->fid, Clunk);
	sync();
}

/*
 * wipe out a dos directory entry
 */
static void
doremove(Xfs *xf, Dosptr *dp)
{
	Iosect *p;
	int prevdo;

	dp->p->iobuf[dp->offset] = DOSEMPTY;
	dp->p->flags |= BMOD;
	for(prevdo = dp->offset-DOSDIRSIZE; prevdo >= 0; prevdo -= DOSDIRSIZE){
		if(dp->p->iobuf[prevdo+11] != 0xf)
			break;
		dp->p->iobuf[prevdo] = DOSEMPTY;
	}
	if(prevdo < 0 && dp->prevaddr != -1){
		p = getsect(xf, dp->prevaddr);
		for(prevdo = ((Dosbpb*)xf->ptr)->sectsize-DOSDIRSIZE; prevdo >= 0; prevdo -= DOSDIRSIZE){
			if(p->iobuf[prevdo+11] != 0xf)
				break;
			p->iobuf[prevdo] = DOSEMPTY;
			p->flags |= BMOD;
		}
		putsect(p);
	}
}

void
rremove(void)
{
	Xfile *f;
	Dosptr *dp;
	Iosect *parp;
	Dosdir *pard;

	f = xfile(req->fid, Asis);
	parp = nil;
	if(f == nil){
		errno = Eio;
		goto out;
	}
	dp = f->ptr;
	if(isroot(dp->addr)){
		errno = Eperm;
		goto out;
	}

	/*
	 * can't remove if parent is read only,
	 * it's a non-empty directory,
	 * or it's a read only file in the root directory
	 */
	parp = getsect(f->xf, dp->paddr);
	if(parp == nil
	|| getfile(f) < 0){
		errno = Eio;
		goto out;
	}
	pard = (Dosdir *)&parp->iobuf[dp->poffset];
	if(!isroot(dp->paddr) && (pard->attr & DRONLY)
	|| (dp->d->attr & DDIR) && emptydir(f) < 0
	|| isroot(dp->paddr) && (dp->d->attr&DRONLY)){
		errno = Eperm;
		goto out;
	}
	if(truncfile(f, 0) < 0){
		errno = Eio;
		goto out;
	}
	doremove(f->xf, f->ptr);
	if(!isroot(dp->paddr)){
		puttime(pard, 0);
		parp->flags |= BMOD;
	}
out:
	if(parp != nil)
		putsect(parp);
	if(f != nil)
		putfile(f);
	xfile(req->fid, Clunk);
	sync();
}

static void
dostat(Xfile *f, Dir *d)
{
	Dosptr *dp;
	Iosect *p;
	char *name, namebuf[DOSNAMELEN];
	int islong, sum, prevdo;

	dp = f->ptr;
	if(isroot(dp->addr)){
		jehanne_memset(d, 0, sizeof(Dir));
		d->name = "/";
		d->qid.type = QTDIR;
		d->qid.path = f->xf->rootqid.path;
		d->mode = DMDIR|0777;
		d->uid = "bill";
		d->muid = "bill";
		d->gid = "trog";
	}else{
		/*
		 * assemble any long file name
		 */
		sum = aliassum(dp->d);
		islong = 0;
		name = namebuf;
		for(prevdo = dp->offset-DOSDIRSIZE; prevdo >= 0; prevdo -= DOSDIRSIZE){
			if(dp->p->iobuf[prevdo+11] != 0xf)
				break;
			name = getnamesect(namebuf, name, &dp->p->iobuf[prevdo], &islong, &sum, -1);
		}
		if(prevdo < 0 && dp->prevaddr != -1){
			p = getsect(f->xf, dp->prevaddr);
			for(prevdo = ((Dosbpb*)f->xf->ptr)->sectsize-DOSDIRSIZE; prevdo >= 0; prevdo -= DOSDIRSIZE){
				if(p->iobuf[prevdo+11] != 0xf)
					break;
				name = getnamesect(namebuf, name, &p->iobuf[prevdo], &islong, &sum, -1);
			}
			putsect(p);
		}
		getdir(f->xf, d, dp->d, dp->addr, dp->offset);
		if(islong && sum == -1 && nameok(namebuf))
			jehanne_strcpy(d->name, namebuf);
	}
}

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

	f = xfile(req->fid, Asis);
	if(!f || getfile(f) < 0){
		errno = Eio;
		return;
	}

	dir.name = repdata;
	dostat(f, &dir);

	rep->nstat = jehanne_convD2M(&dir, statbuf, sizeof statbuf);
	rep->stat = statbuf;
	putfile(f);
}

void
rwstat(void)
{
	Dir dir, wdir;
	Xfile *f, pf;
	Dosptr *dp, ndp, pdp;
	Iosect *parp;
	Dosdir *pard, *d, od;
	char sname[13];
	uint32_t oaddr, ooffset;
	int32_t start, length;
	int i, longtype, changes, attr;

	f = xfile(req->fid, Asis);
	if(!f || getfile(f) < 0){
		errno = Eio;
		return;
	}
	dp = f->ptr;

	if(isroot(dp->addr)){
		errno = Eperm;
		goto out;
	}

	changes = 0;
	dir.name = repdata;
	dostat(f, &dir);
	if(jehanne_convM2D(req->stat, req->nstat, &wdir, (char*)statbuf) != req->nstat){
		errno = Ebadstat;
		goto out;
	}

	/*
	 * To change length, must have write permission on file.
	 * we only allow truncates for now.
	 */
	if(wdir.length!=~0 && wdir.length!=dir.length){
		if(wdir.length > dir.length || !dir.mode & 0222){
			errno = Eperm;
			goto out;
		}
	}

	/*
	 * no chown or chgrp
	 */
	if(wdir.uid[0] != '\0' && jehanne_strcmp(dir.uid, wdir.uid) != 0
	|| wdir.gid[0] != '\0' && jehanne_strcmp(dir.gid, wdir.gid) != 0){
		errno = Eperm;
		goto out;
	}

	/*
	 * mode/mtime allowed
	 */
	if(wdir.mtime != ~0 && dir.mtime != wdir.mtime)
		changes = 1;

	/*
	 * Setting DMAPPEND (make system file contiguous)
	 * requires setting DMEXCL (system file).
	 */
	if(wdir.mode != ~0){
		if((wdir.mode & 7) != ((wdir.mode >> 3) & 7)
		|| (wdir.mode & 7) != ((wdir.mode >> 6) & 7)){
			errno = Eperm;
			goto out;
		}
		if((dir.mode^wdir.mode) & (DMEXCL|DMAPPEND|0777))
			changes = 1;
		if((dir.mode^wdir.mode) & DMAPPEND) {
			if((wdir.mode & (DMEXCL|DMAPPEND)) == DMAPPEND) {
				errno = Eperm;
				goto out;
			}
			if((wdir.mode & DMAPPEND) && makecontig(f, 0) < 0) {
				errno = Econtig;
				goto out;
			}
		}
	}


	/*
	 * to rename:
	 *	1) make up a fake clone
	 *	2) walk to parent
	 *	3) remove the old entry
	 *	4) create entry with new name
	 *	5) write correct mode/mtime info
	 * we need to remove the old entry before creating the new one
	 * to avoid a lock loop.
	 */
	if(wdir.name[0] != '\0' && jehanne_strcmp(dir.name, wdir.name) != 0){
		if(jehanne_utflen(wdir.name) >= DOSNAMELEN){
			errno = Etoolong;
			goto out;
		}

		/*
		 * grab parent directory of file to be changed and check for write perm
		 * rename also disallowed for read-only files in root directory
		 */
		parp = getsect(f->xf, dp->paddr);
		if(parp == nil){
			errno = Eio;
			goto out;
		}
		pard = (Dosdir *)&parp->iobuf[dp->poffset];
		if(!isroot(dp->paddr) && (pard->attr & DRONLY)
		|| isroot(dp->paddr) && (dp->d->attr&DRONLY)){
			putsect(parp);
			errno = Eperm;
			goto out;
		}

		/*
		 * retrieve info from old entry
		 */
		oaddr = dp->addr;
		ooffset = dp->offset;
		d = dp->d;
		od = *d;
		start = getstart(f->xf, d);
		length = GLONG(d->length);
		attr = d->attr;

		/*
		 * temporarily release file to allow other directory ops:
		 * walk to parent, validate new name
		 * then remove old entry
		 */
		putfile(f);
		pf = *f;
		jehanne_memset(&pdp, 0, sizeof(Dosptr));
		pdp.prevaddr = -1;
		pdp.naddr = -1;
		pdp.addr = dp->paddr;
		pdp.offset = dp->poffset;
		pdp.p = parp;
		if(!isroot(pdp.addr))
			pdp.d = (Dosdir *)&parp->iobuf[pdp.offset];
		pf.ptr = &pdp;
		longtype = mk8dot3name(&pf, &ndp, wdir.name, sname);
		if(longtype==Invalid){
			putsect(parp);
			errno = Eperm;
			return;
		}
		if(getfile(f) < 0){
			putsect(parp);
			errno = Eio;
			return;
		}
		doremove(f->xf, dp);
		putfile(f);

		/*
		 * search for dir entry again, since we may be able to use the old slot,
		 * and we need to set up the naddr field if a long name spans the block.
		 * create new entry.
		 */
		if(searchdir(&pf, wdir.name, dp, 1, longtype) < 0
		|| mkdentry(pf.xf, dp, wdir.name, sname, longtype, attr, start, length) < 0){
			putsect(parp);
			errno = Eio;
			goto out;
		}

		/*
		 * copy invisible fields
		 */
		d = dp->d;
		for(i = 0; i < 2; i++)
			d->ctime[i] = od.ctime[i];
		for(i = 0; i < nelem(od.cdate); i++)
			d->cdate[i] = od.cdate[i];
		for(i = 0; i < nelem(od.adate); i++)
			d->adate[i] = od.adate[i];

		putsect(parp);

		/*
		 * relocate up other fids to the same file, if it moved
		 */
		f->qid.path = QIDPATH(dp);
		if(oaddr != dp->addr || ooffset != dp->offset)
			dosptrreloc(f, dp, oaddr, ooffset);

		/*
		 * copy fields that are not supposed to change
		 */
		if(wdir.mtime == ~0)
			wdir.mtime = dir.mtime;
		if(wdir.mode == ~0)
			wdir.mode = dir.mode;
		changes = 1;
	}

	/*
	 * do the actual truncate
	 */
	if(wdir.length != ~0 && wdir.length != dir.length && truncfile(f, wdir.length) < 0)
		errno = Eio;

	if(changes){
		putdir(dp->d, &wdir);
		dp->p->flags |= BMOD;
	}

out:
	putfile(f);
	sync();
}
