#include <u.h>
#include <libc.h>
#include <thread.h>
#include <fcall.h>
#include "dat.h"
#include "fns.h"

int
chref(Fs *fs, uvlong r, int stat)
{
       uvlong i;
       int j;
       ulong rc;
       Buf *c;

       i = fs->fstart + r / REFPERBLK;
       j = r % REFPERBLK;
       c = getbuf(fs->d, i, TREF, 0);
       if(c == nil)
               return -1;
       if(stat < 0 && c->refs[j] < -stat)
               c->refs[j] = 0;
       else
               c->refs[j] += stat;
       rc = c->refs[j];
       if(stat != 0)
               c->op |= BDELWRI;
       putbuf(c);
       return rc;
}

static int
qidcmp(Qid *a, Qid *b)
{
       if(a->type != b->type)
               return 1;
       if(a->path != b->path){
               /* special case for dump, this is ok */
               if(a->path==ROOTQID && b->path==DUMPROOTQID)
                       return 0;
               return 1;
       }
       return 0;
}

Dentry*
getdent(FLoc *l, Buf *b)
{
       Dentry *d;

       d = &b->de[l->deind];
       if((d->mode & (DGONE | DALLOC)) == 0){
               dprint("getdent: file gone, d=%llux, l=%llud/%d %llux, callerpc %#p\n",
                       d->path, l->blk, l->deind, l->path, getcallerpc(&l));
               werrstr("phase error -- directory entry for nonexistent file");
               return nil;
       }
       if(qidcmp(d, l) != 0){
               dprint("getdent: wrong qid d=%llux != l=%llud/%d %llux, callerpc %#p\n",
                       d->path, l->blk, l->deind, l->path, getcallerpc(&l));
               werrstr("phase error -- qid mismatch");
               return nil;
       }
       return d;
}

int
getfree(Fs *fs, uvlong *r)
{
       Dev *d;
       Buf *b;
       uvlong i, l, e;
       int j, have;

       d = fs->d;
       while(nbrecv(fs->freelist, &l) > 0){
               i = fs->fstart + l / REFPERBLK;
               j = l % REFPERBLK;
               b = getbuf(d, i, TREF, 0);
               if(b != nil){
                       if(b->refs[j] == 0){
                               b->refs[j] = 1;
                               *r = l;
                               goto found;
                       }
                       putbuf(b);
               }
       }

       b = getbuf(d, SUPERBLK, TSUPERBLOCK, 0);
       if(b == nil) {
               werrstr("could not find superblock");
               return -1;
       }
       e = b->sb.fend;
       putbuf(b);

       have = 0;
       for(l = 0, i = fs->fstart; i < e; i++){
               b = getbuf(d, i, TREF, 0);
               if(b == nil){
                       l += REFPERBLK;
                       continue;
               }
               for(j = 0; j < REFPERBLK; j++, l++)
                       if(b->refs[j] == 0){
                               if(!have){
                                       b->refs[j] = 1;
                                       *r = l;
                                       have = 1;
                               }else if(nbsend(fs->freelist, &l) <= 0)
                                       goto found;
                       }
               if(have)
                       goto found;
               putbuf(b);
       }
       werrstr("disk full");
       return -1;
found:
       b->op |= BDELWRI;
       putbuf(b);
       return 1;
}

int
putfree(Fs *fs, uvlong r)
{
       int rc;

       rc = chref(fs, r, -1);
       if(rc < 0)
               return -1;
       if(rc == 0)
               nbsend(fs->freelist, &r);
       return 1;
}

