Subversion Repositories planix.SVN

Rev

Go to most recent revision | Blame | Compare with Previous | Last modification | View Log | RSS feed

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

#define debug 0

/*
 * Vac file system.  This is a simplified version of the same code in Fossil.
 * 
 * The locking order in the tree is upward: a thread can hold the lock
 * for a VacFile and then acquire the lock of f->up (the parent),
 * but not vice-versa.
 * 
 * A vac file is one or two venti files.  Plain data files are one venti file,
 * while directores are two: a venti data file containing traditional
 * directory entries, and a venti directory file containing venti 
 * directory entries.  The traditional directory entries in the data file
 * contain integers indexing into the venti directory entry file.
 * It's a little complicated, but it makes the data usable by standard
 * tools like venti/copy.
 *
 */
 
static int filemetaflush(VacFile*, char*);

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;
        
        uvlong  qidoffset;      /* qid offset */
};

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);
}

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
filelock(VacFile *f)
{
        wlock(&f->lk);
        if(chksource(f) < 0){
                wunlock(&f->lk);
                return -1;
        }
        return 0;
}

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

static int
filerlock(VacFile *f)
{
        rlock(&f->lk);
        if(chksource(f) < 0){
                runlock(&f->lk);
                return -1;
        }
        return 0;
}

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

/*
 * The file metadata, like f->dir and f->ref,
 * are synchronized via the parent's lock.
 * This is why locking order goes up.
 */
static void
filemetalock(VacFile *f)
{
        assert(f->up != nil);
        wlock(&f->up->lk);
}

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

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

uvlong
vacfilegetqidoffset(VacFile *f)
{
        return f->qidoffset;
}

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;
}

/*
 * The files are reference counted, and while the reference
 * is bigger than zero, each file can be found in its parent's
 * f->down list (chains via f->next), so that multiple threads
 * end up sharing a VacFile* when referring to the same file.
 *
 * Each VacFile holds a reference to its parent.
 */
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);

        if(f->source && vtfilelock(f->source, -1) >= 0){
                vtfileflush(f->source);
                vtfileunlock(f->source);
        }
        if(f->msource && vtfilelock(f->msource, -1) >= 0){
                vtfileflush(f->msource);
                vtfileunlock(f->msource);
        }

        /*
         * Flush f's directory information to the cache.
         */
        filemetaflush(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;
}


/* 
 * Construct a vacfile for the root of a vac tree, given the 
 * venti file for the root information.  That venti file is a 
 * directory file containing VtEntries for three more venti files:
 * the two venti files making up the root directory, and a 
 * third venti file that would be the metadata half of the 
 * "root's parent".
 *
 * Fossil generates slightly different vac files, due to a now
 * impossible-to-change bug, which contain a VtEntry
 * for just one venti file, that itself contains the expected
 * three directory entries.  Sigh.
 */
VacFile*
_vacfileroot(VacFs *fs, VtFile *r)
{
        int redirected;
        char err[ERRMAX];       
        VtBlock *b;
        VtFile *r0, *r1, *r2;
        MetaBlock mb;
        MetaEntry me;
        VacFile *root, *mr;

        redirected = 0;
Top:
        b = nil;
        root = nil;
        mr = nil;
        r1 = nil;
        r2 = nil;

        if(vtfilelock(r, -1) < 0)
                return nil;
        r0 = vtfileopen(r, 0, fs->mode);
        if(debug)
                fprint(2, "r0 %p\n", r0);
        if(r0 == nil)
                goto Err;
        r2 = vtfileopen(r, 2, fs->mode);
        if(debug)
                fprint(2, "r2 %p\n", r2);
        if(r2 == nil){
                /*
                 * some vac files (e.g., from fossil)
                 * have an extra layer of indirection.
                 */
                rerrstr(err, sizeof err);
                if(!redirected && strstr(err, "not active")){
                        redirected = 1;
                        vtfileunlock(r);
                        r = r0;
                        goto Top;
                }
                goto Err;
        }
        r1 = vtfileopen(r, 1, fs->mode);
        if(debug)
                fprint(2, "r1 %p\n", r1);
        if(r1 == 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;
        vtfileunlock(r);

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

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

        meunpack(&me, &mb, 0);
        if(vdunpack(&root->dir, &me) < 0)
                goto Err1;
        vtblockput(b);

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

        return nil;
}

/*
 * Vac directories are a sequence of metablocks, each of which
 * contains a bunch of metaentries sorted by file name.
 * The whole sequence isn't sorted, though, so you still have
 * to look at every block to find a given name.
 * Dirlookup looks in f for an element name elem.
 * It returns a new VacFile with the dir, boff, and mode
 * filled in, but the sources (venti files) are not, and f is 
 * not yet linked into the tree.  These details must be taken
 * care of by the caller.
 *
 * f must be locked, f->msource must not.
 */
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;
                        }
                        ff->qidoffset = f->qidoffset + ff->dir.qidoffset;
                        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;
}

