#include        "all.h"
#include        "/sys/include/fcall.h"

#define MSIZE   (MAXDAT+128)

static void
seterror(Fcall *ou, int err)
{

       if(0 <= err && err < MAXERR)
               ou->ename = errstring[err];
       else
               ou->ename = "unknown error";
}

static int
fsversion(Chan* chan, Fcall* f, Fcall* r)
{
       if(f->msize < MSIZE)
               r->msize = f->msize;
       else
               r->msize = MSIZE;
       /*
        * Should check the '.' stuff here.
        * What happens if Tversion has already been seen?
        */
       if(strcmp(f->version, VERSION9P) == 0){
               r->version = VERSION9P;
               chan->msize = r->msize;
       }else
               r->version = "unknown";

       fileinit(chan);
       return 0;
}

static int
fsauth(Chan *chan, Fcall *f, Fcall *r)
{
       int err, fd;
       char *aname;
       File *file;

       err = 0;
       if(chan == cons.srvchan)
               return Eauthmsg;
       file = filep(chan, f->afid, 1);
       if(file == nil)
               return Efidinuse;
       file->authokay = 0;
       if(access("/mnt/factotum", 0) < 0)
               if((fd = open("/srv/factotum", ORDWR)) >= 0)
                       mount(fd, -1, "/mnt", MBEFORE, "");
       file->ffd = open("/mnt/factotum/p9any/server", ORDWR);
       if(file->ffd < 0){
               err = Esystem;
               goto out;
       }
       aname = f->aname;
       if(!aname[0])
               aname = "main";
       file->fs = fsstr(aname);
       if(file->fs == nil){
               err = Ebadspc;
               goto out;
       }
       file->uid = strtouid(f->uname);
       if(file->uid < 0){
               err = Ebadu;
               goto out;
       }
       file->qid.path = 0;
       file->qid.vers = 0;
       file->qid.type = QTAUTH;
       r->qid = file->qid;

out:
       if(file != nil){
               qunlock(file);
               if(err != 0)
                       freefp(file);
       }
       return err;
}

void
mkqid9p1(Qid9p1* qid9p1, Qid* qid)
{
       if(qid->path & 0xFFFFFFFF00000000LL)
               panic("mkqid9p1: path %lluX\n", qid->path);
       qid9p1->path = qid->path & 0xFFFFFFFF;
       if(qid->type & QTDIR)
               qid9p1->path |= QPDIR;
       qid9p1->version = qid->vers;
}

void
mkqid9p2(Qid* qid, Qid9p1* qid9p1, int mode)
{
       qid->path = (ulong)(qid9p1->path & ~QPDIR);
       qid->vers = qid9p1->version;
       qid->type = 0;
       if(mode & DDIR)
               qid->type |= QTDIR;
       if(mode & DAPND)
               qid->type |= QTAPPEND;
       if(mode & DLOCK)
               qid->type |= QTEXCL;
}

static int
checkattach(Chan *chan, File *afile, File *file, Filsys *fs)
{
       char buf[1];

       if(chan == cons.srvchan || chan == cons.chan)
               return 0;
       if(file->uid == 0){
               if(!allownone && !chan->authed)
                       return Eauth;
               return 0;
       }
       if(afile == nil)
               return Eauth;
       if(!(afile->qid.type&QTAUTH))
               return Eauth;
       if(afile->uid != file->uid || afile->fs != fs)
               return Eauth;
       if(afile->ffd >= 0){
               afile->authokay = (read(afile->ffd, buf, 1) == 0);
               close(afile->ffd);
               afile->ffd = -1;
       }
       if(!afile->authokay)
               return Eauth;
       chan->authed = 1;
       return 0;
}

static int
fsattach(Chan* chan, Fcall* f, Fcall* r)
{
       char *aname;
       Iobuf *p;
       Dentry *d;
       File *file;
       File *afile;
       Filsys *fs;
       long raddr;
       int error, u;

       aname = f->aname;
       if(!aname[0])   /* default */
               aname = "main";
       p = nil;
       afile = filep(chan, f->afid, 0);
       file = filep(chan, f->fid, 1);
       if(file == nil){
               error = Efidinuse;
               goto out;
       }

       u = -1;
       if(chan != cons.chan){
/*
               if(noattach && strcmp(f->uname, "none")) {
                       error = Enoattach;
                       goto out;
               }
*/
               if(strcmp(f->uname, "adm") == 0){
                       error = Eauth;
                       goto out;
               }
               u = strtouid(f->uname);
               if(u < 0){
                       error = Ebadu;
                       goto out;
               }
       }
       file->uid = u;

       fs = fsstr(aname);
       if(fs == nil){
               error = Ebadspc;
               goto out;
       }

       if(error = checkattach(chan, afile, file, fs))
               goto out;

       raddr = getraddr(fs->dev);
       p = getbuf(fs->dev, raddr, Bread);
       d = getdir(p, 0);
       if(d == nil || checktag(p, Tdir, QPROOT) || !(d->mode & DALLOC)){
               error = Ealloc;
               goto out;
       }
       if(iaccess(file, d, DEXEC)){
               error = Eaccess;
               goto out;
       }
       if(file->uid == 0 && isro(fs->dev)) {
               /*
                * 'none' not allowed on dump
                */
               error = Eaccess;
               goto out;
       }
       accessdir(p, d, FREAD);
       mkqid(&file->qid, d, 1);
       file->fs = fs;
       file->addr = raddr;
       file->slot = 0;
       file->open = 0;
       freewp(file->wpath);
       file->wpath = 0;

       r->qid = file->qid;

//      if(cons.flags & attachflag)
//              print("9p2: attach %s %T to \"%s\" C%d\n",
//                      chan->whoname, chan->whotime, fs->name, chan->chan);

out:
//      if((cons.flags & attachflag) && error)
//              print("9p2: attach %s %T SUCK EGGS --- %s\n",
//                      f->uname, time(), errstr[error]);
       if(p != nil)
               putbuf(p);
       if(afile != nil)
               qunlock(afile);
       if(file != nil){
               qunlock(file);
               if(error)
                       freefp(file);
       }

       return error;
}