static void
createroot(Fs *fs)
{
       uvlong r;
       Buf *c;
       Dentry *d;

       if(getfree(fs, &r) < 0)
               sysfatal("ream: getfree: %r");
       c = getbuf(fs->d, r, TDENTRY, 1);
       if(c == nil)
       error:
               sysfatal("ream: getbuf: %r");
       memset(c->de, 0, sizeof(c->de));
       d = &c->de[0];
       strcpy(d->name, "/");
       d->mode = DALLOC | 0775;
       d->path = ROOTQID;
       d->type = QTDIR;
       d->uid = -1;
       d->muid = -1;
       d->gid = -1;
       d->mtime = time(0);
       d->atime = d->mtime;
       d++;
       strcpy(d->name, "/");
       d->mode = DALLOC | 0775;
       d->path = ROOTQID;
       d->type = QTDIR;
       d->uid = -1;
       d->muid = -1;
       d->gid = -1;
       d->mtime = time(0);
       d->atime = d->mtime;
       c->op |= BWRIM;
       putbuf(c);
       c = getbuf(fs->d, SUPERBLK, TSUPERBLOCK, 0);
       if(c == nil)
               goto error;
       fs->root = r;
       c->sb.root = r;
       c->op |= BDELWRI;
       putbuf(c);
}

void
writeusers(Fs *fs)
{
       Chan *ch;

       ch = chanattach(fs, 0);
       if(ch == nil)
               goto error;
       ch->uid = -1;
       chancreat(ch, "adm", DMDIR | 0775, OREAD);
       chanclunk(ch);
       ch = chanattach(fs, 0);
       if(ch == nil)
               goto error;
       ch->uid = -1;
       if(chanwalk(ch, "adm") <= 0)
               goto error;
       if(chanwalk(ch, "users") > 0){
               if(chanopen(ch, OWRITE|OTRUNC) <= 0)
                       goto error;
       }else if(chancreat(ch, "users", 0664, OWRITE) <= 0)
                       goto error;
       if(userssave(fs, ch) < 0){
               chanremove(ch);
               ch = nil;
               goto error;
       }
       chanclunk(ch);
       return;
error:
       if(ch != nil)
               chanclunk(ch);
       dprint("writeusers: %r\n");
}

void
readusers(Fs *fs)
{
       Chan *ch;

       ch = chanattach(fs, 0);
       if(ch == nil)
               goto err;
       ch->uid = -1;
       if(chanwalk(ch, "adm") <= 0)
               goto err;
       if(chanwalk(ch, "users") <= 0)
               goto err;
       if(chanopen(ch, OREAD) < 0)
               goto err;
       if(usersload(fs, ch) < 0)
               goto err;
       chanclunk(ch);
       return;
err:
       if(ch != nil)
               chanclunk(ch);
       dprint("readusers: %r\nhjfs: using default user db\n");
}

void
ream(Fs *fs)
{
       Dev *d;
       Buf *b, *c;
       uvlong i, firsti, lasti;
       int j, je;

       d = fs->d;
       dprint("reaming %s\n", d->name);
       b = getbuf(d, SUPERBLK, TSUPERBLOCK, 1);
       if(b == nil)
       err:
               sysfatal("ream: getbuf: %r");
       memset(&b->sb, 0, sizeof(b->sb));
       b->sb.magic = SUPERMAGIC;
       b->sb.size = d->size;
       b->sb.fstart = SUPERBLK + 1;
       fs->fstart = b->sb.fstart;
       b->sb.fend = b->sb.fstart + HOWMANY(b->sb.size * REFSIZ);
       b->sb.qidpath = DUMPROOTQID + 1;
       firsti = b->sb.fstart + SUPERBLK / REFPERBLK;
       lasti = b->sb.fstart + b->sb.fend / REFPERBLK;
       for(i = b->sb.fstart; i < b->sb.fend; i++){
               c = getbuf(d, i, TREF, 1);
               if(c == nil)
                       goto err;
               memset(c->refs, 0, sizeof(c->refs));
               if(i >= firsti && i <= lasti){
                       j = 0;
                       je = REFPERBLK;
                       if(i == firsti)
                               j = SUPERBLK % REFPERBLK;
                       if(i == lasti)
                               je = b->sb.fend % REFPERBLK;
                       for(; j < je; j++)
                               c->refs[j] = 1;
               }
               if(i == b->sb.fend - 1){
                       j = b->sb.size % REFPERBLK;
                       if(j != 0)
                               for(; j < REFPERBLK; j++)
                                       c->refs[j] = -1;
               }
               c->op |= BWRIM;
               putbuf(c);
       }
       b->op |= BDELWRI;
       putbuf(b);
       createroot(fs);
       sync(1);
       dprint("ream successful\n");
}

