summaryrefslogtreecommitdiff
path: root/sys/src/cmd/vac/file.c
diff options
context:
space:
mode:
authorTaru Karttunen <taruti@taruti.net>2011-03-30 15:46:40 +0300
committerTaru Karttunen <taruti@taruti.net>2011-03-30 15:46:40 +0300
commite5888a1ffdae813d7575f5fb02275c6bb07e5199 (patch)
treed8d51eac403f07814b9e936eed0c9a79195e2450 /sys/src/cmd/vac/file.c
Import sources from 2011-03-30 iso image
Diffstat (limited to 'sys/src/cmd/vac/file.c')
-rwxr-xr-xsys/src/cmd/vac/file.c2099
1 files changed, 2099 insertions, 0 deletions
diff --git a/sys/src/cmd/vac/file.c b/sys/src/cmd/vac/file.c
new file mode 100755
index 000000000..cc88eabd2
--- /dev/null
+++ b/sys/src/cmd/vac/file.c
@@ -0,0 +1,2099 @@
+#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);
+ }
+
+ 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(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;
+}
+