/*
 * Open the venti file at offset in the directory f->source.
 * f is locked.
 */
static VtFile *
fileopensource(VacFile *f, u32int offset, u32int gen, int dir, uint mode)
{
        VtFile *r;

        if((r = vtfileopen(f->source, offset, mode)) == nil)
                return nil;
        if(r == nil)
                return nil;
        if(r->gen != gen){
                werrstr(ERemoved);
                vtfileclose(r);
                return nil;
        }
        if(r->dir != dir && r->mode != -1){
                werrstr(EBadMeta);
                vtfileclose(r);
                return nil;
        }
        return r;
}

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

/*
 * Given an unlocked vacfile (directory) f,
 * return the vacfile named elem in f.
 * Interprets . and .. as a convenience to callers.
 */
VacFile*
vacfilewalk(VacFile *f, char *elem)
{
        VacFile *ff;

        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)
                return vacfilegetparent(f);

        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(vtfilelock(f->source, f->mode) < 0)
                goto Err;
        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 Err1;
        }else{
                ff->source = fileopensource(f, ff->dir.entry, ff->dir.gen, 0, ff->mode);
                if(ff->source == nil)
                        goto Err1;
        }
        vtfileunlock(f->source);

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

Err1:
        vtfileunlock(f->source);
Err:
        fileunlock(f);
        if(ff != nil)
                vacfiledecref(ff);
        return nil;
}

/* 
 * Open a path in the vac file system: 
 * just walk each element one at a time.
 */
VacFile*
vacfileopen(VacFs *fs, char *path)
{
        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 = vacfilewalk(f, elem);
                        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;
}

/*
 * Extract the score for the bn'th block in f.
 */
int
vacfileblockscore(VacFile *f, u32int bn, u8int *score)
{
        VtFile *s;
        uvlong size;
        int dsize, ret;

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

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

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

/*
 * Read data from f.
 */
int
vacfileread(VacFile *f, void *buf, int cnt, vlong offset)
{
        int n;

        if(offset < 0){
                werrstr(EBadOffset);
                return -1;
        }
        if(filerlock(f) < 0)
                return -1;
        if(vtfilelock(f->source, VtOREAD) < 0){
                filerunlock(f);
                return -1;
        }
        n = vtfileread(f->source, buf, cnt, offset);
        vtfileunlock(f->source);
        filerunlock(f);
        return n;
}

static int
getentry(VtFile *f, VtEntry *e)
{
        if(vtfilelock(f, VtOREAD) < 0)
                return -1;
        if(vtfilegetentry(f, e) < 0){
                vtfileunlock(f);
                return -1;
        }
        vtfileunlock(f);
        if(vtglobaltolocal(e->score) != NilBlock){
                werrstr("internal error - data not on venti");
                return -1;
        }
        return 0;
}

/*
 * Get the VtEntries for the data contained in f.
 */
int
vacfilegetentries(VacFile *f, VtEntry *e, VtEntry *me)
{
        if(filerlock(f) < 0)
                return -1;
        if(e && getentry(f->source, e) < 0){
                filerunlock(f);
                return -1;
        }
        if(me){
                if(f->msource == nil)
                        memset(me, 0, sizeof *me);
                else if(getentry(f->msource, me) < 0){
                        filerunlock(f);
                        return -1;
                }
        }
        filerunlock(f);
        return 0;
}

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

        return 0;
}

/*
 * Directory reading.
 *
 * A VacDirEnum is a buffer containing directory entries.
 * Directory entries contain malloced strings and need to 
 * be cleaned up with vdcleanup.  The invariant in the 
 * VacDirEnum is that the directory entries between
 * vde->i and vde->n are owned by the vde and need to
 * be cleaned up if it is closed.  Those from 0 up to vde->i
 * have been handed to the reader, and the reader must 
 * take care of calling vdcleanup as appropriate.
 */
VacDirEnum*
vdeopen(VacFile *f)
{
        VacDirEnum *vde;
        VacFile *p;

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

        /*
         * There might be changes to this directory's children
         * that have not been flushed out into the cache yet.
         * Those changes are only available if we look at the 
         * VacFile structures directory.  But the directory reader
         * is going to read the cache blocks directly, so update them.
         */
        if(filelock(f) < 0)
                return nil;
        for(p=f->down; p; p=p->next)
                filemetaflush(p, nil);
        fileunlock(f);

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

        return vde;
}

