Browse Source

Plan 9 from Bell Labs 2008-06-16

David du Colombier 13 years ago
parent
commit
068c9c4b7b

+ 18 - 27
dist/replica/_plan9.db

@@ -7515,7 +7515,7 @@ sys/man/1/tweak - 664 sys sys 1196638936 4762
 sys/man/1/uniq - 664 sys sys 1196638933 995
 sys/man/1/units - 664 sys sys 1196638933 2046
 sys/man/1/uptime - 664 sys sys 1196638936 380
-sys/man/1/vac - 664 sys sys 1161727008 3244
+sys/man/1/vac - 664 sys sys 1213503568 4017
 sys/man/1/venti - 664 sys sys 1203807503 2321
 sys/man/1/vi - 664 sys sys 1196638936 2904
 sys/man/1/vnc - 664 sys sys 1196638933 4313
@@ -14324,27 +14324,18 @@ sys/src/cmd/va/l.s - 664 sys sys 944961340 12696
 sys/src/cmd/va/lex.c - 664 sys sys 1143293804 12095
 sys/src/cmd/va/mkfile - 664 sys sys 944961340 215
 sys/src/cmd/vac - 20000000775 sys sys 1055699701 0
-sys/src/cmd/vac/cache.c - 664 sys sys 1189020077 15843
-sys/src/cmd/vac/dat.h - 664 sys sys 1162951177 4029
-sys/src/cmd/vac/error.c - 664 sys sys 1036006057 633
-sys/src/cmd/vac/error.h - 664 sys sys 1036024048 327
-sys/src/cmd/vac/file.c - 664 sys sys 1168307519 19922
-sys/src/cmd/vac/fns.h - 664 sys sys 1036006061 1746
-sys/src/cmd/vac/fs.c - 664 sys sys 1189020075 2950
-sys/src/cmd/vac/mkfile - 664 sys sys 1189020070 435
-sys/src/cmd/vac/pack.c - 664 sys sys 1036006059 10126
-sys/src/cmd/vac/rtest.c - 664 sys sys 1019678787 1116
-sys/src/cmd/vac/source.c - 664 sys sys 1162327879 6767
-sys/src/cmd/vac/srcload.c - 664 sys sys 1036024047 4925
-sys/src/cmd/vac/stdinc.h - 664 sys sys 1189020065 122
-sys/src/cmd/vac/util.c - 664 sys sys 1019678787 930
-sys/src/cmd/vac/vac.c - 664 sys sys 1162350579 23803
-sys/src/cmd/vac/vac.h - 664 sys sys 1091904424 3598
-sys/src/cmd/vac/vacfs.c - 664 sys sys 1162402257 13621
-sys/src/cmd/vac/vactest.c - 664 sys sys 1036024047 2742
-sys/src/cmd/vac/vtdump.c - 664 sys sys 1162951782 8007
-sys/src/cmd/vac/vtread.c - 664 sys sys 1162951740 2016
-sys/src/cmd/vac/wtest.c - 664 sys sys 1019678788 766
+sys/src/cmd/vac/dat.h - 664 sys sys 1213498239 801
+sys/src/cmd/vac/error.c - 664 sys sys 1213498239 709
+sys/src/cmd/vac/error.h - 664 sys sys 1213498239 444
+sys/src/cmd/vac/file.c - 664 sys sys 1213558399 38892
+sys/src/cmd/vac/fns.h - 664 sys sys 1213558385 778
+sys/src/cmd/vac/mkfile - 664 sys sys 1213498239 333
+sys/src/cmd/vac/pack.c - 664 sys sys 1213558400 11975
+sys/src/cmd/vac/stdinc.h - 664 sys sys 1213498240 92
+sys/src/cmd/vac/unvac.c - 664 sys sys 1213504395 5186
+sys/src/cmd/vac/vac.c - 664 sys sys 1213558401 12310
+sys/src/cmd/vac/vac.h - 664 sys sys 1213558389 3987
+sys/src/cmd/vac/vacfs.c - 664 sys sys 1213558401 13323
 sys/src/cmd/vc - 20000000775 sys sys 1039727599 0
 sys/src/cmd/vc/cgen.c - 664 sys sys 1143241861 20147
 sys/src/cmd/vc/enam.c - 664 sys sys 1089299165 1093
@@ -15848,13 +15839,13 @@ sys/src/libventi/dtype.c - 664 sys sys 1177189441 1117
 sys/src/libventi/entry.c - 664 sys sys 1177189441 1779
 sys/src/libventi/fcall.c - 664 sys sys 1177189441 3765
 sys/src/libventi/fcallfmt.c - 664 sys sys 1177189441 1912
-sys/src/libventi/file.c - 664 sys sys 1177189441 23509
+sys/src/libventi/file.c - 664 sys sys 1213500942 23782
 sys/src/libventi/hangup.c - 664 sys sys 1177189441 547
-sys/src/libventi/log.c - 664 sys sys 1190092827 3936
-sys/src/libventi/mem.c - 664 sys sys 1177189441 1184
+sys/src/libventi/log.c - 664 sys sys 1213500951 3742
+sys/src/libventi/mem.c - 664 sys sys 1213503856 1176
 sys/src/libventi/mkfile - 664 sys sys 1188621815 512
 sys/src/libventi/packet.acid - 664 sys sys 1143389340 21446
-sys/src/libventi/packet.c - 664 sys sys 1177189441 15894
+sys/src/libventi/packet.c - 664 sys sys 1213500964 15896
 sys/src/libventi/parsescore.c - 664 sys sys 1177189441 719
 sys/src/libventi/queue.c - 664 sys sys 1177189441 1571
 sys/src/libventi/queue.h - 664 sys sys 1177189441 233
@@ -15862,7 +15853,7 @@ sys/src/libventi/root.c - 664 sys sys 1177189441 1218
 sys/src/libventi/rpc.acid - 664 sys sys 1143491745 12592
 sys/src/libventi/rpc.c - 664 sys sys 1177189442 3174
 sys/src/libventi/scorefmt.c - 664 sys sys 1177189442 248
-sys/src/libventi/send.c - 664 sys sys 1177189442 4395
+sys/src/libventi/send.c - 664 sys sys 1213500967 4396
 sys/src/libventi/server.c - 664 sys sys 1179957527 3693
 sys/src/libventi/srvhello.c - 664 sys sys 1177189442 833
 sys/src/libventi/strdup.c - 664 sys sys 1177189442 201

+ 18 - 27
dist/replica/plan9.db

@@ -7515,7 +7515,7 @@ sys/man/1/tweak - 664 sys sys 1196638936 4762
 sys/man/1/uniq - 664 sys sys 1196638933 995
 sys/man/1/units - 664 sys sys 1196638933 2046
 sys/man/1/uptime - 664 sys sys 1196638936 380
-sys/man/1/vac - 664 sys sys 1161727008 3244
+sys/man/1/vac - 664 sys sys 1213503568 4017
 sys/man/1/venti - 664 sys sys 1203807503 2321
 sys/man/1/vi - 664 sys sys 1196638936 2904
 sys/man/1/vnc - 664 sys sys 1196638933 4313
@@ -14324,27 +14324,18 @@ sys/src/cmd/va/l.s - 664 sys sys 944961340 12696
 sys/src/cmd/va/lex.c - 664 sys sys 1143293804 12095
 sys/src/cmd/va/mkfile - 664 sys sys 944961340 215
 sys/src/cmd/vac - 20000000775 sys sys 1055699701 0
-sys/src/cmd/vac/cache.c - 664 sys sys 1189020077 15843
-sys/src/cmd/vac/dat.h - 664 sys sys 1162951177 4029
-sys/src/cmd/vac/error.c - 664 sys sys 1036006057 633
-sys/src/cmd/vac/error.h - 664 sys sys 1036024048 327
-sys/src/cmd/vac/file.c - 664 sys sys 1168307519 19922
-sys/src/cmd/vac/fns.h - 664 sys sys 1036006061 1746
-sys/src/cmd/vac/fs.c - 664 sys sys 1189020075 2950
-sys/src/cmd/vac/mkfile - 664 sys sys 1189020070 435
-sys/src/cmd/vac/pack.c - 664 sys sys 1036006059 10126
-sys/src/cmd/vac/rtest.c - 664 sys sys 1019678787 1116
-sys/src/cmd/vac/source.c - 664 sys sys 1162327879 6767
-sys/src/cmd/vac/srcload.c - 664 sys sys 1036024047 4925
-sys/src/cmd/vac/stdinc.h - 664 sys sys 1189020065 122
-sys/src/cmd/vac/util.c - 664 sys sys 1019678787 930
-sys/src/cmd/vac/vac.c - 664 sys sys 1162350579 23803
-sys/src/cmd/vac/vac.h - 664 sys sys 1091904424 3598
-sys/src/cmd/vac/vacfs.c - 664 sys sys 1162402257 13621
-sys/src/cmd/vac/vactest.c - 664 sys sys 1036024047 2742
-sys/src/cmd/vac/vtdump.c - 664 sys sys 1162951782 8007
-sys/src/cmd/vac/vtread.c - 664 sys sys 1162951740 2016
-sys/src/cmd/vac/wtest.c - 664 sys sys 1019678788 766
+sys/src/cmd/vac/dat.h - 664 sys sys 1213498239 801
+sys/src/cmd/vac/error.c - 664 sys sys 1213498239 709
+sys/src/cmd/vac/error.h - 664 sys sys 1213498239 444
+sys/src/cmd/vac/file.c - 664 sys sys 1213558399 38892
+sys/src/cmd/vac/fns.h - 664 sys sys 1213558385 778
+sys/src/cmd/vac/mkfile - 664 sys sys 1213498239 333
+sys/src/cmd/vac/pack.c - 664 sys sys 1213558400 11975
+sys/src/cmd/vac/stdinc.h - 664 sys sys 1213498240 92
+sys/src/cmd/vac/unvac.c - 664 sys sys 1213504395 5186
+sys/src/cmd/vac/vac.c - 664 sys sys 1213558401 12310
+sys/src/cmd/vac/vac.h - 664 sys sys 1213558389 3987
+sys/src/cmd/vac/vacfs.c - 664 sys sys 1213558401 13323
 sys/src/cmd/vc - 20000000775 sys sys 1039727599 0
 sys/src/cmd/vc/cgen.c - 664 sys sys 1143241861 20147
 sys/src/cmd/vc/enam.c - 664 sys sys 1089299165 1093