static int
fsflush(Chan* chan, Fcall*, Fcall*)
{
       runlock(&chan->reflock);
       wlock(&chan->reflock);
       wunlock(&chan->reflock);
       rlock(&chan->reflock);

       return 0;
}

static void
clone(File* nfile, File* file)
{
       Wpath *wpath;

       nfile->qid = file->qid;

       lock(&wpathlock);
       nfile->wpath = file->wpath;
       for(wpath = nfile->wpath; wpath != nil; wpath = wpath->up)
               wpath->refs++;
       unlock(&wpathlock);

       nfile->fs = file->fs;
       nfile->addr = file->addr;
       nfile->slot = file->slot;
       nfile->uid = file->uid;
       nfile->open = file->open & ~FREMOV;
}

static int
walkname(File* file, char* wname, Qid* wqid)
{
       Wpath *w;
       Iobuf *p, *p1;
       Dentry *d, *d1;
       int error, slot;
       long addr, qpath;

       p = p1 = nil;

       /*
        * File must not have been opened for I/O by an open
        * or create message and must represent a directory.
        */
       if(file->open != 0){
               error = Emode;
               goto out;
       }

       p = getbuf(file->fs->dev, file->addr, Bread);
       if(p == nil || checktag(p, Tdir, QPNONE)){
               error = Edir1;
               goto out;
       }
       if((d = getdir(p, file->slot)) == nil || !(d->mode & DALLOC)){
               error = Ealloc;
               goto out;
       }
       if(!(d->mode & DDIR)){
               error = Edir1;
               goto out;
       }
       if(error = mkqidcmp(&file->qid, d))
               goto out;

       /*
        * For walked elements the implied user must
        * have permission to search the directory.
        */
       if(file->cp != cons.chan && iaccess(file, d, DEXEC)){
               error = Eaccess;
               goto out;
       }
       accessdir(p, d, FREAD);

       if(strcmp(wname, ".") == 0){
setdot:
               if(wqid != nil)
                       *wqid = file->qid;
               goto out;
       }
       if(strcmp(wname, "..") == 0){
               if(file->wpath == 0)
                       goto setdot;
               putbuf(p);
               p = nil;
               addr = file->wpath->addr;
               slot = file->wpath->slot;
               p1 = getbuf(file->fs->dev, addr, Bread);
               if(p1 == nil || checktag(p1, Tdir, QPNONE)){
                       error = Edir1;
                       goto out;
               }
               if((d1 = getdir(p1, slot)) == nil || !(d1->mode & DALLOC)){
                       error = Ephase;
                       goto out;
               }
               lock(&wpathlock);
               file->wpath->refs--;
               file->wpath = file->wpath->up;
               unlock(&wpathlock);
               goto found;
       }

       for(addr = 0; ; addr++){
               if(p == nil){
                       p = getbuf(file->fs->dev, file->addr, Bread);
                       if(p == nil || checktag(p, Tdir, QPNONE)){
                               error = Ealloc;
                               goto out;
                       }
                       d = getdir(p, file->slot);
                       if(d == nil ||  !(d->mode & DALLOC)){
                               error = Ealloc;
                               goto out;
                       }
               }
               qpath = d->qid.path;
               p1 = dnodebuf1(p, d, addr, 0);
               p = nil;
               if(p1 == nil || checktag(p1, Tdir, qpath)){
                       error = Eentry;
                       goto out;
               }
               for(slot = 0; slot < DIRPERBUF; slot++){
                       d1 = getdir(p1, slot);
                       if(!(d1->mode & DALLOC))
                               continue;
                       if(strncmp(wname, d1->name, NAMELEN) != 0)
                               continue;
                       /*
                        * update walk path
                        */
                       if((w = newwp()) == nil){
                               error = Ewalk;
                               goto out;
                       }
                       w->addr = file->addr;
                       w->slot = file->slot;
                       w->up = file->wpath;
                       file->wpath = w;
                       slot += DIRPERBUF*addr;
                       goto found;
               }
               putbuf(p1);
               p1 = nil;
       }

found:
       file->addr = p1->addr;
       mkqid(&file->qid, d1, 1);
       putbuf(p1);
       p1 = nil;
       file->slot = slot;
       if(wqid != nil)
               *wqid = file->qid;

out:
       if(p1 != nil)
               putbuf(p1);
       if(p != nil)
               putbuf(p);

       return error;
}

