#include "stdinc.h"
#include "vac.h"
#include "dat.h"
#include "fns.h"
#include "error.h"

/*
 * locking order is upwards.  A thread can hold the lock for a VacFile
 * and then acquire the lock of its parent
 */
struct VacFile
{
	VacFs	*fs;	/* immutable */

	/* meta data for file: protected by the lk in the parent */
	int		ref;	/* holds this data structure up */

	int		partial;	/* file was never really open */
	int		removed;	/* file has been removed */
	int		dirty;	/* dir is dirty with respect to meta data in block */
	u32int	boff;		/* block offset within msource for this file's metadata */
	VacDir	dir;		/* metadata for this file */
	VacFile	*up;		/* parent file */
	VacFile	*next;	/* sibling */

	RWLock	lk;		/* lock for the following */
	VtFile	*source;	/* actual data */
	VtFile	*msource;	/* metadata for children in a directory */
	VacFile	*down;	/* children */
	int		mode;
};

static int		filelock(VacFile*);
static u32int	filemetaalloc(VacFile*, VacDir*, u32int);
static int		filemetaflush2(VacFile*, char*);
static void		filemetalock(VacFile*);
static void		filemetaunlock(VacFile*);
static void		fileraccess(VacFile*);
static int		filerlock(VacFile*);
static void		filerunlock(VacFile*);
static void		fileunlock(VacFile*);
static void		filewaccess(VacFile*, char*);

void mbinit(MetaBlock*, u8int*, uint, uint);
int mbsearch(MetaBlock*, char*, int*, MetaEntry*);
int mbresize(MetaBlock*, MetaEntry*, int);
VacFile *vdlookup(VacFile*, char*);

static VacFile*
filealloc(VacFs *fs)
{
	VacFile *f;

	f = vtmallocz(sizeof(VacFile));
	f->ref = 1;
	f->fs = fs;
	f->boff = NilBlock;
	f->mode = fs->mode;
	return f;
}

static void
filefree(VacFile *f)
{
	vtfileclose(f->source);
	vtfileclose(f->msource);
	vdcleanup(&f->dir);
	memset(f, ~0, sizeof *f);	/* paranoia */
	vtfree(f);
}

/*
 * the file is locked already
 * f->msource is unlocked
 */
static VacFile*
dirlookup(VacFile *f, char *elem)
{
	int i;
	MetaBlock mb;
	MetaEntry me;
	VtBlock *b;
	VtFile *meta;
	VacFile *ff;
	u32int bo, nb;

	meta = f->msource;
	b = nil;
	if(vtfilelock(meta, -1) < 0)
		return nil;
	nb = (vtfilegetsize(meta)+meta->dsize-1)/meta->dsize;
	for(bo=0; bo<nb; bo++){
		b = vtfileblock(meta, bo, VtOREAD);
		if(b == nil)
			goto Err;
		if(mbunpack(&mb, b->data, meta->dsize) < 0)
			goto Err;
		if(mbsearch(&mb, elem, &i, &me) < 0){
			ff = filealloc(f->fs);
			if(vdunpack(&ff->dir, &me) < 0){
				filefree(ff);
				goto Err;
			}
			vtfileunlock(meta);
			vtblockput(b);
			ff->boff = bo;
			ff->mode = f->mode;
			return ff;
		}

		vtblockput(b);
		b = nil;
	}
	werrstr(ENoFile);
	/* fall through */
Err:
	vtfileunlock(meta);
	vtblockput(b);
	return nil;
}

VacFile*
_vacfileroot(VacFs *fs, VtFile *r)
{
	VtBlock *b;
	VtFile *r0, *r1, *r2;
	MetaBlock mb;
	MetaEntry me;
	VacFile *root, *mr;

	b = nil;
	root = nil;
	mr = nil;
	r1 = nil;
	r2 = nil;

	if(vtfilelock(r, -1) < 0)
		return nil;
	r0 = vtfileopen(r, 0, fs->mode);
	if(r0 == nil)
		goto Err;
	r1 = vtfileopen(r, 1, fs->mode);
	if(r1 == nil)
		goto Err;
	r2 = vtfileopen(r, 2, fs->mode);
	if(r2 == nil)
		goto Err;

	mr = filealloc(fs);
	mr->msource = r2;
	r2 = nil;

	root = filealloc(fs);
	root->boff = 0;
	root->up = mr;
	root->source = r0;
	r0 = nil;
	root->msource = r1;
	r1 = nil;

	mr->down = root;

	if(vtfilelock(mr->msource, -1) < 0)
		goto Err;
	b = vtfileblock(mr->msource, 0, VtOREAD);
	vtfileunlock(mr->msource);
	if(b == nil)
		goto Err;

	if(mbunpack(&mb, b->data, mr->msource->dsize) < 0)
		goto Err;

	meunpack(&me, &mb, 0);
	if(vdunpack(&root->dir, &me) < 0)
		goto Err;
	vtblockput(b);
	vtfileunlock(r);
	fileraccess(root);

	return root;
Err:
	vtblockput(b);
	if(r0)
		vtfileclose(r0);
	if(r1)
		vtfileclose(r1);
	if(r2)
		vtfileclose(r2);
	if(mr)
		filefree(mr);
	if(root)
		filefree(root);
	vtfileunlock(r);

	return nil;
}

static VtFile *
fileopensource(VacFile *f, u32int offset, u32int gen, int dir, uint mode)
{
	VtFile *r;

	if(vtfilelock(f->source, mode) < 0)
		return nil;
	r = vtfileopen(f->source, offset, mode);
	vtfileunlock(f->source);
	if(r == nil)
		return nil;
	if(r->gen != gen){
		werrstr(ERemoved);
		goto Err;
	}
	if(r->dir != dir && r->mode != -1){
fprint(2, "fileopensource: dir mismatch %d %d\n", r->dir, dir);
		werrstr(EBadMeta);
		goto Err;
	}
	return r;
Err:
	vtfileclose(r);
	return nil;
}

