summaryrefslogtreecommitdiff
path: root/sys/src/cmd/vac
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
Import sources from 2011-03-30 iso image
Diffstat (limited to 'sys/src/cmd/vac')
-rwxr-xr-xsys/src/cmd/vac/dat.h38
-rwxr-xr-xsys/src/cmd/vac/error.c22
-rwxr-xr-xsys/src/cmd/vac/error.h21
-rwxr-xr-xsys/src/cmd/vac/file.c2099
-rwxr-xr-xsys/src/cmd/vac/fns.h30
-rwxr-xr-xsys/src/cmd/vac/glob.c180
-rwxr-xr-xsys/src/cmd/vac/mkfile32
-rwxr-xr-xsys/src/cmd/vac/pack.c736
-rwxr-xr-xsys/src/cmd/vac/stdinc.h8
-rwxr-xr-xsys/src/cmd/vac/testinc.c31
-rwxr-xr-xsys/src/cmd/vac/unvac.c337
-rwxr-xr-xsys/src/cmd/vac/vac.c715
-rwxr-xr-xsys/src/cmd/vac/vac.h145
-rwxr-xr-xsys/src/cmd/vac/vacfs.c788
14 files changed, 5182 insertions, 0 deletions
diff --git a/sys/src/cmd/vac/dat.h b/sys/src/cmd/vac/dat.h
new file mode 100755
index 000000000..000a762a8
--- /dev/null
+++ b/sys/src/cmd/vac/dat.h
@@ -0,0 +1,38 @@
+typedef struct MetaBlock MetaBlock;
+typedef struct MetaEntry MetaEntry;
+
+#define MaxBlock (1UL<<31)
+
+enum {
+ BytesPerEntry = 100, /* estimate of bytes per dir entries - determines number of index entries in the block */
+ FullPercentage = 80, /* don't allocate in block if more than this percentage full */
+ FlushSize = 200, /* number of blocks to flush */
+ DirtyPercentage = 50 /* maximum percentage of dirty blocks */
+};
+
+
+struct MetaEntry
+{
+ uchar *p;
+ ushort size;
+};
+
+struct MetaBlock
+{
+ int maxsize; /* size of block */
+ int size; /* size used */
+ int free; /* free space within used size */
+ int maxindex; /* entries allocated for table */
+ int nindex; /* amount of table used */
+ int unbotch;
+ uchar *buf;
+};
+
+struct VacDirEnum
+{
+ VacFile *file;
+ u32int boff;
+ int i, n;
+ VacDir *buf;
+};
+
diff --git a/sys/src/cmd/vac/error.c b/sys/src/cmd/vac/error.c
new file mode 100755
index 000000000..f28f93760
--- /dev/null
+++ b/sys/src/cmd/vac/error.c
@@ -0,0 +1,22 @@
+#include "stdinc.h"
+#include "vac.h"
+#include "dat.h"
+#include "fns.h"
+#include "error.h"
+
+char ENoDir[] = "directory entry is not allocated";
+char ENoFile[] = "no such file or directory";
+char EBadPath[] = "bad path";
+char EBadDir[] = "corrupted directory entry";
+char EBadMeta[] = "corrupted meta data";
+char ENotDir[] = "not a directory";
+char ENotFile[] = "not a file";
+char EIO[] = "i/o error";
+char EBadOffset[] = "illegal offset";
+char ETooBig[] = "file too big";
+char EReadOnly[] = "read only";
+char ERemoved[] = "file has been removed";
+char ENilBlock[] = "illegal block address";
+char ENotEmpty[] = "directory not empty";
+char EExists[] = "file already exists";
+char ERoot[] = "cannot remove root";
diff --git a/sys/src/cmd/vac/error.h b/sys/src/cmd/vac/error.h
new file mode 100755
index 000000000..3d6c7f8da
--- /dev/null
+++ b/sys/src/cmd/vac/error.h
@@ -0,0 +1,21 @@
+/*
+ * Somehow <errno.h> has been included on Mac OS X
+ */
+#undef EIO
+
+extern char ENoDir[];
+extern char EBadDir[];
+extern char EBadMeta[];
+extern char ENilBlock[];
+extern char ENotDir[];
+extern char ENotFile[];
+extern char EIO[];
+extern char EBadOffset[];
+extern char ETooBig[];
+extern char EReadOnly[];
+extern char ERemoved[];
+extern char ENotEmpty[];
+extern char EExists[];
+extern char ERoot[];
+extern char ENoFile[];
+extern char EBadPath[];
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;
+}
+
diff --git a/sys/src/cmd/vac/fns.h b/sys/src/cmd/vac/fns.h
new file mode 100755
index 000000000..0f691b1e1
--- /dev/null
+++ b/sys/src/cmd/vac/fns.h
@@ -0,0 +1,30 @@
+int mbunpack(MetaBlock *mb, uchar *p, int n);
+void mbinsert(MetaBlock *mb, int i, MetaEntry*);
+void mbdelete(MetaBlock *mb, int i, MetaEntry*);
+void mbpack(MetaBlock *mb);
+uchar *mballoc(MetaBlock *mb, int n);
+void mbinit(MetaBlock *mb, uchar *p, int n, int entries);
+int mbsearch(MetaBlock*, char*, int*, MetaEntry*);
+int mbresize(MetaBlock*, MetaEntry*, int);
+
+int meunpack(MetaEntry*, MetaBlock *mb, int i);
+int mecmp(MetaEntry*, char *s);
+int mecmpnew(MetaEntry*, char *s);
+
+enum {
+ VacDirVersion = 8,
+ FossilDirVersion = 9,
+};
+int vdsize(VacDir *dir, int);
+int vdunpack(VacDir *dir, MetaEntry*);
+void vdpack(VacDir *dir, MetaEntry*, int);
+
+VacFile *_vacfileroot(VacFs *fs, VtFile *file);
+
+int _vacfsnextqid(VacFs *fs, uvlong *qid);
+void vacfsjumpqid(VacFs*, uvlong step);
+
+Reprog* glob2regexp(char*);
+void loadexcludefile(char*);
+int includefile(char*);
+void excludepattern(char*);
diff --git a/sys/src/cmd/vac/glob.c b/sys/src/cmd/vac/glob.c
new file mode 100755
index 000000000..863eb8486
--- /dev/null
+++ b/sys/src/cmd/vac/glob.c
@@ -0,0 +1,180 @@
+#include "stdinc.h"
+#include "vac.h"
+#include "dat.h"
+#include "fns.h"
+#include "error.h"
+
+// Convert globbish pattern to regular expression
+// The wildcards are
+//
+// * any non-slash characters
+// ... any characters including /
+// ? any single character except /
+// [a-z] character class
+// [~a-z] negated character class
+//
+
+Reprog*
+glob2regexp(char *glob)
+{
+ char *s, *p, *w;
+ Reprog *re;
+ int boe; // beginning of path element
+
+ s = malloc(20*(strlen(glob)+1));
+ if(s == nil)
+ return nil;
+ w = s;
+ boe = 1;
+ *w++ = '^';
+ *w++ = '(';
+ for(p=glob; *p; p++){
+ if(p[0] == '.' && p[1] == '.' && p[2] == '.'){
+ strcpy(w, ".*");
+ w += strlen(w);
+ p += 3-1;
+ boe = 0;
+ continue;
+ }
+ if(p[0] == '*'){
+ if(boe)
+ strcpy(w, "([^./][^/]*)?");
+ else
+ strcpy(w, "[^/]*");
+ w += strlen(w);
+ boe = 0;
+ continue;
+ }
+ if(p[0] == '?'){
+ if(boe)
+ strcpy(w, "[^./]");
+ else
+ strcpy(w, "[^/]");
+ w += strlen(w);
+ boe = 0;
+ continue;
+ }
+ if(p[0] == '['){
+ *w++ = '[';
+ if(*++p == '~'){
+ *w++ = '^';
+ p++;
+ }
+ while(*p != ']'){
+ if(*p == '/')
+ goto syntax;
+ if(*p == '^' || *p == '\\')
+ *w++ = '\\';
+ *w++ = *p++;
+ }
+ *w++ = ']';
+ boe = 0;
+ continue;
+ }
+ if(strchr("()|^$[]*?+\\.", *p)){
+ *w++ = '\\';
+ *w++ = *p;
+ boe = 0;
+ continue;
+ }
+ if(*p == '/'){
+ *w++ = '/';
+ boe = 1;
+ continue;
+ }
+ *w++ = *p;
+ boe = 0;
+ continue;
+ }
+ *w++ = ')';
+ *w++ = '$';
+ *w = 0;
+
+ re = regcomp(s);
+ if(re == nil){
+ syntax:
+ free(s);
+ werrstr("glob syntax error");
+ return nil;
+ }
+ free(s);
+ return re;
+}
+
+typedef struct Pattern Pattern;
+struct Pattern
+{
+ Reprog *re;
+ int include;
+};
+
+Pattern *pattern;
+int npattern;
+
+void
+loadexcludefile(char *file)
+{
+ Biobuf *b;
+ char *p, *q;
+ int n, inc;
+ Reprog *re;
+
+ if((b = Bopen(file, OREAD)) == nil)
+ sysfatal("open %s: %r", file);
+ for(n=1; (p=Brdstr(b, '\n', 1)) != nil; free(p), n++){
+ q = p+strlen(p);
+ while(q > p && isspace((uchar)*(q-1)))
+ *--q = 0;
+ switch(p[0]){
+ case '\0':
+ case '#':
+ continue;
+ }
+
+ inc = 0;
+ if(strncmp(p, "include ", 8) == 0){
+ inc = 1;
+ }else if(strncmp(p, "exclude ", 8) == 0){
+ inc = 0;
+ }else
+ sysfatal("%s:%d: line does not begin with include or exclude", file, n);
+
+ if(strchr(p+8, ' '))
+ fprint(2, "%s:%d: warning: space in pattern\n", file, n);
+
+ if((re = glob2regexp(p+8)) == nil)
+ sysfatal("%s:%d: bad glob pattern", file, n);
+
+ pattern = vtrealloc(pattern, (npattern+1)*sizeof pattern[0]);
+ pattern[npattern].re = re;
+ pattern[npattern].include = inc;
+ npattern++;
+ }
+ Bterm(b);
+}
+
+void
+excludepattern(char *p)
+{
+ Reprog *re;
+
+ if((re = glob2regexp(p)) == nil)
+ sysfatal("bad glob pattern %s", p);
+
+ pattern = vtrealloc(pattern, (npattern+1)*sizeof pattern[0]);
+ pattern[npattern].re = re;
+ pattern[npattern].include = 0;
+ npattern++;
+}
+
+int
+includefile(char *file)
+{
+ Pattern *p, *ep;
+
+ for(p=pattern, ep=p+npattern; p<ep; p++)
+ if(regexec(p->re, file, nil, 0))
+ return p->include;
+ return 1;
+}
+
diff --git a/sys/src/cmd/vac/mkfile b/sys/src/cmd/vac/mkfile
new file mode 100755
index 000000000..8572d7aef
--- /dev/null
+++ b/sys/src/cmd/vac/mkfile
@@ -0,0 +1,32 @@
+</$objtype/mkfile
+
+LIBFILES=\
+ error\
+ file\
+ glob\
+ pack\
+
+LIB=${LIBFILES:%=%.$O}
+
+HFILES=\
+ /sys/include/venti.h\
+ stdinc.h\
+ error.h\
+ vac.h\
+ dat.h\
+ fns.h\
+
+TARG=vac unvac vacfs
+BIN=/$objtype/bin
+
+CFILES=${TARG:%=%.c} ${LIBFILES:%=%.c}
+
+UPDATE=\
+ mkfile\
+ $CFILES\
+ $HFILES\
+ ${TARG:%=/386/bin/%}
+
+default:V: all
+
+</sys/src/cmd/mkmany
diff --git a/sys/src/cmd/vac/pack.c b/sys/src/cmd/vac/pack.c
new file mode 100755
index 000000000..9777464f6
--- /dev/null
+++ b/sys/src/cmd/vac/pack.c
@@ -0,0 +1,736 @@
+#include "stdinc.h"
+#include "vac.h"
+#include "dat.h"
+#include "fns.h"
+#include "error.h"
+
+typedef struct MetaChunk MetaChunk;
+
+struct MetaChunk {
+ ushort offset;
+ ushort size;
+ ushort index;
+};
+
+static int stringunpack(char **s, uchar **p, int *n);
+
+/*
+ * integer conversion routines
+ */
+#define U8GET(p) ((p)[0])
+#define U16GET(p) (((p)[0]<<8)|(p)[1])
+#define U32GET(p) (((p)[0]<<24)|((p)[1]<<16)|((p)[2]<<8)|(p)[3])
+#define U48GET(p) (((uvlong)U16GET(p)<<32)|(uvlong)U32GET((p)+2))
+#define U64GET(p) (((uvlong)U32GET(p)<<32)|(uvlong)U32GET((p)+4))
+
+#define U8PUT(p,v) (p)[0]=(v)&0xFF
+#define U16PUT(p,v) (p)[0]=((v)>>8)&0xFF;(p)[1]=(v)&0xFF
+#define U32PUT(p,v) (p)[0]=((v)>>24)&0xFF;(p)[1]=((v)>>16)&0xFF;(p)[2]=((v)>>8)&0xFF;(p)[3]=(v)&0xFF
+#define U48PUT(p,v,t32) t32=(v)>>32;U16PUT(p,t32);t32=(v);U32PUT((p)+2,t32)
+#define U64PUT(p,v,t32) t32=(v)>>32;U32PUT(p,t32);t32=(v);U32PUT((p)+4,t32)
+
+static int
+stringunpack(char **s, uchar **p, int *n)
+{
+ int nn;
+
+ if(*n < 2)
+ return -1;
+
+ nn = U16GET(*p);
+ *p += 2;
+ *n -= 2;
+ if(nn > *n)
+ return -1;
+ *s = vtmalloc(nn+1);
+ memmove(*s, *p, nn);
+ (*s)[nn] = 0;
+ *p += nn;
+ *n -= nn;
+ return 0;
+}
+
+static int
+stringpack(char *s, uchar *p)
+{
+ int n;
+
+ n = strlen(s);
+ U16PUT(p, n);
+ memmove(p+2, s, n);
+ return n+2;
+}
+
+
+int
+mbunpack(MetaBlock *mb, uchar *p, int n)
+{
+ u32int magic;
+
+ mb->maxsize = n;
+ mb->buf = p;
+
+ if(n == 0) {
+ memset(mb, 0, sizeof(MetaBlock));
+ return 0;
+ }
+
+ magic = U32GET(p);
+ if(magic != MetaMagic && magic != MetaMagic+1) {
+ werrstr("bad meta block magic");
+ return -1;
+ }
+ mb->size = U16GET(p+4);
+ mb->free = U16GET(p+6);
+ mb->maxindex = U16GET(p+8);
+ mb->nindex = U16GET(p+10);
+ mb->unbotch = (magic == MetaMagic+1);
+
+ if(mb->size > n) {
+ werrstr("bad meta block size");
+ return -1;
+ }
+ p += MetaHeaderSize;
+ n -= MetaHeaderSize;
+
+ USED(p);
+ if(n < mb->maxindex*MetaIndexSize) {
+ werrstr("truncated meta block 2");
+ return -1;
+ }
+ return 0;
+}
+
+void
+mbpack(MetaBlock *mb)
+{
+ uchar *p;
+
+ p = mb->buf;
+
+ U32PUT(p, MetaMagic);
+ U16PUT(p+4, mb->size);
+ U16PUT(p+6, mb->free);
+ U16PUT(p+8, mb->maxindex);
+ U16PUT(p+10, mb->nindex);
+}
+
+
+void
+mbdelete(MetaBlock *mb, int i, MetaEntry *me)
+{
+ uchar *p;
+ int n;
+
+ assert(i < mb->nindex);
+
+ if(me->p - mb->buf + me->size == mb->size)
+ mb->size -= me->size;
+ else
+ mb->free += me->size;
+
+ p = mb->buf + MetaHeaderSize + i*MetaIndexSize;
+ n = (mb->nindex-i-1)*MetaIndexSize;
+ memmove(p, p+MetaIndexSize, n);
+ memset(p+n, 0, MetaIndexSize);
+ mb->nindex--;
+}
+
+void
+mbinsert(MetaBlock *mb, int i, MetaEntry *me)
+{
+ uchar *p;
+ int o, n;
+
+ assert(mb->nindex < mb->maxindex);
+
+ o = me->p - mb->buf;
+ n = me->size;
+ if(o+n > mb->size) {
+ mb->free -= mb->size - o;
+ mb->size = o + n;
+ } else
+ mb->free -= n;
+
+ p = mb->buf + MetaHeaderSize + i*MetaIndexSize;
+ n = (mb->nindex-i)*MetaIndexSize;
+ memmove(p+MetaIndexSize, p, n);
+ U16PUT(p, me->p - mb->buf);
+ U16PUT(p+2, me->size);
+ mb->nindex++;
+}
+
+int
+meunpack(MetaEntry *me, MetaBlock *mb, int i)
+{
+ uchar *p;
+ int eo, en;
+
+ if(i < 0 || i >= mb->nindex) {
+ werrstr("bad meta entry index");
+ return -1;
+ }
+
+ p = mb->buf + MetaHeaderSize + i*MetaIndexSize;
+ eo = U16GET(p);
+ en = U16GET(p+2);
+
+if(0)print("eo = %d en = %d\n", eo, en);
+ if(eo < MetaHeaderSize + mb->maxindex*MetaIndexSize) {
+ werrstr("corrupted entry in meta block");
+ return -1;
+ }
+
+ if(eo+en > mb->size) {
+ werrstr("truncated meta block");
+ return -1;
+ }
+
+ p = mb->buf + eo;
+
+ /* make sure entry looks ok and includes an elem name */
+ if(en < 8 || U32GET(p) != DirMagic || en < 8 + U16GET(p+6)) {
+ werrstr("corrupted meta block entry");
+ return -1;
+ }
+
+ me->p = p;
+ me->size = en;
+
+ return 0;
+}
+
+/* assumes a small amount of checking has been done in mbentry */
+int
+mecmp(MetaEntry *me, char *s)
+{
+ int n;
+ uchar *p;
+
+ p = me->p;
+
+ p += 6;
+ n = U16GET(p);
+ p += 2;
+
+ assert(n + 8 < me->size);
+
+ while(n > 0) {
+ if(*s == 0)
+ return -1;
+ if(*p < (uchar)*s)
+ return -1;
+ if(*p > (uchar)*s)
+ return 1;
+ p++;
+ s++;
+ n--;
+ }
+ return *s != 0;
+}
+
+int
+mecmpnew(MetaEntry *me, char *s)
+{
+ int n;
+ uchar *p;
+
+ p = me->p;
+
+ p += 6;
+ n = U16GET(p);
+ p += 2;
+
+ assert(n + 8 < me->size);
+
+ while(n > 0) {
+ if(*s == 0)
+ return 1;
+ if(*p < (uchar)*s)
+ return -1;
+ if(*p > (uchar)*s)
+ return 1;
+ p++;
+ s++;
+ n--;
+ }
+ return -(*s != 0);
+}
+
+static int
+offsetcmp(const void *s0, const void *s1)
+{
+ const MetaChunk *mc0, *mc1;
+
+ mc0 = s0;
+ mc1 = s1;
+ if(mc0->offset < mc1->offset)
+ return -1;
+ if(mc0->offset > mc1->offset)
+ return 1;
+ return 0;
+}
+
+static MetaChunk *
+metachunks(MetaBlock *mb)
+{
+ MetaChunk *mc;
+ int oo, o, n, i;
+ uchar *p;
+
+ mc = vtmalloc(mb->nindex*sizeof(MetaChunk));
+ p = mb->buf + MetaHeaderSize;
+ for(i = 0; i<mb->nindex; i++) {
+ mc[i].offset = U16GET(p);
+ mc[i].size = U16GET(p+2);
+ mc[i].index = i;
+ p += MetaIndexSize;
+ }
+
+ qsort(mc, mb->nindex, sizeof(MetaChunk), offsetcmp);
+
+ /* check block looks ok */
+ oo = MetaHeaderSize + mb->maxindex*MetaIndexSize;
+ o = oo;
+ n = 0;
+ for(i=0; i<mb->nindex; i++) {
+ o = mc[i].offset;
+ n = mc[i].size;
+ if(o < oo)
+ goto Err;
+ oo += n;
+ }
+ if(o+n <= mb->size)
+ goto Err;
+ if(mb->size - oo != mb->free)
+ goto Err;
+
+ return mc;
+Err:
+ vtfree(mc);
+ return nil;
+}
+
+static void
+mbcompact(MetaBlock *mb, MetaChunk *mc)
+{
+ int oo, o, n, i;
+
+ oo = MetaHeaderSize + mb->maxindex*MetaIndexSize;
+
+ for(i=0; i<mb->nindex; i++) {
+ o = mc[i].offset;
+ n = mc[i].size;
+ if(o != oo) {
+ memmove(mb->buf + oo, mb->buf + o, n);
+ U16PUT(mb->buf + MetaHeaderSize + mc[i].index*MetaIndexSize, oo);
+ }
+ oo += n;
+ }
+
+ mb->size = oo;
+ mb->free = 0;
+}
+
+uchar *
+mballoc(MetaBlock *mb, int n)
+{
+ int i, o;
+ MetaChunk *mc;
+
+ /* off the end */
+ if(mb->maxsize - mb->size >= n)
+ return mb->buf + mb->size;
+
+ /* check if possible */
+ if(mb->maxsize - mb->size + mb->free < n)
+ return nil;
+
+ mc = metachunks(mb);
+
+ /* look for hole */
+ o = MetaHeaderSize + mb->maxindex*MetaIndexSize;
+ for(i=0; i<mb->nindex; i++) {
+ if(mc[i].offset - o >= n) {
+ vtfree(mc);
+ return mb->buf + o;
+ }
+ o = mc[i].offset + mc[i].size;
+ }
+
+ if(mb->maxsize - o >= n) {
+ vtfree(mc);
+ return mb->buf + o;
+ }
+
+ /* compact and return off the end */
+ mbcompact(mb, mc);
+ vtfree(mc);
+
+ assert(mb->maxsize - mb->size >= n);
+ return mb->buf + mb->size;
+}
+
+int
+vdsize(VacDir *dir, int version)
+{
+ int n;
+
+ if(version < 8 || version > 9)
+ sysfatal("bad version %d in vdpack", version);
+
+ /* constant part */
+ n = 4 + /* magic */
+ 2 + /* version */
+ 4 + /* entry */
+ 8 + /* qid */
+ 4 + /* mtime */
+ 4 + /* mcount */
+ 4 + /* ctime */
+ 4 + /* atime */
+ 4 + /* mode */
+ 0;
+
+ if(version == 9){
+ n += 4 + /* gen */
+ 4 + /* mentry */
+ 4 + /* mgen */
+ 0;
+ }
+
+ /* strings */
+ n += 2 + strlen(dir->elem);
+ n += 2 + strlen(dir->uid);
+ n += 2 + strlen(dir->gid);
+ n += 2 + strlen(dir->mid);
+
+ /* optional sections */
+ if(version < 9 && dir->plan9) {
+ n += 3 + /* option header */
+ 8 + /* path */
+ 4; /* version */
+ }
+ if(dir->qidspace) {
+ n += 3 + /* option header */
+ 8 + /* qid offset */
+ 8; /* qid max */
+ }
+ if(version < 9 && dir->gen) {
+ n += 3 + /* option header */
+ 4; /* gen */
+ }
+
+ return n;
+}
+
+void
+vdpack(VacDir *dir, MetaEntry *me, int version)
+{
+ uchar *p;
+ ulong t32;
+
+ if(version < 8 || version > 9)
+ sysfatal("bad version %d in vdpack", version);
+
+ p = me->p;
+
+ U32PUT(p, DirMagic);
+ U16PUT(p+4, version); /* version */
+ p += 6;
+
+ p += stringpack(dir->elem, p);
+
+ U32PUT(p, dir->entry);
+ p += 4;
+
+ if(version == 9){
+ U32PUT(p, dir->gen);
+ U32PUT(p+4, dir->mentry);
+ U32PUT(p+8, dir->mgen);
+ p += 12;
+ }
+
+ U64PUT(p, dir->qid, t32);
+ p += 8;
+
+ p += stringpack(dir->uid, p);
+ p += stringpack(dir->gid, p);
+ p += stringpack(dir->mid, p);
+
+ U32PUT(p, dir->mtime);
+ U32PUT(p+4, dir->mcount);
+ U32PUT(p+8, dir->ctime);
+ U32PUT(p+12, dir->atime);
+ U32PUT(p+16, dir->mode);
+ p += 5*4;
+
+ if(dir->plan9 && version < 9) {
+ U8PUT(p, DirPlan9Entry);
+ U16PUT(p+1, 8+4);
+ p += 3;
+ U64PUT(p, dir->p9path, t32);
+ U32PUT(p+8, dir->p9version);
+ p += 12;
+ }
+
+ if(dir->qidspace) {
+ U8PUT(p, DirQidSpaceEntry);
+ U16PUT(p+1, 2*8);
+ p += 3;
+ U64PUT(p, dir->qidoffset, t32);
+ U64PUT(p+8, dir->qidmax, t32);
+ p += 16;
+ }
+
+ if(dir->gen && version < 9) {
+ U8PUT(p, DirGenEntry);
+ U16PUT(p+1, 4);
+ p += 3;
+ U32PUT(p, dir->gen);
+ p += 4;
+ }
+
+ assert(p == me->p + me->size);
+}
+
+int
+vdunpack(VacDir *dir, MetaEntry *me)
+{
+ int t, nn, n, version;
+ uchar *p;
+
+ p = me->p;
+ n = me->size;
+
+ memset(dir, 0, sizeof(VacDir));
+
+ /* magic */
+ if(n < 4 || U32GET(p) != DirMagic)
+ goto Err;
+ p += 4;
+ n -= 4;
+
+ /* version */
+ if(n < 2)
+ goto Err;
+ version = U16GET(p);
+ if(version < 7 || version > 9)
+ goto Err;
+ p += 2;
+ n -= 2;
+
+ /* elem */
+ if(stringunpack(&dir->elem, &p, &n) < 0)
+ goto Err;
+
+ /* entry */
+ if(n < 4)
+ goto Err;
+ dir->entry = U32GET(p);
+ p += 4;
+ n -= 4;
+
+ if(version < 9) {
+ dir->gen = 0;
+ dir->mentry = dir->entry+1;
+ dir->mgen = 0;
+ } else {
+ if(n < 3*4)
+ goto Err;
+ dir->gen = U32GET(p);
+ dir->mentry = U32GET(p+4);
+ dir->mgen = U32GET(p+8);
+ p += 3*4;
+ n -= 3*4;
+ }
+
+ /* size is gotten from DirEntry */
+
+ /* qid */
+ if(n < 8)
+ goto Err;
+ dir->qid = U64GET(p);
+ p += 8;
+ n -= 8;
+
+ /* skip replacement */
+ if(version == 7) {
+ if(n < VtScoreSize)
+ goto Err;
+ p += VtScoreSize;
+ n -= VtScoreSize;
+ }
+
+ /* uid */
+ if(stringunpack(&dir->uid, &p, &n) < 0)
+ goto Err;
+
+ /* gid */
+ if(stringunpack(&dir->gid, &p, &n) < 0)
+ goto Err;
+
+ /* mid */
+ if(stringunpack(&dir->mid, &p, &n) < 0)
+ goto Err;
+
+ if(n < 5*4)
+ goto Err;
+ dir->mtime = U32GET(p);
+ dir->mcount = U32GET(p+4);
+ dir->ctime = U32GET(p+8);
+ dir->atime = U32GET(p+12);
+ dir->mode = U32GET(p+16);
+ p += 5*4;
+ n -= 5*4;
+
+ /* optional meta data */
+ while(n > 0) {
+ if(n < 3)
+ goto Err;
+ t = p[0];
+ nn = U16GET(p+1);
+ p += 3;
+ n -= 3;
+ if(n < nn)
+ goto Err;
+ switch(t) {
+ case DirPlan9Entry:
+ /* not valid in version >= 9 */
+ if(version >= 9)
+ break;
+ if(dir->plan9 || nn != 12)
+ goto Err;
+ dir->plan9 = 1;
+ dir->p9path = U64GET(p);
+ dir->p9version = U32GET(p+8);
+ if(dir->mcount == 0)
+ dir->mcount = dir->p9version;
+ break;
+ case DirGenEntry:
+ /* not valid in version >= 9 */
+ if(version >= 9)
+ break;
+ break;
+ case DirQidSpaceEntry:
+ if(dir->qidspace || nn != 16)
+ goto Err;
+ dir->qidspace = 1;
+ dir->qidoffset = U64GET(p);
+ dir->qidmax = U64GET(p+8);
+ break;
+ }
+ p += nn;
+ n -= nn;
+ }
+
+ if(p != me->p + me->size)
+ goto Err;
+
+ return 0;
+Err:
+ werrstr(EBadMeta);
+ vdcleanup(dir);
+ return -1;
+}
+
+void
+vdcleanup(VacDir *dir)
+{
+ vtfree(dir->elem);
+ dir->elem = nil;
+ vtfree(dir->uid);
+ dir->uid = nil;
+ vtfree(dir->gid);
+ dir->gid = nil;
+ vtfree(dir->mid);
+ dir->mid = nil;
+}
+
+void
+vdcopy(VacDir *dst, VacDir *src)
+{
+ *dst = *src;
+ dst->elem = vtstrdup(dst->elem);
+ dst->uid = vtstrdup(dst->uid);
+ dst->gid = vtstrdup(dst->gid);
+ dst->mid = vtstrdup(dst->mid);
+}
+
+int
+mbsearch(MetaBlock *mb, char *elem, int *ri, MetaEntry *me)
+{
+ int i;
+ int b, t, x;
+
+ /* binary search within block */
+ b = 0;
+ t = mb->nindex;
+ while(b < t) {
+ i = (b+t)>>1;
+ if(meunpack(me, mb, i) < 0)
+ return 0;
+ if(mb->unbotch)
+ x = mecmpnew(me, elem);
+ else
+ x = mecmp(me, elem);
+
+ if(x == 0) {
+ *ri = i;
+ return 1;
+ }
+
+ if(x < 0)
+ b = i+1;
+ else /* x > 0 */
+ t = i;
+ }
+
+ assert(b == t);
+
+ *ri = b; /* b is the index to insert this entry */
+ memset(me, 0, sizeof(*me));
+
+ return -1;
+}
+
+void
+mbinit(MetaBlock *mb, uchar *p, int n, int entries)
+{
+ memset(mb, 0, sizeof(MetaBlock));
+ mb->maxsize = n;
+ mb->buf = p;
+ mb->maxindex = entries;
+ mb->size = MetaHeaderSize + mb->maxindex*MetaIndexSize;
+}
+
+int
+mbresize(MetaBlock *mb, MetaEntry *me, int n)
+{
+ uchar *p, *ep;
+
+ /* easy case */
+ if(n <= me->size){
+ me->size = n;
+ return 0;
+ }
+
+ /* try and expand entry */
+
+ p = me->p + me->size;
+ ep = mb->buf + mb->maxsize;
+ while(p < ep && *p == 0)
+ p++;
+ if(n <= p - me->p){
+ me->size = n;
+ return 0;
+ }
+
+ p = mballoc(mb, n);
+ if(p != nil){
+ me->p = p;
+ me->size = n;
+ return 0;
+ }
+
+ return -1;
+}
diff --git a/sys/src/cmd/vac/stdinc.h b/sys/src/cmd/vac/stdinc.h
new file mode 100755
index 000000000..070c56b3f
--- /dev/null
+++ b/sys/src/cmd/vac/stdinc.h
@@ -0,0 +1,8 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <ctype.h>
+#include <thread.h>
+#include <venti.h>
+#include <libsec.h>
+#include <regexp.h>
diff --git a/sys/src/cmd/vac/testinc.c b/sys/src/cmd/vac/testinc.c
new file mode 100755
index 000000000..0cd431434
--- /dev/null
+++ b/sys/src/cmd/vac/testinc.c
@@ -0,0 +1,31 @@
+#include "stdinc.h"
+#include "vac.h"
+#include "dat.h"
+#include "fns.h"
+#include "error.h"
+
+void
+threadmain(int argc, char **argv)
+{
+ Biobuf b;
+ char *p;
+
+ ARGBEGIN{
+ default:
+ goto usage;
+ }ARGEND
+
+ if(argc != 1){
+ usage:
+ fprint(2, "usage: testinc includefile\n");
+ threadexitsall("usage");
+ }
+
+ loadexcludefile(argv[0]);
+ Binit(&b, 0, OREAD);
+ while((p = Brdline(&b, '\n')) != nil){
+ p[Blinelen(&b)-1] = 0;
+ print("%d %s\n", includefile(p), p);
+ }
+ threadexitsall(0);
+}
diff --git a/sys/src/cmd/vac/unvac.c b/sys/src/cmd/vac/unvac.c
new file mode 100755
index 000000000..b197defb7
--- /dev/null
+++ b/sys/src/cmd/vac/unvac.c
@@ -0,0 +1,337 @@
+#include "stdinc.h"
+#include <fcall.h> /* dirmodefmt */
+#include "vac.h"
+
+#pragma varargck type "t" ulong
+
+VacFs *fs;
+int tostdout;
+int diff;
+int nwant;
+char **want;
+int *found;
+int chatty;
+VtConn *conn;
+int errors;
+int settimes;
+int table;
+
+int mtimefmt(Fmt*);
+void unvac(VacFile*, char*, VacDir*);
+
+void
+usage(void)
+{
+ fprint(2, "usage: unvac [-TVcdtv] [-h host] file.vac [file ...]\n");
+ threadexitsall("usage");
+}
+
+struct
+{
+ vlong data;
+ vlong skipdata;
+} stats;
+
+void
+threadmain(int argc, char *argv[])
+{
+ int i, printstats;
+ char *host;
+ VacFile *f;
+
+ fmtinstall('H', encodefmt);
+ fmtinstall('V', vtscorefmt);
+ fmtinstall('F', vtfcallfmt);
+ fmtinstall('t', mtimefmt);
+ fmtinstall('M', dirmodefmt);
+
+ host = nil;
+ printstats = 0;
+
+ ARGBEGIN{
+ case 'T':
+ settimes = 1;
+ break;
+ case 'V':
+ chattyventi = 1;
+ break;
+ case 'c':
+ tostdout++;
+ break;
+ case 'd':
+ diff++;
+ break;
+ case 'h':
+ host = EARGF(usage());
+ break;
+ case 's':
+ printstats++;
+ break;
+ case 't':
+ table++;
+ break;
+ case 'v':
+ chatty++;
+ break;
+ default:
+ usage();
+ }ARGEND
+
+ if(argc < 1)
+ usage();
+
+ if(tostdout && diff){
+ fprint(2, "cannot use -c with -d\n");
+ usage();
+ }
+
+ conn = vtdial(host);
+ if(conn == nil)
+ sysfatal("could not connect to server: %r");
+
+ if(vtconnect(conn) < 0)
+ sysfatal("vtconnect: %r");
+
+ fs = vacfsopen(conn, argv[0], VtOREAD, 128);
+ if(fs == nil)
+ sysfatal("vacfsopen: %r");
+
+ nwant = argc-1;
+ want = argv+1;
+ found = vtmallocz(nwant*sizeof found[0]);
+
+ if((f = vacfsgetroot(fs)) == nil)
+ sysfatal("vacfsgetroot: %r");
+
+ unvac(f, nil, nil);
+ for(i=0; i<nwant; i++){
+ if(want[i] && !found[i]){
+ fprint(2, "warning: didn't find %s\n", want[i]);
+ errors++;
+ }
+ }
+ if(errors)
+ threadexitsall("errors");
+ if(printstats)
+ fprint(2, "%lld bytes read, %lld bytes skipped\n",
+ stats.data, stats.skipdata);
+ threadexitsall(0);
+}
+
+int
+writen(int fd, char *buf, int n)
+{
+ int m;
+ int oldn;
+
+ oldn = n;
+ while(n > 0){
+ m = write(fd, buf, n);
+ if(m <= 0)
+ return -1;
+ buf += m;
+ n -= m;
+ }
+ return oldn;
+}
+
+int
+wantfile(char *name)
+{
+ int i, namelen, n;
+
+ if(nwant == 0)
+ return 1;
+
+ namelen = strlen(name);
+ for(i=0; i<nwant; i++){
+ if(want[i] == nil)
+ continue;
+ n = strlen(want[i]);
+ if(n < namelen && name[n] == '/' && memcmp(name, want[i], n) == 0)
+ return 1;
+ if(namelen < n && want[i][namelen] == '/' && memcmp(want[i], name, namelen) == 0)
+ return 1;
+ if(n == namelen && memcmp(name, want[i], n) == 0){
+ found[i] = 1;
+ return 1;
+ }
+ }
+ return 0;
+}
+
+void
+unvac(VacFile *f, char *name, VacDir *vdir)
+{
+ static char buf[65536];
+ int fd, n, m, bsize;
+ ulong mode, mode9;
+ char *newname;
+ char *what;
+ vlong off;
+ Dir d, *dp;
+ VacDirEnum *vde;
+ VacDir newvdir;
+ VacFile *newf;
+
+ if(vdir)
+ mode = vdir->mode;
+ else
+ mode = vacfilegetmode(f);
+
+ if(vdir){
+ if(table){
+ if(chatty){
+ mode9 = vdir->mode&0777;
+ if(mode&ModeDir)
+ mode9 |= DMDIR;
+ if(mode&ModeAppend)
+ mode9 |= DMAPPEND;
+ if(mode&ModeExclusive)
+ mode9 |= DMEXCL;
+ print("%M %-10s %-10s %11lld %t %s\n",
+ mode9, vdir->uid, vdir->gid, vdir->size,
+ vdir->mtime, name);
+ }else
+ print("%s%s\n", name, (mode&ModeDir) ? "/" : "");
+ }
+ else if(chatty)
+ fprint(2, "%s%s\n", name, (mode&ModeDir) ? "/" : "");
+ }
+
+ if(mode&(ModeDevice|ModeLink|ModeNamedPipe|ModeExclusive)){
+ if(table)
+ return;
+ if(mode&ModeDevice)
+ what = "device";
+ else if(mode&ModeLink)
+ what = "link";
+ else if(mode&ModeNamedPipe)
+ what = "named pipe";
+ else if(mode&ModeExclusive)
+ what = "lock";
+ else
+ what = "unknown type of file";
+ fprint(2, "warning: ignoring %s %s\n", what, name);
+ return;
+ }
+
+ if(mode&ModeDir){
+ if((vde = vdeopen(f)) == nil){
+ fprint(2, "vdeopen %s: %r", name);
+ errors++;
+ return;
+ }
+ if(!table && !tostdout && vdir){
+ // create directory
+ if((dp = dirstat(name)) == nil){
+ if((fd = create(name, OREAD, DMDIR|(mode&0777))) < 0){
+ fprint(2, "mkdir %s: %r\n", name);
+ vdeclose(vde);
+ }
+ close(fd);
+ }else{
+ if(!(dp->mode&DMDIR)){
+ fprint(2, "%s already exists and is not a directory\n", name);
+ errors++;
+ free(dp);
+ vdeclose(vde);
+ return;
+ }
+ free(dp);
+ }
+ }
+ while(vderead(vde, &newvdir) > 0){
+ if(name == nil)
+ newname = newvdir.elem;
+ else
+ newname = smprint("%s/%s", name, newvdir.elem);
+ if(wantfile(newname)){
+ if((newf = vacfilewalk(f, newvdir.elem)) == nil){
+ fprint(2, "walk %s: %r\n", name);
+ errors++;
+ }else if(newf == f){
+ fprint(2, "walk loop: %s\n", newname);
+ vacfiledecref(newf);
+ }else{
+ unvac(newf, newname, &newvdir);
+ vacfiledecref(newf);
+ }
+ }
+ if(newname != newvdir.elem)
+ free(newname);
+ vdcleanup(&newvdir);
+ }
+ vdeclose(vde);
+ }else{
+ if(!table){
+ off = 0;
+ if(tostdout)
+ fd = dup(1, -1);
+ else if(diff && (fd = open(name, ORDWR)) >= 0){
+ bsize = vacfiledsize(f);
+ while((n = readn(fd, buf, bsize)) > 0){
+ if(sha1matches(f, off/bsize, (uchar*)buf, n)){
+ off += n;
+ stats.skipdata += n;
+ continue;
+ }
+ seek(fd, off, 0);
+ if((m = vacfileread(f, buf, n, off)) < 0)
+ break;
+ if(writen(fd, buf, m) != m){
+ fprint(2, "write %s: %r\n", name);
+ goto Err;
+ }
+ off += m;
+ stats.data += m;
+ if(m < n){
+ nulldir(&d);
+ d.length = off;
+ if(dirfwstat(fd, &d) < 0){
+ fprint(2, "dirfwstat %s: %r\n", name);
+ goto Err;
+ }
+ break;
+ }
+ }
+ }
+ else if((fd = create(name, OWRITE, mode&0777)) < 0){
+ fprint(2, "create %s: %r\n", name);
+ errors++;
+ return;
+ }
+ while((n = vacfileread(f, buf, sizeof buf, off)) > 0){
+ if(writen(fd, buf, n) != n){
+ fprint(2, "write %s: %r\n", name);
+ Err:
+ errors++;
+ close(fd);
+ remove(name);
+ return;
+ }
+ off += n;
+ stats.data += n;
+ }
+ close(fd);
+ }
+ }
+ if(vdir && settimes && !tostdout){
+ nulldir(&d);
+ d.mtime = vdir->mtime;
+ if(dirwstat(name, &d) < 0)
+ fprint(2, "warning: setting mtime on %s: %r", name);
+ }
+}
+
+int
+mtimefmt(Fmt *f)
+{
+ Tm *tm;
+
+ tm = localtime(va_arg(f->args, ulong));
+ fmtprint(f, "%04d-%02d-%02d %02d:%02d",
+ tm->year+1900, tm->mon+1, tm->mday,
+ tm->hour, tm->min);
+ return 0;
+}
diff --git a/sys/src/cmd/vac/vac.c b/sys/src/cmd/vac/vac.c
new file mode 100755
index 000000000..5a8bae7d7
--- /dev/null
+++ b/sys/src/cmd/vac/vac.c
@@ -0,0 +1,715 @@
+#include "stdinc.h"
+#include "vac.h"
+#include "dat.h"
+#include "fns.h"
+
+// TODO: qids
+
+void
+usage(void)
+{
+ fprint(2, "vac [-imqsv] [-a archive.vac] [-b bsize] [-d old.vac] [-f new.vac] [-e exclude]... [-h host] file...\n");
+ threadexitsall("usage");
+}
+
+enum
+{
+ BlockSize = 8*1024,
+};
+
+struct
+{
+ int nfile;
+ int ndir;
+ vlong data;
+ vlong skipdata;
+ int skipfiles;
+} stats;
+
+int qdiff;
+int merge;
+int verbose;
+char *host;
+VtConn *z;
+VacFs *fs;
+char *archivefile;
+char *vacfile;
+
+int vacmerge(VacFile*, char*);
+void vac(VacFile*, VacFile*, char*, Dir*);
+void vacstdin(VacFile*, char*);
+VacFile *recentarchive(VacFs*, char*);
+
+static u64int unittoull(char*);
+static void warn(char *fmt, ...);
+static void removevacfile(void);
+
+void
+threadmain(int argc, char **argv)
+{
+ int i, j, fd, n, printstats;
+ Dir *d;
+ char *s;
+ uvlong u;
+ VacFile *f, *fdiff;
+ VacFs *fsdiff;
+ int blocksize;
+ int outfd;
+ char *stdinname;
+ char *diffvac;
+ uvlong qid;
+
+
+ fmtinstall('F', vtfcallfmt);
+ fmtinstall('H', encodefmt);
+ fmtinstall('V', vtscorefmt);
+
+ blocksize = BlockSize;
+ stdinname = nil;
+ printstats = 0;
+ fsdiff = nil;
+ diffvac = nil;
+
+ ARGBEGIN{
+ case 'V':
+ chattyventi++;
+ break;
+ case 'a':
+ archivefile = EARGF(usage());
+ break;
+ case 'b':
+ u = unittoull(EARGF(usage()));
+ if(u < 512)
+ u = 512;
+ if(u > VtMaxLumpSize)
+ u = VtMaxLumpSize;
+ blocksize = u;
+ break;
+ case 'd':
+ diffvac = EARGF(usage());
+ break;
+ case 'e':
+ excludepattern(EARGF(usage()));
+ break;
+ case 'f':
+ vacfile = EARGF(usage());
+ break;
+ case 'h':
+ host = EARGF(usage());
+ break;
+ case 'i':
+ stdinname = EARGF(usage());
+ break;
+ case 'm':
+ merge++;
+ break;
+ case 'q':
+ qdiff++;
+ break;
+ case 's':
+ printstats++;
+ break;
+ case 'v':
+ verbose++;
+ break;
+ case 'x':
+ loadexcludefile(EARGF(usage()));
+ break;
+ default:
+ usage();
+ }ARGEND
+
+ if(argc == 0 && !stdinname)
+ usage();
+
+ if(archivefile && (vacfile || diffvac)){
+ fprint(2, "cannot use -a with -f, -d\n");
+ usage();
+ }
+
+ z = vtdial(host);
+ if(z == nil)
+ sysfatal("could not connect to server: %r");
+ if(vtconnect(z) < 0)
+ sysfatal("vtconnect: %r");
+
+ // Setup:
+ // fs is the output vac file system
+ // f is directory in output vac to write new files
+ // fdiff is corresponding directory in existing vac
+ if(archivefile){
+ VacFile *fp;
+ char yyyy[5];
+ char mmdd[10];
+ char oldpath[40];
+ Tm tm;
+
+ fdiff = nil;
+ if((outfd = open(archivefile, ORDWR)) < 0){
+ if(access(archivefile, 0) >= 0)
+ sysfatal("open %s: %r", archivefile);
+ if((outfd = create(archivefile, OWRITE, 0666)) < 0)
+ sysfatal("create %s: %r", archivefile);
+ atexit(removevacfile); // because it is new
+ if((fs = vacfscreate(z, blocksize, 512)) == nil)
+ sysfatal("vacfscreate: %r");
+ }else{
+ if((fs = vacfsopen(z, archivefile, VtORDWR, 512)) == nil)
+ sysfatal("vacfsopen %s: %r", archivefile);
+ if((fdiff = recentarchive(fs, oldpath)) != nil){
+ if(verbose)
+ fprint(2, "diff %s\n", oldpath);
+ }else
+ if(verbose)
+ fprint(2, "no recent archive to diff against\n");
+ }
+
+ // Create yyyy/mmdd.
+ tm = *localtime(time(0));
+ snprint(yyyy, sizeof yyyy, "%04d", tm.year+1900);
+ fp = vacfsgetroot(fs);
+ if((f = vacfilewalk(fp, yyyy)) == nil
+ && (f = vacfilecreate(fp, yyyy, ModeDir|0555)) == nil)
+ sysfatal("vacfscreate %s: %r", yyyy);
+ vacfiledecref(fp);
+ fp = f;
+
+ snprint(mmdd, sizeof mmdd, "%02d%02d", tm.mon+1, tm.mday);
+ n = 0;
+ while((f = vacfilewalk(fp, mmdd)) != nil){
+ vacfiledecref(f);
+ n++;
+ snprint(mmdd+4, sizeof mmdd-4, ".%d", n);
+ }
+ f = vacfilecreate(fp, mmdd, ModeDir|0555);
+ if(f == nil)
+ sysfatal("vacfscreate %s/%s: %r", yyyy, mmdd);
+ vacfiledecref(fp);
+
+ if(verbose)
+ fprint(2, "archive %s/%s\n", yyyy, mmdd);
+ }else{
+ if(vacfile == nil)
+ outfd = 1;
+ else if((outfd = create(vacfile, OWRITE, 0666)) < 0)
+ sysfatal("create %s: %r", vacfile);
+ atexit(removevacfile);
+ if((fs = vacfscreate(z, blocksize, 512)) == nil)
+ sysfatal("vacfscreate: %r");
+ f = vacfsgetroot(fs);
+
+ fdiff = nil;
+ if(diffvac){
+ if((fsdiff = vacfsopen(z, diffvac, VtOREAD, 128)) == nil)
+ warn("vacfsopen %s: %r", diffvac);
+ else
+ fdiff = vacfsgetroot(fsdiff);
+ }
+ }
+
+ if(stdinname)
+ vacstdin(f, stdinname);
+ for(i=0; i<argc; i++){
+ // We can't use / and . and .. and ../.. as valid archive
+ // names, so expand to the list of files in the directory.
+ if(argv[i][0] == 0){
+ warn("empty string given as command-line argument");
+ continue;
+ }
+ cleanname(argv[i]);
+ if(strcmp(argv[i], "/") == 0
+ || strcmp(argv[i], ".") == 0
+ || strcmp(argv[i], "..") == 0
+ || (strlen(argv[i]) > 3 && strcmp(argv[i]+strlen(argv[i])-3, "/..") == 0)){
+ if((fd = open(argv[i], OREAD)) < 0){
+ warn("open %s: %r", argv[i]);
+ continue;
+ }
+ while((n = dirread(fd, &d)) > 0){
+ for(j=0; j<n; j++){
+ s = vtmalloc(strlen(argv[i])+1+strlen(d[j].name)+1);
+ strcpy(s, argv[i]);
+ strcat(s, "/");
+ strcat(s, d[j].name);
+ cleanname(s);
+ vac(f, fdiff, s, &d[j]);
+ }
+ free(d);
+ }
+ close(fd);
+ continue;
+ }
+ if((d = dirstat(argv[i])) == nil){
+ warn("stat %s: %r", argv[i]);
+ continue;
+ }
+ vac(f, fdiff, argv[i], d);
+ free(d);
+ }
+ if(fdiff)
+ vacfiledecref(fdiff);
+
+ /*
+ * Record the maximum qid so that vacs can be merged
+ * without introducing overlapping qids. Older versions
+ * of vac arranged that the root would have the largest
+ * qid in the file system, but we can't do that anymore
+ * (the root gets created first!).
+ */
+ if(_vacfsnextqid(fs, &qid) >= 0)
+ vacfilesetqidspace(f, 0, qid);
+ vacfiledecref(f);
+
+ /*
+ * Copy fsdiff's root block score into fs's slot for that,
+ * so that vacfssync will copy it into root.prev for us.
+ * Just nice documentation, no effect.
+ */
+ if(fsdiff)
+ memmove(fs->score, fsdiff->score, VtScoreSize);
+ if(vacfssync(fs) < 0)
+ fprint(2, "vacfssync: %r\n");
+
+ fprint(outfd, "vac:%V\n", fs->score);
+ atexitdont(removevacfile);
+ vacfsclose(fs);
+ vthangup(z);
+
+ if(printstats){
+ fprint(2,
+ "%d files, %d files skipped, %d directories\n"
+ "%lld data bytes written, %lld data bytes skipped\n",
+ stats.nfile, stats.skipfiles, stats.ndir, stats.data, stats.skipdata);
+ dup(2, 1);
+ packetstats();
+ }
+ threadexitsall(0);
+}
+
+VacFile*
+recentarchive(VacFs *fs, char *path)
+{
+ VacFile *fp, *f;
+ VacDirEnum *de;
+ VacDir vd;
+ char buf[10];
+ int year, mmdd, nn, n, n1;
+ char *p;
+
+ fp = vacfsgetroot(fs);
+ de = vdeopen(fp);
+ year = 0;
+ if(de){
+ for(; vderead(de, &vd) > 0; vdcleanup(&vd)){
+ if(strlen(vd.elem) != 4)
+ continue;
+ if((n = strtol(vd.elem, &p, 10)) < 1900 || *p != 0)
+ continue;
+ if(year < n)
+ year = n;
+ }
+ }
+ vdeclose(de);
+ if(year == 0){
+ vacfiledecref(fp);
+ return nil;
+ }
+ snprint(buf, sizeof buf, "%04d", year);
+ if((f = vacfilewalk(fp, buf)) == nil){
+ fprint(2, "warning: dirread %s but cannot walk", buf);
+ vacfiledecref(fp);
+ return nil;
+ }
+ fp = f;
+
+ de = vdeopen(fp);
+ mmdd = 0;
+ nn = 0;
+ if(de){
+ for(; vderead(de, &vd) > 0; vdcleanup(&vd)){
+ if(strlen(vd.elem) < 4)
+ continue;
+ if((n = strtol(vd.elem, &p, 10)) < 100 || n > 1231 || p != vd.elem+4)
+ continue;
+ if(*p == '.'){
+ if(p[1] == '0' || (n1 = strtol(p+1, &p, 10)) == 0 || *p != 0)
+ continue;
+ }else{
+ if(*p != 0)
+ continue;
+ n1 = 0;
+ }
+ if(n < mmdd || (n == mmdd && n1 < nn))
+ continue;
+ mmdd = n;
+ nn = n1;
+ }
+ }
+ vdeclose(de);
+ if(mmdd == 0){
+ vacfiledecref(fp);
+ return nil;
+ }
+ if(nn == 0)
+ snprint(buf, sizeof buf, "%04d", mmdd);
+ else
+ snprint(buf, sizeof buf, "%04d.%d", mmdd, nn);
+ if((f = vacfilewalk(fp, buf)) == nil){
+ fprint(2, "warning: dirread %s but cannot walk", buf);
+ vacfiledecref(fp);
+ return nil;
+ }
+ vacfiledecref(fp);
+
+ sprint(path, "%04d/%s", year, buf);
+ return f;
+}
+
+static void
+removevacfile(void)
+{
+ if(vacfile)
+ remove(vacfile);
+}
+
+void
+plan9tovacdir(VacDir *vd, Dir *dir)
+{
+ memset(vd, 0, sizeof *vd);
+
+ vd->elem = dir->name;
+ vd->uid = dir->uid;
+ vd->gid = dir->gid;
+ vd->mid = dir->muid;
+ if(vd->mid == nil)
+ vd->mid = "";
+ vd->mtime = dir->mtime;
+ vd->mcount = 0;
+ vd->ctime = dir->mtime; /* ctime: not available on plan 9 */
+ vd->atime = dir->atime;
+ vd->size = dir->length;
+
+ vd->mode = dir->mode & 0777;
+ if(dir->mode & DMDIR)
+ vd->mode |= ModeDir;
+ if(dir->mode & DMAPPEND)
+ vd->mode |= ModeAppend;
+ if(dir->mode & DMEXCL)
+ vd->mode |= ModeExclusive;
+
+ vd->plan9 = 1;
+ vd->p9path = dir->qid.path;
+ vd->p9version = dir->qid.vers;
+}
+
+
+/*
+ * Archive the file named name, which has stat info d,
+ * into the vac directory fp (p = parent).
+ *
+ * If we're doing a vac -d against another archive, the
+ * equivalent directory to fp in that archive is diffp.
+ */
+void
+vac(VacFile *fp, VacFile *diffp, char *name, Dir *d)
+{
+ char *elem, *s;
+ static char buf[65536];
+ int fd, i, n, bsize;
+ vlong off;
+ Dir *dk; // kids
+ VacDir vd, vddiff;
+ VacFile *f, *fdiff;
+ VtEntry e;
+
+ if(!includefile(name)){
+ warn("excluding %s%s", name, (d->mode&DMDIR) ? "/" : "");
+ return;
+ }
+
+ if(d->mode&DMDIR)
+ stats.ndir++;
+ else
+ stats.nfile++;
+
+ if(merge && vacmerge(fp, name) >= 0)
+ return;
+
+ if(verbose)
+ fprint(2, "%s%s\n", name, (d->mode&DMDIR) ? "/" : "");
+
+ if((fd = open(name, OREAD)) < 0){
+ warn("open %s: %r", name);
+ return;
+ }
+
+ elem = strrchr(name, '/');
+ if(elem)
+ elem++;
+ else
+ elem = name;
+
+ plan9tovacdir(&vd, d);
+ if((f = vacfilecreate(fp, elem, vd.mode)) == nil){
+ warn("vacfilecreate %s: %r", name);
+ return;
+ }
+ if(diffp)
+ fdiff = vacfilewalk(diffp, elem);
+ else
+ fdiff = nil;
+
+ if(vacfilesetdir(f, &vd) < 0)
+ warn("vacfilesetdir %s: %r", name);
+
+ if(d->mode&DMDIR){
+ while((n = dirread(fd, &dk)) > 0){
+ for(i=0; i<n; i++){
+ s = vtmalloc(strlen(name)+1+strlen(dk[i].name)+1);
+ strcpy(s, name);
+ strcat(s, "/");
+ strcat(s, dk[i].name);
+ vac(f, fdiff, s, &dk[i]);
+ free(s);
+ }
+ free(dk);
+ }
+ }else{
+ off = 0;
+ bsize = fs->bsize;
+ if(fdiff){
+ /*
+ * Copy fdiff's contents into f by moving the score.
+ * We'll diff and update below.
+ */
+ if(vacfilegetentries(fdiff, &e, nil) >= 0)
+ if(vacfilesetentries(f, &e, nil) >= 0){
+ bsize = e.dsize;
+
+ /*
+ * Or if -q is set, and the metadata looks the same,
+ * don't even bother reading the file.
+ */
+ if(qdiff && vacfilegetdir(fdiff, &vddiff) >= 0){
+ if(vddiff.mtime == vd.mtime)
+ if(vddiff.size == vd.size)
+ if(!vddiff.plan9 || (/* vddiff.p9path == vd.p9path && */ vddiff.p9version == vd.p9version)){
+ stats.skipfiles++;
+ stats.nfile--;
+ vdcleanup(&vddiff);
+ goto Out;
+ }
+
+ /*
+ * Skip over presumably-unchanged prefix
+ * of an append-only file.
+ */
+ if(vd.mode&ModeAppend)
+ if(vddiff.size < vd.size)
+ if(vddiff.plan9 && vd.plan9)
+ if(vddiff.p9path == vd.p9path){
+ off = vd.size/bsize*bsize;
+ if(seek(fd, off, 0) >= 0)
+ stats.skipdata += off;
+ else{
+ seek(fd, 0, 0); // paranoia
+ off = 0;
+ }
+ }
+
+ vdcleanup(&vddiff);
+ // XXX different verbose chatty prints for kaminsky?
+ }
+ }
+ }
+ if(qdiff && verbose)
+ fprint(2, "+%s\n", name);
+ while((n = readn(fd, buf, bsize)) > 0){
+ if(fdiff && sha1matches(f, off/bsize, (uchar*)buf, n)){
+ off += n;
+ stats.skipdata += n;
+ continue;
+ }
+ if(vacfilewrite(f, buf, n, off) < 0){
+ warn("venti write %s: %r", name);
+ goto Out;
+ }
+ stats.data += n;
+ off += n;
+ }
+ /*
+ * Since we started with fdiff's contents,
+ * set the size in case fdiff was bigger.
+ */
+ if(fdiff && vacfilesetsize(f, off) < 0)
+ warn("vtfilesetsize %s: %r", name);
+ }
+
+Out:
+ vacfileflush(f, 1);
+ vacfiledecref(f);
+ if(fdiff)
+ vacfiledecref(fdiff);
+ close(fd);
+}
+
+void
+vacstdin(VacFile *fp, char *name)
+{
+ vlong off;
+ VacFile *f;
+ static char buf[8192];
+ int n;
+
+ if((f = vacfilecreate(fp, name, 0666)) == nil){
+ warn("vacfilecreate %s: %r", name);
+ return;
+ }
+
+ off = 0;
+ while((n = read(0, buf, sizeof buf)) > 0){
+ if(vacfilewrite(f, buf, n, off) < 0){
+ warn("venti write %s: %r", name);
+ vacfiledecref(f);
+ return;
+ }
+ off += n;
+ }
+ vacfileflush(f, 1);
+ vacfiledecref(f);
+}
+
+/*
+ * fp is the directory we're writing.
+ * mp is the directory whose contents we're merging in.
+ * d is the directory entry of the file from mp that we want to add to fp.
+ * vacfile is the name of the .vac file, for error messages.
+ * offset is the qid that qid==0 in mp should correspond to.
+ * max is the maximum qid we expect to see (not really needed).
+ */
+int
+vacmergefile(VacFile *fp, VacFile *mp, VacDir *d, char *vacfile,
+ vlong offset, vlong max)
+{
+ VtEntry ed, em;
+ VacFile *mf;
+ VacFile *f;
+
+ mf = vacfilewalk(mp, d->elem);
+ if(mf == nil){
+ warn("could not walk %s in %s", d->elem, vacfile);
+ return -1;
+ }
+ if(vacfilegetentries(mf, &ed, &em) < 0){
+ warn("could not get entries for %s in %s", d->elem, vacfile);
+ vacfiledecref(mf);
+ return -1;
+ }
+
+ if((f = vacfilecreate(fp, d->elem, d->mode)) == nil){
+ warn("vacfilecreate %s: %r", d->elem);
+ vacfiledecref(mf);
+ return -1;
+ }
+ if(d->qidspace){
+ d->qidoffset += offset;
+ d->qidmax += offset;
+ }else{
+ d->qidspace = 1;
+ d->qidoffset = offset;
+ d->qidmax = max;
+ }
+ if(vacfilesetdir(f, d) < 0
+ || vacfilesetentries(f, &ed, &em) < 0
+ || vacfilesetqidspace(f, d->qidoffset, d->qidmax) < 0){
+ warn("vacmergefile %s: %r", d->elem);
+ vacfiledecref(mf);
+ vacfiledecref(f);
+ return -1;
+ }
+
+ vacfiledecref(mf);
+ vacfiledecref(f);
+ return 0;
+}
+
+int
+vacmerge(VacFile *fp, char *name)
+{
+ VacFs *mfs;
+ VacDir vd;
+ VacDirEnum *de;
+ VacFile *mp;
+ uvlong maxqid, offset;
+
+ if(strlen(name) < 4 || strcmp(name+strlen(name)-4, ".vac") != 0)
+ return -1;
+ if((mfs = vacfsopen(z, name, VtOREAD, 100)) == nil)
+ return -1;
+ if(verbose)
+ fprint(2, "merging %s\n", name);
+
+ mp = vacfsgetroot(mfs);
+ de = vdeopen(mp);
+ if(de){
+ offset = 0;
+ if(vacfsgetmaxqid(mfs, &maxqid) >= 0){
+ _vacfsnextqid(fs, &offset);
+ vacfsjumpqid(fs, maxqid+1);
+ }
+ while(vderead(de, &vd) > 0){
+ if(vd.qid > maxqid){
+ warn("vacmerge %s: maxqid=%lld but %s has %lld",
+ name, maxqid, vd.elem, vd.qid);
+ vacfsjumpqid(fs, vd.qid - maxqid);
+ maxqid = vd.qid;
+ }
+ vacmergefile(fp, mp, &vd, name,
+ offset, maxqid);
+ vdcleanup(&vd);
+ }
+ vdeclose(de);
+ }
+ vacfiledecref(mp);
+ vacfsclose(mfs);
+ return 0;
+}
+
+#define TWID64 ((u64int)~(u64int)0)
+
+static u64int
+unittoull(char *s)
+{
+ char *es;
+ u64int n;
+
+ if(s == nil)
+ return TWID64;
+ n = strtoul(s, &es, 0);
+ if(*es == 'k' || *es == 'K'){
+ n *= 1024;
+ es++;
+ }else if(*es == 'm' || *es == 'M'){
+ n *= 1024*1024;
+ es++;
+ }else if(*es == 'g' || *es == 'G'){
+ n *= 1024*1024*1024;
+ es++;
+ }
+ if(*es != '\0')
+ return TWID64;
+ return n;
+}
+
+static void
+warn(char *fmt, ...)
+{
+ va_list arg;
+
+ va_start(arg, fmt);
+ fprint(2, "vac: ");
+ vfprint(2, fmt, arg);
+ fprint(2, "\n");
+ va_end(arg);
+}
+
diff --git a/sys/src/cmd/vac/vac.h b/sys/src/cmd/vac/vac.h
new file mode 100755
index 000000000..9888e04fe
--- /dev/null
+++ b/sys/src/cmd/vac/vac.h
@@ -0,0 +1,145 @@
+typedef struct VacFs VacFs;
+typedef struct VacDir VacDir;
+typedef struct VacFile VacFile;
+typedef struct VacDirEnum VacDirEnum;
+
+#pragma incomplete VacFile
+#pragma incomplete VacDirEnum
+
+/*
+ * Mode bits
+ */
+enum
+{
+ ModeOtherExec = (1<<0),
+ ModeOtherWrite = (1<<1),
+ ModeOtherRead = (1<<2),
+ ModeGroupExec = (1<<3),
+ ModeGroupWrite = (1<<4),
+ ModeGroupRead = (1<<5),
+ ModeOwnerExec = (1<<6),
+ ModeOwnerWrite = (1<<7),
+ ModeOwnerRead = (1<<8),
+ ModeSticky = (1<<9),
+ ModeSetUid = (1<<10),
+ ModeSetGid = (1<<11),
+ ModeAppend = (1<<12), /* append only file */
+ ModeExclusive = (1<<13), /* lock file - plan 9 */
+ ModeLink = (1<<14), /* sym link */
+ ModeDir = (1<<15), /* duplicate of DirEntry */
+ ModeHidden = (1<<16), /* MS-DOS */
+ ModeSystem = (1<<17), /* MS-DOS */
+ ModeArchive = (1<<18), /* MS-DOS */
+ ModeTemporary = (1<<19), /* MS-DOS */
+ ModeSnapshot = (1<<20), /* read only snapshot */
+ ModeDevice = (1<<21), /* Unix device */
+ ModeNamedPipe = (1<<22) /* Unix named pipe */
+};
+
+enum
+{
+ MetaMagic = 0x5656fc79,
+ MetaHeaderSize = 12,
+ MetaIndexSize = 4,
+ IndexEntrySize = 8,
+ DirMagic = 0x1c4d9072
+};
+
+enum
+{
+ DirPlan9Entry = 1, /* not valid in version >= 9 */
+ DirNTEntry, /* not valid in version >= 9 */
+ DirQidSpaceEntry,
+ DirGenEntry /* not valid in version >= 9 */
+};
+
+struct VacDir
+{
+ char *elem; /* path element */
+ ulong entry; /* entry in directory for data */
+ ulong gen; /* generation of data entry */
+ ulong mentry; /* entry in directory for meta */
+ ulong mgen; /* generation of meta entry */
+ uvlong size; /* size of file */
+ uvlong qid; /* unique file id */
+
+ char *uid; /* owner id */
+ char *gid; /* group id */
+ char *mid; /* last modified by */
+ ulong mtime; /* last modified time */
+ ulong mcount; /* number of modifications: can wrap! */
+ ulong ctime; /* directory entry last changed */
+ ulong atime; /* last time accessed */
+ ulong mode; /* various mode bits */
+
+ /* plan 9 */
+ int plan9;
+ uvlong p9path;
+ ulong p9version;
+
+ /* sub space of qid */
+ int qidspace;
+ uvlong qidoffset; /* qid offset */
+ uvlong qidmax; /* qid maximum */
+};
+
+struct VacFs
+{
+ char name[128];
+ uchar score[VtScoreSize];
+ VacFile *root;
+ VtConn *z;
+ int mode;
+ int bsize;
+ uvlong qid;
+ VtCache *cache;
+};
+
+VacFs *vacfsopen(VtConn *z, char *file, int mode, int ncache);
+VacFs *vacfsopenscore(VtConn *z, u8int *score, int mode, int ncache);
+VacFs *vacfscreate(VtConn *z, int bsize, int ncache);
+void vacfsclose(VacFs *fs);
+int vacfssync(VacFs *fs);
+int vacfssnapshot(VacFs *fs, char *src, char *dst);
+int vacfsgetscore(VacFs *fs, u8int *score);
+int vacfsgetmaxqid(VacFs*, uvlong*);
+void vacfsjumpqid(VacFs*, uvlong);
+
+VacFile *vacfsgetroot(VacFs *fs);
+VacFile *vacfileopen(VacFs *fs, char *path);
+VacFile *vacfilecreate(VacFile *file, char *elem, ulong perm);
+VacFile *vacfilewalk(VacFile *file, char *elem);
+int vacfileremove(VacFile *file);
+int vacfileread(VacFile *file, void *buf, int n, vlong offset);
+int vacfileblockscore(VacFile *file, u32int, u8int*);
+int vacfilewrite(VacFile *file, void *buf, int n, vlong offset);
+uvlong vacfilegetid(VacFile *file);
+ulong vacfilegetmcount(VacFile *file);
+int vacfileisdir(VacFile *file);
+int vacfileisroot(VacFile *file);
+ulong vacfilegetmode(VacFile *file);
+int vacfilegetsize(VacFile *file, uvlong *size);
+int vacfilegetdir(VacFile *file, VacDir *dir);
+int vacfilesetdir(VacFile *file, VacDir *dir);
+VacFile *vacfilegetparent(VacFile *file);
+int vacfileflush(VacFile*, int);
+VacFile *vacfileincref(VacFile*);
+int vacfiledecref(VacFile*);
+int vacfilesetsize(VacFile *f, uvlong size);
+
+int vacfilegetentries(VacFile *f, VtEntry *e, VtEntry *me);
+int vacfilesetentries(VacFile *f, VtEntry *e, VtEntry *me);
+
+void vdcleanup(VacDir *dir);
+void vdcopy(VacDir *dst, VacDir *src);
+int vacfilesetqidspace(VacFile*, u64int, u64int);
+uvlong vacfilegetqidoffset(VacFile*);
+
+VacDirEnum *vdeopen(VacFile*);
+int vderead(VacDirEnum*, VacDir *);
+void vdeclose(VacDirEnum*);
+int vdeunread(VacDirEnum*);
+
+int vacfiledsize(VacFile *f);
+int sha1matches(VacFile *f, ulong b, uchar *buf, int n);
+
diff --git a/sys/src/cmd/vac/vacfs.c b/sys/src/cmd/vac/vacfs.c
new file mode 100755
index 000000000..e4697c92a
--- /dev/null
+++ b/sys/src/cmd/vac/vacfs.c
@@ -0,0 +1,788 @@
+#include "stdinc.h"
+#include <fcall.h>
+#include "vac.h"
+
+#define convM2Su(a, b, c, d) convM2S(a, b, c)
+#define convS2Mu(a, b, c, d) convS2M(a, b, c)
+#define convM2Du(a, b, c, d) convM2D(a, b, c)
+#define convD2Mu(a, b, c, d) convD2M(a, b, c)
+
+typedef struct Fid Fid;
+
+enum
+{
+ Stacksize = 320 * 1024, /* was 32K */
+ OPERM = 0x3 /* mask of all permission types in open mode */
+};
+
+struct Fid
+{
+ short busy;
+ short open;
+ int fid;
+ char *user;
+ Qid qid;
+ VacFile *file;
+ VacDirEnum *vde;
+ Fid *next;
+};
+
+enum
+{
+ Pexec = 1,
+ Pwrite = 2,
+ Pread = 4,
+ Pother = 1,
+ Pgroup = 8,
+ Powner = 64
+};
+
+Fid *fids;
+uchar *data;
+int mfd[2];
+int srvfd = -1;
+char *user;
+uchar mdata[8192+IOHDRSZ];
+int messagesize = sizeof mdata;
+Fcall rhdr;
+Fcall thdr;
+VacFs *fs;
+VtConn *conn;
+int noperm;
+int dotu;
+char *defmnt;
+
+Fid * newfid(int);
+void error(char*);
+void io(void);
+void vacshutdown(void);
+void usage(void);
+int perm(Fid*, int);
+int permf(VacFile*, char*, int);
+ulong getl(void *p);
+void init(char*, char*, long, int);
+int vacdirread(Fid *f, char *p, long off, long cnt);
+int vacstat(VacFile *parent, VacDir *vd, uchar *p, int np);
+void srv(void* a);
+
+
+char *rflush(Fid*), *rversion(Fid*),
+ *rauth(Fid*), *rattach(Fid*), *rwalk(Fid*),
+ *ropen(Fid*), *rcreate(Fid*),
+ *rread(Fid*), *rwrite(Fid*), *rclunk(Fid*),
+ *rremove(Fid*), *rstat(Fid*), *rwstat(Fid*);
+
+char *(*fcalls[Tmax])(Fid*);
+
+void
+initfcalls(void)
+{
+ fcalls[Tflush]= rflush;
+ fcalls[Tversion]= rversion;
+ fcalls[Tattach]= rattach;
+ fcalls[Tauth]= rauth;
+ fcalls[Twalk]= rwalk;
+ fcalls[Topen]= ropen;
+ fcalls[Tcreate]= rcreate;
+ fcalls[Tread]= rread;
+ fcalls[Twrite]= rwrite;
+ fcalls[Tclunk]= rclunk;
+ fcalls[Tremove]= rremove;
+ fcalls[Tstat]= rstat;
+ fcalls[Twstat]= rwstat;
+}
+
+char Eperm[] = "permission denied";
+char Enotdir[] = "not a directory";
+char Enotexist[] = "file does not exist";
+char Einuse[] = "file in use";
+char Eexist[] = "file exists";
+char Enotowner[] = "not owner";
+char Eisopen[] = "file already open for I/O";
+char Excl[] = "exclusive use file already open";
+char Ename[] = "illegal name";
+char Erdonly[] = "read only file system";
+char Eio[] = "i/o error";
+char Eempty[] = "directory is not empty";
+char Emode[] = "illegal mode";
+
+int dflag;
+
+void
+notifyf(void *a, char *s)
+{
+ USED(a);
+ if(strncmp(s, "interrupt", 9) == 0)
+ noted(NCONT);
+ noted(NDFLT);
+}
+
+void
+threadmain(int argc, char *argv[])
+{
+ char *defsrv, *srvname;
+ int p[2], fd;
+ int stdio;
+ char *host = nil;
+ long ncache;
+
+ stdio = 0;
+ ncache = 256;
+ fmtinstall('H', encodefmt);
+ fmtinstall('V', vtscorefmt);
+ fmtinstall('F', vtfcallfmt);
+
+ defmnt = nil;
+ defsrv = nil;
+ ARGBEGIN{
+ case 'd':
+ fmtinstall('F', fcallfmt);
+ dflag = 1;
+ break;
+ case 'c':
+ ncache = atoi(EARGF(usage()));
+ break;
+ case 'i':
+ defmnt = nil;
+ stdio = 1;
+ mfd[0] = 0;
+ mfd[1] = 1;
+ break;
+ case 'h':
+ host = EARGF(usage());
+ break;
+ case 'S':
+ defsrv = EARGF(usage());
+ break;
+ case 's':
+ defsrv = "vacfs";
+ break;
+ case 'm':
+ defmnt = EARGF(usage());
+ break;
+ case 'p':
+ noperm = 1;
+ break;
+ case 'V':
+ chattyventi = 1;
+ break;
+ default:
+ usage();
+ }ARGEND
+
+ if(argc != 1)
+ usage();
+
+ if(defsrv == nil && defmnt == nil && !stdio)
+ defmnt = "/n/vac";
+ if(stdio && defmnt)
+ sysfatal("cannot use -m with -i");
+
+ initfcalls();
+
+ notify(notifyf);
+ user = getuser();
+
+ conn = vtdial(host);
+ if(conn == nil)
+ sysfatal("could not connect to server: %r");
+
+ if(vtconnect(conn) < 0)
+ sysfatal("vtconnect: %r");
+
+ fs = vacfsopen(conn, argv[0], VtOREAD, ncache);
+ if(fs == nil)
+ sysfatal("vacfsopen: %r");
+
+ if(!stdio){
+ if(pipe(p) < 0)
+ sysfatal("pipe failed: %r");
+ mfd[0] = p[0];
+ mfd[1] = p[0];
+ srvfd = p[1];
+ }
+
+ procrfork(srv, 0, Stacksize, RFFDG|RFNAMEG|RFNOTEG);
+
+ if(!stdio){
+ close(p[0]);
+ if(defsrv){
+ srvname = smprint("/srv/%s", defsrv);
+ fd = create(srvname, OWRITE|ORCLOSE, 0666);
+ if(fd < 0)
+ sysfatal("create %s: %r", srvname);
+ if(fprint(fd, "%d", srvfd) < 0)
+ sysfatal("write %s: %r", srvname);
+ free(srvname);
+ }
+ if(defmnt){
+ if(mount(srvfd, -1, defmnt, MREPL|MCREATE, "") < 0)
+ sysfatal("mount %s: %r", defmnt);
+ }
+ }
+ threadexits(0);
+}
+
+void
+srv(void *a)
+{
+ USED(a);
+ io();
+ vacshutdown();
+}
+
+void
+usage(void)
+{
+ fprint(2, "usage: %s [-sd] [-h host] [-c ncache] [-m mountpoint] vacfile\n", argv0);
+ threadexitsall("usage");
+}
+
+char*
+rversion(Fid *unused)
+{
+ Fid *f;
+
+ USED(unused);
+
+ for(f = fids; f; f = f->next)
+ if(f->busy)
+ rclunk(f);
+
+ if(rhdr.msize < 256)
+ return vtstrdup("version: message size too small");
+ messagesize = rhdr.msize;
+ if(messagesize > sizeof mdata)
+ messagesize = sizeof mdata;
+ thdr.msize = messagesize;
+ if(strncmp(rhdr.version, "9P2000", 6) != 0)
+ return vtstrdup("unrecognized 9P version");
+ thdr.version = "9P2000";
+ if(strncmp(rhdr.version, "9P2000.u", 8) == 0){
+ dotu = 1;
+ thdr.version = "9P2000.u";
+ }
+ return nil;
+}
+
+char*
+rflush(Fid *f)
+{
+ USED(f);
+ return 0;
+}
+
+char*
+rauth(Fid *f)
+{
+ USED(f);
+ return vtstrdup("vacfs: authentication not required");
+}
+
+char*
+rattach(Fid *f)
+{
+ /* no authentication for the momment */
+ VacFile *file;
+ char err[80];
+
+ file = vacfsgetroot(fs);
+ if(file == nil) {
+ rerrstr(err, sizeof err);
+ return vtstrdup(err);
+ }
+
+ f->busy = 1;
+ f->file = file;
+ f->qid.path = vacfilegetid(f->file);
+ f->qid.vers = 0;
+ f->qid.type = QTDIR;
+ thdr.qid = f->qid;
+ if(rhdr.uname[0])
+ f->user = vtstrdup(rhdr.uname);
+ else
+ f->user = "none";
+ return 0;
+}
+
+char*
+rwalk(Fid *f)
+{
+ VacFile *file, *nfile;
+ Fid *nf;
+ int nqid, nwname;
+ Qid qid;
+ char *err = nil;
+
+ if(f->busy == 0)
+ return Enotexist;
+ nf = nil;
+ if(rhdr.fid != rhdr.newfid){
+ if(f->open)
+ return vtstrdup(Eisopen);
+ if(f->busy == 0)
+ return vtstrdup(Enotexist);
+ nf = newfid(rhdr.newfid);
+ if(nf->busy)
+ return vtstrdup(Eisopen);
+ nf->busy = 1;
+ nf->open = 0;
+ nf->qid = f->qid;
+ nf->file = vacfileincref(f->file);
+ nf->user = vtstrdup(f->user);
+ f = nf;
+ }
+
+ nwname = rhdr.nwname;
+
+ /* easy case */
+ if(nwname == 0) {
+ thdr.nwqid = 0;
+ return 0;
+ }
+
+ file = f->file;
+ vacfileincref(file);
+ qid = f->qid;
+
+ for(nqid = 0; nqid < nwname; nqid++){
+ if((qid.type & QTDIR) == 0){
+ err = Enotdir;
+ break;
+ }
+ if(!permf(file, f->user, Pexec)) {
+ err = Eperm;
+ break;
+ }
+ nfile = vacfilewalk(file, rhdr.wname[nqid]);
+ if(nfile == nil)
+ break;
+ vacfiledecref(file);
+ file = nfile;
+ qid.type = QTFILE;
+ if(vacfileisdir(file))
+ qid.type = QTDIR;
+ qid.vers = vacfilegetmcount(file);
+ qid.path = vacfilegetid(file);
+ thdr.wqid[nqid] = qid;
+ }
+
+ thdr.nwqid = nqid;
+
+ if(nqid == nwname){
+ /* success */
+ f->qid = thdr.wqid[nqid-1];
+ vacfiledecref(f->file);
+ f->file = file;
+ return 0;
+ }
+
+ vacfiledecref(file);
+ if(nf != nil)
+ rclunk(nf);
+
+ /* only error on the first element */
+ if(nqid == 0)
+ return vtstrdup(err);
+
+ return 0;
+}
+
+char *
+ropen(Fid *f)
+{
+ int mode, trunc;
+
+ if(f->open)
+ return vtstrdup(Eisopen);
+ if(!f->busy)
+ return vtstrdup(Enotexist);
+
+ mode = rhdr.mode;
+ thdr.iounit = messagesize - IOHDRSZ;
+ if(f->qid.type & QTDIR){
+ if(mode != OREAD)
+ return vtstrdup(Eperm);
+ if(!perm(f, Pread))
+ return vtstrdup(Eperm);
+ thdr.qid = f->qid;
+ f->vde = nil;
+ f->open = 1;
+ return 0;
+ }
+ if(mode & ORCLOSE)
+ return vtstrdup(Erdonly);
+ trunc = mode & OTRUNC;
+ mode &= OPERM;
+ if(mode==OWRITE || mode==ORDWR || trunc)
+ if(!perm(f, Pwrite))
+ return vtstrdup(Eperm);
+ if(mode==OREAD || mode==ORDWR)
+ if(!perm(f, Pread))
+ return vtstrdup(Eperm);
+ if(mode==OEXEC)
+ if(!perm(f, Pexec))
+ return vtstrdup(Eperm);
+ thdr.qid = f->qid;
+ thdr.iounit = messagesize - IOHDRSZ;
+ f->open = 1;
+ return 0;
+}
+
+char*
+rcreate(Fid* fid)
+{
+ VacFile *vf;
+ ulong mode;
+
+ if(fid->open)
+ return vtstrdup(Eisopen);
+ if(!fid->busy)
+ return vtstrdup(Enotexist);
+ if(fs->mode & ModeSnapshot)
+ return vtstrdup(Erdonly);
+ vf = fid->file;
+ if(!vacfileisdir(vf))
+ return vtstrdup(Enotdir);
+ if(!permf(vf, fid->user, Pwrite))
+ return vtstrdup(Eperm);
+
+ mode = rhdr.perm & 0777;
+
+ if(rhdr.perm & DMDIR){
+ if((rhdr.mode & OTRUNC) || (rhdr.perm & DMAPPEND))
+ return vtstrdup(Emode);
+ switch(rhdr.mode & OPERM){
+ default:
+ return vtstrdup(Emode);
+ case OEXEC:
+ case OREAD:
+ break;
+ case OWRITE:
+ case ORDWR:
+ return vtstrdup(Eperm);
+ }
+ mode |= ModeDir;
+ }
+ vf = vacfilecreate(vf, rhdr.name, mode);
+ if(vf == nil) {
+ char err[80];
+ rerrstr(err, sizeof err);
+
+ return vtstrdup(err);
+ }
+
+ vacfiledecref(fid->file);
+
+ fid->file = vf;
+ fid->qid.type = QTFILE;
+ if(vacfileisdir(vf))
+ fid->qid.type = QTDIR;
+ fid->qid.vers = vacfilegetmcount(vf);
+ fid->qid.path = vacfilegetid(vf);
+
+ thdr.qid = fid->qid;
+ thdr.iounit = messagesize - IOHDRSZ;
+
+ return 0;
+}
+
+char*
+rread(Fid *f)
+{
+ char *buf;
+ vlong off;
+ int cnt;
+ VacFile *vf;
+ char err[80];
+ int n;
+
+ if(!f->busy)
+ return vtstrdup(Enotexist);
+ vf = f->file;
+ thdr.count = 0;
+ off = rhdr.offset;
+ buf = thdr.data;
+ cnt = rhdr.count;
+ if(f->qid.type & QTDIR)
+ n = vacdirread(f, buf, off, cnt);
+ else if(vacfilegetmode(f->file)&ModeDevice)
+ return vtstrdup("device");
+ else if(vacfilegetmode(f->file)&ModeLink)
+ return vtstrdup("symbolic link");
+ else if(vacfilegetmode(f->file)&ModeNamedPipe)
+ return vtstrdup("named pipe");
+ else
+ n = vacfileread(vf, buf, cnt, off);
+ if(n < 0) {
+ rerrstr(err, sizeof err);
+ return vtstrdup(err);
+ }
+ thdr.count = n;
+ return 0;
+}
+
+char*
+rwrite(Fid *f)
+{
+ USED(f);
+ return vtstrdup(Erdonly);
+}
+
+char *
+rclunk(Fid *f)
+{
+ f->busy = 0;
+ f->open = 0;
+ vtfree(f->user);
+ f->user = nil;
+ if(f->file)
+ vacfiledecref(f->file);
+ f->file = nil;
+ vdeclose(f->vde);
+ f->vde = nil;
+ return 0;
+}
+
+char *
+rremove(Fid *f)
+{
+ VacFile *vf, *vfp;
+ char errbuf[80];
+ char *err = nil;
+
+ if(!f->busy)
+ return vtstrdup(Enotexist);
+ vf = f->file;
+ vfp = vacfilegetparent(vf);
+
+ if(!permf(vfp, f->user, Pwrite)) {
+ err = Eperm;
+ goto Exit;
+ }
+
+ if(!vacfileremove(vf)) {
+ rerrstr(errbuf, sizeof errbuf);
+ err = errbuf;
+ }
+
+Exit:
+ vacfiledecref(vfp);
+ rclunk(f);
+ return vtstrdup(err);
+}
+
+char *
+rstat(Fid *f)
+{
+ VacDir dir;
+ static uchar statbuf[1024];
+ VacFile *parent;
+
+ if(!f->busy)
+ return vtstrdup(Enotexist);
+ parent = vacfilegetparent(f->file);
+ vacfilegetdir(f->file, &dir);
+ thdr.stat = statbuf;
+ thdr.nstat = vacstat(parent, &dir, thdr.stat, sizeof statbuf);
+ vdcleanup(&dir);
+ vacfiledecref(parent);
+ return 0;
+}
+
+char *
+rwstat(Fid *f)
+{
+ if(!f->busy)
+ return vtstrdup(Enotexist);
+ return vtstrdup(Erdonly);
+}
+
+int
+vacstat(VacFile *parent, VacDir *vd, uchar *p, int np)
+{
+ int ret;
+ Dir dir;
+
+ memset(&dir, 0, sizeof(dir));
+
+ dir.qid.path = vd->qid + vacfilegetqidoffset(parent);
+ if(vd->qidspace)
+ dir.qid.path += vd->qidoffset;
+ dir.qid.vers = vd->mcount;
+ dir.mode = vd->mode & 0777;
+ if(vd->mode & ModeAppend){
+ dir.qid.type |= QTAPPEND;
+ dir.mode |= DMAPPEND;
+ }
+ if(vd->mode & ModeExclusive){
+ dir.qid.type |= QTEXCL;
+ dir.mode |= DMEXCL;
+ }
+ if(vd->mode & ModeDir){
+ dir.qid.type |= QTDIR;
+ dir.mode |= DMDIR;
+ }
+
+
+ dir.atime = vd->atime;
+ dir.mtime = vd->mtime;
+ dir.length = vd->size;
+
+ dir.name = vd->elem;
+ dir.uid = vd->uid;
+ dir.gid = vd->gid;
+ dir.muid = vd->mid;
+
+ ret = convD2Mu(&dir, p, np, dotu);
+ return ret;
+}
+
+int
+vacdirread(Fid *f, char *p, long off, long cnt)
+{
+ int i, n, nb;
+ VacDir vd;
+
+ /*
+ * special case of rewinding a directory
+ * otherwise ignore the offset
+ */
+ if(off == 0 && f->vde){
+ vdeclose(f->vde);
+ f->vde = nil;
+ }
+
+ if(f->vde == nil){
+ f->vde = vdeopen(f->file);
+ if(f->vde == nil)
+ return -1;
+ }
+
+ for(nb = 0; nb < cnt; nb += n) {
+ i = vderead(f->vde, &vd);
+ if(i < 0)
+ return -1;
+ if(i == 0)
+ break;
+ n = vacstat(f->file, &vd, (uchar*)p, cnt-nb);
+ if(n <= BIT16SZ) {
+ vdeunread(f->vde);
+ break;
+ }
+ vdcleanup(&vd);
+ p += n;
+ }
+ return nb;
+}
+
+Fid *
+newfid(int fid)
+{
+ Fid *f, *ff;
+
+ ff = 0;
+ for(f = fids; f; f = f->next)
+ if(f->fid == fid)
+ return f;
+ else if(!ff && !f->busy)
+ ff = f;
+ if(ff){
+ ff->fid = fid;
+ return ff;
+ }
+ f = vtmallocz(sizeof *f);
+ f->fid = fid;
+ f->next = fids;
+ fids = f;
+ return f;
+}
+
+void
+io(void)
+{
+ char *err;
+ int n;
+
+ for(;;){
+ n = read9pmsg(mfd[0], mdata, sizeof mdata);
+ if(n <= 0)
+ break;
+ if(convM2Su(mdata, n, &rhdr, dotu) != n)
+ sysfatal("convM2S conversion error");
+
+ if(dflag)
+ fprint(2, "vacfs:<-%F\n", &rhdr);
+
+ thdr.data = (char*)mdata + IOHDRSZ;
+ if(!fcalls[rhdr.type])
+ err = "bad fcall type";
+ else
+ err = (*fcalls[rhdr.type])(newfid(rhdr.fid));
+ if(err){
+ thdr.type = Rerror;
+ thdr.ename = err;
+ }else{
+ thdr.type = rhdr.type + 1;
+ thdr.fid = rhdr.fid;
+ }
+ thdr.tag = rhdr.tag;
+ if(dflag)
+ fprint(2, "vacfs:->%F\n", &thdr);
+ n = convS2Mu(&thdr, mdata, messagesize, dotu);
+ if(n <= BIT16SZ)
+ sysfatal("convS2Mu conversion error");
+ if(err)
+ vtfree(err);
+
+ if(write(mfd[1], mdata, n) != n)
+ sysfatal("mount write: %r");
+ }
+}
+
+int
+permf(VacFile *vf, char *user, int p)
+{
+ VacDir dir;
+ ulong perm;
+
+ if(vacfilegetdir(vf, &dir))
+ return 0;
+ perm = dir.mode & 0777;
+
+ if(noperm)
+ goto Good;
+ if((p*Pother) & perm)
+ goto Good;
+ if(strcmp(user, dir.gid)==0 && ((p*Pgroup) & perm))
+ goto Good;
+ if(strcmp(user, dir.uid)==0 && ((p*Powner) & perm))
+ goto Good;
+ vdcleanup(&dir);
+ return 0;
+Good:
+ vdcleanup(&dir);
+ return 1;
+}
+
+int
+perm(Fid *f, int p)
+{
+ return permf(f->file, f->user, p);
+}
+
+void
+vacshutdown(void)
+{
+ Fid *f;
+
+ for(f = fids; f; f = f->next) {
+ if(!f->busy)
+ continue;
+ rclunk(f);
+ }
+
+ vacfsclose(fs);
+ vthangup(conn);
+}
+