static int
fswalk(Chan* chan, Fcall* f, Fcall* r)
{
       int error, nwname;
       File *file, *nfile, tfile;

       /*
        * The file identified by f->fid must be valid in the
        * current session and must not have been opened for I/O
        * by an open or create message.
        */
       if((file = filep(chan, f->fid, 0)) == nil)
               return Efid;
       if(file->open != 0){
               qunlock(file);
               return Emode;
       }

       /*
        * If newfid is not the same as fid, allocate a new file;
        * a side effect is checking newfid is not already in use (error);
        * if there are no names to walk this will be equivalent to a
        * simple 'clone' operation.
        * Otherwise, fid and newfid are the same and if there are names
        * to walk make a copy of 'file' to be used during the walk as
        * 'file' must only be updated on success.
        * Finally, it's a no-op if newfid is the same as fid and f->nwname
        * is 0.
        */
       r->nwqid = 0;
       if(f->newfid != f->fid){
               if((nfile = filep(chan, f->newfid, 1)) == nil){
                       qunlock(file);
                       return Efidinuse;
               }
       }
       else if(f->nwname != 0){
               nfile = &tfile;
               memset(nfile, 0, sizeof(File));
               nfile->cp = chan;
               nfile->fid = ~0;
       }
       else{
               qunlock(file);
               return 0;
       }
       clone(nfile, file);

       /*
        * Should check name is not too long.
        */
       error = 0;
       for(nwname = 0; nwname < f->nwname; nwname++){
               error = walkname(nfile, f->wname[nwname], &r->wqid[r->nwqid]);
               if(error != 0 || ++r->nwqid >= MAXDAT/sizeof(Qid))
                       break;
       }

       if(f->nwname == 0){
               /*
                * Newfid must be different to fid (see above)
                * so this is a simple 'clone' operation - there's
                * nothing to do except unlock unless there's
                * an error.
                */
               if(error){
                       freewp(nfile->wpath);
                       qunlock(nfile);
                       freefp(nfile);
               }
               else
                       qunlock(nfile);
       }
       else if(r->nwqid < f->nwname){
               /*
                * Didn't walk all elements, 'clunk' nfile
                * and leave 'file' alone.
                * Clear error if some of the elements were
                * walked OK.
                */
               freewp(nfile->wpath);
               if(nfile != &tfile){
                       qunlock(nfile);
                       freefp(nfile);
               }
               if(r->nwqid != 0)
                       error = 0;
       }
       else{
               /*
                * Walked all elements. If newfid is the same
                * as fid must update 'file' from the temporary
                * copy used during the walk.
                * Otherwise just unlock (when using tfile there's
                * no need to unlock as it's a local).
                */
               if(nfile == &tfile){
                       file->qid = nfile->qid;
                       freewp(file->wpath);
                       file->wpath = nfile->wpath;
                       file->addr = nfile->addr;
                       file->slot = nfile->slot;
               }
               else
                       qunlock(nfile);
       }
       qunlock(file);

       return error;
}

static int
fsopen(Chan* chan, Fcall* f, Fcall* r)
{
       Iobuf *p;
       Dentry *d;
       File *file;
       Tlock *t;
       Qid qid;
       int error, ro, fmod, wok;

       wok = 0;
       p = nil;

       if(chan == cons.chan || writeallow)
               wok = 1;

       if((file = filep(chan, f->fid, 0)) == nil){
               error = Efid;
               goto out;
       }

       /*
        * if remove on close, check access here
        */
       ro = isro(file->fs->dev);
       if(f->mode & ORCLOSE){
               if(ro){
                       error = Eronly;
                       goto out;
               }
               /*
                * check on parent directory of file to be deleted
                */
               if(file->wpath == 0 || file->wpath->addr == file->addr){
                       error = Ephase;
                       goto out;
               }
               p = getbuf(file->fs->dev, file->wpath->addr, Bread);
               if(p == nil || checktag(p, Tdir, QPNONE)){
                       error = Ephase;
                       goto out;
               }
               if((d = getdir(p, file->wpath->slot)) == nil || !(d->mode & DALLOC)){
                       error = Ephase;
                       goto out;
               }
               if(iaccess(file, d, DWRITE)){
                       error = Eaccess;
                       goto out;
               }
               putbuf(p);
       }
       p = getbuf(file->fs->dev, file->addr, Bread);
       if(p == nil || checktag(p, Tdir, QPNONE)){
               error = Ealloc;
               goto out;
       }
       if((d = getdir(p, file->slot)) == nil || !(d->mode & DALLOC)){
               error = Ealloc;
               goto out;
       }
       if(error = mkqidcmp(&file->qid, d))
               goto out;
       mkqid(&qid, d, 1);
       switch(f->mode & 7){

       case OREAD:
               if(iaccess(file, d, DREAD) && !wok)
                       goto badaccess;
               fmod = FREAD;
               break;

       case OWRITE:
               if((d->mode & DDIR) || (iaccess(file, d, DWRITE) && !wok))
                       goto badaccess;
               if(ro){
                       error = Eronly;
                       goto out;
               }
               fmod = FWRITE;
               break;

       case ORDWR:
               if((d->mode & DDIR)
               || (iaccess(file, d, DREAD) && !wok)
               || (iaccess(file, d, DWRITE) && !wok))
                       goto badaccess;
               if(ro){
                       error = Eronly;
                       goto out;
               }
               fmod = FREAD+FWRITE;
               break;

       case OEXEC:
               if((d->mode & DDIR) || (iaccess(file, d, DEXEC) && !wok))
                       goto badaccess;
               fmod = FREAD;
               break;

       default:
               error = Emode;
               goto out;
       }
       if(f->mode & OTRUNC){
               if((d->mode & DDIR) || (iaccess(file, d, DWRITE) && !wok))
                       goto badaccess;
               if(ro){
                       error = Eronly;
                       goto out;
               }
       }
       t = 0;
       if(d->mode & DLOCK){
               if((t = tlocked(p, d)) == nil){
                       error = Elocked;
                       goto out;
               }
       }
       if(f->mode & ORCLOSE)
               fmod |= FREMOV;
       file->open = fmod;
       if((f->mode & OTRUNC) && !(d->mode & DAPND)){
               dtrunc(p, d);
               qid.vers = d->qid.version;
       }
       r->qid = qid;
       file->tlock = t;
       if(t != nil)
               t->file = file;
       file->lastra = 1;
       goto out;

badaccess:
       error = Eaccess;
       file->open = 0;

out:
       if(p != nil)
               putbuf(p);
       if(file != nil)
               qunlock(file);

       r->iounit = chan->msize-IOHDRSZ;

       return error;
}