VacFile*
_filewalk(VacFile *f, char *elem, int partial)
{
	VacFile *ff;

	fileraccess(f);

	if(elem[0] == 0){
		werrstr(EBadPath);
		return nil;
	}

	if(!vacfileisdir(f)){
		werrstr(ENotDir);
		return nil;
	}

	if(strcmp(elem, ".") == 0){
		return vacfileincref(f);
	}

	if(strcmp(elem, "..") == 0){
		if(vacfileisroot(f))
			return vacfileincref(f);
		return vacfileincref(f->up);
	}

	if(filelock(f) < 0)
		return nil;

	for(ff = f->down; ff; ff=ff->next){
		if(strcmp(elem, ff->dir.elem) == 0 && !ff->removed){
			ff->ref++;
			goto Exit;
		}
	}

	ff = dirlookup(f, elem);
	if(ff == nil)
		goto Err;

	if(ff->dir.mode & ModeSnapshot)
		ff->mode = VtOREAD;

	if(partial){
		/*
		 * Do nothing.  We're opening this file only so we can clri it.
		 * Usually the sources can't be opened, hence we won't even bother.
		 * Be VERY careful with the returned file.  If you hand it to a routine
		 * expecting ff->source and/or ff->msource to be non-nil, we're
		 * likely to dereference nil.  FileClri should be the only routine
		 * setting partial.
		 */
		ff->partial = 1;
	}else if(ff->dir.mode & ModeDir){
		ff->source = fileopensource(f, ff->dir.entry, ff->dir.gen, 1, ff->mode);
		ff->msource = fileopensource(f, ff->dir.mentry, ff->dir.mgen, 0, ff->mode);
		if(ff->source == nil || ff->msource == nil)
			goto Err;
	}else{
		ff->source = fileopensource(f, ff->dir.entry, ff->dir.gen, 0, ff->mode);
		if(ff->source == nil)
			goto Err;
	}

	/* link in and up parent ref count */
	ff->next = f->down;
	f->down = ff;
	ff->up = f;
	vacfileincref(f);
Exit:
	fileunlock(f);
	return ff;
Err:
	fileunlock(f);
	if(ff != nil)
		vacfiledecref(ff);
	return nil;
}

VacFile*
vacfilewalk(VacFile *f, char *elem)
{
	return _filewalk(f, elem, 0);
}

VacFile*
_fileopen(VacFs *fs, char *path, int partial)
{
	VacFile *f, *ff;
	char *p, elem[VtMaxStringSize], *opath;
	int n;

	f = fs->root;
	vacfileincref(f);
	opath = path;
	while(*path != 0){
		for(p = path; *p && *p != '/'; p++)
			;
		n = p - path;
		if(n > 0){
			if(n > VtMaxStringSize){
				werrstr("%s: element too long", EBadPath);
				goto Err;
			}
			memmove(elem, path, n);
			elem[n] = 0;
			ff = _filewalk(f, elem, partial && *p=='\0');
			if(ff == nil){
				werrstr("%.*s: %R", utfnlen(opath, p-opath), opath);
				goto Err;
			}
			vacfiledecref(f);
			f = ff;
		}
		if(*p == '/')
			p++;
		path = p;
	}
	return f;
Err:
	vacfiledecref(f);
	return nil;
}

VacFile*
vacfileopen(VacFs *fs, char *path)
{
	return _fileopen(fs, path, 0);
}

#if 0
static void
filesettmp(VacFile *f, int istmp)
{
	int i;
	VtEntry e;
	VtFile *r;

	for(i=0; i<2; i++){
		if(i==0)
			r = f->source;
		else
			r = f->msource;
		if(r == nil)
			continue;
		if(vtfilegetentry(r, &e) < 0){
			fprint(2, "vtfilegetentry failed (cannot happen): %r\n");
			continue;
		}
		if(istmp)
			e.flags |= VtEntryNoArchive;
		else
			e.flags &= ~VtEntryNoArchive;
		if(vtfilesetentry(r, &e) < 0){
			fprint(2, "vtfilesetentry failed (cannot happen): %r\n");
			continue;
		}
	}
}
#endif