@@ -15848,13 +15839,13 @@ sys/src/libventi/dtype.c - 664 sys sys 1177189441 1117
 sys/src/libventi/entry.c - 664 sys sys 1177189441 1779
 sys/src/libventi/fcall.c - 664 sys sys 1177189441 3765
 sys/src/libventi/fcallfmt.c - 664 sys sys 1177189441 1912
-sys/src/libventi/file.c - 664 sys sys 1177189441 23509
+sys/src/libventi/file.c - 664 sys sys 1213500942 23782
 sys/src/libventi/hangup.c - 664 sys sys 1177189441 547
-sys/src/libventi/log.c - 664 sys sys 1190092827 3936
-sys/src/libventi/mem.c - 664 sys sys 1177189441 1184
+sys/src/libventi/log.c - 664 sys sys 1213500951 3742
+sys/src/libventi/mem.c - 664 sys sys 1213503856 1176
 sys/src/libventi/mkfile - 664 sys sys 1188621815 512
 sys/src/libventi/packet.acid - 664 sys sys 1143389340 21446
-sys/src/libventi/packet.c - 664 sys sys 1177189441 15894
+sys/src/libventi/packet.c - 664 sys sys 1213500964 15896
 sys/src/libventi/parsescore.c - 664 sys sys 1177189441 719
 sys/src/libventi/queue.c - 664 sys sys 1177189441 1571
 sys/src/libventi/queue.h - 664 sys sys 1177189441 233
@@ -15862,7 +15853,7 @@ sys/src/libventi/root.c - 664 sys sys 1177189441 1218
 sys/src/libventi/rpc.acid - 664 sys sys 1143491745 12592
 sys/src/libventi/rpc.c - 664 sys sys 1177189442 3174
 sys/src/libventi/scorefmt.c - 664 sys sys 1177189442 248
-sys/src/libventi/send.c - 664 sys sys 1177189442 4395
+sys/src/libventi/send.c - 664 sys sys 1213500967 4396
 sys/src/libventi/server.c - 664 sys sys 1179957527 3693
 sys/src/libventi/srvhello.c - 664 sys sys 1177189442 833
 sys/src/libventi/strdup.c - 664 sys sys 1177189442 201

+ 38 - 0
dist/replica/plan9.log

@@ -19847,3 +19847,41 @@
 1213362005 0 c sys/games/lib/fortunes - 664 sys sys 1213360784 269499
 1213470005 0 c sys/man/1/vac - 664 sys sys 1213469074 4017
 1213475409 0 c sys/man/1/vac - 664 sys sys 1161727008 3244
+1213502404 0 c sys/src/libventi/file.c - 664 sys sys 1213500942 23782
+1213502404 1 c sys/src/libventi/log.c - 664 sys sys 1213500951 3742
+1213502404 2 c sys/src/libventi/packet.c - 664 sys sys 1213500964 15896
+1213502404 3 c sys/src/libventi/send.c - 664 sys sys 1213500967 4396
+1213504204 0 c sys/man/1/vac - 664 sys sys 1213503568 4017
+1213504204 1 c sys/src/cmd/vac/dat.h - 664 sys sys 1213498239 801
+1213504204 2 c sys/src/cmd/vac/error.c - 664 sys sys 1213498239 709
+1213504204 3 c sys/src/cmd/vac/error.h - 664 sys sys 1213498239 444
+1213504204 4 c sys/src/cmd/vac/file.c - 664 sys sys 1213501512 37620
+1213504204 5 c sys/src/cmd/vac/fns.h - 664 sys sys 1213498239 738
+1213504204 6 c sys/src/cmd/vac/mkfile - 664 sys sys 1213498239 333
+1213504204 7 c sys/src/cmd/vac/pack.c - 664 sys sys 1213498240 11976
+1213504204 8 c sys/src/cmd/vac/stdinc.h - 664 sys sys 1213498240 92
+1213504204 9 a sys/src/cmd/vac/unvac.c - 664 sys sys 1213498240 5455
+1213504204 10 c sys/src/cmd/vac/vac.c - 664 sys sys 1213498240 12468
+1213504204 11 c sys/src/cmd/vac/vac.h - 664 sys sys 1213503433 3824
+1213504204 12 c sys/src/cmd/vac/vacfs.c - 664 sys sys 1213498240 14179
+1213504204 13 c sys/src/cmd/vac/vtdump.c - 664 sys sys 1213498240 6977
+1213504204 14 c sys/src/libventi/mem.c - 664 sys sys 1213503856 1176
+1213504204 15 d sys/src/cmd/vac/wtest.c - 664 sys sys 1019678788 0
+1213504204 16 d sys/src/cmd/vac/vtread.c - 664 sys sys 1162951740 0
+1213504204 17 d sys/src/cmd/vac/vactest.c - 664 sys sys 1036024047 0
+1213504204 18 d sys/src/cmd/vac/util.c - 664 sys sys 1019678787 0
+1213504204 19 d sys/src/cmd/vac/srcload.c - 664 sys sys 1036024047 0
+1213504204 20 d sys/src/cmd/vac/source.c - 664 sys sys 1162327879 0
+1213504204 21 d sys/src/cmd/vac/rtest.c - 664 sys sys 1019678787 0
+1213504204 22 d sys/src/cmd/vac/fs.c - 664 sys sys 1189020075 0
+1213504204 23 d sys/src/cmd/vac/cache.c - 664 sys sys 1189020077 0
+1213506005 0 c sys/src/cmd/vac/unvac.c - 664 sys sys 1213504395 5186
+1213506005 1 c sys/src/cmd/vac/vac.c - 664 sys sys 1213504573 11455
+1213506005 2 c sys/src/cmd/vac/vacfs.c - 664 sys sys 1213504498 13305
+1213560004 0 c sys/src/cmd/vac/file.c - 664 sys sys 1213558399 38892
+1213560004 1 c sys/src/cmd/vac/fns.h - 664 sys sys 1213558385 778
+1213560004 2 c sys/src/cmd/vac/pack.c - 664 sys sys 1213558400 11975
+1213560004 3 c sys/src/cmd/vac/vac.c - 664 sys sys 1213558401 12310
+1213560004 4 c sys/src/cmd/vac/vac.h - 664 sys sys 1213558389 3987
+1213560004 5 c sys/src/cmd/vac/vacfs.c - 664 sys sys 1213558401 13323
+1213560004 6 d sys/src/cmd/vac/vtdump.c - 664 sys sys 1213498240 0

+ 39 - 1
sys/man/1/vac

@@ -1,6 +1,6 @@
 .TH VAC 1
 .SH NAME
-vac \- create a vac archive on Venti
+vac, unvac \- create, extract a vac archive on Venti
 .SH SYNOPSIS
 .B vac
 [
@@ -25,6 +25,18 @@ vac \- create a vac archive on Venti
 .I host
 ]
 .I file ...
+.PP
+.B unvac
+[
+.B -Tctv
+] [
+.B -h
+.I host
+]
+.I vacfile
+[
+.I file ...
+]
 .SH DESCRIPTION
 .I Vac
 creates an archival copy of Plan 9 file trees on Venti. It can be used
@@ -123,6 +135,32 @@ Print out various statistics on standard error.
 .B -v
 Produce more verbose output on standard error, including the name of the files added to the archive
 and the vac archives that are expanded and merged.
+.PP
+.I Unvac
+lists or extracts files stored in the vac archive
+.IR vacfile ,
+which can be either a vac archive string in the format
+given above or the name of a file containing one.
+If
+.I file
+arguments are given, only those files or directories
+will be extracted.
+The options are:
+.TP
+.B -T
+Set the modification time on extracted files
+to the time listed in the archive.
+.TP
+.B -c
+Write extracted files to standard output instead of creating a file.
+.TP
+.B -t
+Print a list of the files to standard output rather than extracting them.
+.TP
+.B -v
+If extracting files, print the name of each file and directory
+to standard error.
+If listing files, print metadata in addition to the names.
 .SH SOURCE
 .B /sys/src/cmd/vac
 .SH "SEE ALSO"

+ 0 - 877
sys/src/cmd/vac/cache.c