/*
 * Figure out the size of the directory entry at offset.
 * The rest of the metadata is kept in the data half,
 * but since venti has to track the data size anyway,
 * we just use that one and avoid updating the directory
 * each time the file size changes.
 */
static int
direntrysize(VtFile *s, ulong offset, ulong gen, uvlong *size)
{
        VtBlock *b;
        ulong bn;
        VtEntry e;
        int epb;

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

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

        /* dangling 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;
}

/*
 * Fill in vde with a new batch of directory entries.
 */
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;
}

/*
 * Read a single directory entry from vde into de.
 * Returns -1 on error, 0 on EOF, and 1 on success.
 * When it returns 1, it becomes the caller's responsibility
 * to call vdcleanup(de) to free the strings contained
 * inside, or else to call vdunread to give it back.
 */
int
vderead(VacDirEnum *vde, VacDir *de)
{
        int ret;
        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;

        while(vde->i >= vde->n){
                if(vde->boff >= nb){
                        ret = 0;
                        goto Return;
                }
                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);

        return ret;
}

/*
 * "Unread" the last directory entry that was read,
 * so that the next vderead will return the same one.
 * If the caller calls vdeunread(vde) it should not call
 * vdcleanup on the entry being "unread".
 */
int
vdeunread(VacDirEnum *vde)
{
        if(vde->i > 0){
                vde->i--;
                return 0;
        }
        return -1;
}

/*
 * Close the enumerator.
 */
void
vdeclose(VacDirEnum *vde)
{
        int i;
        if(vde == nil)
                return;
        /* free the strings */
        for(i=vde->i; i<vde->n; i++)
                vdcleanup(vde->buf+i);
        vtfree(vde->buf);
        vacfiledecref(vde->file);
        vtfree(vde);
}


/*
 * On to mutation.  If the vac file system has been opened
 * read-write, then the files and directories can all be edited.
 * Changes are kept in the in-memory cache until flushed out
 * to venti, so we must be careful to explicitly flush data 
 * that we're not likely to modify again.
 *
 * Each VacFile has its own copy of its VacDir directory entry
 * in f->dir, but otherwise the cache is the authoratative source
 * for data.  Thus, for the most part, it suffices if we just 
 * call vtfileflushbefore and vtfileflush when we modify things.
 * There are a few places where we have to remember to write
 * changed VacDirs back into the cache.  If f->dir *is* out of sync,
 * then f->dirty should be set.
 *
 * The metadata in a directory is, to venti, a plain data file,
 * but as mentioned above it is actually a sequence of 
 * MetaBlocks that contain sorted lists of VacDir entries.
 * The filemetaxxx routines manipulate that stream.
 */

/*
 * Find space in fp for the directory entry dir (not yet written to disk)
 * and write it to disk, returning NilBlock on failure,
 * or the block number on success.
 *
 * Start is a suggested block number to try.
 * The caller must have filemetalock'ed f and have
 * vtfilelock'ed f->up->msource.
 */
static u32int
filemetaalloc(VacFile *fp, VacDir *dir, u32int start)
{
        u32int nb, bo;
        VtBlock *b;
        MetaBlock mb;
        int nn;
        uchar *p;
        int i, n;
        MetaEntry me;
        VtFile *ms;
        
        ms = fp->msource;
        n = vdsize(dir, VacDirVersion);
        
        /* Look for a block with room for a new entry of size n. */
        nb = (vtfilegetsize(ms)+ms->dsize-1)/ms->dsize;
        if(start == NilBlock){
                if(nb > 0)
                        start = nb - 1;
                else
                        start = 0;
        }
        
        if(start > nb)
                start = nb;
        for(bo=start; bo<nb; bo++){
                if((b = vtfileblock(ms, bo, VtOREAD)) == 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){
                        /* reopen for writing */
                        vtblockput(b);
                        if((b = vtfileblock(ms, bo, VtORDWR)) == nil)
                                goto Err;
                        mbunpack(&mb, b->data, ms->dsize);
                        goto Found;
                }
                vtblockput(b);
        }

        /* No block found, extend the file by one metablock. */
        vtfileflushbefore(ms, nb*(uvlong)ms->dsize);
        if((b = vtfileblock(ms, nb, VtORDWR)) == nil)
                goto Err;
        vtfilesetsize(ms, (nb+1)*ms->dsize);
        mbinit(&mb, b->data, ms->dsize, ms->dsize/BytesPerEntry);

Found:
        /* Now we have a block; allocate space to write the entry. */
        p = mballoc(&mb, n);
        if(p == nil){
                /* mballoc might have changed block */
                mbpack(&mb);
                werrstr(EBadMeta);
                goto Err;
        }

        /* Figure out where to put the index entry, and write it. */
        mbsearch(&mb, dir->elem, &i, &me);
        assert(me.p == nil);    /* not already there */
        me.p = p;
        me.size = n;
        vdpack(dir, &me, VacDirVersion);
        mbinsert(&mb, i, &me);
        mbpack(&mb);
        vtblockput(b);
        return bo;

Err:
        vtblockput(b);
        return NilBlock;
}