VacFile*
vacfilecreate(VacFile *f, char *elem, ulong mode, char *uid)
{
	VacFile *ff;
	VacDir *dir;
	VtFile *pr, *r, *mr;
	int isdir;

	if(filelock(f) < 0)
		return nil;

	r = nil;
	mr = nil;
	for(ff = f->down; ff; ff=ff->next){
		if(strcmp(elem, ff->dir.elem) == 0 && !ff->removed){
			ff = nil;
			werrstr(EExists);
			goto Err1;
		}
	}

	ff = dirlookup(f, elem);
	if(ff != nil){
		werrstr(EExists);
		goto Err1;
	}

	pr = f->source;
	if(pr->mode != VtORDWR){
		werrstr(EReadOnly);
		goto Err1;
	}

	if(vtfilelock2(f->source, f->msource, -1) < 0)
		goto Err1;

	ff = filealloc(f->fs);
	isdir = mode & ModeDir;

	r = vtfilecreate(pr, pr->dsize, isdir, 0);
	if(r == nil)
		goto Err;
	if(isdir){
		mr = vtfilecreate(pr, pr->dsize, 0, r->offset);
		if(mr == nil)
			goto Err;
	}

	dir = &ff->dir;
	dir->elem = vtstrdup(elem);
	dir->entry = r->offset;
	dir->gen = r->gen;
	if(isdir){
		dir->mentry = mr->offset;
		dir->mgen = mr->gen;
	}
	dir->size = 0;
	if(_vacfsnextqid(f->fs, &dir->qid) < 0)
		goto Err;
	dir->uid = vtstrdup(uid);
	dir->gid = vtstrdup(f->dir.gid);
	dir->mid = vtstrdup(uid);
	dir->mtime = time(0L);
	dir->mcount = 0;
	dir->ctime = dir->mtime;
	dir->atime = dir->mtime;
	dir->mode = mode;

	ff->boff = filemetaalloc(f, dir, 0);
	if(ff->boff == NilBlock)
		goto Err;

	vtfileunlock(f->source);
	vtfileunlock(f->msource);

	ff->source = r;
	ff->msource = mr;

#if 0
	if(mode&ModeTemporary){
		if(vtfilelock2(r, mr, -1) < 0)
			goto Err1;
		filesettmp(ff, 1);
		vtfileunlock(r);
		if(mr)
			vtfileunlock(mr);
	}
#endif

	/* committed */

	/* link in and up parent ref count */
	ff->next = f->down;
	f->down = ff;
	ff->up = f;
	vacfileincref(f);

	filewaccess(f, uid);

	fileunlock(f);
	return ff;

Err:
	vtfileunlock(f->source);
	vtfileunlock(f->msource);
Err1:
	if(r){
		vtfilelock(r, -1);
		vtfileremove(r);
	}
	if(mr){
		vtfilelock(mr, -1);
		vtfileremove(mr);
	}
	if(ff)
		vacfiledecref(ff);
	fileunlock(f);
	return nil;
}

int
vacfileblockscore(VacFile *f, u32int bn, u8int *score)
{
	VtFile *s;
	uvlong size;
	int dsize, ret;

	ret = -1;
	if(filerlock(f) < 0)
		return -1;
	fileraccess(f);
	if(vtfilelock(f->source, VtOREAD) < 0)
		goto out;

	s = f->source;
	dsize = s->dsize;
	size = vtfilegetsize(s);
	if((uvlong)bn*dsize >= size)
		goto out;
	ret = vtfileblockscore(f->source, bn, score);

out:
	vtfileunlock(f->source);
	filerunlock(f);
	return ret;
}

int
vacfileread(VacFile *f, void *buf, int cnt, vlong offset)
{
	VtFile *s;
	uvlong size;
	u32int bn;
	int off, dsize, n, nn;
	VtBlock *b;
	uchar *p;

if(0)fprint(2, "fileRead: %s %d, %lld\n", f->dir.elem, cnt, offset);

	if(filerlock(f) < 0)
		return -1;

	if(offset < 0){
		werrstr(EBadOffset);
		goto Err1;
	}

	fileraccess(f);

	if(vtfilelock(f->source, VtOREAD) < 0)
		goto Err1;

	s = f->source;
	dsize = s->dsize;
	size = vtfilegetsize(s);

	if(offset >= size)
		offset = size;

	if(cnt > size-offset)
		cnt = size-offset;
	bn = offset/dsize;
	off = offset%dsize;
	p = buf;
	while(cnt > 0){
		b = vtfileblock(s, bn, OREAD);
		if(b == nil)
			goto Err;
		n = cnt;
		if(n > dsize-off)
			n = dsize-off;
		nn = dsize-off;
		if(nn > n)
			nn = n;
		memmove(p, b->data+off, nn);
		memset(p+nn, 0, nn-n);
		off = 0;
		bn++;
		cnt -= n;
		p += n;
		vtblockput(b);
	}
	vtfileunlock(s);
	filerunlock(f);
	return p-(uchar*)buf;

Err:
	vtfileunlock(s);
Err1:
	filerunlock(f);
	return -1;
}

#if 0
/* 
 * Changes the file block bn to be the given block score.
 * Very sneaky.  Only used by flfmt.
 */
int
filemapblock(VacFile *f, ulong bn, uchar score[VtScoreSize], ulong tag)
{
	VtBlock *b;
	VtEntry e;
	VtFile *s;

	if(filelock(f) < 0)
		return -1;

	s = nil;
	if(f->dir.mode & ModeDir){
		werrstr(ENotFile);
		goto Err;
	}

	if(f->source->mode != VtORDWR){
		werrstr(EReadOnly);
		goto Err;
	}

	if(vtfilelock(f->source, -1) < 0)
		goto Err;

	s = f->source;
	b = _vtfileblock(s, bn, VtORDWR, 1, tag);
	if(b == nil)
		goto Err;

	if(vtfilegetentry(s, &e) < 0)
		goto Err;
	if(b->l.type == BtDir){
		memmove(e.score, score, VtScoreSize);
		assert(e.tag == tag || e.tag == 0);
		e.tag = tag;
		e.flags |= VtEntryLocal;
		vtentrypack(&e, b->data, f->source->offset % f->source->epb);
	}else
		memmove(b->data + (bn%(e.psize/VtScoreSize))*VtScoreSize, score, VtScoreSize);
	vtblockdirty(b);
	vtblockput(b);
	vtfileunlock(s);
	fileunlock(f);
	return 0;

Err:
	if(s)
		vtfileunlock(s);
	fileunlock(f);
	return -1;
}
#endif