static int
dir9p2(Dir* dir, Dentry* dentry, void* strs)
{
       char *op, *p;

       memset(dir, 0, sizeof(Dir));
       mkqid(&dir->qid, dentry, 1);
       dir->mode = (dir->qid.type<<24)|(dentry->mode & 0777);
       dir->atime = dentry->atime;
       dir->mtime = dentry->mtime;
       dir->length = dentry->size;

       op = p = strs;
       dir->name = p;
       p += sprint(p, "%s", dentry->name)+1;

       dir->uid = p;
       uidtostr(p, dentry->uid);
       p += strlen(p)+1;

       dir->gid = p;
       uidtostr(p, dentry->gid);
       p += strlen(p)+1;

       dir->muid = p;
       strcpy(p, "");
       p += strlen(p)+1;

       return p-op;
}

static int
checkname9p2(char* name)
{
       char *p;

       /*
        * Return length of string if valid, 0 if not.
        */
       if(name == nil)
               return 0;

       for(p = name; *p != 0; p++){
               if((*p & 0xFF) <= 040)
                       return 0;
       }

       return p-name;
}

static int
fscreate(Chan* chan, Fcall* f, Fcall* r)
{
       Iobuf *p, *p1;
       Dentry *d, *d1;
       File *file;
       int error, slot, slot1, fmod, wok, l;
       long addr, addr1, path;
       Tlock *t;
       Wpath *w;

       wok = 0;
       p = nil;

       if(chan == cons.chan || writeallow)
               wok = 1;

       if((file = filep(chan, f->fid, 0)) == nil){
               error = Efid;
               goto out;
       }
       if(isro(file->fs->dev)){
               error = Eronly;
               goto out;
       }

       p = getbuf(file->fs->dev, file->addr, Bread);
       if(p == nil || checktag(p, Tdir, QPNONE)){
               error = Ealloc;
               goto out;
       }
       if((d = getdir(p, file->slot)) == nil || !(d->mode & DALLOC)){
               error = Ealloc;
               goto out;
       }
       if(error = mkqidcmp(&file->qid, d))
               goto out;
       if(!(d->mode & DDIR)){
               error = Edir2;
               goto out;
       }
       if(iaccess(file, d, DWRITE) && !wok) {
               error = Eaccess;
               goto out;
       }
       accessdir(p, d, FREAD);

       /*
        * Check the name is valid and will fit in an old
        * directory entry.
        */
       if((l = checkname9p2(f->name)) == 0){
               error = Ename;
               goto out;
       }
       if(l+1 > NAMELEN){
               error = Etoolong;
               goto out;
       }
       if(strcmp(f->name, ".") == 0 || strcmp(f->name, "..") == 0){
               error = Edot;
               goto out;
       }

       addr1 = 0;
       slot1 = 0;      /* set */
       for(addr = 0; ; addr++){
               if((p1 = dnodebuf(p, d, addr, 0)) == nil){
                       if(addr1 != 0)
                               break;
                       p1 = dnodebuf(p, d, addr, Tdir);
               }
               if(p1 == nil){
                       error = Efull;
                       goto out;
               }
               if(checktag(p1, Tdir, d->qid.path)){
                       putbuf(p1);
                       goto phase;
               }
               for(slot = 0; slot < DIRPERBUF; slot++){
                       d1 = getdir(p1, slot);
                       if(!(d1->mode & DALLOC)){
                               if(addr1 == 0){
                                       addr1 = p1->addr;
                                       slot1 = slot + addr*DIRPERBUF;
                               }
                               continue;
                       }
                       if(strncmp(f->name, d1->name, sizeof(d1->name)) == 0){
                               putbuf(p1);
                               error = Eexist;
                               goto out;
                       }
               }
               putbuf(p1);
       }

       switch(f->mode & 7){
       case OEXEC:
       case OREAD:             /* seems only useful to make directories */
               fmod = FREAD;
               break;

       case OWRITE:
               fmod = FWRITE;
               break;

       case ORDWR:
               fmod = FREAD+FWRITE;
               break;

       default:
               error = Emode;
               goto out;
       }
       if(f->perm & PDIR)
               if((f->mode & OTRUNC) || (f->perm & PAPND) || (fmod & FWRITE))
                       goto badaccess;
       /*
        * do it
        */
       path = qidpathgen(&file->fs->dev);
       if((p1 = getbuf(file->fs->dev, addr1, Bread|Bimm|Bmod)) == nil)
               goto phase;
       d1 = getdir(p1, slot1);
       if(d1 == nil || checktag(p1, Tdir, d->qid.path)) {
               putbuf(p1);
               goto phase;
       }
       if(d1->mode & DALLOC){
               putbuf(p1);
               goto phase;
       }

       strncpy(d1->name, f->name, sizeof(d1->name));
       if(chan == cons.chan){
               d1->uid = cons.uid;
               d1->gid = cons.gid;
       }
       else{
               d1->uid = file->uid;
               d1->gid = d->gid;
               f->perm &= d->mode | ~0666;
               if(f->perm & PDIR)
                       f->perm &= d->mode | ~0777;
       }
       d1->qid.path = path;
       d1->qid.version = 0;
       d1->mode = DALLOC | (f->perm & 0777);
       if(f->perm & PDIR) {
               d1->mode |= DDIR;
               d1->qid.path |= QPDIR;
       }
       if(f->perm & PAPND)
               d1->mode |= DAPND;
       t = nil;
       if(f->perm & PLOCK){
               d1->mode |= DLOCK;
               t = tlocked(p1, d1);
               /* if nil, out of tlock structures */
       }
       accessdir(p1, d1, FWRITE);
       mkqid(&r->qid, d1, 0);
       putbuf(p1);
       accessdir(p, d, FWRITE);

       /*
        * do a walk to new directory entry
        */
       if((w = newwp()) == nil){
               error = Ewalk;
               goto out;
       }
       w->addr = file->addr;
       w->slot = file->slot;
       w->up = file->wpath;
       file->wpath = w;
       file->qid = r->qid;
       file->tlock = t;
       if(t != nil)
               t->file = file;
       file->lastra = 1;
       if(f->mode & ORCLOSE)
               fmod |= FREMOV;
       file->open = fmod;
       file->addr = addr1;
       file->slot = slot1;
       goto out;

badaccess:
       error = Eaccess;
       goto out;

phase:
       error = Ephase;

out:
       if(p != nil)
               putbuf(p);
       if(file != nil)
               qunlock(file);

       r->iounit = chan->msize-IOHDRSZ;

       return error;
}

