#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", *loc);
			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(Loc *loc, Dentry *d, short uid)
{
	d->mtime = time(0);
	d->atime = d->mtime;
	d->muid = uid;
	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;
}