int
vacfilesetsize(VacFile *f, uvlong size)
{
	int r;

	if(filelock(f) < 0)
		return -1;
	r = 0;
	if(f->dir.mode & ModeDir){
		werrstr(ENotFile);
		goto Err;
	}
	if(f->source->mode != VtORDWR){
		werrstr(EReadOnly);
		goto Err;
	}
	if(vtfilelock(f->source, -1) < 0)
		goto Err;
	r = vtfilesetsize(f->source, size);
	vtfileunlock(f->source);
Err:
	fileunlock(f);
	return r;
}

int
filewrite(VacFile *f, void *buf, int cnt, vlong offset, char *uid)
{
	VtFile *s;
	ulong bn;
	int off, dsize, n;
	VtBlock *b;
	uchar *p;
	vlong eof;

if(0)fprint(2, "fileWrite: %s %d, %lld\n", f->dir.elem, cnt, offset);

	if(filelock(f) < 0)
		return -1;

	s = nil;
	if(f->dir.mode & ModeDir){
		werrstr(ENotFile);
		goto Err;
	}

	if(f->source->mode != VtORDWR){
		werrstr(EReadOnly);
		goto Err;
	}
	if(offset < 0){
		werrstr(EBadOffset);
		goto Err;
	}

	filewaccess(f, uid);

	if(vtfilelock(f->source, -1) < 0)
		goto Err;
	s = f->source;
	dsize = s->dsize;

	eof = vtfilegetsize(s);
	if(f->dir.mode & ModeAppend)
		offset = eof;
	bn = offset/dsize;
	off = offset%dsize;
	p = buf;
	while(cnt > 0){
		n = cnt;
		if(n > dsize-off)
			n = dsize-off;
		b = vtfileblock(s, bn, n<dsize?VtORDWR:VtOWRITE);
		if(b == nil){
			if(offset > eof)
				vtfilesetsize(s, offset);
			goto Err;
		}
		memmove(b->data+off, p, n);
		off = 0;
		cnt -= n;
		p += n;
		offset += n;
		bn++;
		vtblockdirty(b);
		vtblockput(b);
	}
	if(offset > eof && vtfilesetsize(s, offset) < 0)
		goto Err;
	vtfileunlock(s);
	fileunlock(f);
	return p-(uchar*)buf;
Err:
	if(s)
		vtfileunlock(s);
	fileunlock(f);
	return -1;
}

int
vacfilegetdir(VacFile *f, VacDir *dir)
{
	if(filerlock(f) < 0)
		return -1;

	filemetalock(f);
	vdcopy(dir, &f->dir);
	filemetaunlock(f);

	if(vacfileisdir(f) < 0){
		if(vtfilelock(f->source, VtOREAD) < 0){
			filerunlock(f);
			return -1;
		}
		dir->size = vtfilegetsize(f->source);
		vtfileunlock(f->source);
	}
	filerunlock(f);

	return 0;
}

int
vacfiletruncate(VacFile *f, char *uid)
{
	if(vacfileisdir(f)){
		werrstr(ENotFile);
		return -1;
	}

	if(filelock(f) < 0)
		return -1;

	if(f->source->mode != VtORDWR){
		werrstr(EReadOnly);
		fileunlock(f);
		return -1;
	}
	if(vtfilelock(f->source, -1) < 0){
		fileunlock(f);
		return -1;
	}
	if(vtfiletruncate(f->source) < 0){
		vtfileunlock(f->source);
		fileunlock(f);
		return -1;
	}
	vtfileunlock(f->source);
	fileunlock(f);

	filewaccess(f, uid);

	return 0;
}

int
vacfilesetdir(VacFile *f, VacDir *dir, char *uid)
{
	VacFile *ff;
	char *oelem;
	u32int mask;
	u64int size;

	/* can not set permissions for the root */
	if(vacfileisroot(f)){
		werrstr(ERoot);
		return -1;
	}

	if(filelock(f) < 0)
		return -1;

	if(f->source->mode != VtORDWR){
		werrstr(EReadOnly);
		fileunlock(f);
		return -1;
	}

	filemetalock(f);

	/* check new name does not already exist */
	if(strcmp(f->dir.elem, dir->elem) != 0){
		for(ff = f->up->down; ff; ff=ff->next){
			if(strcmp(dir->elem, ff->dir.elem) == 0 && !ff->removed){
				werrstr(EExists);
				goto Err;
			}
		}

		ff = dirlookup(f->up, dir->elem);
		if(ff != nil){
			vacfiledecref(ff);
			werrstr(EExists);
			goto Err;
		}
	}

	if(vtfilelock2(f->source, f->msource, -1) < 0)
		goto Err;
	if(!vacfileisdir(f)){
		size = vtfilegetsize(f->source);
		if(size != dir->size){
			if(vtfilesetsize(f->source, dir->size) < 0){
				vtfileunlock(f->source);
				if(f->msource)
					vtfileunlock(f->msource);
				goto Err;
			}
			/* commited to changing it now */
		}
	}
	/* commited to changing it now */
#if 0
	if((f->dir.mode&ModeTemporary) != (dir->mode&ModeTemporary))
		filesettmp(f, dir->mode&ModeTemporary);
#endif
	vtfileunlock(f->source);
	if(f->msource)
		vtfileunlock(f->msource);

	oelem = nil;
	if(strcmp(f->dir.elem, dir->elem) != 0){
		oelem = f->dir.elem;
		f->dir.elem = vtstrdup(dir->elem);
	}

	if(strcmp(f->dir.uid, dir->uid) != 0){
		vtfree(f->dir.uid);
		f->dir.uid = vtstrdup(dir->uid);
	}

	if(strcmp(f->dir.gid, dir->gid) != 0){
		vtfree(f->dir.gid);
		f->dir.gid = vtstrdup(dir->gid);
	}

	f->dir.mtime = dir->mtime;
	f->dir.atime = dir->atime;

//fprint(2, "mode %x %x ", f->dir.mode, dir->mode);
	mask = ~(ModeDir|ModeSnapshot);
	f->dir.mode &= ~mask;
	f->dir.mode |= mask & dir->mode;
	f->dirty = 1;
//fprint(2, "->%x\n", f->dir.mode);

	filemetaflush2(f, oelem);
	vtfree(oelem);

	filemetaunlock(f);
	fileunlock(f);

	filewaccess(f->up, uid);

	return 0;
Err:
	filemetaunlock(f);
	fileunlock(f);
	return -1;
}