@@ -1,877 +0,0 @@
-#include "stdinc.h"
-#include "vac.h"
-#include "dat.h"
-#include "fns.h"
-
-typedef struct Label Label;
-
-enum {
-	BadHeap = ~0,
-};
-
-/*
- * the plan is to store data to the cache in c->size blocks
- * with the block zero extended to fill it out.  When writing to
- * venti, the block will be zero truncated.  The walker will also check
- * that the block fits within psize or dsize as the case may be.
- */
-
-struct Cache
-{
-	VtLock	*lk;
-	VtSession *z;
-	u32int	now;			/* ticks for usage timestamps */
-	int	size;			/* max. size of any block; allocated to each block */
-	Lump	**heads;		/* hash table for finding address */
-	int	nheap;			/* number of available victims */
-	Lump	**heap;			/* heap for locating victims */
-	long	nblocks;		/* number of blocks allocated */
-	Lump	*blocks;		/* array of block descriptors */
-	u8int	*mem;			/* memory for all block descriptors */
-	Lump	*free;			/* free list of lumps */
-
-	long hashSize;
-};
-
-/*
- * the tag for a block is hash(index, parent tag)
- */
-
-struct Label {
-	uchar gen[4];
-	uchar state;
-	uchar type;		/* top bit indicates it is part of a directory */
-	uchar tag[4];		/* tag of file it is in */
-};
-
-
-static char ENoDir[] = "directory entry is not allocated";
-
-static void fixHeap(int si, Lump *b);
-static int upHeap(int i, Lump *b);
-static int downHeap(int i, Lump *b);
-static char	*lumpState(int);
-static void	lumpSetState(Lump *u, int state);
-
-Cache *
-cacheAlloc(VtSession *z, int blockSize, long nblocks)
-{
-	int i;
-	Cache *c;
-	Lump *b;
-
-	c = vtMemAllocZ(sizeof(Cache));
-	
-	c->lk = vtLockAlloc();
-	c->z = z;
-	c->size = blockSize;
-	c->nblocks = nblocks;
-	c->hashSize = nblocks;
-	c->heads = vtMemAllocZ(c->hashSize*sizeof(Lump*));
-	c->heap = vtMemAllocZ(nblocks*sizeof(Lump*));
-	c->blocks = vtMemAllocZ(nblocks*sizeof(Lump));
-	c->mem = vtMemAllocZ(nblocks * blockSize);
-	for(i = 0; i < nblocks; i++){
-		b = &c->blocks[i];
-		b->lk = vtLockAlloc();
-		b->c = c;
-		b->data = &c->mem[i * blockSize];
-		b->addr = i+1;
-		b->state = LumpFree;
-		b->heap = BadHeap;
-		b->next = c->free;
-		c->free = b;
-	}
-	c->nheap = 0;
-
-	return c;
-}
-
-long
-cacheGetSize(Cache *c)
-{
-	return c->nblocks;
-}
-
-int
-cacheGetBlockSize(Cache *c)
-{
-	return c->size;
-}
-
-int
-cacheSetSize(Cache *c, long nblocks)
-{
-	USED(c);
-	USED(nblocks);
-	return 0;
-}
-
-void
-cacheFree(Cache *c)
-{
-	int i;
-
-	for(i = 0; i < c->nblocks; i++){
-		assert(c->blocks[i].ref == 0);
-		vtLockFree(c->blocks[i].lk);
-	}
-	vtMemFree(c->heads);
-	vtMemFree(c->blocks);
-	vtMemFree(c->mem);
-	vtMemFree(c);
-}
-
-static u32int
-hash(Cache *c, uchar score[VtScoreSize], int type)
-{
-	u32int h;
-	uchar *p = score + VtScoreSize-4;
-
-	h = (p[0] << 24) | (p[1] << 16) | (p[2] << 8) | p[3];
-	h += type;
-	return h % c->hashSize;
-}
-
-static void
-findLump(Cache *c, Lump *bb)
-{
-	Lump *b, *last;
-	int h;
-
-	last = nil;
-	h = hash(c, bb->score, bb->type);
-	for(b = c->heads[h]; b != nil; b = b->next){
-		if(last != b->prev)
-			vtFatal("bad prev link");
-		if(b == bb)
-			return;
-		last = b;
-	}
-	vtFatal("block missing from hash table");
-}
-
-void
-cacheCheck(Cache *c)
-{
-	u32int size, now;
-	int i, k, refed, free;
-	static uchar zero[VtScoreSize];
-	Lump *p;
-
-	size = c->size;
-	now = c->now;
-
-	free = 0;
-	for(p=c->free; p; p=p->next)
-		free++;
-	for(i = 0; i < c->nheap; i++){
-		if(c->heap[i]->heap != i)
-			vtFatal("mis-heaped at %d: %d", i, c->heap[i]->heap);
-		if(i > 0 && c->heap[(i - 1) >> 1]->used2 - now > c->heap[i]->used2 - now)
-			vtFatal("bad heap ordering");
-		k = (i << 1) + 1;
-		if(k < c->nheap && c->heap[i]->used2 - now > c->heap[k]->used2 - now)
-			vtFatal("bad heap ordering");
-		k++;
-		if(k < c->nheap && c->heap[i]->used2 - now > c->heap[k]->used2 - now)
-			vtFatal("bad heap ordering");
-	}
-
-	refed = 0;
-	for(i = 0; i < c->nblocks; i++){
-		if(c->blocks[i].data != &c->mem[i * size])
-			vtFatal("mis-blocked at %d", i);
-		if(c->blocks[i].ref && c->blocks[i].heap == BadHeap){
-			refed++;
-		}
-		if(memcmp(zero, c->blocks[i].score, VtScoreSize))
-			findLump(c, &c->blocks[i]);
-	}
-if(refed > 0)fprint(2, "cacheCheck: nheap %d refed %d free %d\n", c->nheap, refed, free);
-	assert(c->nheap + refed + free == c->nblocks);
-	refed = 0;
-	for(i = 0; i < c->nblocks; i++){
-		if(c->blocks[i].ref) {
-if(1)fprint(2, "%d %V %d %s\n", c->blocks[i].type, c->blocks[i].score, c->blocks[i].ref, lumpState(c->blocks[i].state));
-			refed++;
-		}
-	}
-if(refed > 0)fprint(2, "cacheCheck: in used %d\n", refed);
-}
-
-/*
- * delete an arbitrary block from the heap
- */
-static void
-delHeap(Lump *db)
-{
-	fixHeap(db->heap, db->c->heap[--db->c->nheap]);
-	db->heap = BadHeap;
-}
-
-static void
-fixHeap(int si, Lump *b)
-{
-	int i;
-
-	i = upHeap(si, b);
-	if(i == si)
-		downHeap(i, b);
-}
-
-static int
-upHeap(int i, Lump *b)
-{
-	Lump *bb;
-	u32int now;
-	int p;
-	Cache *c;
-	
-	c = b->c;
-	now = c->now;
-	for(; i != 0; i = p){
-		p = (i - 1) >> 1;
-		bb = c->heap[p];
-		if(b->used2 - now >= bb->used2 - now)
-			break;
-		c->heap[i] = bb;
-		bb->heap = i;
-	}
-	c->heap[i] = b;
-	b->heap = i;
-
-	return i;
-}
-
-static int
-downHeap(int i, Lump *b)
-{
-	Lump *bb;
-	u32int now;
-	int k;
-	Cache *c;
-	
-	c = b->c;
-	now = c->now;
-	for(; ; i = k){
-		k = (i << 1) + 1;
-		if(k >= c->nheap)
-			break;
-		if(k + 1 < c->nheap && c->heap[k]->used2 - now > c->heap[k + 1]->used2 - now)
-			k++;
-		bb = c->heap[k];
-		if(b->used2 - now <= bb->used2 - now)
-			break;
-		c->heap[i] = bb;
-		bb->heap = i;
-	}
-	c->heap[i] = b;
-	b->heap = i;
-	return i;
-}
-
-
-/* called with c->lk held */
-Lump *
-cacheBumpLump(Cache *c)
-{
-	Lump *b;
-
-	/*
-	 * missed: locate the block with the oldest second to last use.
-	 * remove it from the heap, and fix up the heap.
-	 */
-	if(c->free) {
-		b = c->free;
-		c->free = b->next;
-	} else {
-		for(;;){
-			if(c->nheap == 0) {
-				cacheCheck(c);
-				assert(0);
-				return nil;
-			}
-			b = c->heap[0];
-			delHeap(b);
-			if(b->ref == 0)
-				break;
-		}
-
-		/*
-		 * unchain the block from hash chain
-		 */
-		if(b->prev == nil)
-			c->heads[hash(c, b->score, b->type)] = b->next;
-		else
-			b->prev->next = b->next;
-		if(b->next != nil)
-			b->next->prev = b->prev;
-
-	}
-
-	/*
-	 * the new block has no last use, so assume it happens sometime in the middle
-	 */
-	b->used = (b->used2 + c->now) / 2;
-	b->asize = 0;
-
-	return b;
-}
-
-Lump *
-cacheAllocLump(Cache *c, int type, int size, int dir)
-{
-	Lump *b;
-	ulong h;
-
-	assert(size <= c->size);
-
-again:
-	vtLock(c->lk);
-	b = cacheBumpLump(c);
-	if(b == nil) {
-		vtUnlock(c->lk);
-fprint(2, "cache is full\n");
-		/* XXX should be better */
-		sleep(100);
-		goto again;
-	}
-
-	vtLock(b->lk);
-
-	assert(b->ref == 0);
-	b->ref++;
-	b->used2 = b->used;
-	b->used = c->now++;
-
-	/* convert addr into score */
-	memset(b->score, 0, VtScoreSize-4);
-	b->score[VtScoreSize-4] = b->addr>>24;
-	b->score[VtScoreSize-3] = b->addr>>16;
-	b->score[VtScoreSize-2] = b->addr>>8;
-	b->score[VtScoreSize-1] = b->addr;
-	
-	b->dir = dir;
-	b->type = type;
-	b->gen = 0;
-	b->asize = size;
-	b->state = LumpFree;
-
-	h = hash(c, b->score, b->type);
-
-	/* chain onto correct hash */
-	b->next = c->heads[h];
-	c->heads[h] = b;
-	if(b->next != nil)
-		b->next->prev = b;
-	b->prev = nil;
-
-	vtUnlock(c->lk);
-
-	vtZeroExtend(type, b->data, 0, size);
-	lumpSetState(b, LumpActive);
-
-	return b;
-}
-
-int
-scoreIsLocal(uchar score[VtScoreSize])
-{
-	static uchar zero[VtScoreSize];
-	
-	return memcmp(score, zero, VtScoreSize-4) == 0;
-}
-
-Lump *
-cacheGetLump(Cache *c, uchar score[VtScoreSize], int type, int size)
-{
-	Lump *b;
-	ulong h;
-	int n;
-	static uchar zero[VtScoreSize];
-
-	assert(size <= c->size);
-
-	h = hash(c, score, type);
-
-again:
-	/*
-	 * look for the block in the cache
-	 */
-	vtLock(c->lk);
-	for(b = c->heads[h]; b != nil; b = b->next){
-		if(memcmp(b->score, score, VtScoreSize) == 0 && b->type == type)
-			goto found;
-	}
-
-	/* should not be looking for a temp block */
-	if(scoreIsLocal(score)) {
-		if(memcmp(score, zero, VtScoreSize) == 0)
-			vtSetError("looking for zero score");
-		else
-			vtSetError("missing local block");
-		vtUnlock(c->lk);
-		return nil;
-	}
-
-	b = cacheBumpLump(c);
-	if(b == nil) {
-		vtUnlock(c->lk);
-		sleep(100);
-		goto again;
-	}
-
-	/* chain onto correct hash */
-	b->next = c->heads[h];
-	c->heads[h] = b;
-	if(b->next != nil)
-		b->next->prev = b;
-	b->prev = nil;
-
-	memmove(b->score, score, VtScoreSize);	
-	b->type = type;
-	b->state = LumpFree;
-
-found:
-	b->ref++;
-	b->used2 = b->used;
-	b->used = c->now++;
-	if(b->heap != BadHeap)
-		fixHeap(b->heap, b);
-
-	vtUnlock(c->lk);
-
-	vtLock(b->lk);
-	if(b->state != LumpFree)
-		return b;
-	
-	n = vtRead(c->z, score, type, b->data, size);
-	if(n < 0) {
-		fprint(2, "read %V: %r\n", score);
-		lumpDecRef(b, 1);
-		return nil;
-	}
-	if(!vtSha1Check(score, b->data, n)) {
-		vtSetError("vtSha1Check failed");
-		lumpDecRef(b, 1);
-		return nil;
-	}
-	vtZeroExtend(type, b->data, n, size);
-	b->asize = size;
-	lumpSetState(b, LumpVenti);
-
-	return b;
-}
-
-static char *
-lumpState(int state)
-{
-	switch(state) {
-	default:
-		return "Unknown!!";
-	case LumpFree:
-		return "Free";
-	case LumpActive:
-		return "Active";
-	case LumpSnap:
-		return "Snap";
-	case LumpZombie:
-		return "Zombie";
-	case LumpVenti:
-		return "Venti";
-	}
-}
-
-static void
-lumpSetState(Lump *u, int state)
-{
-//	if(u->state != LumpFree)
-//		fprint(2, "%V: %s -> %s\n", u->score, lumpState(u->state), lumpState(state));
-	u->state = state;
-}
-	
-int
-lumpGetScore(Lump *u, int offset, uchar score[VtScoreSize])
-{
-	uchar *sp;
-	VtRoot root;
-	VtEntry dir;
-
-	vtLock(u->lk);
-
-	switch(u->type) {
-	default:
-		vtSetError("bad type");
-		goto Err;
-	case VtPointerType0:
-	case VtPointerType1:
-	case VtPointerType2:
-	case VtPointerType3:
-	case VtPointerType4:
-	case VtPointerType5:
-	case VtPointerType6:
-		if((offset+1)*VtScoreSize > u->asize)
-			sp = nil;
-		else
-			sp = u->data + offset*VtScoreSize;
-		break;
-	case VtRootType:
-		if(u->asize < VtRootSize) {
-			vtSetError("runt root block");
-			goto Err;
-		}
-		if(!vtRootUnpack(&root, u->data))
-			goto Err;
-		sp = root.score;
-		break;
-	case VtDirType:
-		if((offset+1)*VtEntrySize > u->asize) {
-			vtSetError(ENoDir);
-			goto Err;
-		}
-		if(!vtEntryUnpack(&dir, u->data, offset))
-			goto Err;
-		if(!dir.flags & VtEntryActive) {
-			vtSetError(ENoDir);
-			goto Err;
-		}
-		sp = dir.score;
-		break;
-	}
-
-	if(sp == nil)
-		memmove(score, vtZeroScore, VtScoreSize);
-	else
-		memmove(score, sp, VtScoreSize);
-
-	vtUnlock(u->lk);
-	return !scoreIsLocal(score);
-Err:
-	vtUnlock(u->lk);
-	return 0;
-}
-
-Lump *
-lumpWalk(Lump *u, int offset, int type, int size, int readOnly, int lock)
-{
-	Lump *v, *vv;
-	Cache *c;
-	uchar score[VtScoreSize], *sp;
-	VtRoot root;
-	VtEntry dir;
-	int split, isdir;
-
-	c = u->c;
-	vtLock(u->lk);
-
-Again:
-	v = nil;
-	vv = nil;
-
-	isdir = u->dir;
-	switch(u->type) {
-	default:
-		vtSetError("bad type");
-		goto Err;
-	case VtPointerType0:
-	case VtPointerType1:
-	case VtPointerType2:
-	case VtPointerType3:
-	case VtPointerType4:
-	case VtPointerType5:
-	case VtPointerType6:
-		if((offset+1)*VtScoreSize > u->asize)
-			sp = nil;
-		else
-			sp = u->data + offset*VtScoreSize;
-		break;
-	case VtRootType:
-		if(u->asize < VtRootSize) {
-			vtSetError("runt root block");
-			goto Err;
-		}
-		if(!vtRootUnpack(&root, u->data))
-			goto Err;
-		sp = root.score;
-		break;
-	case VtDirType:
-		if((offset+1)*VtEntrySize > u->asize) {
-			vtSetError(ENoDir);
-			goto Err;
-		}
-		if(!vtEntryUnpack(&dir, u->data, offset))
-			goto Err;
-		if(!(dir.flags & VtEntryActive)) {
-			vtSetError(ENoDir);
-			goto Err;
-		}
-		isdir = (dir.flags & VtEntryDir) != 0;
-//		sp = dir.score;
-		sp = u->data + offset*VtEntrySize + 20;
-		break;
-	}
-
-	if(sp == nil)
-		memmove(score, vtZeroScore, VtScoreSize);
-	else
-		memmove(score, sp, VtScoreSize);
-	
-	vtUnlock(u->lk);
-
-
-if(0)fprint(2, "lumpWalk: %V:%s %d:%d-> %V:%d\n", u->score, lumpState(u->state), u->type, offset, score, type);
-	v = cacheGetLump(c, score, type, size);
-	if(v == nil)
-		return nil;
-
-	split = 1;
-	if(readOnly)
-		split = 0;
-
-	switch(v->state) {
-	default:
-		assert(0);
-	case LumpFree:
-fprint(2, "block is free %V!\n", v->score);
-		vtSetError("phase error");
-		goto Err2;
-	case LumpActive:	
-		if(v->gen < u->gen) {
-print("LumpActive gen\n");
-			lumpSetState(v, LumpSnap);
-			v->gen = u->gen;
-		} else
-			split = 0;
-		break;
-	case LumpSnap:
-	case LumpVenti:
-		break;
-	}
-	
-	/* easy case */
-	if(!split) {
-		if(!lock)
-			vtUnlock(v->lk);
-		return v;
-	}
-
-	if(sp == nil) {
-		vtSetError("bad offset");
-		goto Err2;
-	}
-
-	vv = cacheAllocLump(c, v->type, size, isdir);
-	/* vv is locked */
-	vv->gen = u->gen;
-	memmove(vv->data, v->data, v->asize);
-if(0)fprint(2, "split %V into %V\n", v->score, vv->score);
-
-	lumpDecRef(v, 1);
-	v = nil;
-
-	vtLock(u->lk);
-	if(u->state != LumpActive) {
-		vtSetError("bad parent state: can not happen");
-		goto Err;
-	}
-
-	/* check that nothing changed underfoot */
-	if(memcmp(sp, score, VtScoreSize) != 0) {
-		lumpDecRef(vv, 1);
-fprint(2, "lumpWalk: parent changed under foot\n");
-		goto Again;
-	}
-
-	/* XXX - hold Active blocks up - will go eventually */
-	lumpIncRef(vv);
-
-	/* change the parent */
-	memmove(sp, vv->score, VtScoreSize);
-	
-	vtUnlock(u->lk);
-	
-	if(!lock)
-		vtUnlock(vv->lk);
-	return vv;
-Err:
-	vtUnlock(u->lk);
-	lumpDecRef(v, 0);
-	lumpDecRef(vv, 1);
-	return nil;
-Err2:
-	lumpDecRef(v, 1);
-	return nil;
-	
-}
-
-void
-lumpFreeEntry(Lump *u, int entry)
-{
-	uchar score[VtScoreSize];
-	int type;
-	ulong gen;
-	VtEntry dir;
-	Cache *c;
-
-	c = u->c;
-	vtLock(u->lk);
-	if(u->state == LumpVenti)
-		goto Exit;
-
-	switch(u->type) {
-	default:
-		fprint(2, "freeing bad lump type: %d\n", u->type);
-		return;
-	case VtPointerType0:
-		if((entry+1)*VtScoreSize > u->asize)
-			goto Exit;
-		memmove(score, u->data + entry*VtScoreSize, VtScoreSize);
-		memmove(u->data + entry*VtScoreSize, vtZeroScore, VtScoreSize);
-		type = u->dir?VtDirType:VtDataType;
-		break;
-	case VtPointerType1:
-	case VtPointerType2:
-	case VtPointerType3:
-	case VtPointerType4:
-	case VtPointerType5:
-	case VtPointerType6:
-		if((entry+1)*VtScoreSize > u->asize)
-			goto Exit;
-		memmove(score, u->data + entry*VtScoreSize, VtScoreSize);
-		memmove(u->data + entry*VtScoreSize, vtZeroScore, VtScoreSize);
-		type = u->type-1;
-		break;
-	case VtDirType:
-		if((entry+1)*VtEntrySize > u->asize)
-			goto Exit;
-		if(!vtEntryUnpack(&dir, u->data, entry))
-			goto Exit;
-		if(!dir.flags & VtEntryActive)
-			goto Exit;
-		gen = dir.gen;
-		if(gen != ~0)
-			gen++;
-		if(dir.depth == 0)
-			type = (dir.flags&VtEntryDir)?VtDirType:VtDataType;
-		else
-			type = VtPointerType0 + dir.depth - 1;
-		memmove(score, dir.score, VtScoreSize);
-		memset(&dir, 0, sizeof(dir));
-		dir.gen = gen;
-		vtEntryPack(&dir, u->data, entry);
-		break;
-	case VtDataType:
-		type = VtErrType;
-		break;
-	}
-	vtUnlock(u->lk);
-	if(type == VtErrType || !scoreIsLocal(score))
-		return;
-
-	u = cacheGetLump(c, score, type, c->size);
-	if(u == nil)
-		return;
-	lumpDecRef(u, 1);
-	/* XXX remove extra reference */
-	lumpDecRef(u, 0);
-	return;
-Exit:
-	vtUnlock(u->lk);
-	return;
-
-}
-
-void
-lumpCleanup(Lump *u)
-{
-	int i, n;
-
-	switch(u->type) {
-	default:
-		return;
-	case VtPointerType0:
-	case VtPointerType1:
-	case VtPointerType2:
-	case VtPointerType3:
-	case VtPointerType4:
-	case VtPointerType5:
-	case VtPointerType6:
-		n = u->asize/VtScoreSize;
-		break;	
-	case VtDirType:
-		n = u->asize/VtEntrySize;
-		break;
-	}
-
-	for(i=0; i<n; i++)
-		lumpFreeEntry(u, i);
-}
-
-
-void
-lumpDecRef(Lump *b, int unlock)
-{
-	int i;
-	Cache *c;
-
-	if(b == nil)
-		return;
-
-	if(unlock)
-		vtUnlock(b->lk);
-
-	c = b->c;
-	vtLock(c->lk);
-	if(--b->ref > 0) {
-		vtUnlock(c->lk);
-		return;
-	}
-	assert(b->ref == 0);
-
-	switch(b->state) {
-	default:
-		fprint(2, "bad state: %s\n", lumpState(b->state));
-		assert(0);
-	case LumpActive:
-		/* hack - but will do for now */
-		b->ref++;
-		vtUnlock(c->lk);
-		lumpCleanup(b);
-		vtLock(c->lk);
-		b->ref--;
-		lumpSetState(b, LumpFree);
-		break;
-	case LumpZombie:
-		lumpSetState(b, LumpFree);
-		break;
-	case LumpFree:
-	case LumpVenti:
-		break;
-	}
-
-	/*
-	 * reinsert in the free heap
-	 */
-	if(b->heap == BadHeap) {
-		i = upHeap(c->nheap++, b);
-		c->heap[i] = b;
-		b->heap = i;
-	}
-
-	vtUnlock(c->lk);
-}
-
-Lump *
-lumpIncRef(Lump *b)
-{
-	Cache *c;
-
-	c = b->c;
-
-	vtLock(c->lk);
-	assert(b->ref > 0);
-	b->ref++;
-	vtUnlock(c->lk);
-	return b;
-}