Fs *
initfs(Dev *d, int doream, int flags)
{
       Fs *fs;
       Buf *b;
       FLoc f;

       fs = emalloc(sizeof(*fs));
       fs->d = d;
       if(doream)
               ream(fs);
       b = getbuf(d, SUPERBLK, TSUPERBLOCK, 0);
       if(b == nil || b->sb.magic != SUPERMAGIC)
               goto error;
       fs->root = b->sb.root;
       fs->fstart = b->sb.fstart;
       fs->freelist = chancreate(sizeof(uvlong), FREELISTLEN);
       f.blk = fs->root;
       f.deind = 0;
       f.path = ROOTQID;
       f.vers = 0;
       f.type = QTDIR;
       fs->rootloc = getloc(fs, f, nil);
       f.deind++;
       f.path = DUMPROOTQID;
       fs->dumprootloc = getloc(fs, f, nil);
       putbuf(b);
       fs->flags = flags;
       if(doream)
               writeusers(fs);
       readusers(fs);
       dprint("fs is %s\n", d->name);
       return fs;

error:
       if(b != nil)
               putbuf(b);
       free(fs);
       return nil;
}

void
chbegin(Chan *ch)
{
       if((ch->flags & CHFNOLOCK) == 0)
               rlock(ch->fs);
}

void
chend(Chan *ch)
{
       if((ch->flags & CHFNOLOCK) == 0)
               runlock(ch->fs);
}

int
newqid(Fs *fs, uvlong *q)
{
       Buf *b;

       b = getbuf(fs->d, SUPERBLK, TSUPERBLOCK, 0);
       if(b == nil)
               return -1;
       *q = b->sb.qidpath++;
       b->op |= BDELWRI;
       putbuf(b);
       return 1;
}

Loc *
getloc(Fs *fs, FLoc f, Loc *next)
{
       Loc *l;

       qlock(&fs->loctree);
       if(next != nil && next->child != nil){
               l = next->child;
               do{
                       if(l->blk == f.blk && l->deind == f.deind){
                               l->ref++;
                               qunlock(&fs->loctree);
                               return l;
                       }
                       l = l->cnext;
               }while(l != next->child);
       }
       l = emalloc(sizeof(*l));
       l->ref = 1;
       l->FLoc = f;
       l->next = next;
       if(fs->rootloc != nil){
               l->gnext = fs->rootloc;
               l->gprev = l->gnext->gprev;
               l->gnext->gprev = l;
               l->gprev->gnext = l;
       }else
               l->gnext = l->gprev = l;
       l->cprev = l->cnext = l;
       if(next != nil){
               if(next->child == nil)
                       next->child = l;
               else{
                       l->cnext = next->child;
                       l->cprev = next->child->cprev;
                       l->cnext->cprev = l;
                       l->cprev->cnext = l;
               }
       }
       qunlock(&fs->loctree);
       return l;
}

int
haveloc(Fs *fs, uvlong blk, int deind, Loc *next)
{
       Loc *l;

       qlock(&fs->loctree);
       l = next->child;
       if(l != nil) do{
               if(l->blk == blk && l->deind == deind){
                       qunlock(&fs->loctree);
                       return 1;
               }
               l = l->cnext;
       } while(l != next->child);
       qunlock(&fs->loctree);
       return 0;
}

Loc *
cloneloc(Fs *fs, Loc *l)
{
       Loc *m;

       qlock(&fs->loctree);
       for(m = l; m != nil; m = m->next)
               m->ref++;
       qunlock(&fs->loctree);
       return l;
}