int
vacfilesetqidspace(VacFile *f, u64int offset, u64int max)
{
	int ret;

	if(filelock(f) < 0)
		return -1;
	filemetalock(f);
	f->dir.qidspace = 1;
	f->dir.qidoffset = offset;
	f->dir.qidmax = max;
	ret = filemetaflush2(f, nil);
	filemetaunlock(f);
	fileunlock(f);
	return ret;
}

uvlong
vacfilegetid(VacFile *f)
{
	/* immutable */
	return f->dir.qid;
}

ulong
vacfilegetmcount(VacFile *f)
{
	ulong mcount;

	filemetalock(f);
	mcount = f->dir.mcount;
	filemetaunlock(f);
	return mcount;
}

ulong
vacfilegetmode(VacFile *f)
{
	ulong mode;

	filemetalock(f);
	mode = f->dir.mode;
	filemetaunlock(f);
	return mode;
}

int
vacfileisdir(VacFile *f)
{
	/* immutable */
	return (f->dir.mode & ModeDir) != 0;
}

int
vacfileisroot(VacFile *f)
{
	return f == f->fs->root;
}

int
vacfilegetsize(VacFile *f, uvlong *size)
{
	if(filerlock(f) < 0)
		return 0;
	if(vtfilelock(f->source, VtOREAD) < 0){
		filerunlock(f);
		return -1;
	}
	*size = vtfilegetsize(f->source);
	vtfileunlock(f->source);
	filerunlock(f);

	return 0;
}

int
vacfilegetvtentry(VacFile *f, VtEntry *e)
{
	if(filerlock(f) < 0)
		return 0;
	if(vtfilelock(f->source, VtOREAD) < 0){
		filerunlock(f);
		return -1;
	}
	vtfilegetentry(f->source, e);
	vtfileunlock(f->source);
	filerunlock(f);

	return 0;
}

void
vacfilemetaflush(VacFile *f, int rec)
{
	VacFile **kids, *p;
	int nkids;
	int i;

	filemetalock(f);
	filemetaflush2(f, nil);
	filemetaunlock(f);

	if(!rec || !vacfileisdir(f))
		return;

	if(filelock(f) < 0)
		return;
	nkids = 0;
	for(p=f->down; p; p=p->next)
		nkids++;
	kids = vtmalloc(nkids*sizeof(VacFile*));
	i = 0;
	for(p=f->down; p; p=p->next){
		kids[i++] = p;
		p->ref++;
	}
	fileunlock(f);

	for(i=0; i<nkids; i++){
		vacfilemetaflush(kids[i], 1);
		vacfiledecref(kids[i]);
	}
	vtfree(kids);
}

/* assumes metaLock is held */
static int
filemetaflush2(VacFile *f, char *oelem)
{
	VacFile *fp;
	VtBlock *b, *bb;
	MetaBlock mb;
	MetaEntry me, me2;
	int i, n;
	u32int boff;

	if(!f->dirty)
		return 0;

	if(oelem == nil)
		oelem = f->dir.elem;

	fp = f->up;

	if(vtfilelock(fp->msource, -1) < 0)
		return 0;
	/* can happen if source is clri'ed out from under us */
	if(f->boff == NilBlock)
		goto Err1;
	b = vtfileblock(fp->msource, f->boff, VtORDWR);
	if(b == nil)
		goto Err1;

	if(mbunpack(&mb, b->data, fp->msource->dsize) < 0)
		goto Err;
	if(mbsearch(&mb, oelem, &i, &me) < 0)
		goto Err;

	n = vdsize(&f->dir);
if(0)fprint(2, "old size %d new size %d\n", me.size, n);

	if(mbresize(&mb, &me, n) >= 0){
		/* fits in the block */
		mbdelete(&mb, i, &me);
		if(strcmp(f->dir.elem, oelem) != 0)
			mbsearch(&mb, f->dir.elem, &i, &me2);
		vdpack(&f->dir, &me);
		mbinsert(&mb, i, &me);
		mbpack(&mb);
		vtblockdirty(b);
		vtblockput(b);
		vtfileunlock(fp->msource);
		f->dirty = 0;
		return -1;
	}

	/*
	 * moving entry to another block
	 * it is feasible for the fs to crash leaving two copies
	 * of the directory entry.  This is just too much work to
	 * fix.  Given that entries are only allocated in a block that
	 * is less than PercentageFull, most modifications of meta data
	 * will fit within the block.  i.e. this code should almost
	 * never be executed.
	 */
	boff = filemetaalloc(fp, &f->dir, f->boff+1);
	if(boff == NilBlock){
		/* mbResize might have modified block */
		mbpack(&mb);
		vtblockdirty(b);
		goto Err;
	}
fprint(2, "fileMetaFlush moving entry from %ud -> %ud\n", f->boff, boff);
	f->boff = boff;

	/* make sure deletion goes to disk after new entry */
	bb = vtfileblock(fp->msource, f->boff, VtORDWR);
	mbdelete(&mb, i, &me);
	mbpack(&mb);
//	blockDependency(b, bb, -1, nil, nil);
	vtblockput(bb);
	vtblockdirty(b);
	vtblockput(b);
	vtfileunlock(fp->msource);

	f->dirty = 0;

	return 0;

Err:
	vtblockput(b);
Err1:
	vtfileunlock(fp->msource);
	return -1;
}