/*
 * Update f's directory entry in the block cache. 
 * We look for the directory entry by name;
 * if we're trying to rename the file, oelem is the old name.
 *
 * Assumes caller has filemetalock'ed f.
 */
static int
filemetaflush(VacFile *f, char *oelem)
{
        int i, n;
        MetaBlock mb;
        MetaEntry me, me2;
        VacFile *fp;
        VtBlock *b;
        u32int bo;

        if(!f->dirty)
                return 0;

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

        /*
         * Locate f's old metadata in the parent's metadata file.
         * We know which block it was in, but not exactly where
         * in the block.
         */
        fp = f->up;
        if(vtfilelock(fp->msource, -1) < 0)
                return -1;
        /* 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;

        /*
         * Check whether we can resize the entry and keep it 
         * in this block.
         */
        n = vdsize(&f->dir, VacDirVersion);
        if(mbresize(&mb, &me, n) >= 0){
                /* Okay, can be done without moving to another block. */

                /* Remove old data */
                mbdelete(&mb, i, &me);

                /* Find new location if renaming */
                if(strcmp(f->dir.elem, oelem) != 0)
                        mbsearch(&mb, f->dir.elem, &i, &me2);

                /* Pack new data into new location. */
                vdpack(&f->dir, &me, VacDirVersion);
vdunpack(&f->dir, &me);
                mbinsert(&mb, i, &me);
                mbpack(&mb);
                
                /* Done */
                vtblockput(b);
                vtfileunlock(fp->msource);
                f->dirty = 0;
                return 0;
        }
        
        /*
         * The entry must be moved to another block.
         * This can only really happen on renames that
         * make the name very long.
         */
        
        /* Allocate a spot in a new block. */
        if((bo = filemetaalloc(fp, &f->dir, f->boff+1)) == NilBlock){
                /* mbresize above might have modified block */
                mbpack(&mb);
                goto Err;
        }
        f->boff = bo;

        /* Now we're committed.  Delete entry in old block. */
        mbdelete(&mb, i, &me);
        mbpack(&mb);
        vtblockput(b);
        vtfileunlock(fp->msource);

        f->dirty = 0;
        return 0;

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

/*
 * Remove the directory entry for f.
 */
static int
filemetaremove(VacFile *f)
{
        VtBlock *b;
        MetaBlock mb;
        MetaEntry me;
        int i;
        VacFile *fp;

        b = nil;
        fp = f->up;
        filemetalock(f);

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

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

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

        filemetaunlock(f);
        return 0;

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

/*
 * That was far too much effort for directory entries.
 * Now we can write code that *does* things.
 */

/*
 * Flush all data associated with f out of the cache and onto venti.
 * If recursive is set, flush f's children too.
 * Vacfiledecref knows how to flush source and msource too.
 */
int
vacfileflush(VacFile *f, int recursive)
{
        int ret;
        VacFile **kids, *p;
        int i, nkids;
        
        if(f->mode == VtOREAD)
                return 0;

        ret = 0;
        filemetalock(f);
        if(filemetaflush(f, nil) < 0)
                ret = -1;
        filemetaunlock(f);

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

        /*
         * Lock order prevents us from flushing kids while holding
         * lock, so make a list and then flush without the lock.
         */
        nkids = 0;
        kids = nil;
        if(recursive){
                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++;
                }
        }
        if(nkids > 0){
                fileunlock(f);
                for(i=0; i<nkids; i++){
                        if(vacfileflush(kids[i], 1) < 0)
                                ret = -1;
                        vacfiledecref(kids[i]);
                }
                filelock(f);
        }
        free(kids);

        /*
         * Now we can flush our own data.
         */     
        vtfilelock(f->source, -1);
        if(vtfileflush(f->source) < 0)
                ret = -1;
        vtfileunlock(f->source);
        if(f->msource){
                vtfilelock(f->msource, -1);
                if(vtfileflush(f->msource) < 0)
                        ret = -1;
                vtfileunlock(f->msource);
        }
        fileunlock(f);

        return ret;
}
                
/*
 * Create a new file named elem in fp with the given mode.
 * The mode can be changed later except for the ModeDir bit.
 */
VacFile*
vacfilecreate(VacFile *fp, char *elem, ulong mode)
{
        VacFile *ff;
        VacDir *dir;
        VtFile *pr, *r, *mr;
        int type;
        u32int bo;

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

        /*
         * First, look to see that there's not a file in memory
         * with the same name.
         */
        for(ff = fp->down; ff; ff=ff->next){
                if(strcmp(elem, ff->dir.elem) == 0 && !ff->removed){
                        ff = nil;
                        werrstr(EExists);
                        goto Err1;
                }
        }

        /*
         * Next check the venti blocks.
         */
        ff = dirlookup(fp, elem);
        if(ff != nil){
                werrstr(EExists);
                goto Err1;
        }

        /*
         * By the way, you can't create in a read-only file system.
         */
        pr = fp->source;
        if(pr->mode != VtORDWR){
                werrstr(EReadOnly);
                goto Err1;
        }

        /*
         * Okay, time to actually create something.  Lock the two
         * halves of the directory and create a file.
         */
        if(vtfilelock2(fp->source, fp->msource, -1) < 0)
                goto Err1;
        ff = filealloc(fp->fs);
        ff->qidoffset = fp->qidoffset;  /* hopefully fp->qidoffset == 0 */
        type = VtDataType;
        if(mode & ModeDir)
                type = VtDirType;
        mr = nil;
        if((r = vtfilecreate(pr, pr->psize, pr->dsize, type)) == nil)
                goto Err;
        if(mode & ModeDir)
        if((mr = vtfilecreate(pr, pr->psize, pr->dsize, VtDataType)) == nil)
                goto Err;

        /*
         * Fill in the directory entry and write it to disk.
         */
        dir = &ff->dir;
        dir->elem = vtstrdup(elem);
        dir->entry = r->offset;
        dir->gen = r->gen;
        if(mode & ModeDir){
                dir->mentry = mr->offset;
                dir->mgen = mr->gen;
        }
        dir->size = 0;
        if(_vacfsnextqid(fp->fs, &dir->qid) < 0)
                goto Err;
        dir->uid = vtstrdup(fp->dir.uid);
        dir->gid = vtstrdup(fp->dir.gid);
        dir->mid = vtstrdup("");
        dir->mtime = time(0L);
        dir->mcount = 0;
        dir->ctime = dir->mtime;
        dir->atime = dir->mtime;
        dir->mode = mode;
        if((bo = filemetaalloc(fp, &ff->dir, NilBlock)) == NilBlock)
                goto Err;

        /*
         * Now we're committed.
         */
        vtfileunlock(fp->source);
        vtfileunlock(fp->msource);
        ff->source = r;
        ff->msource = mr;
        ff->boff = bo;

        /* Link into tree. */
        ff->next = fp->down;
        fp->down = ff;
        ff->up = fp;
        vacfileincref(fp);

        fileunlock(fp);
        
        filelock(ff);
        vtfilelock(ff->source, -1);
        vtfileunlock(ff->source);
        fileunlock(ff);

        return ff;

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

/*
 * Change the size of the file f.
 */
int
vacfilesetsize(VacFile *f, uvlong size)
{
        if(vacfileisdir(f)){
                werrstr(ENotFile);
                return -1;
        }
        
        if(filelock(f) < 0)
                return -1;

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

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

/*
 * Write data to f.
 */
int
vacfilewrite(VacFile *f, void *buf, int cnt, vlong offset)
{
        if(vacfileisdir(f)){
                werrstr(ENotFile);
                return -1;
        }
        if(filelock(f) < 0)
                return -1;
        if(f->source->mode != VtORDWR){
                werrstr(EReadOnly);
                goto Err;
        }
        if(offset < 0){
                werrstr(EBadOffset);
                goto Err;
        }

        if(vtfilelock(f->source, -1) < 0)
                goto Err;
        if(f->dir.mode & ModeAppend)
                offset = vtfilegetsize(f->source);
        if(vtfilewrite(f->source, buf, cnt, offset) != cnt
        || vtfileflushbefore(f->source, offset) < 0){
                vtfileunlock(f->source);
                goto Err;
        }
        vtfileunlock(f->source);
        fileunlock(f);
        return cnt;

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

/*
 * Set (!) the VtEntry for the data contained in f.
 * This let's us efficiently copy data from one file to another.
 */
int
vacfilesetentries(VacFile *f, VtEntry *e, VtEntry *me)
{
        int ret;

        vacfileflush(f, 0);     /* flush blocks to venti, since we won't see them again */

        if(!(e->flags&VtEntryActive)){
                werrstr("missing entry for source");
                return -1;
        }
        if(me && !(me->flags&VtEntryActive))
                me = nil;
        if(f->msource && !me){
                werrstr("missing entry for msource");
                return -1;
        }
        if(me && !f->msource){
                werrstr("no msource to set");
                return -1;
        }

        if(filelock(f) < 0)
                return -1;
        if(f->source->mode != VtORDWR
        || (f->msource && f->msource->mode != VtORDWR)){
                werrstr(EReadOnly);
                fileunlock(f);
                return -1;
        }
        if(vtfilelock2(f->source, f->msource, -1) < 0){
                fileunlock(f);
                return -1;
        }
        ret = 0;
        if(vtfilesetentry(f->source, e) < 0)
                ret = -1;
        else if(me && vtfilesetentry(f->msource, me) < 0)
                ret = -1;

        vtfileunlock(f->source);
        if(f->msource)
                vtfileunlock(f->msource);
        fileunlock(f);
        return ret;
}

/*
 * Get the directory entry for f.
 */
int
vacfilegetdir(VacFile *f, VacDir *dir)
{
        if(filerlock(f) < 0)
                return -1;

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

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

        return 0;
}

/*
 * Set the directory entry for f.
 */
int
vacfilesetdir(VacFile *f, VacDir *dir)
{
        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;
        filemetalock(f);
        
        if(f->source->mode != VtORDWR){
                werrstr(EReadOnly);
                goto Err;
        }

        /* On rename, 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;
                }
                werrstr("");    /* "failed" dirlookup poisoned it */
        }

        /* Get ready... */
        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;
                        }
                }
        }
        /* ... now commited to changing it. */
        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);
        }

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

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

        mask = ~(ModeDir|ModeSnapshot);
        f->dir.mode &= ~mask;
        f->dir.mode |= mask & dir->mode;
        f->dirty = 1;

        if(filemetaflush(f, oelem) < 0){
                vtfree(oelem);
                goto Err;       /* that sucks */
        }
        vtfree(oelem);

        filemetaunlock(f);
        fileunlock(f);
        return 0;

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

