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

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 = 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 = 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);
       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 error;
       }
       if(req->fid != req->newfid){
               nf = doclone(f, req->newfid);
               if(nf == nil){
                       chat("\tclone failed\n");
                       goto error;
               }
               f = nf;
       }

       saveqid = f->qid;
       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(strcmp(req->wname[rep->nwqid], ".") == 0){
                       ;
               }else if(strcmp(req->wname[rep->nwqid], "..") == 0){
                       if(f->qid.path != f->xf->rootqid.path){
                               r = walkup(f, dp);
                               if(r < 0)
                                       goto error;
                               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;
                       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;
       memmove(f->ptr, savedp, sizeof(Dosptr));
       if(nf != nil)
               xfile(req->newfid, Clunk);
       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 & 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 & 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 OREAD:
       case OEXEC:
               omode |= Oread;
               break;
       case ORDWR:
               omode |= Oread;
               /* fall through */
       case OWRITE:
               omode |= Owrite;
               if(attr & DRONLY){
                       errno = Eperm;
                       goto out;
               }
               break;
       default:
               errno = Eio;
               goto out;
       }
       if(req->mode & 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(strcmp(name, ".") == 0 || 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.
                */
               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);
       }
       abort();
       return -1;
}

/*
* fill in a directory entry for a new file
*/
static int
mkdentry(Xfs *xf, Dosptr *ndp, char *name, char *sname, int longtype, int nattr, long start, long 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;
       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];
       long 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 & ORCLOSE)
               omode |= Orclose;
       switch(req->mode & 7){
       case OREAD:
       case OEXEC:
               omode |= Oread;
               break;
       case ORDWR:
               omode |= Oread;
               /* fall through */
       case 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 = 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){
               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){
                       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);
               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];
               memmove(xd, ndp->d, DOSDIRSIZE);
               memset(xd->name, ' ', sizeof xd->name+sizeof xd->ext);
               xd->name[0] = '.';
               xd = (Dosdir *)&xp->iobuf[DOSDIRSIZE];
               if(pd)
                       memmove(xd, pd, DOSDIRSIZE);
               else{
                       memset(xd, 0, DOSDIRSIZE);
                       puttime(xd, 0);
                       xd->attr = DDIR;
               }
               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);
       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)){
               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))
                       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 = 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];
       ulong oaddr, ooffset;
       long 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(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' && strcmp(dir.uid, wdir.uid) != 0
       || wdir.gid[0] != '\0' && 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' && strcmp(dir.name, wdir.name) != 0){
               if(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;
               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();
}