static int
filemetaremove(VacFile *f, char *uid)
{
	VtBlock *b;
	MetaBlock mb;
	MetaEntry me;
	int i;
	VacFile *up;

	b = nil;
	up = f->up;
	filewaccess(up, uid);
	filemetalock(f);

	if(vtfilelock(up->msource, VtORDWR) < 0)
		goto Err;
	b = vtfileblock(up->msource, f->boff, VtORDWR);
	if(b == nil)
		goto Err;

	if(mbunpack(&mb, b->data, up->msource->dsize) < 0)
		goto Err;
	if(mbsearch(&mb, f->dir.elem, &i, &me) < 0)
		goto Err;
	mbdelete(&mb, i, &me);
	mbpack(&mb);
	vtfileunlock(up->msource);

	vtblockdirty(b);
	vtblockput(b);

	f->removed = 1;
	f->boff = NilBlock;
	f->dirty = 0;

	filemetaunlock(f);
	return 0;

Err:
	vtfileunlock(up->msource);
	vtblockput(b);
	filemetaunlock(f);
	return -1;
}

/* assume file is locked, assume f->msource is locked */
static int
filecheckempty(VacFile *f)
{
	u32int i, n;
	VtBlock *b;
	MetaBlock mb;
	VtFile *r;

	r = f->msource;
	n = (vtfilegetsize(r)+r->dsize-1)/r->dsize;
	for(i=0; i<n; i++){
		b = vtfileblock(r, i, VtORDWR);
		if(b == nil)
			goto Err;
		if(mbunpack(&mb, b->data, r->dsize) < 0)
			goto Err;
		if(mb.nindex > 0){
			werrstr(ENotEmpty);
			goto Err;
		}
		vtblockput(b);
	}
	return 0;
Err:
	vtblockput(b);
	return -1;
}

int
vacfileremove(VacFile *f, char *muid)
{
	VacFile *ff;

	/* can not remove the root */
	if(vacfileisroot(f)){
		werrstr(ERoot);
		return -1;
	}

	if(filelock(f) < 0)
		return -1;

	if(f->source->mode != VtORDWR){
		werrstr(EReadOnly);
		goto Err1;
	}
	if(vtfilelock2(f->source, f->msource, -1) < 0)
		goto Err1;
	if(vacfileisdir(f) && filecheckempty(f)<0)
		goto Err;

	for(ff=f->down; ff; ff=ff->next)
		assert(ff->removed);

	vtfileremove(f->source);
	f->source = nil;
	if(f->msource){
		vtfileremove(f->msource);
		f->msource = nil;
	}

	fileunlock(f);

	if(filemetaremove(f, muid) < 0)
		return -1;

	return 0;

Err:
	vtfileunlock(f->source);
	if(f->msource)
		vtfileunlock(f->msource);
Err1:
	fileunlock(f);
	return -1;
}

static int
clri(VacFile *f, char *uid)
{
	int r;

	if(f == nil)
		return -1;
	if(f->up->source->mode != VtORDWR){
		werrstr(EReadOnly);
		vacfiledecref(f);
		return -1;
	}
	r = filemetaremove(f, uid);
	vacfiledecref(f);
	return r;
}

int
vacfileclripath(VacFs *fs, char *path, char *uid)
{
	return clri(_fileopen(fs, path, 1), uid);
}

int
vacfileclri(VacFile *dir, char *elem, char *uid)
{
	return clri(_filewalk(dir, elem, 1), uid);
}

VacFile*
vacfileincref(VacFile *vf)
{
	filemetalock(vf);
	assert(vf->ref > 0);
	vf->ref++;
	filemetaunlock(vf);
	return vf;
}

int
vacfiledecref(VacFile *f)
{
	VacFile *p, *q, **qq;

	if(f->up == nil){
		/* never linked in */
		assert(f->ref == 1);
		filefree(f);
		return 0;
	}

	filemetalock(f);
	f->ref--;
	if(f->ref > 0){
		filemetaunlock(f);
		return -1;
	}
	assert(f->ref == 0);
	assert(f->down == nil);

	filemetaflush2(f, nil);

	p = f->up;
	qq = &p->down;
	for(q = *qq; q; q = *qq){
		if(q == f)
			break;
		qq = &q->next;
	}
	assert(q != nil);
	*qq = f->next;

	filemetaunlock(f);
	filefree(f);
	vacfiledecref(p);
	return 0;
}

VacFile*
filegetparent(VacFile *f)
{
	if(vacfileisroot(f))
		return vacfileincref(f);
	return vacfileincref(f->up);
}

VacDirEnum*
vdeopen(VacFile *f)
{
	VacDirEnum *vde;
	VacFile *p;

	if(!vacfileisdir(f)){
		werrstr(ENotDir);
		return nil;
	}

	/* flush out meta data */
	if(filelock(f) < 0)
		return nil;
	for(p=f->down; p; p=p->next)
		filemetaflush2(p, nil);
	fileunlock(f);

	vde = vtmallocz(sizeof(VacDirEnum));
	vde->file = vacfileincref(f);

	return vde;
}