/*
 * Set the qid space.
 */
int
vacfilesetqidspace(VacFile *f, u64int offset, u64int max)
{
        int ret;

        if(filelock(f) < 0)
                return -1;
        if(f->source->mode != VtORDWR){
                fileunlock(f);
                werrstr(EReadOnly);
                return -1;
        }
        filemetalock(f);
        f->dir.qidspace = 1;
        f->dir.qidoffset = offset;
        f->dir.qidmax = max;
        f->dirty = 1;
        ret = filemetaflush(f, nil);
        filemetaunlock(f);
        fileunlock(f);
        return ret;
}

/*
 * Check that the file is empty, returning 0 if it is.
 * Returns -1 on error (and not being empty is an error).
 */
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, VtOREAD);
                if(b == nil)
                        return -1;
                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;
}

/*
 * Remove the vac file f.
 */
int
vacfileremove(VacFile *f)
{
        VacFile *ff;

        /* Cannot 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) < 0)
                return -1;
        return 0;

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

/*
 * Vac file system format.
 */
static char EBadVacFormat[] = "bad format for vac file";

static VacFs *
vacfsalloc(VtConn *z, int bsize, int ncache, int mode)
{
        VacFs *fs;

        fs = vtmallocz(sizeof(VacFs));
        fs->z = z;
        fs->bsize = bsize;
        fs->mode = mode;
        fs->cache = vtcachealloc(z, bsize, ncache);
        return fs;
}

static int
readscore(int fd, uchar score[VtScoreSize])
{
        char buf[45], *pref;
        int n;

        n = readn(fd, buf, sizeof(buf)-1);
        if(n < sizeof(buf)-1) {
                werrstr("short read");
                return -1;
        }
        buf[n] = 0;

        if(vtparsescore(buf, &pref, score) < 0){
                werrstr(EBadVacFormat);
                return -1;
        }
        if(pref==nil || strcmp(pref, "vac") != 0) {
                werrstr("not a vac file");
                return -1;
        }
        return 0;
}

VacFs*
vacfsopen(VtConn *z, char *file, int mode, int ncache)
{
        int fd;
        uchar score[VtScoreSize];
        char *prefix;
        
        if(vtparsescore(file, &prefix, score) >= 0){
                if(prefix == nil || strcmp(prefix, "vac") != 0){
                        werrstr("not a vac file");
                        return nil;
                }
        }else{
                fd = open(file, OREAD);
                if(fd < 0)
                        return nil;
                if(readscore(fd, score) < 0){
                        close(fd);
                        return nil;
                }
                close(fd);
        }
        return vacfsopenscore(z, score, mode, ncache);
}

VacFs*
vacfsopenscore(VtConn *z, u8int *score, int mode, int ncache)
{
        VacFs *fs;
        int n;
        VtRoot rt;
        uchar buf[VtRootSize];
        VacFile *root;
        VtFile *r;
        VtEntry e;

        n = vtread(z, score, VtRootType, buf, VtRootSize);
        if(n < 0)
                return nil;
        if(n != VtRootSize){
                werrstr("vtread on root too short");
                return nil;
        }

        if(vtrootunpack(&rt, buf) < 0)
                return nil;

        if(strcmp(rt.type, "vac") != 0) {
                werrstr("not a vac root");
                return nil;
        }

        fs = vacfsalloc(z, rt.blocksize, ncache, mode);
        memmove(fs->score, score, VtScoreSize);
        fs->mode = mode;

        memmove(e.score, rt.score, VtScoreSize);
        e.gen = 0;
        e.psize = rt.blocksize;
        e.dsize = rt.blocksize;
        e.type = VtDirType;
        e.flags = VtEntryActive;
        e.size = 3*VtEntrySize;

        root = nil;
        if((r = vtfileopenroot(fs->cache, &e)) == nil)
                goto Err;
        if(debug)
                fprint(2, "r %p\n", r);
        root = _vacfileroot(fs, r);
        if(debug)
                fprint(2, "root %p\n", root);
        vtfileclose(r);
        if(root == nil)
                goto Err;
        fs->root = root;
        return fs;
Err:
        if(root)
                vacfiledecref(root);
        vacfsclose(fs);
        return nil;
}

int
vacfsmode(VacFs *fs)
{
        return fs->mode;
}

VacFile*
vacfsgetroot(VacFs *fs)
{
        return vacfileincref(fs->root);
}

int
vacfsgetblocksize(VacFs *fs)
{
        return fs->bsize;
}

int
vacfsgetscore(VacFs *fs, u8int *score)
{
        memmove(score, fs->score, VtScoreSize);
        return 0;
}

int
_vacfsnextqid(VacFs *fs, uvlong *qid)
{
        ++fs->qid;
        *qid = fs->qid;
        return 0;
}

void
vacfsjumpqid(VacFs *fs, uvlong step)
{
        fs->qid += step;
}

/*
 * Set *maxqid to the maximum qid expected in this file system.
 * In newer vac archives, the maximum qid is stored in the
 * qidspace VacDir annotation.  In older vac archives, the root
 * got created last, so it had the maximum qid.
 */
int
vacfsgetmaxqid(VacFs *fs, uvlong *maxqid)
{
        VacDir vd;
        
        if(vacfilegetdir(fs->root, &vd) < 0)
                return -1;
        if(vd.qidspace)
                *maxqid = vd.qidmax;
        else
                *maxqid = vd.qid;
        vdcleanup(&vd);
        return 0;
}


void
vacfsclose(VacFs *fs)
{
        if(fs->root)
                vacfiledecref(fs->root);
        fs->root = nil;
        vtcachefree(fs->cache);
        vtfree(fs);
}

/*
 * Create a fresh vac fs.
 */
VacFs *
vacfscreate(VtConn *z, int bsize, int ncache)
{
        VacFs *fs;
        VtFile *f;
        uchar buf[VtEntrySize], metascore[VtScoreSize];
        VtEntry e;
        VtBlock *b;
        MetaBlock mb;
        VacDir vd;
        MetaEntry me;
        int psize;
        int mbsize;
        
        if((fs = vacfsalloc(z, bsize, ncache, VtORDWR)) == nil)
                return nil;
        
        /*
         * Fake up an empty vac fs.
         */
        psize = bsize;
        f = vtfilecreateroot(fs->cache, psize, bsize, VtDirType);
        vtfilelock(f, VtORDWR);
        
        /* Metablocks can't be too big -- they have 16-bit offsets in them. */
        mbsize = bsize;
        if(mbsize >= 56*1024)
                mbsize = 56*1024;

        /* Write metablock containing root directory VacDir. */
        b = vtcacheallocblock(fs->cache, VtDataType);
        mbinit(&mb, b->data, mbsize, mbsize/BytesPerEntry);
        memset(&vd, 0, sizeof vd);
        vd.elem = "/";
        vd.mode = 0777|ModeDir;
        vd.uid = "vac";
        vd.gid = "vac";
        vd.mid = "";
        me.size = vdsize(&vd, VacDirVersion);
        me.p = mballoc(&mb, me.size);
        vdpack(&vd, &me, VacDirVersion);
        mbinsert(&mb, 0, &me);
        mbpack(&mb);
        vtblockwrite(b);
        memmove(metascore, b->score, VtScoreSize);
        vtblockput(b);
        
        /* First entry: empty venti directory stream. */
        memset(&e, 0, sizeof e);
        e.flags = VtEntryActive;
        e.psize = psize;
        e.dsize = bsize;
        e.type = VtDirType;
        memmove(e.score, vtzeroscore, VtScoreSize);
        vtentrypack(&e, buf, 0);
        vtfilewrite(f, buf, VtEntrySize, 0);
        
        /* Second entry: empty metadata stream. */
        e.type = VtDataType;
        e.dsize = mbsize;
        vtentrypack(&e, buf, 0);
        vtfilewrite(f, buf, VtEntrySize, VtEntrySize);

        /* Third entry: metadata stream with root directory. */
        memmove(e.score, metascore, VtScoreSize);
        e.size = mbsize;
        vtentrypack(&e, buf, 0);
        vtfilewrite(f, buf, VtEntrySize, VtEntrySize*2);

        vtfileflush(f);
        vtfileunlock(f);
        
        /* Now open it as a vac fs. */
        fs->root = _vacfileroot(fs, f);
        if(fs->root == nil){
                werrstr("vacfileroot: %r");
                vacfsclose(fs);
                return nil;
        }

        return fs;
}

int
vacfssync(VacFs *fs)
{
        uchar buf[1024];
        VtEntry e;
        VtFile *f;
        VtRoot root;

        /* Sync the entire vacfs to disk. */
        if(vacfileflush(fs->root, 1) < 0)
                return -1;
        if(vtfilelock(fs->root->up->msource, -1) < 0)
                return -1;
        if(vtfileflush(fs->root->up->msource) < 0){
                vtfileunlock(fs->root->up->msource);
                return -1;
        }
        vtfileunlock(fs->root->up->msource);

        /* Prepare the dir stream for the root block. */
        if(getentry(fs->root->source, &e) < 0)
                return -1;
        vtentrypack(&e, buf, 0);
        if(getentry(fs->root->msource, &e) < 0)
                return -1;
        vtentrypack(&e, buf, 1);
        if(getentry(fs->root->up->msource, &e) < 0)
                return -1;
        vtentrypack(&e, buf, 2);

        f = vtfilecreateroot(fs->cache, fs->bsize, fs->bsize, VtDirType);
        vtfilelock(f, VtORDWR);
        if(vtfilewrite(f, buf, 3*VtEntrySize, 0) < 0
        || vtfileflush(f) < 0){
                vtfileunlock(f);
                vtfileclose(f);
                return -1;
        }
        vtfileunlock(f);
        if(getentry(f, &e) < 0){
                vtfileclose(f);
                return -1;
        }
        vtfileclose(f);

        /* Build a root block. */
        memset(&root, 0, sizeof root);
        strcpy(root.type, "vac");
        strcpy(root.name, fs->name);
        memmove(root.score, e.score, VtScoreSize);
        root.blocksize = fs->bsize;
        memmove(root.prev, fs->score, VtScoreSize);
        vtrootpack(&root, buf);
        if(vtwrite(fs->z, fs->score, VtRootType, buf, VtRootSize) < 0){
                werrstr("writing root: %r");
                return -1;
        }
        if(vtsync(fs->z) < 0)
                return -1;
        return 0;
}

int
vacfiledsize(VacFile *f)
{
        VtEntry e;

        if(vacfilegetentries(f,&e,nil) < 0)
                return -1;
        return e.dsize;
}

/*
 * Does block b of f have the same SHA1 hash as the n bytes at buf?
 */
int
sha1matches(VacFile *f, ulong b, uchar *buf, int n)
{
        uchar fscore[VtScoreSize];
        uchar bufscore[VtScoreSize];
        
        if(vacfileblockscore(f, b, fscore) < 0)
                return 0;
        n = vtzerotruncate(VtDataType, buf, n);
        sha1(buf, n, bufscore, nil);
        if(memcmp(bufscore, fscore, VtScoreSize) == 0)
                return 1;
        return 0;
}