# HG changeset patch # User Ron Minnich # Date 1317231298 25200 # Node ID 5e8d97b0d457bec8d1053b490aec3b9e88c7ef7d # Parent 25e87a450f81c22ad161f26ce030e54ac2ddf34c This is a hack just to test an idea. vtvacfs is a combined mmventi and vacfs. The library already had good support for managing cached blocks and I am retargeting the "cached blocks" to be patches. I want to get this code visible so people can tell me better ways to do things if they see them. The mmventi built in here works fine -- tested -- but the built-in vacfs does not, since the 9p server in it needs some work. That doesn't seem like a huge thing to fix at the moment. But I have other fish to fry. BTW the way attach will work is you pass the score in the aname. I've done this on Lucho's Go library and it works fine. sys/src/cmd/vtvacfs/vtvacfs.c: mmventi modified in minor ways R=nix-dev, nemo CC=nix-dev http://codereview.appspot.com/5144051 diff -r 25e87a450f81 -r 5e8d97b0d457 sys/src/cmd/venti/srv/mkfile --- a/sys/src/cmd/venti/srv/mkfile Thu Sep 29 17:32:24 2011 +0000 +++ b/sys/src/cmd/venti/srv/mkfile Wed Sep 28 10:34:58 2011 -0700 @@ -38,6 +38,7 @@ xml.$O\ zblock.$O\ zeropart.$O\ + vacfs.$O\ SLIB=libvs.a$O diff -r 25e87a450f81 -r 5e8d97b0d457 sys/src/cmd/vtvacfs/dat.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sys/src/cmd/vtvacfs/dat.h Wed Sep 28 10:34:58 2011 -0700 @@ -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 -r 25e87a450f81 -r 5e8d97b0d457 sys/src/cmd/vtvacfs/error.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sys/src/cmd/vtvacfs/error.h Wed Sep 28 10:34:58 2011 -0700 @@ -0,0 +1,21 @@ +/* + * Somehow 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 -r 25e87a450f81 -r 5e8d97b0d457 sys/src/cmd/vtvacfs/file.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sys/src/cmd/vtvacfs/file.c Wed Sep 28 10:34:58 2011 -0700 @@ -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; bodata, 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; in; 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; ibuf + 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; in; 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; bodata, 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; isource, -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; idata, 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 -r 25e87a450f81 -r 5e8d97b0d457 sys/src/cmd/vtvacfs/fns.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sys/src/cmd/vtvacfs/fns.h Wed Sep 28 10:34:58 2011 -0700 @@ -0,0 +1,34 @@ +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*); + +/* mmventi */ +int getdata(u8int *score, u8int *data, u8int len, u8int blocktype); +int putdata(u8int *score, u8int *data, int len, uchar blocktype); diff -r 25e87a450f81 -r 5e8d97b0d457 sys/src/cmd/vtvacfs/mkfile --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sys/src/cmd/vtvacfs/mkfile Wed Sep 28 10:34:58 2011 -0700 @@ -0,0 +1,31 @@ + +#include +#include +int getdata(u8int *score, u8int *data, u8int len, u8int blocktype); +int putdata(u8int *score, u8int *data, int len, uchar blocktype); + +int vtpatchnread; +int vtpatchncopy; +int vtpatchnwrite; +int vttracelevel; + +enum { + BioLocal = 1, + BioVenti, + BioReading, + BioWriting, + BioEmpty, + BioVentiError +}; +enum { + BadHeap = ~0 +}; + +typedef struct VtPatch VtPatch; + +struct VtPatch +{ + QLock lk; + VtConn *z; + u32int blocksize; + u32int now; /* ticks for usage time stamps */ + VtBlock **hash; /* hash table for finding addresses */ + int nhash; + VtBlock **heap; /* heap for finding victims */ + int nheap; + VtBlock *block; /* all allocated blocks */ + int nblock; + uchar *mem; /* memory for all blocks and data */ + int (*write)(VtConn*, uchar[VtScoreSize], uint, uchar*, int); +}; + +static void patchcheck(VtPatch*); + +VtPatch* +vtpatchalloc(int blocksize, ulong nblock) +{ + uchar *p; + VtPatch *c; + int i; + VtBlock *b; + + c = vtmallocz(sizeof(VtPatch)); + + c->blocksize = (blocksize + 127) & ~127; + c->nblock = nblock; + c->nhash = nblock; + c->hash = vtmallocz(nblock*sizeof(VtBlock*)); + c->heap = vtmallocz(nblock*sizeof(VtBlock*)); + c->block = vtmallocz(nblock*sizeof(VtBlock)); + c->mem = vtmallocz(nblock*c->blocksize); + + p = c->mem; + for(i=0; iblock[i]; + b->addr = NilBlock; + b->c = (VtCache *)c; /* XXX */ + b->data = p; + b->heap = i; + c->heap[i] = b; + p += c->blocksize; + } + c->nheap = nblock; + patchcheck(c); + return c; +} + +void +vtpatchfree(VtPatch *c) +{ + int i; + + qlock(&c->lk); + + patchcheck(c); + for(i=0; inblock; i++) + assert(c->block[i].ref == 0); + + vtfree(c->hash); + vtfree(c->heap); + vtfree(c->block); + vtfree(c->mem); + vtfree(c); +} + +static void +vtpatchdump(VtPatch *c) +{ + int i; + VtBlock *b; + + for(i=0; inblock; i++){ + b = &c->block[i]; + print("patch block %d: type %d score %V iostate %d addr %d ref %d nlock %d\n", + i, b->type, b->score, b->iostate, b->addr, b->ref, b->nlock); + } +} + +static void +patchcheck(VtPatch *c) +{ + u32int size, now; + int i, k, refed; + VtBlock *b; + + size = c->blocksize; + now = c->now; + + for(i = 0; i < c->nheap; i++){ + if(c->heap[i]->heap != i) + sysfatal("mis-heaped at %d: %d", i, c->heap[i]->heap); + if(i > 0 && c->heap[(i - 1) >> 1]->used - now > c->heap[i]->used - now) + sysfatal("bad heap ordering"); + k = (i << 1) + 1; + if(k < c->nheap && c->heap[i]->used - now > c->heap[k]->used - now) + sysfatal("bad heap ordering"); + k++; + if(k < c->nheap && c->heap[i]->used - now > c->heap[k]->used - now) + sysfatal("bad heap ordering"); + } + + refed = 0; + for(i = 0; i < c->nblock; i++){ + b = &c->block[i]; + if(b->data != &c->mem[i * size]) + sysfatal("mis-blocked at %d", i); + if(b->ref && b->heap == BadHeap) + refed++; + else if(b->addr != NilBlock) + refed++; + } + assert(c->nheap + refed == c->nblock); + refed = 0; + for(i = 0; i < c->nblock; i++){ + b = &c->block[i]; + if(b->ref){ + refed++; + } + } +} + +static int +upheap(int i, VtBlock *b) +{ + VtBlock *bb; + u32int now; + int p; + VtPatch *c; + + c = (VtPatch *)b->c; + now = c->now; + for(; i != 0; i = p){ + p = (i - 1) >> 1; + bb = c->heap[p]; + if(b->used - now >= bb->used - now) + break; + c->heap[i] = bb; + bb->heap = i; + } + c->heap[i] = b; + b->heap = i; + + return i; +} + +static int +downheap(int i, VtBlock *b) +{ + VtBlock *bb; + u32int now; + int k; + VtPatch *c; + + c = (VtPatch *)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]->used - now > c->heap[k + 1]->used - now) + k++; + bb = c->heap[k]; + if(b->used - now <= bb->used - now) + break; + c->heap[i] = bb; + bb->heap = i; + } + c->heap[i] = b; + b->heap = i; + return i; +} + +/* + * Delete a block from the heap. + * Called with c->lk held. + */ +static void +heapdel(VtBlock *b) +{ + int i, si; + VtPatch *c; + + c = (VtPatch *)b->c; + + si = b->heap; + if(si == BadHeap) + return; + b->heap = BadHeap; + c->nheap--; + if(si == c->nheap) + return; + b = c->heap[c->nheap]; + i = upheap(si, b); + if(i == si) + downheap(i, b); +} + +/* + * Insert a block into the heap. + * Called with c->lk held. + */ +static void +heapins(VtBlock *b) +{ + assert(b->heap == BadHeap); + upheap(((VtPatch *)b->c)->nheap++, b); +} + +/* + * locate the vtBlock with the oldest second to last use. + * remove it from the heap, and fix up the heap. + */ +/* called with c->lk held */ +static VtBlock* +vtpatchbumpblock(VtPatch *c) +{ + VtBlock *b; + + /* + * locate the vtBlock with the oldest second to last use. + * remove it from the heap, and fix up the heap. + */ + if(c->nheap == 0){ + vtpatchdump(c); + fprint(2, "vtpatchbumpblock: no free blocks in vtPatch"); + abort(); + } + b = c->heap[0]; + heapdel(b); + + assert(b->heap == BadHeap); + assert(b->ref == 0); + + /* + * unchain the vtBlock from hash chain if any + */ + if(b->prev){ + *(b->prev) = b->next; + if(b->next) + b->next->prev = b->prev; + b->prev = nil; + } + + +if(0)fprint(2, "droping %x:%V\n", b->addr, b->score); + /* set vtBlock to a reasonable state */ + b->ref = 1; + b->iostate = BioEmpty; + return b; +} + +/* + * fetch a local block from the memory patch. + * if it's not there, load it, bumping some other Block. + * if we're out of free blocks, we're screwed. + */ +VtBlock* +vtpatchlocal(VtPatch *c, u32int addr, int type) +{ + VtBlock *b; + + if(addr == 0) + sysfatal("vtpatchlocal: asked for nonexistent block 0"); + if(addr > c->nblock) + sysfatal("vtpatchlocal: asked for block #%ud; only %d blocks", + addr, c->nblock); + + b = &c->block[addr-1]; + if(b->addr == NilBlock || b->iostate != BioLocal) + sysfatal("vtpatchlocal: block is not local"); + + if(b->type != type) + sysfatal("vtpatchlocal: block has wrong type %d != %d", b->type, type); + + qlock(&c->lk); + b->ref++; + qunlock(&c->lk); + + qlock(&b->lk); + b->nlock = 1; + b->pc = getcallerpc(&c); + return b; +} + +VtBlock* +vtpatchallocblock(VtPatch *c, int type) +{ + VtBlock *b; + + qlock(&c->lk); + b = vtpatchbumpblock(c); + b->iostate = BioLocal; + b->type = type; + b->addr = (b - c->block)+1; + vtzeroextend(type, b->data, 0, c->blocksize); + vtlocaltoglobal(b->addr, b->score); + qunlock(&c->lk); + + qlock(&b->lk); + b->nlock = 1; + b->pc = getcallerpc(&c); + return b; +} + +/* + * fetch a global (Venti) block from the memory patch. + * Return a pointer to it. + */ +VtBlock* +vtpatchglobal(VtPatch *c, uchar score[VtScoreSize], int type) +{ + VtBlock *b; + ulong h; + int n; + u32int addr; + + if(vttracelevel) + fprint(2, "vtpatchglobal %V %d from %p\n", score, type, getcallerpc(&c)); + addr = vtglobaltolocal(score); + if(addr != NilBlock){ + if(vttracelevel) + fprint(2, "vtpatchglobal %V %d => local\n", score, type); + b = vtpatchlocal(c, addr, type); + if(b) + b->pc = getcallerpc(&c); + return b; + } + + h = (u32int)(score[0]|(score[1]<<8)|(score[2]<<16)|(score[3]<<24)) % c->nhash; + + /* + * look for the block in the patch + */ + qlock(&c->lk); + for(b = c->hash[h]; b != nil; b = b->next){ + if(b->addr != NilBlock || memcmp(b->score, score, VtScoreSize) != 0 || b->type != type) + continue; + heapdel(b); + b->ref++; + qunlock(&c->lk); + if(vttracelevel) + fprint(2, "vtpatchglobal %V %d => found in patch %p; locking\n", score, type, b); + qlock(&b->lk); + b->nlock = 1; + if(b->iostate == BioVentiError){ + if(chattyventi) + fprint(2, "patchd read error for %V\n", score); + if(vttracelevel) + fprint(2, "vtpatchglobal %V %d => patch read error\n", score, type); + vtblockput(b); + werrstr("venti i/o error"); + return nil; + } + if(vttracelevel) + fprint(2, "vtpatchglobal %V %d => found in patch; returning\n", score, type); + b->pc = getcallerpc(&c); + return b; + } + + /* + * not found + */ + b = vtpatchbumpblock(c); + b->addr = NilBlock; + b->type = type; + memmove(b->score, score, VtScoreSize); + /* chain onto correct hash */ + b->next = c->hash[h]; + c->hash[h] = b; + if(b->next != nil) + b->next->prev = &b->next; + b->prev = &c->hash[h]; + + /* + * Lock b before unlocking c, so that others wait while we read. + * + * You might think there is a race between this qlock(b) before qunlock(c) + * and the qlock(c) while holding a qlock(b) in vtblockwrite. However, + * the block here can never be the block in a vtblockwrite, so we're safe. + * We're certainly living on the edge. + */ + if(vttracelevel) + fprint(2, "vtpatchglobal %V %d => bumped; locking %p\n", score, type, b); + qlock(&b->lk); + b->nlock = 1; + qunlock(&c->lk); + + vtpatchnread++; + n = getdata(score, b->data, c->blocksize, type); + if(n < 0){ + if(chattyventi) + fprint(2, "read %V: %r\n", score); + if(vttracelevel) + fprint(2, "vtpatchglobal %V %d => bumped; read error\n", score, type); + b->iostate = BioVentiError; + vtblockput(b); + return nil; + } + vtzeroextend(type, b->data, n, c->blocksize); + b->iostate = BioVenti; + b->nlock = 1; + if(vttracelevel) + fprint(2, "vtpatchglobal %V %d => loaded into patch; returning\n", score, type); + b->pc = getcallerpc(&b); + return b; +} + +/* + * we're done with the block. + * unlock it. can't use it after calling this. + */ +void +patchblockput(VtBlock* b) +{ + VtPatch *c; + + if(b == nil) + return; + +if(0)fprint(2, "vtblockput: %d: %x %d %d\n", getpid(), b->addr, c->nheap, b->iostate); + if(vttracelevel) + fprint(2, "vtblockput %p from %p\n", b, getcallerpc(&b)); + + if(--b->nlock > 0) + return; + + /* + * b->nlock should probably stay at zero while + * the vtBlock is unlocked, but diskThread and vtSleep + * conspire to assume that they can just qlock(&b->lk); vtblockput(b), + * so we have to keep b->nlock set to 1 even + * when the vtBlock is unlocked. + */ + assert(b->nlock == 0); + b->nlock = 1; + + qunlock(&b->lk); + c = (VtPatch *)b->c; + qlock(&c->lk); + + if(--b->ref > 0){ + qunlock(&c->lk); + return; + } + + assert(b->ref == 0); + switch(b->iostate){ + case BioVenti: +/*if(b->addr != NilBlock) print("blockput %d\n", b->addr); */ + b->used = c->now++; + /* fall through */ + case BioVentiError: + heapins(b); + break; + case BioLocal: + break; + } + qunlock(&c->lk); +} + +int +patchblockwrite(VtBlock *b) +{ + uchar score[VtScoreSize]; + VtPatch *c; + uint h; + int n; + + if(b->iostate != BioLocal){ + werrstr("vtblockwrite: not a local block"); + return -1; + } + + c = (VtPatch *)b->c; + n = vtzerotruncate(b->type, b->data, c->blocksize); + vtpatchnwrite++; + if(putdata(score, b->data, n, b->type) < 0) + return -1; + + memmove(b->score, score, VtScoreSize); + + qlock(&c->lk); + b->addr = NilBlock; /* now on venti */ + b->iostate = BioVenti; + h = (u32int)(score[0]|(score[1]<<8)|(score[2]<<16)|(score[3]<<24)) % c->nhash; + b->next = c->hash[h]; + c->hash[h] = b; + if(b->next != nil) + b->next->prev = &b->next; + b->prev = &c->hash[h]; + qunlock(&c->lk); + return 0; +} + +VtBlock* +patchblockcopy(VtBlock *b) +{ + VtBlock *bb; + + vtpatchncopy++; + bb = vtpatchallocblock(((VtPatch *)b->c), b->type); + if(bb == nil){ + patchblockput(b); + return nil; + } + memmove(bb->data, b->data, ((VtPatch *)b->c)->blocksize); + patchblockput(b); + bb->pc = getcallerpc(&b); + return bb; +} + + diff -r 25e87a450f81 -r 5e8d97b0d457 sys/src/cmd/vtvacfs/stdinc.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sys/src/cmd/vtvacfs/stdinc.h Wed Sep 28 10:34:58 2011 -0700 @@ -0,0 +1,8 @@ +#include +#include +#include +#include +#include +#include +#include +#include diff -r 25e87a450f81 -r 5e8d97b0d457 sys/src/cmd/vtvacfs/vac.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sys/src/cmd/vtvacfs/vac.h Wed Sep 28 10:34:58 2011 -0700 @@ -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 -r 25e87a450f81 -r 5e8d97b0d457 sys/src/cmd/vtvacfs/vacfs.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sys/src/cmd/vtvacfs/vacfs.c Wed Sep 28 10:34:58 2011 -0700 @@ -0,0 +1,718 @@ +#include "stdinc.h" +#include +#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 +}; + +static Fid *fids; +static uchar *data; +static int mfd[2]; +static int srvfd = -1; +static char *user; +static uchar mdata[8192+IOHDRSZ]; +static int messagesize = sizeof mdata; +static Fcall rhdr; +static Fcall thdr; +static VacFs *fs; +static VtConn *conn; +static int noperm; +static int dotu; +static char *defmnt; + +static Fid * newfid(int); +static void error(char*); +static void io(void); +static void vacshutdown(void); +static void usage(void); +static int perm(Fid*, int); +static int permf(VacFile*, char*, int); +static ulong getl(void *p); +static void init(char*, char*, long, int); +static int vacdirread(Fid *f, char *p, long off, long cnt); +static int vacstat(VacFile *parent, VacDir *vd, uchar *p, int np); +static void srv(void* a); + + +static 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*); + +static char *(*fcalls[Tmax])(Fid*); + +static 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; +} + +static char Eperm[] = "permission denied"; +static char Enotdir[] = "not a directory"; +static char Enotexist[] = "file does not exist"; +static char Einuse[] = "file in use"; +static char Eexist[] = "file exists"; +static char Enotowner[] = "not owner"; +static char Eisopen[] = "file already open for I/O"; +static char Excl[] = "exclusive use file already open"; +static char Ename[] = "illegal name"; +static char Erdonly[] = "read only file system"; +static char Eio[] = "i/o error"; +static char Eempty[] = "directory is not empty"; +static char Emode[] = "illegal mode"; + +static int dflag; + +static void +notifyf(void *a, char *s) +{ + USED(a); + if(strncmp(s, "interrupt", 9) == 0) + noted(NCONT); + noted(NDFLT); +} + +void +vacfs(void) +{ + char *defsrv, *srvname; + int p[2], fd; + char *host = nil; + long ncache; + + defsrv = "mmventi"; + + initfcalls(); + + notify(notifyf); + user = getuser(); + + 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); + + close(p[0]); + 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); + + threadexits(0); +} + +static void +srv(void *a) +{ + USED(a); + io(); + vacshutdown(); +} + +static void +usage(void) +{ + fprint(2, "usage: %s [-sd] [-h host] [-c ncache] [-m mountpoint] vacfile\n", argv0); + threadexitsall("usage"); +} + +static 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; +} + +static char* +rflush(Fid *f) +{ + USED(f); + return 0; +} + +static char* +rauth(Fid *f) +{ + USED(f); + return vtstrdup("vacfs: authentication not required"); +} + +static char* +rattach(Fid *f) +{ + /* no authentication for the moment */ + /* since the score is in the aname :-) */ + 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; +} + +static 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; +} + +static 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; +} + +static 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; +} + +static 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; +} + +static char* +rwrite(Fid *f) +{ + USED(f); + return vtstrdup(Erdonly); +} + +static 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; +} + +static 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); +} + +static 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; +} + +static char * +rwstat(Fid *f) +{ + if(!f->busy) + return vtstrdup(Enotexist); + return vtstrdup(Erdonly); +} + +static 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; +} + +static 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; +} + +static 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; +} + +static 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"); + } +} + +static 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 +static perm(Fid *f, int p) +{ + return permf(f->file, f->user, p); +} + +static void +vacshutdown(void) +{ + Fid *f; + + for(f = fids; f; f = f->next) { + if(!f->busy) + continue; + rclunk(f); + } + + vacfsclose(fs); + vthangup(conn); +} + diff -r 25e87a450f81 -r 5e8d97b0d457 sys/src/cmd/vtvacfs/vtvacfs.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sys/src/cmd/vtvacfs/vtvacfs.c Wed Sep 28 10:34:58 2011 -0700 @@ -0,0 +1,448 @@ +#ifdef PLAN9PORT +#include +#include +#endif +#include "/sys/src/cmd/venti/srv/stdinc.h" +#include +#include "/sys/src/cmd/venti/srv/dat.h" +#include "/sys/src/cmd/venti/srv/fns.h" + +#include "/sys/src/cmd/venti/srv/whack.h" +#define HOWMANY(x, y) (((x)+((y)-1))/(y)) +#define ROUNDUP(x, y) (HOWMANY((x), (y))*(y)) +enum { + GiB = 1ULL << 32 +}; + +struct map +{ + unsigned char score[VtScoreSize]; + u8int *data; + int len; + uchar blocktype; +}; + +int debug; +int nofork; +int mainstacksize = 256*1024; + +static struct map *maps = nil; +static int hashb, maxmap; +static u8int *mmventidata, *mmventidatabase; +static int ventifd = -1; + +VtSrv *ventisrv; + +static void ventiserver(void*); + +static void +datasha1(u8int *p, unsigned long len, uchar digest[VtScoreSize]) +{ + DigestState ds; + memset(&ds, 0, sizeof ds); + sha1(p, len, digest, &ds); +} + +static void +syncentry(struct map *m) +{ + u8int len[4]; + uvlong offset = m->data - mmventidatabase; + len[0] = m->len>>24; + len[1] = m->len>>16; + len[2] = m->len>>8; + len[3] = m->len; + //fprint(2,"Write len %d:%d:%d:%d at %lld\n", len[0], len[1], len[2], len[3], offset); + if (pwrite(ventifd, len, 4, offset) < sizeof(len)) + sysfatal("Write entry len at %lld: %r", offset); + if (pwrite(ventifd, m->data, m->len, offset+4) < m->len) + sysfatal("Write (%p, %d) bytes of data at %lld: %r", m->data, m->len, offset); +} + +int +installentry(u8int *data, ulong len, u8int *score, uchar blocktype) +{ + int ix, initial; + datasha1(data, len, score); + initial = ix = hashbits(score, hashb); + //fprint(2, "installentry: ix %d, V %V, maps[].data %p\n", ix, score, maps[ix].data); + while (maps[ix].data) { + ix++; + if (ix > maxmap) + ix = 0; + if (ix == initial) + sysfatal("OOPS -- no more map slots"); + } + maps[ix].data = data; + //fprint(2, "set map[%d] to %p\n", ix, mmventidata); + maps[ix].len = len; + scorecp(maps[ix].score, score); + maps[ix].blocktype = blocktype; + return ix; +} +static void +reload(void) +{ + u8int score[VtScoreSize]; + u8int *len = mmventidata; + ulong entrylen; + int i; + int entrycount = 0; + + while (1) { + if (read(ventifd, len, 4*sizeof(*len)) < 4*sizeof(*len)) + sysfatal("reload read entry len: %r"); + //fprint(2, "%d:%d:%d:%d\n", len[0], len[1], len[2], len[3]); + for(i = entrylen = 0; i < 4; i++) { + entrylen <<= 8; + entrylen |= len[i]; + } + //fprint(2, "Entry len %ld\n", entrylen); + if (entrylen == 0) + break; + mmventidata += 4; + if (read(ventifd, mmventidata, entrylen) < entrylen) + sysfatal("reload read (%p, %ld) bytes of data: %r", mmventidata, entrylen); + installentry(mmventidata, entrylen, score, 0); + mmventidata += entrylen; + entrycount++; + } + fprint(2, "Reloaded %d entries", entrycount); + +} + +unsigned long log2(unsigned long x) +{ + unsigned long i = 1ULL << (sizeof(x)* 8 - 1ULL); + unsigned long pow = sizeof(x) * 8 - 1ULL; + + if (! x) { + return -1; + } + for(; i > x; i >>= 1, pow--) + ; + + return pow; +} +void +usage(void) +{ + fprint(2, "usage: venti [-Ldrsw] [-a ventiaddr] [-c config] " +"[-h httpaddr] [-m %%mem] [-B blockcachesize] [-C cachesize] [-I icachesize] " +"[-W webroot]file\n"); + threadexitsall("usage"); +} + +void +mminit(char *file, int mode) +{ + Dir *d; + uintptr va; + void *p, *np; + int hashsize; /* make it a power of two -- see why later */ + + ventifd = open(file, mode); + if (ventifd < 0) + sysfatal("Can't open %s: %r\n", file); + d = dirfstat(ventifd); + if (! d) + sysfatal("Can't stat %s: %r", file); + + /* allocate: size for the file, 1/32 that size for the map, and + * start it at the 1 GB boundary, please. + */ + /* get top of heap */ + p = segbrk(0, 0); + va = (uintptr)p; + /* no non-nix systems we just usr sbrk and only have little pages */ + hashsize = d->length/32; + maxmap = hashsize / sizeof(*maps); + hashb = log2(maxmap); + if (va == (uintptr)-1) { + p = sbrk(0); + va = (uintptr)p; + maps = (void *)va; + va += hashsize; + mmventidatabase = mmventidata = (void *)va; + va += d->length; + va = ROUNDUP((va), 4096); + if (brk((void *)va) < 0) + sysfatal("brk to %#p failed\n", (void *)va); + } else { + va = ROUNDUP((va), 1ULL*GiB); + maps = (void *)va; + va += hashsize; + mmventidatabase = mmventidata = (void *)va; + va += d->length; + va = ROUNDUP((va), 1ULL*GiB); + segbrk(0, (void *)va); + } + fprint(2, "mmventidatabase is %#p\n", mmventidatabase); + + fprint(2, "File size %lld, hashsize %d, maps %#p, data %#p\n", d->length, + hashsize, maps, mmventidata); + /* morecore */ + np=(void*)va; + segbrk(p, np); + + reload(); +} + +struct map *findscore(u8int *score) +{ + int ix; + ix = hashbits(score, hashb); + //fprint(2, "find for %V is %d, maps[].data %p\n", score, ix, maps[ix].data); + while (maps[ix].data) { + //fprint(2, "Check: %d, %V\n", ix, maps[ix].score); + //fprint(2, "scorecmp(%V,%V, %d\n", maps[ix].score, score,scorecmp(maps[ix].score, score) ); + if (scorecmp(maps[ix].score, score) == 0) + return &maps[ix]; + ix++; + } + return nil; +} + +int +getdata(u8int *score, u8int *data, u8int len, u8int blocktype) +{ + struct map *m = findscore(score); + if (! m) + return -1; + if (len > m->len) + len = m->len; + memmove(data, m->data, len); + if (m->blocktype != blocktype) + fprint(2, "Mismatched blocktype! now what?\n"); + return len; +} + + +int +putscore(Packet *p, u8int *score, uchar blocktype) +{ + int ix, len; + + /* yes, it's a little weird. But it will do for now. */ + /* leave room for the length. This will make sure we still work for the + * mmap'ed version. + */ + mmventidata += 4; + + len = packetsize(p); + packetconsume(p, mmventidata,len); + ix = installentry(mmventidata, len, score, blocktype); + mmventidata += len; + + //fprint(2, "mmventidata now %p\n", mmventidata); + syncentry(&maps[ix]); + return maps[ix].len; +} + +int +putdata(u8int *score, u8int *data, int len, uchar blocktype) +{ + int ix; + /* yes, it's a little weird. But it will do for now. */ + /* leave room for the length. This will make sure we still work for the + * mmap'ed version. + */ + mmventidata += 4; + /* could also use ainc here */ + memmove(mmventidata, data, len); + ix = installentry(mmventidata, len, score, blocktype); + mmventidata += len; + + //fprint(2, "mmventidata now %p\n", mmventidata); + syncentry(&maps[ix]); + return maps[ix].len; +} + + + +void +threadmain(int argc, char *argv[]) +{ + void vacfs(void); + char *haddr, *vaddr, *webroot, *file; + + traceinit(); + threadsetname("main"); + vaddr = nil; + haddr = "tcp!*!9000"; + webroot = nil; + ARGBEGIN{ + case 'a': + vaddr = EARGF(usage()); + break; + case 'D': + settrace(EARGF(usage())); + break; + case 'd': + debug = 1; + nofork = 1; + break; + case 'h': + haddr = EARGF(usage()); + break; + case 'L': + ventilogging = 1; + break; + case 'r': + readonly = 1; + break; + case 's': + nofork = 1; + break; + case 'W': + webroot = EARGF(usage()); + break; + default: + usage(); + }ARGEND + + if(argc < 1) + usage(); + + file = argv[0]; + + if(!nofork) + rfork(RFNOTEG); + +#ifdef PLAN9PORT + { + /* sigh - needed to avoid signals when writing to hungup networks */ + struct sigaction sa; + memset(&sa, 0, sizeof sa); + sa.sa_handler = SIG_IGN; + sigaction(SIGPIPE, &sa, nil); + } +#endif + + ventifmtinstall(); + trace(TraceQuiet, "venti started"); + fprint(2, "%T venti: "); + + statsinit(); + mminit(file, readonly ? OREAD : ORDWR); + + /* + * default other configuration-file parameters + */ + if(vaddr == nil) + vaddr = "tcp!*!venti"; + + if(haddr){ + fprint(2, "httpd %s...", haddr); + if(httpdinit(haddr, webroot) < 0) + fprint(2, "warning: can't start http server: %r"); + } + fprint(2, "init..."); + + + fprint(2, "announce %s...", vaddr); + ventisrv = vtlisten(vaddr); + if(ventisrv == nil) + sysfatal("can't announce %s: %r", vaddr); + + fprint(2, "serving.\n"); + if(nofork) + ventiserver(nil); + else + vtproc(ventiserver, nil); + + vacfs(); + threadexits(nil); +} + +static void +vtrerror(VtReq *r, char *error) +{ + r->rx.msgtype = VtRerror; + r->rx.error = estrdup(error); +} + +static void +ventiserver(void *v) +{ + Packet *p; + VtReq *r; + char err[ERRMAX]; + uint ms; + int ok; + struct map *m; + + USED(v); + threadsetname("ventiserver"); + trace(TraceWork, "start"); + while((r = vtgetreq(ventisrv)) != nil){ + trace(TraceWork, "finish"); + trace(TraceWork, "start request %F", &r->tx); + trace(TraceRpc, "<- %F", &r->tx); + r->rx.msgtype = r->tx.msgtype+1; + addstat(StatRpcTotal, 1); + if(0) print("req (arenas[0]=%p sects[0]=%p) %F\n", + mainindex->arenas[0], mainindex->sects[0], &r->tx); + switch(r->tx.msgtype){ + default: + vtrerror(r, "unknown request"); + break; + case VtTread: + ms = msec(); + m = findscore(r->tx.score); + //fprint(2, "findscore says %p\n", m); + if (m) { + //fprint(2, "Found the block\n"); + r->rx.data = packetalloc(); + packetappend(r->rx.data, m->data, m->len); + r->rx.blocktype = m->blocktype; + } else { + r->rx.data = nil; + } + ms = msec() - ms; + addstat2(StatRpcRead, 1, StatRpcReadTime, ms); + if(r->rx.data == nil){ + addstat(StatRpcReadFail, 1); + rerrstr(err, sizeof err); + vtrerror(r, err); + }else{ + addstat(StatRpcReadBytes, packetsize(r->rx.data)); + addstat(StatRpcReadOk, 1); + addstat2(StatRpcReadCached, 1, StatRpcReadCachedTime, ms); + r->rx.msgtype = VtRread; + r->rx.error = nil; + } + break; + case VtTwrite: + if(readonly){ + vtrerror(r, "read only"); + break; + } + p = r->tx.data; + r->tx.data = nil; + addstat(StatRpcWriteBytes, packetsize(p)); + ms = msec(); + /* todo: check for overflow of file */ + ok = putscore(p, r->rx.score, r->tx.blocktype); + ms = msec() - ms; + addstat2(StatRpcWrite, 1, StatRpcWriteTime, ms); + + if(ok < 0){ + addstat(StatRpcWriteFail, 1); + rerrstr(err, sizeof err); + vtrerror(r, err); + } else { + r->rx.msgtype = VtRwrite; + r->rx.error = nil; + } + break; + case VtTsync: + /* nonsense. Write synchronously. For now. Later, have a helper thread and VtTsync will just write a Fence to it and wait for it to come back. */ + break; + } + trace(TraceRpc, "-> %F", &r->rx); + vtrespond(r); + trace(TraceWork, "start"); + } + threadexitsall(0); +}