static int
direntrysize(VtFile *s, ulong elem, ulong gen, uvlong *size)
{
	VtBlock *b;
	ulong bn;
	VtEntry e;
	int epb;

	epb = s->dsize/VtEntrySize;
	bn = elem/epb;
	elem -= bn*epb;

	b = vtfileblock(s, bn, VtOREAD);
	if(b == nil)
		goto Err;
	if(vtentryunpack(&e, b->data, elem) < 0)
		goto Err;

	/* hanging entries are returned as zero size */
	if(!(e.flags & VtEntryActive) || e.gen != gen)
		*size = 0;
	else
		*size = e.size;
	vtblockput(b);
	return 0;

Err:
	vtblockput(b);
	return -1;
}

static int
vdefill(VacDirEnum *vde)
{
	int i, n;
	VtFile *meta, *source;
	MetaBlock mb;
	MetaEntry me;
	VacFile *f;
	VtBlock *b;
	VacDir *de;

	/* clean up first */
	for(i=vde->i; i<vde->n; i++)
		vdcleanup(vde->buf+i);
	vtfree(vde->buf);
	vde->buf = nil;
	vde->i = 0;
	vde->n = 0;

	f = vde->file;

	source = f->source;
	meta = f->msource;

	b = vtfileblock(meta, vde->boff, VtOREAD);
	if(b == nil)
		goto Err;
	if(mbunpack(&mb, b->data, meta->dsize) < 0)
		goto Err;

	n = mb.nindex;
	vde->buf = vtmalloc(n * sizeof(VacDir));

	for(i=0; i<n; i++){
		de = vde->buf + i;
		meunpack(&me, &mb, i);
		if(vdunpack(de, &me) < 0)
			goto Err;
		vde->n++;
		if(!(de->mode & ModeDir))
		if(direntrysize(source, de->entry, de->gen, &de->size) < 0)
			goto Err;
	}
	vde->boff++;
	vtblockput(b);
	return 0;
Err:
	vtblockput(b);
	return -1;
}

int
vderead(VacDirEnum *vde, VacDir *de)
{
	int ret, didread;
	VacFile *f;
	u32int nb;

	f = vde->file;
	if(filerlock(f) < 0)
		return -1;

	if(vtfilelock2(f->source, f->msource, VtOREAD) < 0){
		filerunlock(f);
		return -1;
	}

	nb = (vtfilegetsize(f->msource)+f->msource->dsize-1)/f->msource->dsize;

	didread = 0;
	while(vde->i >= vde->n){
		if(vde->boff >= nb){
			ret = 0;
			goto Return;
		}
		didread = 1;
		if(vdefill(vde) < 0){
			ret = -1;
			goto Return;
		}
	}

	memmove(de, vde->buf + vde->i, sizeof(VacDir));
	vde->i++;
	ret = 1;

Return:
	vtfileunlock(f->source);
	vtfileunlock(f->msource);
	filerunlock(f);

	if(didread)
		fileraccess(f);
	return ret;
}

void
vdeclose(VacDirEnum *vde)
{
	int i;
	if(vde == nil)
		return;
	for(i=vde->i; i<vde->n; i++)
		vdcleanup(vde->buf+i);
	vtfree(vde->buf);
	vacfiledecref(vde->file);
	vtfree(vde);
}

/*
 * caller must lock f->source and f->msource
 * caller must NOT lock the source and msource
 * referenced by dir.
 */
static u32int
filemetaalloc(VacFile *f, VacDir *dir, u32int start)
{
	u32int nb, bo;
	VtBlock *b, *bb;
	MetaBlock mb;
	int nn;
	uchar *p;
	int i, n, epb;
	MetaEntry me;
	VtFile *s, *ms;

	s = f->source;
	ms = f->msource;

	n = vdsize(dir);
	nb = (vtfilegetsize(ms)+ms->dsize-1)/ms->dsize;
	b = nil;
	if(start > nb)
		start = nb;
	for(bo=start; bo<nb; bo++){
		b = vtfileblock(ms, bo, VtORDWR);
		if(b == nil)
			goto Err;
		if(mbunpack(&mb, b->data, ms->dsize) < 0)
			goto Err;
		nn = (mb.maxsize*FullPercentage/100) - mb.size + mb.free;
		if(n <= nn && mb.nindex < mb.maxindex)
			break;
		vtblockput(b);
		b = nil;
	}

	/* add block to meta file */
	if(b == nil){
		b = vtfileblock(ms, bo, VtORDWR);
		if(b == nil)
			goto Err;
		vtfilesetsize(ms, (nb+1)*ms->dsize);
		mbinit(&mb, b->data, ms->dsize, ms->dsize/BytesPerEntry);
	}

	p = mballoc(&mb, n);
	if(p == nil){
		/* mbAlloc might have changed block */
		mbpack(&mb);
		vtblockdirty(b);
		werrstr(EBadMeta);
		goto Err;
	}

	mbsearch(&mb, dir->elem, &i, &me);
	assert(me.p == nil);
	me.p = p;
	me.size = n;
	vdpack(dir, &me);
	mbinsert(&mb, i, &me);
	mbpack(&mb);

#ifdef notdef /* XXX */
	/* meta block depends on super block for qid ... */
	bb = cacheLocal(b->c, PartSuper, 0, VtOREAD);
	blockDependency(b, bb, -1, nil, nil);
	vtblockput(bb);

	/* ... and one or two dir entries */
	epb = s->dsize/VtEntrySize;
	bb = vtfileblock(s, dir->entry/epb, VtOREAD);
	blockDependency(b, bb, -1, nil, nil);
	vtblockput(bb);
	if(dir->mode & ModeDir){
		bb = sourceBlock(s, dir->mentry/epb, VtOREAD);
		blockDependency(b, bb, -1, nil, nil);
		vtblockput(bb);
	}
#endif

	vtblockdirty(b);
	vtblockput(b);
	return bo;
Err:
	vtblockput(b);
	return NilBlock;
}