void
putloc(Fs *fs, Loc *l, int loop)
{
       Loc *m;
       Buf *b;

       qlock(&fs->loctree);
       if(!loop && --l->ref <= 0)
               goto freeit;
       while(loop && l != nil && l->ref <= 1){
freeit:
               if((l->flags & LGONE) != 0){
                       /*
                        * safe to unlock here, the file is gone and
                        * we're the last reference.
                        */
                       qunlock(&fs->loctree);
                       b = getbuf(fs->d, l->blk, TDENTRY, 0);
                       if(b != nil){
                               delete(fs, l, b);
                               putbuf(b);
                       }
                       qlock(&fs->loctree);
               }
               l->cnext->cprev = l->cprev;
               l->cprev->cnext = l->cnext;
               l->gnext->gprev = l->gprev;
               l->gprev->gnext = l->gnext;
               if(l->next != nil && l->next->child == l){
                       if(l->cnext == l)
                               l->next->child = nil;
                       else
                               l->next->child = l->cnext;
               }
               m = l->next;
               free(l);
               l = m;
       }
       for(; loop && l != nil; l = l->next)
               --l->ref;
       qunlock(&fs->loctree);
}

static int
dumpblk(Fs *fs, FLoc *, uvlong *l)
{
       uvlong n;
       int i;
       Buf *b, *c;
       Dentry *d;

       b = getbuf(fs->d, *l, TDONTCARE, 0);
       if(b == nil)
               return -1;
       if(getfree(fs, &n) < 0){
       berr:
               putbuf(b);
               return -1;
       }
       c = getbuf(fs->d, n, b->type, 1);
       if(c == nil){
               putfree(fs, n);
               goto berr;
       }
       switch(b->type){
       case TINDIR:
               memcpy(c->offs, b->offs, sizeof(b->offs));
               for(i = 0; i < OFFPERBLK; i++)
                       if(b->offs[i] != 0)
                               chref(fs, b->offs[i], 1);
               break;
       case TRAW:
               memcpy(c->data, b->data, sizeof(b->data));
               break;
       case TDENTRY:
               memcpy(c->de, b->de, sizeof(b->de));
               for(d = b->de; d < &b->de[DEPERBLK]; d++){
                       if((d->mode & DALLOC) == 0)
                               continue;
                       if((d->type & QTTMP) != 0)
                               continue;
                       for(i = 0; i < NDIRECT; i++)
                               if(d->db[i] != 0)
                                       chref(fs, d->db[i], 1);
                       for(i = 0; i < NINDIRECT; i++)
                               if(d->ib[i] != 0)
                                       chref(fs, d->ib[i], 1);
               }
               break;
       default:
               werrstr("dumpblk -- unknown type %d", b->type);
               putfree(fs, n);
               putbuf(c);
               putbuf(b);
               return -1;
       }
       c->op |= BDELWRI;
       putbuf(c);
       putbuf(b);
       putfree(fs, *l);
       *l = n;
       return 0;
}

/*
* getblk returns the address of a block in a file
* given its relative address blk
* the address are returned in *r
* mode has to be one of:
* - GBREAD: this block will only be read
* - GBWRITE: this block will be written, but don't create it
*            if it doesn't exist
* - GBCREATE: this block will be written, create it if necessary
* - GBOVERWR: like GBCREATE, but return an empty block if a dump
*             would be necessary
* return value is 1 if the block existed, -1 on error
* a return value of 0 means the block did not exist
* this is only an error in case of GBREAD and GBWRITE
*/