static int
fsread(Chan* chan, Fcall* f, Fcall* r)
{
       uchar *data;
       Iobuf *p, *p1;
       File *file;
       Dentry *d, *d1;
       Tlock *t;
       long addr, offset, start, tim;
       int error, iounit, nread, count, n, o, slot;
       Dir dir;
       char strdata[28*10];

       p = nil;
       data = (uchar*)r->data;
       count = f->count;
       offset = f->offset;
       nread = 0;
       if((file = filep(chan, f->fid, 0)) == nil){
               error = Efid;
               goto out;
       }
       if(file->qid.type & QTAUTH){
               error = 0;
               nread = pread(file->ffd, data, count, offset);
               if(nread < 0)
                       error = Esystem;
               goto out;
       }
       if(!(file->open & FREAD)){
               error = Eopen;
               goto out;
       }
       iounit = chan->msize-IOHDRSZ;
       if(count < 0 || count > iounit){
               error = Ecount;
               goto out;
       }
       if(offset < 0){
               error = Eoffset;
               goto out;
       }
       p = getbuf(file->fs->dev, file->addr, Bread);
       if(p == nil || checktag(p, Tdir, QPNONE)){
               error = Ealloc;
               goto out;
       }
       if((d = getdir(p, file->slot)) == nil || !(d->mode & DALLOC)){
               error = Ealloc;
               goto out;
       }
       if(error = mkqidcmp(&file->qid, d))
               goto out;
       if(t = file->tlock){
               tim = time(0);
               if(t->time < tim || t->file != file){
                       error = Ebroken;
                       goto out;
               }
               /* renew the lock */
               t->time = tim + TLOCK;
       }
       accessdir(p, d, FREAD);
       if(d->mode & DDIR)
               goto dread;
       if(offset+count > d->size)
               count = d->size - offset;
       while(count > 0){
               if(p == nil){
                       p = getbuf(file->fs->dev, file->addr, Bread);
                       if(p == nil || checktag(p, Tdir, QPNONE)){
                               error = Ealloc;
                               goto out;
                       }
                       if((d = getdir(p, file->slot)) == nil || !(d->mode & DALLOC)){
                               error = Ealloc;
                               goto out;
                       }
               }
               addr = offset / BUFSIZE;
               o = offset % BUFSIZE;
               n = BUFSIZE - o;
               if(n > count)
                       n = count;
               p1 = dnodebuf1(p, d, addr, 0);
               p = nil;
               if(p1 != nil){
                       if(checktag(p1, Tfile, QPNONE)){
                               error = Ephase;
                               putbuf(p1);
                               goto out;
                       }
                       memmove(data+nread, p1->iobuf+o, n);
                       putbuf(p1);
               }
               else
                       memset(data+nread, 0, n);
               count -= n;
               nread += n;
               offset += n;
       }
       goto out;

dread:
       /*
        * Pick up where we left off last time if nothing has changed,
        * otherwise must scan from the beginning.
        */
       if(offset == file->doffset /*&& file->qid.vers == file->dvers*/){
               addr = file->dslot/DIRPERBUF;
               slot = file->dslot%DIRPERBUF;
               start = offset;
       }
       else{
               addr = 0;
               slot = 0;
               start = 0;
       }

dread1:
       if(p == nil){
               /*
                * This is just a check to ensure the entry hasn't
                * gone away during the read of each directory block.
                */
               p = getbuf(file->fs->dev, file->addr, Bread);
               if(p == nil || checktag(p, Tdir, QPNONE)){
                       error = Ealloc;
                       goto out1;
               }
               if((d = getdir(p, file->slot)) == nil || !(d->mode & DALLOC)){
                       error = Ealloc;
                       goto out1;
               }
       }
       p1 = dnodebuf1(p, d, addr, 0);
       p = nil;
       if(p1 == nil)
               goto out1;
       if(checktag(p1, Tdir, QPNONE)){
               error = Ephase;
               putbuf(p1);
               goto out1;
       }

       for(; slot < DIRPERBUF; slot++){
               d1 = getdir(p1, slot);
               if(!(d1->mode & DALLOC))
                       continue;
               dir9p2(&dir, d1, strdata);
               if((n = convD2M(&dir, data+nread, iounit - nread)) <= BIT16SZ){
                       putbuf(p1);
                       goto out1;
               }
               start += n;
               if(start < offset)
                       continue;
               if(count < n){
                       putbuf(p1);
                       goto out1;
               }
               count -= n;
               nread += n;
               offset += n;
       }
       putbuf(p1);
       slot = 0;
       addr++;
       goto dread1;

out1:
       if(error == 0){
               file->doffset = offset;
               file->dvers = file->qid.vers;
               file->dslot = slot+DIRPERBUF*addr;
       }

out:
       /*
        * Do we need this any more?
       count = f->count - nread;
       if(count > 0)
               memset(data+nread, 0, count);
        */
       if(p != nil)
               putbuf(p);
       if(file != nil)
               qunlock(file);
       r->count = nread;
       r->data = (char*)data;

       return error;
}