+ 15 - 135
sys/src/cmd/vac/dat.h

@@ -1,62 +1,24 @@
-typedef struct Source Source;
-typedef struct VacFile VacFile;
 typedef struct MetaBlock MetaBlock;
 typedef struct MetaEntry MetaEntry;
-typedef struct Lump Lump;
-typedef struct Cache Cache;
-#pragma incomplete Cache
-
-enum {
-	NilBlock	= (~0UL),
-	MaxBlock	= (1UL<<31),
-};
 
+#define MaxBlock (1UL<<31)
 
-struct VacFS {
-	int ref;
-	
-	/* need a read write lock? */
-
-	uchar score[VtScoreSize];
-	VacFile *root;
-	
-	VtSession *z;
-	int readOnly;
-	int bsize;		/* maximum block size */
-	uvlong qid;		/* next qid */
-	Cache *cache;
+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 Source {
-	VtLock *lk;
-
-	Cache *cache;	/* immutable */
-	int readOnly;	/* immutable */
-
-	VacFile *vf;	/* pointer back */
-
-	Lump *lump;	/* lump containing venti dir entry */
-	ulong block;	/* block number within parent: immutable */
-	int entry;	/* which entry in the block: immutable */
-
-	/* most of a VtEntry, except the score */
-	ulong gen;	/* generation: immutable */
-	int dir;	/* dir flags: immutable */
-	int depth;	/* number of levels of pointer blocks */
-	int psize;	/* pointer block size: immutable */
-	int dsize;	/* data block size: immutable */
-	uvlong size;	/* size in bytes of file */
-
-	int epb;	/* dir entries per block = dize/VtEntrySize: immutable */
-};
-
-struct MetaEntry {
+struct MetaEntry
+{
 	uchar *p;
 	ushort size;
 };
 
-struct MetaBlock {
+struct MetaBlock
+{
 	int maxsize;		/* size of block */
 	int size;		/* size used */
 	int free;		/* free space within used size */
@@ -66,93 +28,11 @@ struct MetaBlock {
 	uchar *buf;
 };
 
-/*
- * contains a one block buffer
- * to avoid problems of the block changing underfoot
- * and to enable an interface that supports unget.
- */
-struct VacDirEnum {
+struct VacDirEnum
+{
 	VacFile *file;
-	
-	ulong block;	/* current block */
-	MetaBlock mb;	/* parsed version of block */
-	int index;	/* index in block */
-};
-
-/* Lump states */
-enum {
-	LumpFree,
-	LumpVenti,	/* on venti server: score > 2^32: just a cached copy */
-	LumpActive,	/* active */
-	LumpActiveRO,	/* active: read only block */
-	LumpActiveA,	/* active: achrived */
-	LumpSnap,	/* snapshot: */
-	LumpSnapRO,	/* snapshot: read only */
-	LumpSnapA,	/* snapshot: achived */
-	LumpZombie,	/* block with no pointer to it: waiting to be freed */
-	
-	LumpMax
-};
-
-/*
- * Each lump has a state and generation
- * The following invariants are maintained
- * 	Each lump has no more than than one parent per generation
- * 	For Active*, no child has a parent of a greater generation
- *	For Snap*, there is a snap parent of given generation and there are
- *		no parents of greater gen - implies no children of a greater gen
- *	For *RO, the lump is fixed - no change can be made - all pointers
- *		are valid venti addresses
- *	For *A, the lump is on the venti server
- *	There are no pointers to Zombie lumps
- *
- * Transitions
- *	Archiver at generation g
- *	Mutator at generation h
- *	
- *	Want to modify a lump
- *		Venti: create new Active(h)
- *		Active(x): x == h: do nothing
- *		Acitve(x): x < h: change to Snap(h-1) + add Active(h)
- *		ActiveRO(x): change to SnapRO(h-1) + add Active(h)
- *		ActiveA(x): add Active(h)
- *		Snap*(x): should not occur
- *		Zombie(x): should not occur
- *	Want to archive
- *		Active(x): x != g: should never happen
- *		Active(x): x == g fix children and free them: move to ActoveRO(g);
- *		ActiveRO(x): x != g: should never happen
- *		ActiveRO(x): x == g: wait until it hits ActiveA or SnapA
- *		ActiveA(x): done
- *		Active(x): x < g: should never happen
- *		Snap(x): x >= g: fix children, freeing all SnapA(y) x == y;
- *		SnapRO(x): wait until it hits SnapA
- *
- */
-
-
-struct Lump {
-	int ref;
-
-	Cache *c;
-
-	VtLock *lk;
-	
-	int state;
-	ulong gen;
-
-	uchar 	*data;
-	uchar	score[VtScoreSize];	/* score of packet */
-	uchar	vscore[VtScoreSize];	/* venti score - when archived */
-	u8int	type;			/* type of packet */
-	int	dir;			/* part of a directory - extension of type */
-	u16int	asize;			/* allocated size of block */
-	Lump	*next;			/* doubly linked hash chains */
-	Lump	*prev;
-	u32int	heap;			/* index in heap table */
-	u32int	used;			/* last reference times */
-	u32int	used2;
-
-	u32int	addr;			/* mutable block address */
+	u32int boff;
+	int i, n;
+	VacDir *buf;
 };
 

+ 2 - 0
sys/src/cmd/vac/error.c

@@ -5,6 +5,8 @@
 #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";

+ 7 - 0
sys/src/cmd/vac/error.h

@@ -1,3 +1,8 @@
+/*
+ * Somehow <errno.h> has been included on Mac OS X
+ */
+#undef EIO
+
 extern char ENoDir[];
 extern char EBadDir[];
 extern char EBadMeta[];
@@ -12,3 +17,5 @@ extern char ERemoved[];
 extern char ENotEmpty[];
 extern char EExists[];
 extern char ERoot[];
+extern char ENoFile[];
+extern char EBadPath[];

+ 1712 - 923
sys/src/cmd/vac/file.c

@@ -4,1260 +4,2049 @@
 #include "fns.h"
 #include "error.h"
 
+#define debug 0
+
 /*
- * locking order is upwards.  A thread can hold the lock for a VacFile
- * and then acquire the lock of its parent
+ * 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 {
-	/* meta data for file: protected by the lk in the parent */
-	int ref;		/* holds this data structure up */
-	VacFS *fs;		/* immutable */
-
-	int	removed;	/* file has been removed */
-	int	dirty;		/* dir is dirty with respect to meta data in block */
-	ulong	block;		/* block offset withing msource for this file's meta data */
-
-	VacDir dir;		/* meta data for this file */
-
-	VacFile *up;	/* parent file */
-	VacFile *next;	/* sibling */
+struct VacFile
+{
+	VacFs	*fs;	/* immutable */
 
-	/* data for file */
-	VtLock *lk;		/* lock for source and msource */
-	Source *source;
-	Source *msource;	/* for directories: meta data for children */
-	VacFile *down;		/* children */
+	/* 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 */
 };
 
-char *vfName(VacFile *, char *);
-
-static int vfMetaFlush(VacFile*);
-static ulong msAlloc(Source *ms, ulong, int n);
+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
-vfRUnlock(VacFile *vf)
+filefree(VacFile *f)
 {
-	vtRUnlock(vf->lk);
+	vtfileclose(f->source);
+	vtfileclose(f->msource);
+	vdcleanup(&f->dir);
+	memset(f, ~0, sizeof *f);	/* paranoia */
+	vtfree(f);
 }
 
 static int
-vfRLock(VacFile *vf)
+chksource(VacFile *f)
 {
-	vtRLock(vf->lk);
-	if(vf->source == nil) {
-		vfRUnlock(vf);
-		vtSetError(ERemoved);
+	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 1;
+	return 0;
 }
 
 static void
-vfUnlock(VacFile *vf)
+fileunlock(VacFile *f)
 {
-	vtUnlock(vf->lk);
+	wunlock(&f->lk);
 }
 
 static int
-vfLock(VacFile *vf)
+filerlock(VacFile *f)
 {
-	vtLock(vf->lk);
-	if(vf->source == nil) {
-		vfUnlock(vf);
-		vtSetError(ERemoved);
-		return 0;
+	rlock(&f->lk);
+	if(chksource(f) < 0){
+		runlock(&f->lk);
+		return -1;
 	}
-	return 1;
+	return 0;
 }
 
 static void
-vfMetaLock(VacFile *vf)
+filerunlock(VacFile *f)
 {
-	assert(vf->up->msource != nil);
-	vtLock(vf->up->lk);
+	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
-vfMetaUnlock(VacFile *vf)
+filemetalock(VacFile *f)
 {
-	vtUnlock(vf->up->lk);
+	assert(f->up != nil);
+	wlock(&f->up->lk);
 }
 
-
 static void
-vfRAccess(VacFile* vf)
+filemetaunlock(VacFile *f)
 {
-	vfMetaLock(vf);
-	vf->dir.atime = time(0L);
-	vf->dirty = 1;
-	vfMetaUnlock(vf);
-	vfMetaFlush(vf);
+	wunlock(&f->up->lk);
 }
 
-static void
-vfWAccess(VacFile* vf, char *mid)
+uvlong
+vacfilegetid(VacFile *f)
 {
-	vfMetaLock(vf);
-	vf->dir.atime = vf->dir.mtime = time(0L);
-	if(strcmp(vf->dir.mid, mid) != 0) {
-		vtMemFree(vf->dir.mid);
-		vf->dir.mid = vtStrDup(mid);
-	}
-	vf->dir.mcount++;
-	vf->dirty = 1;
-	vfMetaUnlock(vf);
-	vfMetaFlush(vf);
+	/* immutable */
+fprint(2, "getid %s %lld+%lld = %lld\n", f->dir.elem, f->qidoffset, f->dir.qid, f->qidoffset+f->dir.qid);
+	return f->qidoffset + f->dir.qid;
 }
 
-void
-vdCleanup(VacDir *dir)
+uvlong
+vacfilegetqidoffset(VacFile *f)
 {
-	vtMemFree(dir->elem);
-	dir->elem = nil;
-	vtMemFree(dir->uid);
-	dir->uid = nil;
-	vtMemFree(dir->gid);
-	dir->gid = nil;
-	vtMemFree(dir->mid);
-	dir->mid = nil;
+	return f->qidoffset;
 }
 
-void
-vdCopy(VacDir *dst, VacDir *src)
+ulong
+vacfilegetmcount(VacFile *f)
 {
-	*dst = *src;
-	dst->elem = vtStrDup(src->elem);
-	dst->uid = vtStrDup(src->uid);
-	dst->gid = vtStrDup(src->gid);
-	dst->mid = vtStrDup(src->mid);
+	ulong mcount;
+
+	filemetalock(f);
+	mcount = f->dir.mcount;
+	filemetaunlock(f);
+	return mcount;
 }
 
-static int
-mbSearch(MetaBlock *mb, char *elem, int *ri, MetaEntry *me)
+ulong
+vacfilegetmode(VacFile *f)
 {
-	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))
-			return 0;
-		if(mb->unbotch)
-			x = meCmpNew(me, elem);
-		else
-			x = meCmp(me, elem);
+	ulong mode;
 
-		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;
+	filemetalock(f);
+	mode = f->dir.mode;
+	filemetaunlock(f);
+	return mode;
 }
 
-static void
-mbInit(MetaBlock *mb, uchar *p, int n)
+int
+vacfileisdir(VacFile *f)
 {
-	memset(mb, 0, sizeof(MetaBlock));
-	mb->maxsize = n;
-	mb->buf = p;
-	mb->maxindex = n/100;
-	mb->size = MetaHeaderSize + mb->maxindex*MetaIndexSize;
+	/* immutable */
+	return (f->dir.mode & ModeDir) != 0;
 }
 
-static int
-vfMetaFlush(VacFile *vf)
+int
+vacfileisroot(VacFile *f)
 {
-	VacFile *vfp;
-	Lump *u;
-	MetaBlock mb;
-	MetaEntry me, nme;
-	uchar *p;
-	int i, n, moved;
-
-//print("vfMetaFlush %s\n", vf->dir.elem);
-
-	/* assume name has not changed for the moment */
-
-	vfMetaLock(vf);
-
-	vfp = vf->up;
-	moved = 0;
-
-	u = sourceGetLump(vfp->msource, vf->block, 0, 1);
-	if(u == nil)
-		goto Err;
-
-	if(!mbUnpack(&mb, u->data, u->asize))
-		goto Err;
-	if(!mbSearch(&mb, vf->dir.elem, &i, &me) || me.p == nil)
-		goto Err;
-
-	nme = me;
-	n = vdSize(&vf->dir);
-//print("old size %d new size %d\n", me.size, n);
-	if(n <= nme.size) {
-		nme.size = n;
-	} else {
-		/* try expand entry? */
-		p = mbAlloc(&mb, n);
-//print("alloced %ld\n", p - mb.buf);
-		if(p == nil) {
-assert(0);
-			/* much more work */
-		}
-		nme.p = p;
-		nme.size = n;
-	}
-
-	mbDelete(&mb, i, &me);
-	memset(me.p, 0, me.size);
-	if(!moved) {
-		vdPack(&vf->dir, &nme);
-		mbInsert(&mb, i, &nme);
-	}
-
-	mbPack(&mb);
-	lumpDecRef(u, 1);
-
-	vf->dirty = 0;
-
-	vfMetaUnlock(vf);
-	return 1;
-
-Err:
-	lumpDecRef(u, 1);
-	vfMetaUnlock(vf);
-	return 0;
+	return f == f->fs->root;
 }
 
-static VacFile *
-vfAlloc(VacFS *fs)
+/*
+ * 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)
 {
-	VacFile *vf;
-
-	vf = vtMemAllocZ(sizeof(VacFile));
-	vf->lk = vtLockAlloc();
-	vf->ref = 1;
-	vf->fs = fs;
+	filemetalock(vf);
+	assert(vf->ref > 0);
+	vf->ref++;
+	filemetaunlock(vf);
 	return vf;
 }
 
-static void
-vfFree(VacFile *vf)
+int
+vacfiledecref(VacFile *f)
 {
-	sourceFree(vf->source);
-	vtLockFree(vf->lk);
-	sourceFree(vf->msource);
-	vdCleanup(&vf->dir);
+	VacFile *p, *q, **qq;
 
-	vtMemFree(vf);
-}
+	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);
 
-/* the file is locked already */
-static VacFile *
-dirLookup(VacFile *vf, char *elem)
-{
-	int i, j, nb;
-	MetaBlock mb;
-	MetaEntry me;
-	Lump *u;
-	Source *meta;
-	VacFile *nvf;
-
-	meta = vf->msource;
-	u = nil;
-	nb = sourceGetNumBlocks(meta);
-	for(i=0; i<nb; i++) {
-		u = sourceGetLump(meta, i, 1, 1);
-		if(u == nil)
-			goto Err;
-		if(!mbUnpack(&mb, u->data, u->asize))
-			goto Err;
-		if(!mbSearch(&mb, elem, &j, &me))
-			goto Err;
-		if(me.p != nil) {
-			nvf = vfAlloc(vf->fs);
-			if(!vdUnpack(&nvf->dir, &me)) {
-				vfFree(nvf);
-				goto Err;
-			}
-			lumpDecRef(u, 1);
-			nvf->block = i;
-			return nvf;
-		}
+	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);
 
-		lumpDecRef(u, 1);
-		u = nil;
+	p = f->up;
+	qq = &p->down;
+	for(q = *qq; q; q = *qq){
+		if(q == f)
+			break;
+		qq = &q->next;
 	}
-	vtSetError("file does not exist");
-	/* fall through */
-Err:
-	lumpDecRef(u, 1);
-	return nil;
-}
+	assert(q != nil);
+	*qq = f->next;
 
-/* point r back at vf */
-static void
-pointback(Source *r, VacFile *vf)
-{
-	assert(r->vf == nil);
-	r->vf = vf;
+	filemetaunlock(f);
+	filefree(f);
+	vacfiledecref(p);
+	return 0;
 }
 
-VacFile *
-vfRoot(VacFS *fs, uchar *score)
+
+/* 
+ * 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)
 {
-	VtEntry e;
-	Lump *u, *v;
-	Source *r, *r0, *r1, *r2;
+	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;
-	r0 = nil;
 	r1 = nil;
 	r2 = nil;
-	v = nil;
-	r = nil;
 
-	u = cacheGetLump(fs->cache, score, VtDirType, fs->bsize);
-	if(u == 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;
-	if(!fs->readOnly) {
-		v = cacheAllocLump(fs->cache, VtDirType, fs->bsize, 1);
-		if(v == nil) {
-			vtUnlock(u->lk);
-			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;
 		}
-		v->gen = u->gen;
-		v->asize = u->asize;
-		v->state = LumpActive;
-		memmove(v->data, u->data, v->asize);
-		lumpDecRef(u, 1);
-		u = v;
-		v = nil;
-	}
-	vtUnlock(u->lk);
-	vtEntryUnpack(&e, u->data, 2);
-	if(e.flags == 0){		/* just one entry */
-		r = sourceAlloc(fs->cache, u, 0, 0, fs->readOnly);
-		if(r == nil)
-			goto Err;
-		r0 = sourceOpen(r, 0, fs->readOnly);
-		if(r0 == nil)
-			goto Err;
-		r1 = sourceOpen(r, 1, fs->readOnly);
-		if(r1 == nil)
-			goto Err;
-		r2 = sourceOpen(r, 2, fs->readOnly);
-		if(r2 == nil)
-			goto Err;
-		sourceFree(r);
-		r = nil;
-	}else{
-		r0 = sourceAlloc(fs->cache, u, 0, 0, fs->readOnly);
-		if(r0 == nil)
-			goto Err;
-		r1 = sourceAlloc(fs->cache, u, 0, 1, fs->readOnly);
-		if(r1 == nil)
-			goto Err;
-		r2 = sourceAlloc(fs->cache, u, 0, 2, fs->readOnly);
-		if(r2 == nil)
-			goto Err;
+		goto Err;
 	}
-	lumpDecRef(u, 0);
-	u = sourceGetLump(r2, 0, 1, 0);
-	if(u == nil)
+	r1 = vtfileopen(r, 1, fs->mode);
+	if(debug)
+		fprint(2, "r1 %p\n", r1);
+	if(r1 == nil)
 		goto Err;
 
-	mr = vfAlloc(fs);
+	mr = filealloc(fs);
 	mr->msource = r2;
-	pointback(r2, mr);
 	r2 = nil;
 
-	root = vfAlloc(fs);
+	root = filealloc(fs);
+	root->boff = 0;
 	root->up = mr;
 	root->source = r0;
-	pointback(r0, root);
 	r0 = nil;
 	root->msource = r1;
-	pointback(r1, root);
 	r1 = nil;
 
 	mr->down = root;
+	vtfileunlock(r);
 
-	if(!mbUnpack(&mb, u->data, u->asize))
-		goto Err;
+	if(vtfilelock(mr->msource, VtOREAD) < 0)
+		goto Err1;
+	b = vtfileblock(mr->msource, 0, VtOREAD);
+	vtfileunlock(mr->msource);
+	if(b == nil)
+		goto Err1;
 
-	if(!meUnpack(&me, &mb, 0))
-		goto Err;
-	if(!vdUnpack(&root->dir, &me))
-		goto Err;
+	if(mbunpack(&mb, b->data, mr->msource->dsize) < 0)
+		goto Err1;
 
-	vfRAccess(root);
-	lumpDecRef(u, 0);
-	sourceFree(r2);
+	meunpack(&me, &mb, 0);
+	if(vdunpack(&root->dir, &me) < 0)
+		goto Err1;
+	vtblockput(b);
 
 	return root;
 Err:
-	lumpDecRef(u, 0);
-	lumpDecRef(v, 0);
+	vtfileunlock(r);
+Err1:
+	vtblockput(b);
 	if(r0)
-		sourceFree(r0);
+		vtfileclose(r0);
 	if(r1)
-		sourceFree(r1);
+		vtfileclose(r1);
 	if(r2)
-		sourceFree(r2);
-	if(r)
-		sourceFree(r);
+		vtfileclose(r2);
 	if(mr)
-		vfFree(mr);
+		filefree(mr);
 	if(root)
-		vfFree(root);
+		filefree(root);
 
 	return nil;
 }
 
-VacFile *
-vfWalk(VacFile *vf, char *elem)
+/*
+ * 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)
 {
-	VacFile *nvf;
+	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;
+			}
+fprint(2, "offset %s %lld\n", ff->dir.elem, ff->dir.qidoffset);
+			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;
+}
 
-	vfRAccess(vf);
+/*
+ * 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(elem[0] == 0) {
-		vtSetError("illegal path element");
+	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(!vfIsDir(vf)) {
-		vtSetError("not a directory");
+	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);
+}
 
-	if(strcmp(elem, ".") == 0) {
-		return vfIncRef(vf);
+/*
+ * 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(strcmp(elem, "..") == 0) {
-		if(vfIsRoot(vf))
-			return vfIncRef(vf);
-		return vfIncRef(vf->up);
+	if(!vacfileisdir(f)){
+		werrstr(ENotDir);
+		return nil;
 	}
 
-	if(!vfLock(vf))
+	if(strcmp(elem, ".") == 0)
+		return vacfileincref(f);
+
+	if(strcmp(elem, "..") == 0)
+		return vacfilegetparent(f);
+
+	if(filelock(f) < 0)
 		return nil;
 
-	for(nvf = vf->down; nvf; nvf=nvf->next) {
-		if(strcmp(elem, nvf->dir.elem) == 0 && !nvf->removed) {
-			nvf->ref++;
+	for(ff = f->down; ff; ff=ff->next){
+		if(strcmp(elem, ff->dir.elem) == 0 && !ff->removed){
+			ff->ref++;
 			goto Exit;
 		}
 	}
 
-	nvf = dirLookup(vf, elem);
-	if(nvf == nil)
+	ff = dirlookup(f, elem);
+	if(ff == nil)
 		goto Err;
-	nvf->source = sourceOpen(vf->source, nvf->dir.entry, vf->fs->readOnly);
-	if(nvf->source == nil)
+
+	if(ff->dir.mode & ModeSnapshot)
+		ff->mode = VtOREAD;
+
+	if(vtfilelock(f->source, f->mode) < 0)
 		goto Err;
-	pointback(nvf->source, nvf);
-	if(nvf->dir.mode & ModeDir) {
-		nvf->msource = sourceOpen(vf->source, nvf->dir.mentry, vf->fs->readOnly);
-		if(nvf->msource == nil)
-			goto Err;
-		pointback(nvf->msource, nvf);
+	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 */
-	nvf->next = vf->down;
-	vf->down = nvf;
-	nvf->up = vf;
-	vfIncRef(vf);
+	ff->next = f->down;
+	f->down = ff;
+	ff->up = f;
+	vacfileincref(f);
 Exit:
-	vfUnlock(vf);
-	return nvf;
+	fileunlock(f);
+	return ff;
+
+Err1:
+	vtfileunlock(f->source);
 Err:
-	vfUnlock(vf);
-	if(nvf != nil)
-		vfFree(nvf);
+	fileunlock(f);
+	if(ff != nil)
+		vacfiledecref(ff);
 	return nil;
 }
 
-VacFile *
-vfOpen(VacFS *fs, char *path)
+/* 
+ * Open a path in the vac file system: 
+ * just walk each element one at a time.
+ */
+VacFile*
+vacfileopen(VacFs *fs, char *path)
 {
-	VacFile *vf, *nvf;
-	char *p, elem[VtMaxStringSize];
+	VacFile *f, *ff;
+	char *p, elem[VtMaxStringSize], *opath;
 	int n;
 
-	vf = fs->root;
-	vfIncRef(vf);
-	while(*path != 0) {
+	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) {
-				vtSetError("path element too long");
+		if(n > 0){
+			if(n > VtMaxStringSize){
+				werrstr("%s: element too long", EBadPath);
 				goto Err;
 			}
 			memmove(elem, path, n);
 			elem[n] = 0;
-			nvf = vfWalk(vf, elem);
-			if(nvf == nil)
+			ff = vacfilewalk(f, elem);
+			if(ff == nil){
+				werrstr("%.*s: %r", utfnlen(opath, p-opath), opath);
 				goto Err;
-			vfDecRef(vf);
-			vf = nvf;
+			}
+			vacfiledecref(f);
+			f = ff;
 		}
 		if(*p == '/')
 			p++;
 		path = p;
 	}
-	return vf;
+	return f;
 Err:
-	vfDecRef(vf);
+	vacfiledecref(f);
 	return nil;
 }
 
-VacFile *
-vfCreate(VacFile *vf, char *elem, ulong mode, char *user)
+/*
+ * Extract the score for the bn'th block in f.
+ */
+int
+vacfileblockscore(VacFile *f, u32int bn, u8int *score)
 {
-	VacFile *nvf;
-	VacDir *dir;
-	int n, i;
-	uchar *p;
-	Source *pr, *r, *mr;
-	int isdir;
-	MetaBlock mb;
-	MetaEntry me;
-	Lump *u;
+	VtFile *s;
+	uvlong size;
+	int dsize, ret;
 
-	if(!vfLock(vf))
-		return nil;
+	ret = -1;
+	if(filerlock(f) < 0)
+		return -1;
+	if(vtfilelock(f->source, VtOREAD) < 0)
+		goto out;
 
-	r = nil;
-	mr = nil;
-	u = nil;
+	s = f->source;
+	dsize = s->dsize;
+	size = vtfilegetsize(s);
+	if((uvlong)bn*dsize >= size)
+		goto out;
+	ret = vtfileblockscore(f->source, bn, score);
+
+out:
+	vtfileunlock(f->source);
+	filerunlock(f);
+	return ret;
+}
 
-	for(nvf = vf->down; nvf; nvf=nvf->next) {
-		if(strcmp(elem, nvf->dir.elem) == 0 && !nvf->removed) {
-			nvf = nil;
-			vtSetError(EExists);
-			goto Err;
-		}
-	}
+/*
+ * Read data from f.
+ */
+int
+vacfileread(VacFile *f, void *buf, int cnt, vlong offset)
+{
+	int n;
 
-	nvf = dirLookup(vf, elem);
-	if(nvf != nil) {
-		vtSetError(EExists);
-		goto Err;
+	if(offset < 0){
+		werrstr(EBadOffset);
+		return -1;
 	}
-
-	nvf = vfAlloc(vf->fs);
-	isdir = mode & ModeDir;
-
-	pr = vf->source;
-	r = sourceCreate(pr, pr->psize, pr->dsize, isdir, 0);
-	if(r == nil)
-		goto Err;
-	if(isdir) {
-		mr = sourceCreate(pr, pr->psize, pr->dsize, 0, r->block*pr->epb + r->entry);
-		if(mr == nil)
-			goto Err;
+	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;
+}
 
-	dir = &nvf->dir;
-	dir->elem = vtStrDup(elem);
-	dir->entry = r->block*pr->epb + r->entry;
-	dir->gen = r->gen;
-	if(isdir) {
-		dir->mentry = mr->block*pr->epb + mr->entry;
-		dir->mgen = mr->gen;
+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;
 	}
-	dir->size = 0;
-	dir->qid = vf->fs->qid++;
-	dir->uid = vtStrDup(user);
-	dir->gid = vtStrDup(vf->dir.gid);
-	dir->mid = vtStrDup(user);
-	dir->mtime = time(0L);
-	dir->mcount = 0;
-	dir->ctime = dir->mtime;
-	dir->atime = dir->mtime;
-	dir->mode = mode;
-
-	n = vdSize(dir);
-	nvf->block = msAlloc(vf->msource, 0, n);
-	if(nvf->block == NilBlock)
-		goto Err;
-	u = sourceGetLump(vf->msource, nvf->block, 0, 1);
-	if(u == nil)
-		goto Err;
-	if(!mbUnpack(&mb, u->data, u->asize))
-		goto Err;
-	p = mbAlloc(&mb, n);
-	if(p == nil)
-		goto Err;
-
-	if(!mbSearch(&mb, elem, &i, &me))
-		goto Err;
-	assert(me.p == nil);
-	me.p = p;
-	me.size = n;
-
-	vdPack(dir, &me);
-	mbInsert(&mb, i, &me);
-	mbPack(&mb);
-	lumpDecRef(u, 1);
-
-	nvf->source = r;
-	pointback(r, nvf);
-	nvf->msource = mr;
-	pointback(mr, vf);
-
-	/* link in and up parent ref count */
-	nvf->next = vf->down;
-	vf->down = nvf;
-	nvf->up = vf;
-	vfIncRef(vf);
-
-	vfWAccess(vf, user);
-
-	vfUnlock(vf);
-	return nvf;
-
-Err:
-	lumpDecRef(u, 1);
-	if(r)
-		sourceRemove(r);
-	if(mr)
-		sourceRemove(mr);
-	if(nvf)
-		vfFree(nvf);
-	vfUnlock(vf);
 	return 0;
 }
 
+/*
+ * Get the VtEntries for the data contained in f.
+ */
 int
-vfRead(VacFile *vf, void *buf, int cnt, vlong offset)
+vacfilegetentries(VacFile *f, VtEntry *e, VtEntry *me)
 {
-	Source *s;
-	uvlong size;
-	ulong bn;
-	int off, dsize, n, nn;
-	Lump *u;
-	uchar *b;
-
-if(0)fprint(2, "vfRead: %s %d, %lld\n", vf->dir.elem, cnt, offset);
-
-	if(!vfRLock(vf))
+	if(filerlock(f) < 0)
+		return -1;
+	if(e && getentry(f->source, e) < 0){
+		filerunlock(f);
 		return -1;
-
-	s = vf->source;
-
-	dsize = s->dsize;
-	size = sourceGetSize(s);
-
-	if(offset < 0) {
-		vtSetError(EBadOffset);
-		goto Err;
 	}
-
-	vfRAccess(vf);
-
-	if(offset >= size)
-		offset = size;
-
-	if(cnt > size-offset)
-		cnt = size-offset;
-	bn = offset/dsize;
-	off = offset%dsize;
-	b = buf;
-	while(cnt > 0) {
-		u = sourceGetLump(s, bn, 1, 0);
-		if(u == nil)
-			goto Err;
-		if(u->asize <= off) {
-			lumpDecRef(u, 0);
-			goto Err;
+	if(me){
+		if(f->msource == nil)
+			memset(me, 0, sizeof *me);
+		if(getentry(f->msource, me) < 0){
+			filerunlock(f);
+			return -1;
 		}
-		n = cnt;
-		if(n > dsize-off)
-			n = dsize-off;
-		nn = u->asize-off;
-		if(nn > n)
-			nn = n;
-		memmove(b, u->data+off, nn);
-		memset(b+nn, 0, n-nn);
-		off = 0;
-		bn++;
-		cnt -= n;
-		b += n;
-		lumpDecRef(u, 0);
-	}
-	vfRUnlock(vf);
-	return b-(uchar*)buf;
-Err:
-	vfRUnlock(vf);
-	return -1;
+	}
+	filerunlock(f);
+	return 0;
 }
 
+/*
+ * Get the file's size.
+ */
 int
-vfWrite(VacFile *vf, void *buf, int cnt, vlong offset, char *user)
+vacfilegetsize(VacFile *f, uvlong *size)
 {
-	Source *s;
-	ulong bn;
-	int off, dsize, n;
-	Lump *u;
-	uchar *b;
+	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);
 
-	USED(user);
+	return 0;
+}
 
-	if(!vfLock(vf))
-		return -1;
+/*
+ * 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(vf->fs->readOnly) {
-		vtSetError(EReadOnly);
-		goto Err;
+	if(!vacfileisdir(f)){
+		werrstr(ENotDir);
+		return nil;
 	}
 
-	if(vf->dir.mode & ModeDir) {
-		vtSetError(ENotFile);
+	/*
+	 * 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;
-	}
-if(0)fprint(2, "vfWrite: %s %d, %lld\n", vf->dir.elem, cnt, offset);
 
-	s = vf->source;
-	dsize = s->dsize;
+	/* dangling entries are returned as zero size */
+	if(!(e.flags & VtEntryActive) || e.gen != gen)
+		*size = 0;
+	else
+		*size = e.size;
+	vtblockput(b);
+	return 0;
 
-	if(offset < 0) {
-		vtSetError(EBadOffset);
+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;
-	}
 
-	vfWAccess(vf, user);
+	n = mb.nindex;
+	vde->buf = vtmalloc(n * sizeof(VacDir));
 
-	bn = offset/dsize;
-	off = offset%dsize;
-	b = buf;
-	while(cnt > 0) {
-		n = cnt;
-		if(n > dsize-off)
-			n = dsize-off;
-		if(!sourceSetDepth(s, offset+n))
-			goto Err;
-		u = sourceGetLump(s, bn, 0, 0);
-		if(u == nil)
+	for(i=0; i<n; i++){
+		de = vde->buf + i;
+		meunpack(&me, &mb, i);
+		if(vdunpack(de, &me) < 0)
 			goto Err;
-		if(u->asize < dsize) {
-			vtSetError("runt block");
-			lumpDecRef(u, 0);
-			goto Err;
-		}
-		memmove(u->data+off, b, n);
-		off = 0;
-		cnt -= n;
-		b += n;
-		offset += n;
-		bn++;
-		lumpDecRef(u, 0);
-		if(!sourceSetSize(s, offset))
+		vde->n++;
+		if(!(de->mode & ModeDir))
+		if(direntrysize(source, de->entry, de->gen, &de->size) < 0)
 			goto Err;
 	}
-	vfLock(vf);
-	return b-(uchar*)buf;
+	vde->boff++;
+	vtblockput(b);
+	return 0;
 Err:
-	vfLock(vf);
+	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
-vfGetDir(VacFile *vf, VacDir *dir)
+vderead(VacDirEnum *vde, VacDir *de)
 {
-	if(!vfRLock(vf))
-		return 0;
+	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;
+		}
+	}
 
-	vfMetaLock(vf);
-	vdCopy(dir, &vf->dir);
-	vfMetaUnlock(vf);
+	memmove(de, vde->buf + vde->i, sizeof(VacDir));
+	vde->i++;
+	ret = 1;
 
-	if(!vfIsDir(vf))
-		dir->size = sourceGetSize(vf->source);
-	vfRUnlock(vf);
+Return:
+	vtfileunlock(f->source);
+	vtfileunlock(f->msource);
+	filerunlock(f);
 
-	return 1;
+	return ret;
 }
 
-uvlong
-vfGetId(VacFile *vf)
+/*
+ * "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)
 {
-	/* immutable */
-	return vf->dir.qid;
+	if(vde->i > 0){
+		vde->i--;
+		return 0;
+	}
+	return -1;
 }
 
-ulong
-vfGetMcount(VacFile *vf)
+/*
+ * Close the enumerator.
+ */
+void
+vdeclose(VacDirEnum *vde)
 {
-	ulong mcount;
-
-	vfMetaLock(vf);
-	mcount = vf->dir.mcount;
-	vfMetaUnlock(vf);
-	return mcount;
+	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);
 }
 
 
-int
-vfIsDir(VacFile *vf)
-{
-	/* immutable */
-	return (vf->dir.mode & ModeDir) != 0;
-}
+/*
+ * 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.
+ */
 
-int
-vfIsRoot(VacFile *vf)
+/*
+ * 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)
 {
-	return vf == vf->fs->root;
+	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;
+			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);