int
getblk(Fs *fs, FLoc *L, Buf *bd, uvlong blk, uvlong *r, int mode)
{
       uvlong k, l;
       uvlong *loc;
       int i, j, rc, prc;
       Buf *b;
       Dentry *d;

       b = bd;
       d = getdent(L, b);
       if(d == nil){
               dprint("getblk: dirent gone\n");
               return -1;
       }
       if(blk < NDIRECT){
               loc = &d->db[blk];
               goto found;
       }
       blk -= NDIRECT;
       l = 1;
       for(i = 0; i < NINDIRECT; i++){
               l *= OFFPERBLK;
               if(blk < l)
                       break;
               blk -= l;
       }
       if(i == NINDIRECT){
               werrstr("getblk: block offset too large");
               return -1;
       }
       loc = &d->ib[i];
       for(j = 0; j <= i; j++){
               if(*loc == 0){
                       if(mode == GBREAD || mode == GBWRITE){
                               if(b != bd)
                                       putbuf(b);
                               return 0;
                       }
                       if(getfree(fs, loc) < 0){
                               if(b != bd)
                                       putbuf(b);
                               return -1;
                       }
                       b->op |= BDELWRI;
                       k = *loc;
                       if(b != bd)
                               putbuf(b);
                       b = getbuf(fs->d, k, TINDIR, 1);
                       if(b == nil)
                               return -1;
                       memset(b->offs, 0, sizeof(b->offs));
                       b->op |= BDELWRI;
               }else{
                       if(mode != GBREAD && chref(fs, *loc, 0) > 1){
                               if(dumpblk(fs, L, loc) < 0){
                                       if(b != bd)
                                               putbuf(b);
                                       return -1;
                               }
                               b->op |= BDELWRI;
                       }
                       k = *loc;
                       if(b != bd)
                               putbuf(b);
                       b = getbuf(fs->d, k, TINDIR, 0);
                       if(b == nil)
                               return -1;
               }
               l /= OFFPERBLK;
               loc = &b->offs[blk / l];
               blk %= l;
       }

found:
       rc = 0;
       prc = 0;
       if(*loc != 0){
               if(mode == GBREAD)
                       goto okay;
               if((rc = chref(fs, *loc, 0)) > 1){
                       if(mode == GBOVERWR){
                               putfree(fs, *loc);
                               *loc = 0;
                               b->op |= BDELWRI;
                               prc = 1;
                               goto new;
                       }
                       if(dumpblk(fs, L, loc) < 0){
                               rc = -1;
                               goto end;
                       }
                       b->op |= BDELWRI;
               }
               if(rc < 0)
                       goto end;
               if(rc == 0){
                       dprint("getblk: block %lld has refcount 0\n");
                       werrstr("phase error -- getblk");
                       rc = -1;
                       goto end;
               }
okay:
               *r = *loc;
               rc = 1;
       }else if(mode == GBCREATE || mode == GBOVERWR){
new:
               rc = getfree(fs, r);
               if(rc > 0){
                       *loc = *r;
                       b->op |= BDELWRI;
                       rc = prc;
               }
       }
end:
       if(b != bd)
               putbuf(b);
       return rc;
}

static void
delindir(Fs *fs, uvlong *l, int n)
{
       Buf *b;
       int k;

       if(*l == 0)
               return;
       if(chref(fs, *l, 0) > 1){
               *l = 0;
               return;
       }
       if(n > 0){
               b = getbuf(fs->d, *l, TINDIR, 0);
               if(b != nil){
                       for(k = 0; k < OFFPERBLK; k++)
                               if(b->offs[k] != 0){
                                       delindir(fs, &b->offs[k], n-1);
                                       b->op |= BDELWRI;
                               }
                       putbuf(b);
               }
       }
       putfree(fs, *l);
       *l = 0;
}

static int
zeroes(int *a, int i)
{
       while(i--)
               if(*a++ != 0)
                       return 0;
       return 1;
}

static void
delindirpart(Fs *fs, FLoc *p, uvlong *l, int *a, int n)
{
       Buf *b;
       int k;

       if(*l == 0)
               return;
       if(n == 0){
               putfree(fs, *l);
               *l = 0;
               return;
       }
       if(zeroes(a, n)){
               delindir(fs, l, n);
               return;
       }
       if(chref(fs, *l, 0) > 1)
               dumpblk(fs, p, l);
       b = getbuf(fs->d, *l, TINDIR, 0);
       if(b == nil)
               return;
       delindirpart(fs, p, &b->offs[*a], a + 1, n - 1);
       for(k = a[0] + 1; k < OFFPERBLK; k++)
               if(b->offs[k] != 0)
                       delindir(fs, &b->offs[k], n - 1);
       b->op |= BDELWRI;
       putbuf(b);
}