static int
fswrite(Chan* chan, Fcall* f, Fcall* r)
{
       Iobuf *p, *p1;
       Dentry *d;
       File *file;
       Tlock *t;
       long offset, addr, tim, qpath;
       int count, error, nwrite, o, n;

       offset = f->offset;
       count = f->count;

       nwrite = 0;
       p = nil;

       if((file = filep(chan, f->fid, 0)) == nil){
               error = Efid;
               goto out;
       }
       if(file->qid.type & QTAUTH){
               error = 0;
               nwrite = pwrite(file->ffd, f->data, count, offset);
               if(nwrite < 0)
                       error = Esystem;
               goto out;
       }
       if(!(file->open & FWRITE)){
               error = Eopen;
               goto out;
       }
       if(isro(file->fs->dev)){
               error = Eronly;
               goto out;
       }
       if(count < 0 || count > chan->msize-IOHDRSZ){
               error = Ecount;
               goto out;
       }
       if(offset < 0) {
               error = Eoffset;
               goto out;
       }
       if((p = getbuf(file->fs->dev, file->addr, Bread|Bmod)) == nil){
               error = Ealloc;
               goto out;
       }
       if((d = getdir(p, file->slot)) == nil || !(d->mode & DALLOC)){
               error = Ealloc;
               goto out;
       }
       if(error = mkqidcmp(&file->qid, d))
               goto out;
       if(t = file->tlock) {
               tim = time(0);
               if(t->time < tim || t->file != file){
                       error = Ebroken;
                       goto out;
               }
               /* renew the lock */
               t->time = tim + TLOCK;
       }
       accessdir(p, d, FWRITE);
       if(d->mode & DAPND)
               offset = d->size;
       if(offset+count > d->size)
               d->size = offset+count;
       while(count > 0){
               if(p == nil){
                       p = getbuf(file->fs->dev, file->addr, Bread|Bmod);
                       if((d = getdir(p, file->slot)) == nil || !(d->mode & DALLOC)){
                               error = Ealloc;
                               goto out;
                       }
               }
               addr = offset / BUFSIZE;
               o = offset % BUFSIZE;
               n = BUFSIZE - o;
               if(n > count)
                       n = count;
               qpath = d->qid.path;
               p1 = dnodebuf1(p, d, addr, Tfile);
               p = nil;
               if(p1 == nil) {
                       error = Efull;
                       goto out;
               }
               if(checktag(p1, Tfile, qpath)){
                       putbuf(p1);
                       error = Ephase;
                       goto out;
               }
               memmove(p1->iobuf+o, f->data+nwrite, n);
               p1->flags |= Bmod;
               putbuf(p1);
               count -= n;
               nwrite += n;
               offset += n;
       }

out:
       if(p != nil)
               putbuf(p);
       if(file != nil)
               qunlock(file);
       r->count = nwrite;

       return error;
}

static int
_clunk(File* file, int remove, int wok)
{
       Tlock *t;
       int error;

       error = 0;
       if(t = file->tlock){
               if(t->file == file)
                       t->time = 0;            /* free the lock */
               file->tlock = 0;
       }
       if(remove)
               error = doremove(file, wok);
       file->open = 0;
       freewp(file->wpath);
       freefp(file);
       qunlock(file);

       return error;
}

static int
fsclunk(Chan* chan, Fcall* f, Fcall*)
{
       File *file;

       if((file = filep(chan, f->fid, 0)) == nil)
               return Efid;

       _clunk(file, file->open & FREMOV, 0);
       return 0;
}

static int
fsremove(Chan* chan, Fcall* f, Fcall*)
{
       File *file;

       if((file = filep(chan, f->fid, 0)) == nil)
               return Efid;

       return _clunk(file, 1, chan == cons.chan);
}

static int
fsstat(Chan* chan, Fcall* f, Fcall* r, uchar* data)
{
       Dir dir;
       Iobuf *p;
       Dentry *d;
       File *file;
       int error, len;

       if((file = filep(chan, f->fid, 0)) == nil)
               return Efid;

       p = getbuf(file->fs->dev, file->addr, Bread);
       if(p == nil || checktag(p, Tdir, QPNONE)){
               error = Edir1;
               goto out;
       }
       if((d = getdir(p, file->slot)) == nil || !(d->mode & DALLOC)){
               error = Ealloc;
               goto out;
       }
       if(error = mkqidcmp(&file->qid, d))
               goto out;

       if(d->qid.path == QPROOT)       /* stat of root gives time */
               d->atime = time(0);

       len = dir9p2(&dir, d, data);
       data += len;
       if((r->nstat = convD2M(&dir, data, chan->msize - len)) == 0)
               error = Ersc;
       else
               r->stat = data;

out:
       if(p != nil)
               putbuf(p);
       if(file != nil)
               qunlock(file);

       return error;
}