static int
chksource(VacFile *f)
{
	if(f->partial)
		return 0;

	if(f->source == nil || (f->dir.mode & ModeDir) && f->msource == nil){
		werrstr(ERemoved);
		return -1;
	}
	return 0;
}

static int
filerlock(VacFile *f)
{
//	assert(!canwlock(&f->fs->elk));
	rlock(&f->lk);
	if(chksource(f) < 0){
		runlock(&f->lk);
		return -1;
	}
	return 0;
}

static void
filerunlock(VacFile *f)
{
	runlock(&f->lk);
}

static int
filelock(VacFile *f)
{
//	assert(!canwlock(&f->fs->elk));
	wlock(&f->lk);
	if(chksource(f) < 0){
		wunlock(&f->lk);
		return -1;
	}
	return 0;
}

static void
fileunlock(VacFile *f)
{
	wunlock(&f->lk);
}

/*
 * f->source and f->msource must NOT be locked.
 * fileMetaFlush locks the fileMeta and then the source (in fileMetaFlush2).
 * We have to respect that ordering.
 */
static void
filemetalock(VacFile *f)
{
	assert(f->up != nil);
//	assert(!canwlock(&f->fs->elk));
	wlock(&f->up->lk);
}

static void
filemetaunlock(VacFile *f)
{
	wunlock(&f->up->lk);
}

/*
 * f->source and f->msource must NOT be locked.
 * see filemetalock.
 */
static void
fileraccess(VacFile* f)
{
	if(f->mode == VtOREAD)
		return;

	filemetalock(f);
	f->dir.atime = time(0L);
	f->dirty = 1;
	filemetaunlock(f);
}

/*
 * f->source and f->msource must NOT be locked.
 * see filemetalock.
 */
static void
filewaccess(VacFile* f, char *mid)
{
	if(f->mode == VtOREAD)
		return;

	filemetalock(f);
	f->dir.atime = f->dir.mtime = time(0L);
	if(strcmp(f->dir.mid, mid) != 0){
		vtfree(f->dir.mid);
		f->dir.mid = vtstrdup(mid);
	}
	f->dir.mcount++;
	f->dirty = 1;
	filemetaunlock(f);

/*RSC: let's try this */
/*presotto - lets not
	if(f->up)
		filewaccess(f->up, mid);
*/
}

#if 0
static void
markCopied(Block *b)
{
	VtBlock *lb;
	Label l;

	if(globalToLocal(b->score) == NilBlock)
		return;

	if(!(b->l.state & BsCopied)){
		/*
		 * We need to record that there are now pointers in
		 * b that are not unique to b.  We do this by marking
		 * b as copied.  Since we don't return the label block,
		 * the caller can't get the dependencies right.  So we have
		 * to flush the block ourselves.  This is a rare occurrence.
		 */
		l = b->l;
		l.state |= BsCopied;
		lb = _blockSetLabel(b, &l);
	WriteAgain:
		while(!blockWrite(lb)){
			fprint(2, "getEntry: could not write label block\n");
			sleep(10*1000);
		}
		while(lb->iostate != BioClean && lb->iostate != BioDirty){
			assert(lb->iostate == BioWriting);
			vtSleep(lb->ioready);
		}
		if(lb->iostate == BioDirty)
			goto WriteAgain;
		vtblockput(lb);
	}
}

static int
getEntry(VtFile *r, Entry *e, int mark)
{
	Block *b;

	if(r == nil){
		memset(&e, 0, sizeof e);
		return 1;
	}

	b = cacheGlobal(r->fs->cache, r->score, BtDir, r->tag, VtOREAD);
	if(b == nil)
		return 0;
	if(!entryUnpack(e, b->data, r->offset % r->epb)){
		vtblockput(b);
		return 0;
	}

	if(mark)
		markCopied(b);
	vtblockput(b);
	return 1;
}

static int
setEntry(Source *r, Entry *e)
{
	Block *b;
	Entry oe;

	b = cacheGlobal(r->fs->cache, r->score, BtDir, r->tag, VtORDWR);
	if(0) fprint(2, "setEntry: b %#ux %d score=%V\n", b->addr, r->offset % r->epb, e->score);
	if(b == nil)
		return 0;
	if(!entryUnpack(&oe, b->data, r->offset % r->epb)){
		vtblockput(b);
		return 0;
	}
	e->gen = oe.gen;
	entryPack(e, b->data, r->offset % r->epb);

	/* BUG b should depend on the entry pointer */

	markCopied(b);
	vtblockdirty(b);
	vtblockput(b);
	return 1;
}

/* assumes hold elk */
int
fileSnapshot(VacFile *dst, VacFile *src, u32int epoch, int doarchive)
{
	Entry e, ee;

	/* add link to snapshot */
	if(!getEntry(src->source, &e, 1) || !getEntry(src->msource, &ee, 1))
		return 0;

	e.snap = epoch;
	e.archive = doarchive;
	ee.snap = epoch;
	ee.archive = doarchive;

	if(!setEntry(dst->source, &e) || !setEntry(dst->msource, &ee))
		return 0;
	return 1;
}

int
fileGetSources(VacFile *f, Entry *e, Entry *ee, int mark)
{
	if(!getEntry(f->source, e, mark)
	|| !getEntry(f->msource, ee, mark))
		return 0;
	return 1;
}	

int
fileWalkSources(VacFile *f)
{
	if(f->mode == VtOREAD)
		return 1;
	if(!sourceLock2(f->source, f->msource, VtORDWR))
		return 0;
	vtfileunlock(f->source);
	vtfileunlock(f->msource);
	return 1;
}

#endif