/*
* call willmodify() before and modified()
* after calling this function
*/
int
trunc(Fs *fs, FLoc *ll, Buf *bd, uvlong size)
{
       uvlong blk;
       Dentry *d;
       int a[NINDIRECT];
       uvlong l;
       int i, j;

       d = getdent(ll, bd);
       if(d == nil)
               return -1;
       if(size >= d->size)
               goto done;
       blk = HOWMANY(size);
       while(blk < NDIRECT){
               if(d->db[blk] != 0){
                       putfree(fs, d->db[blk]);
                       d->db[blk] = 0;
                       bd->op |= BDELWRI;
               }
               blk++;
       }
       blk -= NDIRECT;
       l = 1;
       for(i = 0; i < NINDIRECT; i++){
               l *= OFFPERBLK;
               if(blk < l)
                       break;
               blk -= l;
       }
       if(blk >= l){
               werrstr("phase error -- truncate");
               return -1;
       }
       if(blk == 0)
               goto rest;
       if(d->ib[i] == 0){
               i++;
               goto rest;
       }
       for(j = 0; j <= i; j++){
               l /= OFFPERBLK;
               a[j] = blk / l;
               blk %= l;
       }
       delindirpart(fs, ll, &d->ib[i], a, i + 1);
       i++;
rest:
       for(; i < NINDIRECT; i++)
               delindir(fs, &d->ib[i], i + 1);
done:
       d->size = size;
       bd->op |= BDELWRI;
       return 1;
}

/*
* find a direntry
* name == nil allows any entry to match
* rl == nil allowed
* return value 1 on success, 0 on Enoent,
* -1 on other errors
*/
int
findentry(Fs *fs, FLoc *l, Buf *b, char *name, FLoc *rl, int dump)
{
       uvlong i;
       int j;
       Dentry *d;
       uvlong r;
       Buf *c;

       d = getdent(l, b);
       if(d == nil)
               return -1;
       for(i = 0; i < d->size; i++){
               if(getblk(fs, l, b, i, &r, GBREAD) <= 0)
                       continue;
               c = getbuf(fs->d, r, TDENTRY, 0);
               if(c == nil)
                       continue;
               for(j = 0; j < DEPERBLK; j++)
                       if((c->de[j].mode & DALLOC) != 0 &&
                          (name == nil || strcmp(c->de[j].name, name) == 0)){
                               if(dump && (c->de[j].type & QTTMP) != 0)
                                       continue;
                               if(rl != nil){
                                       rl->Qid = c->de[j].Qid;
                                       rl->blk = r;
                                       rl->deind = j;
                               }
                               putbuf(c);
                               return 1;
                       }
               putbuf(c);
       }
       werrstr(Enoent);
       return 0;
}

void
modified(Chan *ch, Dentry *d)
{
       d->mtime = time(0);
       d->atime = d->mtime;
       d->muid = ch->uid;
       ch->loc->vers = ++d->vers;
}

typedef struct Del Del;

struct Del {
       FLoc;
       Del *next, *prev;
};

static int
deltraverse(Fs *fs, Del *p, Buf *b, Del **last)
{
       Buf *c;
       int frb;
       Dentry *d;
       uvlong i, s, r;
       int j, rc;
       Del *dd;

       frb = b == nil;
       if(frb){
               b = getbuf(fs->d, p->blk, TDENTRY, 0);
               if(b == nil)
                       return -1;
       }
       d = getdent(p, b);
       if(d == nil){
               if(frb)
                       putbuf(b);
               return -1;
       }
       s = d->size;
       for(i = 0; i < s; i++){
               rc = getblk(fs, p, b, i, &r, GBREAD);
               if(rc <= 0)
                       continue;
               c = getbuf(fs->d, r, TDENTRY, 0);
               if(c == nil)
                       continue;
               for(j = 0; j < DEPERBLK; j++){
                       d = &c->de[j];
                       if((d->mode & (DALLOC|DGONE)) != 0){
                               if((d->type & QTDIR) == 0){
                                       trunc(fs, p, b, 0);
                                       memset(d, 0, sizeof(*d));
                                       c->op |= BDELWRI;
                               }else{
                                       dd = emalloc(sizeof(Del));
                                       dd->blk = i;
                                       dd->deind = j;
                                       dd->Qid = d->Qid;
                                       dd->prev = *last;
                                       (*last)->next = dd;
                                       *last = dd;
                               }
                       }
               }
               putbuf(c);
       }
       if(frb)
               putbuf(b);
       return 0;
}