static int
fswstat(Chan* chan, Fcall* f, Fcall*, char *strs)
{
       Iobuf *p, *p1;
       Dentry *d, *d1, xd;
       File *file;
       int error, slot, uid, gid, l;
       long addr;
       Dir dir;
       ulong mode;

       p = p1 = nil;
       d1 = nil;

       if((file = filep(chan, f->fid, 0)) == nil){
               error = Efid;
               goto out;
       }

       /*
        * if user none,
        * can't do anything
        * unless allow.
        */
       if(file->uid == 0 && !wstatallow){
               error = Eaccess;
               goto out;
       }

       if(isro(file->fs->dev)){
               error = Eronly;
               goto out;
       }

       /*
        * first get parent
        */
       if(file->wpath){
               p1 = getbuf(file->fs->dev, file->wpath->addr, Bread);
               if(p1 == nil){
                       error = Ephase;
                       goto out;
               }
               d1 = getdir(p1, file->wpath->slot);
               if(d1 == nil || checktag(p1, Tdir, QPNONE) || !(d1->mode & DALLOC)){
                       error = Ephase;
                       goto out;
               }
       }

       if((p = getbuf(file->fs->dev, file->addr, Bread)) == nil){
               error = Ealloc;
               goto out;
       }
       d = getdir(p, file->slot);
       if(d == nil || checktag(p, Tdir, QPNONE) || !(d->mode & DALLOC)){
               error = Ealloc;
               goto out;
       }
       if(error = mkqidcmp(&file->qid, d))
               goto out;

       /*
        * Convert the message and fix up
        * fields not to be changed.
        */
       if(convM2D(f->stat, f->nstat, &dir, strs) == 0){
               print("9p2: convM2D returns 0\n");
               error = Econvert;
               goto out;
       }
       if(dir.uid == nil || strlen(dir.uid) == 0)
               uid = d->uid;
       else
               uid = strtouid(dir.uid);
       if(dir.gid == nil || strlen(dir.gid) == 0)
               gid = d->gid;
       else
               gid = strtouid(dir.gid);
       if(dir.name == nil || strlen(dir.name) == 0)
               dir.name = d->name;
       else{
               if((l = checkname9p2(dir.name)) == 0){
                       error = Ename;
                       goto out;
               }
               if(l > NAMELEN){
                       error = Etoolong;
                       goto out;
               }
       }

       /*
        * Before doing sanity checks, find out what the
        * new 'mode' should be:
        * if 'type' and 'mode' are both defaults, take the
        * new mode from the old directory entry;
        * else if 'type' is the default, use the new mode entry;
        * else if 'mode' is the default, create the new mode from
        * 'type' or'ed with the old directory mode;
        * else neither are defaults, use the new mode but check
        * it agrees with 'type'.
        */
       if(dir.qid.type == 0xFF && dir.mode == ~0){
               dir.mode = d->mode & 0777;
               if(d->mode & DLOCK)
                       dir.mode |= DMEXCL;
               if(d->mode & DAPND)
                       dir.mode |= DMAPPEND;
               if(d->mode & DDIR)
                       dir.mode |= DMDIR;
       }
       else if(dir.qid.type == 0xFF){
               /* nothing to do */
       }
       else if(dir.mode == ~0)
               dir.mode = (dir.qid.type<<24)|(d->mode & 0777);
       else if(dir.qid.type != ((dir.mode>>24) & 0xFF)){
               error = Eqidmode;
               goto out;
       }

       /*
        * Check for unknown type/mode bits
        * and an attempt to change the directory bit.
        */
       if(dir.mode & ~(DMDIR|DMAPPEND|DMEXCL|0777)){
               error = Enotm;
               goto out;
       }
       if(d->mode & DDIR)
               mode = DMDIR;
       else
               mode = 0;
       if((dir.mode^mode) & DMDIR){
               error = Enotd;
               goto out;
       }

       if(dir.mtime == ~0)
               dir.mtime = d->mtime;
       if(dir.length == ~0)
               dir.length = d->size;

       /*
        * Currently, can't change length.
        */
       if(dir.length != d->size){
               error = Enotl;
               goto out;
       }

       /*
        * if chown,
        * must be god
        * wstatallow set to allow chown during boot
        */
       if(uid != d->uid && !wstatallow) {
               error = Enotu;
               goto out;
       }

       /*
        * if chgroup,
        * must be either
        *      a) owner and in new group
        *      b) leader of both groups
        * wstatallow and writeallow are set to allow chgrp during boot
        */
       while(gid != d->gid) {
               if(wstatallow || writeallow)
                       break;
               if(d->uid == file->uid && ingroup(file->uid, gid))
                       break;
               if(leadgroup(file->uid, gid))
                       if(leadgroup(file->uid, d->gid))
                               break;
               error = Enotg;
               goto out;
       }

       /*
        * if rename,
        * must have write permission in parent
        */
       while(strncmp(d->name, dir.name, sizeof(d->name)) != 0) {
               if(checkname(dir.name) || d1 == nil) {
                       error = Ename;
                       goto out;
               }
               if(strcmp(dir.name, ".") == 0 || strcmp(xd.name, "..") == 0) {
                       error = Ename;
                       goto out;
               }

               /*
                * drop entry to prevent lock, then
                * check that destination name is unique,
                */
               putbuf(p);
               for(addr = 0; ; addr++) {
                       if((p = dnodebuf(p1, d1, addr, 0)) == nil)
                               break;
                       if(checktag(p, Tdir, d1->qid.path)) {
                               putbuf(p);
                               continue;
                       }
                       for(slot = 0; slot < DIRPERBUF; slot++) {
                               d = getdir(p, slot);
                               if(!(d->mode & DALLOC))
                                       continue;
                               if(strncmp(dir.name, d->name, sizeof(d->name)) == 0) {
                                       error = Eexist;
                                       goto out;
                               }
                       }
                       putbuf(p);
               }

               /*
                * reacquire entry
                */
               if((p = getbuf(file->fs->dev, file->addr, Bread)) == nil){
                       error = Ephase;
                       goto out;
               }
               d = getdir(p, file->slot);
               if(d == nil || checktag(p, Tdir, QPNONE) || !(d->mode & DALLOC)) {
                       error = Ephase;
                       goto out;
               }

               if(wstatallow || writeallow) /* set to allow rename during boot */
                       break;
               if(d1 == nil || iaccess(file, d1, DWRITE)) {
                       error = Eaccess;
                       goto out;
               }
               break;
       }

       /*
        * if mode/time, either
        *      a) owner
        *      b) leader of either group
        */
       mode = dir.mode & 0777;
       if(dir.mode & DMAPPEND)
               mode |= DAPND;
       if(dir.mode & DMEXCL)
               mode |= DLOCK;
       while(d->mtime != dir.mtime || ((d->mode^mode) & (DAPND|DLOCK|0777))) {
               if(wstatallow)                  /* set to allow chmod during boot */
                       break;
               if(d->uid == file->uid)
                       break;
               if(leadgroup(file->uid, gid))
                       break;
               if(leadgroup(file->uid, d->gid))
                       break;
               error = Enotu;
               goto out;
       }
       d->mtime = dir.mtime;
       d->uid = uid;
       d->gid = gid;
       d->mode = (mode & (DAPND|DLOCK|0777)) | (d->mode & (DALLOC|DDIR));

       strncpy(d->name, dir.name, sizeof(d->name));
       accessdir(p, d, FREAD);

out:
       if(p != nil)
               putbuf(p);
       if(p1 != nil)
               putbuf(p1);
       if(file != nil)
               qunlock(file);

       return error;
}

static int
recv(Chan *c, uchar *buf, int n)
{
       int fd, m, len;

       fd = c->chan;
       /* read count */
       qlock(&c->rlock);
       m = readn(fd, buf, BIT32SZ);
       if(m != BIT32SZ){
               qunlock(&c->rlock);
               if(m < 0){
                       print("readn(BIT32SZ) fails: %r\n");
                       return -1;
               }
               print("readn(BIT32SZ) returns %d: %r\n", m);
               return 0;
       }

       len = GBIT32(buf);
       if(len <= BIT32SZ || len > n){
               print("recv bad length %d\n", len);
               werrstr("bad length in 9P2000 message header");
               qunlock(&c->rlock);
               return -1;
       }
       len -= BIT32SZ;
       m = readn(fd, buf+BIT32SZ, len);
       qunlock(&c->rlock);
       if(m < len){
               print("recv wanted %d got %d\n", len, m);
               return 0;
       }
       return BIT32SZ+m;
}

static void
send(Chan *c, uchar *buf, int n)
{
       int fd, m;

       fd = c->chan;
       qlock(&c->wlock);
       m = write(fd, buf, n);
       qunlock(&c->wlock);
       if(m == n)
               return;
       panic("write failed");
}

void
serve9p2(Chan *chan, uchar *ib, int nib)
{
       uchar inbuf[MSIZE+IOHDRSZ], outbuf[MSIZE+IOHDRSZ];
       Fcall f, r;
       char ename[64];
       int error, n, type;

       chan->msize = MSIZE;
       fmtinstall('F', fcallfmt);

       for(;;){
               if(nib){
                       memmove(inbuf, ib, nib);
                       n = nib;
                       nib = 0;
               }else
                       n = recv(chan, inbuf, sizeof inbuf);
               if(chat){
                       print("read msg %d (fd %d)\n", n, chan->chan);
                       if(n <= 0)
                               print("\terr: %r\n");
               }
               if(n == 0 && (chan == cons.srvchan || chan == cons.chan))
                       continue;
               if(n <= 0)
                       break;
               if(convM2S(inbuf, n, &f) != n){
                       print("9p2: cannot decode\n");
                       continue;
               }

               type = f.type;
               if(type < Tversion || type >= Tmax || (type&1) || type == Terror){
                       print("9p2: bad message type %d\n", type);
                       continue;
               }

               if(CHAT(chan))
                       print("9p2: f %F\n", &f);

               r.type = type+1;
               r.tag = f.tag;
               error = 0;

               rlock(&mainlock);
               rlock(&chan->reflock);
               switch(type){
               default:
                       r.type = Rerror;
                       snprint(ename, sizeof ename, "unknown message: %F", &f);
                       r.ename = ename;
                       break;
               case Tversion:
                       error = fsversion(chan, &f, &r);
                       break;
               case Tauth:
                       error = fsauth(chan, &f, &r);
                       break;
               case Tattach:
                       error = fsattach(chan, &f, &r);
                       break;
               case Tflush:
                       error = fsflush(chan, &f, &r);
                       break;
               case Twalk:
                       error = fswalk(chan, &f, &r);
                       break;
               case Topen:
                       error = fsopen(chan, &f, &r);
                       break;
               case Tcreate:
                       error = fscreate(chan, &f, &r);
                       break;
               case Tread:
                       r.data = (char*)inbuf;
                       error = fsread(chan, &f, &r);
                       break;
               case Twrite:
                       error = fswrite(chan, &f, &r);
                       break;
               case Tclunk:
                       error = fsclunk(chan, &f, &r);
                       break;
               case Tremove:
                       error = fsremove(chan, &f, &r);
                       break;
               case Tstat:
                       error = fsstat(chan, &f, &r, inbuf);
                       break;
               case Twstat:
                       error = fswstat(chan, &f, &r, (char*)outbuf);
                       break;
               }
               runlock(&chan->reflock);
               runlock(&mainlock);

               if(error != 0){
                       r.type = Rerror;
                       if(error >= MAXERR){
                               snprint(ename, sizeof(ename), "error %d", error);
                               r.ename = ename;
                       }
                       else
                               r.ename = errstring[error];
               }
               if(CHAT(chan))
                       print("9p2: r %F\n", &r);

               n = convS2M(&r, outbuf, sizeof outbuf);
               if(n == 0){
                       type = r.type;
                       r.type = Rerror;
                       snprint(ename, sizeof(ename), "9p2: convS2M: type %d", type);
                       r.ename = ename;
                       print(ename);
                       n = convS2M(&r, outbuf, sizeof outbuf);
                       if(n == 0){
                               /*
                                * What to do here, the failure notification failed?
                                */
                               panic("can't write anything at all");
                       }
               }
               send(chan, outbuf, n);
       }
       fileinit(chan);
       close(chan->chan);
       if(chan == cons.srvchan || chan == cons.chan)
               print("console chan read error");
}