int
delete(Fs *fs, FLoc *l, Buf *b)
{
       Dentry *d;
       Buf *c;
       Del *first, *last, *p, *q;

       d = getdent(l, b);
       if(d == nil)
               return -1;
       if((d->type & QTDIR) == 0){
               trunc(fs, l, b, 0);
               memset(d, 0, sizeof(*d));
               b->op |= BDELWRI;
               return 0;
       }
       first = last = emalloc(sizeof(Del));
       first->FLoc = *l;
       for(p = first; p != nil; p = p->next)
               deltraverse(fs, p, p == first ? b : nil, &last);
       for(p = last; p != nil; q = p->prev, free(p), p = q){
               if(p == first)
                       c = b;
               else
                       c = getbuf(fs->d, p->blk, TDENTRY, 0);
               if(c == nil)
                       continue;
               d = getdent(p, c);
               if(d != nil){
                       trunc(fs, p, c, 0);
                       memset(d, 0, sizeof(*d));
                       c->op |= BDELWRI;
               }
               if(p != first)
                       putbuf(c);
       }
       return 0;
}

/*
* newentry() looks for a free slot in the directory
* and returns FLoc pointing to the slot. if no free
* slot is available a new block is allocated. if
* dump == 0, then the resulting blk from the FLoc
* *is not* dumped, so to finally allocate the Dentry,
* one has to call willmodify() on res before modyfing it.
*/
int
newentry(Fs *fs, Loc *l, Buf *b, char *name, FLoc *res, int dump)
{
       Dentry *d, *dd;
       uvlong i, si, r;
       int j, sj;
       Buf *c;

       d = getdent(l, b);
       if(d == nil)
               return -1;
       si = sj = -1;
       for(i = 0; i < d->size; i++){
               if(getblk(fs, l, b, i, &r, GBREAD) <= 0)
                       continue;
               c = getbuf(fs->d, r, TDENTRY, 0);
               if(c == nil)
                       continue;
               for(j = 0; j < DEPERBLK; j++){
                       dd = &c->de[j];
                       if((dd->mode & DGONE) != 0)
                               continue;
                       if((dd->mode & DALLOC) != 0){
                               if(strcmp(dd->name, name) == 0){
                                       werrstr(Eexists);
                                       putbuf(c);
                                       return 0;
                               }
                               continue;
                       }
                       if(si != -1 || haveloc(fs, r, j, l))
                               continue;
                       si = i;
                       sj = j;
               }
               putbuf(c);
       }
       if(si == -1 && i == d->size){
               if(getblk(fs, l, b, i, &r, GBCREATE) >= 0){
                       c = getbuf(fs->d, r, TDENTRY, 1);
                       if(c != nil){
                               si = i;
                               sj = 0;
                               d->size = i+1;
                               b->op |= BDELWRI;
                               memset(c->de, 0, sizeof(c->de));
                               c->op |= BDELWRI;
                               putbuf(c);
                       }
               }
       }
       if(si == -1 || sj == -1){
               werrstr("phase error -- create");
               return -1;
       }
       if(getblk(fs, l, b, si, &res->blk, dump != 0 ? GBWRITE : GBREAD) <= 0)
               return -1;
       res->deind = sj;
       res->Qid = (Qid){0, 0, 0};
       return 1;
}